Uniscribe radi više posla nego što većina pozivalaca shvata. ScriptItemize izvodi dvosmjernu analizu i segmentaciju pisma u jednom prolazu, i ScriptLayout proizvodi vizuelni redosled rezultujućih nizova. HarfBuzz, prenosiva zamena za koju se ljudi okreću, ne radi ni jedno: oblikuje jedan niz čije su smer i pismo već odlučeni od nekog drugog. Pa teški deo nošenja Windows PDF tekst pipeline-a na Linux ili macOS nije vezivanje motora oblikovanja. To je snabdevanje dvosmjernim algoritmom koji je Uniscribe tiho obezbeđivao, i u PDFium komponenti to je ono čemu služi FPdfBidi
Jedinica implementira UAX #9 direktno: pravila P2 i P3 za smer pasusa, X1 do X10 za eksplicitna ugrađivanja i izolate, W1 do W7 za slabe tipove, N0 do N2 za neutrale i zagrade, I1 i I2 za implicitne nivoe, i L1 i L2 za konačno preuređivanje. Dve funkcije nose to: PdfResolveBidiLevels vraća jedan nivo ugrađivanja po UTF-16 kodnoj jedinici, i PdfBidiVisualOrder pretvara te nivoe u permutaciju koja postavlja kodne jedinice sleva-udesno
Šta algoritam vam daje, a što ne
Daje vam brojeve. Parani nivoi su sleva-udesno, neparni sdesna-ulevo, i nivo svakog znaka koduje ugnežđavanje dvosmjernih nizova u koje znak sedi. Iz tih brojeva L2 izvodi permutaciju. Ono što algoritam namerno ne radi jeste odlučiti koji font koristiti, oblikovati ligature, ili preurediti glifove unutar klastera; to su brige oblikovanja i pripadaju etapi posle ove
uses
FPdfBidi;
var
Levels: TPdfBidiLevels;
Order: TPdfBidiOrder;
ParagraphLevel: Byte;
Text, Visual: WideString;
I: Integer;
begin
Text := SourceLine;
// pbdAuto primenjuje P2-P3: prvi jak znak odlučuje
if PdfResolveBidiLevels(Text, pbdAuto, Levels, ParagraphLevel) then
begin
Order := PdfBidiVisualOrder(Text, Levels);
SetLength(Visual, Length(Order));
for I := 0 to High(Order) do
Visual[I + 1] := Text[Order[I] + 1];
// Visual sada čita sleva-udesno; Levels[] i dalje kaže koji
// su nizovi RTL pa oblikovaču mogu biti predati ispravni smerovi
end;
end;
Tabela klasa znakova generisana je, ne pisana
Svaka kodna tačka ima svojstvo Bidi_Class, i algoritam ga stalno konsultuje, pa je tabela temelj na kojem sve ostalo stoji. Generisana je iz Unicode Character Database umesto održavana rukom: peto polje UnicodeData.txt daje dodeljene klase, i deklaracije @missing u DerivedBidiClass.txt daju podrazumevane za kodne tačke koje baza ne dodeljuje, što je način na koji nedodeljeni blokovi ispravno podrazumevaju R, AL, ET ili BN umesto L
Trik kompresije je emitovati samo opsege čija klasa nije L. Sve što pada van svakog opsega jeste L, što je i Unicode podrazumevano i klasa pretežne većine kodnih tačaka. To uzima tabelu koja bi inače išla na hiljade unosa na 745 opsega i oko 6.7 KB. Operativna posledica vredi izjaviti: kada pređete na novu Unicode verziju, ponovo pokrenite generator. Ručno uređivanje include datoteke radiće, i takođe će se tiho udaljiti od baze pri sledećoj nadogradnji
L2 mora preurediti kodne tačke, ne UTF-16 kodne jedinice
Ovo je greška koja proizvodi istinski oštećen izlaz, i prva implementacija ju je učinila. L2 kaže obrnuti kontinuirane nizove na svakom nivou od najvišeg nadole do najnižeg neparnog nivoa. Napisano protiv UTF-16 niza znakova, "obrni niz" prirodno znači obrnuti kodne jedinice u njemu. Za znakove u Osnovnoj višejezičnoj ravni to je u redu. Za RTL znak u astralnoj ravnini, poput onih u Ciparskim ili Starojuznoarapskim blokovima blizu U+10800, nije: znak je surrogatni par, obrtanje niza stavlja niski surrogat pre visokog, i niz znakova sada sadrži dva nesparenja surrogata umesto jednog znaka. Ništa nizvodno ne može to oporaviti
Popravka je raditi L2 na jedinicama kodnih tačaka. Implementacija spaja kodne jedinice u jedinice kodnih tačaka, izvršava obrtanja na tim jedinicama, i širi rezultat nazad u indekse kodnih jedinica na kraju. Zato PdfBidiVisualOrder prima tekst, a ne samo niz nivoa: ne može reći gde su granice surrogata samo iz nivoa. Ista disciplina surrogatnih parova prolazi kroz tekstualne API-je opšte, kao što je opisano u članku o emotikonima, CJK i surrogatnim parovima
Silazak kroz nivoe mora uključiti nivoe koji se ne javljaju
Druga greška suptilnija je i ne proizvodi pad, samo tekst koji nije preuređen. L2 kaže početi od najvišeg prisutnog nivoa i raditi nadole do najnižeg neparnog nivoa. Prirodna optimizacija je sakupiti skup nivoa koji stvarno postoje i iterirati preko tog skupa. Pogrešna je
Uzmite liniju latiničnog teksta unutar ugrađivanja sdesna-ulevo. Nivo pasusa je 0, ugrađivanje gura latinične znakove na nivo 2, i nijedan znak ne sedi na nivou 1. Iteriranje preko postojećih nivoa nalazi samo 0 i 2, i nema neparnog nivoa uopšte, pa petlja ne izvršava obrtanje. Taj odgovor ispravan je, ali iz razloga koji optimizacija ne zna: obrtanje na nivou 2 praćeno obrtanjem na nivou 1 poništilo bi se tačno, pa izvršenje nijednog ispravan je ishod. Promenite ulaz malo, tako da i nivo 1 i nivo 3 znakovi postoje ali nivo 2 ne, i petlja zasnovana na skupu preskače obrtanje nivoa 2 koje algoritam traži
// Ispravno: hodajte svaki nivo od maksimuma nadole do najnižeg
// neparnog nivoa, uključujući nivoe koje nijedan znak zapravo nema
Level := MaxLevel;
while Level >= LowestOddLevel do
begin
ReverseRunsAtOrAbove(Level); // bez dejstva kada nijedan niz ne ispunjava uslov
Dec(Level);
end;
Napisano kao obična petlja umanjivanja ponašanje ispada besplatno, i iteracije bez dejstva ne koštaju ništa merljivo. Ovo je slučaj gde očigledna optimizacija nije malo pogrešna, pogrešna je na način zavisnom od ulaza koji mali test korpus nikad neće otkriti
Zagrade: BD16 sa pragmatičnom tabelom
Pravilo N0 i BD16 algoritam parova zagrada postoje da zagrada u tekstu mešanog smera razreši se u smer onoga što obuhvata umesto u šta god se slučajno nalazi pored. To traži tabelu parova zagrada. Implementacija nosi parove u opštoj upotrebi umesto punog sadržaja Unicode datoteke zagrada: ASCII, CJK, punu-širinu, matematičke i ukrasne zagrade
Nenavedena zagrada nije greška. Razrešava se kao običan neutral kroz N1 i N2, što je tačno ponašanje koje je svaka implementacija imala pre nego što je Unicode 6.3 uneo N0. Pa je granica "manje profinjeno za retke zagrade", ne "neispravno". Jedan detalj traži eksplicitno rukovanje: kanonička ekvivalencija između ugaonih zagrada na U+2329 i U+232A i onih na U+3008 i U+3009 mora se saviti pri poklapanju parova, ili otvorena zagrada napisana jednim načinom otkazaće uparivanje sa zatvorenom napisanom drugim
Kako testirati trideset međusobno delujućih pravila
Ne velikim korpusom, barem ne prvo. Plodan pristup bilo je šesnaest ručno verifikovanih slučajeva, svaki izabran da vežba specifično pravilo i svaki proveren protiv nivoa koje UAX #9 kaže da bi trebalo proizvesti: detekcija smera pasusa pod P2 i P3, pravila slabog tipa W2, W3 i W7, pravila implicitnog nivoa I1 i I2, eksplicitno ugrađivanje putem X2 i X7, izolate putem X5a i X6a, L1 resetovanje pratećih razmaka i razdelnika, N0 slučaj zagrade, i jedan slučaj sa astralnim znakom da se prikova rukovanje surrogatima
Šesnaest slučajeva sa poznato-ispravnim očekivanim nivoima hvata više od šesnaest stotina slučajeva sa izlazom koji deluje uverljivo, jer je režim otkazivanja dvosmerne implementacije tekst koji se čita skoro ispravno. Kada ti prođu, korpus koristan je za nalaženje jazova tabele i problema performansi, koji su različite klase defekta
Unutar PDFium komponente nivoi hrane dva potrošača. Na strani pisanja govore backend-u oblikovanja smer svakog niza, što je ulaz koji HarfBuzz traži. Na strani čitanja obaveštavaju geometriju izbora i redosled čitanja, pošto klik u RTL tekstu mora mapirati u logički položaj umesto vizuelni; to mapiranje pokriveno je u članku o izboru vizuelne linije a model redosleda čitanja u strukturiranim blokovima teksta i redosledu čitanja. Detalji podrške platformi za komponentu nalaze se na stranici proizvoda PDFium Delphi component