Eine PDF-Datei ist im Grunde eine Sammlung von Objekten, die aufeinander verweisen. Lässt man die Komprimierung, die Querverweis-Buchführung und die Byte-Offsets weg, bleibt ein Graph: eine kleine Menge getypter Werte, die durch Referenzen miteinander verdrahtet sind und an einem einzigen Objekt wurzeln, das der Reader zu finden weiß. Alles, was ein PDF ausdrücken kann, von einem Textabsatz über eine eingebettete Schriftart bis hin zu einer digitalen Signatur, wird aus acht primitiven Objekttypen und der Regel, die es einem Objekt erlaubt, auf ein anderes zu verweisen, gebildet. Wenn man diese lernt, liest sich der Rest des Formats eher als Komposition denn als Mysterium
Dies ist die logische Schicht von PDF, definiert in ISO 32000-1 Klausel 7.3, und sie sitzt eine Ebene über dem physischen Dateilayout (dem Header, dem Body, der Querverweistabelle und dem Trailer, der ein eigenes Thema im technischen Überblick zur PDF-Dateistruktur ist). Das logische Modell ist das, was diese Bytes bedeuten, sobald sie analysiert wurden. Ein Viewer liest die Datei rückwärts, um den Trailer zu finden, folgt diesem zum Stamm (Root) und von dort entfaltet sich das Dokument als Objekte, die auf Objekte verweisen. Das ist der Teil, über den Sie nachdenken, wenn Sie eine fehlerhafte Seite debuggen, einen Parser schreiben oder darauf vertrauen, dass eine Bibliothek ein Dokument zusammenstellt
Acht Objekttypen und sonst nichts
PDF definiert genau acht grundlegende Objekttypen. Jeder Wert in einem Dokument ist einer von ihnen, was das Format trotz seiner Reichweite nachvollziehbar hält
Booleans (Wahrheitswerte) sind die Schlüsselwörter true und false. Sie schalten Flags ein und aus, wie z. B. ob eine Anmerkung gedruckt wird
Numbers (Zahlen) gibt es in zwei Ausprägungen, die die Spezifikation als einen Typ behandelt: ganze Zahlen (Integer) wie 42 und reelle Zahlen (Reals) wie 3.14 oder -0.002. PDF hat keine Exponentenschreibweise, daher werden Sie in einer konformen Datei niemals 1e6 sehen. Koordinaten, Schriftgrößen und Drehwinkel sind allesamt Zahlen
Strings (Zeichenfolgen) enthalten Byte-Sequenzen, die entweder in Klammern, (Hello), oder in spitzen Klammern als Hexadezimalwerte, <48656C6C6F>, geschrieben werden. Beide Notationen kodieren identischen Inhalt; Hexadezimal ist das Ausweichmanöver für Bytes, die innerhalb von Klammern ungünstig sind. Strings transportieren Text, aber sie sind in erster Linie Bytes, was in dem Moment wichtig wird, in dem Sie etwas jenseits von ASCII verarbeiten
Names (Namen) sind atomare Token, die mit einem Schrägstrich eingeleitet werden: /Type, /Pages, /MediaBox. Ein Name ist kein String; er ist ein Identifikator, der als Wörterbuchschlüssel (Dictionary-Key) oder als Aufzählungswert verwendet wird, und zwei Namen sind nur dann gleich, wenn sie Byte für Byte übereinstimmen. Der Schrägstrich ist Syntax, kein Teil des Namens. Dies verwirrt Anfänger, die /Times-Roman und den String (Times-Roman) als austauschbar behandeln; das Format tut dies nicht
Arrays (Datenfelder) sind geordnete, heterogene Listen in eckigen Klammern: [0 0 612 792] ist ein Seitenrechteck, und ein Array kann Typen frei mischen, einschließlich Referenzen auf andere Objekte. Dictionaries (Wörterbücher) sind die Arbeitspferde. Geschrieben zwischen << und >>, bildet ein Dictionary Namensschlüssel auf Werte eines beliebigen Typs ab, und fast jede bedeutsame Struktur in PDF – Seite, Katalog, Schriftart, Anmerkung – ist ein Dictionary mit einem /Type-Schlüssel, der deklariert, was es ist
Streams (Datenströme) sind Dictionaries mit einem Anhang aus Rohbytes zwischen den Schlüsselwörtern stream und endstream. Das Dictionary beschreibt die Bytes (ihre Länge und etwaige Filter wie FlateDecode, die sie komprimieren), und die Bytes transportieren die sperrige Nutzlast: Anweisungen für den Seiteninhalt, eingebettete Schriftprogramme, Bilder. Ein Stream ist der Ort, an dem PDF alles ablegt, was zu groß oder zu binär ist, um inline zu stehen
Der achte Typ ist das Null-Objekt, das Schlüsselwort null. Es ist ein echter Wert und unterscheidet sich davon, dass ein Schlüssel fehlt. Ein Dictionary-Eintrag, der auf null gesetzt ist, wird so behandelt, als wäre er nicht vorhanden, und eine Referenz, die zu einem nicht existierenden Objekt aufgelöst wird, ergibt ebenfalls null statt eines Fehlers. Dieses nachsichtige Verhalten ist Absicht: Es ermöglicht einer beschädigten Datei, mit Einschränkungen zu funktionieren, anstatt das Öffnen zu verweigern. Es gibt keinen neunten Typ; alles, was PDF ausdrückt, entsteht aus der Kombination dieser acht
Direkte Werte, indirekte Objekte und Referenzen
Jeder dieser acht Typen kann auf zwei Arten vorkommen. Ein direktes Objekt wird an Ort und Stelle geschrieben, wie die 612 innerhalb eines MediaBox-Arrays. Ein indirektes Objekt erhält eine Identität, damit andere Objekte darauf verweisen können: zwei Ganzzahlen, eine Objektnummer und eine Generationsnummer, die die Definition in obj und endobj einwickeln:
12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
Dies ist Objekt 12, Generation 0, ein Schriftarten-Dictionary. Überall sonst in der Datei verweist ein anderes Objekt mit einer indirekten Referenz darauf: dieselben zwei Zahlen, gefolgt vom Schlüsselwort R, 12 0 R. Die Referenz ist ein Zeiger. Wenn das Ressourcen-Dictionary einer Seite /Font << /F1 12 0 R >> sagt, benennt es Objekt 12 als die Schriftart hinter dem Ressourcennamen /F1, ohne die Definition der Schriftart in die Seite zu kopieren
Die Generationsnummer existiert für Löschungen und Wiederverwendung. Wenn ein Objekt freigegeben und sein Slot wiederverwendet wird, erhöht sich die Generation, sodass ein veraltetes 12 0 R nicht auf den neuen Mieter von Slot 12 auflösen kann. Frisch geschriebene Dateien sind fast alle Generation 0, aber eine stark bearbeitete Datei kann höhere Nummern tragen, und ein Parser, der die Generation ignoriert, wird schließlich das falsche Objekt lesen
Indirektion macht PDF effizient und bearbeitbar. Eine Schriftart, ein Bild oder ein Farbraum kann einmal definiert und von hundert Seiten aus referenziert werden. Eine kleine Änderung kann als neue Revision angehängt werden, die ein einzelnes Objekt ersetzt, anstatt die Datei neu zu schreiben. Die Querverweistabelle ist der Index, der eine Objektnummer in einen Byte-Offset verwandelt, sodass der Reader direkt zu 12 0 obj springt, ohne zu scannen, aber das ist eine physische Optimierung. Logisch gesehen müssen Sie nur wissen, dass 12 0 R „das als 12 0 identifizierte Objekt“ bedeutet
Der Katalog: Wo jedes Dokument beginnt
Das Auflösen von Referenzen muss irgendwo beginnen, und dieser Ort ist der /Root-Eintrag des Trailers, der auf den Dokumentkatalog (Document Catalog) verweist: die Wurzel des Objektgraphen, ein Dictionary mit /Type /Catalog. Der Reader erreicht ihn zuerst, weil der Trailer zuerst gefunden wird, und von dort aus ist jeder andere Teil des Dokuments erreichbar, indem man den Referenzen folgt
Der Katalog trägt nur zwei zwingend erforderliche Einträge: seinen /Type und /Pages, eine indirekte Referenz auf die Wurzel des Seitenbaums. Der Rest ist optional und beschreibt eher das dokumentweite Verhalten als den Inhalt: /Outlines verweist auf den Lesezeichenbaum, /Names hält Namensbäume, die durch Strings geschlüsselt sind, /Metadata referenziert einen XMP-Metadaten-Stream, und /PageMode sowie /PageLayout schlagen vor, wie ein Viewer das Dokument öffnen soll. Nichts davon wird benötigt, um eine Seite zu rendern; sie konfigurieren das Erlebnis rund um die Seiten. Die Lesezeichen-, Metadaten- und Anmerkungsstrukturen, die am Katalog hängen, werden im Artikel über PDF-Metadaten, Lesezeichen und Anmerkungen behandelt
Das folgende Diagramm zeigt, wo der Objekt-Body in der umgebenden Datei sitzt. Der Katalog und der Seitenbaum leben innerhalb dieses Bodys als gewöhnliche indirekte Objekte; der Header, die Querverweistabelle und der Trailer um sie herum sind das physische Gerüst, das es einem Reader ermöglicht, sie zu lokalisieren

Der Seitenbaum: Eine ausgewogene Hierarchie von Seiten
Von /Pages verzweigt sich das Dokument in den Seitenbaum, wo sich PDFs Wahl eines Graphen gegenüber einer flachen Liste auszahlt. Seiten werden nicht als einfache Sequenz gespeichert; sie hängen an einem Baum, dessen innere Knoten Seitenbaumknoten (/Type /Pages) und dessen Blätter Seitenobjekte (/Type /Page) sind. Ein innerer Knoten listet seine Kinder in einem /Kids-Array auf und verzeichnet in /Count, wie viele Blatt-Seiten unter ihm leben. Jeder Knoten außer der Wurzel trägt eine /Parent-Referenz nach oben, sodass der Baum in beide Richtungen durchlaufen werden kann
2 0 obj % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj
3 0 obj % a leaf page
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources << /Font << /F1 12 0 R >> >>
/Contents 5 0 R >>
endobj
4 0 obj % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj
Hier ist Objekt 2 die Wurzel, mit drei Seiten darunter: dem Blatt der Seite 3 plus zwei weiteren, die über den inneren Knoten 4 erreichbar sind. Die /Count der Wurzel von 3 muss der Gesamtzahl der darunterliegenden Blätter entsprechen, und eine Anzahl, die nicht mit der tatsächlichen Struktur übereinstimmt, ist ein häufiger Weg, wie eine handbearbeitete Datei fehlschlägt. Der Sinn des Baumes ist die Lokalität des Zugriffs. Ein Reader, der Seite 900 eines tausendseitigen Dokuments öffnet, durchläuft keine 900 Objekte; er steigt eine Handvoll Knoten hinab, weil ein wohlgeformter Baum flach und ausgewogen bleibt. Einen solchen Baum von Hand zu bauen, ist fummelig genug, um es wert zu sein, es von Anfang bis Ende zu sehen, was der Leitfaden zum Erstellen eines PDF-Dokuments von Grund auf tut
Der Baum verdient seinen zweiten Lebensunterhalt durch Vererbung (Inheritance). Eine Handvoll Seitenattribute, /Resources, /MediaBox, /CropBox und /Rotate, können an einem inneren Knoten gesetzt und auf den einzelnen Seiten weggelassen werden, die dann den Wert des nächsten Vorfahren erben. Setzen Sie /MediaBox einmal an der Wurzel und jedes Blatt erhält dieselbe Seitengröße, ohne sie zu wiederholen; eine Seite, die abweichen muss, deklariert ihre eigene. Dies ist der einzige Ort im Objektmodell, an dem die Bedeutung eines Wertes von der Position eines Objekts im Baum abhängt, nicht nur von seinem eigenen Inhalt
Was eine Blattseite tatsächlich enthält
Ein Seitenobjekt ist der Verbindungspunkt zwischen dem Strukturmodell und dem sichtbaren Inhalt. Sein /Contents-Eintrag verweist auf einen oder mehrere Content-Streams, die Zeichenoperatoren, die Text und Grafiken auf die Seite malen. Sein /Resources-Dictionary benennt die Schriftarten, Bilder und Farbräume, auf die sich diese Operatoren stützen, wobei jeder Eintrag eine indirekte Referenz auf ein objektübergreifend genutztes Objekt ist. Die /MediaBox gibt das Seitenrechteck in Punkten (1/72 Zoll) an, und Einträge wie /Rotate und /CropBox passen an, wie es präsentiert wird
Diese Arbeitsteilung ist das gesamte Modell im Kleinen. Das Seiten-Dictionary ist Struktur: getypte Einträge und Referenzen, die sagen, was die Seite ist und womit sie zeichnet. Der Content-Stream besteht aus Anweisungen: ein separater, komprimierbarer Block (Blob), der sagt, wie gezeichnet werden soll. Die Schriftart hinter /F1 ist eine gemeinsam genutzte Ressource, die einmal definiert und überall dort referenziert wird, wo sie verwendet wird. Dictionary, Stream und Referenz arbeiten zusammen, um eine Seite zu rendern, und dieselben Muster skalieren auf das gesamte Dokument. Die Content-Stream-Operatoren innerhalb dieses Blocks werden separat für Text und Schriftarten sowie für Grafiken und visuelle Elemente behandelt
Warum es sich lohnt, dieses Modell zu kennen
Die meisten Entwickler begegnen dem Objektmodell nur dann, wenn etwas kaputtgeht: Eine Seite wird leer gerendert, weil ihre /Contents-Referenz ins Leere läuft, Text wird als Kästchen ausgegeben, weil eine Schriftartenressource nie eingebettet wurde, ein Tool meldet einen /Count, der nicht mit den gefundenen Seiten übereinstimmt. Jedes davon ist eine Aussage über den Graphen, und den Graphen direkt zu lesen, ist besser als zu raten. Die acht Typen und die Referenzregel sind ein Vokabular, das klein genug ist, um es im Kopf zu behalten, und sobald Sie ein PDF als Objekte sehen, die auf Objekte verweisen, hören fehlerhafte Dateien auf, undurchsichtig zu sein
Davon abgesehen ist das manuelle Schreiben des Modells abseits von Lernzwecken selten die richtige Entscheidung. Das Konsistenthalten von Querverweis-Offsets, Generationsnummern, Seitenbaum-Zählungen und Stream-Längen über Bearbeitungen hinweg ist die Art von Buchführung, für deren Bewältigung eine Bibliothek existiert. In der Produktion verwaltet eine ausgereifte PDF-Entwicklungsbibliothek den Objektgraphen, während Sie in Seiten und Inhalten denken können. Das Modell zu kennen, zahlt sich dennoch aus: Sie verstehen, was die Bibliothek darunter aufbaut und warum