unipdf/pdf/model/outlines.go

316 lines
7.4 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 model
import (
2016-08-16 09:36:24 +00:00
"fmt"
"github.com/unidoc/unidoc/common"
. "github.com/unidoc/unidoc/pdf/core"
)
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
}
// PdfOutline represents a PDF outline dictionary (Table 152 - p. 376).
type PdfOutline struct {
2016-08-16 09:36:24 +00:00
PdfOutlineTreeNode
Parent *PdfOutlineTreeNode
Count *int64
primitive *PdfIndirectObject
}
// PdfOutlineItem represents an 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
primitive *PdfIndirectObject
}
// NewPdfOutline returns an initialized PdfOutline.
func NewPdfOutline() *PdfOutline {
outline := &PdfOutline{}
container := &PdfIndirectObject{}
container.PdfObject = MakeDict()
outline.primitive = container
return outline
}
// NewPdfOutlineTree returns an initialized PdfOutline tree.
func NewPdfOutlineTree() *PdfOutline {
outlineTree := NewPdfOutline()
outlineTree.context = &outlineTree
return outlineTree
}
// NewPdfOutlineItem returns an initialized PdfOutlineItem.
func NewPdfOutlineItem() *PdfOutlineItem {
outlineItem := &PdfOutlineItem{}
container := &PdfIndirectObject{}
container.PdfObject = MakeDict()
outlineItem.primitive = container
return outlineItem
}
// NewOutlineBookmark returns an initialized PdfOutlineItem for a given bookmark title and page.
func NewOutlineBookmark(title string, page *PdfIndirectObject) *PdfOutlineItem {
bookmark := PdfOutlineItem{}
bookmark.context = &bookmark
bookmark.Title = MakeString(title)
destArray := MakeArray()
destArray.Append(page)
destArray.Append(MakeName("Fit"))
bookmark.Dest = destArray
return &bookmark
}
2016-08-16 09:36:24 +00:00
// Does not traverse the tree.
func newPdfOutlineFromIndirectObject(container *PdfIndirectObject) (*PdfOutline, error) {
dict, isDict := container.PdfObject.(*PdfObjectDictionary)
if !isDict {
return nil, fmt.Errorf("Outline object not a dictionary")
}
2016-08-16 09:36:24 +00:00
outline := PdfOutline{}
outline.primitive = container
outline.context = &outline
2016-08-16 09:36:24 +00:00
if obj := dict.Get("Type"); obj != nil {
2016-08-16 09:36:24 +00:00
typeVal, ok := obj.(*PdfObjectName)
if ok {
if *typeVal != "Outlines" {
2016-10-31 21:48:25 +00:00
common.Log.Debug("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 := dict.Get("Count"); obj != nil {
// 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.
func (r *PdfReader) newPdfOutlineItemFromIndirectObject(container *PdfIndirectObject) (*PdfOutlineItem, error) {
dict, isDict := container.PdfObject.(*PdfObjectDictionary)
if !isDict {
return nil, fmt.Errorf("Outline object not a dictionary")
}
2016-08-16 09:36:24 +00:00
item := PdfOutlineItem{}
item.primitive = container
item.context = &item
2016-08-16 09:36:24 +00:00
// Title (required).
obj := dict.Get("Title")
if obj == nil {
2016-08-16 09:36:24 +00:00
return nil, fmt.Errorf("Missing Title from Outline Item (required)")
}
obj, err := r.traceToObject(obj)
2016-08-19 09:13:12 +00:00
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 := dict.Get("Count"); obj != nil {
2016-08-16 09:36:24 +00:00
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 := dict.Get("Dest"); obj != nil {
item.Dest, err = r.traceToObject(obj)
2016-08-19 09:13:12 +00:00
if err != nil {
return nil, err
}
err := r.traverseObjectData(item.Dest)
2016-08-19 09:13:12 +00:00
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj := dict.Get("A"); obj != nil {
item.A, err = r.traceToObject(obj)
2016-08-19 09:13:12 +00:00
if err != nil {
return nil, err
}
err := r.traverseObjectData(item.A)
2016-08-19 09:13:12 +00:00
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj := dict.Get("SE"); obj != nil {
2018-12-09 21:37:27 +02:00
// TODO: To add structure element support.
// Currently not supporting structure elements.
item.SE = nil
/*
item.SE, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
*/
2016-08-16 17:57:23 +00:00
}
if obj := dict.Get("C"); obj != nil {
item.C, err = r.traceToObject(obj)
2016-08-19 09:13:12 +00:00
if err != nil {
return nil, err
}
2016-08-16 17:57:23 +00:00
}
if obj := dict.Get("F"); obj != nil {
item.F, err = r.traceToObject(obj)
2016-08-19 09:13:12 +00:00
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() PdfModel {
if outline, isOutline := n.context.(*PdfOutline); isOutline {
return outline
}
if outlineItem, isOutlineItem := n.context.(*PdfOutlineItem); isOutlineItem {
return outlineItem
}
2016-10-31 21:48:25 +00:00
common.Log.Debug("ERROR Invalid outline tree node item") // Should never happen.
return nil
}
func (n *PdfOutlineTreeNode) GetContainingPdfObject() PdfObject {
return n.getOuter().GetContainingPdfObject()
}
func (n *PdfOutlineTreeNode) ToPdfObject() PdfObject {
return n.getOuter().ToPdfObject()
}
func (o *PdfOutline) GetContainingPdfObject() PdfObject {
return o.primitive
}
// ToPdfObject recursively builds the Outline tree PDF object.
func (o *PdfOutline) ToPdfObject() PdfObject {
container := o.primitive
dict := container.PdfObject.(*PdfObjectDictionary)
dict.Set("Type", MakeName("Outlines"))
if o.First != nil {
dict.Set("First", o.First.ToPdfObject())
}
if o.Last != nil {
dict.Set("Last", o.Last.getOuter().GetContainingPdfObject())
//PdfObjectConverterCache[o.Last.getOuter()]
}
if o.Parent != nil {
dict.Set("Parent", o.Parent.getOuter().GetContainingPdfObject())
}
return container
}
func (oi *PdfOutlineItem) GetContainingPdfObject() PdfObject {
return oi.primitive
}
// ToPdfObject recursively builds the Outline tree PDF object.
func (oi *PdfOutlineItem) ToPdfObject() PdfObject {
container := oi.primitive
dict := container.PdfObject.(*PdfObjectDictionary)
dict.Set("Title", oi.Title)
if oi.A != nil {
dict.Set("A", oi.A)
}
if obj := dict.Get("SE"); obj != nil {
2018-12-09 21:37:27 +02:00
// TODO: Currently not supporting structure element hierarchy.
// Remove it.
dict.Remove("SE")
// delete(*dict, "SE")
}
/*
if oi.SE != nil {
(*dict)["SE"] = oi.SE
}
*/
if oi.C != nil {
dict.Set("C", oi.C)
}
if oi.Dest != nil {
dict.Set("Dest", oi.Dest)
}
if oi.F != nil {
dict.Set("F", oi.F)
}
if oi.Count != nil {
dict.Set("Count", MakeInteger(*oi.Count))
}
if oi.Next != nil {
dict.Set("Next", oi.Next.ToPdfObject())
}
if oi.First != nil {
dict.Set("First", oi.First.ToPdfObject())
}
if oi.Prev != nil {
dict.Set("Prev", oi.Prev.getOuter().GetContainingPdfObject())
//PdfObjectConverterCache[oi.Prev.getOuter()]
}
if oi.Last != nil {
dict.Set("Last", oi.Last.getOuter().GetContainingPdfObject())
// PdfObjectConverterCache[oi.Last.getOuter()]
}
if oi.Parent != nil {
dict.Set("Parent", oi.Parent.getOuter().GetContainingPdfObject())
//PdfObjectConverterCache[oi.Parent.getOuter()]
}
return container
}