Skip to content

Internals Forms and Outlines

Dragon edited this page Jun 3, 2026 · 1 revision

Internals: Forms and Outlines

This page covers the internal representation of outline trees (bookmarks), link annotations, and AcroForm interactive fields in phppdf.

Outlines and hyperlinks

Outline tree (Bookmarks panel)

The outline is a hierarchical doubly-linked tree of /Type /Outlines dictionaries with /First, /Last, /Next, /Prev, /Parent, and /Count pointers. phppdf builds this tree from the Outline::add(...) declarative API and serializes the cross-references at output() time.

Each outline item has a /Dest referencing a destination:

  • [page /XYZ left top zoom] - page + position + zoom (the default for Destination::page(0) is XYZ at top-left).
  • [page /Fit] - fit whole page in viewport.
  • [page /FitH top] - fit page width.

Link annotations

Each $page->link(...) call adds a /Type /Annot /Subtype /Link dictionary to the page's /Annots array. Two variants:

  • URL link: /A << /S /URI /URI (https://...) >>.
  • Internal jump: /Dest [page ...] using the same destination encoding as outline items.

The /Border [0 0 0] entry makes the link rectangle invisible - the standard "underline appears on click in Acrobat" behavior is what users expect, but the rectangle itself is not outlined by default.

Byte-identity preservation

Pages without any link() calls produce no /Annots array, so their content streams remain byte-identical to the equivalent page from earlier phases of the library. This is what allows the golden-test fixtures from Phase 1-6 to stay valid after Phase 7 shipped outlines.

AcroForm interactive forms

AcroForm fields live both in the document catalog (as /AcroForm) and on individual pages (as widget /Annots). phppdf uses a hybrid appearance strategy:

TextField, Combobox, Listbox - NeedAppearances

  • The document catalog's /AcroForm dictionary gets /NeedAppearances true.
  • The field declares a /DA (default appearance) string like /Helv 12 Tf 0 g, which sets the font and color.
  • No /AP (appearance stream) is generated. The PDF reader is responsible for rendering the field on the fly when the file opens.
  • This way, the displayed text always matches the field's current value (including user-edited values).

Checkbox, Radio - pre-generated /AP

  • For these, NeedAppearances is unreliable across readers (Acrobat draws boxes but Firefox PDF.js does not, etc.).
  • phppdf pre-generates the /AP (appearance) streams for both On and Off states, embedding a small content stream that draws the tick mark or filled circle.
  • The widget references these via /AP << /N << /Yes 12 0 R /Off 13 0 R >> >>.
  • This guarantees identical rendering across Acrobat, browser viewers (Chrome PDF, Firefox PDF.js), headless PDF renderers, and others.

Field styling

Optional per-field FieldAppearance:

  • Border color + width: emitted as /Border [0 0 W] plus /BS << /W ... /S /S >> and /MK << /BC [r g b] >>. The /Border entry is what makes Edge / Firefox / Brave render the colored frame (some readers ignore /BS and only honor /Border, so we emit both).
  • Background color: /MK << /BG [r g b] >>.
  • Text color: encoded in the /DA string (r g b rg for fill).
  • Font: Helvetica, Courier, or Times. Auto-registered in the form's default resources /AcroForm /DR << /Font << /Helv 14 0 R /Cour 15 0 R /TiRo 16 0 R >> >> only if used.
  • Font size: encoded in /DA (/Helv 12 Tf).
  • Alignment (TextField only): /Q 0 (left), /Q 1 (center), /Q 2 (right).

Byte-identity preservation

Pages without any field() calls produce:

  • No extra /Annots entries (only what link() adds, if anything).
  • No /AcroForm entry in the catalog.

So the byte-identity baseline of earlier phases is preserved.

See also

Clone this wiki locally