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.
|
|
|
|
*/
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
package pdf
|
|
|
|
|
|
|
|
import (
|
2016-08-16 09:36:24 +00:00
|
|
|
"fmt"
|
2016-08-18 09:43:44 +00:00
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
2016-08-15 01:31:35 +00:00
|
|
|
)
|
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
// Convertible to a PDF object interface.
|
2016-08-18 09:43:44 +00:00
|
|
|
type PdfObjectConverter interface {
|
2016-08-18 14:38:49 +00:00
|
|
|
ToPdfObject(updateIfExists bool) PdfObject
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
// Object cache.
|
2016-08-18 09:43:44 +00:00
|
|
|
var PdfObjectConverterCache map[PdfObjectConverter]PdfObject = map[PdfObjectConverter]PdfObject{}
|
|
|
|
|
2016-08-16 09:36:24 +00:00
|
|
|
type PdfOutlineTreeNode struct {
|
2016-08-17 00:07:56 +00:00
|
|
|
context interface{} // Allow accessing outer structure.
|
|
|
|
First *PdfOutlineTreeNode
|
|
|
|
Last *PdfOutlineTreeNode
|
2016-08-16 09:36:24 +00:00
|
|
|
}
|
|
|
|
|
2016-08-17 16:45:38 +00:00
|
|
|
// PDF outline dictionary (Table 152 - p. 376).
|
2016-08-15 01:31:35 +00:00
|
|
|
type PdfOutline struct {
|
2016-08-16 09:36:24 +00:00
|
|
|
PdfOutlineTreeNode
|
2016-08-15 01:31:35 +00:00
|
|
|
Count *int64
|
|
|
|
}
|
|
|
|
|
2016-08-17 16:45:38 +00:00
|
|
|
// Pdf outline item dictionary (Table 153 - pp. 376 - 377).
|
2016-08-15 01:31:35 +00:00
|
|
|
type PdfOutlineItem struct {
|
2016-08-16 09:36:24 +00:00
|
|
|
PdfOutlineTreeNode
|
2016-08-18 14:38:49 +00:00
|
|
|
Title *PdfObjectString
|
|
|
|
Parent *PdfOutlineTreeNode
|
|
|
|
Prev *PdfOutlineTreeNode
|
|
|
|
Next *PdfOutlineTreeNode
|
|
|
|
Count *int64
|
|
|
|
Dest PdfObject
|
|
|
|
A PdfObject
|
|
|
|
SE PdfObject
|
|
|
|
C PdfObject
|
|
|
|
F PdfObject
|
2016-08-15 01:31:35 +00:00
|
|
|
}
|
|
|
|
|
2016-08-18 09:43:44 +00:00
|
|
|
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{}
|
2016-08-17 00:07:56 +00:00
|
|
|
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" {
|
2016-08-19 11:34:55 +00:00
|
|
|
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 {
|
2016-08-19 11:34:55 +00:00
|
|
|
// 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{}
|
2016-08-17 00:07:56 +00:00
|
|
|
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
|
|
|
|
}
|
2016-08-18 09:43:44 +00:00
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
// 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
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
2016-08-18 14:38:49 +00:00
|
|
|
if outlineItem, isOutlineItem := n.context.(*PdfOutlineItem); isOutlineItem {
|
|
|
|
return outlineItem
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
common.Log.Error("Invalid outline tree node item") // Should never happen.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
func (this *PdfOutlineTreeNode) ToPdfObject(updateIfExists bool) PdfObject {
|
|
|
|
return this.getOuter().ToPdfObject(updateIfExists)
|
|
|
|
}
|
|
|
|
|
2016-08-18 09:43:44 +00:00
|
|
|
// Recursively build the Outline tree PDF object.
|
2016-08-18 14:38:49 +00:00
|
|
|
func (this *PdfOutline) ToPdfObject(updateIfExists bool) PdfObject {
|
|
|
|
var container PdfIndirectObject
|
2016-08-18 09:43:44 +00:00
|
|
|
if cachedObj, isCached := PdfObjectConverterCache[this]; isCached {
|
2016-08-18 14:38:49 +00:00
|
|
|
if !updateIfExists {
|
|
|
|
return cachedObj
|
|
|
|
}
|
|
|
|
obj := cachedObj.(*PdfIndirectObject)
|
|
|
|
container = *obj
|
|
|
|
} else {
|
|
|
|
container = PdfIndirectObject{}
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
2016-08-18 14:38:49 +00:00
|
|
|
PdfObjectConverterCache[this] = &container
|
2016-08-18 09:43:44 +00:00
|
|
|
|
|
|
|
outlinesDict := PdfObjectDictionary{}
|
2016-08-18 14:38:49 +00:00
|
|
|
outlinesDict["Type"] = MakeName("Outlines")
|
2016-08-18 09:43:44 +00:00
|
|
|
|
|
|
|
if this.First != nil {
|
2016-08-18 14:38:49 +00:00
|
|
|
outlinesDict["First"] = this.First.ToPdfObject(false)
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if this.Last != nil {
|
2016-08-18 14:38:49 +00:00
|
|
|
outlinesDict["Last"] = PdfObjectConverterCache[this.Last.getOuter()]
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
container.PdfObject = &outlinesDict
|
2016-08-18 09:43:44 +00:00
|
|
|
|
2016-08-18 14:38:49 +00:00
|
|
|
return &container
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Outline item.
|
|
|
|
// Recursively build the Outline tree PDF object.
|
2016-08-18 14:38:49 +00:00
|
|
|
func (this *PdfOutlineItem) ToPdfObject(updateIfExists bool) PdfObject {
|
|
|
|
var container PdfIndirectObject
|
2016-08-18 09:43:44 +00:00
|
|
|
if cachedObj, isCached := PdfObjectConverterCache[this]; isCached {
|
2016-08-18 14:38:49 +00:00
|
|
|
if !updateIfExists {
|
|
|
|
return cachedObj
|
|
|
|
}
|
|
|
|
obj := cachedObj.(*PdfIndirectObject)
|
|
|
|
container = *obj
|
|
|
|
} else {
|
|
|
|
container = PdfIndirectObject{}
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
2016-08-18 14:38:49 +00:00
|
|
|
PdfObjectConverterCache[this] = &container
|
2016-08-18 09:43:44 +00:00
|
|
|
|
|
|
|
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 {
|
2016-08-18 14:38:49 +00:00
|
|
|
dict["Next"] = this.Next.ToPdfObject(false)
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
if this.First != nil {
|
2016-08-18 14:38:49 +00:00
|
|
|
dict["First"] = this.First.ToPdfObject(false)
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
if this.Prev != nil {
|
2016-08-18 14:38:49 +00:00
|
|
|
dict["Prev"] = PdfObjectConverterCache[this.Prev.getOuter()]
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
if this.Last != nil {
|
2016-08-18 14:38:49 +00:00
|
|
|
dict["Last"] = PdfObjectConverterCache[this.Last.getOuter()]
|
|
|
|
}
|
|
|
|
if this.Parent != nil {
|
|
|
|
dict["Parent"] = PdfObjectConverterCache[this.Parent.getOuter()]
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
container.PdfObject = &dict
|
|
|
|
|
|
|
|
return &container
|
|
|
|
}
|