Dieses Format war einst weit verbreitet, vor allem im professionellen Sektor. Mittlerweile ist es weitgehend durch TrueType und OpenType ersetzt. Trotzdem findet man es aber noch, und es kann relativ leicht eingebunden werden.
Eine Postscript Type1 Schrift besteht aus mehreren Dateien. Zumindest gibt es eine AFM Datei mit den Metadaten, und eine PFB Datei mit den eigentlichen Schriftdaten. Oft sind noch eine PFM und eine INF Datei vorhanden, welche vom Adobe Type Manager benötigt werden.
Wir benötigen lediglich die AFM Datei, um an die Metadaten für das Schriftobjekt und das Schriftdeskriptorobjekt zu kommen, sowie die PFB Datei für das Streamobjekt. Manchmal gibt es statt der PFB Datei eine PFA Datei. Das ist ein leicht anderes Format, welches von PDF nicht unterstützt wird. Zum Glück kann PFA aber leicht in PFB umgewandelt werden. Vorsicht: PFA Dateien können ausser Type1 Schriften auch die nicht unterstützten Type3 oder Type42 Schriften enthalten.
Falls die AFM-Datei fehlt, so kann sie z.b. mit dem mit Ghostscript mitgelieferten Utility pfb2afm rekonstruiert werden. Viele der Metadaten in einer so erstellten AFM-Datei enthalten aber nur Abschätzungen, und natürlich fehlen jegliche Feinjustierungen durch den Schriftdesigner.
Vorsicht Postscript Umsteiger:
Das „ISOLatin1Encoding“ von Postscript setzt den Code 0x2D auf das Zeichen „minus“. Das „WinAnsiEncoding“ von PDF setzt diesen Code hingegen auf „hyphen“. Beide stellen einen einfachen, horizontalen Strich dar, aber „hyphen“ ist in vielen Schriften schmaler als „minus“.
Wie gesagt sind die Metadaten in der AFM Datei enthalten. Dabei handelt es sich um eine Textdatei. Normalerweise haben diese Zeilenumbrüche nach Microsoft-Norm (<0D 0A>), gelegentlich aber auch nach Unix-Norm (<0A>). Solche nach der pre Mac OS X Norm (<0D>) sollten mittlerweile ausgestorben sein. Die meisten Programmiersprachen können alle Varianten lesen. Falls aber Schwierigkeiten auftreten, hilft es unter Umständen, die Datei vorgängig in die passende Variante zu konvertieren.
Die Informationen sind zeilenweise abgelegt, wobei grundsätzlich eine Information pro Zeile steht. Das erste Wort der Zeile benennt den Typ der Information, der Rest ist die Information selbst. Worte und Werte sind durch ein oder mehrere Leerschläge oder Tabulatoren getrennt.
Die erste Zeile ist immer StartFontMetrics v
, wobei v für eine Versionsnummer steht. Die letzte Zeile ist immer EndFontMetrics
.
Von den in der Datei angegebenen Werten interessieren uns zunächst jene, welche mit folgenden Worten gekennzeichnet sind:
FontName | Schriftname |
---|---|
Ascender | Oberlänge |
Descender | Unterlänge |
CapHeight | Versalhöhe |
FontBBox | Zeichenumfang |
ItalicAngle | Schrägung |
StdVW | Stammdicke |
IsFixedPitch | Fixbreitenschrift |
CharWidth | Zeichenbreite |
EncodingScheme | die interne Schriftkodierung |
FontName
muss in ein Namenobjekt umgewandelt werden, und kann danach für /BaseFont
im Schriftobjekt und für /FontName
im Schriftdeskriptorobjekt eingesetzt werden. Ascender
, Descender
, CapHeight
, ItalicAngle
und StdVW
können 1:1 in /Ascent
, /Descent
, /CapHeight
, /ItalicAngle
und /StemV
des Schriftdeskriptorobjekts übernommen werden. FontBBox
hat als Wert 4 durch Leerschläge und/oder Tabulatoren getrennte Zahlen. Diese müssen noch mit eckigen Klammern umschlossen werden, um ein Array zu bilden, und können danach für /FontBBox
des Schriftdeskriptorobjekts eingesetzt werden.
Für die Flags gilt folgendes:
IsFixedPitch
vor, und ist als Wert true
angegeben, so ist es eine Fixbreitenschrift, sonst nicht.EncodingScheme
vor, und ist als Wert FontSpecific
angegeben, so ist es eine symbolische Schrift, sonst eine nichtsymbolische Schrift.ItalicAngle
vor, und ist der Wert nicht 0
, so ist es eine Schrägschrift, sonst nicht.
CharWidth
kann bei Fixbreitenschriften vorkommen. Falls vorhanden, hat es als Wert zwei durch Leerschläge und/oder Tabulatoren getrennte Zahlen, von denen die erste Zahl die Zeichenbreite für alle Zeichen dieser Schrift ist.
Gewisse Angaben können für horizontale und vertikale Schreibrichtung unterschiedlich sein. Für diesen Fall werden in der Datei Abschnitte definiert, die nur für eine Schreibrichtung gültig sind.
Ein Schreibrichtungsblock beginnt mit einer Zeile StartDirection d
, wobei d für die Schreibrichtung steht. 0 ist horizontal, 1 ist vertikal, 2 ist beides. Der Schreibrichtungsblock endet mit einer Zeile EndDirection
.
Da wir nur die horizontale Schreibrichtung verwenden, können wir es uns einfach machen: Wenn immer wir eine Zeile StartDirection 1
finden, ignorieren wir einfach alle weiteren Zeilen, bis wir eine mit EndDirection
finden.
Falls wir eine Fixbreitenschrift haben, und CharWidth definiert wurde, können wir die dort angegebene Breite für alle Zeichen übernehmen. Ansonsten müssen wir sie aus dem Zeichenbreitenblock holen.
Der Zeichenbreitenblock beginnt mit einer Zeile StartCharMetrics x
, wobei x für die Anzahl Zeichen steht. Der Block endet mit einer Zeile EndCharMetrics
. Die Zeilen innerhalb des Blocks haben ein spezielles Format.
Jedes Zeichen hat seine eigene Zeile. Jede Zeile besteht aus verschiedenen Informationen, die mit Strichpunkten voneinander getrennt sind. Die einzelnen Informationen bestehen aus einer Buchstabenkombination, die den Typ festlegt, und einem oder mehreren Werten. Die Typen, Werte und Strichpunkte sind durch ein oder mehrere Leerschläge und/oder Tabulatoren getrennt.
Beispiel:
C 36 ; WX 556 ; N dollar ; B 32 -126 518 770 ;
Von den Informationen interessieren uns folgende:
N | Name des Zeichens |
---|---|
C | Nummer des Zeichens |
WX oder W0X | Breite des Zeichens |
W oder W0 | Breite und Höhe des Zeichens |
Im Falle von WX
oder W0X
haben wir genau eine Zahl, welche die Zeichenbreite ist. Bei W
oder W0
haben wir zwei Zahlen, von denen die Erste die Zeichenbreite ist. In beiden Fällen ist die Zeichenbreite in Promillen der Schriftgrösse, also schon im richtigen Format.
Um bei symbolischen Schriften die Zeichenbreiten zu erhalten, suchen wir für alle möglichen Codes von 0 bis 255 einen Eintrag mit entsprechender Zeichennummer.
Um bei normalen Schriften die Zeichenbreiten zu erhalten, ermitteln wir für alle möglichen Codes von 0 bis 255 den passenden Zeichennamen, und suchen anschliessend einen Eintrag mit diesem Namen. Im Anhang ist eine Tabelle mit den Zeichennamen für die Zeichen in „Windows westlich“.
Bei Zeichen, die keinen Namen haben, oder für die wir keinen Eintrag finden, nehmen wir als Breite 0.
Die so ermittelten Breiten stellen wir zu einer Liste zusammen. Diese können wir nun für /Widths
im Schriftobjekt verwenden.
Die Datei kann einen Kerningblock enthalten. Dieser enthält Angaben zur Unterschneidung. Wir brauchen diese Angaben nicht, müssen aber wissen, wie der Block erkannt und überlesen werden kann.
Der Kerningblock, soweit vorhanden, beginnt mit einer Zeile StartKernData
, und endet mit einer Zeile EndKernData
. Diesen Bereich müssen wir überlesen.
Die meisten Angaben sind optional. Wir müssen sie ersetzen, falls sie nicht in der Datei sind.
Die eigentlichen Schriftdaten sind in einer PFB Datei. Dabei handelt es sich um eine binäre Datei. Zahlen, die mehr als 1 Byte benötigen, sind in Little-Endian Anordnung. Es kommen folgende Datentypen zur Anwendung:
uint8 | vorzeichenlose 8-bit Zahl |
---|---|
uint32 | vorzeichenlose 32-bit Zahl |
Der Aufbau ist ähnlich wie bei JPEG: Die Datei besteht aus mehreren aufeinanderfolgenden Blöcken, die mit einem 2 Byte langen Marker und einer 4 Byte langen Längenangabe beginnen. In diesem Fall sind es immer exakt drei Blöcke.
Position | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 1 Byte | uint8 | immer 0x80 |
1 | 1 Byte | uint8 | Inhaltstyp |
2 | 4 Byte | uint32 | Datenlänge |
6 | Datenlänge | * | Daten |
Der Marker hat als erstes Byte 0x80, und als zweites Byte den Wert 1 oder 2, was den Inhaltstyp festlegt. Der erste und dritte Block sind immer Typ 1, der zweite Block ist immer Typ 2. Auf den Marker folgt ein uint32 Wert mit der Länge des Blocks. Die Längenangabe ist dabei nur für die eigentlichen Blockdaten, ohne die 6 Bytes für Marker und Längenangabe selbst. Nach dem dritten Block folgt ein Marker <80 03> ohne Längenangabe, welcher das Ende der Datei markiert.
Wir müssen den Inhalt nicht weiter interpretieren. Wir müssen lediglich die Daten der einzelnen Blöcke extrahieren und aneinanderhängen (ohne Marker und Längenangaben). Diese aneinandergehängten Daten werden als Inhalt des Streamobjekts verwendet. Die drei Längenangaben müssen wir als /Length1
, /Length2
und /Length3
in das Dictionary des Streams eintragen.
Der dritte Block einer PFB Datei ist immer gleich. Darum ist es uns erlaubt diesen wegzulassen. In dem Fall muss /Length3
einfach auf 0 gesetzt werden.
Beispiel:
<80 01 B9 12 00 00> (4793 Bytes) <80 02 68 75 00 00> (30056 Bytes) <80 01 14 02 00 00> (532 Bytes) <80 03>
Der erste Block ist 0x12B9 (4793) Bytes lang, der zweite 0x7568 (30056) Bytes, und der letzte wie eigentlich immer 0x214 (532) Bytes. Wenn wir nur mit den ersten beiden Blocks arbeiten, sollte das Streamobjekt also etwa so aussehen:
6 0 obj << /Length1 4793 /Length2 30056 /Length3 0 /Length 44145 /Filter /ASCII85Decode >> stream ...~> endstream endobj
Falls wir die Datei im PFA Format haben, können wir sie folgendermassen verwenden:
PFA Dateien sind Textdateien, nicht binäre Dateien. Auch sie bestehen grundsätzlich aus denselben drei Blöcken. Deren Abgrenzung ist aber nicht mit Blockmarkern festgelegt, sondern ergibt sich aus dem Kontext.
Der erste Block geht vom Anfang der Datei bis zum Schlüsselwort eexec
1). Er endet nach dem auf eexec folgenden Zeilenumbruch.
Der dritte Block ist am Dateiende. Er umfasst 512 Nullen, die von Leerschlägen, Tabulatoren und Zeilenumbrüchen unterbrochen sein können2), gefolgt vom Schlüsselwort cleartomark
.
Der zweite Block geht exakt vom Ende des ersten Blocks zum Anfang des dritten Blocks. Es handelt sich um binäre Daten in Hexadezimalschreibweise. Um diesen Block verwenden zu können, müssen wir die binären Daten rekonstruieren. Die meisten Programmiersprachen liefern hierfür eine fertige Funktion mit.
Nun hängen wir den ersten Block und den konvertierten zweiten Block aneinander, und verwenden dies als Inhalt des Streamobjekts. /Length1
setzen wir auf die Länge des ersten Blocks, /Length2
auf die Länge des konvertierten zweiten Blocks, und /Length3
auf 0.
Diskussion