Technischer Artikel

PDF-Schriftarten und -Text: Warum Glyphen zu Kästchen werden

Ein PDF, das auf Ihrem Computer perfekt aussieht und auf einem anderen als eine Reihe leerer Kästchen dargestellt wird, ist der häufigste Schriftartfehler in der Dokumentensoftware. Und er bedeutet fast nie, dass der Text falsch ist. Die Zeichen sind intakt, die Codierung ist in Ordnung, die Glyphen fehlen einfach. Der Unterschied zwischen den beiden Computern besteht darin, welche Schriftarten das Betriebssystem installiert hatte. Und der Unterschied zwischen einer portablen und einer fragilen Datei besteht in einer Entscheidung, die beim Schreiben der Seite getroffen wurde: ob die Schriftart im PDF enthalten ist oder auf der Gegenseite als vorhanden vorausgesetzt wird

Um zu verstehen, warum das passiert und warum ein separater Fehler durchsuchbaren Text erzeugt, der beim Kopieren als Zeichensalat herauskommt, muss man sich ansehen, wie PDF Text speichert. Sätze werden nicht gespeichert. Stattdessen werden Glyphencodes sowie ein Schriftartenprogramm und Tabellen gespeichert, die das eine dem anderen zuordnen. Jeder Rendering- oder Extraktionsfehler ist auf eine Lücke zwischen diesen dreien zurückzuführen. Was folgt, ist ein Rundgang durch diese Maschinerie, basierend auf ISO 32000, mit den Delphi-Aufrufen, die sie bei Bedarf steuern

Zeichen, Codes und Glyphen sind drei verschiedene Dinge

Das Vokabular ist für die Leute verwirrend, weil in der Alltagssprache drei verschiedene Ideen im Wort „Buchstabe“ zusammengefasst sind. Ein Zeichen ist eine abstrakte Schreibeinheit, die Idee eines großen A, identifiziert in Unicode als U+0041. Eine Glyphe ist eine gezeichnete Form, der Kurven- und Stammumriss, den eine bestimmte Schriftart zur Darstellung dieses Zeichens verwendet. Dazwischen sitzt der Code: das Byte oder die Bytes im Inhaltsstream, die dem Viewer mitteilen, welche Glyphe in der aktuellen Schriftart gezeichnet werden soll

PDF funktioniert mit Codes. Wenn in einem Inhaltsstream eine Zeichenfolge angezeigt wird, sind diese Bytes Indizes in die aktive Schriftart, nicht Unicode. Die Codierung der Schriftart legt fest, dass ein Code von 65 bedeutet, „die unter 65 abgelegte Glyphe zeichnen“, und bei diesem Vorgang weiß man nicht, ob das Ergebnis für einen Menschen wie ein A aussieht. Das ist der Grund, warum PDF überall dort identisch gerendert wird, wo die Glyphen gefunden werden können. Aus diesem Grund ist die Extraktion ein vom Anzeigen separates Problem: Zum Zeichnen ist nur Code-to-Glyph erforderlich, zum Lesen ist Code-to-Unicode erforderlich, und das sind zwei verschiedene Tabellen, die nicht übereinstimmen oder unabhängig voneinander fehlen können

Die Schriftarten, denen Sie tatsächlich begegnen werden

Die Norm ISO 32000 definiert mehrere Schriftartenwörterbuchtypen, und in der Praxis wird in einem Dokument, das Sie empfangen oder erstellen, einer von drei Typen verwendet. Wenn Sie wissen, um welchen Typ es sich handelt, erklärt dies das meiste, was schiefgehen kann

Type 1 ist das ursprüngliche PostScript-Outline-Format von Adobe, das aus kubischen Bezier-Kurven erstellt wird. Die vierzehn Standardschriftarten, die jeder kompatible Reader bereitstellen muss (Helvetica, Times, Courier, Symbol und ZapfDingbats), sind Type 1. Ein Schriftartenwörterbuch, das eine dieser Schriftarten nennt, kann das Schriftartenprogramm rechtmäßig weglassen. Dies ist der einzige Fall, in dem es gemäß den Spezifikationen und nicht aufgrund von Glück sicher ist, eine Schriftart nicht einzubetten. Bei jeder anderen Type-1-Schriftart muss das Programm eingebettet sein, andernfalls ersetzt der Viewer sie, in der Regel durch eine metrisch ähnliche, aber sichtbar andere Schriftart

TrueType verwendet quadratische Kurven und stammt aus der Apple- und Microsoft-Welt. Die meisten Systemschriftarten sind von dieser Art und werden am häufigsten eingebettet. Eine einfache TrueType-Schriftart im PDF-Format ist auf Einzelbyte-Codes beschränkt, sodass eine solche Schriftart maximal 256 Glyphen gleichzeitig verarbeiten kann. Diese Obergrenze ist der strukturelle Grund, warum CJK und andere große Schriften nicht auf einer einfachen Schriftart ausgeführt werden können

Type 0, die CID-basierte oder zusammengesetzte Schriftart, ist die Antwort auf dieses Limit. Sie verwendet Mehrbytecodes und eine CMap, um sie durch eine absteigende CIDFont weiterzuleiten, deren Umrisse selbst entweder TrueType oder CFF/Type 1 sind. Dies ist die einzige Schriftart, die Tausende von Glyphen enthalten kann. Daher verwendet jedes PDF, das Chinesisch, Japanisch, Koreanisch oder eine große mehrsprachige Mischung enthält, den Type 0, unabhängig davon, ob der Autor darüber nachgedacht hat oder nicht. Der Nachteil ist die Komplexität: mehr bewegliche Teile, von denen mehr sowohl für das Rendern als auch für die Extraktion korrekt sein müssen

Eine TrueType-Schriftart, gerendert mit 12, 18, 24 und 36 Punkt in einem PDF, was zeigt, dass eine einzige eingebettete Outline auf jede Größe skaliert werden kann

Ein Detail hinter diesem Bild treibt die Dateigröße in die Höhe. Eine Schriftart ist eine Bibliothek von Konturen, keine Bitmaps mit fester Größe. Dasselbe eingebettete Programm dient also jeder Punktgröße auf der Seite. Die Skalierung ist eine Transformation, die zum Zeitpunkt des Zeichnens angewendet wird. Aus diesem Grund haben eine Überschrift und der zugehörige Textkörper eine gemeinsame eingebettete Schriftart und die Kosten für die Einbettung fallen pro Schriftart und nicht pro Größe an

Einbettung ist der Unterschied zwischen portabel und fragil

Einbetten bedeutet, dass das Schriftprogramm, also die tatsächlichen Umrissdaten, als Datenstrom in die PDF-Datei geschrieben wird. Ein Reader auf einem Rechner, der Ihre Schriftart noch nie gesehen hat, liest diese Umrisse direkt aus der Datei und zeichnet exakte Glyphen. Wenn Sie das Einbetten überspringen, wetten Sie darauf, dass am Zielort eine Schriftart mit demselben Namen vorhanden ist. Wenn dies nicht der Fall ist, greift der Viewer auf einen Ersatz zurück. Für die Standard vierzehn ist diese Ersetzung definiert und unbedenklich. Für alles andere reicht das Spektrum von einer nahen Übereinstimmung in einer anderen Schriftart bis zum Ergebnis „Leeres Kästchen“, wenn überhaupt kein Ersatz das Skript abdeckt

Bei HotPDF ist das Steuerelement eine einzelne Eigenschaft, die vor dem Öffnen des Dokuments festgelegt wird. FontEmbedding teilt der Bibliothek mit, die für das Zeichnen verwendeten Schrifttypen in die Datei zu packen:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'report.pdf';
    Pdf.Compression := cmFlateDecode;
    Pdf.FontEmbedding := True;          // outlines travel inside the file
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Calibri', [], 11);
    Pdf.CurrentPage.TextOut(72, 760, 0, 'This renders the same on a machine without Calibri.');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Die Reihenfolge ist nicht nur kosmetischer Natur. In BeginDoc schreibt HotPDF die Dokumentstruktur fest, weshalb FontEmbedding vor diesem Aufruf true sein muss. Wenn Sie es erst danach zuweisen, gibt es keinen Fehler, keine Warnung, nur eine Datei, die still und leise ohne ihre Schriftarten ausgegeben wurde. Das ist die schlimmste Art von Fehler: Er besteht jeden Test auf dem Rechner des Entwicklers, wo die Schriftart zufällig installiert ist, und taucht erst beim Kunden auf, wo dies nicht der Fall ist

Beim Einbetten treffen auch Lizenzierung und Entwicklung aufeinander. Ein Schriftartprogramm enthält Flags, die beschreiben, ob es frei, nur zur Vorschau oder überhaupt nicht eingebettet werden darf. Das Beachten dieser Flags liegt in Ihrer Verantwortung, nicht in der des Renderers, und „es hat funktioniert“ ist nicht dasselbe wie „es war erlaubt“

Untergruppenbildung: Binden Sie nur die von Ihnen verwendeten Glyphen ein

Die vollständige Einbettung schreibt das gesamte Schriftartprogramm in die Datei. Eine große CJK-TrueType-Schriftart kann mehrere Megabyte umfassen, und sie in Gänze einzubetten, um ein Dutzend Zeichen anzuzeigen, ist verschwenderisch und potenziert sich in einem mehrseitigen Dokument. Die Untergruppenbildung (Subsetting) löst dieses Problem, indem nur die Glyphen geschrieben werden, auf die sich das Dokument bezieht, und die Schriftart dann mit einem sechsstelligen Tag und einem Pluszeichen umbenannt wird (die Form ABCDEF+Calibri in jeder Schriftartenliste eines PDFs mit Teilmengen), sodass ein Reader die Teilschriftart nie mit einer vollständigen Systemschriftart desselben Namens verwechselt

Bei den meisten generierten Dokumenten ist die Untergruppenbildung der richtige Standardwert. Dadurch bleibt die Dateigröße proportional zum Inhalt und nicht zur Quellschriftart. Das ist bei den großen mehrsprachigen Schriftarten, die andernfalls in der Datei vorherrschen würden, am wichtigsten. Der einzige Vorbehalt besteht darin, dass eine Teilmenge nur das enthält, was zum Zeitpunkt der Erstellung verwendet wurde. Wenn ein nachgeschalteter Prozess später versucht, einer Teilmengenschriftart Text hinzuzufügen, befinden sich die erforderlichen Glyphen möglicherweise nicht in der Datei. Das ist eine echte Einschränkung für die inkrementelle Bearbeitung des PDFs einer anderen Person

Unicode-Schriftarten und das CJK-Kastenproblem

Wenn der Text kein einfaches Latein ist, funktioniert der Weg der einfachen Schriftarten nicht mehr. Die Lösung besteht darin, explizit eine Unicode-fähige Schriftart zu registrieren und HotPDF daraus eine Schriftart vom Type 0 erstellen zu lassen. RegisterUnicodeTTF lädt eine TrueType-Datei nach Pfad; danach kann der registrierte Name in SetFont wie jeder andere verwendet werden:

Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;

Zwei Dinge sind hierbei entscheidend. Die Schriftart muss die Schriften in der Zeichenfolge abdecken: Ein rein lateinisches TrueType-Programm liefert keine chinesischen Glyphen, nur weil Sie darum gebeten haben. Das Ergebnis sind wieder leere Kästchen, diesmal weil die Glyphe in dieser Schriftart tatsächlich nicht existiert. Und die Einbettung muss aktiviert bleiben, da eine Type 0-Schriftart, die aus einem registrierten TTF zusammengestellt wird, für einen Reader, der die Umrisse nicht finden kann, bedeutungslos ist. Für gemischte Inhalte ist die dauerhafte Wahl eine Schrift mit breiter Abdeckung, wobei die Familien Noto und Arial Unicode MS die üblichen Antworten sind, eingebettet und mit Teilmengen

Rechts-nach-links-Schriften und komplexe Schriften fügen der Abdeckung eine formgebende Ebene hinzu. HotPDF bietet RtLTextOut für Arabisch und Hebräisch an, das die direktionale Neuordnung handhabt, sodass Sie eine logische Reihenfolge übergeben und die Bibliothek das Layout durchführen lassen. Um Arabisch richtig darzustellen, sind Abdeckung plus Formgebung plus Richtung erforderlich. Dies sind drei separate Aspekte, und ein Kästchen an dieser Stelle kann bedeuten, dass einer von ihnen fehlgeschlagen ist

Die ToUnicode-Tabelle: Wo Copy-Paste lebt

Alles oben Genannte betrifft das Zeichnen. Die Extraktion ist das Spiegelbild und schlägt aus eigenen Gründen fehl. Ein Viewer rendert eine Seite mithilfe der Code-to-Glyph-Zuordnung der Schriftart. Wenn ein Benutzer jedoch Text auswählt und kopiert, muss der Viewer dieselben Codes wieder in Unicode konvertieren. Diese umgekehrte Zuordnung ist die ToUnicode CMap, ein optionaler Stream, der an die Schriftart angehängt wird

Wenn er vorhanden und korrekt ist, wird kopierter Text als die richtigen Zeichen ausgegeben. Wenn er fehlt oder falsch ist oder wenn eine Teilmenge der Schriftart mit benutzerdefinierten Glyphencodes erstellt wurde und kein ToUnicode geschrieben wurde, sieht die Seite perfekt aus, aber die Zwischenablage ist voll mit Zeichensalat: Glyphencodes werden gelesen, als wären sie Unicode, was sie bei einer benutzerdefiniert codierten Teilmenge jedoch nicht sind. Aus diesem Grund kann ein gescanntes Dokument mit einer OCR-Textebene durchsuchbar sein, während dies bei einem nativ digitalen PDF aus einem fehlerhaften Generator nicht der Fall ist. Beim Rendern und Extrahieren werden unterschiedliche Tabellen herangezogen. Daher kann eine Datei die eine Anforderung erfüllen, die andere jedoch nicht. Wenn die Extraktion für Ihre Ausgabe wichtig ist, sollten Sie eine korrekte ToUnicode-Zuordnung als zwingend betrachten und diese überprüfen, indem Sie Text aus einem Muster kopieren, anstatt einfach darauf zu vertrauen, dass sie vorhanden ist

So diagnostizieren Sie schnell einen Schriftartenfehler

Der Fehlermodus sagt Ihnen, wo Sie suchen müssen. Leere Kästchen auf einem anderen Rechner bedeuten fast immer eine Schriftart, die nicht eingebettet war. Überprüfen Sie also zuerst die Einbettung und dann die Glyphenabdeckung. Kästchen, die selbst auf Ihrem eigenen Rechner erscheinen, weisen auf die Abdeckung hin: Die Schriftart enthält dieses Skript nicht, unabhängig von der Einbettung. Text, der korrekt gerendert wird, beim Kopieren jedoch Unsinn ausgibt, ist ein ToUnicode-Problem, kein Rendering-Problem, und das Herumspielen mit Schriftarten oder Einbettung wird es nicht beheben, da die Zeichnung nie defekt war. Um eine fertige Datei zu lesen, öffnen Sie sie in Acrobat und sehen Sie unter Dokumentspezifikationen, Schriftarten nach: Ein fehlerfreier Eintrag zeigt den Typ, den Hinweis Eingebettet oder Eingebettete Untergruppe und nennt die Kodierung. Eine Schriftart, die eingebettet sein sollte, es aber nicht ist, wird sich dort bemerkbar machen, bevor ein Kunde dies tut

Nichts davon ist exotisch, wenn man einmal die Unterscheidung zwischen Zeichen, Code und Glyphe verstanden hat. Betten Sie die Schriftarten ein, mit denen Sie zeichnen, unterteilen Sie die großen Schriftarten, greifen Sie auf eine Unicode-Schrift zurück und verwenden Sie RegisterUnicodeTTF, sobald der Text das Lateinische verlässt. Behalten Sie zudem eine korrekte ToUnicode-Zuordnung bei, wenn jemand den Text extrahieren soll. Wenn Sie diese Aspekte richtig handhaben, tauchen die Kästchen nicht mehr auf. Zu der umgebenden Mechanik finden Sie im Artikel Aufbau eines einfachen PDFs Hinweise zur Position des Schriftartenverzeichnisses im Objektbaum. Im Leitfaden zur Dokumentstruktur, Eingehende Untersuchung der PDF-Dokumentstruktur, wird erläutert, wie Ressourcen seitenübergreifend freigegeben werden

Die hier gezeigten Aufrufe SetFont, FontEmbedding und RegisterUnicodeTTF sind Teil der HotPDF-Komponente für Delphi und C++Builder