Technischer Artikel

RtLTextOut in HotPDF: Rechts-nach-links-Text in Delphi

Schicken Sie den arabischen Satz يوضح ملف PDF هذا an das einfache TextOut, und die Seite, die zurückkommt, ist auf zwei Arten zugleich falsch. Die Wörter laufen von links nach rechts statt von rechts nach links, und die Buchstaben stehen getrennt in ihren isolierten Formen, statt sich zu verbundenen Wörtern zusammenzufügen. Nichts meldet einen Fehler. Das Delphi-Programm kompiliert, die Datei öffnet sich, und ein Reviewer, der Arabisch liest, sagt Ihnen, dass die Ausgabe unbrauchbar ist. Die Lösung ist ein einziger Aufruf, kein Bibliothekswechsel: HotPDF leitet Rechts-nach-links-Text über eine separate Methode, RtLTextOut, die die Umordnung übernimmt, die das einfache TextOut nicht leistet. Diese Seite ist die praktische Referenz für diese Methode: die Signatur und ihre Parameter, das Charset-Argument, das die Schrift auswählt, die Nebenwirkung auf Dokumentebene, die Schriftart-Einrichtung, die zuerst erfolgen muss, und die Fehler, die tatsächlich beim Support landen, jeweils mit Lösung

Signatur und Parameter

procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: PWORD; TextLength: Integer); overload;

X und Y verankern den Textlauf im eigenen Koordinatensystem der Seite, gemessen von der linken unteren Ecke mit nach oben wachsendem Y, demselben Ursprung, den jeder TextOut-Aufruf verwendet; RtLTextOut ändert die Glyphenreihenfolge, nicht den Messpunkt der Seite. angle dreht die Grundlinie genau wie in TextOut, sodass 0 eine horizontale Zeile zeichnet. Text ist die Zeichenkette in logischer Reihenfolge, der Reihenfolge, in der Sie sie tippen würden, und die zweite Überladung nimmt dieselben UTF-16-Daten als rohen PWORD-Puffer mit expliziter Anzahl an Code-Units entgegen, was die Form ist, die Sie verwenden, wenn der Text aus einer API statt aus einem Delphi-String kommt. Auf älteren Delphi-Versionen, die noch keine Überladungsauflösung für diese Typen kennen, wird die String-Form unter dem Namen RtLTextOutStr mit identischer Parameterliste bereitgestellt

Die Arbeitsteilung zwischen den beiden Ausgabeaufrufen ist strikt. TextOut zeichnet Codepunkte in der Reihenfolge, in der Sie sie übergeben, was für Latein, Kyrillisch und CJK richtig und für Arabisch und Hebräisch falsch ist. RtLTextOut ordnet jede Zeile zuerst in visuelle Rechts-nach-links-Reihenfolge um und zeichnet dann, wobei eingebettete lateinische Wörter und Ziffern innerhalb der Zeile weiter von links nach rechts gelesen werden. HotPDF hält die beiden Methoden bewusst getrennt, statt die Richtung aus den Zeichen zu erraten, sodass die Wahl des Aufrufs die Wahl des Schriftverhaltens ist; verwenden Sie RtLTextOut für Rechts-nach-links-Läufe, TextOut für alles andere, und leiten Sie nie das eine durch das andere. Warum die Umordnung überhaupt existiert, was der Unicode-Bidirektionalalgorithmus und die kontextabhängige arabische Verbindung tatsächlich tun und wo das Shaping von HotPDF endet, ist Thema des Begleitartikels über arabisches und RTL-Text-Shaping mit HotPDF; alles Folgende ist die praktische Einrichtung

Diagramm, wie RtLTextOut eine gemischte arabisch-lateinische Zeile in visuelle Rechts-nach-links-Reihenfolge umordnet, bevor sie in ein PDF gezeichnet wird
RtLTextOut ordnet jede Zeile vor dem Zeichnen in visuelle Reihenfolge um: Rechts-nach-links-Läufe behalten ihre Abfolge, während eingebettete lateinische Wörter und Ziffern innerhalb der Zeile von links nach rechts gelesen werden

Das Charset-Argument entscheidet über die Schrift

Was RtLTextOut mitteilt, ob es Arabisch oder Hebräisch setzt, ist nicht die Methode, sondern die Schriftart. SetFont nimmt als viertes Argument ein Windows-Charset entgegen, und dieser Wert trägt die Schriftregeln in den Rechts-nach-links-Aufruf: 178 wählt Arabisch, 177 wählt Hebräisch. Setzen Sie das Charset, zeichnen Sie dann, und die beiden Zeilen unten kommen ohne weitere Konfiguration in korrekter Lesereihenfolge heraus

// Arabisch: Charset 178 weist RtLTextOut an, arabische Regeln anzuwenden
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// Hebräisch: Charset 177 schaltet die Regeln auf Hebräisch um
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Ein Reihenfolgedetail wird leicht übersehen: Das SetFont muss zuerst kommen und nach jedem AddPage wiederholt werden, weil die aktuelle Schriftart, Charset eingeschlossen, einen Seitenumbruch nicht überlebt. Vergessen Sie die Wiederholung, fällt die zweite Seite auf die gerade aktive Schriftart zurück, was für Arabisch meist leere Kästchen bedeutet

Es kehrt keinen Text um, den Sie bereits umgekehrt haben

Der eine Fehler, der hier die meiste Debugging-Zeit verschlingt, ist, RtLTextOut eine Zeichenkette zu übergeben, die Sie bereits von Hand gedreht haben. Man landet bei dieser Methode, nachdem ein erster Versuch mit dem einfachen TextOut rückwärts herauskam, und eine verbreitete Notlösung ist, die Zeichen vor dem Zeichnen im Code umzukehren. RtLTextOut kehrt intern selbst um, sodass eine vorab umgekehrte Zeichenkette ein zweites Mal umgekehrt wird und genau dort landet, wo sie begonnen hat. Übergeben Sie den Text in logischer Reihenfolge, der Reihenfolge, in der Sie ihn tippen und vorlesen würden, und überlassen Sie dem Aufruf die Umordnung

Die Falle ist tückischer als ein einfaches Drehen, weil eine doppelt umgekehrte Zeichenkette bei einer rein arabischen Testphrase korrekt aussehen kann und dann in dem Moment bricht, in dem eine Zeile ein lateinisches Wort oder eine Zahl trägt. Innerhalb einer Rechts-nach-links-Zeile sollen diese eingebetteten Läufe von links nach rechts gelesen werden, und die manuelle Umkehrung zerstört diese Verschachtelung, während der rein arabische Fall sie zufällig übersteht. Der Fehler segelt also durch Ihren ersten Smoke-Test und taucht später auf einer echten Rechnung mit einer Kontonummer auf. Entfernen Sie jede manuelle Umkehrung in dem Moment, in dem Sie auf RtLTextOut umstellen

Die Direction-Nebenwirkung, die man kennen sollte

Der Aufruf von RtLTextOut ändert mehr als die Zeile, die Sie zeichnen. Er schaltet auch die Leserichtungs-Präferenz des Dokuments auf rechts-nach-links um, dasselbe, was Sie sonst selbst über die Eigenschaft Direction setzen würden. Dieser Setter fügt vpDirection zu den ViewerPreferences des Dokuments hinzu, was einem Viewer mitteilt, wie er Doppelseiten anordnet und auf welcher Seite ein Layout mit gegenüberliegenden Seiten beginnt. Wenn das ganze Dokument arabisch oder hebräisch ist, ist das genau das, was Sie wollen, und Sie bekommen es umsonst

Es lohnt sich gerade deshalb, davon zu wissen, weil es auf einer einzelnen Seite unsichtbar ist. Ist das Dokument überwiegend links-nach-rechts mit einem einzigen Rechts-nach-links-Block, kippt der erste RtLTextOut-Aufruf trotzdem die Präferenz der ganzen Datei, und nichts in Ihrem einseitigen Probedruck wird das zeigen. Das Symptom erscheint Wochen später, wenn jemand eine Duplex-Broschüre druckt und die Doppelseiten gespiegelt herauskommen. Wenn das nicht gewünscht ist, setzen Sie Direction nach dem Rechts-nach-links-Lauf explizit zurück:

// RtLTextOut hat die Dokumentrichtung bereits auf RightToLeft gesetzt;
// links-nach-rechts wiederherstellen, wenn das Dokument überwiegend LTR ist
Pdf.Direction := LeftToRight;

Bei einem Dokument, das wirklich von rechts nach links gelesen wird, lassen Sie es in Ruhe. Der Punkt ist zu wissen, dass der Aufruf eine dokumentweite Wirkung hat, damit die Broschüren-Überraschung nie eintritt

Registrieren Sie die Schriftart, die Sie ausliefern, nicht die, auf deren Installation Sie hoffen

Nichts von der Umordnung zählt, wenn die Schriftart keine Glyphen zum Zeichnen hat. Der klassische Fehler ist ein Bericht, der auf dem Rechner des Entwicklers, wo Arial Unicode MS zufällig vorhanden ist, makellos rendert und auf dem Server eines Kunden als Reihen leerer Kästchen herauskommt, weil Windows stillschweigend eine Schriftart ohne jede arabische Abdeckung eingesetzt hat. Die Kur besteht darin, installierten Systemschriften nicht mehr zu vertrauen und eine zu registrieren, die Sie mit der Anwendung ausliefern

// 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 begleiten die Registrierung. Eine über RegisterUnicodeTTF eingebrachte Schriftart wird eingebettet, und die eingebettete Unicode-Verarbeitung von HotPDF braucht das Dokument in PDF 1.5 oder neuer; das beißt nur, wenn etwas Nachgelagertes auf PDF 1.4 besteht, aber wenn es das tut, ist der Fehler stumm. Die andere ist rechtlicher statt technischer Natur: TrueType-Dateien tragen Einbettungs-Berechtigungsbits, und ein Schriftschnitt, der auf dem Bildschirm gut aussieht, kann so lizenziert sein, dass die Auslieferung in Kundendokumenten verboten ist. Prüfen Sie die Lizenz, bevor Sie einbetten, nicht nach einer Beschwerde

Ein vollständiges Konsolenbeispiel

Setzt man die Teile zusammen, ergibt sich hier ein eigenständiges Programm, das eine Seite mit einer arabischen Zeile, einer hebräischen Zeile und einer gemischten Zeile mit einem lateinischen Produktnamen schreibt. Jeder Block setzt sein Charset und zeichnet dann in logischer Reihenfolge

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // HotPDF-Hauptunit

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'RtLTextOut.pdf';
    Pdf.BeginDoc;

    // Eine lateinische Überschrift geht über den gewöhnlichen TextOut-Pfad
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // Arabisch: Charset 178, logische Reihenfolge, RtLTextOut übernimmt die Umordnung
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

    // Hebräisch: Charset 177
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
    Pdf.CurrentPage.RtLTextOut(400, 680, 0,
      'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');

    // Gemischte Zeile: Das eingebettete lateinische Wort wird weiter von links nach rechts gelesen
    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.

Führen Sie es aus und öffnen Sie das Ergebnis. Die arabische und die hebräische Zeile werden von rechts nach links gelesen, die Buchstaben verbinden sich dort, wo die Schrift sie verbindet, und in der letzten Zeile sitzt das Token HotPDF links-nach-rechts innerhalb des arabischen Laufs. Diese Verschachtelung ist das korrekte bidirektionale Ergebnis, kein Fehler, auch wenn Erst-Reviewer es routinemäßig als solchen melden; der oben verlinkte Shaping-Artikel erklärt, warum die Unicode-Regeln es verlangen und wie Sie Ihre Abnahmekriterien formulieren, damit die Meldung nie eingereicht wird

Häufige Fehler und ihre Lösungen

Jeder Fehler unten ist in einem echten Support-Thread aufgetaucht, und jeder lässt sich auf einen der obigen Abschnitte zurückführen

  • Die Ausgabe wird rückwärts gelesen oder gerät bei gemischten Zeilen durcheinander – die Zeichenkette wurde vor dem Aufruf von Hand umgekehrt, meist ein Überbleibsel eines Workarounds aus einem TextOut-Versuch. Löschen Sie jede manuelle Umkehrung und übergeben Sie logische Reihenfolge; RtLTextOut kehrt intern um
  • Buchstaben erscheinen unverbunden in isolierten Formen – der Text ging durch das einfache TextOut, oder SetFont wurde ohne Rechts-nach-links-Charset aufgerufen. Zeichnen Sie mit RtLTextOut und übergeben Sie 178 für Arabisch oder 177 für Hebräisch als viertes SetFont-Argument
  • Leere Kästchen auf dem Rechner des Kunden – Windows hat eine Schriftart ohne arabische oder hebräische Abdeckung eingesetzt. Hören Sie auf, installierte Schriftarten zu benennen; registrieren Sie einen Schriftschnitt, den Sie ausliefern, über RegisterUnicodeTTF und wählen Sie ihn per SetFont unter diesem Namen
  • Die zweite Seite rendert in der falschen Schriftart – die aktuelle Schriftart überlebt AddPage nicht. Wiederholen Sie den SetFont-Aufruf, Charset eingeschlossen, nach jedem Seitenumbruch
  • Duplex-Doppelseiten drucken gespiegelt bei einem überwiegend LTR-Dokument – der erste RtLTextOut-Aufruf hat als Nebenwirkung die Direction des Dokuments umgeschaltet. Setzen Sie Pdf.Direction := LeftToRight nach dem Rechts-nach-links-Lauf
  • Eingebetteter Unicode-Text verschlechtert sich nachgelagert stillschweigend – etwas in der Pipeline erzwingt PDF 1.4, und die eingebettete Unicode-Verarbeitung von HotPDF braucht 1.5 oder neuer. Heben Sie die Dokumentversion an oder entfernen Sie die nachgelagerte Einschränkung

Bevor das Format ausgeliefert wird, prüfen Sie über das bloße Hinsehen hinaus: Kopieren Sie den Text aus dem Viewer zurück, führen Sie die Suche im Dokument aus, öffnen Sie die Datei auf einem Rechner ohne Ihre Entwicklungsschriften und legen Sie ein echtes Dokument einem muttersprachlichen Leser vor. Die vollständige Prüfliste, die Abdeckungskarte je Schrift und der Korpus an Teststrings, den es sich aufzubauen lohnt, stehen alle im Begleitartikel über arabisches und RTL-Text-Shaping mit HotPDF

Die hier gezeigten Aufrufe RtLTextOut, SetFont und RegisterUnicodeTTF sind Teil der HotPDF Delphi Component für Delphi und C++Builder