Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,23 @@ func Runs(d *reader.Document, page int) ([]Run, error) {
}

// walk reads a page's content stream once, keeping what it finds.
//
// A page whose content decoded only part of the way is read as far as it went:
// 263 streams in 212 of the 1 633 real forms in the corpus cannot be decoded
// cleanly, and half a page of text is worth more to somebody searching than
// none of it. A page that decoded no bytes at all is a different answer and
// stays an error — a conversion that silently produces an empty document from
// an unreadable page is worse than one that says it could not read it, and
// go-pdfkit/latex relies on being told.
func walk(d *reader.Document, page int) (*extractor, error) {
content, err := d.PageContent(page)
dec, err := d.PageContentDecoded(page)
if err != nil {
return nil, err
}
if len(dec.Data) == 0 && dec.Cause != nil {
return nil, dec.Cause
}
content := dec.Data
p, _ := d.Page(page)
resources, _ := d.GetDict(p, "Resources")
e := &extractor{doc: d, fonts: map[int]*pdffont.Font{}}
Expand Down
76 changes: 76 additions & 0 deletions extract_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package extract

import (
"bytes"
"compress/zlib"
"strings"
"testing"

Expand Down Expand Up @@ -390,3 +392,77 @@ func TestAWordGapInAFontWithNoSpaceOfItsOwn(t *testing.T) {
t.Errorf("the page says %q", text)
}
}

// filteredPage builds a page whose content stream carries the filter and bytes
// the test names, which pageWith does not allow: it writes the content plain.
func filteredPage(t *testing.T, filter reader.Name, raw []byte) *reader.Document {
t.Helper()
w := reader.NewWriter("1.7")
pagesRef := w.Reserve()
page := w.Add(reader.Dict{
"Type": reader.Name("Page"), "Parent": pagesRef,
"MediaBox": reader.Array{reader.Integer(0), reader.Integer(0),
reader.Integer(200), reader.Integer(200)},
"Contents": w.Add(&reader.Stream{
Dict: reader.Dict{"Filter": filter}, Raw: raw}),
"Resources": reader.Dict{"Font": reader.Dict{"F1": w.Add(simpleFont(w))}},
})
w.Put(pagesRef, reader.Dict{"Type": reader.Name("Pages"),
"Kids": reader.Array{page}, "Count": reader.Integer(1)})
out, err := w.Finish(reader.Dict{"Root": w.Add(reader.Dict{
"Type": reader.Name("Catalog"), "Pages": pagesRef})})
if err != nil {
t.Fatal(err)
}
d, err := reader.Open(out)
if err != nil {
t.Fatal(err)
}
return d
}

func TestAPageThatDecodedPartOfTheWayIsReadThatFar(t *testing.T) {
// reader v0.6.0 keeps what a broken filter chain did produce. A page whose
// content inflates part of the way is read that far: 263 streams in 212 of
// the 1 633 real forms cannot be decoded cleanly, and half a page of text
// is worth more to somebody searching than none of it.
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
text := "BT /F1 12 Tf 10 100 Td (hello) Tj ET "
for i := 0; i < 300; i++ {
text += "BT /F1 12 Tf 10 100 Td (padding) Tj ET "
}
if _, err := zw.Write([]byte(text)); err != nil {
t.Fatal(err)
}
if err := zw.Close(); err != nil {
t.Fatal(err)
}
// The whole stream is 108 bytes and holds 301 pieces of text. Keeping 80
// of them inflates part of the way.
whole, err := Runs(filteredPage(t, "FlateDecode", buf.Bytes()), 1)
if err != nil {
t.Fatal(err)
}
runs, err := Runs(filteredPage(t, "FlateDecode", buf.Bytes()[:80]), 1)
if err != nil {
t.Fatalf("a page that decoded part of the way came back as an error: %v", err)
}
switch {
case len(runs) == 0:
t.Error("nothing was read from a page that decoded part of the way")
case len(runs) >= len(whole):
t.Errorf("the cut stream gave %d runs and the whole one %d: nothing was cut",
len(runs), len(whole))
}
}

func TestAPageThatDecodedNothingIsStillAnError(t *testing.T) {
// The other side of the same rule, and the one go-pdfkit/latex relies on:
// a page whose content yields no bytes at all is reported rather than
// turned into an empty document.
d := filteredPage(t, "FlateDecode", []byte("not compressed at all"))
if _, err := Runs(d, 1); err == nil {
t.Error("a page whose content will not decode read without error")
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.26.4
require (
github.com/go-opentype/opentype v0.9.0
github.com/go-pdfkit/pdffont v0.3.0
github.com/go-pdfkit/reader v0.5.0
github.com/go-pdfkit/reader v0.6.0
)

require github.com/go-opentype/fonts v0.8.0
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ github.com/go-opentype/opentype v0.9.0 h1:GFgcJ3nwTDp4NJr5O+Paw7lhZx5Jv/R+noZwvh
github.com/go-opentype/opentype v0.9.0/go.mod h1:AOixevJf7XQaH7+WG+OMIOZEbYPXfMqklVk26Y6YTUU=
github.com/go-pdfkit/pdffont v0.3.0 h1:G5DKcAmsZJ0e17QhSrcUaL7PKEXKjUx4P/iGMl7bAbI=
github.com/go-pdfkit/pdffont v0.3.0/go.mod h1:bfmNLna1l1CljNX/Utg55YzFylovfmI7sJnvgA3bzKI=
github.com/go-pdfkit/reader v0.5.0 h1:DaJ5C6eKXPRG9Cdu+olA/0KLmsQgyHEC1coteQQuBxU=
github.com/go-pdfkit/reader v0.5.0/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8=
github.com/go-pdfkit/reader v0.6.0 h1:KAabNOYUcTlZlNBTbG9bEhWP1NiZhjuhzUdavdTdfes=
github.com/go-pdfkit/reader v0.6.0/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8=
Loading