TrueType

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.

problematische Schriften

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.

die TrueType Datei

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.

das Verzeichnis

Am Anfang der Datei steht das Blockverzeichnis. Es enthält zunächst einmal folgende Felder:

PositionGrösseTyp Inhalt
04 Bytechar Signatur
42 Byteuint16 Anzahl Blocks
62 Byteuint16 Suchrahmen
82 Byteuint16 Selektorgrösse
102 Byteuint16 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:

PositionGrösse Typ Inhalt
04 Byte char ID
44 Byte uint32 Prüfsumme
84 Byte uint32 Position
124 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.

der "name" Block

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:

PositionGrösseTyp Inhalt
02 Byteuint16Blockformatversion
22 Byteuint16Anzahl Strings
42 Byteuint16Startposition 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:

PositionGrösse Typ Inhalt
02 Byte uint16 Plattform
22 Byte uint16 Kodierung
42 Byte uint16 Sprache
62 Byte uint16 Typ
82 Byte uint16 Länge
102 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.

der "head" Block

Das ist ein einfacher Datenblock, aus dem uns nur ein paar wenige Informationen interessieren (auf die Position achten):

PositionGrösse Typ Inhalt
182 Byte uint16 Skala
362 Byte int16 x1
382 Byte int16 y1
402 Byte int16 x2
422 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.

der "OS/2" Block

Auch hier interessieren uns nur ein paar Angaben:

PositionGrösse Typ Inhalt
02 Byte uint16 Blockformatversion
682 Byte int16 Oberlänge
702 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.

der "post" Block

Von diesem Block schliesslich interessiert uns folgendes:

PositionGrösse Typ Inhalt
42 Byte int16 Schrägung Ganzzahlteil
62 Byte uint16 Schrägung Bruchteil
124 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.

Zwischenstand und Zeichenbreiten

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.

der "cmap" Block

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:

PositionGrösseTyp Inhalt
02 Byteuint16Blockversion
22 Byteuint16Anzahl Unterblocks

Davon interessiert uns nur die Anzahl Unterblocks.

Direkt anschliessend folgt eine Liste der Unterblocks. Jeder Eintrag ist 8 Byte lang, und folgendermassen aufgebaut:

PositionGrösse Typ Inhalt
02 Byte uint16 Plattform
22 Byte uint16 Kodierung
44 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.

der "cmap" Unterblock

Von den Unterblöcken gibt es verschiedene Formate. Die ersten zwei Felder sind aber immer gleich:

PositionGrösse Typ Inhalt
02 Byte uint16 Format
22 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:

PositionGrösse Typ Inhalt
02 Byte uint16 Format
22 Byte uint16 Länge
42 Byte uint16 Sprache
62 Byte uint16 Listenlänge
82 Byte uint16 Suchrahmen
102 Byte uint16 Selektorlänge
122 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:

  1. Wir suchen das erste Segment, dessen Endcode grösser oder gleich der gesuchten Nummer ist.
  2. Falls der Startcode dieses Segments grösser als die gesuchte Nummer ist, so ist die GID 0, und wir sind fertig. Ansonsten geht es weiter mit Schritt 3.
  3. Falls das Offset 0 ist, so setzen wir als GID provisorisch die Unicode Nummer, und machen weiter mit Schritt 4. Ansonsten geht es weiter mit Schritt 3.a.
    1. Wir ermitteln die Position der GID-Liste dieses Segments. Dazu addieren wir das Offset zur Position, an der das Offset für dieses Segment abgelegt ist.
    2. Wir ermitteln die Position der GID innerhalb der Liste. Dazu subtrahieren wir von der Unicode Nummer den Startcode, und multiplizieren das Ergebnis mit 2.
    3. An der ermittelten Position ist ein uint16 Wert, den wir provisorisch als GID einsetzen.
  4. Falls das Delta 0 ist, so wird die GID nicht weiter verändert, und wir sind fertig. Ansonsten geht es weiter mit Schritt 4.a.
    1. Wir addieren das Delta zur GID.
    2. Wir rechnen die GID modulo 0x10000

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.

der "hhea" Block

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.

der "hmtx" Block

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:

PositionGrösse Typ Inhalt
02 Byte uint16 Zeichenbreite
22 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.

nicht einbettbare Schriften

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.

1) genau dafür sorgt der kuriose Eintrag im „head“ Block