From ebc614975e281873ae88f4cd9e21ff238102774b Mon Sep 17 00:00:00 2001 From: Dave Pifke Date: Tue, 6 Jun 2017 19:40:44 -0700 Subject: [PATCH 1/3] Fetch page from outline I've implemented the ability to get the page corresponding to an entry in the outline (aka table of contents). This is admittedly quick and dirty. It works on the handful of PDF files I tried it with, but I haven't actively sought out corner cases. --- page.go | 19 +++++++++++++++++++ read.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/page.go b/page.go index 9c7d6881..19d66366 100644 --- a/page.go +++ b/page.go @@ -647,6 +647,7 @@ func (x TextHorizontal) Less(i, j int) bool { type Outline struct { Title string // title for this element Child []Outline // child elements + dest Value // destination, which will need to be resolved } // Outline returns the document outline. @@ -659,8 +660,26 @@ func (r *Reader) Outline() Outline { func buildOutline(entry Value) Outline { var x Outline x.Title = entry.Key("Title").Text() + x.dest = entry.Key("Dest") for child := entry.Key("First"); child.Kind() == Dict; child = child.Key("Next") { x.Child = append(x.Child, buildOutline(child)) } return x } + +// OutlinePage returns the page of the document corresponding to an outline +// (aka table of contents) entry. +func (r *Reader) OutlinePage(o Outline) Page { + dests := r.Trailer().Key("Root").Key("Names").Key("Dests") + if dests.IsNull() { + dests = r.Trailer().Key("Root").Key("Dests") // PDF 1.1 + } + + if o.dest.Kind() == String { + dest := dests.nameTreeLookup(o.dest.String()).Key("D").Index(0) + if dest.Key("Type").Name() == "Page" { + return Page{dest} + } + } // non-named destinations are not supported + return Page{} +} diff --git a/read.go b/read.go index eb8b9aab..628074d6 100644 --- a/read.go +++ b/read.go @@ -73,6 +73,7 @@ import ( "os" "sort" "strconv" + "strings" ) // A Reader is a single PDF file open for reading. @@ -664,6 +665,41 @@ func (v Value) Key(key string) Value { return v.r.resolve(v.ptr, x[name(key)]) } +// nameTreeLookup looks up a value from a name tree. Name trees are similar +// to dictionaries, so this is analogous to Key(). See 7.9.6 of the PDF seec. +func (v Value) nameTreeLookup(key string) Value { +NTSearch: + for { + limits := v.Key("Limits") + if !limits.IsNull() && (strings.Compare(key, limits.Index(0).String()) == -1 || strings.Compare(key, limits.Index(1).String()) == 1) { + break // key is outside stated limits + } + + names := v.Key("Names") + if !names.IsNull() { + for i := 0; i < names.Len(); i += 2 { + if names.Index(i).String() == key { + return names.Index(i + 1) + } + } + break // name not found + } + + kids := v.Key("Kids") + for i := 0; i < kids.Len(); i++ { + kid := kids.Index(i) + limits := kid.Key("Limits") + if !limits.IsNull() && strings.Compare(key, limits.Index(0).String()) >= 0 && strings.Compare(key, limits.Index(1).String()) <= 0 { + v = kid + continue NTSearch + } + } + break // key is not within any kid's limits + } + + return Value{} +} + // Keys returns a sorted list of the keys in the dictionary v. // If v is a stream, Keys applies to the stream's header dictionary. // If v.Kind() != Dict and v.Kind() != Stream, Keys returns nil. From 75b121929a4b4ff0e5ce81d52e2b75688edeac5c Mon Sep 17 00:00:00 2001 From: Dave Pifke Date: Tue, 20 Jun 2017 14:12:50 -0700 Subject: [PATCH 2/3] Add Page.Next() and Page.Prev() functions This commit adds support for finding the next or previous pages in the document. This is especially useful when the page number isn't known, e.g. because it was reached via the outline. --- lex.go | 5 +++++ page.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lex.go b/lex.go index ee73fd92..fe82c1c3 100644 --- a/lex.go +++ b/lex.go @@ -404,6 +404,11 @@ type objptr struct { gen uint16 } +// Equal returns true if two object references point to the same object. +func (a objptr) Equal(b objptr) bool { + return a.id == b.id +} + type objdef struct { ptr objptr obj object diff --git a/page.go b/page.go index 19d66366..4282ceeb 100644 --- a/page.go +++ b/page.go @@ -50,6 +50,44 @@ Search: return Page{} } +func (p Page) findAdjacent(direction int) Page { + lookingFor := p.V.ptr + found := false + pages := p.V.Key("Parent") +FindNext: + for pages.Key("Type").Name() == "Pages" { + var kid Value + kids := pages.Key("Kids") + for i := 0; i < kids.Len(); i++ { + if direction == 1 { + kid = kids.Index(i) + } else { + kid = kids.Index(kids.Len() - i - 1) + } + if found { + if kid.Key("Type").Name() == "Page" { + return Page{kid} + } else if kid.Key("Type").Name() == "Pages" { + pages = kid + continue FindNext + } + } else if kid.ptr.Equal(lookingFor) { + found = true + } + } + lookingFor = pages.ptr + found = false + pages = pages.Key("Parent") + } + return Page{} +} + +// Next returns the next page in the document. +func (p Page) Next() Page { return p.findAdjacent(1) } + +// Prev returns the previous page in the document. +func (p Page) Prev() Page { return p.findAdjacent(-1) } + // NumPage returns the number of pages in the PDF file. func (r *Reader) NumPage() int { return int(r.Trailer().Key("Root").Key("Pages").Key("Count").Int64()) From 4528a8751453ab63501a51fed549b81f7ab0cb32 Mon Sep 17 00:00:00 2001 From: Dave Pifke Date: Wed, 21 Jun 2017 15:07:58 -0700 Subject: [PATCH 3/3] Make Page() a method of Outline I didn't realize I already had a handy reference to *pdf.Reader present in Outline (by way of Value). This means Page() can be a method of Outline, rather than a method of *pdf.Reader, which seems to me a more logical place for it. Best to change this now, before anyone else might be using this code. --- page.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/page.go b/page.go index 4282ceeb..66e8b730 100644 --- a/page.go +++ b/page.go @@ -705,12 +705,13 @@ func buildOutline(entry Value) Outline { return x } -// OutlinePage returns the page of the document corresponding to an outline -// (aka table of contents) entry. -func (r *Reader) OutlinePage(o Outline) Page { - dests := r.Trailer().Key("Root").Key("Names").Key("Dests") +// Page returns the page of the document corresponding to an outline (aka +// table of contents) entry. +func (o Outline) Page() Page { + root := o.dest.r.Trailer().Key("Root") + dests := root.Key("Names").Key("Dests") if dests.IsNull() { - dests = r.Trailer().Key("Root").Key("Dests") // PDF 1.1 + dests = root.Key("Dests") // PDF 1.1 } if o.dest.Kind() == String {