Technical Article

RtLTextOut u HotPDF-u: PDF tekst zdesna nalevo u Delphi-ju

Pošaljite arapsku rečenicu يوضح ملف PDF هذا običnoj funkciji TextOut i stranica koja se vrati biće pogrešna na dva načina odjednom. Reči idu sleva nadesno umesto zdesna nalevo, a slova stoje odvojeno u svojim izolovanim oblicima umesto da se spajaju u povezane reči. Ne prijavljuje se nikakva greška. Delphi se kompajlira, datoteka se otvara, a recenzent koji čita arapski reći će vam da je izlaz neupotrebljiv. Rešenje je jedan poziv, a ne zamena biblioteke: HotPDF usmerava tekst zdesna nalevo kroz posebnu metodu, RtLTextOut, koja upravlja promenom redosleda koju običan TextOut ne vrši. Četiri stvari u vezi sa tom metodom odlučuju da li je izlaz upotrebljiv: šta radi sa stringom, kako njen argument charset bira pismo, promena na nivou dokumenta koju pravi kao sporedni efekat i rad sa fontovima koji mora da se uradi pre svega.

Zašto je za tekst zdesna nalevo potreban poseban poziv

PDF tok sadržaja (content stream) ne čuva tekst koji se može menjati. On čuva glifove na fiksnim pozicijama, što znači da ono što emituje tok ima zadatak da odluči kojim redosledom ti glifovi idu. Na ekranu je operativni sistem to radio za vas: ubacite arapski u TEdit i OS tekstualni stek menja redosled i spaja ga pre nego što uopšte vidite piksel. To je upravo razlog zašto string izgleda savršeno u vašoj formi, a kvari se u PDF-u. Desktop je taj posao obavio nečujno, a onog trenutka kada sami upišete svoj tok sadržaja, taj posao se vraća na vašu stranu.

TextOut veruje na reč. On iscrtava kodne tačke (codepoints) redosledom kojim ih prosledite, sleva nadesno, što je ispravno za latinicu, ćirilicu i CJK (kineski, japanski, korejski), a pogrešno za arapski i hebrejski. RtLTextOut je poziv koji najpre menja redosled linije u vizuelni redosled zdesna nalevo, a zatim je iscrtava. HotPDF namerno drži ove dve metode odvojenim umesto da pogađa smer na osnovu karaktera, tako da je izbor koju ćete pozvati zapravo izbor ponašanja pisma koje dobijate. Dublja mehanika dvosmerne promene redosleda (bidirectional reordering) i arapskog kontekstualnog spajanja su sopstvena tema, pokrivena u članku o oblikovanju arapskog i RTL teksta pomoću HotPDF-a; ovde je praktična poenta uža. Koristite RtLTextOut za delove teksta zdesna nalevo, koristite TextOut za sve ostalo, i nikada ne usmeravajte jedno kroz drugo.

Dijagram kako RtLTextOut menja redosled mešovite arapske i latinične linije u vizuelni redosled zdesna nalevo pre iscrtavanja u PDF
RtLTextOut menja redosled svake linije u vizuelni redosled pre iscrtavanja: delovi zdesna nalevo zadržavaju svoj redosled dok se ugrađene latinične reči i cifre čitaju sleva nadesno unutar linije.

Argument charset odlučuje o pismu

Ono što govori funkciji RtLTextOut da li raspoređuje arapski ili hebrejski nije metoda, već font. SetFont prima Windows charset kao svoj četvrti argument, i ta vrednost prenosi pravila pisma u poziv zdesna nalevo: 178 bira arapski, 177 bira hebrejski. Podesite charset, a zatim iscrtajte, i dve linije ispod će ispasti u ispravnom redosledu čitanja bez ikakvih dodatnih podešavanja.

// 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 detalja o ovim koordinatama je lako propustiti. Pozicija koju prosleđujete je i dalje početak dela teksta u sopstvenom koordinatnom sistemu stranice, mereno od donjeg levog ugla sa Y koji raste nagore, što je isti početak koji koristi svaki TextOut; RtLTextOut menja redosled glifova, a ne mesto od kog stranica meri. I kao i kod svakog poziva za iscrtavanje, SetFont mora doći prvi i mora se ponoviti nakon svakog AddPage, jer trenutni font ne preživljava prelom stranice. Zaboravite na ponavljanje i druga stranica će se vratiti na font koji je bio aktivan, što za arapski obično znači prazne kvadrate.

Ne obrće tekst koji ste već ručno obrnuli

Jedina greška koja ovde oduzima najviše vremena za otklanjanje grešaka jeste prosleđivanje funkciji RtLTextOut stringa koji ste već ručno obrnuli. Programeri dolaze do ove metode nakon što je prvi pokušaj sa običnim TextOut ispao naopako, pa je uobičajeno privremeno rešenje obrtanje karaktera u kodu pre iscrtavanja. RtLTextOut samostalno vrši obrtanje iznutra, tako da se unapred obrnuti string obrće i drugi put i vraća se tačno tamo odakle je počeo. Prosledite tekst u logičkom redosledu, redosledom kojim biste ga kucali i čitali naglas, i pustite da poziv odradi promenu redosleda.

Zamka je nezgodnija od običnog obrtanja jer dvostruko obrnuti string može izgledati ispravno za jednu test frazu na čisto arapskom, a zatim se pokvariti čim linija sadrži latiničnu reč ili broj. Unutar linije zdesna nalevo ti ugrađeni delovi bi trebalo da se čitaju sleva nadesno, a ručno obrtanje uništava to gnežđenje dok slučaj sa čisto arapskim tekstom uspeva da ga preživi. Tako greška prolazi vaš prvi brzi test i pojavljuje se kasnije na stvarnoj fakturi sa brojem računa u njoj. Uklonite svako ručno obrtanje onog trenutka kada pređete na RtLTextOut.

Sporedni efekat Direction koji vredi znati

Pozivanje funkcije RtLTextOut menja više od same linije koju iscrtavate. Ono takođe menja željeni smer čitanja dokumenta na smer zdesna nalevo, što biste inače sami podesili preko svojstva Direction. Taj seter dodaje vpDirection u ViewerPreferences dokumenta, što govori čitaču kako da rasporedi dvolisne prikaze i sa koje strane počinje raspored naspramnih stranica. Kada je ceo dokument na arapskom ili hebrejskom, to je upravo ono što želite i dobijate to besplatno.

To vredi znati upravo zato što je nevidljivo na jednoj stranici. Ako je dokument uglavnom sleva nadesno sa jednim blokom zdesna nalevo, prvi poziv RtLTextOut će ipak promeniti željeni smer celog fajla, a ništa u vašem probnom ispisu na jednoj stranici to neće pokazati. Simptom se pojavljuje nedeljama kasnije kada neko štampa dvostranu knjižicu i stranice izađu preslikano u ogledalu. Ako to nije ono što želite, vratite Direction eksplicitno nakon dela teksta zdesna nalevo:

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

Za dokument koji se zaista čita zdesna nalevo, ostavite ga kako jeste. Poenta je znati da poziv ima efekat na nivou celog dokumenta kako se iznenađenje sa knjižicom nikada ne bi dogodilo.

Registrujte font koji isporučujete, a ne onaj za koji se nadate da je instaliran

Nijedna promena redosleda nije važna ako font nema glifove za iscrtavanje. Klasičan neuspeh je izveštaj koji se besprekorno prikazuje na računaru programera, gde je Arial Unicode MS slučajno prisutan, a izlazi kao nizovi praznih kvadrata na klijentskom serveru gde je Windows tiho zamenio font onim koji uopšte nema podršku za arapski. Rešenje je da prestanete da verujete instaliranim sistemskim fontovima i registrujete onaj koji isporučujete uz aplikaciju.

// 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 هذا');

Dve granice dolaze sa registracijom. Font uvezen preko RegisterUnicodeTTF se ugrađuje, a HotPDF-ovo rukovanje ugrađenim Unicode-om zahteva dokument u PDF 1.5 ili novijem formatu; to utiče samo ako nešto dalje u lancu insistira na PDF 1.4, ali kada se to desi, neuspeh je tih. Druga granica je pravne, a ne tehničke prirode: TrueType datoteke sadrže bitove za dozvolu ugradnje, a font koji izgleda dobro na ekranu može biti licenciran na način koji zabranjuje njegovo slanje unutar korisničkih dokumenata. Potvrdite licencu pre nego što ga ugradite, a ne nakon žalbe.

Kompletan konzolni primer

Spajajući delove, evo samostalnog programa koji ispisuje jednu stranicu sa arapskom linijom, hebrejskom linijom i mešovitom linijom koja sadrži latinični naziv proizvoda. Svaki blok podešava svoj charset, a zatim crta u logičkom redosledu.

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.

Pokrenite ga i otvorite rezultat. Arapske i hebrejske linije se čitaju zdesna nalevo, slova se spajaju tamo gde ih pismo spaja, a u poslednjoj liniji token HotPDF stoji sleva nadesno unutar arapskog dela, što je ispravan rezultat prema specifikaciji, iako iznenađuje svakoga ko prvi put vidi dvosmerni raspored. Ta poslednja tačka je vredna uvrštavanja u vaše kriterijume prihvatanja pre nego što izvorni čitač pregleda izlaz, jer je ugrađeni deo koji se čita u "pogrešnom" smeru u odnosu na okolno pismo stvar koja se najčešće prijavljuje kao greška kada to zapravo nije.

Verifikacija izlaza

Stranica koja izgleda ispravno nije isto što i stranica koja je ispravna, pa je proverite na način na koji će to uraditi neki nizvodni sistem. Kopirajte tekst nazad iz čitača i uporedite kodne tačke sa vašim izvornim stringom; ispravan vizuelni redosled sa pomešanim logičkim redosledom je čest način neuspeha. Pokrenite pretragu unutar dokumenta u čitaču za reč koju možete videti na stranici. Zatim otvorite datoteku na računaru koji nema vaše razvojne fontove, onom koji će najverovatnije izložiti tihu zamenu fontova. Ništa od toga ne može zameniti izvornog govornika koji čita jedan stvaran dokument, što hvata probleme koje nijedan sintetički test string neće otkriti, pa uvrstite taj pregled u kalendar pre nego što pošaljete format.

RtLTextOut upravlja dvosmernom promenom redosleda i arapskim kontekstualnim spajanjem, što pokriva veliku većinu rada sa izveštajima i dokumentima zdesna nalevo. Tamo gde se on zaustavlja, pisma koja zahtevaju više od promene redosleda i spajanja kao što su indijske porodice pisama, i opcione OpenType funkcije koje idu kroz zamenu pojedinačnih glifova, opisani su zajedno sa detaljima o pokrivenosti glifovima i oblikovanju u pratećem članku o oblikovanju arapskog i RTL teksta pomoću HotPDF-a.

Pozivi RtLTextOut, SetFont i RegisterUnicodeTTF koji su ovde prikazani deo su HotPDF komponente za Delphi i C++Builder.