Wie bei allen Textformaten haben auch die Daten in einem PDF eine bestimmte Syntax. Das sind bestimmte Regeln, wie die Daten in die Datei geschrieben werden. Bevor wir den Aufbau der Datei genauer anschauen, müssen wir erst mal diese Regeln kennen.
Alles im eigentlichen Dokumentkörper ist in „Objekten“ organisiert. Im Hallo Welt ist der Dokumentkörper der Teil zwischen %PDF-1.4
und xref
(nicht einschliesslich dieser Zeilen). Ein „Objekt“ im Sinne von PDF ist irgend eine Information. Es hat also nicht direkt mit objektorientierter Programmierung zu tun.
Von diesen Objekten gibt es verschiedene Typen, die verschiedene Informationen aufnehmen können, und verschieden in die Datei geschrieben werden. Es gibt aber eine universelle Regel: Zwei hintereinander stehende Objekte müssen durch ein oder mehrere Leerräume getrennt sein, ausser wenn der Beginn des nächsten Objekts von der Syntax her nicht ohnehin klar ist.
Eine generelle Regel muss noch beachtet werden: Es wird überall zwischen Gross- und Kleinschreibung unterschieden. Darum muss auch die Schreibweise immer exakt eingehalten werden.
Als Leerraum gelten der Leerschlag (ASCII 0x20), der Tabulator (ASCII 0X09), und der Zeilenumbruch. Was letzteren betrifft, so ist sowohl der von Windows verwendete IBM-Zeilenumbruch (ASCII 0x0D gefolgt von 0x0A), der unter anderem von Linux und Mac OS X verwendete Unix-Zeilenumbruch (ASCII 0x0A) und der bis Mac OS 9 verwendete Apple-Zeilenumbruch (ASCII 0X0D) erlaubt.
Im Grunde ebenfalls erlaubt sind das NUL-Zeichen (ASCII 0x00) und der Seitenvorschub (ASCII 0x0C). Diese Zeichen sind in der Praxis aber nicht verbreitet.
Alle Objekte sollten durch Leerräume getrennt sein. Art und Anzahl ist nicht relevant.
Zahlen können als Ganzzahlen oder Dezimalbrüche geschrieben werden. Ganzzahlen schreibt man einfach mit den Ziffern 0-9, bei negativen Zahlen mit einem vorangestellten Minus. Andere Zeichen sind nicht erlaubt, insbesondere keine Leerschläge oder Tausendertrenner.
Richtig:
1234 -1234
Falsch:
1'234 1234-
Bei Dezimalzahlen muss noch ein Dezimalpunkt an der richtigen Stelle gesetzt werden muss. Das Komma ist nicht erlaubt, ebensowenig Exponentialnotation.
Richtig:
123.4 0.1234
Falsch:
123,4 1.234e2
Namen sind kurze Texte, die dazu dienen, dokumentinterne Dinge zu bezeichnen. Sie werden dargestellt durch einen Schrägstrich gefolgt vom Text. Als Zeichen im Text sind alle druckbaren ASCII-Zeichen erlaubt (Bereich 0x20 – 0x7E) ausser Leerräumen, sowie den Zeichen #
,(
,)
,<
,>
,[
,]
,{
,}
,/
und %
.
/Type /MediaBox
Manchmal muss man Namen setzen, die Leerschläge oder andere problematische Zeichen enthalten. Diese können dargestellt werden, indem eine Raute gefolgt vom Hexcode des Zeichens verwendet wird. Die Kodierung sollte ASCII oder UTF-8 sein, zumal Programme, welche Namenobjekte anzeigen, normalerweise diese beiden Kodierungen erwarten.
Louis Grand | /Louis#20Grand |
Seite#34 | /Seite#2334 |
Höflinger | /H#C3#B6flinger |
Als weitere Einschränkung gilt: Der Code für das NUL-Zeichen (#00
) darf nicht vorkommen, und die übrigen Codes für Leerräume (#09
, #0A
, #0C
, #0D
, #20
) dürfen nicht unmittelbar auf den einleitenden Schrägstrich folgen.
Strings sind Texte, die zur Ausgabe in irgend einer Form gedacht sind. Sie werden einfach in runde Klammern eingeschlossen. Der Text selbst darf ohne weiteres runde Klammern enthalten, solange geöffnete Klammern mit nachfolgenden schliessenden Klammern ein Paar bilden (jede Klammer darf nur zu einem Paar gehören). Überzähligen Klammern muss ein Backslash vorangestellt werden. Backslashes, die im Text selbst enthalten sind, müssen verdoppelt werden.
Hallo Welt | (Hallo Welt) |
Text (mit Klammern) drinn | (Text (mit Klammern) drinn) |
Text (mit (unausgeglichenen Klammern) drinn | (Text \(mit (unausgeglichenen Klammern) drinn) |
Text \mit \Backslashes | (Text \\mit \\Backslashes) |
Die Zeichenkodierung ist ein Knackpunkt. Strings für Metadaten müssen in PDFDoc kodiert sein. Das ist eine Variante von ISO Latin-1 bei der manche Steuerzeichen durch zusätzliche, druckbare Zeichen ersetzt wurden. Ebenfalls ersetzt wurde das Zeichen mit dem Code 0xA0. Dieses steht in ISO Latin-1 für einen Abstand innerhalb eines Wortes, in PDFDoc hingegen für das Eurozeichen.
Strings für Texte auf dem „Papier“ hingegen sind in der Kodierung der jeweiligen Schrift. Das kann uns ein heilloses Kodierungsdurcheinander bescheren. Zum Glück gibt es einen Trick. Im Hallo Welt haben wir die Kodierung des Fonts mit /Encoding /WinAnsiEncoding
auf WinAnsi eingestellt. Das ist Adobes Name für die Kodierung „Windows westlich“.
Windows westlich ist wie PDFDoc eine Variante von ISO Latin-1, bei der einige Steuerzeichen durch druckbare Zeichen ersetzt wurden. Im Unterschied zu PDFDoc hat man dabei die Finger von bestehenden, druckbaren Zeichen gelassen. Wenn wir uns also festlegen, dass wir alle Fonts auf Windows westlich stellen, und uns in Metadatenstrings auf druckbare ISO Latin-1 Zeichen (ausser Code 0xA0) beschränken, dann können wir die Datei einfach als in Windows westlich kodiert betrachten.
Manchmal müssen wir Zeichenketten einfügen, die nicht einem Text nach ASCII, ISO Latin-1, PDFDoc oder Windows westlich entsprechen. Für solche Fälle gibt es die Hexstrings. Diese werden in spitze Klammern eingeschlossen (gemeint ist das „kleiner als“ und das „grösser als“ Zeichen), und enthalten pro Byte zwei Hexadezimalziffern. Leerräume werden ignoriert. Hier ein paar Varianten von „Hallo Welt“ als Hexstring:
<48 61 6C 6C 6F 20 57 65 6C 74> <48616C6C6F2057656C74> <48616C6C6F20 57656C74>
Dies sind drei wenig gebrauchte Spezialobjekte.
null
steht für „kein Wert“.true
steht für „zutreffend“.false
steht für „nicht zutreffend“.Arrayobjekte sind Sammlungen zusammengehöriger Objekte. Dazu werden die enthaltenen Objekte einfach – wie üblich durch Leerräume getrennt – hintereinander geschrieben. Die gesamte Liste wird mit eckigen Klammern umschlossen. Da das Array selbst auch ein Objekt ist, kann es ebenfalls in Arrays enthalten sein.
[1 2 3] [/Eins /Zwei /Drei] [/Ein /Array [/mit /verschachteltem] /Array]
Dictionaryobjekte sind ebenfalls Sammlungen zusammengehöriger Objekte. Im Unterschied zu Arrays bekommt aber jedes enthaltene Objekt einen Bezeichner. Die Reihenfolge der Objekte ist dabei egal (bei Arrays ist dies in der Regel nicht der Fall).
Dictionaries beginnen mit zwei offenen, spitzen Klammern, und enden mit zwei geschlossenen, spitzen Klammern. Innerhalb der Klammern notiert man abwechselnd einen Bezeichner und das bezeichnete Objekt (wiederum mit Leerräumen getrennt). Die Bezeichner müssen Namenobjekte sein. Das bezeichnete Objekt darf von einem beliebigen Typ ausser null
sein. Ist ein null
Objekt vorhanden, so gilt der entsprechende Eintrag als nichtexistent.
<< /Title (Hallo Welt) /Author (Peter Muster) >> << /Title (Hallo Welt) /Author (Peter Muster) >>
Wie auch bei Arrays gilt: Dictionaries sind selbst Objekte, und können darum in anderen Dictionaries oder Arrays auftauchen. Ebenso können Arrays in Dictionaries enthalten sein.
Streams dienen dazu, grössere Datenblöcken zu Objekten zusammenzufassen. Sie kommen hauptsächlich für die Seitenbeschreibung sowie für eingebettete Schriften und Bilder zum Einsatz.
Ein Stream beginnt mit einem Dictionary. Dieses muss zumindest einen Eintrag /Length
haben, der die Grösse der eigentlichen Daten in Bytes beschreibt. Auf das Dictionary folgt das Schlüsselwort stream
, gefolgt von einem Zeilenumbruch und den eigentlichen Daten. Nach den Daten folgt ein Zeilenumbruch und das Schlüsselwort endstream
.
Zwischen stream
und den Daten muss genau ein Zeilenumbruch nach Windows- oder Unix-Norm stehen. Dies, weil bei einem Zeilenumbruch nach der alten Apple-Norm oder anderen Leerräumen unter Umständen nicht klar wäre, wo der Leerraum endet und wo die Daten beginnen.
<< /Length 51 >> stream Dies ist ein kurzer Stream, der als Beispiel dient. endstream
Ein indirektes Objekt ist ein Objekt, dem eine eindeutige Nummer zugewiesen wurde. Das ist mit jedem Objekt möglich, üblich ist es allerdings nur für Dictionaries und Streams. Streams sind nur als indirekte Objekte erlaubt.
Einem indirekten Objekt wird die Objektnummer, die Generationennummer und das Schlüsselwort obj
vorangestellt. Nach dem Objekt muss das Schlüsselwort endobj
stehen. Die Objektnummer ist eine fortlaufende Zahl (es dürfen keine Zahlen übersprungen werden). Die Generationennummer ist normalerweise 0. Sie wird nur bei Nachbearbeitungen gebraucht für Objektnummern, die gelöscht und später neu vergeben wurden.
1 0 obj << /Title (Hallo Welt) /Author (Peter Muster) >> endobj 2 0 obj (Auch andere Objekte können indirekte Objekte sein.) endobj
Referenzen sind eigentlich keine Objekte, sondern Platzhalter. Sie werden eingesetzt, um auf ein indirektes Objekt zu verweisen. Dabei kann auf ein und dasselbe indirekte Objekt auch mehrfach verwiesen werden.
Referenzen bestehen aus der Objektnummer und der Generationennummer des gewünschten indirekten Objekts, gefolgt vom Grossbuchstaben R
.
1 0 obj (indirekter Text) endobj 2 0 obj << /Direkt (direkter Text) /Indirekt 1 0 R >> endobj
Referenzen in Arrays wirken etwas verwirrend, da sie auf den ersten Blick wie drei separate Objekte aussehen. Man muss manchmal schon genau hinschauen.
Dies sind 9 Zahlen:
[1 0 8 2 0 8 3 0 8]
Dies hingegen sind 3 Referenzen:
[1 0 R 2 0 R 3 0 R]
In Seitenbeschreibungen finden sich noch Anweisungen. Dabei handelt es sich um festgelegte Schlüsselworte, die verschiede Aktionen auslösen. So etwa die Platzierung und das Schreiben eines Texts.
Wie in Postscript, so sind auch in PDF die Anweisungen in Postfixnotation. Das heisst, dass Parameter vor dem Schlüsselwort in die Datei geschrieben werden.
72 746 Td (Hallo Welt) Tj
Hier wird erst die Anweisung Td
aufgerufen. Sie nimmt die Zahlen 72 und 746, und legt fest, dass der Text an diesen Koordinaten platziert wird. Danach wird die Anweisung Tj
aufgerufen. Diese nimmt den String „Hallo Welt“, und schreibt ihn auf das virtuelle Papier.
Bei Anweisungen muss man ganz besonders auf die Gross- und Kleinschreibung achten, da hier besonders leicht Verwechslungen auftreten können. So haben etwa die Anweisungen S
und s
leicht unterschiedliche Bedeutungen, wie wir in Kürze sehen werden.
Diskussion