Tehnički članak

Oblikovanje arapskog i teksta zdesna nalevo (RTL) u Delphi PDF-ovima pomoću HotPDF-a

Prosledite arapsku frazu يوضح ملف PDF funkciji TextOut i otvorite rezultat. Slova idu naopako, a svako od njih se nalazi u svom izolovanom obliku sa vidljivim razmakom pre sledećeg, kao da je neko ukucao engleski unazad i pritisnuo razmak između svakog karaktera. Nijedan izuzetak (exception) nije pokrenut. Nikakvo upozorenje nije odštampano. Izlaz je prosto pogrešan, a pogrešan je zato što se dve odvojene transformacije od kojih arapski jezik zavisi nikada nisu dogodile. Poznavanje toga koje su to dve transformacije i koji poziv ih izvršava jeste ono na šta se najvećim delom svodi izlaz PDF-ova sa kompleksnim pismima (complex-script)

HotPDF je izvorna VCL PDF komponenta za Delphi i C++Builder, a rad zdesna nalevo obavlja umesto vas kroz zaseban poziv. On se takođe zaustavlja na nekoliko specifičnih mesta o kojima bi trebalo da znate pre nego što se posvetite određenom jeziku (locale), tako da ovaj tekst mapira koncepte i iskrene granice; praktično postavljanje za sam poziv nalazi se u referentnom članku RtLTextOut

Zašto se ispravan string i dalje štampa pogrešno

Unicode čuva tekst u logičkom redosledu, redosledu po kojem ga kucate i čitate naglas. Program za iscrtavanje (renderer) mora da postavi glifove po vizuelnom redosledu. Kod pisama (scripts) koja idu sleva nadesno, ti redosledi se podudaraju i niko ne razmišlja o tome. Za arapski i hebrejski jezik se ne podudaraju, i kada se u jednom redu mešaju smerovi, recimo u arapskoj rečenici koja sadrži latinični znak "PDF" ili cenu ispisanu brojevima, Dvosmerni Unicode algoritam (Unicode Bidirectional Algorithm – UAX #9) odlučuje tačno kako se fragmenti koji idu sleva nadesno ugnježđuju (nest) u okviru linije zdesna nalevo. To je prva transformacija, preuređivanje (reordering), a njeno preskakanje je ono što preokreće liniju

Druga je kontekstualno oblikovanje (contextual shaping). Arapsko slovo se crta drugačije u zavisnosti od toga gde u reči pada: na početku (initial), u sredini (medial), na kraju (final) ili kada stoji samo. Kodna tačka (codepoint) ostaje ista tokom celog procesa; menja se samo glif. Proces (pipeline) koji predaje svaku kodnu tačku direktno njenom podrazumevanom glifu proizvodi upravo onakav nepovezan, izolovano-oblikovan izlaz (isolated-form output) iz uvodnog pasusa. Hebrejski preskače ovaj korak, budući da se njegova slova ne spajaju, ali mu je i dalje potrebno preuređivanje. Arapskom su potrebna oba i to je razlog zbog koga se testiranje sprovodi pomoću arapskog, a ne hebrejskog stringa

Na radnoj površini (desktop), ništa od ovoga nije Vaš problem. Kada VCL forma ucrta arapski tekst u komponentu TEdit, tekstualni stek (text stack) operativnog sistema je tiho preuređuje i oblikuje, a upravo je to razlog zašto string koji izgleda savršeno na ekranu ispadne neispravan u nekom naivnom PDF-u. Tok sadržaja (content stream) ne skladišti tekst koji se može menjati (editable). On skladišti pozicionirane glifove, tako da ko god emituje tok, nasleđuje posao oblikovanja koji je OS nekada obavljao. RtLTextOut je poziv koji vraća taj posao natrag

Šta RtLTextOut oblikuje za Vas

HotPDF drži putanju za latinicu i putanju za složena pisma (complex-script) kao dve različite metode. TextOut štampa ono što mu date redosledom kojim ste mu dali. RtLTextOut prvo obavlja obe transformacije — dvosmerno preuređivanje duž cele linije, kontekstualnu analizu za pisma sa spajanjem — i tek onda štampa. Pravila kojeg će se pisma primeniti putuju preko charset-a fonta umesto kroz sam poziv, pa je smer (direction) eksplicitan izbor na svakom mestu gde se nalazi poziv umesto pretpostavke napravljene na osnovu karaktera. Postavka parametar po parametar, charset vrednosti, koraci za registraciju fonta i potpuni primer koda koji se može kompajlirati nalaze se u referentnom članku RtLTextOut; ovaj tekst će se zadržati na značenju transformacija, tome gde se one zaustavljaju i na tome kako dokazati da su radile

Jedno pravilo pri korišćenju je važno čak i iz ove perspektive: ulaz mora biti u logičkom redosledu, jer RtLTextOut sam vrši obrtanje, a string koji ste već ručno okrenuli izaći će dvostruko obrnut — referentni članak detaljno objašnjava ovu zamku i njeno rešavanje. Ono zbog čega ta zamka zaslužuje da se pomene ovde jeste zašto ona prolazi na testiranju. Dvostruko obrnuti čisto arapski string može da izgleda savršeno tačno, a raspadne se tek kada red nosi latiničnu reč ili neki broj, jer ti umetnuti redovi (embedded runs) više nisu ugnježdeni onako kako diktira UAX #9 algoritam. Greška se ne nalazi u renderovanju (rendering); greška je u tome što je u algoritam ubačen tekst koji je već napola obrađen

Isto to ponašanje sa mešovitim pravcima češće zbunjuje recenzente (reviewers) nego što zbunjuje sâm kôd. Unutar linije zdesna nalevo, cifre i umetnute latinične reči se i dalje čitaju sleva nadesno. Neko ko nije radio na dvosmernom rasporedu (layout) pogledaće u renderovanu fakturu, videti da se broj računa čita na "pogrešan" način u odnosu na arapski oko njega, i to će zavesti kao grešku (bug). Ipak, to je u skladu sa specifikacijom (spec-correct) kao rezultat. Kratka napomena u kriterijumima za prihvatanje, napisana pre prvog prolaska kroz tekst koji radi izvorni govornik (native-speaker pass), sačuvaće vreme koje bi bilo potrošeno na ovakve prepravke (round trip)

Kada su preuređivanje i spajanje dovoljni, a kada ne

Za neprekidan tekst (running text) na arapskom i hebrejskom jeziku — izveštaje, fakture, ugovore, pisma — preuređivanje i kontekstualno spajanje je ceo posao, i RtLTextOut to obavlja sam. Granica se pojavljuje onda kada tipografija traži nešto više od spajanja. HotPDF-ov odgovor na arapskoj strani je podrazumevano isključeni oblikovalac na strani proizvođača (opt-in producer-side shaper): podesite AutoShapeArabic := True i komponenta prepisuje tekst unet logičkim redosledom u Unicode prezentacione oblike (Unicode Presentation Forms) pre dvosmernog prolaza, tako da se spajanje oblika računa u odnosu na logičke susede, a uvijanje ligatura (ligature folds) je ugrađeno (baked) u kodne tačke koje taj PDF zapravo nosi, radije nego da ostavi programu za pregledanje (viewer) da to razreši. Ovaj preklopnik (switch) je po podrazumevanoj vrednosti isključen (off), a izlaz je stabilan za svaki bajt (byte-stable) kada je on isključen, tako da je uključivanje namerna odluka po liniji izrade dokumenta (document pipeline), a ne opšta (globalna) nadogradnja. Isti model proširuje se i na druga pisma zdesna nalevo (right-to-left scripts) sa spajanjem koja HotPDF oblikuje: Sirijski (Syriac), N'Ko, Adlam i Hanifi Rohindža svako ima sopstvenu auto-shape oznaku (flag) koja je preslikana iz one kod arapskog pisma

Opcionalna OpenType svojstva ponovo su potpuno drugačiji mehanizam. Opcionalne ligature (Discretionary ligatures) i slične funkcije jedne supstitucije idu preko GetSingleSubstituteGlyph(GID, 'liga'), koji rešava po jednu supstituciju (zamenu) u jednom prolazu — prvo ID unetog glifa, a zatim oznaka funkcije (feature tag) — i vraća ulazni glif nepromenjen kada se to svojstvo (feature) ne primenjuje. To je dovoljno da vozi poznatu i konačnu listu ligatura koju sami održavate. To nije kompletan GSUB mehanizam (engine), i razlika je upravo ono mesto gde ambiciozni planovi vezani za locale kreću nizbrdo: tok oblikovanja (shaping pipeline) koji besprekorno obrađuje arapski pokazao je da obavlja samo preuređivanje i spajanje, i to je sve

Pokrivenost između pisama (Coverage across scripts)

Arapski primenjuje (exercises) obe transformacije, zbog čega to jeste niz (string) na kojem treba sprovoditi testiranje, i zbog čega arapski test (Arabic pass) predstavlja najjači dokaz da linija (pipeline) zapravo funkcioniše. Hebrejskom je potrebno samo preuređivanje (reordering) a ne i spajanje, s obzirom na to da su njegova slova samostalna; ukoliko se hebrejski jezik renderuje ispravno, a arapski ispadne nepovezan, to je onda znak da je dvosmerna (bidirectional) polovina u redu i da se ona kontekstualna polovina nikada nije ni izvršila. Persijski i urdu jezik oslanjaju se na arapsko pismo i usvajaju njegovo ponašanje, s tim da urdu preferira Nastaliq stil te da takva odluka oko fonta donosi posledice na samo čitanje (legibility) koje bi izvorni govornik morao da proceni

Tajlandski jezik nalazi se potpuno sa druge strane. On se piše sleva nadesno, tako da tu ne treba raditi nikakvo dvosmerno ispisivanje, njegova slova se ne spajaju te njemu i ne treba neka kontekstualna analiza; nizovi (strings) teksta na tajlandskom jeziku prolaze kroz uobičajenu TextOut putanju kao latinični. Ono šta tajlandski jezik poseduje jeste slaganje oznaka (stacked marks) – tu su znakovi za samoglasnike i tonove iznad i ispod suglasnika koji čini osnovu (base consonant) – te da li su ti znakovi pravilno raspoređeni zavisiće isključivo od samog fonta prilikom generisanja onih kombinovanih oznaka na način koji bi ih slagao da im ne treba pomoć od mašinerije za oblikovanje. Većina fontova koja su preodređena na upotrebu u tajlandskom jeziku to i rade. Testiranje ipak sprovedite na onom fontu koji ugrađujete, a ne na nekom koji prosto liči na njega

Devanagari (Devanagari) kao i čitav preostali deo indijske (Indic) jezičke familije jesu pravi primer da tu prestaje svaki dalji uobičajeni rad. Njihovi znaci kojim se označavaju samoglasnici poređani su uvek oko nekih grupa u kojima stoji par suglasnika, te njihove strukture konjunkata nastaju samo usled lanca sačinjenog od različitih kontekstnih zamena. Ono što im za to zapravo zatreba je onaj celokupan GSUB mehanizam jer im neće biti dovoljno preuređivanje (reordering) ili pak spajanje. Ako Vam je na mapi da uradite neku platformu (locale) za indijski jezik, onda svakako prvo pre svega isprobajte (pilot) testiranje na tekstovima koji su direktno uzeti od korisnika – ako arapski sistem radi to nije ni u kakvom smislu osiguranje da će Devanagari isto to uraditi. Kad Vam zatrebaju nizovi znakova (strings) u CJK verzijama ili onaj u vijetnamskom sa njegovim oznakama (stacked diacritics), kao i za tekst koji je recimo skroz izmešan u kombinaciji evropskih jezika tu sve obično ide jednom normalnom uobičajenom stazom kojoj neće ni zatrebati dvosmerna analiza (bidirectional analysis). Pametno je razdvojiti tu izveštajnu logiku i u onom programerskom segmentu teksta postaviti jasna fizička raslojavanja između puta jednih, što ima kodnu strukturu namenjenu isključivo za RTL staze te i ove ostale. Iz takvog nekog koda svaka logika postavljenog sistema (locale logic) prosto sija na pozvanom segmentu i to na veoma lak i jasan način (at the call site), a neće Vam biti zaturena ispod bilo kog znaka (flag) na koga svako u svakom sekundu ume tu da previdi te zaboravi da označi postavke (to set)

Pokrivenost glifovima se određuje pre nego što oblikovanje uopšte započne

Oblikovanje bira glifove iz fonta. Ako ih font ne sadrži, nema šta da se izabere, što je razlog zašto klasična greška pri primeni (deployment failure) — besprekorno radi na mašini programera, a samo prazni kvadrati na serveru kupca nakon tihe zamene fontova (font substitution) — predstavlja problem pokrivenosti (coverage problem), a ne problem sa oblikovanjem (shaping). Praktičan lek, registracija fonta koji isporučujete (ship) umesto da verujete onome što mašina već ima instalirano, detaljno je objašnjena u referentnom članku. Konceptualna poenta je da se pokrivenost mora uspostaviti pre nego što bilo kakvo pitanje oko oblikovanja (shaping) postane smisleno, i to se može uraditi programski umesto da se samo baci pogled (eyeballing) na izlaz (output)

// After RegisterUnicodeTTF, audit coverage for the
// codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

Sama registracija donosi dva ograničenja — nivo PDF 1.5 potreban za rukovanje ugrađenim Unicode-om, kao i bitove za dozvolu za ugradnju u samom fontu — a i jedno i drugo objašnjeno je zajedno sa koracima za podešavanje (setup steps) u RtLTextOut referentnom članku. Ono šta pripada ovom mestu je revizorska navika (audit habit): GetUnicodeGlyphForCodepoint služi Vam i tu stoji kao vaš sistem za rano uočavanje opasnosti (early-warning system). Prođite opsege kodnih tačaka koje vaši podaci (data) zaista (actually) i koriste tokom starta datog servisa (when the service starts up) tako što ćete zabeležiti glif ID vrednosti (glyph IDs) povratnog materijala. Bilo koji jaz po tom pitanju u vidu pokrivenosti, na taj način je odmah vidljiv ukoliko se samo obrati pažnja na evidenciju o paljenju u procesu (startup log during rollout), za razliku od traženja nedostataka u vidu karaktera kojih fali na nekoj fakturi (invoice) koju je već vaš klijent uveliko i dobio na uvid

Smer (redosled) čitanja je deo dokumenata a ne samih glifova

Sređivanje svih glifova ostavlja jednu stvar po strani pa tu ima propusta (undone). Sam onaj propis po imenu ISO 32000-1 §12.2 definiše određena svojstva u interfejsima programa za pregledanja koja se zovu (viewer preference called) /Direction a služe da diktiraju generalni smer (overall reading order) prikaza svakog dokumenta ponaosob. To sve, pogađate, i nema neke direktne poveznice sa radom na samim glifovima (touches no glyphs). Glavni cilj u svemu ovome tiče se programa (viewer) za pregled i njegovog snalaženja – u situacijama da imate otvorene dve stranice jednu pored druge (two-up spreads), kako one tada treba da su posložene te da se utvrdi koju pre pregledati s leva a koju zdesna kao i kako to utiče na navigaciju celog interfejsa. Na prosto iscrtanom dokumentu (single page) u pregledu toga i nema pa taj deo biva lako i veoma često previđen (forgotten)

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

Samo postavljanje ovog upita po pitanju pravca tj Direction biva zapravo taj ceo vaš rad ovde: upit s postavljenim elementima donosi sobom i to vpDirection pravo tu u ono navedeno kodno ime fajla (document's) i svojstvo (ViewerPreferences), i onda vidimo tu da jedan kratak (one line carries) ubačaj po pitanju postavki biva presudni trag koji na dalje oblikuje svaku takvu liniju. Kad je pravac tog celog prikaza definisan pri komandi RtLTextOut tad sve dolazi besplatno (for free) i vi ne razmišljate, iz razloga tu se ugrađuje promena u strukturi pozadine i menja čitav onaj pravac samog dokumenta u celosti kao nusprodukt – što inače pominje svaki onaj članak za preporučenu izradu oko svih onih tipičnih radnji oko uvrstavanja i povlačenja po pitanju mešovitih pravila. To i takve situacije da morate da rešavate, pa da taj svoj smer teksta podesite desno-ka-levo dolaze ukoliko imate generisani zapis proizveden različitim postupkom u kom je tekstualni put obradio program drugačije od vas pre nego šta se taj isti dokument (ordinary path) predstavi vama na uvid (pre-shaped upstream). Ignorišite ovu fazu, pa sve na tom istom prikazu (single-page proof) ima sasvim naoko prelep izgled a onda sutra na poslu neki od radnika da odštampa obostranu štampu, pa vam svi tekstovi postanu usled takve štampe skroz zarotirani s lica da i ne pominjemo taj čisti promašaj (missing one-liner) propušten pri čitavim pre nedelju i po na kontrolama takvih poslova

Provera oblikovanog izlaza

Proveravajte posao u celosti sa krajeva (end to end), na svakom delu postupka ukoliko to ne odradite desiće vam se da će vama takva strana (page) možda s ekrana ličiti na neki lep i normalan i uređen posao a biti posve skroz besmislena pri testovima što će tek doći na redu kasnije. Prva takva tri (three checks) detalja sa ispravkama rešiće brdo onih najvećih (most problems) nezgoda. Izvucite taj sadržaj (Copy the text back out) koji testirate na način da tekst s prikaza prenesete u obične sveske te uvidite tačne promene znakova na onom vašem polaznom materijalu koji vi koristite za ispitivanje (source string). Radite to i s pretragama dokumenta po pogledu tražeći samo jednu pojedinu reč te se tek iz takve neke proste stvari za pregled (viewer's in-document search) na prikazanoj bazi za testiranje, uvidite i tačno uočite pretrage te se odmah razume ako tu ne postoji takvo neko prisustvo date reči. Proverite kako će da se ta datoteka prikaže na tuđim računarima, na jednom takvom koji recimo kod sebe uopšte nema iste one ubačene delove instalacionog procesa (development fonts), on će odmah primetiti sve nedoslednosti zamene (substitution) tog datog instalacionog predmeta što mu tu pri testiranju upravo fali da to on ispita. Pa se ni svi takvi testirani nivoi sa svim pređašnjim stvarima, ma ni izbliza ni delimično ne mogu porediti i posmatrati se u odnosu na kontrolu što odradi čovek, (native reader) izvorni korisnik tog jezika koji posmatra ovakav radni i pravi materijal onim živim testiranjem. Nijedan mehanizovani test po sintetičkoj osnovi to nažalost nikad ni neće razumeti te postići (synthetic corpus will). Neka to čitanje iz prvog ugla onda ipak bude zadatak pre predaje i zvaničnog (ships) puštanja materijala

Brajte one probne sadržaje sa svesnom odlukom namere umesto toga što vršite i uzimate onaj isti probni prevod za kontrolu, te tako godinama obrtite isti rad nekog Vašeg starog prevodioca koga on dostavio pa ga sad izbacuje unazad i to mesecima. Imati i neku tu prostu dozu materijala svedene vrednosti, neki održivi i primenljivi (workable minimum) okvir bi sadržao obično (per locale): jednu prosto rečenu pravu rečenicu po izvornom pravilu (pure-script sentence), i još jednu sa umetnutim izrazima po bazi (embedded Latin brand names) zapadne strukture, onda bar neku s brojevima pa uz izraz za onaj devizni i ostali sistem obračuna cene te u tome svakako upotrebite i neko slovo s dijakritičkim dodatkom (diacritics or combining marks). Onim tuđim stranim (Real customer names) korisnicima koji budu ostavljali svoje i imena kompanija, tu biva to polje koje oni popune (filler text leaves untouched) obično mesto onih stalnih previda jer nećete naići nikad na baš iste situacije kakvim ste ih podesili da idu u vašim kontrolisanim planovima testiranja, i sad tu trebate dozvoliti rad da s greškama postane deo pri rješavanju budućih problema tako što svaki takav uočen model kod podrške ubacite nazad da postane stalan i redovni dodatak onim daljnjim rutinama prepoznavanja s onim stvarima što Vi prvi nikad ne biste bili (pattern you had not seen) u mogućnosti ni na pamet da znate

Registracija fontova (Font registration), ugrađivanje na njihovom osnovnom nivou (subsetting) kao i primena kod onih uobičajenih izrada kroz crteže kôda na interfejsima obrađene su u članku o izlazu izveštaja, fontovima i slikama (report output, fonts, and images) u programu HotPDF. Kada ti isti dokumenti dodatno treba da prate obavezna podešavanja na strani prilagodljivosti pristupa onda rad na polju jezičke oznake (language tagging) baš kao na svim tim ostalim stukturnim pravilima obrađen je (structure rules) unutar članka za PDF/A i PDF/UA validaciju a to zapravo dođe i dodaje samo jedan veći onaj najviši prioritet radu na oblikovanju tekstova po ovom dosadašnjem celokupnom osnovu iz prethodne strane samog opisa ovog rada (on top of the shaping work here)

Desno-na-levo preuređivanje, te i svi navedeni API iznosi za Unicode (Unicode font APIs) opcije što su prethodno navedene idu rame uz rame sa standardnom komponentom (HotPDF Component) čija primena pripada kodovima s Delphi osnovom takođe pa i onom iz C++Builder alata (Delphi and C++Builder); stranica ovog proizvoda (product page) obavezno s linkom upućuje i dalje vodi direktno u taj celokupni materijal za izradu tih i takvih vrsta ispisa