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.
A PDF is a graph of objects
Section titled “A PDF is a graph of objects”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 type | What it is |
|---|---|
| Boolean, Number, String | Scalars |
| Name | A token starting with /, e.g. /Type, /Font |
| Array | An ordered list: [ 1 2 3 ] |
| Dictionary | Key/value map of names to objects: << /Type /Page ... >> |
| Stream | A dictionary plus a blob of (usually compressed) bytes |
| Null | Absence |
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.
The document skeleton
Section titled “The document skeleton”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.
Content streams
Section titled “Content streams”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 stringET % end textq ... Q % save / restore graphics statere f % rectangle, then fillTwo things to internalize:
- 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.)
- 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.
Beyond text: resources, images, and more
Section titled “Beyond text: resources, images, and more”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.
Why this matters for Rivoli PDF
Section titled “Why this matters for Rivoli PDF”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.
- The layout engine: how the model becomes positioned content before it’s written.
- The document model: the structured tree you actually build against.