Odborný článok

RtLTextOut v HotPDF: text sprava doľava v Delphi

Pošlite arabskú vetu يوضح ملف PDF هذا obyčajnej metóde TextOut a strana, ktorá sa vráti, bude nesprávna hneď dvojako. Slová idú zľava doprava namiesto sprava doľava a písmená stoja oddelene vo svojich izolovaných tvaroch namiesto toho, aby sa spájali do súvislých slov. Nič nevyhodí chybu. Delphi sa preloží, súbor sa otvorí a recenzent, ktorý číta po arabsky, vám povie, že výstup je nepoužiteľný. Riešením je jedno volanie, nie výmena knižnice: HotPDF smeruje text sprava doľava cez samostatnú metódu RtLTextOut, ktorá zvláda preusporiadanie, aké obyčajný TextOut nerobí. Táto stránka je funkčnou referenciou k tejto metóde: signatúra a jej parametre, argument znakovej sady, ktorý volí písmo, vedľajší účinok na úrovni dokumentu, nastavenie písma, ktoré musí prísť ako prvé, a zlyhania, ktoré sa naozaj dostanú na podporu, každé aj s opravou

Signatúra a parametre

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

X a Y ukotvujú beh textu v súradnicovej sústave samotnej strany, meranej od ľavého dolného rohu s Y rastúcim nahor, teda z rovnakého počiatku, aký používa každé volanie TextOut; RtLTextOut mení poradie glyfov, nie to, odkiaľ strana meria. angle otáča účiaru presne tak ako v TextOut, takže 0 kreslí vodorovný riadok. Text je reťazec v logickom poradí, teda v poradí, v akom by ste ho napísali, a druhá preťažená verzia berie tie isté dáta UTF-16 ako surový buffer PWORD s explicitným počtom kódových jednotiek, čo je tvar na použitie vtedy, keď text prichádza z API namiesto z reťazca v Delphi. Na starších verziách Delphi, ktoré predchádzajú riešeniu preťaženia pre tieto typy, je reťazcová verzia dostupná pod názvom RtLTextOutStr s identickým zoznamom parametrov

Rozdelenie práce medzi obe výstupné volania je prísne. TextOut kreslí kódové body v poradí, v akom ich odovzdáte, čo je správne pre latinku, cyriliku a CJK a nesprávne pre arabčinu a hebrejčinu. RtLTextOut najprv preusporiada každý riadok do vizuálneho poradia sprava doľava a až potom kreslí, pričom vložené latinské slová a číslice v rámci riadka čítajú zľava doprava. HotPDF drží tieto dve metódy zámerne oddelene namiesto hádania smeru zo znakov, takže voľba, ktorú z nich zavoláte, je voľbou správania pre dané písmo; pre behy sprava doľava použite RtLTextOut, pre všetko ostatné TextOut, a nikdy jedno neposielajte cez druhé. Prečo preusporiadanie vôbec existuje, čo presne robí obojsmerný algoritmus Unicode a kontextové spájanie arabčiny a kde končí shaping v HotPDF, je predmetom sprievodného článku o shapingu arabského a RTL textu pomocou HotPDF; všetko nižšie je praktické nastavenie

Diagram toho, ako RtLTextOut preusporiada zmiešaný arabsko-latinský riadok do vizuálneho poradia sprava doľava ešte pred jeho vykreslením do PDF
RtLTextOut preusporiada každý riadok do vizuálneho poradia pred kreslením: behy sprava doľava si zachovajú svoju postupnosť, kým vložené latinské slová a číslice v rámci riadka čítajú zľava doprava

O písme rozhoduje argument znakovej sady

To, čo metóde RtLTextOut povie, či sadzí arabčinu alebo hebrejčinu, nie je samotná metóda, ale písmo. SetFont berie ako štvrtý argument znakovú sadu Windows a táto hodnota vnáša pravidlá písma do volania sprava doľava: 178 volí arabčinu, 177 volí hebrejčinu. Nastavte znakovú sadu, potom kreslite a oba riadky nižšie vyjdú v správnom poradí čítania bez ďalšej konfigurácie

// Arabčina: znaková sada 178 povie RtLTextOut, aby použil arabské pravidlá
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// Hebrejčina: znaková sada 177 prepne pravidlá na hebrejské
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Jeden detail poradia sa dá ľahko prehliadnuť: SetFont musí prísť ako prvé a musí sa zopakovať po každom AddPage, pretože aktuálne písmo vrátane znakovej sady zalomenie strany neprežije. Zabudnite na opakovanie a druhá strana spadne späť na akékoľvek písmo, ktoré bolo aktívne, čo pri arabčine zvyčajne znamená prázdne štvorčeky

Text, ktorý ste už obrátili, znovu neobracia

Jedinou chybou, ktorá tu spotrebuje najviac času na ladenie, je podávať metóde RtLTextOut reťazec, ktorý ste už ručne otočili. Ľudia sa k tejto metóde dostanú po tom, čo prvý pokus s obyčajným TextOut vyšiel pozpiatku, a bežnou provizórnou náplasťou je obrátiť znaky v kóde ešte pred kreslením. RtLTextOut obracia vnútri sám, takže vopred obrátený reťazec sa obráti druhýkrát a pristane presne tam, kde začal. Odovzdajte text v logickom poradí, teda v tom, v akom by ste ho napísali a nahlas prečítali, a preusporiadanie nechajte na volanie

Táto pasca je zákernejšia než obyčajné otočenie, pretože dvojito obrátený reťazec môže pri jednej celoarabskej testovacej fráze vyzerať správne a rozsypať sa v okamihu, keď riadok nesie latinské slovo alebo číslo. Vnútri riadka sprava doľava majú tieto vložené behy čítať zľava doprava a ručné obracanie toto vnorenie zničí, hoci čisto arabský prípad ho zhodou okolností prežije. Chyba tak prekĺzne vaším prvým rýchlym testom a vyplává neskôr na skutočnej faktúre s číslom účtu. Odstráňte každé ručné obracanie v okamihu, keď prejdete na RtLTextOut

Vedľajší účinok na Direction, ktorý treba poznať

Volanie RtLTextOut zmení viac než len riadok, ktorý práve kreslíte. Preklopí aj preferenciu smeru čítania celého dokumentu na sprava doľava, teda to isté, čo by ste inak nastavili sami cez vlastnosť Direction. Tento setter pridá vpDirection do ViewerPreferences dokumentu, čo prehliadaču povie, ako usporiadať dvojstrany a z ktorej strany sa rozloženie s protiľahlými stranami začína. Keď je celý dokument arabský alebo hebrejský, je to presne to, čo chcete, a dostanete to zadarmo

Oplatí sa o tom vedieť práve preto, že na jednej strane je to neviditeľné. Ak je dokument prevažne zľava doprava s jedným blokom sprava doľava, prvé volanie RtLTextOut aj tak preklopí preferenciu celého súboru a vo vašom jednostranovom náhľade to nič neukáže. Príznak sa objaví o týždne neskôr, keď niekto vytlačí obojstrannú brožúru a dvojstrany vyjdú zrkadlovo. Ak to nechcete, nastavte Direction po behu sprava doľava naspäť explicitne:

// RtLTextOut už nastavil smer dokumentu na RightToLeft;
// obnovte zľava doprava, ak je dokument prevažne LTR
Pdf.Direction := LeftToRight;

Pri dokumente, ktorý naozaj číta sprava doľava, to nechajte tak. Ide o to vedieť, že volanie má účinok na celý dokument, aby prekvapenie s brožúrou nikdy nenastalo

Zaregistrujte písmo, ktoré dodávate, nie to, o ktorom dúfate, že je nainštalované

Žiadne preusporiadanie nepomôže, ak písmo nemá čo kresliť. Klasickým zlyhaním je report, ktorý sa bezchybne vykreslí na vývojárskom stroji, kde Arial Unicode MS zhodou okolností je, a na zákazníkovom serveri vyjde ako rady prázdnych štvorčekov, pretože Windows ticho nahradil písmo takým, ktoré arabčinu vôbec nepokrýva. Liekom je prestať dôverovať nainštalovaným systémovým písmam a zaregistrovať si to, ktoré dodávate s aplikáciou

// Dodajte známe arabské písmo a pred kreslením ho zaregistrujte
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

S registráciou prichádzajú dve hranice. Písmo vnesené cez RegisterUnicodeTTF sa vloží do dokumentu a práca s vloženým Unicode v HotPDF vyžaduje dokument vo verzii PDF 1.5 alebo novšej; to zabolí len vtedy, keď niečo ďalej v spracovaní trvá na PDF 1.4, no keď sa to stane, zlyhanie je tiché. Druhá hranica je skôr právna než technická: súbory TrueType nesú bity s povolením na vkladanie a rez, ktorý na obrazovke vyzerá v poriadku, môže mať licenciu, ktorá jeho dodávanie vnútri zákazníckych dokumentov zakazuje. Licenciu si overte pred vložením, nie až po sťažnosti

Kompletný konzolový príklad

Keď skladáme diely dohromady, tu je samostatný program, ktorý zapíše jednu stranu s arabským riadkom, hebrejským riadkom a zmiešaným riadkom nesúcim latinský názov produktu. Každý blok si nastaví svoju znakovú sadu a potom kreslí v logickom poradí

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // hlavná jednotka HotPDF

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

    // Latinský nadpis ide obyčajnou cestou cez TextOut
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // Arabčina: znaková sada 178, logické poradie, preusporiadanie rieši RtLTextOut
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

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

    // Zmiešaný riadok: vložené latinské slovo stále číta zľava doprava
    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.

Spustite ho a otvorte výsledok. Arabský a hebrejský riadok čítajú sprava doľava, písmená sa spájajú tam, kde ich písmo spája, a v poslednom riadku sedí token HotPDF zľava doprava vnútri arabského behu. Toto vnorenie je správnym obojsmerným výsledkom, nie chybou, hoci recenzenti ho pri prvom stretnutí bežne nahlasujú ako chybu; článok o shapingu odkazovaný vyššie vysvetľuje, prečo to pravidlá Unicode vyžadujú a ako sformulovať vaše akceptačné kritériá, aby sa také hlásenie nikdy nepodalo

Bežné chyby a ich opravy

Každé zlyhanie nižšie sa objavilo v skutočnom vlákne podpory a každé sa dá vystopovať k niektorej z častí vyššie

  • Výstup číta pozpiatku alebo sa na zmiešaných riadkoch rozsype — reťazec bol pred volaním obrátený ručne, zvyčajne ide o pozostatok náhradného riešenia z pokusu s TextOut. Zmažte každé ručné obracanie a odovzdajte logické poradie; RtLTextOut obracia vnútri
  • Písmená sa tlačia oddelene v izolovaných tvaroch — text prešiel obyčajným TextOut, alebo bolo SetFont zavolané bez znakovej sady pre smer sprava doľava. Kreslite cez RtLTextOut a ako štvrtý argument SetFont odovzdajte 178 pre arabčinu alebo 177 pre hebrejčinu
  • Prázdne štvorčeky na zákazníkovom stroji — Windows nahradil písmo takým, ktoré arabčinu ani hebrejčinu nepokrýva. Prestaňte pomenúvať nainštalované písma; zaregistrujte rez, ktorý dodávate, cez RegisterUnicodeTTF a nastavte ho cez SetFont pod týmto názvom
  • Druhá strana sa vykreslí v nesprávnom písme — aktuálne písmo neprežije AddPage. Zopakujte volanie SetFont vrátane znakovej sady po každom zalomení strany
  • Obojstranné dvojstrany sa v prevažne LTR dokumente tlačia zrkadlovo — prvé volanie RtLTextOut ako vedľajší účinok preklopilo Direction dokumentu. Po behu sprava doľava nastavte Pdf.Direction := LeftToRight
  • Vložený Unicode text ticho degraduje ďalej v spracovaní — niečo v pipeline vynucuje PDF 1.4 a práca s vloženým Unicode v HotPDF vyžaduje 1.5 alebo novšiu. Zdvihnite verziu dokumentu alebo odstráňte to obmedzenie

Než formát pošlete do sveta, overte ho nad rámec obhliadky očami: skopírujte text z prehliadača von, spustite vyhľadávanie v dokumente, otvorte súbor na stroji bez vašich vývojárskych písiem a jeden skutočný dokument položte pred rodeného čitateľa. Kompletný overovací zoznam, mapa pokrytia pre jednotlivé písma aj korpus testovacích reťazcov, ktorý sa oplatí vybudovať, sídlia v sprievodnom článku o shapingu arabského a RTL textu pomocou HotPDF

Volania RtLTextOut, SetFont a RegisterUnicodeTTF zobrazené tu sú súčasťou komponentu HotPDF Delphi Component pre Delphi a C++Builder