diff --git a/lex.go b/lex.go index ee73fd9..fe82c1c 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 9c7d688..66e8b73 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()) @@ -647,6 +685,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 +698,27 @@ 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 } + +// 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 = 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 eb8b9aa..628074d 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.