PDF von Grund auf selbst schreiben (01): Hello, World – Erstellen eines minimalen PDF
PDF von Grund auf selbst schreiben (01): Hello, World – Erstellen eines minimalen PDF
Ziel der Serie: Verstehen Sie PDF als ein lesbares Dateiformat – beginnen Sie mit einem minimalen Beispiel, das "funktioniert", und erweitern Sie es schrittweise auf Grafiken, mehrere Seiten, Kompression und Ressourcenwiederverwendung.
Inhaltsverzeichnis der Serie
- Artikel 01 (dieser): Schreiben Sie ein minimales PDF (1 Seite + 1 Zeile Text) und vervollständigen Sie es mit einem Tool zu einem standardmäßigen, öffnbaren PDF
- Artikel 02: Linien/ Rechtecke im Inhaltsstrom zeichnen (Verstehen von Pfaden, Konturen, Füllungen)
- Artikel 03: Mehrseitige PDFs (Wie der Seitenbaum entsteht)
- Artikel 04: Näher an der realen Welt (Kompressionsströme, Ressourcenwiederverwendung, optionale Strukturen usw.)
Warum ist es wichtig, die zugrunde liegende Struktur von PDF zu verstehen?
PDF (Portable Document Format, tragbares Dokumentenformat) ist eines der beliebtesten Seitenbeschreibungssprachen der heutigen Zeit. Im Gegensatz zu HTML/CSS, das "Inhalt und Präsentation trennt und umfließend (Reflowable)" ist, betont PDF mehr feste Layouts und WYSIWYG (What You See Is What You Get) – unabhängig davon, auf welchem Gerät es geöffnet wird, bleibt das Layout konsistent.
Das Verständnis der zugrunde liegenden Struktur von PDF hat mehrere praktische Vorteile:
- Fehlerbehebung bei PDF-Generierungsproblemen: Wenn Sie beim Generieren von PDFs mit einer Codebibliothek auf Fehler stoßen, können Sie nur dann schnell das Problem lokalisieren, wenn Sie die zugrunde liegende Struktur verstehen.
- Automatisierte Verarbeitung: Bei der massenhaften Extraktion von Text, dem Zusammenführen von Dokumenten, dem Hinzufügen von Wasserzeichen usw. können Sie nur dann präzise arbeiten, wenn Sie die Struktur verstehen.
- Sicherheitsaudit: Zu wissen, welche Inhalte in PDF eingebettet werden können (JavaScript, Anhänge, Formulare usw.), hilft bei der Sicherheitsanalyse.
- Lernen von Dateiformatdesign: Das Design von PDF mit "Objektgrafik + Zufallszugriff" ist ein klassisches Beispiel, das es wert ist, studiert zu werden.
Vorbereitung
In diesem Artikel werden wir eine "strukturierte, aber logische" hello-broken.pdf erstellen und dann mit pdftk die entscheidenden Strukturen automatisch vervollständigen und hello.pdf ausgeben.
- Benötigte Tools: pdftk (kostenloses Befehlszeilentool, unterstützt Windows/macOS/Linux)
- Ausgabedateien:
hello-broken.pdf(handgeschrieben),hello.pdf(nach der Reparatur öffnbar)
Kernkonzept: Die dreischichtige Struktur von PDF
Das Wichtigste beim Verständnis von PDF ist, ein dreischichtiges mentales Modell zu etablieren:

1. Objektschicht (Dokumenteninhalt)
PDF-Dokumente bestehen aus vielen Objekten, die durch indirekte Referenzen (z. B. 2 0 R) zu einem Diagramm verbunden sind. Häufige Objekttypen:
| Typ | Beispiel | Beschreibung |
|---|---|---|
| Name | /Page | Mit / beginnender Name |
| Ganzzahl/Fließkommazahl | 50, 36.0 | Zahlenwert |
| Zeichenfolge | (Hello, World!) | In runden Klammern |
| Array | [0 0 612 792] | Geordnete Sammlung |
| Wörterbuch | << /Type /Page >> | Sammlung von Schlüssel-Wert-Paaren |
| Indirekte Referenz | 2 0 R | Verweist auf Objekt 2 (Generierungsnummer 0) |
| Strom | stream...endstream | Binärdaten (z. B. Zeichnungsanweisungen, Bilder) |
2. Inhaltschicht (Seiteninhalt)
Die tatsächlichen Anweisungsfolgen, die "Text/Grafiken auf die Seite zeichnen", werden normalerweise in stream ... endstream geschrieben. Das Format ist: Operanden zuerst, Operatoren danach.
/F0 36 Tf ← Operand: /F0, 36 Operator: Tf (Schriftart setzen)
(Hello, World!) Tj ← Operand: Zeichenfolge Operator: Tj (Text zeichnen)
3. Dateistruktur (File Structure)
Ermöglicht es dem Leser, schnell zufällig auf beliebige Objekte zuzugreifen, ohne von Anfang bis Ende lesen zu müssen:
| Element | Funktion |
|---|---|
%PDF-1.x | Dateikopf, kennzeichnet die PDF-Version |
xref | Kreuzreferenztabelle: Objektnummer → Byte-Offset |
trailer | Enddiktat: verweist auf das Wurzelobjekt /Root |
startxref | Gibt die Startposition der xref-Tabelle an |
%%EOF | Dateiende-Markierung |
Welche Objekte benötigt ein minimales PDF?
Ein "minimales, aber anzeigbares" PDF hat folgende Referenzbeziehungen zwischen den Objekten:

Minimale Objektliste:
| Objekt | Funktion | Schlüsselattribute |
|---|---|---|
| Katalog | Wurzelobjekt, Dokumenteneingang | /Type /Catalog, /Pages |
| Seiten | Seitenbaum | /Type /Pages, /Kids, /Count |
| Seite | Einzelne Seite | /Type /Page, /MediaBox, /Resources, /Contents, /Parent |
| Ressourcen | Ressourcencontainer | /Font (Schriftartdiktat) |
| Schriftart | Schriftartdefinition | /Type /Font, /BaseFont, /Subtype |
| Inhalte | Inhaltsstrom | Strom von Zeichnungsanweisungen |
Praktische Übung: Schreiben Sie hello-broken.pdf
Erstellen Sie die Datei hello-broken.pdf und fügen Sie den folgenden Inhalt vollständig ein:
%PDF-1.0
1 0 obj
<< /Type /Pages
/Count 1
/Kids [2 0 R]
>>
endobj
2 0 obj
<< /Type /Page
/MediaBox [0 0 612 792]
/Resources 3 0 R
/Parent 1 0 R
/Contents [4 0 R]
>>
endobj
3 0 obj
<< /Font
<< /F0
<< /Type /Font
/BaseFont /Times-Italic
/Subtype /Type1 >>
>>
>>
endobj
4 0 obj
<< >>
stream
1. 0. 0. 1. 50. 700. cm
BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Type /Catalog
/Pages 1 0 R
>>
endobj
xref
0 6
trailer
<< /Size 6
/Root 5 0 R
>>
startxref
0
%%EOF
Warum ist diese Datei "kaputt"?
Wir haben absichtlich folgende Inhalte weggelassen oder falsch ausgefüllt:
| Fehlende/fehlerhafte Elemente | Beschreibung |
|---|---|
xref Offset | Es wurde kein tatsächliches Byte-Offset für jedes Objekt angegeben |
startxref | Es wurde 0 angegeben, nicht die tatsächliche Position der xref |
/Length | Der Inhaltsstrom hat keine Längenangabe |
| Binärmarkierung | Es fehlt die Kopfzeile mit der binären Kennzeichnung |
Dies sind alles wichtige Informationen, die der Leser benötigt; das Fehlen kann dazu führen, dass die Datei nicht geöffnet werden kann oder nur fehlerhaft geöffnet wird.
Detaillierte Erklärung der wichtigen Inhaltsstromanweisungen
Der Inhaltsstrom befindet sich zwischen 4 0 obj in stream ... endstream, hier zeilenweise erklärt:
1. 0. 0. 1. 50. 700. cm ← Setzt die Transformationsmatrix (beachten Sie, dass 1. für die Fließkommazahl 1.0 steht)
BT ← Beginnt das Textobjekt
/F0 36. Tf ← Wählt die Schriftart F0, Schriftgröße 36pt
(Hello, World!) Tj ← Zeichnet die Zeichenfolge
ET ← Beendet das Textobjekt
Transformationsmatrix cm Operator
1 0 0 1 50 700 cm ist eine 6-Elemente-Transformationsmatrix [a b c d e f], die entspricht:
| a b 0 | | 1 0 0 |
| c d 0 | = | 0 1 0 |
| e f 1 | | 50 700 1 |
Wenn a=1, b=0, c=0, d=1 ist, handelt es sich um eine reine Verschiebungsmatrix, die den Ursprung des Koordinatensystems (also den Punkt (0,0), an dem die nachfolgenden Zeichnungsoperationen stattfinden) auf (50, 700) verschiebt. Wenn Sie nicht verschieben, befindet sich der Ursprung standardmäßig in der linken unteren Ecke der Seite.
Textoperationen
| Operator | Bedeutung | Beispiel |
|---|---|---|
BT | Begin Text, beginnt das Textobjekt | BT |
ET | End Text, beendet das Textobjekt | ET |
Tf | Setzt Schriftart und Schriftgröße | /F0 36 Tf |
Tj | Zeichnet die Zeichenfolge | (Hello!) Tj |
Verwenden Sie pdftk, um es in ein öffnbares PDF zu reparieren
Führen Sie im Verzeichnis von hello-broken.pdf Folgendes aus:
pdftk hello-broken.pdf output hello.pdf
Öffnen Sie hello.pdf mit einem beliebigen PDF-Reader, und Sie sollten auf der Seite "Hello, World!" sehen (Schriftart Times-Italic, 36pt, oben links auf der Seite).
Was hat pdftk für Sie vervollständigt?
| Vervollständigung | Beschreibung |
|---|---|
| Binärmarkierungszeile | Fügt nach %PDF-1.0 eine Zeile nicht druckbarer Zeichen hinzu, um sicherzustellen, dass sie als Binärdatei erkannt wird |
/Length | Berechnet und fügt die Byte-Länge für den Inhaltsstrom hinzu |
xref Tabelle | Berechnet das Byte-Offset für jedes Objekt und füllt es ein |
startxref | Füllt die tatsächliche Startposition der xref-Tabelle ein |
Warum benötigen wir xref / trailer / startxref?
Kernzweck: Zufallszugriff
Stellen Sie sich ein 500-seitiges PDF vor. Wenn es kein xref gibt, muss der Reader, um Seite 450 anzuzeigen, von Anfang an bis Seite 449 lesen – das ist zu langsam.
Mit xref kann der Reader:
- Zuerst
startxreflesen → die Position von xref finden trailerlesen → das Wurzelobjekt/Rootfinden- Vom Wurzelobjekt aus weiterverfolgen → direkt zum Objekt der Seite 450 springen
- Über xref das Byte-Offset dieses Objekts abfragen → direkt dorthin springen und lesen
Die Zeitkomplexität sinkt von O(n) auf O(1).
Übung in diesem Artikel
Es wird empfohlen, tatsächlich hello-broken.pdf zu ändern und dann erneut mit pdftk zu reparieren, um die Effekte zu beobachten:
| Übung | Änderungsinhalt | Beobachtungspunkt |
|---|---|---|
| A | Ändern Sie (Hello, World!) in einen anderen englischen Satz | Textänderung |
| B | Ändern Sie 36 in 12 oder 72 | Schriftgrößenänderung |
| C | Ändern Sie 50 700 in 50 100 | Position nach unten verschieben (PDF-Koordinatensystem hat den Ursprung in der linken unteren Ecke) |
| D | Ändern Sie /Times-Italic in /Helvetica oder /Courier | Schriftartenänderung |
| E | Ändern Sie /MediaBox [0 0 612 792] in [0 0 595 842] | Papier von US Letter auf A4 ändern |
Hinweis: Der Ursprung des PDF-Koordinatensystems befindet sich in der linken unteren Ecke der Seite, die Y-Achse zeigt nach oben.
(50, 700)bedeutet 50pt von der linken Seite und 700pt von der Unterseite.
Häufige Fragen
F: Warum verwenden wir Type1-Builtin-Schriftarten anstelle von TrueType?
A: Die 14 Standard-Schriftarten vom Typ 1 (Times, Helvetica, Courier usw.) müssen in PDF-Readern eingebaut sein und müssen nicht in Schriftartdateien eingebettet werden, was am einfachsten ist. In der realen Anwendung müssen Schriftarten normalerweise eingebettet werden, um plattformübergreifende Konsistenz zu gewährleisten.
F: Was sind die Zahlen in /MediaBox [0 0 612 792]?
A: Die Einheit ist Punkt (1 Punkt = 1/72 Zoll). 612 × 792 Punkte = 8.5 × 11 Zoll = US Letter-Papier. A4 ist 595 × 842 Punkte.
F: Was ist die Generierungsnummer (z. B. die 0 in 2 0 R)?
A: Sie wird für inkrementelle Updates verwendet. Wenn ein Objekt geändert wird, wird die Generierungsnummer um 1 erhöht. In neu erstellten PDFs haben alle Objekte normalerweise die Generierungsnummer 0.
Vorschau auf den nächsten Artikel
Im Artikel 02 werden wir weiterhin die Methode "Inhaltsstrom selbst schreiben" verwenden und die grundlegendsten Grafikpfadoperationen hinzufügen:
m(moveto),l(lineto): Pfad definierenS(stroke): Kontur zeichnenre(rectangle),f(fill): Rechteck zeichnen und füllen
Damit können Sie auf derselben Seite gleichzeitig zeichnen: Titeltext + eine horizontale Trennlinie + ein Rechteck, vom "Schreiben" zum "Zeichnen von Grafiken" übergehen.
