# Compact PDF Output: Content Streams, Font Subsets, Object Streams and Page Tree

PD4ML writes compact PDF files without any configuration: page content streams carry only the operators that change what is drawn, dictionaries leave out entries that only restate a default, embedded fonts hold only the glyphs the pages show, the small objects of a document are compressed together in object streams, and the pages are organised as a tree. The pages look exactly the same as before; the files are smaller and faster to open, render and process.

{% hint style="version" %}
Available from **PD4ML v4.2.0**, with both layout engines. Nothing needs to be enabled; see [Turning the optimizations off](#turning-the-optimizations-off) for the two switches that restore the previous output.
{% endhint %}

## What PD4ML does

| Measure | What changes in the file | Effect |
|---|---|---|
| [Page content streams](#page-content-streams) | operators that change nothing are left out | 16–18% less page content to parse and render |
| [Default dictionary entries](#dictionary-entries) | entries that restate a default value are left out | cleaner output, slightly smaller |
| [Font subsets](#font-subsets) | an embedded TrueType font holds only the shown glyphs | the largest saving: fonts are often half of a file |
| [Object streams](#object-streams-and-cross-reference-streams) | small objects are compressed together; the cross-reference table becomes a compressed stream | a few percent smaller, faster to load |
| [Page tree](#page-tree) | pages are grouped in nodes of at most 10 | faster page access in large documents |

On the PDFs of PD4ML's own test suites (1,642 documents of all kinds: text, tables, forms, SVG, images, PDF/A, ZUGFeRD invoices) the files are **25.9% smaller** in total, every page renders pixel-identical to the previous output, and every PDF/A document still passes veraPDF validation. How much an individual document gains depends on its content: small text documents with embedded fonts gain the most, documents dominated by photographs the least.

## Page content streams

Every page is described by a content stream: a sequence of operators that set colors, line widths, fonts and transformations, and then draw paths, text and images. When a page is finished, PD4ML removes the operators that cannot change anything on it:

- **state set again to the value already in effect**, including the initial value of a page: `0 0 0 rg` after `0 0 0 rg`, `1 w` (the initial line width) on a fresh page;
- **state set but never used**: a stroke color (`RG`) when only filling follows before the next color is set, or settings dropped by a `Q` (restore) before anything uses them;
- **`q … Q` blocks that draw nothing**, clipping paths and transformations (`cm`) that no drawing follows, identity transformations;
- comments and indentation (earlier versions repeated the text of every text run as a `%` comment in the stream).

Marked content (`BMC`/`BDC`/`EMC`, which tagged PDF and PDF/UA rely on) is always kept, and so is everything that draws. A stream the optimizer cannot read completely is written as it is.

```
before                               after
q                                    q
0.74 0 0 -0.74 0 842 cm              1.0 0 0 rg
1.0 1.0 1.0 RG                       0 0 803 1137 re
1.0 1.0 1.0 rg                       f
1.35 0 0 -1.35 -8.1 1128.6 cm        Q
Q                                    …
q
1.0 0 0 RG
1.0 0 0 rg
0 0 803 1137 re
f
Q
…
```

The first block draws nothing and goes as a whole; in the second, the stroke color is never used.

## Dictionary entries

PDF dictionaries (pages, fonts, annotations, transparency groups…) describe the objects of a document. PD4ML writes every entry the PDF specification requires and leaves out entries that only repeat the specification's default value, for example:

| Dictionary | Omitted entry | Default |
|---|---|---|
| Page transparency group | `/I false`, `/K false` | `false` |
| Form XObject | `/FormType 1` | `1` |
| CID font | `/DW 1000` | `1000` |
| Document catalog | `/PageLayout /SinglePage` | `/SinglePage` |

A page layout other than single page (see `PD4Constants.PD4ML_DOCUMENT_VIEW_MODE`) is still written.

## Font subsets

PD4ML embeds TrueType fonts as subsets: only the glyphs the document uses are included. Earlier versions kept the glyph numbering of the original font, so a subset still carried an index and a width entry for every glyph of the font — for a large font with thousands of glyphs, most of the subset was such entries, even when a page used a few dozen glyphs.

Now an embedded subset holds glyph 0, the glyphs the pages show and the parts of composite glyphs (such as *é* built from *e* and an accent), numbered from 0. The font's `CIDToGIDMap` stream maps the glyph numbers used in the page content to the subset's glyphs. Everything else stays as it was: the text in the content streams, the glyph widths (`/W`), the `ToUnicode` map that makes the text searchable and copyable, and the `CIDSet` PDF/A requires.

Fonts with PostScript (CFF) outlines and fonts that are referenced but not embedded are not affected.

## Object streams and cross-reference streams

A PDF file consists of numbered objects: besides the large content, image and font streams, a document has many small dictionaries — pages, fonts, annotations, outline entries, structure elements of a tagged PDF. PD4ML now writes these small objects compressed together in **object streams**, and the cross-reference section, which lists where each object is, as a compressed **cross-reference stream** instead of a table of 20-byte lines.

Object streams were introduced with PDF 1.5 and are read by all current PDF viewers and libraries. PD4ML uses them only where they are allowed and safe:

| Output | Object streams |
|---|---|
| PDF 1.5 and later (PD4ML writes PDF 1.7 by default) | yes |
| PDF/A-2, PDF/A-3, PDF/A-4, PDF/UA, ZUGFeRD / Factur-X | yes |
| PDF/A-1 (PDF/A-1a, PDF/A-1b) | no — PDF/A-1 forbids them |
| encrypted output (`setPermissions(...)`) | no — the classic layout is written |

PD4ML's own PDF tools (merging, overlays, the PDF COS API, signing) read documents with object streams, so PD4ML output can be processed further as before.

## Page tree

A PDF document lists its pages in a tree of page nodes. PD4ML used to list all pages in one node; now the pages are grouped into nodes of at most 10 entries, and the nodes again, level by level, until the top node has at most 10 entries. A viewer finds page 900 of a large document by descending three levels instead of scanning a list of all the pages. Documents of up to 10 pages keep a single node.

The same applies to merged and appended documents ([PD4ML PDF Merge](../pdf-tools/pd4ml-pdf-merge), `PdfDocument.append(...)`): the pages of the result are grouped the same way.

## Turning the optimizations off

The previous output can be restored with two Java system properties, for example for a downstream tool that cannot read PDF 1.5 object streams, or to compare output:

| System property | Effect |
|---|---|
| `-Dpd4ml.objectStreams=false` | every object is written on its own, with a classic cross-reference table |
| `-Dpd4ml.optimizeContent=false` | page content streams are written as generated, comments included |

```
java -Dpd4ml.objectStreams=false -jar pd4ml.jar …
```

The properties are read once, when PD4ML is loaded; set them on the command line of the JVM (or before PD4ML's classes are first used). Font subsets, the omitted default entries and the page tree have no switch: they change only how the file is organised, never what it shows.

{% hint style="info" %}
If your application inspected the raw content streams of PD4ML output — for example searching them for the `%` comments that repeated each text run — read the text with a PDF library's text extraction instead (or set `-Dpd4ml.optimizeContent=false`). The comments were never part of the visible or extractable text.
{% endhint %}
