Der weitaus grösste Teil der Schriften ist heute im TrueType Format. Leider macht uns das die Sache schwieriger, denn die Metadaten sind in reichlich grossen und komplexen, binären Strukturen abgelegt. Dazu kommen noch Inkompatibilitäten verschiedener Versionen des Standards.
Es gibt drei Arten von TrueType-Schriften, in PDF problematisch sind. Dies wären:
Mit „reine Apple TrueType Schriften“ sind dabei Schriften gemeint, welche den Anforderungen von Apple, nicht aber jenen von Microsoft genügen. Dies ist vor allem bei älteren, für MacOS 9 oder früher konzipierten Schriften der Fall. Diese Schriften sind nur bedingt für PDF geeignet, und umständlicher einzubinden.
TrueType Collections und dfont Schriftsammlungen sind Dateien, welche mehrere TrueType Schriften enthalten. Die Formate werden nicht von PDF unterstützt. Es ist aber mit einigem Aufwand möglich, die einzelnen Schriften herauszuholen.
Da diese Schriften nicht all zu häufig sind, deren Verwendung aber einen gewissen Mehraufwand bedeutet, gehe ich hier nicht darauf ein, und verweise statt dessen auf das Kapitel Problematische Schriften im Teil für fortgeschrittene Typographie.
TrueType Schriften bestehen nur aus je einer Datei. Diese enthält sowohl die Metadaten, wie auch die Schriftdaten. Im Streamobjekt kann die Datei als ganzes übergeben werden, wir brauchen also nicht die Schriftdaten zu extrahieren. Die Metadaten müssen wir aber trotzdem selber angeben. Ausserdem brauchen wir ja selber die Zeichenbreiten, damit wir die Breiten der einzelnen Texte berechnen können.
TrueType Dateien sind binäre Dateien. Zahlen, die mehr als ein Byte benötigen, sind in Big-Endian Anordnung abgelegt. Es kommen folgende Datentypen zur Anwendung:
char | String mit 8-Bit Kodierung |
---|---|
wchar | String mit 16-Bit Kodierung |
int16 | vorzeichenbehaftete 16-Bit Zahl |
uint16 | vorzeichenlose 16-Bit Zahl |
uint32 | vorzeichenlose 32-Bit Zahl |
Ähnlich wie die binären Dateien, die uns bisher begegnet sind, besteht die Datei aus mehreren, aneinandergereiten Datenblocks. Anders als die bisherigen Formate beginnen die Blocks aber nicht mir Markern und Längenangaben. Statt dessen steht am Anfang der Datei eine Art Inhaltsverzeichnis.
Am Anfang der Datei steht das Blockverzeichnis. Es enthält zunächst einmal folgende Felder:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 4 Byte | char | Signatur |
4 | 2 Byte | uint16 | Anzahl Blocks |
6 | 2 Byte | uint16 | Suchrahmen |
8 | 2 Byte | uint16 | Selektorgrösse |
10 | 2 Byte | uint16 | Suchüberschuss |
Die Signatur sollte der Binärstring <00 01 00 00> sein. Steht dort der ASCII-String „true“, so handelt es sich wahrscheinlich um eine reine Apple-Schrift. Andere mögliche Werte sind die ASCII-Strings „OTTO“ für OpenType Type1, „ttcf“ für TrueType Collections und „typ1“ für einen alten, nicht mehr unterstützten Standard zur Unterbringung von Postscript-Schriften in TrueType-Dateien. Bei dfont Schriftsammlungen gibt es keine Signatur: Die Datei beginnt direkt mit Inhaltsdaten.
Die Anzahl Blocks gibt an, wieviele weitere Blocks in der Datei existieren (also den Verzeichnisblock selbst nicht eingerechnet).
Suchrahmen, Selektorgrösse und Suchüberschuss sind Parameter für den binären Suchalgorithmus von Apple. Für uns sind diese Zahlen uninteressant.
Nach diesen Angaben kommt eine Liste mit Angaben zu den weiteren Blocks. Die Liste hat soviele Einträge wie „Anzahl Blocks“, von denen jeder 16 Byte lang ist. Die Einträge sind folgendermassen aufgebaut:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 4 Byte | char | ID |
4 | 4 Byte | uint32 | Prüfsumme |
8 | 4 Byte | uint32 | Position |
12 | 4 Byte | uint32 | Länge |
Die ID ist ein 4 Zeichen langer ASCII-String, welcher den Typ des Blocks definiert. Position und Länge definieren, wo in der Datei die Blockdaten zu finden sind.
Um die Prüfsumme zu berechnen, betrachtet man die Blockdaten als eine Reihe von uint32 Zahlen. Falls die Blockgrösse nicht auf 4 Byte aufgeht, können wir einfach über den Block hinaus lesen (die Datei ist entsprechend mit 0-Bytes aufgefüllt). Um die Prüfsumme zu berechnen, müssen wir einfach alle Zahlen addieren, und das Ergebnis modulo 0x100000000 rechnen. Achtung: Beim „head“ Block muss die dritte Zahl (die dritte Vierergruppe Bytes) ignoriert werden. Dies, weil dort ein Wert liegt, der erst errechnet wird, nachdem die Prüfsummen über die Blocks errechnet wurden.
Man kann nach der gleichen Methode eine Prüfsumme über die ganze Datei berechnen, wobei man diesmal die dritte Zahl im „head“ Block nicht ignorieren darf. Die Prüfsumme der Datei muss immer 0xB1B0AFBA lauten1).
Von den ganzen Blocks interessieren uns diejenigen mit folgenden IDs: „name“, „head“, „OS/2“, „post“, „cmap“, „hhea“ und „hmtx“. Mit den Angaben aus dem Verzeichnisblock, können wir diese Blocks aus der Datei extrahieren, und mit der Prüfsumme kontrollieren. Positionsangaben innerhalb eines Blocks beziehen sich nie auf den Dateianfang, wir können die Blocks also individuell betrachten.
Falls ein Block fehlt, so entspricht die Datei nicht den Minimalanforderungen für die sichere Verwendung in PDF.
Dieser Block enthält verschiedene, beschreibende Zeichenstrings, darunter den Schriftnamen. Zudem können wir hier feststellen, ob es sich um eine normale oder eine symbolische Schrift handelt.
Am Anfang des Blocks stehen wieder ein paar grundlegende Angaben:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Blockformatversion |
2 | 2 Byte | uint16 | Anzahl Strings |
4 | 2 Byte | uint16 | Startposition Strings |
Die Anzahl Strings brauchen wir, um die Länge der nachfolgenden Liste zu wissen. Die Startposition ist die Position des ersten Bytes des ersten Strings (gerechnet vom Anfang des Blocks).
Direkt auf diese drei Angaben folgt nun eine Liste mit einem Eintrag für jeden String. Die Einträge sind je 12 Byte lang, und folgendermassen aufgebaut:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Plattform |
2 | 2 Byte | uint16 | Kodierung |
4 | 2 Byte | uint16 | Sprache |
6 | 2 Byte | uint16 | Typ |
8 | 2 Byte | uint16 | Länge |
10 | 2 Byte | uint16 | Position |
Anhand der Codes für Plattform, Kodierung, Sprache und Typ wird festgelegt, wofür der jeweilige String steht. Länge ist die Stringlänge in Byte. Die Position zeigt auf das erste Byte des Strings, gerechnet von der Startposition der Strings.
Laut PDF Standard sollten wir den Postscript-Namen der Schrift verwenden. Falls dieser nicht vorhanden ist, sollten wir den betriebssystemspezifischen Namen verwenden.
Der Postscript-Name hat Plattform 3 und Typ 6. Kodierung und Sprache können unterschiedlich sein. Aus Kompatibilitätsgründen können mehrere Einträge für den Postscript-Namen vorhanden sein, die aber alle auf denselben String verweisen sollten.
Der betriebssystemspezifische Name hat Plattform 3, Sprache 0x409 und Typ 4. Die Kodierung ist bei symbolischen Schriften 0, bei normalen Schriften 1. Somit lassen sich anhand der Kodierung symbolische von nichtsymbolischen Schriften unterscheiden.
Ist kein betriebssystemspezifischer Name gemäss obiger Spezifikation vorhanden, so handelt es sich um eine reine Apple-Schrift.
Mit diesen Angaben können wir nun den String mit dem Namen aus dem Block extrahieren. Allerdings ist er in UTF-16 (Big-Endian) abgelegt. Für die Verwendung in PDF muss er noch nach UTF-8 konvertiert werden. Dafür gibt es in den meisten Programmiersprachen eingebaute Konvertierroutinen, oder gängige Bibliotheken. Laut PDF Standard sollte man zudem allfällige Leerschläge aus dem Namen entfernen. Nicht vergessen: Der Schriftname muss als Namenobjekt abgelegt werden, nicht als String.
Das ist ein einfacher Datenblock, aus dem uns nur ein paar wenige Informationen interessieren (auf die Position achten):
Position | Grösse | Typ | Inhalt |
---|---|---|---|
18 | 2 Byte | uint16 | Skala |
36 | 2 Byte | int16 | x1 |
38 | 2 Byte | int16 | y1 |
40 | 2 Byte | int16 | x2 |
42 | 2 Byte | int16 | y2 |
Die Skala beschreibt, welcher Wert innerhalb dieser Datei als Äquivalent der Schriftgrösse betrachtet wird. Steht die Skala zum Beispiel auf 2048, so steht nachfolgend ein Wert von 1024 für die halbe Schriftgrösse. In PDF verwenden wir überall ganzzahlige Promille der Schriftgrösse. Das heisst, bevor wir die ermittelten Metadaten in das PDF einsetzen, müssen wir sie mit 1000 multiplizieren, durch die Skala dividieren, und das Ergebnis auf eine ganze Zahl runden.
x1, y1, x2 und y2 sind die vier Werte für /FontBBox
.
Auch hier interessieren uns nur ein paar Angaben:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Blockformatversion |
68 | 2 Byte | int16 | Oberlänge |
70 | 2 Byte | int16 | Unterlänge |
Oberlänge und Unterlänge sind die Werte für /Ascent
und /Descent
.
Falls die Blockformatversion 2 oder grösser ist, so können wir zusätzlich die Versalhöhe ermitteln. Diese befindet sich dann an Position 88, und ist eine int16 Zahl.
Die Versalhöhe ist der Wert für /CapHeight
. Ist sie nicht vorhanden (Bei Version 0 oder 1), so können wir statt dessen die Oberlänge einsetzen.
Von diesem Block schliesslich interessiert uns folgendes:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
4 | 2 Byte | int16 | Schrägung Ganzzahlteil |
6 | 2 Byte | uint16 | Schrägung Bruchteil |
12 | 4 Byte | uint32 | Fixbreitenschrift |
Der Eintrag für Fixbreitenschrift ist ungleich 0, falls es sich um eine Fixbreitenschrift handelt, oder gleich 0, wenn es eine Proportionalschrift ist.
Um die Werte für Schrägung in einen Dezimalbruch zu erhalten, der in PDF verwendet werden kann, dividiert man den Bruchteil durch 65536, und addiert den Ganzzahlteil. Da dieser Wert nicht relativ zur Schriftgrösse ist, muss er nicht mittels der Skala umgerechnet werden.
Damit haben wir jetzt alle Daten zusammen ausser /StemV
und den Zeichenbreiten. Was /StemV
betrifft, so gibt es leider keinen zuverlässigen Weg, dies aus der Schriftdatei zu erfahren. Üblicherweise wird dieser Wert bei TrueType Schriften daher einfach auf 0 gesetzt. Die Zeichenbreiten können wir aber selbstverständlich ermitteln. Nur ist genau dies der komplizierteste Teil.
In TrueType sind die Zeichenbreiten sortiert nach der Glyphenindexnummer (kurz GID) abgelegt. der „cmap“ Block enthält eine Tabelle, mit der wir die GID anhand der Unicode Nummer ermitteln können. Der erst Schritt ist es also, die Unicode Nummer der einzelnen Zeichen zu ermitteln.
Bei symbolischen Fonts addiert man einfach 0xF000 zum gesuchten Zeichencode. Dadurch erhalten die einzelnen Zeichen Unicode Nummern im Bereich 0xF000 - 0xF0FF. Dies ist ein spezieller Bereich, der für nicht offiziell festgelegte Zeichen reserviert ist.
Bei normalen Fonts müssen wir den Zeichencode von „Windows westlich“ in die entsprechende Unicode Variante umwandeln. Im Anhang findet sich eine Tabelle der Unicodenummern für die Zeichen in „Windows westlich“.
Damit haben wir jetzt die Unicode-Nummern. Ermitteln wir nun die GID.
Dieser Block besteht aus mehreren Unterblocks, von dem jeder eine Tabelle mit GIDs enthält. Zumindest eine davon muss eine Tabelle von Unicode (nach UCS-2) auf die passenden GIDs sein. Als erstes müssen wir diesen Unterblock finden.
Am Anfang des Blocks stehen folgende zwei Angaben:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Blockversion |
2 | 2 Byte | uint16 | Anzahl Unterblocks |
Davon interessiert uns nur die Anzahl Unterblocks.
Direkt anschliessend folgt eine Liste der Unterblocks. Jeder Eintrag ist 8 Byte lang, und folgendermassen aufgebaut:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Plattform |
2 | 2 Byte | uint16 | Kodierung |
4 | 4 Byte | uint32 | Position |
Plattform und Kodierung definieren – ähnlich wie im „name“ Block – was im Unterblock enthalten ist. Die Umsetztabelle, die wir suchen, hat als Plattform den Wert 3, und als Kodierung den Wert 1 für normale Fonts, 0 für symbolische Fonts.
Die Position ist ab dem Anfang des „cmap“ Blocks gerechnet.
Von den Unterblöcken gibt es verschiedene Formate. Die ersten zwei Felder sind aber immer gleich:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Format |
2 | 2 Byte | uint16 | Länge |
Das Format muss 4 sein, sonst haben wir eine fehlerhafte Datei. Die Länge gibt die Länge des Unterblocks in Byte an, aber der Wert ist leider unzuverlässig.
Für einen Unterblock vom Format 4 sieht der Anfang des Blocks insgesamt so aus:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Format |
2 | 2 Byte | uint16 | Länge |
4 | 2 Byte | uint16 | Sprache |
6 | 2 Byte | uint16 | Listenlänge |
8 | 2 Byte | uint16 | Suchrahmen |
10 | 2 Byte | uint16 | Selektorlänge |
12 | 2 Byte | uint16 | Suchüberlauf |
Davon interessiert uns die Listenlänge. Sie gibt die Länge der nun folgenden Listen in Byte an.
Bevor wir weiterlesen muss man wissen, dass die Tabelle nicht alle möglichen Unicode Nummern auf GIDs abbildet. Statt dessen sind bestimmte Nummernsegmente definiert, und diese auf GIDs abgebildet. Unicode-Nummern, die ausserhalb dieser Segmente sind, werden als GID 0 betrachtet. Das Zeichen mit GID 0 ist gemäss TrueType Standard immer ein Zeichen, welches ausgegeben wird, wenn man versucht, ein nicht in der Schrift vorhandenes Zeichen auszugeben.
Auf die oben aufgeführten Daten folgen nun vier Listen mit der angegebenen Listenlänge. Zwischen der ersten und der zweiten Liste sind noch zwei 0-Bytes eingefügt, die restlichen Listen folgen direkt aufeinander. Die erste Liste enthält die Endcodes der Segmente, die zweite die Startcodes der Segmente, die dritte GID Deltas, und die vierte GID-Listenoffsets.
Jede Liste hat für jedes Segment einen uint16 Wert, die Anzahl Segmente ist folglich die Listenlänge durch 2.
Zusammengefasst sieht es so aus:
Grösse | Inhalt |
---|---|
14 Byte | allgemeine Daten |
Listenlänge | Liste der Endcodes |
2 Byte | 0-Bytes |
Listenlänge | Liste der Startcodes |
Listenlänge | Deltaliste |
Listenlänge | Offsetliste |
Der offizielle Algorithmus, um von einer Unicode Nummer auf einen Glyphenindex zu kommen, sieht folgendermassen aus:
Was die Schritte 3.a und 3.b betrifft, so kann man die Position der GID innerhalb des Unterblocks auch mit folgender Formel ermitteln (die Segmente sind hier ab 0 aufsteigend nummeriert):
Position = 16 + Listenlänge * 3 + Segmentnummer * 2 + Offset + (Unicode - Startcode) * 2
Ein Hinweis noch: Die Länge des Unterblocks ist in manchen Schriften zu kurz angegeben, so dass die GID-Listen ausserhalb des Unterblocks liegen. Am besten, man verlässt sich überhaupt nicht auf diese Längenangabe.
Nun haben wir endlich die GIDs zu den einzelnen Zeichen. Bevor wir die Zeichenbreiten nachschlagen, müssen wir noch eine andere Information holen.
Diese ist im „hhea“ Block an Position 34. Es handelt sich um eine uint16 Zahl, und sie enthält die Anzahl der GIDs mit explizit aufgeführter Zeichenbreite. Da die GIDs mit 0 beginnen, ist diese Zahl minus eins die grösste GID, zu der die Zeichenbreite abgelegt ist. Diese Zeichenbreite gilt implizit für alle folgenden GIDs.
Dieser Trick wird vor allem bei Fixbreitenschriften verwendet, da bei diesen so die Zeichenbreite nur einmal abgelegt werden muss.
Dieser Block besteht aus einer Liste mit der in „hhea“ vermerkten Anzahl Einträge. Jeder Eintrag steht für eine GID (von 0 an aufsteigend), und ist 4 Byte lang. Die einzelnen Einträge sind folgendermassen aufgebaut:
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 2 Byte | uint16 | Zeichenbreite |
2 | 2 Byte | int16 | Weissraum links |
Davon interessiert uns die Zeichenbreite. Somit haben wir endlich das letzte Puzzlestück, und können auch die /Widths
Liste bestücken.
Nochmals zusammengefasst: Die Einträge in der /Widths
Liste stehen für die möglichen Zeichencodes von 0 bis 255. Für jeden Zeichencode müssen wir zuerst die entsprechende Unicode-Nummer ermitteln. Danach ermitteln wir anhand des „cmap“ Blocks die GID. Dann kontrollieren wir anhand des „hhea“ Blocks, ob diese GID eine explizite Zeichenbreite hat, und ersetzen sie ansonsten durch die grösste GID mit expliziter Zeichenbreite. Zuletzt holen wir im „hmtx“ Block die Zeichenbreite zur GID, und rechnen sie anhand der Skala aus dem „head“ Block in ganzzahlige Promille um.
Manche Schriften dürfen ihrer Lizenz wegen nicht in PDFs eingebettet werden. Ob diese Vorschrift gültig ist, hängt von der jeweiligen Rechtslage des Landes ab. Falls man dies jedoch beachten will, so lässt es sich leicht automatisiert prüfen. Nicht einbettbare Schriften haben nämlich eine entsprechende Angabe in der Datei.
Die Information findet sich im „OS/2“ Block an Position 8. Es handelt sich um eine uint16 Zahl. Ähnlich wie bei /Flags
werden auch hier Zahlen addiert, die für eine bestimmte Eigenschaft stehen, um den Eintrag in der Datei zu erhalten:
Option | Wert | Hexwert |
---|---|---|
nicht einbettbar | 2 | 0x0002 |
read-only einbettbar | 4 | 0x0004 |
einbettbar | 8 | 0x0008 |
kein Subsetting | 256 | 0x0100 |
nur Bitmaps | 512 | 0x0200 |
Ist der Wert 0, so bedeutet dies, dass die Schrift ohne Einschränkung eingebettet werden kann.
„read-only einbettbar“ bedeutet, dass die Schrift in nicht bearbeitbare Dokumente eingebettet werden darf. PDFs gelten als nicht bearbeitbar, auch wenn mit spezieller Software gewisse Korrekturen und Anmerkungen gemacht werden können. Die einzige Ausnahme sind PDFs mit Formularen (die wir aber nicht behandeln).
„kein Subsetting“ bedeutet, dass die Schrift nur vollständig eingebettet werden darf.
„nur Bitmaps“ bedeutet, dass die Schrift nur als Bitmapschrift eingebettet werden kann. Das ist nicht nur kompliziert, es macht die Schrift auch praktisch nutzlos für PDF. Dieser Wert ist damit faktisch ein Einbettungsverbot für PDF.
In manchen Schriften sind mehrere der Werte 2, 4 und 8 enthalten. In diesem Fall gilt der am Wenigsten restriktive Wert.
Diskussion