Odešlete arabskou větu يوضح ملف PDF هذا do běžné funkce TextOut a stránka, která se vám vrátí, bude špatná hned dvěma způsoby najednou. Slova poběží zleva doprava místo zprava doleva a písmena budou odděleně ve svých izolovaných formách, místo aby se spojila do slov. K žádné chybě ale nedojde. Kód v Delphi se zkompiluje, soubor se otevře a recenzent, který umí číst arabsky, vám sdělí, že výstup je nepoužitelný. Nápravou je jedno volání, nikoli výměna knihovny: HotPDF směruje text psaný zprava doleva přes samostatnou metodu RtLTextOut, která řeší uspořádání, což běžná TextOut nezvládne. Tato stránka je pracovní referencí pro tuto metodu: její signatura a parametry, argument charset, který vybírá písmo, vedlejší efekt na úrovni dokumentu, nastavení písma, které musí přijít jako první, a chyby, které se nejčastěji dostanou na podporu, včetně jejich řešení
Signatura a parametry
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: PWORD; TextLength: Integer); overload;
Parametry X a Y ukotvují běh textu ve vlastním souřadnicovém systému stránky, měřeno od levého dolního rohu, přičemž Y roste nahoru – to je stejný počátek, který používá každé volání TextOut; RtLTextOut mění pořadí glyfů, nikoli bod, od kterého stránka měří. Parametr angle otáčí základní linii (baseline) naprosto stejně jako v TextOut, takže hodnota 0 nakreslí vodorovnou čáru. Parametr Text představuje řetězec v logickém pořadí, tedy v pořadí, ve kterém byste jej psali. Druhé přetížení (overload) přijímá stejná data UTF-16 jako hrubý buffer PWORD s explicitním počtem kódových jednotek, což je tvar, který se má použít, pokud text přichází z rozhraní API a nikoli jako řetězec Delphi. U starších verzí Delphi, které ještě neměly rozlišení přetížení pro tyto typy, je řetězcová podoba k dispozici pod názvem RtLTextOutStr se stejným seznamem parametrů
Rozdělení práce mezi tato dvě výstupní volání je striktní. TextOut kreslí kódové body (codepoints) v pořadí, ve kterém mu je předáte, což je správně pro latinku, cyrilici a CJK, ale špatně pro arabštinu a hebrejštinu. RtLTextOut naopak každý řádek nejprve přeřadí do vizuálního uspořádání zprava doleva a teprve poté kreslí, přičemž uvnitř řádku ponechává vložená latinská slova a číslice čitelné zleva doprava. HotPDF udržuje obě metody záměrně oddělené, místo aby odhadovalo směr ze znaků, takže volba, kterou metodu zavoláte, je volbou chování požadovaného skriptu. Používejte RtLTextOut pro zápisy zprava doleva a TextOut pro všechno ostatní. Proč vůbec existuje přeřazování pořadí (reordering), co vlastně dělá Unicode Bidirectional Algorithm a arabské kontextové spojování a kde končí možnosti tvarování textu (shaping) v HotPDF, to je předmětem doprovodného článku Tvarování arabského a RTL textu pomocí HotPDF; vše, co najdete níže, je pouze praktické nastavení

Argument charset rozhoduje o skriptu
To, co metodě RtLTextOut říká, zda má sázet arabštinu nebo hebrejštinu, není samotná metoda, ale písmo. SetFont přijímá jako čtvrtý argument znakovou sadu Windows (charset) a tato hodnota přenáší pravidla skriptu do volání pro směr zprava doleva: 178 vybírá arabštinu, 177 hebrejštinu. Nastavte znakovou sadu a pak kreslete; následující dva řádky vyjdou ve správném pořadí čtení bez další nutné konfigurace
// 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 זה');
Na jeden detail ohledně pořadí se ale snadno zapomíná: volání SetFont musí předcházet a musí se opakovat po každém AddPage, protože aktuální písmo, včetně znakové sady, nepřetrvá zlom stránky (page break). Pokud opakování vynecháte, druhá stránka spadne k jakémukoli písmu, které bylo zrovna aktivní, což pro arabštinu obvykle znamená prázdné čtverečky
Metoda nepřevrátí text, který jste už obrátili
Chybou, která spolkne nejvíce času při ladění (debugging), je dodání řetězce, který jste již ručně převrátili, do RtLTextOut. Lidé sahají po této metodě poté, co první pokus s obyčejnou funkcí TextOut nevyšel a text se zobrazil obráceně, takže častým dočasným řešením je převrátit znaky v kódu ještě před kreslením. RtLTextOut však interně převrací text samo, takže dříve obrácený řetězec je přehozen podruhé a ocitne se přesně tam, kde začal. Předávejte text v logickém pořadí (tak, jak byste ho napsali a nahlas přečetli) a nechte samotné volání udělat změnu pořadí
Tato past je zrádnější než běžné převrácení, protože dvakrát převrácený řetězec může vypadat správně u celoarabské testovací věty a okamžitě se rozbít v momentě, kdy řádek obsahuje latinské slovo nebo číslo. Uvnitř řádku psaného zprava doleva by se vložené latinské části měly číst zleva doprava a ruční přehození toto zanoření zničí, zatímco čistě arabský případ to může přežít. Takže chyba projde prvním smoke testem a objeví se až později na reálné faktuře obsahující číslo účtu. Jakmile přejdete na RtLTextOut, odstraňte každý ruční převrat textu
Vedlejší účinek u parametru Direction, který stojí za to znát
Zavolání RtLTextOut ovlivní více než jen řádek, který právě kreslíte. Také přepne preferenci směru čtení dokumentu na zprava doleva, tedy udělá přesně to, co byste jinak museli nastavit prostřednictvím vlastnosti Direction. Tato vlastnost přidá vpDirection do nastavení ViewerPreferences dokumentu, což prohlížeči říká, jak má uspořádat zobrazení dvou stránek vedle sebe a na které straně rozložení (spread) má začít. U dokumentu, který je čistě v arabštině nebo hebrejštině, je to přesně to, co chcete, a získáte to tak zdarma
Stojí ale za to o tom vědět, protože na jedné stránce to není vidět. Pokud se jedná o dokument, který je z většiny psaný zleva doprava a obsahuje jen jeden blok textu psaný zprava doleva, první volání RtLTextOut stejně změní preferenci celého souboru, a žádný důkaz na jedné stránce to neprozradí. Symptom se objeví až o týdny později, když někdo vytiskne oboustrannou brožuru a stránky vyjdou zrcadlově. Pokud toto chování nechcete, nastavte po dokončení bloku psaného zprava doleva Direction explicitně zpět:
// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;
U dokumentu, který se skutečně čte zprava doleva, do tohoto nastavení nesahejte. Záměrem je vědět, že toto volání má vliv na celý dokument, takže se nikdy nestane, že by vás po tisku překvapila brožura naruby
Registrujte písmo, které dodáváte, ne to, u kterého doufáte, že je nainstalované
Změna uspořádání textu k ničemu není, pokud písmo nemá glyfy, které by mohl vykreslit. Klasickým selháním je report, který se bezchybně vykreslí na počítači vývojáře, kde je náhodou Arial Unicode MS k dispozici, a následně se zobrazí jako řady prázdných obdélníků na zákazníkově serveru, kde Windows tiše nahradila písmo jiným, bez pokrytí arabštiny. Záchranou je přestat věřit systémovým písmům, která mohou a nemusí být nainstalována, a místo toho zaregistrovat písmo, které dodáváte s aplikací
// 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 هذا');
Tato registrace ovšem s sebou nese dvě omezení. Písmo vložené přes RegisterUnicodeTTF je do souboru embedováno (vloženo) a způsob zpracování vloženého Unicode v HotPDF vyžaduje PDF verze 1.5 nebo vyšší; to se stává problémem pouze v případě, když se něco v následném zpracování trvá na starším formátu PDF 1.4. V takovém případě ale k chybě dochází skrytě. Druhým omezením jsou licenční práva: soubory TrueType obsahují práva (embedding-permission bits) specifikující, zda mohou být vkládána. Písmo, které na monitoru vypadá hezky, může být licencováno takovým způsobem, že zakazuje jeho odesílání vloženého do zákaznických dokumentů. Zkontrolujte licenci dříve, než font začnete vkládat (embedovat), ne až po přijetí stížnosti
Kompletní příklad v konzoli
Spojením výše popsaných kroků získáme kompletní program, který vytvoří stránku s arabským řádkem, hebrejským řádkem a smíšeným řádkem nesoucím latinský název produktu. Každý blok má nastavený svůj charset (znakovou sadu) a text je odeslán do metody v logickém uspořádání (pořadí)
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.
Spusťte program a otevřete výsledek. Arabské a hebrejské řádky se čtou zprava doleva, písmena se spojují v místech, kde to určuje skript, a na posledním řádku sedí označení (token) HotPDF ukotvené zleva doprava uprostřed arabského běhu. Toto zanoření (nesting) je správným bidirekcionálním výsledkem a nikoliv chybou, ačkoli jej jako chybu často nahlašují recenzenti, kteří takovou konfiguraci vidí poprvé. Odkazovaný článek o tvarování textu výše vysvětluje, proč pravidla Unicode právě toto zanoření vyžadují a jak zformulovat kritéria přijetí tak, abyste tento problém už nikdy nemuseli s klienty řešit
Běžné chyby a jejich opravy
Každá níže zmíněná chyba se již reálně objevila na vláknech technické podpory a vždy vycházela ze skutečností popsaných v předchozích odstavcích
- Výstupní text se čte pozpátku nebo jsou slova ve smíšeném řádku neuspořádaná — pravděpodobně jste manuálně řetězec otočili dříve, než jste jej předali funkci, většinou po předchozích pokusech s klasickým
TextOut. Smažte všechna manuální obracení textu a pošlete data do funkce v logickém uspořádání.RtLTextOutprovádí změnu uspořádání interně - Písmena jsou vytištěna rozpojená, a to každé v oddělené formě — předali jste text klasické funkci
TextOutnebo jste zapomněli přesSetFontnakonfigurovat charset směrovaný na zprava doleva. Vždy sázejte tyto texty skrzeRtLTextOuts předáním hodnoty 178 pro arabštinu či 177 pro hebrejštinu jakožto čtvrtého argumentu ve voláníSetFont - Na počítači klienta (customer's machine) jsou vykresleny pouze prázdné čtverečky — operační systém Windows nemohl najít vhodný font, takže nahradil požadované písmo jiným, jež ale arabské ani hebrejské znaky nepodporuje. Přestaňte se spoléhat na to, co uživatel má nebo nemá na svém systému. Skrze
RegisterUnicodeTTFzaregistrujte font, jež do instalace své aplikace přibalíte, a odvolejte se na něj následně funkcíSetFont - Druhá stránka se vykresluje nesprávným písmem — vaše navolené písmo (current font) nepřežije přesun funkcí
AddPagena další list. Dbejte na zopakování voláníSetFonta to včetně správného nastavení hodnoty pro charset - Zkušební oboustranný duplex tisk vytvořil pouze převrácený (zrcadlový) booklet z textu vedeného převážně formou LTR — úplně to první vyvolání
RtLTextOutpřevrátilo kvůli svému vedlejšímu chování vlastnost dokumentu jménemDirection. Proto ihned poté, kdy dokončíte sázení zprava doleva, navraťte stav příkazemPdf.Direction := LeftToRight - Embedovaný text v Unicode vykazuje neočekávanou (či dokonce prázdnou) sníženou funkčnost pro některé starší navazující (downstream) postupy — objevilo se něco, co vás omezuje ve workflow tím, že vynucuje dodržet formát PDF verze 1.4, jenže to odporuje požadavku nástroje pro zpracování embedded Unicode v knihovně HotPDF požadující ve výchozím nastavení minimálně formát 1.5 nebo vyšší. Proto nezapomínejte povolit formát s vyšším verzováním (raise the document version), případně toto umělé omezení zcela odstraňte (remove downstream constraint)
Dříve, než váš formát zašlete klientům nebo uvolníte formou aktualizace do produkce (ships), ujistěte se důkladným otestováním v reálném prohlížeči dokumentů. Překopírujte text z dokumentu jinam, pusťte vevnitř zabudovaný vyhledávač, otestujte takový hotový soubor někde na cizím zařízení postrádajícím instalované development fonty a nezapomeňte poslat finální dokument nativně mluvícímu uživateli pro závěrečné prověření čitelnosti. Abyste získali představu, na co vše se přesně zaměřit v testování, pro jaká slova si tvořit zkušební sestavy dat (test-string corpus) i pro nalezení tabulky zobrazující pokrytí, nahlédněte do již dříve zmiňovaného doprovodného článku zabývajícím se tímto: Tvarování arabského a RTL textu pomocí HotPDF
Ukázková rozhraní a volání (jako je RtLTextOut, SetFont či RegisterUnicodeTTF) zobrazená výše jsou součástí komponenty HotPDF Component pro Delphi a C++Builder