diff --git a/pdf/outlines.go b/pdf/outlines.go index 0be4b5ce..e44bfeab 100644 --- a/pdf/outlines.go +++ b/pdf/outlines.go @@ -2,8 +2,16 @@ package pdf import ( "fmt" + + "github.com/unidoc/unidoc/common" ) +type PdfObjectConverter interface { + ToPdfObject() PdfObject +} + +var PdfObjectConverterCache map[PdfObjectConverter]PdfObject = map[PdfObjectConverter]PdfObject{} + type PdfOutlineTreeNode struct { context interface{} // Allow accessing outer structure. First *PdfOutlineTreeNode @@ -30,6 +38,26 @@ type PdfOutlineItem struct { 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 +} + // Does not traverse the tree. func newPdfOutlineFromDict(dict *PdfObjectDictionary) (*PdfOutline, error) { outline := PdfOutline{} @@ -101,3 +129,87 @@ func newPdfOutlineItemFromDict(dict *PdfObjectDictionary) (*PdfOutlineItem, erro return &item, nil } + +func (this *PdfOutlineTreeNode) ToPdfObject() PdfObject { + if outline, isOutline := this.context.(*PdfOutline); isOutline { + return outline.ToPdfObject() + } + if outlineItem, isOutlineItem := this.context.(*PdfOutlineItem); isOutlineItem { + return outlineItem.ToPdfObject() + } + + common.Log.Error("Invalid outline tree node item") // Should never happen. + return nil +} + +// Recursively build the Outline tree PDF object. +func (this *PdfOutline) ToPdfObject() PdfObject { + if cachedObj, isCached := PdfObjectConverterCache[this]; isCached { + return cachedObj + } + + outlines := PdfIndirectObject{} + + outlinesDict := PdfObjectDictionary{} + outlinesDict[PdfObjectName("Type")] = MakeName("Outlines") + + if this.First != nil { + outlinesDict[PdfObjectName("First")] = this.First.ToPdfObject() + } + + if this.Last != nil { + outlinesDict[PdfObjectName("Last")] = this.Last.ToPdfObject() + } + + outlines.PdfObject = &outlinesDict + + PdfObjectConverterCache[this] = &outlines + + return &outlines +} + +// Outline item. +// Recursively build the Outline tree PDF object. +func (this *PdfOutlineItem) ToPdfObject() PdfObject { + if cachedObj, isCached := PdfObjectConverterCache[this]; isCached { + return cachedObj + } + + container := PdfIndirectObject{} + + 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() + } + if this.First != nil { + dict["First"] = this.First.ToPdfObject() + } + if this.Prev != nil { + dict["Prev"] = this.Prev.ToPdfObject() + } + if this.Last != nil { + dict["Last"] = this.Last.ToPdfObject() + } + + container.PdfObject = &dict + PdfObjectConverterCache[this] = &container + + return &container +} diff --git a/pdf/page.go b/pdf/page.go index 994f59b6..699e3918 100644 --- a/pdf/page.go +++ b/pdf/page.go @@ -275,6 +275,7 @@ func NewPdfPagesFromDict(dict PdfObjectDictionary) (*PdfPages, error) { } // Build a PdfPage based on the underlying dictionary. +// Used in loading existing PDF files. func (reader *PdfReader) newPdfPageFromDict(p *PdfObjectDictionary) (*PdfPage, error) { page := PdfPage{} page.pageDict = &PdfObjectDictionary{} diff --git a/pdf/reader.go b/pdf/reader.go index dc82ae55..d9d25ac3 100644 --- a/pdf/reader.go +++ b/pdf/reader.go @@ -648,6 +648,7 @@ func (this *PdfReader) GetOutlinesForPage(page PdfObject) ([]*PdfIndirectObject, // Get a page by the page number. // Indirect object with type /Page. +// GetPageAsIndirectObject func (this *PdfReader) GetPage(pageNumber int) (PdfObject, error) { if this.parser.crypter != nil && !this.parser.crypter.authenticated { return nil, fmt.Errorf("File need to be decrypted first") @@ -667,3 +668,18 @@ func (this *PdfReader) GetPage(pageNumber int) (PdfObject, error) { return page, nil } + +// Get a page by the page number. +// Indirect object with type /Page. +// GetPageAsIndirectObject +func (this *PdfReader) GetPageAsPdfPage(pageNumber int) (*PdfPage, error) { + if this.parser.crypter != nil && !this.parser.crypter.authenticated { + return nil, fmt.Errorf("File need to be decrypted first") + } + if len(this.pageList) < pageNumber { + return nil, errors.New("Invalid page number (page count too short)") + } + page := this.PageList[pageNumber-1] + + return page, nil +} diff --git a/pdf/writer.go b/pdf/writer.go index 5cf028b1..62ad2b9c 100644 --- a/pdf/writer.go +++ b/pdf/writer.go @@ -60,15 +60,16 @@ func SetPdfCreator(creator string) { } type PdfWriter struct { - root *PdfIndirectObject - pages *PdfIndirectObject - objects []PdfObject - objectsMap map[PdfObject]bool // Quick lookup table. - writer *bufio.Writer - outlines []*PdfIndirectObject - catalog *PdfObjectDictionary - fields []PdfObject - infoObj *PdfIndirectObject + root *PdfIndirectObject + pages *PdfIndirectObject + objects []PdfObject + objectsMap map[PdfObject]bool // Quick lookup table. + writer *bufio.Writer + outlines []*PdfIndirectObject + outlineTree *PdfOutlineTreeNode + catalog *PdfObjectDictionary + fields []PdfObject + infoObj *PdfIndirectObject // Encryption crypter *PdfCrypt encryptDict *PdfObjectDictionary @@ -317,6 +318,11 @@ func (this *PdfWriter) AddOutlines(outlinesList []*PdfIndirectObject) error { return nil } +// Add outlines to a PDF file. +func (this *PdfWriter) AddOutlineTree(outlineTree *PdfOutlineTreeNode) { + this.outlineTree = outlineTree +} + // Look for a specific key. Returns a list of entries. // What if something appears on many pages? func (this *PdfWriter) seekByName(obj PdfObject, followKeys []string, key string) ([]PdfObject, error) { @@ -353,10 +359,6 @@ func (this *PdfWriter) seekByName(obj PdfObject, followKeys []string, key string return list, nil } - // Ignore arrays. - //if arr, isArray := obj.(*PdfObjectArray); isArray { - //} - return list, nil } @@ -553,6 +555,16 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio // Write the pdf out. func (this *PdfWriter) Write(ws io.WriteSeeker) error { common.Log.Debug("Write()") + // Outlines. + if this.outlineTree != nil { + outlines := this.outlineTree.ToPdfObject() + (*this.catalog)["Outlines"] = outlines + err := this.addObjects(outlines) + if err != nil { + return err + } + } + // Phase this one out. if len(this.outlines) > 0 { // Add the outlines dictionary if some outlines added. // Assume they are correct, not referencing anything not added