Den arabischen Satz يوضح ملف PDF هذا an das normale TextOut schicken, und die zurückkommende Seite ist auf zwei Arten gleichzeitig falsch. Die Wörter laufen von links nach rechts statt von rechts nach links, und die Buchstaben stehen vereinzelt in ihren isolierten Formen, anstatt zu zusammenhängenden Wörtern verbunden zu sein. Nichts gibt einen Fehler aus. Der Delphi-Compiler ist zufrieden, die Datei öffnet sich, und ein arabisch lesender Prüfer teilt mit, dass die Ausgabe unbrauchbar ist. Die Lösung ist ein einziger Aufruf, kein Bibliothekswechsel: HotPDF leitet Rechts-nach-links-Text über eine eigene Methode, RtLTextOut, die die Umsortierung vornimmt, die das normale TextOut nicht leistet. Vier Dinge an dieser Methode entscheiden darüber, ob die Ausgabe brauchbar ist: was sie mit der Zeichenkette macht, wie ihr Zeichensatz-Argument das Skript auswählt, die dokumentweite Änderung, die sie als Nebeneffekt vornimmt, und die Schriftartarbeit, die zuerst erledigt werden muss.
Warum Rechts-nach-links einen eigenen Aufruf braucht
Ein PDF-Content-Stream speichert keinen editierbaren Text. Er speichert Glyphen an festen Positionen, was bedeutet, dass alles, was den Stream ausgibt, selbst entscheiden muss, in welcher Reihenfolge diese Glyphen stehen. Auf dem Bildschirm hat das Betriebssystem das für einen erledigt: Arabisch in ein TEdit eingeben, und der OS-Text-Stack sortiert um und verbindet, bevor man überhaupt ein Pixel sieht. Genau das ist der Grund, warum die Zeichenkette im Formular perfekt aussieht und im PDF kaputt geht. Das Desktop-System hat die Arbeit still geleistet, und in dem Moment, in dem man seinen eigenen Content-Stream schreibt, liegt die Arbeit wieder auf der eigenen Seite.
TextOut nimmt einen beim Wort. Es zeichnet die Codepoints in der Reihenfolge, in der man sie übergibt, von links nach rechts, was für Latein, Kyrillisch und CJK richtig und für Arabisch und Hebräisch falsch ist. RtLTextOut ist der Aufruf, der die Zeile zuerst in die visuelle Rechts-nach-links-Reihenfolge umsortiert und dann zeichnet. HotPDF hält die beiden Methoden bewusst getrennt, anstatt die Richtung anhand der Zeichen zu raten, sodass die Wahl des Aufrufs die Wahl des Skript-Verhaltens ist. Die tiefere Mechanik der bidirektionalen Umsortierung und der arabischen Kontextverbindung sind ein eigenes Thema, das im Artikel über Arabisch- und RTL-Textformung mit HotPDF behandelt wird; der praktische Punkt hier ist enger gefasst. RtLTextOut für Rechts-nach-links-Läufe verwenden, TextOut für alles andere, und niemals eines durch das andere leiten.

Das Zeichensatz-Argument wählt das Skript
Was RtLTextOut mitteilt, ob Arabisch oder Hebräisch gesetzt wird, ist nicht die Methode selbst, sondern die Schriftart. SetFont nimmt als viertes Argument einen Windows-Zeichensatz, und dieser Wert trägt die Skriptregeln in den Rechts-nach-links-Aufruf: 178 wählt Arabisch, 177 wählt Hebräisch. Den Zeichensatz setzen, dann zeichnen, und die beiden folgenden Zeilen erscheinen in korrekter Lesereihenfolge ohne weitere Konfiguration.
// Arabisch: Zeichensatz 178 weist RtLTextOut an, arabische Regeln anzuwenden
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebräisch: Zeichensatz 177 schaltet auf hebräische Regeln um
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Zwei Details an diesen Koordinaten sind leicht zu übersehen. Die übergebene Position ist immer noch der Anfang des Laufs im eigenen Koordinatensystem der Seite, gemessen von der unteren linken Ecke mit Y nach oben wachsend, denselben Ursprung, den jedes TextOut verwendet; RtLTextOut ändert die Glyphenreihenfolge, nicht den Messpunkt der Seite. Und wie bei jedem Zeichenaufruf muss SetFont zuerst kommen und nach jedem AddPage wiederholt werden, weil die aktuelle Schriftart keinen Seitenumbruch übersteht. Das Wiederholen vergessen, und die zweite Seite fällt auf die zuletzt aktive Schriftart zurück, was bei Arabisch meist leere Kästchen bedeutet.
Bereits umgekehrten Text nicht nochmals umkehren
Der einzige Fehler, der hier die meiste Debugging-Zeit verschlingt, ist RtLTextOut eine Zeichenkette zu übergeben, die man bereits manuell umgekehrt hat. Viele greifen zu dieser Methode nach einem ersten Versuch mit dem normalen TextOut, der rückwärts herauskam, und ein häufiges Behelfsmittel ist das Umkehren der Zeichen im Code vor dem Zeichnen. RtLTextOut kehrt intern selbstständig um, sodass eine vorab umgekehrte Zeichenkette ein zweites Mal umgekehrt wird und wieder dort landet, wo sie begann. Den Text in logischer Reihenfolge übergeben, in der Reihenfolge, in der man ihn tippt und laut liest, und den Aufruf die Umsortierung erledigen lassen.
Die Falle ist heimtückischer als eine einfache Umkehrung, weil eine doppelt umgekehrte Zeichenkette für eine rein arabische Testphrase korrekt aussehen kann und im selben Moment bricht, in dem eine Zeile ein lateinisches Wort oder eine Zahl enthält. Innerhalb einer Rechts-nach-links-Zeile sollen diese eingebetteten Läufe von links nach rechts lesen, und manuelle Umkehrung zerstört diese Schachtelung, während der rein arabische Fall zufällig überlebt. Damit segelt der Fehler durch den ersten Rauchtest und taucht später auf einer echten Rechnung mit einer Kontonummer darin auf. Alle manuellen Umkehrungen sofort entfernen, sobald man zu RtLTextOut wechselt.
Der Direction-Nebeneffekt, den man kennen sollte
Das Aufrufen von RtLTextOut ändert mehr als die Zeile, die gerade gezeichnet wird. Es kippt auch die Leserichtungspräferenz des Dokuments auf Rechts-nach-links um, dasselbe, was man sonst selbst über die Direction-Eigenschaft setzen würde. Dieser Setter fügt vpDirection zu den ViewerPreferences des Dokuments hinzu, was einem Viewer mitteilt, wie er zweiseitige Spreads anordnen und von welcher Seite ein gegenüberliegendes Seitenlayout beginnen soll. Wenn das gesamte Dokument Arabisch oder Hebräisch ist, ist das genau das Gewünschte, und man bekommt es kostenlos dazu.
Es ist genau deshalb wissenswert, weil es auf einer einzelnen Seite unsichtbar ist. Wenn das Dokument überwiegend links-nach-rechts ist und nur einen Rechts-nach-links-Block enthält, wird der erste RtLTextOut-Aufruf trotzdem die Präferenz der gesamten Datei kippen, und nichts im einseitigen Proof zeigt das. Das Symptom erscheint Wochen später, wenn jemand ein Duplex-Heft druckt und die Spreads gespiegelt herauskommen. Wenn das nicht gewünscht ist, Direction nach dem Rechts-nach-links-Lauf explizit zurücksetzen:
// RtLTextOut hat die Dokumentrichtung bereits auf RightToLeft gesetzt;
// zurück auf links-nach-rechts, wenn das Dokument überwiegend LTR ist
Pdf.Direction := LeftToRight;
Für ein Dokument, das wirklich von rechts nach links gelesen wird, lässt man es unberührt. Der Punkt ist, zu wissen, dass der Aufruf einen dokumentweiten Effekt hat, damit die Heft-Überraschung nie eintritt.
Die eigene Schriftart registrieren, nicht die, von der man hofft, sie sei installiert
Alle Umsortierung nützt nichts, wenn die Schriftart keine Glyphen zum Zeichnen hat. Der klassische Fehler ist ein Bericht, der auf dem Entwicklerrechner einwandfrei rendert, wo Arial Unicode MS zufällig vorhanden ist, und auf dem Server eines Kunden als Reihen leerer Kästchen herauskommt, wo Windows stillschweigend eine Schriftart ohne arabische Abdeckung ersetzt hat. Das Heilmittel ist, aufzuhören, installierten Systemschriftarten zu vertrauen, und stattdessen eine mit der Anwendung mitgelieferte Schriftart zu registrieren.
// Eine bekannte arabische Schriftart mitliefern und vor dem Zeichnen registrieren
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Zwei Grenzen sind mit der Registrierung verbunden. Eine über RegisterUnicodeTTF eingebrachte Schriftart wird eingebettet, und HotPDFs eingebettete Unicode-Verarbeitung benötigt das Dokument in PDF 1.5 oder höher; das wirkt sich nur aus, wenn etwas nachgelagert auf PDF 1.4 besteht, aber wenn es das tut, ist der Fehler stumm. Die andere ist eher rechtlicher als technischer Natur: TrueType-Dateien tragen Einbettungsberechtigungs-Bits, und eine Schriftart, die auf dem Bildschirm gut aussieht, kann so lizenziert sein, dass das Einbetten in Kundendokumente verboten ist. Die Lizenz vor dem Einbetten prüfen, nicht danach bei einer Reklamation.
Ein vollständiges Konsolen-Beispiel
Die Bausteine zusammensetzend: Hier ist ein eigenständiges Programm, das eine Seite mit einer arabischen Zeile, einer hebräischen Zeile und einer gemischten Zeile schreibt, die einen lateinischen Produktnamen enthält. Jeder Block setzt seinen Zeichensatz, dann zeichnet er in logischer Reihenfolge.
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // HotPDF main unit
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// Eine lateinische Überschrift geht den normalen TextOut-Weg
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabisch: Zeichensatz 178, logische Reihenfolge, RtLTextOut übernimmt die Umsortierung
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebräisch: Zeichensatz 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Gemischte Zeile: das eingebettete lateinische Wort liest sich weiterhin von links nach rechts
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
Das Programm ausführen und das Ergebnis öffnen. Die arabischen und hebräischen Zeilen laufen von rechts nach links, die Buchstaben verbinden sich, wo das Skript sie verbindet, und in der letzten Zeile sitzt der Token HotPDF von links nach rechts innerhalb des arabischen Laufs, was das spezifikationskonforme Ergebnis ist, auch wenn es jeden überrascht, der bidirektionales Layout zum ersten Mal sieht. Dieser letzte Punkt lohnt es, in die Abnahmekriterien aufgenommen zu werden, bevor ein Muttersprachler die Ausgabe prüft, denn der eingebettete Lauf, der in Bezug auf das umgebende Skript in die "falsche" Richtung läuft, ist das Einzige, das am häufigsten als Fehler gemeldet wird, obwohl es keiner ist.
Die Ausgabe verifizieren
Eine Seite, die richtig aussieht, ist nicht dasselbe wie eine Seite, die richtig ist, also so prüfen, wie ein nachgelagertes System es tun wird. Den Text aus dem Viewer herauskopieren und die Codepoints mit der Quellzeichenkette vergleichen; korrekte visuelle Reihenfolge mit durcheinander gebrachter logischer Reihenfolge ist ein realer Fehlermodus. Die dokumentinterne Suche des Viewers nach einem Wort ausführen, das auf der Seite sichtbar ist. Dann die Datei auf einer Maschine öffnen, die nicht die Entwicklungsschriftarten hat, derjenigen, die am wahrscheinlichsten eine stille Ersetzung aufdeckt. Nichts davon ersetzt einen Muttersprachler, der ein echtes Dokument liest, was Probleme aufdeckt, die kein synthetischer Teststring aufdeckt; diese Prüfung also in den Kalender eintragen, bevor das Format live geht.
RtLTextOut behandelt die bidirektionale Umsortierung und die arabische Kontextverbindung, was den größten Teil der Rechts-nach-links-Berichts- und Dokumentarbeit abdeckt. Wo es aufhört, bei Schriften, die mehr als Umsortierung und Verbindung benötigen wie die indischen Schriftfamilien und die optionalen OpenType-Features, die durch Einzelglyphen-Substitution gehen, ist zusammen mit den Glyphenabdeckungs- und Formungsdetails im Begleitartikel über Arabisch- und RTL-Textformung mit HotPDF dargelegt.
Die hier gezeigten Aufrufe RtLTextOut, SetFont und RegisterUnicodeTTF sind Teil der HotPDF Component für Delphi und C++Builder.