Technical Article

RtLTextOut v HotPDF: Text sprava doľava v PDF v Delphi

Pošlite arabskú vetu يوضح ملف PDF هذا do obyčajného TextOut a výsledná stránka bude nesprávna hneď v dvoch smeroch. Slová budú plynúť zľava doprava namiesto sprava doľava a písmená zostanú oddelené vo svojich izolovaných tvaroch namiesto toho, aby sa spojili do súvislých slov. Nevygeneruje sa žiadna chyba. Delphi kód sa skompiluje, súbor sa otvorí a recenzent, ktorý číta arabčinu, vám povie, že výstup je nepoužiteľný. Nápravou je jedno volanie, nie výmena knižnice: HotPDF smeruje text sprava doľava cez samostatnú metódu RtLTextOut, ktorá spracováva zmenu poradia, ktorú obyčajný TextOut nevykoná. Štyri aspekty tejto metódy rozhodujú o tom, či je výstup použiteľný: čo robí s reťazcom, ako jej parameter znakovej sady vyberá písmo, zmena na úrovni dokumentu, ktorú spôbuje ako vedľajší efekt, a nastavenie písma, ktoré musí prebehnúť ako prvé.

Prečo text sprava doľava potrebuje vlastné volanie

Prúd obsahu PDF (content stream) neukladá upraviteľný text. Ukladá glyfy na pevných pozíciách, čo znamená, že čokoľvek generuje tento prúd, nesie zodpovednosť za rozhodnutie, v akom poradí tieto glyfy pôjdu. Na obrazovke to za vás urobil operačný systém: vložte arabčinu do TEdit a textový zásobník OS zmení poradie a spojí znaky skôr, ako uvidíte jediný pixel. To je presne dôvod, prečo reťazec vyzerá vo vašom formulári dokonale, ale v PDF sa rozpadne. Desktop vykonal prácu potichu a vo chvíli, keď píšete vlastný prúd obsahu, je táto úloha opäť na vašej strane.

TextOut vás berie doslovne. Vykresľuje kódové body v poradí, v akom ich odovzdáte, zľava doprava, čo je správne pre latinku, cyriliku a CJK (čínštinu, japončinu, kórejčinu) a nesprávne pre arabčinu a hebrejčinu. RtLTextOut je volanie, ktoré najprv preusporiada riadok do vizuálneho poradia sprava doľava a až potom ho vykreslí. HotPDF zámerne uchováva tieto dve metódy oddelené, namiesto toho, aby hádal smer zo znakov, takže výber toho, ktorú metódu zavolať, určuje správanie písma, ktoré získate. Podrobnejšia mechanika obojsmerného preusporiadania a arabského kontextového spájania je samostatnou témou, ktorej sa venuje článok o tvarovaní arabského a RTL textu v HotPDF; tu je praktický bod užší. Na úseky sprava doľava použite RtLTextOut, na všetko ostatné použite TextOut a nikdy nesmerujte jedno cez druhé.

Diagram of how RtLTextOut reorders a mixed Arabic and Latin line into visual right-to-left order before drawing it into a PDF
RtLTextOut preusporiada každý riadok do vizuálneho poradia pred vykreslením: úseky sprava doľava si zachovávajú svoju sekvenciu, zatiaľ čo vložené latinské slová a číslice sa v rámci riadku čítajú zľava doprava.

Parameter charset určuje písmo

To, čo hovorí metóde RtLTextOut, či rozkladá arabčinu alebo hebrejčinu, nie je samotná metóda, ale písmo. SetFont berie znakovú sadu Windows (charset) ako svoj štvrtý parameter a táto hodnota prenáša pravidlá písma do volania sprava doľava: 178 vyberá arabčinu, 177 vyberá hebrejčinu. Nastavte znakovú sadu, potom vykreslite a dva riadky nižšie sa zobrazia v správnom poradí čítania bez akéhokoľvek ďalšieho nastavenia.

// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Dva detaily o týchto súradniciach je ľahké prehliadnuť. Pozícia, ktorú odovzdávate, je stále začiatok úseku v súradnicovom systéme samotnej stránky, meraný od ľavého dolného rohu s rastúcou osou Y smerom nahor, čo je rovnaký počiatok, aký používa každé volanie TextOut. RtLTextOut mení poradie glyfov, nie miesto, od ktorého stránka meria. A rovnako ako pri každom inom volaní vykresľovania, SetFont musí prísť ako prvý a musí sa opakovať po každom AddPage, pretože aktuálne písmo neprežije zlom stránky. Ak na opakovanie zabudnete, druhá stránka sa vráti k akémukoľvek aktívnemu písmu, čo pri arabčine zvyčajne znamená prázdne štvorčeky.

Nereverzuje text, ktorý ste už reverzovali manuálne

Jedinou chybou, ktorá tu pohltí najviac času na ladenie, je odovzdanie reťazca do RtLTextOut, ktorý ste už ručne otočili. Vývojári sa k tejto metóde často dostanú po tom, čo prvý pokus s obyčajným TextOut skončil obrátene, a bežným dočasným riešením je otočiť znaky v kóde pred vykreslením. RtLTextOut vykonáva reverzovanie interne sám, takže vopred otočený reťazec sa otočí druhýkrát a skončí presne tam, kde začal. Odovzdajte text v logickom poradí, teda v poradí, v akom by ste ho písali a čítali nahlas, a preusporiadanie nechajte na samotné volanie.

Táto pasca je zákernejšia než obyčajné otočenie, pretože dvakrát otočený reťazec môže vyzerať správne pre jednu testovaciu frázu kompletne v arabčine a potom sa rozpadne v momente, keď riadok obsahuje latinské slovo alebo číslo. V rámci riadku sprava doľava sa tieto vložené úseky majú čítať zľava doprava a manuálne otočenie toto vnorenie naruší, zatiaľ čo čisto arabský prípad ho náhodou prežije. Chyba tak prejde cez váš prvý rýchly test a objaví sa neskôr na skutočnej faktúre s číslom účtu. Odstráňte akékoľvek manuálne obracanie textu hneď, ako prejdete na RtLTextOut.

Vedľajší efekt Direction, o ktorom je dobré vedieť

Volanie RtLTextOut mení viac než len riadok, ktorý práve vykresľujete. Prepína tiež preferenciu smeru čítania dokumentu na sprava doľava, čo by ste inak nastavili sami prostredníctvom vlastnosti Direction. Tento setter pridáva vpDirection do ViewerPreferences dokumentu, čo hovorí prehliadaču, ako usporiadať dvojstránky a z ktorej strany začína rozloženie protiľahlých stránok. Keď je celý dokument v arabčine alebo hebrejčine, je to presne to, čo chcete, a získate to zadarmo.

Je dôležité o tom vedieť práve preto, že to na jednej stránke nie je viditeľné. Ak je dokument prevažne zľava doprava s jedným blokom sprava doľava, prvé volanie RtLTextOut stále nakloní preferenciu celého súboru a na vašom jednostránkovom náhľade sa nič neprejaví. Príznak sa objaví o niekoľko týždňov neskôr, keď niekto vytlačí obojstrannú brožúru a strany vyjdú zrkadlovo otočené. Ak to nie je to, čo chcete, po dokončení úseku sprava doľava explicitne nastavte Direction späť:

// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;

Pri dokumente, ktorý sa skutočne číta sprava doľava, to nechajte tak. Cieľom je vedieť, že volanie má vplyv na celý dokument, aby sa predišlo prekvapeniu s brožúrou.

Registrujte písmo, ktoré distribuujete, nie to, o ktorom dúfate, že je nainštalované

Žiadne preusporiadanie nepomôže, ak písmo nemá glyfy na vykreslenie. Klasickým zlyhaním je report, ktorý sa vykresľuje bezchybne na počítači vývojára, kde je náhodou prítomný font Arial Unicode MS, ale na serveri zákazníka sa zobrazí ako riadky prázdnych štvorčekov, pretože Windows ticho nahradil písmo iným, ktoré nemá žiadne pokrytie arabčiny. Riešením je prestať dôverovať nainštalovaným systémovým písmam a zaregistrovať také, ktoré dodávate spolu s aplikáciou.

// Ship a known Arabic font and register it before drawing
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 obmedzenia. Písmo vložené cez RegisterUnicodeTTF sa integruje priamo do dokumentu a spracovanie vloženého Unicode v HotPDF vyžaduje verziu PDF 1.5 alebo novšiu; to predstavuje problém iba vtedy, ak softvér na spracovanie trvá na verzii PDF 1.4, ale v takom prípade je zlyhanie tiché. Druhé obmedzenie je skôr právneho než technického charakteru: súbory TrueType nesú príznaky oprávnení na vkladanie a písmo, ktoré vyzerá dobre na obrazovke, môže byť licencované spôsobom, ktorý zakazuje jeho šírenie v dokumentoch zákazníkov. Licenciu si overte pred vkladaním, nie až po sťažnosti.

Kompletný príklad pre konzolu

Spojením všetkých častí získate samostatný program, ktorý zapíše jednu stránku s arabským riadkom, hebrejským riadkom a zmiešaným riadkom obsahujúcim latinský názov produktu. Každý blok nastaví svoju znakovú sadu a potom kreslí v logickom poradí.

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // HotPDF main unit

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

    // A Latin heading goes through the ordinary TextOut path
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // Arabic: charset 178, logical order, RtLTextOut does the reordering
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

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

    // Mixed line: the embedded Latin word still reads left to right
    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é riadky sa čítajú sprava doľava, písmená sa spájajú tam, kde sa spájať majú, a v poslednom riadku sa token HotPDF nachádza zľava doprava v rámci arabského textu, čo je správny výsledok podľa špecifikácie, hoci prekvapí každého, kto vidí obojsmerné rozloženie prvýkrát. Tento posledný bod stojí za to zapísať do vašich akceptačných kritérií pred tým, než výstup skontroluje rodený hovorca, pretože vložený úsek čítaný „opačným“ smerom vzhľadom na okolitý text je najčastejšie nahlasovaný ako chyba, hoci ňou nie je.

Overenie výstupu

Stránka, ktorá vyzerá správne, nie je to isté ako stránka, ktorá je správne, preto ju skontrolujte tak, ako to urobí následný systém. Skopírujte text z prehliadača a porovnajte kódové body s pôvodným reťazcom; správne vizuálne poradie s rozhádzaným logickým poradím je reálnym chybovým stavom. Spustite vyhľadávanie v dokumente pre slovo, ktoré vidíte na stránke. Potom otvorte súbor na počítači, ktorý nemá vaše vývojové písma, čo najpravdepodobnejšie odhalí tichú náhradu. Nič z toho nenahradí čítanie skutočného dokumentu rodeným hovorcom, ktorý zachytí problémy, ktoré žiadny syntetický testovací reťazec neodhalí, preto si túto kontrolu naplánujte pred odoslaním formátu do produkcie.

RtLTextOut spracováva obojsmerné preusporiadanie a arabské kontextové spájanie, čo pokrýva drvivú väčšinu úloh spojených s reportami a dokumentmi sprava doľava. Tam, kde končí, sú písma, ktoré vyžadujú viac než len preusporiadanie a spájanie (napríklad indické rodiny písiem) a voliteľné funkcie OpenType, ktoré prechádzajú cez substitúciu jedného glyfu. Tie sú popísané spolu s detailmi pokrytia glyfov a tvarovania v sprievodnom článku o tvarovaní arabského a RTL textu v HotPDF.

Volania RtLTextOut, SetFont a RegisterUnicodeTTF uvedené v tomto článku sú súčasťou komponentu HotPDF Component pre Delphi a C++Builder.