Set Outlines
A model.Outline built in Go or unmarshaled from JSON becomes a document’s
bookmarks through PdfWriter.AddOutlineTree. The shape of the JSON is exactly what
get outlines produces, so the usual workflow is to dump the
outlines of one document, edit the file, and apply it back.
Doing it
var newOutlines model.Outline
if err := json.Unmarshal(data, &newOutlines); err != nil {
return err
}
pdfWriter, err := pdfReader.ToWriter(&model.ReaderToWriterOpts{SkipOutlines: true})
if err != nil {
return err
}
pdfWriter.AddOutlineTree(newOutlines.ToOutlineTree())
return pdfWriter.WriteToFile("output.pdf")SkipOutlines: true matters. Without it, ToWriter copies the reader’s existing
outline tree into the writer, and the AddOutlineTree call then replaces it, so
the result is the same but the copy is wasted work; the option states the intent.
What you cannot do this way is merge. AddOutlineTree sets the tree, it does not
append to it, so adding a bookmark to a document means reading its outlines with
GetOutlines first, adding to that Outline, and writing the whole tree back.
ToOutlineTree is what turns the high-level tree into the linked list of
PdfOutlineTreeNode a PDF wants. It walks the Entries of every item, wires up
First, Last, Next, Prev and Parent, and sets each item’s Count to its
number of descendants. Nothing needs to be linked by hand.
Building the tree in code instead of reading JSON uses the same types:
outline := model.NewOutline()
item := model.NewOutlineItem("Chapter 1", model.NewOutlineDest(0, 0, 792))
item.Add(model.NewOutlineItem("Section 1.1", model.NewOutlineDest(0, 50, 640)))
outline.Add(item)NewOutlineDest(page, x, y) takes a zero-based page index and sets Mode to
XYZ. OutlineItem.Add appends a child, Insert puts one at an index, and the
same two methods exist on Outline for top level items.
Coordinates are in PDF user space with the origin at the bottom left of the page,
so y counts up from the bottom. To point at a particular piece of text, extract
its bounding box and use Llx for x and Ury for y.
Limitations
The page value in the JSON is a zero-based index, so the first page is 0.
Getting this wrong is the usual cause of bookmarks landing one page off.
Mode decides which coordinates are written out. XYZ writes x, y and zoom;
FitH and FitBH write only y; FitV and FitBV only x; Fit and FitB
write neither. Anything else is silently replaced with Fit, which discards the
coordinates. A destination with an empty mode, or a negative page and no page
object, is written as a null destination, giving a bookmark that goes nowhere.
Destinations loaded from JSON carry a page index rather than a reference to the
page object, because OutlineDest.PageObj is not serialized. UniPDF writes the
index as an integer in the destination array. The same is true of destinations
built with NewOutlineDest, so this is the normal output of the API, but it does
mean the page index is resolved by position: applying a JSON file to a document
whose pages are in a different order points the bookmarks at the wrong pages.
Titles are written as encoded strings, so non-ASCII titles survive. Anything else
a PDF bookmark can carry, such as color, bold or italic styling, or an open or
closed initial state, is not part of OutlineItem and cannot be set through it.
Run the example
applyOutlines is the whole example: read the JSON, unmarshal into a
model.Outline, convert the reader to a writer with SkipOutlines, apply the tree
and write the file. It takes three paths, in the order input, outlines, output.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/outlines
go run pdf_set_outlines.go input.pdf outlines.json output.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.