Mit den Schriftobjekten, so wie wir sie kennen, können wir mit nichtsymbolischen Schriften Texte in „Windows westlich“ setzen. Was aber, wenn wir Zeichen aus anderen Zeichensätzen benötigen? Das Schriftobjekt kennt zwar zusätzliche Parameter, mit der andere Kodierungen definiert werden können. Die Möglichkeiten sind aber eingeschränkt, und funktionieren bei TrueType-Schriften nur halbwegs. Zudem wären wir nach wie vor auf 256 Zeichen beschränkt.
Im Zeitalter von Unicode mit seinen über 100'000 Zeichen ist dies nicht mehr zeitgemäss. TrueType/OpenType Schriften können bis zu 65'536 Zeichen enthalten, und so zumindest einen grossen Teil von Unicode abdecken. Glücklichlicherweise enthält der PDF Standard ein alternatives Schriftsystem, welches uns Zugriff auf den vollen Zeichenumfang einer Schrift gibt, nämlich das Kompositschriftsystem. Unglücklicherweise war dieses System ursprünglich für Postscript Kompositschriften vorgesehen, die inzwischen praktisch verschwunden sind. Dementsprechend sind wir mit einigen Seltsamheiten konfrontiert.
Als Lohn der Mühe wartet aber nicht nur die Möglichkeit, die Schriften voll auszunutzen. Wir können damit auch OpenType CID Schriften einbinden. Diesen Schrifttyp habe ich bisher nicht beschrieben, weil er sich leider nur über Kompositschriftobjekte einbinden lässt.
Bevor wir zur Einbindung und Verwendung der Schriften kommen: In den Metadaten und im Inhaltsverzeichnis können die Texte ganz einfach mit UTF-16BE statt PDFDoc kodiert abgelegt werden. Um den Text als UTF-16BE zu markieren, muss das Unicodezeichen 0xFEFF am Anfang eingefügt werden. Dieses Zeichen (BOM) ist als „Leerzeichen mit Breite 0“ definiert, und wird normalerweise verwendet, um zwischen UTF-16LE und UTF-16BE zu unterscheiden. In PDFDoc entspricht es der Zeichenkombination „þÿ“, deren Auftreten unwahrscheinlich ist.
Am einfachsten ist es wohl, solche Texte als Hexstring einzusetzen. Wie in den Grundlagen unter Platz sparen erläutert, können die Texte aber auch mit normalen Strings eingesetzt werden, sofern man an der richtigen Stelle mit Backslashes, \n
oder \r
arbeitet. Dabei braucht man sich keine Sorgen zu machen, dass dies die Kodierung durcheinanderbringt: Die zusätzlichen Codes werden entfernt bzw. ersetzt, bevor der Text als UTF-16BE interpretiert wird.
Das Kompositschriftsystem ist recht flexibel, und es gibt sehr viele verschiedene Möglichkeiten, wie genau wir die Schriften einbinden wollen. Davon sind zwei aber besonders verbreitet: Die Unicodemethode und der Direktzugriff.
Bei der Unicodemethode wird die Schrift so eingebunden, dass wir die Texte in Unicode nach UCS-2 (Big Endian) angeben können. Das ist recht bequem, aber mit ein paar Nachteilen verbunden. Erstens funktioniert diese Methode nur mit TrueType Schriften (nicht mit OpenType Type1 oder CID). Zweitens können wir keine Zeichen mit Unicode-Nummern grösser 65535 ansprechen. Drittens haben wir keinen Zugriff auf die in vielen Schriften enthaltenen Alternativzeichen und Ligaturen.
Beim Direktzugriff wird die Schrift so eingebunden, dass wir die Texte als Liste von GIDs angeben müssen. Die Textsatzanweisungen (Tj
usw.) bekommen dabei einen binären String, in dem für jedes darzustellende Zeichen die entsprechende GID als uint16 in Big Endian Anordnung enthalten ist. Das ist natürlich eine ganze Ecke komplizierter. Dafür unterliegt diese Methode keiner der beim Unicodezugriff aufgezählten Einschränkungen.
Anstelle eines einzelnen Schriftobjektes haben wir bei diesem System deren zwei: Ein Kompositschriftobjekt und ein Teilschriftobjekt. Das ist ein Überbleibsel von den Postscript Kompositschriften. Diese waren nämlich über mehrere Dateien verteilt, und für jede Datei brauchte es ein Teilschriftobjekt. Vor derartigen Basteleien bleiben wir zum Glück verschont.
Dieses Objekt ist ähnlich aufgebaut, wie das normale Schriftobjekt. Es handelt sich um ein Dictionary mit folgenden Einträgen:
/Type | immer /Font |
---|---|
/Subtype | immer /Type0 |
/BaseFont | Name der Schrift |
/Encoding | immer /Identity-H |
/DescendantFonts | Array mit Referenz auf das Teilschriftobjekt |
/ToUnicode | Referenz auf das ToUnicode Programm |
Der /Subtype
ist hier immer /Type0
. Dabei ist egal, ob es sich um eine TrueType, eine Type1 oder eine CID Schrift handelt.
/DescendantFonts
muss ein Array mit genau einem Eintrag sein, welcher auf das Teilschriftobjekt verweist.
Der /ToUnicode
Eintrag ist etwas spezieller. Es handelt sich um eine Referenz auf einen Stream. Dieser Stream enthält eine Definition, ob und wie die in den Strings enthaltenen Zeichencodes in Unicodes umgewandelt werden müssen. Dementsprechend ist die Definition abhängig von der verwendeten Methode, und darum in den Unterkapiteln erklärt. Der Eintrag kann weggelassen werden. In diesem Fall sind aber im PDF die Volltextsuche sowie Copy&Paste von Texten nicht möglich.
Die Zeichenbreiten und die Referenz auf das Deskriptorobjekt sind hier nicht enthalten. Diese befinden sich im Teilschriftobjekt.
Auch das Teilschriftobjekt ähnelt dem normalen Schriftobjekt:
/Type | immer /Font |
---|---|
/Subtype | technischer Typus der Schrift |
/BaseFont | Name der Schrift |
/CIDSystemInfo | << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> |
/DW | Standardzeichenbreite |
/W | Zeichenbreiten |
/FontDescriptor | Referenz auf das Schriftdeskriptorobjekt |
/CIDToGIDMap | GID-Tabelle für Unicode-Methode |
Der technische Typus ist hier /CIDFontType2
für TrueType oder /CIDFontType0
für Type1 oder CID.
Für /CIDSytemInfo
habe ich hier einen Standardwert angegeben, der eigentlich immer verwendet werden kann. Bei OpenType CID ist es aber empfehlenswert, diesen Eintrag individuell anhand der Schrift zu setzen.
Die /CIDToGIDMap
wird nur für die Unicode-Methode benötigt, und ist dort erklärt.
Mittels der Einträge /DW
und /W
werden die Zeichenbreiten hinterlegt. Um Platz zu sparen, wurde dabei ein anderes Format gewählt, als bei den normalen Schriften. Ähnlich wie beim cmap-Block ist die Grundidee, dass die Zeichen in Segmente aufgeteilt werden. Die Segmente können mit einer gemeinsamen Zeichenbreite oder individuellen Zeichenbreiten bestückt werden. Zusätzlich gibt es eine Standardbreite, die für alle Zeichen ausserhalb dieser Segmente gilt. Die Zeichennummern für die Definition der Segmente sind dabei abhängig von der gewählten Methode: Bei der Unicodemethode sind es die Unicodenummern, beim Direktzugriff hingegen die GIDs.
/DW
hat als Wert eine Ganzzahl, welche die Standardzeichenbreite festlegt. Typischerweise setzt man die Breite der GID 0 ein, da dies das Zeichen ist, welches gedruckt wird, wenn man versucht, ein nicht in der Schrift vorhandenes Zeichen zu drucken.
/W
hat als Wert ein Array mit den Segmenten. Ein Segment mit gleichmässiger Zeichenbreite wird dabei definiert, indem drei Ganzzahlen geschrieben werden: Das erste Zeichen des Segments, das letzte Zeichen des Segments, und die Zeichenbreite der Zeichen im Segment. Ein Segment mit unregelmässigen Zeichenbreiten wird definiert, indem eine Zahl gefolgt von einem Unterarray geschrieben wird. Die Zahl bezeichnet das erste Zeichen des Segments, das Unterarray enthält nacheinander die Zeichenbreiten der Zeichen des Segments.
Beispiel:
/DW 500 /W [32 255 300 256 [600 700 300 400 800]]
Damit ist der Bereich 32 - 255 auf eine Breite von 300 festgelegt, die Zeichen 256 - 260 haben nacheinander die Breiten 600, 700, 300, 400 und 800, alle anderen Zeichen haben die Breite 500.
Bei Fixbreitenschriften kann man auch auf den /W
Eintrag verzichten. In dem Fall gilt der /DW
Eintrag für alle Zeichen.
Das Schriftdeskriptorobjekt wird genau gleich geschrieben, wie bisher. Beim Schriftstream gibt es aber einen Unterschied bei OpenType Type1 und OpenType CID: Der Eintrag /Subtype
muss auf /CIDType0C
gesetzt werden (statt /Type1C
).
Für den eigentlichen Textsatz verwenden wir wie gehabt die Tj
, TJ
oder '
Anweisung. Der String (bzw. bei TJ
die Strings) enthalten weiterhin den darzustellenden Text, aber natürlich nicht in der „Windows westlich“ Kodierung. Bei der Unicodemethode muss der Text mit UCS-2 in der Big-Endian Variante abgelegt sein (ohne 0xFEFF am Anfang). Beim Direktzugriff enthalten die Strings eine Abfolge der GIDs der darzustellenden Zeichen, wobei diese jeweils als uint16 in Big-Endian Anordnung geschrieben werden müssen.
Auch hier gilt, dass man am Einfachsten Hexstrings nimmt, aber alternativ wie unter Platz sparen erwähnt auch normale Strings verwenden kann, solange man korrekt Backslashes, \n
und \r
setzt.
Eine Schwierigkeit gibt es noch bei Blocksatz. Die Tw
Anweisung funktioniert bei über das Kompositschriftsystem eingebundenen Schriften leider nicht (die Tc
Anweisung hingegen schon). Wir können aber die unter Unterschneidung beschriebene TJ
Anweisung verwenden, um die Leerschläge zu verbreitern. Nicht vergessen: Bei TJ
muss die Angabe in Promille der Schriftgrösse gemacht werden, und für die Verbreiterung eines Abstands braucht es eine negative Zahl.
Diskussion