Zum Inhalt springen
getnextpdf.com

Die Anatomie einer PDF-Datei

Öffnen Sie ein beliebiges PDF in einem einfachen Texteditor, und das Erste, was Sie sehen, ist beruhigend: ein %PDF-1.x- oder %PDF-2.0-Header. Das Letzte, was Sie sehen, ist %%EOF. Alles zwischen diesen beiden Zeilen ist eine adrette kleine Maschine, um Objekte nach Nummer zu finden. Diese Seite ist eine Sezierung. Wir öffnen die Datei, benennen jedes Organ und zeigen, wie sie zusammenhängen.

Es ist der strukturelle Begleiter zweier Nachbarn. Was ein PDF wirklich ist behandelt die Datei als Objektgraphen; inkrementelle Aktualisierungen decken ab, wie sie im Laufe der Zeit wächst. Diese Seite bleibt nah an den Bytes — den physischen Regionen, die ein Parser durchläuft, in der Reihenfolge, in der sie auf der Festplatte liegen.

Sie brauchen das fast nie, um ein PDF zu verwenden. Sie brauchen es an dem Tag, an dem eines schiefgeht. Eine Datei öffnet sich in einem Viewer und in einem anderen nicht; ein Validator meldet eine „beschädigte Querverweistabelle”; ein signiertes Dokument lässt sich plötzlich nicht mehr verifizieren. Keines davon ist ein Rätsel, sobald Sie die Anatomie lesen können. Sie sind eine Zahl, die nicht mehr zu einer Position passt, eine Region am falschen Ort oder ein Ende, das ins Leere zeigt.

Das Layout zu kennen, verwandelt „das PDF ist beschädigt” in eine Diagnose, nach der Sie handeln können. Es ist der Unterschied zwischen dem Achselzucken vor einer Blackbox und dem Zeigen auf genau das Byte, das lügt.

Ein konformes PDF hat vier physische Teile, in dieser Dateireihenfolge (Spec: ISO 32000-2, §7.5.1):

  1. Einen Header — eine Zeile, %PDF-2.0, die die Version benennt.
  2. Einen Rumpf — der Großteil der Datei: eine Folge nummerierter indirekter Objekte.
  3. Einen Querverweisabschnitt — einen Index von der Objektnummer zum Byte-Offset, an dem dieses Objekt liegt. Klassische PDFs verwenden eine Text-Tabelle; PDF 2.0 verwendet einen komprimierten Querverweis-Stream.
  4. Einen Trailer — ein kleines Dictionary, das den Einstiegspunkt benennt, gefolgt von startxref, einem Offset und %%EOF.

Die Wendung: Ein Reader beginnt nicht oben. Er beginnt unten, liest startxref, um den Index zu finden, und nutzt diesen Index, um jedes Objekt direkt zu erreichen. Die Datei wird von vorne nach hinten geschrieben, aber von hinten nach vorne gelesen.

Gehen wir die vier Regionen der Reihe nach durch, mit den Bytes vor uns.

Der Header ist eine Zeile. NextPDF schreibt %PDF-2.0 und üblicherweise eine zweite Kommentarzeile aus High-Bit-Bytes, damit naive Übertragungswerkzeuge die Datei als binär und nicht als Text behandeln. Diese zweite Zeile ist der Grund, warum ein als reiner Text geöffnetes PDF gleich nach der Version ein wenig Kauderwelsch zeigt.

Der Rumpf ist, wo das Dokument lebt. Jedes indirekte Objekt ist eine Zahl, eine Generation, das Schlüsselwort obj, ein Wert und endobj (Spec: ISO 32000-2, §7.3.10). Der Wert ist eine von wenigen Gestalten — aber zwei tragen fast das gesamte Gewicht:

  • Ein Dictionary, << /Key value … >>, ist eine Zuordnung von Namen zu Werten. Der Seitenbaum, der Katalog, die Schriftdeskriptoren: alles Dictionaries.
  • Ein Stream ist ein Dictionary, gefolgt von stream, einem Block beliebiger Bytes und endstream. Seiteninhalt, eingebettete Schriften und Bilder sind Streams, fast immer komprimiert. (Ihre Filter sind eine Geschichte für sich, erzählt in Streams und Filter.)

Objekte zeigen aufeinander per indirekter Referenz2 0 R bedeutet „Objekt 2, Generation 0.” Das ist die Verdrahtung, die aus einer flachen Liste von Objekten einen Graphen macht.

Der Querverweisabschnitt ist der Teil, den die meisten Menschen nie korrekt vor sich sehen. In der klassischen Form ist er reiner Text: das Schlüsselwort xref, dann Unterabschnitte aus festbreiten 20-Byte-Zeilen (Spec: ISO 32000-2, §7.5.4). Jede Zeile ist ein zehnstelliger Byte-Offset, eine fünfstellige Generation und ein einzelnes Flag — n für in Verwendung, f für frei — auf genau zwanzig Bytes aufgefüllt, damit ein Reader allein durch Arithmetik zu jedem Eintrag springen kann. PDF 2.0 ersetzt dies durch einen Querverweis-Stream: derselbe Index, aber binär und komprimiert innerhalb eines Stream-Objekts, das mit /Type /XRef markiert ist (Spec: ISO 32000-2, §7.5.8). Kleiner und in der Lage, Objekte zu beschreiben, die in Objekt-Streams gepackt sind.

Der Trailer ist das Inhaltsverzeichnis der Datei (Spec: ISO 32000-2, §7.5.5). Er benennt /Root — den Dokumentkatalog, das einzige Objekt, an dem alles andere hängt — und /Size, die Objektanzahl. Dann kommt der Handschlag, der das Rückwärtslesen möglich macht: startxref, ein Byte-Offset in einer eigenen Zeile und %%EOF. Ein Reader springt ans Ende, liest diesen Offset, springt direkt zum Querverweisabschnitt und ist unterwegs.

  1. HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
  2. BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
  3. Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
  4. TrailerNames /Root and /Size, then startxref + offset + %%EOF.
  5. Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

Es gibt eine fünfte Region, die eine langlebige Datei wachsen lässt: eine inkrementelle Aktualisierung. Eine Änderung wird nicht an Ort und Stelle geschrieben. Die geänderten Objekte, ein frischer Querverweisabschnitt und ein neuer Trailer werden nach dem ersten %%EOF angehängt, und dieser neue Trailer trägt /Prev — den Offset des vorherigen Querverweisabschnitts (Spec: ISO 32000-2, §7.5.6). Die Abschnitte bilden eine rückwärts gerichtete Kette; für jede Objektnummer gewinnt der neueste Eintrag. Weil sich die ursprünglichen Bytes nie bewegen, hält die kryptografische Prüfung über den Byte-Bereich, den eine Signatur tatsächlich abdeckt, auch nach einer Aktualisierung noch. Eine spätere inkrementelle Aktualisierung kann immer noch ändern, was ein Validator über das Dokument als Ganzes meldet — ob die Änderungen nach der Signatur erlaubt sind und was die Signatur zu zertifizieren gilt — aber sie kann die signierten Bytes selbst nicht verändern. Diese Eigenschaft ist das ganze Thema von inkrementellen Aktualisierungen.

Hier ist die gesamte Anatomie in einer minimalen Datei. Die Zahlen unter xref sind Byte-Offsets, und sie müssen genau sein — zeigen Sie ein Zeichen hinter den Beginn eines Objekts, und ein strikter Reader gibt auf.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

Lesen Sie es so, wie ein Parser es tut. Letzte Zeile: %%EOF. Darüber: startxref 186, also springen Sie zu Byte 186, wo xref beginnt. Die Tabelle sagt, Objekt 1 liegt bei Byte 9. Das /Root 1 0 R des Trailers zeigt dorthin — den Katalog — und vom Katalog aus folgen Sie /Pages zum Seitenbaum und finden die einzelne Seite. Objekt 0 ist immer der Kopf der Freiliste mit Generation 65535, ein Fossil aus dem ersten Entwurf des Formats, das jeder Reader noch immer zu sehen erwartet.

Die Falle besteht darin, ein PDF wie eine Geschichte zu lesen — von oben nach unten, der Reihe nach. Es ist keine Geschichte; es ist ein Index mit einem Rückwärtszeiger. Objektnummern müssen in der Datei nicht fortlaufend sein, Objekte können in beliebiger physischer Reihenfolge auftreten, und ein Reader verlässt sich nie auf ihre Position. Die einzige maßgebliche Karte ist der Querverweisabschnitt, und der einzige Weg, diese Karte zu finden, ist der startxref-Offset ganz am Ende.

Die Folge überrascht die Leute. Ein PDF mit einem makellosen Rumpf und einer falschen Ziffer in startxref ist unlesbar — der Reader kann den Index nicht finden. Ein PDF mit seinen Objekten in durcheinandergewürfelter Reihenfolge, aber einem korrekten Querverweisabschnitt ist vollkommen in Ordnung. Die physische Position trägt keine Bedeutung. Die aufgezeichnete Position trägt sie ganz.

Diese Seite beschreibt die physische Struktur, nicht den Seiteninhalt. Wie Marken auf einer Seite landen — Inhaltsstrom-Operatoren, Textausgabe, Grafikzustand — ist ein eigenes Thema. Sie beschreibt außerdem eine wohlgeformte Datei. PDFs aus der echten Welt sind häufig ein wenig kaputt und überleben nur, weil nachsichtige Viewer die Querverweistabelle wiederaufbauen, indem sie nach obj-Schlüsselwörtern suchen. Diese Rettung ist ein Verhalten des Viewers, nicht etwas, das das Format garantiert.

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF ist ein Writer. Er zeichnet jeden Offset aus dem Ausgabepuffer in dem Moment auf, in dem jedes Objekt ausgegeben wird, sodass die von ihm erzeugten Dateien einen Querverweisabschnitt haben, der konstruktionsbedingt zum Rumpf passt.
ProDas Parsen, Rekonstruieren oder Reparieren einer beschädigten Querverweistabelle in einer Datei, die NextPDF nicht geschrieben hat, liegt in jeder Edition außerhalb des Umfangs.
EnterpriseFür die Inspektion der Struktur einer bestehenden Datei verwenden Sie einen dedizierten Parser oder Validator; NextPDF garantiert Korrektheit für das, was es schreibt, nicht für das, was es liest.

Warum hat ein PDF nach dem Header eine Binärmarkierungszeile? Manche älteren Übertragungswerkzeuge würden eine Datei verstümmeln, die sie für reinen Text hielten. Der High-Bit-Kommentar lässt die Datei unmissverständlich binär aussehen, sodass sie die Reise unverändert übersteht.

Ist der Querverweis-Stream einfach eine kleinere Tabelle? Größtenteils, mit einer zusätzlichen Fähigkeit. Über das Komprimiertsein hinaus kann ein Querverweis-Stream Objekte beschreiben, die innerhalb von Objekt-Streams gespeichert sind — Einträge, die die klassische 20-Byte-Texttabelle nicht ausdrücken kann.

Kann ich in einer Datei sowohl eine Tabelle als auch einen Stream haben? Eine einzelne Revision verwendet das eine oder das andere. Aber eine Hybriddatei kann eine klassische Tabelle für alte Reader mit einem Querverweis-Stream für neue paaren, sodass jede Art von Reader einen Index findet, den sie versteht.

  • Header — die erste Zeile, %PDF-2.0, die die Version benennt; meist gefolgt von einem Binärmarkierungskommentar.
  • Indirektes Objekt — ein nummeriertes Objekt im Rumpf, geschrieben N G obj … endobj, wobei N die Objektnummer und G die Generation ist.
  • Dictionary — eine << /Key value … >>-Zuordnung von Namen zu Werten; die häufigste Objektgestalt.
  • Stream — ein Dictionary plus ein Block von Bytes zwischen stream und endstream, verwendet für Inhalt, Schriften und Bilder.
  • Querverweistabelle (xref) — der Index von der Objektnummer zum Byte-Offset; klassisch eine Texttabelle mit 20 Byte pro Eintrag, in PDF 2.0 ein /Type /XRef-Stream.
  • Trailer — das Dictionary, das /Root und /Size benennt, lokalisiert über den startxref-Offset am Ende der Datei.
  • Inkrementelle Aktualisierung — geänderte Objekte, ein neuer Querverweisabschnitt und ein neuer Trailer, die nach %%EOF angehängt werden, wobei /Prev zum vorherigen Abschnitt zurückverkettet.