package model import ( "fmt" "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/common" "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/core" ) type PdfOutlineTreeNode struct { context any // Allow accessing outer structure. First *PdfOutlineTreeNode Last *PdfOutlineTreeNode } // PDF outline dictionary (Table 152 - p. 376). type PdfOutline struct { PdfOutlineTreeNode Parent *PdfOutlineTreeNode Count *int64 primitive *core.PdfIndirectObject } // Pdf outline item dictionary (Table 153 - pp. 376 - 377). type PdfOutlineItem struct { PdfOutlineTreeNode Title *core.PdfObjectString Parent *PdfOutlineTreeNode Prev *PdfOutlineTreeNode Next *PdfOutlineTreeNode Count *int64 Dest core.PdfObject A core.PdfObject SE core.PdfObject C core.PdfObject F core.PdfObject primitive *core.PdfIndirectObject } func NewPdfOutline() *PdfOutline { outline := &PdfOutline{} container := &core.PdfIndirectObject{} container.PdfObject = core.MakeDict() outline.primitive = container return outline } func NewPdfOutlineTree() *PdfOutline { outlineTree := NewPdfOutline() outlineTree.context = &outlineTree return outlineTree } func NewPdfOutlineItem() *PdfOutlineItem { outlineItem := &PdfOutlineItem{} container := &core.PdfIndirectObject{} container.PdfObject = core.MakeDict() outlineItem.primitive = container return outlineItem } func NewOutlineBookmark(title string, page *core.PdfIndirectObject) *PdfOutlineItem { bookmark := PdfOutlineItem{} bookmark.context = &bookmark bookmark.Title = core.MakeString(title) destArray := core.PdfObjectArray{} destArray = append(destArray, page) destArray = append(destArray, core.MakeName("Fit")) bookmark.Dest = &destArray return &bookmark } // Does not traverse the tree. func newPdfOutlineFromIndirectObject(container *core.PdfIndirectObject) (*PdfOutline, error) { dict, isDict := container.PdfObject.(*core.PdfObjectDictionary) if !isDict { return nil, fmt.Errorf("outline object not a dictionary") } outline := PdfOutline{} outline.primitive = container outline.context = &outline if obj := dict.Get("Type"); obj != nil { typeVal, ok := obj.(*core.PdfObjectName) if ok { if *typeVal != "Outlines" { 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. } } } 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 } outline.Count = &count } return &outline, nil } // Does not traverse the tree. func (pr *PdfReader) newPdfOutlineItemFromIndirectObject(container *core.PdfIndirectObject) (*PdfOutlineItem, error) { dict, isDict := container.PdfObject.(*core.PdfObjectDictionary) if !isDict { return nil, fmt.Errorf("outline object not a dictionary") } item := PdfOutlineItem{} item.primitive = container item.context = &item // Title (required). obj := dict.Get("Title") if obj == nil { return nil, fmt.Errorf("missing Title from Outline Item (required)") } obj, err := pr.traceToObject(obj) if err != nil { return nil, err } title, ok := core.TraceToDirectObject(obj).(*core.PdfObjectString) if !ok { return nil, fmt.Errorf("title not a string (%T)", obj) } item.Title = title // Count (optional). if obj := dict.Get("Count"); obj != nil { countVal, ok := obj.(*core.PdfObjectInteger) if !ok { return nil, fmt.Errorf("count not an integer (%T)", obj) } count := int64(*countVal) item.Count = &count } // Other keys. if obj := dict.Get("Dest"); obj != nil { item.Dest, err = pr.traceToObject(obj) if err != nil { return nil, err } err := pr.traverseObjectData(item.Dest) if err != nil { return nil, err } } if obj := dict.Get("A"); obj != nil { item.A, err = pr.traceToObject(obj) if err != nil { return nil, err } err := pr.traverseObjectData(item.A) if err != nil { return nil, err } } if obj := dict.Get("SE"); obj != nil { // XXX: To add structure element support. // Currently not supporting structure elements. item.SE = nil } if obj := dict.Get("C"); obj != nil { item.C, err = pr.traceToObject(obj) if err != nil { return nil, err } } if obj := dict.Get("F"); obj != nil { item.F, err = pr.traceToObject(obj) if err != nil { return nil, err } } 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 } common.Log.Debug("error Invalid outline tree node item") // Should never happen. return nil } func (pot *PdfOutlineTreeNode) GetContainingPdfObject() core.PdfObject { return pot.getOuter().GetContainingPdfObject() } func (pot *PdfOutlineTreeNode) ToPdfObject() core.PdfObject { return pot.getOuter().ToPdfObject() } func (po *PdfOutline) GetContainingPdfObject() core.PdfObject { return po.primitive } // Recursively build the Outline tree PDF object. func (po *PdfOutline) ToPdfObject() core.PdfObject { container := po.primitive dict := container.PdfObject.(*core.PdfObjectDictionary) dict.Set("Type", core.MakeName("Outlines")) if po.First != nil { dict.Set("First", po.First.ToPdfObject()) } if po.Last != nil { dict.Set("Last", po.Last.getOuter().GetContainingPdfObject()) //PdfObjectConverterCache[this.Last.getOuter()] } if po.Parent != nil { dict.Set("Parent", po.Parent.getOuter().GetContainingPdfObject()) } return container } func (poi *PdfOutlineItem) GetContainingPdfObject() core.PdfObject { return poi.primitive } // Outline item. // Recursively build the Outline tree PDF object. func (poi *PdfOutlineItem) ToPdfObject() core.PdfObject { container := poi.primitive dict := container.PdfObject.(*core.PdfObjectDictionary) dict.Set("Title", poi.Title) if poi.A != nil { dict.Set("A", poi.A) } if obj := dict.Get("SE"); obj != nil { // XXX: Currently not supporting structure element hierarchy. // Remove it. dict.Remove("SE") // delete(*dict, "SE") } if poi.C != nil { dict.Set("C", poi.C) } if poi.Dest != nil { dict.Set("Dest", poi.Dest) } if poi.F != nil { dict.Set("F", poi.F) } if poi.Count != nil { dict.Set("Count", core.MakeInteger(*poi.Count)) } if poi.Next != nil { dict.Set("Next", poi.Next.ToPdfObject()) } if poi.First != nil { dict.Set("First", poi.First.ToPdfObject()) } if poi.Prev != nil { dict.Set("Prev", poi.Prev.getOuter().GetContainingPdfObject()) } if poi.Last != nil { dict.Set("Last", poi.Last.getOuter().GetContainingPdfObject()) } if poi.Parent != nil { dict.Set("Parent", poi.Parent.getOuter().GetContainingPdfObject()) } return container }