Technical Article

Tvarovanie arabského a RTL textu v Delphi PDF pomocou HotPDF

Skúste odovzdať arabskú frázu يوضح ملف PDF metóde TextOut a otvorte výsledný súbor. Písmená smerujú opačne a každé z nich stojí samostatne s viditeľnou medzerou pred ďalším, akoby niekto písal anglický text od konca a za každým znakom stlačil medzerník. Nevyskytla sa žiadna výnimka ani varovanie. Výstup je však nesprávny, pretože sa neuskutočnili dve samostatné transformácie, od ktorých arabčina závisí. Pochopenie toho, čo tieto dve transformácie robia a ktoré volanie ich zabezpečuje, tvorí jadro problematiky vykresľovania zložitejších písiem do PDF.

HotPDF je natívny VCL PDF komponent pre Delphi a C++Builder a prácu s písmom sprava doľava (RTL) vykonáva automaticky prostredníctvom samostatného volania. V niektorých špecifických prípadoch má však svoje limity, o ktorých je dobré vedieť pred nasadením lokalizácie, preto im venujeme vlastnú časť na konci článku.

Prečo sa správny reťazec vytlačí nesprávne

Štandard Unicode uchováva text v logickom poradí – tak, ako ho píšete a čítate. Vykresľovací systém (renderer) však musí umiestniť glyfy vo vizuálnom poradí. Pri písmach písaných zľava doprava sa tieto poradia zhodujú, takže nad tým nikto neuvažuje. Pri arabčine a hebrejčine to však neplatí. Ak jeden riadok mieša rôzne smery, napríklad arabská veta obsahujúca latinské slovo "PDF" alebo cenu napísanú číslicami, obojsmerný algoritmus Unicode (UAX #9) presne určuje, ako sa fragmenty zľava doprava vnoria do riadka písaného sprava doľava. To je prvá transformácia – zmena poradia znakov (reordering) – a jej vynechanie spôsobí prevrátenie riadka.

Druhou transformáciou je kontextové tvarovanie (contextual shaping). Arabské písmeno sa kreslí odlišne podľa toho, kde v slove sa nachádza: na začiatku, v strede, na konci alebo samostatne. Kódový bod (codepoint) zostáva rovnaký, mení sa iba glyf. Spracovanie, ktoré pošle každý kódový bod priamo na jeho predvolený glyf, vyprodukuje presne ten nesúvislý výstup so samostatnými znakmi opísaný v úvode článku. Hebrejčina tento krok vynecháva, keďže jej písmená sa nespájajú, stále však vyžaduje zmenu poradia znakov. Arabčina vyžaduje obidva kroky, a preto je arabský reťazec tým najlepším na testovanie.

Na desktope nič z tohto neriešite. Keď formulár VCL vykresľuje arabčinu do komponentu TEdit, textový subsystém operačného systému text potichu usporiada a vytvaruje. Presne preto reťazec, ktorý na obrazovke vyzerá dokonale, skončí v jednoduchom PDF poškodený. Dátový tok obsahu (content stream) totiž neuchováva upraviteľný text. Uchováva iba umiestnené glyfy, takže ten, kto generuje tento tok, preberá na seba úlohu tvarovania, ktorú predtým riešil operačný systém. A práve metóda RtLTextOut túto prácu vykoná.

RtLTextOut zabezpečuje zmenu poradia aj spájanie znakov

Knižnica HotPDF oddelila latinskú cestu a cestu pre zložitejšie písma do dvoch rôznych metód. TextOut vytlačí to, čo mu odovzdáte, v presnom poradí. RtLTextOut najskôr zmení poradie znakov, vykoná kontextovú analýzu a až potom tlačí. Ktoré pravidlá písma sa aplikujú, definuje parameter znakovej sady (charset) v metóde SetFont: hodnota 178 označuje arabčinu, 177 hebrejčinu.

// 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 זה');

Jedna chyba tu dokáže spotrebovať viac času na ladenie ako ktorákoľvek iná. Metóda RtLTextOut sama obracia poradie reťazca. Ak jej teda predložíte text, ktorý ste už predtým ručne obrátili (zvyčajne ako pokus o obídenie problému cez klasický TextOut), obráti ho znova a ste tam, kde ste začali. Zákernosť spočíva v tom, že dvakrát obrátený text môže vyzerať správne pre jednoduchý čisto arabský testovací reťazec, no rozpadne sa hneď, ako riadok obsahuje latinské slovo alebo číslo, pretože tieto vložené úseky už nebudú rešpektovať pravidlá UAX #9. Vždy odovzdávajte text v logickom poradí a nechajte metódu, nech sa o to postará.

Rovnaké miešanie smerov v riadku často pletie revíznych pracovníkov viac než samotný kód. Vo vnútri riadka smerujúceho sprava doľava sa číslice a vložené latinské slová stále čítajú zľava doprava. Niekto, kto nepracoval s obojsmerným rozložením, sa pozrie na vygenerovanú faktúru, uvidí číslo účtu smerujúce "nesprávne" voči arabskej vete okolo neho, a nahlási to ako chybu. Ide pritom o správny výsledok podľa špecifikácie. Krátka poznámka v akceptačných kritériách pred prvým posúdením rodeným hovorcom vám ušetrí toto zbytočné kolo opráv.

Dostupnosť glyfov sa rieši ešte pred tvarovaním

Tvarovanie vyberá glyfy z písma. Ak ich písmo neobsahuje, nie je z čoho vyberať. Toto je presne to zlyhanie pri nasadení, ktoré vás obere o celé popoludnie: report funguje bezchybne na počítači vývojára, kde je nainštalované písmo Arial Unicode MS, no na serveri zákazníka sa zobrazí ako rad prázdnych štvorčekov, pretože Windows tam priradil náhradné písmo bez podpory arabčiny. Riešením je prestať sa spoliehať na systémové písma konkrétneho stroja a zaregistrovať vlastný súbor písma dodávaný spolu s aplikáciou.

// 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);

S týmto prístupom sú spojené dve obmedzenia. Písmo zaregistrované cez RegisterUnicodeTTF sa vkladá priamo do dokumentu, a spracovanie vloženého Unicode v HotPDF vyžaduje verziu dokumentu PDF 1.5 alebo novšiu. To môže byť problém, len ak systémy ďalej v reťazci vyžadujú verziu PDF 1.4, no ak k tomu dôjde, prejaví sa to tichým zlyhaním. Druhé obmedzenie je právne: súbory TrueType obsahujú informáciu o povolení na vkladanie (embedding permissions), a písmo, ktoré sa výborne vykresľuje na obrazovke, môže mať licenciu zakazujúcu jeho šírenie v klientskych dokumentoch. Skontrolujte to pred distribúciou a nie až po sťažnosti.

Druhé volanie, GetUnicodeGlyphForCodepoint, je vaším systémom včasného varovania. Pri štarte služby prejdite rozsahy kódových bodov, ktoré vaše dáta reálne používajú, a zaznamenajte vrátené ID glyfov. Chýbajúca podpora znakov sa tak prejaví ako riadok v logu pri spúšťaní aplikácie a nie ako chýbajúce znaky na faktúre, ktorú už dostal zákazník.

Text, ktorý je v Unicode, ale nie je písaný sprava doľava (napr. CJK znaky, vietnamčina s jej diakritikou alebo zmiešaný európsky text), prechádza klasickou cestou. Metóda TextOut prijíma typ WideString a vykresľuje ho cez zaregistrované písmo bez akejkoľvek obojsmernej analýzy. Oplatí sa tieto dve cesty v kóde reportov fyzicky oddeliť – vytvoriť jednu rutinu pre RTL reťazce a druhú pre všetko ostatné, aby bola logika lokalizácie viditeľná priamo na mieste volania a nie skrytá za príznakom, ktorý nakoniec niekto zabudne nastaviť.

Smer čítania patrí dokumentu, nie glyfom

Správne vykreslenie každého glyfu ešte neznamená kompletné riešenie. Norma ISO 32000-1 §12.2 definuje predvoľbu prehliadača s názvom /Direction, ktorá určuje celkový smer čítania dokumentu. Netýka sa samotných glyfov. Prehliadaču hovorí, ako usporiadať dvojstránky, z ktorej strany začať rozloženie protiľahlých strán a na ktorú stranu orientovať ovládacie prvky. Nič z toho sa neprejaví na samostatnej stránke, čo je presný dôvod, prečo sa na to zabúda.

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

Nastavenie vlastnosti Direction je všetko, čo treba urobiť: priradenie hodnoty pridá príznak vpDirection do nastavení ViewerPreferences dokumentu, takže jeden riadok kódu prenesie túto predvoľbu do súboru. Chybným postupom je jej vynechanie, čo sa zdá byť neškodné, pretože jednostránkový náhľad vyzerá v oboch prípadoch identicky. Keď však niekto vytlačí obojstrannú brožúru, strany vyjdú zrkadlovo obrátené a príčinou bude chýbajúci riadok kódu spred niekoľkých týždňov.

Kde končia možnosti tvarovania v HotPDF

Úprimný pohľad na obmedzenia vám ušetrí týždne testovania. Metóda RtLTextOut pokrýva obojsmernú zmenu poradia znakov aj kontextové spájanie arabčiny. Čo však automaticky nerobí, je všeobecná aplikácia funkcií OpenType. Voliteľné ligatúry a podobné typografické prvky sa musia riešiť cez GetSingleSubstituteGlyph(GID, 'liga'), čo vyhodnocuje jedno nahradenie za druhým (najskôr ID glyfu, potom značka funkcie) a vracia pôvodný glyf, ak sa funkcia neaplikuje. To postačuje na obsluhu známeho, obmedzeného zoznamu ligatúr, ktorý si sami spravujete. Nejde o plnohodnotný GSUB engine. Pri písmach, ktoré vyžadujú viac než len zmenu poradia a spájanie (typickým príkladom sú indické písma s preusporiadaním samohlások), vykonajte reálny test s reálnymi klientskych reťazcami predtým, než prisľúbite podporu danej lokalizácie. Skutočnosť, že funguje arabčina, nie je dôkazom, že bude fungovať aj písmom dévanágarí.

Overujte výstup od začiatku do konca, pretože stránka môže vyzerať správne, no pre ďalšie spracovanie byť nepoužiteľná. Tri kontroly odhalia väčšinu chýb. Skopírujte text z programu Acrobat a porovnajte kódové body s pôvodným reťazcom. Vyskúšajte vyhľadať slovo, ktoré vidíte na stránke, pomocou vyhľadávania v prehliadači. A otvorte výstup na počítači, ktorý nemá vaše vývojové písma, čo najlepšie odhalí nechcené nahrádzanie písiem. Nič z toho však nenahradí kontrolu rodeným hovorcom na reálnom dokumente, ktorá odhalí detaily, ktoré syntetické testy minú. Naplánujte si túto kontrolu ešte pred odovzdaním formátu.

Testovacie reťazce vyberajte zámerne a nerecyklujte len to, čo vám minule poslal prekladateľ. Odporúčané minimum pre každú lokalizáciu obsahuje: vetu napísanú výhradne daným písmom, vetu s vloženými latinskými názvami značiek, riadok s číslami a menou, a mená s diakritikou alebo kombinačnými znakmi. Skutočné mená zákazníkov často porušujú predpoklady, ktoré bežný text neotestuje, preto rozširujte sadu regresných testov o nový reťazec zakaždým, keď podpora rieši prípad, s akým ste sa doteraz nestretli.

Registrácii písiem, ich podmnožinám (subsetting) a bežnému API na vykresľovanie textu sa venuje článok o reportových výstupoch, písmach a obrázkoch v HotPDF. Keď tie isté dokumenty musia spĺňať aj profily prístupnosti, pravidlá pre označovanie jazykov a štruktúru z článku o validácii PDF/A a PDF/UA sa stavajú na základy popísané tu.

API pre smer sprava doľava a Unicode písma popísané vyššie sú súčasťou komponentu HotPDF pre Delphi a C++Builder. Stránka produktu odkazuje na kompletnú referenčnú príručku k výstupu textu.