unipdf/pdf/outlines.go

270 lines
6.3 KiB
Go
Raw Normal View History

2016-08-22 08:46:18 +00:00
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package pdf
import (
2016-08-16 09:36:24 +00:00
"fmt"
"github.com/unidoc/unidoc/common"
)
// Convertible to a PDF object interface.
type PdfObjectConverter interface {
ToPdfObject(updateIfExists bool) PdfObject
}
// Object cache.
var PdfObjectConverterCache map[PdfObjectConverter]PdfObject = map[PdfObjectConverter]PdfObject{}
2016-08-16 09:36:24 +00:00
type PdfOutlineTreeNode struct {
context interface{} // Allow accessing outer structure.
First *PdfOutlineTreeNode
Last *PdfOutlineTreeNode
2016-08-16 09:36:24 +00:00
}
// PDF outline dictionary (Table 152 - p. 376).
type PdfOutline struct {
2016-08-16 09:36:24 +00:00
PdfOutlineTreeNode
Count *int64
}
// Pdf outline item dictionary (Table 153 - pp. 376 - 377).
type PdfOutlineItem struct {
2016-08-16 09:36:24 +00:00
PdfOutlineTreeNode
Title *PdfObjectString
Parent *PdfOutlineTreeNode
Prev *PdfOutlineTreeNode
Next *PdfOutlineTreeNode
Count *int64
Dest PdfObject
A PdfObject
SE PdfObject
C PdfObject
F PdfObject
}
func NewPdfOutlineTree() *PdfOutline {
outlineTree := PdfOutline{}
outlineTree.context = &outlineTree
return &outlineTree
}
func NewOutlineBookmark(title string, page *PdfIndirectObject) *PdfOutlineItem {
bookmark := PdfOutlineItem{}
bookmark.context = &bookmark
bookmark.Title = MakeString(title)
destArray := PdfObjectArray{}
destArray = append(destArray, page)
destArray = append(destArray, MakeName("Fit"))
bookmark.Dest = &destArray
return &bookmark
}
2016-08-16 09:36:24 +00:00
// Does not traverse the tree.
func newPdfOutlineFromDict(dict *PdfObjectDictionary) (*PdfOutline, error) {
outline := PdfOutline{}
outline.context = &outline
2016-08-16 09:36:24 +00:00
if obj, hasType := (*dict)["Type"]; hasType {
typeVal, ok := obj.(*PdfObjectName)
if ok {
if *typeVal != "Outlines" {
common.Log.Error("Type != Outlines (%s)", *typeVal)
// Should be "Outlines" if there, but some files have other types
// Log as an error but do not quit.
// Might be a good idea to log this kind of deviation from the standard separately.
2016-08-16 09:36:24 +00:00
}
}
}
if obj, hasCount := (*dict)["Count"]; hasCount {
// This should always be an integer, but in a few cases has been a float.
count, err := getNumberAsInt64(obj)
if err != nil {
return nil, err
2016-08-16 09:36:24 +00:00
}
outline.Count = &count
}
return &outline, nil
}
// Does not traverse the tree.
2016-08-19 09:13:12 +00:00
func (this *PdfReader) newPdfOutlineItemFromDict(dict *PdfObjectDictionary) (*PdfOutlineItem, error) {
2016-08-16 09:36:24 +00:00
item := PdfOutlineItem{}
item.context = &item
2016-08-16 09:36:24 +00:00
// Title (required).
obj, hasTitle := (*dict)["Title"]
if !hasTitle {
return nil, fmt.Errorf("Missing Title from Outline Item (required)")
}
2016-08-19 09:13:12 +00:00
obj, err := this.traceToObject(obj)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
title, ok := TraceToDirectObject(obj).(*PdfObjectString)
2016-08-16 09:36:24 +00:00
if !ok {
return nil, fmt.Errorf("Title not a string (%T)", obj)
}
item.Title = title
// Count (optional).
if obj, hasCount := (*dict)["Count"]; hasCount {
countVal, ok := obj.(*PdfObjectInteger)
if !ok {
return nil, fmt.Errorf("Count not an integer (%T)", obj)
}
count := int64(*countVal)
item.Count = &count
}
2016-08-16 17:57:23 +00:00
// Other keys.
if obj, hasKey := (*dict)["Dest"]; hasKey {
2016-08-19 09:13:12 +00:00
item.Dest, err = this.traceToObject(obj)
if err != nil {
return nil, err
}
err := this.traverseObjectData(item.Dest)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj, hasKey := (*dict)["A"]; hasKey {
2016-08-19 09:13:12 +00:00
item.A, err = this.traceToObject(obj)
if err != nil {
return nil, err
}
err := this.traverseObjectData(item.A)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj, hasKey := (*dict)["SE"]; hasKey {
2016-08-19 09:13:12 +00:00
item.SE, err = this.traceToObject(obj)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj, hasKey := (*dict)["C"]; hasKey {
2016-08-19 09:13:12 +00:00
item.C, err = this.traceToObject(obj)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj, hasKey := (*dict)["F"]; hasKey {
2016-08-19 09:13:12 +00:00
item.F, err = this.traceToObject(obj)
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
2016-08-16 09:36:24 +00:00
return &item, nil
}
// Get the outer object of the tree node (Outline or OutlineItem).
func (n *PdfOutlineTreeNode) getOuter() PdfObjectConverter {
if outline, isOutline := n.context.(*PdfOutline); isOutline {
return outline
}
if outlineItem, isOutlineItem := n.context.(*PdfOutlineItem); isOutlineItem {
return outlineItem
}
common.Log.Error("Invalid outline tree node item") // Should never happen.
return nil
}
func (this *PdfOutlineTreeNode) ToPdfObject(updateIfExists bool) PdfObject {
return this.getOuter().ToPdfObject(updateIfExists)
}
// Recursively build the Outline tree PDF object.
func (this *PdfOutline) ToPdfObject(updateIfExists bool) PdfObject {
var container PdfIndirectObject
if cachedObj, isCached := PdfObjectConverterCache[this]; isCached {
if !updateIfExists {
return cachedObj
}
obj := cachedObj.(*PdfIndirectObject)
container = *obj
} else {
container = PdfIndirectObject{}
}
PdfObjectConverterCache[this] = &container
outlinesDict := PdfObjectDictionary{}
outlinesDict["Type"] = MakeName("Outlines")
if this.First != nil {
outlinesDict["First"] = this.First.ToPdfObject(false)
}
if this.Last != nil {
outlinesDict["Last"] = PdfObjectConverterCache[this.Last.getOuter()]
}
container.PdfObject = &outlinesDict
return &container
}
// Outline item.
// Recursively build the Outline tree PDF object.
func (this *PdfOutlineItem) ToPdfObject(updateIfExists bool) PdfObject {
var container PdfIndirectObject
if cachedObj, isCached := PdfObjectConverterCache[this]; isCached {
if !updateIfExists {
return cachedObj
}
obj := cachedObj.(*PdfIndirectObject)
container = *obj
} else {
container = PdfIndirectObject{}
}
PdfObjectConverterCache[this] = &container
dict := PdfObjectDictionary{}
dict["Title"] = this.Title
if this.A != nil {
dict["A"] = this.A
}
if this.C != nil {
dict["C"] = this.C
}
if this.Dest != nil {
dict["Dest"] = this.Dest
}
if this.F != nil {
dict["F"] = this.F
}
if this.Count != nil {
dict["Count"] = MakeInteger(*this.Count)
}
if this.Next != nil {
dict["Next"] = this.Next.ToPdfObject(false)
}
if this.First != nil {
dict["First"] = this.First.ToPdfObject(false)
}
if this.Prev != nil {
dict["Prev"] = PdfObjectConverterCache[this.Prev.getOuter()]
}
if this.Last != nil {
dict["Last"] = PdfObjectConverterCache[this.Last.getOuter()]
}
if this.Parent != nil {
dict["Parent"] = PdfObjectConverterCache[this.Parent.getOuter()]
}
container.PdfObject = &dict
return &container
}