Skip to content

How PDF works

You can use Rivoli PDF productively without knowing how a PDF file is structured: you build a document tree, the library writes the bytes. But a little knowledge of the PDF object model goes a long way when you are reading an error message, debugging an imported file, or wondering why a font behaves the way it does. This primer covers just enough to demystify those moments.

Under the hood, a PDF file is not a stream of “pages”: it is a collection of numbered objects that reference one another, forming a graph. A handful of primitive object types make up everything:

Object typeWhat it is
Boolean, Number, StringScalars
NameA token starting with /, e.g. /Type, /Font
ArrayAn ordered list: [ 1 2 3 ]
DictionaryKey/value map of names to objects: << /Type /Page ... >>
StreamA dictionary plus a blob of (usually compressed) bytes
NullAbsence

Objects are numbered (“indirect objects”) so other objects can point at them by reference. A page dictionary doesn’t contain its content; it references the content stream object, the resources object, and so on.

/Type /Page
/Parent 2 0 R % reference to object 2 (the page tree node)
/MediaBox [0 0 612 792]
/Contents 5 0 R % reference to the content stream
/Resources << ... >>

When a diagnostic mentions an “object number”, a “dictionary key”, or a “name”, it is talking about this graph.

A few special dictionaries hold the graph together:

  • Catalog: the root of the document. Everything reachable starts here.
  • Page tree: a tree of nodes whose leaves are the page dictionaries. Each page records its size (MediaBox), its content, and its resources.
  • Cross-reference table (xref): an index that maps each object number to its byte offset in the file, so a reader can jump straight to any object without scanning.
  • Trailer: points at the catalog and the xref. A reader starts at the end of the file, reads the trailer, finds the xref, then the catalog, then walks outward.

A “broken xref” or “could not find trailer” error means a reader couldn’t bootstrap this skeleton: usually a truncated or malformed file.

A page’s actual marks (text, lines, fills, images), live in its content stream: a sequence of drawing operators in a tiny stack-based language. You rarely read it by hand, but recognizing it helps:

BT % begin text
/F1 12 Tf % select font /F1 at 12pt
72 700 Td % move to position (72, 700)
(Hello, world) Tj % show the string
ET % end text
q ... Q % save / restore graphics state
re f % rectangle, then fill

Two things to internalize:

  1. Coordinates are points (1/72 inch), and the origin is the bottom-left of the page. (Note this is the opposite of Rivoli PDF’s layout coordinates, which are top-left and page-relative: the writer handles the flip.)
  2. Fonts are referenced by name (/F1), and that name is resolved through the page’s resources, not baked into the text. Which brings us to fonts.

Text in a PDF is drawn with a font resource. A font dictionary describes the font’s encoding (how byte values map to glyphs), its metrics (glyph widths), and whether the font program itself is embedded in the file or merely named.

This is the source of most font-related confusion:

  • Standard fonts (Helvetica, Times-Roman, Courier and their variants) are assumed to be available to any reader and are often not embedded.
  • Embedded fonts carry the actual glyph outlines, so the file renders identically everywhere: at the cost of size.
  • Encoding determines which glyph a byte selects. A wrong encoding is why text can render as boxes or the wrong characters even though the bytes are “correct”.

When you pick a FontFamily in a style, you are ultimately choosing which font resource the writer sets up and references from the content stream.

A page’s resource dictionary is the lookup table the content stream draws from: fonts, images (XObjects), color spaces, and graphics-state presets all live there and are referenced by name. Images, embedded files, and form-field appearances are all just more objects in the graph, reachable from the catalog.

Rivoli PDF shields you from almost all of this. You build a structured model; the layout engine positions content; the writer emits the object graph, content streams, fonts, and xref correctly. The determinism the library prides itself on lives at exactly this level: the same document produces the same bytes, which is what makes round-trip pixel-fidelity testing possible.

You’ll find this primer most useful when:

  • A diagnostic names an object, dictionary key, or font and you want to know what it’s pointing at.
  • You’re importing an existing PDF and need to reason about why some content parses the way it does.
  • A font renders unexpectedly and you need to decide between a standard and an embedded font.