Nehmen Sie die arabische Phrase يوضح ملف PDF, übergeben Sie sie an TextOut und öffnen Sie die Ausgabe: Die Buchstaben laufen in die falsche Richtung, und jeder steht in isolierter Form, getrennt von seinen Nachbarn. Für einen arabischen Leser wirkt das wie rückwärts getipptes Englisch mit einem Leerzeichen nach jedem Buchstaben. Nichts ist fehlgeschlagen, keine Exception, keine Warnung, weil zwei unterschiedliche Texttransformationen schlicht nie ausgeführt wurden. Zu verstehen, welche Transformationen das sind und welche API sie anwendet, ist der Kern komplexer Schriftausgabe in PDFs.
Dieser Artikel geht Right-to-left- und Complex-script-Text mit HotPDF durch, einer nativen VCL-PDF-Komponente für Delphi und C++Builder, einschließlich der Stelle, an der ihre Shaping-Unterstützung wirklich endet. Genau das ist bei der Entscheidung, ob Ihre Locales abgedeckt sind, genauso wichtig wie die unterstützten Fälle.
Zwei Transformationen liegen zwischen String und gedruckter Zeile
Unicode speichert Text in logischer Reihenfolge: in der Reihenfolge, in der er getippt, gespeichert und vorgelesen wird. Ein Renderer zeichnet in visueller Reihenfolge. Bei Rechts-nach-links-Schriften unterscheiden sich beide, und bei gemischtem Inhalt, etwa einem arabischen Satz mit dem lateinischen Token „PDF“ oder einem Preis in Ziffern, definiert der Unicode Bidirectional Algorithm (UAX #9), wie Links-nach-rechts-Läufe in eine Rechts-nach-links-Zeile eingebettet werden. Das ist Transformation eins: Umordnung.
Transformation zwei ist kontextuelles Shaping. Ein arabischer Buchstabe verwendet ein anderes Glyph, je nachdem ob er am Wortanfang, in der Mitte, am Ende oder allein steht. Der Codepoint ändert sich nie, nur die gerenderte Form. Eine Textpipeline, die Codepoints direkt auf Standardglyphen abbildet, erzeugt die oben beschriebene getrennte Ausgabe. Hebräisch braucht keine Verbindung, aber weiterhin Umordnung; Arabisch braucht beides, weshalb es der klassische Testfall ist.
Desktopentwicklung versteckt diese Mechanik. Wenn eine VCL-Anwendung Arabisch auf den Bildschirm zeichnet, ordnet und formt der Textstack des Betriebssystems unsichtbar. Deshalb sieht derselbe String, der in einem TEdit perfekt gerendert wird, in einem naiven PDF falsch aus. Ein PDF-Content-Stream speichert positionierte Glyphen, keine editierbaren Textläufe. Wer den Stream schreibt, ist für das Shaping verantwortlich, und genau diese Lücke soll RtLTextOut schließen.
RtLTextOut: Umordnen und Verbinden in einem Aufruf
HotPDF trennt den lateinischen Pfad vom Complex-script-Pfad auf API-Ebene. TextOut zeichnet, was es bekommt, in der Reihenfolge, in der es es bekommt; RtLTextOut führt zuerst Umordnung und Kontextanalyse aus. Der Charset-Parameter von SetFont sagt der Engine, welche Schriftregeln gelten: 178 wählt arabische Verarbeitung, 177 wählt hebräische.
// Arabic: pass logical order; RtLTextOut reorders and joins
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF');
// Hebrew: reordering only, no contextual joining
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Die Falle mit dem höchsten Debugging-Aufwand: RtLTextOut übernimmt die Umkehrung selbst. Wenn Sie ihm bereits vorab umgekehrten Text geben, meist ein übrig gebliebener „Fix“ aus einem früheren Versuch mit einfachem TextOut, wird die Zeile doppelt umgekehrt. Das kann bei einem rein arabischen Teststring sogar korrekt aussehen und erst bei der ersten Zeile mit lateinischen Buchstaben oder Ziffern brechen, weil gemischte Läufe dann nicht mehr der UAX-#9-Reihenfolge folgen. Übergeben Sie immer logische Reihenfolge und lassen Sie die API arbeiten.
Gemischt gerichteter Inhalt ist auch der Punkt, an dem manuelle Erwartungen in Reviews schiefgehen: Innerhalb einer Rechts-nach-links-Zeile werden Zahlen und eingebettete lateinische Wörter weiterhin von links nach rechts gelesen. Reviewer ohne Erfahrung mit bidirektionalem Layout melden das regelmäßig als Fehler. Es ist jedoch spezifikationskonformes Verhalten und gehört vor der ersten Prüfung durch Muttersprachler in Ihre Abnahmedokumentation.
Glyphabdeckung entscheidet, bevor Shaping läuft
Shaping wählt Glyphen; die Schrift muss sie tatsächlich enthalten. Der klassische Deployment-Fehler ist ein Bericht, der auf der Entwicklerworkstation perfekt gerendert wird, weil Arial Unicode MS dort installiert ist, und auf dem Kundenserver leere Quadrate erzeugt, weil Windows still eine Schrift ohne Arabisch-Abdeckung ersetzt hat. Die Korrektur ist, sich nicht auf installierte Systemfonts zu verlassen, sondern eine mitgelieferte Fontdatei zu registrieren:
// Ship a known font instead of relying on installed system fonts
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12);
// Audit coverage for the codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628); // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);
Zwei Versionsgrenzen gelten. So registrierte Fonts müssen eingebettet werden, und HotPDFs Behandlung eingebetteter Unicode-Fonts verlangt, dass die PDF-Version des Dokuments 1.5 oder neuer ist. Das ist nur relevant, wenn ein nachgelagertes System Ihre Ausgabe auf PDF 1.4 festnagelt. Außerdem muss die Fontlizenz Einbettung erlauben: TrueType-Dateien tragen Einbettungsberechtigungsbits, und eine Schrift, die am Bildschirm korrekt rendert, kann rechtlich ungeeignet für die Verteilung in Kundendokumenten sein.
GetUnicodeGlyphForCodepoint ist der Audit-Hook: Gehen Sie beim Start des Dienstes die Codepoint-Bereiche durch, die Ihre Daten verwenden, und protokollieren Sie die aufgelösten Glyph-IDs. So zeigt sich eine Abdeckungslücke als Logzeile während des Deployments statt als fehlende Zeichen auf einer Kundenrechnung.
Für Unicode-Text, der nicht rechts-nach-links läuft, etwa CJK-Strings, vietnamesische Diakritika oder gemischte europäische Schriften, gilt die einfache Pipeline: TextOut akzeptiert einen WideString und zeichnet ihn über die registrierte Schrift ohne bidirektionale Analyse. Die zwei Aufrufpfade in Berichtscode getrennt zu halten, eine Routine für RTL-Läufe und eine für alles andere, macht das Locale-Verhalten explizit statt es in einem Flag zu vergraben, an das sich niemand erinnert.
Lesereihenfolge ist auch eine Dokumenteigenschaft
Korrektheit auf Glyph-Ebene beendet die Arbeit nicht. ISO 32000-1 §12.2 definiert eine Viewer Preference, /Direction, die die vorherrschende Lesereihenfolge des Dokuments erklärt. Sie ändert keine Glyphen; sie sagt Viewern, wie Doppelseiten anzuordnen sind, wo in Facing-page-Layouts die Fortschrittsrichtung ankert und welche Richtung die Benutzeroberfläche annehmen soll. Diese Details zählen bei Broschüren und jedem Dokument, durch das ein Benutzer blättert.
// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft; // adds vpDirection to ViewerPreferences
Direction zu setzen genügt für sich genommen. Der Property Setter fügt vpDirection automatisch zu ViewerPreferences hinzu, sodass die Einstellung mit einer einzigen Zeile in die Datei gelangt. Der zu beobachtende Fehler ist das vollständige Auslassen der Deklaration, was gerade deshalb leicht passiert, weil sich auf einer einzelnen Seite nichts sichtbar ändert; es fällt erst auf, wenn jemand eine Duplex-Broschüre druckt und die Doppelseiten gespiegelt herauskommen.
Wo HotPDF-Shaping endet
Eine ehrliche Fähigkeitskarte spart eine Evaluierungswoche. RtLTextOut behandelt bidirektionale Umordnung und arabisches kontextuelles Verbinden automatisch. Optionale typografische Ligaturen und breitere OpenType-Feature-Anwendung sind nicht automatisch: GetSingleSubstituteGlyph(GID, 'liga') löst jeweils eine Substitution auf, Glyph-ID hinein, Feature-Tag daneben. Das funktioniert für eine bekannte, endliche Ligaturliste, die Sie selbst anwenden, ist aber keine allgemeine GSUB-Feature-Engine. Für Schriften, deren Shaping-Anforderungen weiter gehen, Indic-Schriften mit umgeordneten Vokalzeichen sind das übliche Beispiel, sollten Sie vor einer Locale-Freigabe mit echten Kundenstrings pilotieren, statt aus arabischen Ergebnissen zu extrapolieren.
Die Prüfung muss Ende-zu-Ende erfolgen, weil eine Seite richtig aussehen und trotzdem jede nachgelagerte Nutzung verfehlen kann. Drei Checks fangen das meiste ab: Text aus Acrobat zurückkopieren und Codepoints mit dem Quellstring vergleichen; im Dokument nach einem Wort suchen, das auf der Seite steht; und die Ausgabe auf einem Rechner prüfen, auf dem Ihre Entwicklungsfonts nicht installiert sind. Ein muttersprachlicher Kollege, der ein echtes Dokument betrachtet, schlägt jede Menge synthetischer Testdaten. Planen Sie dieses Review ein, bevor das Format ausgeliefert wird, nicht nach der ersten Beschwerde.
Wählen Sie Teststrings bewusst, statt einfach wiederzuverwenden, was ein Übersetzer im Vorjahr geliefert hat. Ein nützliches Mindestset pro Locale: ein reiner Schrift-Satz, ein Satz mit eingebetteten lateinischen Markennamen, eine Zeile mit Ziffern und Währung sowie Namen mit Diakritika oder kombinierenden Zeichen. Echte Kundennamen brechen Shaping-Annahmen, die Fülltext nie berührt. Der Regressionskorpus sollte um einen Eintrag wachsen, wann immer ein Supportfall ein neues Muster offenlegt.
Fontregistrierung, Subsetting und die allgemeine Textzeichnungs-API behandelt der Artikel über Berichtsausgabe, Fonts und Bilder mit HotPDF; wenn dieselben Dokumente außerdem Accessibility-Profile erfüllen müssen, bauen Sprach-Tags und Strukturvorgaben aus dem Artikel zur PDF/A- und PDF/UA-Validierung auf der hier beschriebenen Shaping-Arbeit auf.
Die Right-to-left- und Unicode-Font-APIs in diesem Artikel werden mit HotPDF Component für Delphi und C++Builder ausgeliefert; die Produktseite verlinkt die vollständige Textausgabe-Referenz.