Trimite expresia arabă يوضح ملف PDF către TextOut și deschide rezultatul. Literele rulează în direcția greșită, și fiecare stă în forma sa izolată cu un spațiu vizibil înainte de următoarea, ca și cum cineva ar fi tastat engleza invers și ar fi apăsat spațiu între fiecare caracter. Nicio excepție nu a fost declanșată. Niciun avertisment nu a fost tipărit. Rezultatul este pur și simplu greșit, și este greșit pentru că două transformări separate de care depinde limba arabă nu au avut loc niciodată. A ști care sunt aceste două transformări, și ce apel le execută, reprezintă cea mai mare parte a procesului de generare a PDF-urilor cu scriere complexă
HotPDF este o componentă PDF VCL nativă pentru Delphi și C++Builder, și face treaba de la dreapta la stânga pentru tine printr-un apel distinct. De asemenea, se oprește în câteva locuri specifice despre care vrei să știi înainte să te angajezi la un anumit locale, așa că acest articol cartografiază conceptele și limitele reale; configurarea practică pentru apelul în sine se găsește în articolul de referință RtLTextOut
De ce un șir corect încă se tipărește greșit
Unicode păstrează textul în ordine logică, ordinea în care îl tastezi și îl citești cu voce tare. Un renderer trebuie să plaseze glifele în ordine vizuală. Pentru scrierile de la stânga la dreapta, aceste ordine coincid și nimeni nu se gândește la asta. Pentru arabă și ebraică nu, și când un singur rând amestecă direcțiile, să zicem o propoziție în arabă purtând indicativul latin "PDF" sau un preț scris în cifre, Algoritmul Bidirecțional Unicode (UAX #9) decide exact cum fragmentele de la stânga la dreapta se cuibăresc în rândul de la dreapta la stânga. Aceasta este prima transformare, reordonarea, și dacă o sari, rândul se va răsturna
A doua este formatarea contextuală. O literă arabă este desenată diferit în funcție de locul unde cade într-un cuvânt: inițial, median, final, sau stând singură. Codul de caracter rămâne același pe tot parcursul; doar glifa se schimbă. O conductă care înmânează fiecare cod de caracter direct la glifa sa implicită produce exact rezultatul deconectat, în formă izolată din paragraful de deschidere. Ebraica sare acest pas, deoarece literele sale nu se unesc, dar tot are nevoie de reordonare. Araba are nevoie de ambele, și de aceea araba, nu ebraica, este șirul cu care testezi
Pe desktop, nimic din toate astea nu e problema ta. Când un formular VCL desenează limba arabă într-un TEdit, stiva de text a sistemului de operare reordonează și formatează în liniște, ceea ce este exact motivul pentru care șirul care arată perfect pe ecran iese stricat într-un PDF naiv. Un flux de conținut nu stochează text editabil. Stochează glife poziționate, așa că oricine emite fluxul moștenește sarcina de formatare de care sistemul de operare obișnuia să se ocupe. RtLTextOut este apelul care preia acea sarcină înapoi
Ce formatează RtLTextOut pentru tine
HotPDF păstrează calea latină și calea de scriere complexă ca două metode diferite. TextOut tipărește ce îi dai în ordinea în care îi dai. RtLTextOut execută ambele transformări mai întâi — reordonare bidirecțională de-a lungul întregului rând, analiză contextuală pentru scrierile cu caractere unite — și apoi tipărește. Care reguli de scriere se aplică circulă prin setul de caractere al fontului (charset) mai degrabă decât prin apelul în sine, așa că direcția este o alegere explicită la fiecare loc de apel în loc de o ghicire făcută din caractere. Configurarea parametru-cu-parametru, valorile charset, pașii de înregistrare a fontului, și un exemplu complet compilabil sunt toate în articolul de referință RtLTextOut; acest articol rămâne la ce înseamnă transformările, unde se opresc ele, și cum să demonstrezi că au funcționat
O regulă de utilizare contează chiar și la acest nivel: intrarea trebuie să fie în ordine logică, pentru că RtLTextOut efectuează inversarea el însuși, iar un șir pe care l-ai inversat deja manual iese dublu-inversat — articolul de referință trece prin acea capcană și prin curățarea ei. Ce aduce capcanei o mențiune aici este motivul pentru care supraviețuiește testării. Un șir pur arab dublu-inversat poate părea perfect corect, și se destramă doar când un rând poartă un cuvânt latin sau un număr, pentru că acele fluxuri încorporate nu se mai cuibăresc în modul în care dictează UAX #9. Problema (bug-ul) nu este în randare; este în a hrăni algoritmul cu un text care a fost deja parțial procesat
Același comportament de direcție mixtă încurcă recenzorii mai mult decât încurcă codul. În interiorul unui rând de la dreapta la stânga, cifrele și cuvintele latine încorporate tot se citesc de la stânga la dreapta. Cineva care nu a lucrat cu layout bidirecțional se va uita la o factură randată, va vedea numărul contului citindu-se în direcția "greșită" față de araba din jurul său, și va nota asta ca un defect (bug). Este rezultatul corect conform specificației. O notă scurtă în criteriile tale de acceptare, scrisă înainte de prima trecere a unui vorbitor nativ, salvează acea călătorie dus-întors
Când reordonarea și unirea sunt suficiente, și când nu sunt
Pentru textul curent în arabă și ebraică — rapoarte, facturi, contracte, scrisori — reordonarea plus unirea contextuală este toată treaba, iar RtLTextOut o duce singur. Limita apare atunci când tipografia cere mai mult decât unirea. Răspunsul HotPDF pe partea arabă este un formator opțional de partea producătorului: setează AutoShapeArabic := True și componenta rescrie secvența în ordine logică în Unicode Presentation Forms înainte de trecerea bidirecțională, astfel încât formele de unire sunt calculate în raport cu vecinii logici și pliurile de ligatură sunt fixate în codurile de caractere pe care PDF-ul le poartă de fapt, în loc să fie lăsate pentru un vizualizator să le rezolve. Comutatorul este implicit oprit și rezultatul este stabil la nivel de octeți (byte) atunci când rămâne oprit, așa că activarea sa este o decizie deliberată pe conductă (pipeline) de document, nu o actualizare globală. Același model opțional se extinde la celelalte scrieri de la dreapta la stânga care unesc caracterele și pe care HotPDF le formatează: Siriacă, N'Ko, Adlam, și Hanifi Rohingya, fiecare are propriul flag de auto-formatare (auto-shape) care îl reflectă pe cel arab
Caracteristicile opționale OpenType sunt un mecanism complet diferit. Ligaturile discreționare și caracteristicile similare de substituție singulară trec prin GetSingleSubstituteGlyph(GID, 'liga'), care rezolvă o substituție pe rând — ID-ul glifei de intrare primul, eticheta (tag) caracteristicii al doilea — și returnează glifa de intrare neschimbată atunci când caracteristica nu se aplică. Asta e de ajuns pentru a conduce o listă cunoscută, finită, de ligaturi pe care o menții tu însuți. Nu este un motor GSUB complet, iar diferența este exact locul în care planurile ambițioase de localizare (locale) merg prost: o conductă (pipeline) de formatare care gestionează araba impecabil a demonstrat reordonarea și unirea, nimic mai mult
Acoperire pe parcursul scrierilor
Araba exersează ambele transformări, motiv pentru care este șirul cu care se testează, și de ce un test (pass) în arabă este cea mai puternică dovadă singulară că conducta (pipeline) funcționează. Ebraica are nevoie de reordonare, dar nu de unire, din moment ce literele sale stau singure; dacă ebraica se randează corect, dar araba iese deconectată, jumătatea bidirecțională este în regulă, dar jumătatea contextuală nu a rulat niciodată. Persana și urdu rulează pe scrierea arabă și îi moștenesc comportamentul, deși preferința urdu pentru stilul Nastaliq este o decizie a fontului cu consecințe de lizibilitate pe care un cititor nativ ar trebui să le judece
Thailandeza stă pe cealaltă parte a liniei în întregime. Rulează de la stânga la dreapta, deci nu are nevoie de nicio muncă bidirecțională, iar literele sale nu se unesc, deci nu are nevoie de nicio analiză contextuală; șirurile thailandeze trec prin calea obișnuită TextOut ca și latina. Ceea ce are thailandeza sunt mărci suprapuse — vocale și mărci tonale deasupra și dedesubtul consoanei de bază — și dacă acelea se așează corect depinde de fontul care își construiește mărcile combinatoare pentru a le suprapune fără ajutorul unui motor de formatare. Cele mai multe fonturi thailandeze dedicate o fac. Testează cu fontul exact pe care îl vei încorpora, nu cu o copie vizuală a sa (lookalike)
Devanagari și restul familiei indiene reprezintă bariera reală (hard stop). Semnele lor vocale se reordonează în jurul clusterelor de consoane și conjuncturile lor se formează prin lanțuri de substituții dependente de context, care este teritoriu GSUB complet, dincolo de reordonare și unire. Dacă un locale indic este pe harta (roadmap) ta, rulează un pilot real pe șiruri autentice de clienți înainte de a promite — faptul că araba funcționează nu este o dovadă că devanagari o va face. Șirurile CJK, vietnameza cu diacriticele sale stivuite și textul european mixt iau toate calea obișnuită, fără analiză bidirecțională, și merită să păstrezi cele două căi separate fizic în codul de raport, o rutină pentru rulările RTL și una pentru tot restul, astfel încât logica pentru locale să fie vizibilă la locul apelului în loc să fie ascunsă în spatele unui flag pe care cineva uită să-l seteze
Acoperirea glifelor se decide chiar înainte ca formatarea să ruleze
Formatarea alege glifele dintr-un font. Dacă fontul nu le are, nu e nimic de ales, motiv pentru care eșecul clasic de implementare (deployment) — impecabil pe mașina dezvoltatorului, cutii goale pe serverul clientului după o înlocuire silențioasă de font — este o problemă de acoperire, nu o problemă de formatare. Tratamentul practic, înregistrarea unui font pe care îl livrezi în loc de a te încrede în ce are instalat o mașină, e prezentat pas cu pas în articolul de referință. Punctul conceptual este că acoperirea trebuie stabilită înainte ca orice chestiune de formatare să fie chiar relevantă, și că ea poate fi stabilită programatic în loc de a estima vizual (eyeballing) ieșirea (output)
// După RegisterUnicodeTTF, auditează acoperirea pentru
// codurile de caractere pe care datele tale le folosesc de fapt
GID := Pdf.GetUnicodeGlyphForCodepoint($0628); // U+0628 LITERA ARABĂ BEH
LogGlyphAudit($0628, GID);
Înregistrarea în sine poartă două constrângeri — un prag minim PDF 1.5 pentru manipularea caracterelor Unicode încorporate și biturile de permisiune de încorporare ale fontului — ambele acoperite împreună cu pașii de configurare în articolul de referință RtLTextOut. Ce-și are locul aici este obiceiul de a audita: GetUnicodeGlyphForCodepoint este sistemul tău de avertizare timpurie (early-warning). Parcurge seriile de coduri de caractere pe care datele tale le folosesc de fapt când serviciul pornește și scrie în jurnal (log) ce ID-uri de glife se întorc. Un decalaj de acoperire apare apoi ca o linie într-un startup log în timpul lansării (rollout), degrabă decât ca niște caractere lipsă într-o factură care a ajuns deja la un client
Ordinea citirii aparține documentului, nu glifelor
Corectarea fiecărei glife lasă un singur lucru de făcut. ISO 32000-1 §12.2 definește o preferință de vizualizare numită /Direction, care specifică ordinea de citire generală a documentului. Nu atinge nicio glifă. Ceea ce face este să spună unui vizualizator (viewer) cum să aranjeze panouri de afișare alăturate, din ce parte să înceapă un layout cu pagini față în față (facing-page), și în ce parte ar trebui să se încline interfața (UI) de citire. Nimic din toate astea nu se vede pe o singură pagină, de aceea se uită de această setare
// Declară ordinea de citire de la dreapta la stânga la nivelul documentului
Pdf.Direction := RightToLeft; // adaugă vpDirection la ViewerPreferences
Setarea Direction este toată treaba: proprietatea (property setter) adaugă vpDirection la opțiunile ViewerPreferences ale documentului, așa că o linie poartă preferința în fișier. Dacă textul iese prin RtLTextOut obții asta gratuit, deoarece apelul inversează direcția documentului ca efect secundar — articolul de referință tratează și cazurile când un document mixt necesită anularea acestei decizii. Cazul în care trebuie să-l setezi singur este un document de la dreapta la stânga produs în orice alt mod, de exemplu din intrarea (input) pe care ai pre-formatat-o mai sus (upstream) pe traseu și ai desenat-o prin calea obișnuită (ordinary path). Lasă-l deoparte și exemplarul test (proof) cu o singură pagină la care te uiți arată identic oricum; apoi cineva tipărește o broșură pe două fețe (duplex), seturile (spreads) ies invers (mirrored), iar cauza e o linie lipsă de acum câteva săptămâni
Verificarea output-ului formatat (shaped)
Verifică tot (end to end), pentru că o pagină poate să arate corect, și totuși, să fie nefolositoare pentru tot ce e mai jos (downstream). Trei verificări găsesc cele mai multe probleme. Copiază textul înapoi din Acrobat și compară codurile de caractere față de șirul sursă. Rulează căutarea vizualizatorului (viewer) în interiorul documentului pentru un cuvânt pe care-l poți vedea pe pagină. Și deschide documentul final pe un computer care nu are fonturile tale de dezvoltare, pentru a expune cea mai probabilă substituție de fonturi. Nimic din astea nu înlocuiește o persoană nativă care privește un document real, care observă lucruri pe care niciun fel de corp de testare sintetic nu o va face. Pune acest review în calendar, chiar înainte să expediezi (ship) formatul final
Alege șiruri de test (test strings) cu o destinație precisă (on purpose), în loc de a recicla orice ți-a dat vreun traducător, anul trecut. Minimul, pentru fiecare limbaj (locale): o propoziție dintr-o scriere pură (pure-script), o propoziție având un brand name latin (latin brand name) înglobat, o linie cu cifre și monedă și o listă de nume cu diacritice, sau accente amestecate (combining marks). Numele clienților reali (customer names) sparg supozițiile, neafectate de umplutură, de aceea e mai bine să adaugi un rând la setul de regresie, cu fiecare sesizare la suport (support case), ce-ți oferă un tipar (pattern) nemaivăzut
Fonturile încorporate (registration), setul de selecție parțială (subsetting) și modul de scriere curentă în pagină (everyday text-drawing API) se explică amănunțit în articolul privind raporturile, caracterele (fonts) și imaginile cu HotPDF. Atunci când acele documente trebuie, de asemenea, să respecte un profil de accesibilitate, regulile referitoare la indicatoare de limbă (language tagging) și la felul în care sunt structurate în articolul cu validarea PDF/A și PDF/UA stau așezate peste munca de formare (shaping work) ce a fost prezentată, acum, aici
API-urile cu texte (fonts) aliniate la dreapta la stânga sau Unicode (right-to-left and Unicode font APIs), ilustrate anterior, apar livrate împreună cu Componenta HotPDF pentru mediile Delphi sau C++Builder; pe site-ul produsului se oferă un link plin cu referințe despre output-ul bazat pe texte