Cross-Reference-Streams und PDF-2.0-Optimierungen
Die klassische xref-Tabelle am Ende einer PDF-Datei sieht aus wie eine Reliquie aus den 1990ern, und genau das ist sie auch. Seit PDF 1.5 darf sie durch einen kompakten, komprimierten Cross-Reference-Stream ersetzt werden; in PDF 2.0 ist das der Normalfall. Diese Seite erklärt, was sich technisch ändert, warum es sich ändert und worauf du beim Lesen und Schreiben von PDFs achten musst.
Was ein Cross-Reference-Stream ist
Jede PDF-Datei braucht ein Verzeichnis, das sagt: „Objekt Nr. 42 beginnt an Byte-Offset 18 245." Ohne dieses Verzeichnis findet kein Reader nichts, denn PDF wird nicht von vorn nach hinten gelesen, sondern vom Ende her: der Trailer verweist auf das Cross-Reference , das Cross-Reference verweist auf die Objekte.
Bis PDF 1.4 war dieses Verzeichnis eine textuelle Tabelle, eingeleitet
mit dem Schlüsselwort xref
und jeder Zeile fest
formatiert (20 Bytes). Mit PDF 1.5 wurde eine Alternative eingeführt:
ein Cross-Reference-Stream. Statt einer
Klartext-Tabelle steht ein Stream-Objekt
mit binär kodiertem
Inhalt und einem Dictionary-Header, in dem genau festgelegt ist, wie
die Bytes zu lesen sind. PDF 2.0 (ISO 32000-2) hebt das auf den
Default an: Neue PDF-Werkzeuge schreiben standardmäßig xref-Streams,
die alte Tabelle taucht nur noch in Backward-Compatibility-Szenarien
auf.
Warum die xref-Tabelle abgelöst wird
Die klassische xref-Tabelle hat drei strukturelle Schwächen, die in modernen PDFs zunehmend stören:
- Sie ist groß. 20 Bytes pro Objekt klingt wenig, summiert sich aber bei zehntausenden Objekten zu mehreren hundert Kilobyte. Ein Cross-Reference-Stream ist FlateDecode-komprimiert und schrumpft auf einen Bruchteil davon zusammen.
- Sie kann nicht in den Datei-Inhalt. Die Tabelle muss textuell und unkomprimiert am Ende der Datei stehen. Damit wandert sie an Object-Streams (Punkt 3) vorbei, die eigentlich die größte Kompressions-Hebelwirkung haben.
- Sie zwingt zur 4-GB-Grenze. Die textuelle Form kodiert Offsets als 10-stellige Zahlen, was de facto die Datei auf knapp 10 Milliarden Bytes begrenzt. xref-Streams kodieren Offsets binär in beliebig vielen Bytes; die offizielle qpdf-Dokumentation nennt 10 GB als praktische Schwelle, ab der xref-Streams alternativlos werden.
Dazu kommt: xref-Streams sind Stream-Objekte. Das heißt, sie folgen demselben Lese- und Schreibprotokoll wie alle anderen Objekte im PDF. Wer einen PDF-Parser baut, hat damit einen Code-Pfad weniger: die textuelle Tabelle erforderte einen Sonderfall, der mit dem Rest der Datei nichts gemeinsam hatte.
startxref
am Datei-Ende → Cross-Reference-Stream mit
Dictionary → Trailer-Keys im selben Dictionary → Catalog →
einzelne Seiten. Der ehemalige separate trailer
-Block
ist Teil des xref-Stream-Dictionaries geworden.Aufbau einer xref-Stream-Entry
Ein Cross-Reference-Stream besteht aus einem Stream-Dictionary und
einem binären Datenblock. Im Dictionary stehen die üblichen
Trailer-Schlüssel ( /Root
, /Size
, /Info
, /Encrypt
) plus drei
stream-spezifische Felder: /Type /XRef
, ein /W
-Array, das die Byte-Breite jeder Entry-Spalte
definiert, und ein /Index
-Array, das die Objekt-Nummern
benennt, die abgedeckt werden.
xref-stream Header (vereinfacht)
15 0 obj << /Type /XRef /Size 16 /W [ 1 4 2 ] /Root 1 0 R /Filter /FlateDecode /Length 67 >> stream ...binäre Entries... endstream endobj startxref 18245 %%EOF
Die binären Entries werden in Big-Endian-Reihenfolge
konkateniert. Jede Entry besteht laut PDF-2.0-Spezifikation (Tabelle
17, Klausel 7.5.8) aus genau drei Feldern: einem Typ-Byte, einem
Offset-Feld (variable Breite, von /W
gesteuert) und
einem Generations-Feld. Das Typ-Byte sagt, was die Entry beschreibt:
| Typ-Byte | Bedeutung | Zweites Feld | Drittes Feld |
|---|---|---|---|
0
|
Freie Entry (Objekt gelöscht oder noch nie vergeben) | Objekt-Nummer der nächsten freien Entry | Generations-Nummer für die nächste Wiederverwendung |
1
|
Belegte Entry, Objekt liegt direkt in der Datei | Byte-Offset des Objekts in der Datei | Generations-Nummer (meist 0
) |
2
|
Komprimierte Entry, Objekt liegt in einem Object-Stream | Objekt-Nummer des Object-Streams | Index innerhalb des Object-Streams |
Typ 2 ist der eigentliche Hebel: Er erlaubt, ein PDF-Objekt nicht direkt in der Datei, sondern in einem komprimierten Object-Stream zu speichern. Das ist genau die Kombination, die moderne PDFs so klein macht: Hunderte von kleinen Dictionary-Objekten landen gebündelt in einem einzigen Flate-komprimierten Stream.
Object-Streams: der natürliche Partner
Ein Object-Stream ist ein Container, der mehrere andere PDF-Objekte enthält, komprimiert. Er hat selbst eine Objekt-Nummer und einen eigenen Eintrag im xref-Stream (Typ 1). Im Header des Object-Streams steht, welche Objekt-Nummern darin liegen und an welchen Offsets im Stream-Inhalt sie beginnen.
Wichtig ist: Nur Objekte ohne eigenen Stream-Inhalt (also Dictionary- und Array-Objekte) dürfen in Object-Streams. Stream-Objekte selbst, also Content-Streams einer Seite, Bilder und eingebettete Schriften, bleiben außerhalb. Das ist auch der Grund, warum die Datei trotz aller Object-Stream-Konsolidierung viele Top-Level-Objekte enthält: jedes Bild, jeder Content-Stream ist eins davon.
Hybrid-Files und Kompatibilität
Wer mit gemischten Reader-Versionen umgehen muss, kann eine Hybrid-Datei
bauen: Sie enthält sowohl eine klassische
xref-Tabelle als auch einen xref-Stream. Ältere Reader (PDF 1.4
und früher) finden die textuelle Tabelle und ignorieren den Stream;
neuere Reader sehen im Trailer der Tabelle den Eintrag /XRefStm
mit dem Byte-Offset zum Stream und nehmen
diesen.
Hybrid-Files haben drei Nachteile, die in der Praxis fast immer gegen sie sprechen: Sie sind größer als reine xref-Stream-Dateien, sie verdoppeln die Wartungs-Logik beim Schreiben, und sie werden durch Tools, die nur das eine oder das andere kennen, leicht inkonsistent. PDF 2.0 erlaubt sie weiterhin, der ISO-Norm-Text betont aber, dass neue PDF-Werkzeuge sie nur in expliziten Kompatibilitäts-Szenarien erzeugen sollen.
Bei inkrementellen Updates, also wenn eine bestehende
PDF-Datei um neue Objekte erweitert wird, ohne den Rest neu zu
schreiben, hängt sich der neue xref-Stream einfach hinten an. Sein
Dictionary verweist über /Prev
auf den Byte-Offset des
vorherigen xref-Streams (oder der vorherigen Tabelle). Reader folgen
dieser Kette rückwärts, bis sie an einer Entry ohne /Prev
ankommen.
/XRefStm
zum Stream. Praktisch, aber größer und daher
meist vermieden.Tooling: qpdf und pikepdf
In der Praxis musst du xref-Streams selten von Hand parsen, denn die etablierten Open-Source-Bibliotheken erledigen das automatisch. Trotzdem hilft es, zu wissen, welche Hebel sie anbieten.
qpdf: der Schreib-Modus
qpdf(in C++ geschrieben, mit CLI und C-API) bietet drei Modi für das Schreiben von Object-Streams, jeweils mit passender xref-Variante:
-
--object-streams=disable: keine Object-Streams, und zugleich eine klassische xref-Tabelle. Erzeugt PDFs, die auch alte Reader öffnen können. -
--object-streams=preserve: Default. Übernimmt das Format der Quell-Datei. -
--object-streams=generate: bündelt Objekte in Object-Streams (Gruppen von max. 100), erzeugt einen xref-Stream und hebt die PDF-Version auf mindestens 1.5 an.
pikepdf: die Python-Sicht
pikepdf
wickelt qpdf in Python-Bindings ein. Object-
und xref-Streams werden beim Öffnen automatisch dekomprimiert; im
Python-Code arbeitet man mit normalen pikepdf.Object
-
Instanzen. Beim Speichern lässt sich das Stream-Verhalten über die
Optionen von Pdf.save()
steuern. Ein praktisches Detail
für Forensik-Tools: Pdf.open(..., ignore_xref_streams=True)
zwingt pikepdf, eventuell vorhandene xref-Streams zu ignorieren und
ausschließlich der textuellen Tabelle zu folgen.
pikepdf: Stream-Verhalten steuern
# Beim Schreiben Object-Streams erzwingen (kompakte PDFs) import pikepdf with pikepdf.open("eingabe.pdf") as pdf: pdf.save( "ausgabe.pdf", object_stream_mode=pikepdf.ObjectStreamMode.generate, compress_streams=True, ) # Beim Öffnen xref-Streams ignorieren (für Forensik) pdf = pikepdf.open("verdaechtig.pdf", ignore_xref_streams=True)
Mythen-Prüfung
PDF-Pipeline auf PDF 2.0 umstellen?
Wir begleiten Entwicklungsteams beim Wechsel von klassischen xref-Tabellen auf xref-Streams, beim Aufbau eigener PDF-Validator- Pipelines und beim Audit bestehender PDF-Erzeuger im Kontext von Barrierefreiheit und Langzeitarchivierung.
Beratung oder Schulung anfragen
