package officekit import ( "bytes" "compress/zlib" "strings" "testing" ) // buildMinimalPdf wraps one FlateDecode-compressed content stream in // just enough surrounding PDF syntax for ReadPdfText's regex-based // scanner to find it — a %PDF- header and one `N 0 obj << ... /Filter // /FlateDecode >> stream ... endstream endobj` object. Not a complete, // schema-valid PDF (no xref table, no page tree), but ReadPdfText // never looks at either of those, so this is a faithful enough fixture // for exercising it without needing a real binary PDF checked into // the repo. func buildMinimalPdf(t *testing.T, content string) []byte { t.Helper() var compressed bytes.Buffer zw := zlib.NewWriter(&compressed) if _, err := zw.Write([]byte(content)); err != nil { t.Fatalf("zlib write: %v", err) } if err := zw.Close(); err != nil { t.Fatalf("zlib close: %v", err) } var buf bytes.Buffer buf.WriteString("%PDF-1.4\n") buf.WriteString("4 0 obj\n<< /Length 999 /Filter /FlateDecode >>\nstream\n") buf.Write(compressed.Bytes()) buf.WriteString("\nendstream\nendobj\n%%EOF") return buf.Bytes() } func TestReadPdfTextBasic(t *testing.T) { data := buildMinimalPdf(t, "BT /F1 12 Tf 72 712 Td (Hello World) Tj ET") text, err := ReadPdfText(data) if err != nil { t.Fatalf("ReadPdfText: %v", err) } if text != "Hello World" { t.Errorf("got %q, want %q", text, "Hello World") } } func TestReadPdfTextLineBreaksAndEscapes(t *testing.T) { content := "BT /F1 12 Tf 72 712 Td (Line one \\(escaped\\)) Tj\n72 700 Td (Line two) Tj ET" data := buildMinimalPdf(t, content) text, err := ReadPdfText(data) if err != nil { t.Fatalf("ReadPdfText: %v", err) } if !strings.Contains(text, "Line one (escaped)") { t.Errorf("expected unescaped parens in %q", text) } if !strings.Contains(text, "Line two") { t.Errorf("expected second Td'd line's text in %q", text) } if !strings.Contains(text, "\n") { t.Errorf("expected a newline between Td-separated text runs, got %q", text) } } func TestReadPdfTextTJArrayKerning(t *testing.T) { data := buildMinimalPdf(t, "BT /F1 12 Tf 72 712 Td [(Hel)-20(lo)20( world)] TJ ET") text, err := ReadPdfText(data) if err != nil { t.Fatalf("ReadPdfText: %v", err) } if text != "Hello world" { t.Errorf("got %q, want %q (TJ kerning numbers must not become spaces)", text, "Hello world") } } func TestReadPdfTextRejectsNonPdf(t *testing.T) { if _, err := ReadPdfText([]byte("just some ordinary bytes, not a PDF")); err == nil { t.Error("expected an error for input with no %PDF- header") } } func TestReadPdfTextNoShownTextOperators(t *testing.T) { // A validly-structured FlateDecode stream (so the inflate step // succeeds) with no Tj/TJ/'/" calls at all — e.g. a pure image draw. data := buildMinimalPdf(t, "q 100 0 0 100 0 0 cm /Im0 Do Q") if _, err := ReadPdfText(data); err == nil { t.Error("expected an error when the stream has no text-showing operators") } }