Tehnični članak

Oblikovanje arabskega in RTL besedila v datotekah PDF z Delphi s HotPDF

Posredujte arabsko besedno zvezo يوضح ملف PDF metodi TextOut in odprite rezultat. Črke tečejo v napačno smer, vsaka pa sedi v svoji izolirani obliki z vidno vrzeljo pred naslednjo, kot bi nekdo tipkal angleščino nazaj in pritisnil presledek med vsakim znakom. Nobena izjema (exception) se ni sprožila. Nobeno opozorilo ni bilo natisnjeno. Izhod je preprosto napačen in je napačen, ker se dve ločeni transformaciji, od katerih je odvisna arabščina, nista nikoli zgodili. Vedeti, kateri dve transformaciji to sta in kateri klic ju izvede, je večina tistega, na kar se skrči PDF izpis kompleksnih pisav (complex-script PDF output)

HotPDF je nativna VCL PDF komponenta za Delphi in C++Builder in za vas opravi delo od desne proti levi (right-to-left) prek ločenega klica. Prav tako se ustavi na nekaj določenih mestih, za katera želite vedeti, preden se zavežete določenemu lokalnemu okolju (locale), zato ta prispevek preslikava koncepte in iskrene meje; praktična nastavitev za sam klic živi v referenčnem članku RtLTextOut

Zakaj se pravilen niz še vedno natisne napačno

Unicode ohranja besedilo v logičnem vrstnem redu, v vrstnem redu, kot ga vtipkate in preberete naglas. Izrisovalnik (renderer) mora postaviti glife v vizualnem vrstnem redu. Pri pisavah od leve proti desni se ti vrstni redi ujemajo in o tem nihče ne razmišlja. Pri arabščini in hebrejščini pa se ne, in ko posamezna vrstica meša smeri, na primer arabski stavek, ki vsebuje latinični žeton "PDF", ali cena, napisana v številkah, algoritem Unicode Bidirectional Algorithm (UAX #9) natančno odloči, kako se fragmenti od leve proti desni gnezdijo znotraj vrstice od desne proti levi. To je prva transformacija, preurejanje (reordering), in njeno preskakovanje je tisto, kar vrstico obrne

Druga je kontekstualno oblikovanje (contextual shaping). Arabska črka je narisana različno, odvisno od tega, kam pade v besedi: na začetku, v sredini, na koncu ali stoji sama. Kodna točka (codepoint) ostane ves čas ista; spremeni se samo glif (glyph). Cevovod (pipeline), ki preda vsako kodno točko neposredno njenemu privzetemu glifu, proizvede natanko nepovezan izpis v izolirani obliki iz uvodnega odstavka. Hebrejščina ta korak preskoči, saj se njene črke ne združujejo (join), vseeno pa potrebuje preurejanje (reordering). Arabščina potrebuje oboje in zato je arabščina tisti niz, s katerim testirate, in ne hebrejščina

Na namizju (desktop) nič od tega ni vaš problem. Ko obrazec VCL izriše (paints) arabščino v TEdit, tekstovni sklad operacijskega sistema to tiho preuredi in oblikuje, in to je natanko razlog, zakaj je niz, ki na zaslonu izgleda popolno, v naivnem PDF-ju pokvarjen. Tok vsebine (content stream) ne shranjuje besedila, ki bi ga bilo mogoče urejati. Shranjuje pozicionirane glife, zato kdor koli oddaja tok, podeduje nalogo oblikovanja (shaping job), ki jo je včasih upravljal operacijski sistem (OS). RtLTextOut je klic, ki to delo vzame nazaj

Kaj RtLTextOut oblikuje za vas

HotPDF ohranja latinsko pot in pot kompleksnih pisav (complex-script) kot dve različni metodi. TextOut natisne, kar mu podate, v vrstnem redu, v katerem podate. RtLTextOut najprej izvede obe transformaciji – dvosmerno (bidirectional) preurejanje prek celotne vrstice, kontekstualno analizo za povezovalne pisave (joining scripts) – in nato natisne. Pravila katere pisave veljajo, potujejo prek nabora znakov pisave (font's charset) in ne prek samega klica, zato je smer eksplicitna izbira na vsakem mestu klica (call site) namesto ugibanja, narejenega iz znakov. Nastavitev parameter-za-parametrom, vrednosti charset, koraki za registracijo pisav in celoten zbirni primer so vsi v referenčnem članku RtLTextOut; ta prispevek ostaja pri tem, kaj transformacije pomenijo, kje se ustavijo in kako dokazati, da so delovale

Eno pravilo uporabe pa je pomembno tudi na tej višini: vnos mora biti v logičnem vrstnem redu, ker RtLTextOut sam izvede obrat, in niz, ki ste ga že ročno obrnili, pride ven dvojno obrnjen – referenčni članek vas popelje skozi to past in njeno čiščenje. Kar tej pasti prinaša omembo tukaj, je to, zakaj preživi testiranje. Dvojno obrnjen čisto arabski niz (pure-Arabic string) lahko izgleda popolnoma pravilno in razpade šele, ko vrstica nosi latinsko besedo ali številko, ker ti vdelani poteki (embedded runs) niso več gnezdeni tako, kot narekuje UAX #9. Hrošč (bug) ni v izrisovanju (rendering); hrošč je v dovajanju algoritmu besedilo, ki je že bilo napol obdelano

Isto vedenje mešanih smeri spotika pregledovalce (reviewers) bolj kot kodo. Znotraj vrstice od desne proti levi se številke in vdelane latinske besede še vedno berejo od leve proti desni. Nekdo, ki še ni delal z dvosmerno postavitvijo (bidirectional layout), bo pogledal upodobljen račun, videl, da se številka računa bere na "napačen" način glede na arabščino okoli njega, in to zapisal kot hrošč (bug). To je popolnoma v skladu s specifikacijo (spec-correct result). Kratka opomba v vaših kriterijih sprejemljivosti (acceptance criteria), napisana pred prvim prehodom z naravnim govorcem (native-speaker pass), prihrani to povratno potovanje

Kdaj sta preurejanje in združevanje dovolj in kdaj ne

Za arabsko in hebrejsko tekoče besedilo (running text) – poročila, računi, pogodbe, pisma – sta preurejanje (reordering) plus kontekstualno združevanje (contextual joining) celotno delo in RtLTextOut to nosi sam. Meja se pojavi, ko tipografija zahteva več kot le združevanje (joining). Odgovor HotPDF na arabski strani je izbirni oblikovalec na strani producenta (opt-in producer-side shaper): nastavite AutoShapeArabic := True in komponenta pred dvosmernim prehodom na novo prepiše logični vrstni red (logical-order run) v Unicode Presentation Forms, tako da so združevalne oblike (joining forms) izračunane glede na logične sosede in preklopi ligatur (ligature folds) so zapečeni (baked) v kodne točke (codepoints), ki jih PDF dejansko nosi, in niso prepuščeni pregledovalniku, da jih reši. Stikalo je privzeto izklopljeno in izhod je bajtovno stabilen, ko ostane izklopljen, zato je vklop namerna odločitev na cevovod dokumentov (document pipeline), ne pa globalna nadgradnja. Isti opt-in model se razširi na druge povezovalne pisave od desne proti levi, ki jih oblikuje HotPDF: Sirščina (Syriac), N'Ko, Adlam in Hanifi Rohingya, ki imajo vsak svojo zastavico auto-shape (auto-shape flag), ki zrcali arabsko

Izbirne funkcije OpenType so znova drugačen mehanizem. Izbirne ligature (discretionary ligatures) in podobne funkcije z enojno zamenjavo gredo skozi GetSingleSubstituteGlyph(GID, 'liga'), ki razreši po eno zamenjavo hkrati – najprej vhodni ID glifa (input glyph ID), drugič oznako funkcije (feature tag) – in vrne vhodni glif nespremenjen, ko funkcija ni uporabna. To je dovolj za vožnjo znanega, končnega seznama ligatur, ki ga vzdržujete sami. To ni poln GSUB pogon (full GSUB engine), in razlika je točno tam, kjer se ambiciozni načrti o lokacijah (locale plans) sfižijo: cevovod za oblikovanje, ki brezhibno obravnava arabščino, je dokazal preurejanje in združevanje, nič drugega

Pokritost po pisavah (Coverage across scripts)

Arabščina vadi obe transformaciji, kar je razlog, da je to niz za testiranje in zakaj je arabski prehod (Arabic pass) najmočnejši posamezen kos dokaza, da cevovod (pipeline) deluje. Hebrejščina potrebuje preurejanje (reordering), ne pa tudi združevanja (joining), saj njene črke stojijo same; če se hebrejščina izriše pravilno, arabščina pa se izpiše nepovezana, je dvosmerna polovica v redu, kontekstualna polovica pa ni bila nikoli zagnana. Perzijščina in urdujščina se vozita na arabski pisavi in podedujeta njeno obnašanje, čeprav je prednost urdujščine za stil Nastaliq odločitev o pisavi (font decision) s posledicami na čitljivost, ki naj jih presodi rojeni bralec (native reader)

Tajščina sedi povsem na drugi strani črte. Teče od leve proti desni, tako da ne potrebuje dvosmernega (bidirectional) dela, in njene črke se ne povezujejo, tako da ne potrebuje kontekstualne analize; tajski nizi gredo skozi navadno pot TextOut tako kot latinični. Kar ima tajščina, so naložena znamenja (stacked marks) – samoglasniki in tonska znamenja (tone marks) nad in pod osnovnim soglasnikom – in ali so tista nameščena pravilno, je odvisno od tega, ali font zgradi svoja sestavljena znamenja (combining marks), da se nalagajo brez pomoči pogona za oblikovanje (shaping-engine help). Večina namenskih tajskih pisav to naredi. Testirajte z točno tistim fontom, ki ga boste vdelali (embed), in ne z nečim, kar mu je samo podobno

Devanagari in preostala družina Indijskih pisav (Indic family) sta iskren trdi udarec (hard stop). Njihova znamenja za samoglasnike se preuredijo (reorder) okoli skupin soglasnikov in se njihovi konjunkti (conjuncts) tvorijo skozi verige kontekstno odvisnih zamenjav, kar je polni GSUB teritorij (full GSUB territory), onkraj preurejanja (reordering) in združevanja (joining). Če so na časovnem načrtu (roadmap) indikološka lokalna okolja (Indic locale), izvedite pravi pilotski projekt s pravimi strankinimi nizi, preden karkoli obljubite – delujoča arabščina ni dokaz, da bo deloval tudi Devanagari. CJK nizi, vietnamščina s svojimi naloženimi diakritičnimi znaki (stacked diacritics) in mešano evropsko besedilo vsi uporabijo navadno pot brez dvosmerne analize (no bidirectional analysis), in splača se imeti obe poti fizično ločeni v programski kodi (report code), ena rutina za RTL nize in druga za vse ostalo, tako da je lokalna logika vidna na mestu klica (call site), ne pa skrita za zastavico (flag), na katero nekdo pozabi

Pokritost z glifi (Glyph coverage) je odločena preden se oblikovanje sploh zažene

Oblikovanje izbere glife (glyphs) iz pisave (font). Če jih pisava ne nosi, ni kaj izbrati, kar je razlog, da klasična napaka pri namestitvi – brezhibno na računalniku razvijalca, prazni kvadratki (blank boxes) na strankinem strežniku po tihi zamenjavi pisave (silent font substitution) – predstavlja problem pokritosti (coverage problem) in ne problema oblikovanja. Praktično zdravilo, ki vključuje registracijo pisave, ki jo pošiljate vi, namesto da bi zaupali temu, kar je morda nameščeno na računalniku, je korak za korakom pojasnjeno v referenčnem članku. Konceptualno bistvo pa je, da je potrebno pokritost preveriti, še preden vprašanje o oblikovanju (shaping) postane smiselno, ter da to lahko preverite programsko in ne le z ocenjevanjem izpisa z očesom (eyeballing 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 nosi dve omejitvi – osnovno raven PDF 1.5 za vdelano upravljanje znakov (embedded Unicode handling) in bite, ki določajo pravice do vdelave pisave (font's embedding-permission bits) – obe sta vključeni skupaj s koraki nastavitve v referenčnem članku RtLTextOut. Tisto, kar spada sem, je navada revidiranja (audit habit): GetUnicodeGlyphForCodepoint je vaš sistem za zgodnje opozarjanje. Ko se storitev zažene, se sprehodite skozi razpone kodnih točk (codepoint ranges), ki jih vaši podatki dejansko uporabljajo, in zabeležite (log), kateri ID-ji glifov se vrnejo. Reža v pokritosti (coverage gap) se bo tako med uvedbo (rollout) pojavila kot vrstica v dnevniku zagonov (startup log) in ne kot manjkajoči znaki (missing characters) na računu, ki ga je stranka že prejela

Vrstni red branja (Reading order) pripada dokumentu, ne glifom

Če poskrbite, da je vsak glif ustrezen, ena stvar še vedno ostane nedokončana. ISO 32000-1 §12.2 določa preferenco pregledovalnika z imenom /Direction, ki izjavlja o splošnem bralnem vrstnem redu (overall reading order) dokumenta. Ne dotika se nobenih glifov. Tisto, kar stori, je to, da pregledovalniku pove, kako naj razporedi prikaze dveh strani naenkrat (two-up spreads), s katere strani naj začne postavitev strani z nasprotnimi stranmi (facing-page layout) in na katero stran naj se nagne bralni uporabniški vmesnik (reading UI). Nič od tega ni vidno na eni sami strani, kar je natanko razlog, da se na to pozabi

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

Nastavitev parametra Direction pomeni celotno delo: lastnost (property setter) doda vpDirection v ViewerPreferences dokumenta, tako da ena vrstica prenese preferenco v datoteko. Če gre besedilo ven s pomočjo RtLTextOut, to dobite brezplačno, saj klic kot stranski učinek obrne smeri dokumenta – referenčni članek pojasnjuje situacije, ko mora biti to vrnjeno nazaj pri mešanih dokumentih (mixed document). Primer, kjer ga morate nastaviti sami, je dokument od desne proti levi, ki je nastal s kakršnim koli drugim načinom, na primer iz vhodnih podatkov (input), ki ste jih oblikovali (pre-shaped upstream) in zarisali s pomočjo navadne poti. Izpustite to vrstico, in tisti osnutek na eni strani (single-page proof), v katerega gledate, bo na prvi pogled deloval enako v obeh primerih; potem pa nekdo natisne dvostransko knjižico (duplex booklet), prikazi obeh strani se prikažejo kot v ogledalu (spreads come out mirrored) in vzrok je ena manjkajoča enovrstična koda, na katero ste pozabili nekaj tednov prej

Preverjanje oblikovanega izpisa (Verifying shaped output)

Preverite celoten proces (end to end), saj lahko stran izgleda pravilno in je kljub temu neuporabna za vse korake naprej (downstream). Tri preverjanja odkrijejo večino težav. Kopirajte besedilo (copy the text back out) iz Acrobata in primerjajte kodne točke (codepoints) z izvornim nizom (source string). Uporabite brskalnikovo orodje za iskanje v dokumentu in poiščite besedo, ki jo vidite na strani. In nazadnje, odprite izpis (output) na računalniku, ki nima nameščenih vaših razvojnih pisav, na tistem, ki bo z največjo verjetnostjo razkril napačno zamenjavo (substitution). Nič od tega ne nadomesti naravnega govorca (native reader), ki pregleda pravi dokument (real document), s tem pa lahko ujame napake, ki jih sintetični korpus (synthetic corpus) ne bo. Prepričajte se, da boste imeli to preverjanje na urniku še preden izdate nov format

Nalašč in načrtno izberite preizkusne nize (test strings) in ne reciklirajte tistega, kar vam je prevajalec poslal lansko leto. Sprejemljiv minimum (workable minimum) na posamezno lokalno okolje (locale) vključuje: stavek v čisti pisavi (pure-script sentence), stavek z vključenimi (embedded) latiničnimi blagovnimi znamkami (brand names), vrstico, ki nosi številke in vrednost valute (currency), ter imena, ki vsebujejo diakritične znake ali kombinirana znamenja (combining marks). Prava (resnična) imena strank pokažejo napačne predpostavke (break assumptions), na katere polnilno besedilo (filler text) nima vpliva, zato naj niz za regresijsko testiranje (regression set) zraste za en string vsakič, ko primer za podporo odkrije vzorec, ki ga prej niste videli

Registracija pisave (font registration), podmnoženje (subsetting) in vsakodnevni vmesnik API za izrisovanje besedila so pojasnjeni v članku o rezultatih poročil, pisavah in slikah, z orodjem HotPDF. Ko pa morajo ti dokumenti zadostiti tudi merilom profilov dostopnosti, so oznake jezikov (language tagging) in pravila strukture v članku PDF/A in potrjevanje PDF/UA nameščena na vrh zgornjega dela za oblikovanje (shaping work)

Zgoraj predstavljeni programski vmesniki (API-ji) s kodiranjem od desne proti levi (right-to-left) in Unicodnimi pisavami so vključeni in so dostavljeni s HotPDF Component za okolji Delphi in C++Builder; na spletni strani pa je povezava do referenc celotnega izrisa za besedilo