Uniscribe atlieka daugiau darbo, nei dauguma iškviečiųjų suvokia. ScriptItemize atlieka dvišalę analizę ir rašto sistemų segmentavimą vienu pravažiavimu, o ScriptLayout pagamina gautų atkarpų vizualinę tvarką. HarfBuzz — perkeliamasis atitikmuo, prie kurio žmonės griebiasi — nedaro nė vieno: ji formuoja vieną atkarpą, kurios kryptį ir rašto sistemą kažkas kitas jau nusprendė. Taigi sunkioji dalis perkeliant Windows PDF teksto konvejerį į Linux arba macOS nėra formavimo variklio susiejimas. Tai dvišaliojo algoritmo tiekimas, kurį Uniscribe tyliai teikė, ir PDFium komponentėje būtent tam yra FPdfBidi
Modulis įgyvendina UAX #9 tiesiogiai: taisyklės P2 ir P3 pastraipos krypčiai, X1 iki X10 aiškiems įdėjimams ir izoliatams, W1 iki W7 silpniems tipams, N0 iki N2 neutraliesiems ir skliaustams, I1 ir I2 netiesioginiams lygiams, L1 ir L2 galutiniam perkėlimui. Dvi funkcijos tai neša: PdfResolveBidiLevels grąžina vieną įdėjimo lygį kiekvienam UTF-16 kodiniam vienetui, o PdfBidiVisualOrder tuos lygius paverčia pertvarkymu, dedančiu kodinius vienetus iš kairės į dešinę
Ką algoritmas duoda, ir ko neduoda
Jis duoda jums skaičius. Lyginiai lygiai yra iš kairės į dešinę, nelyginiai — iš dešinės į kairę, ir kiekvieno simbolio lygis užkoduoja dvišalių atkarpų vidinį įdėjimą, kuriame tas simbolis sėdi. Iš tų skaičių L2 išveda pertvarkymą. Ko algoritmas sąmoningai nedaro — nesprendžia, kurį šriftą naudoti, nesudaro ligatūrų, neperkelia glifų klasterio viduje; tai formavimo rūpesčiai ir jie priklauso kitai po šios pakopai
uses
FPdfBidi;
var
Levels: TPdfBidiLevels;
Order: TPdfBidiOrder;
ParagraphLevel: Byte;
Text, Visual: WideString;
I: Integer;
begin
Text := SourceLine;
// pbdAuto taiko P2-P3: pirmasis stiprusis simbolis nusprendžia
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 dabar skaitosi iš kairės į dešinę; Levels[] vis dar sako,
// kurios atkarpos RTL, todėl formuotojui galima perduoti teisingas kryptis
end;
end;
Simbolių klasių lentelė generuojama, o ne rašoma
Kiekvienas kodinis taškas turi Bidi_Class savybę, ir algoritmas jos nuolat klauso, todėl lentelė yra pamatas, ant kurio stovi visa kita. Ji generuojama iš Unicode Character Database, o ne prižiūrima rankomis: penktasis UnicodeData.txt laukas duoda priskirtas klases, o @missing deklaracijos DerivedBidiClass.txt duoda numatytąsias kodiniams taškams, kurių duomenų bazė nepriskiria, — todėl nepaskirti blokai teisingai numato R, AL, ET arba BN, o ne L
Glaudinimo gudrybė — skleisti tik intervalus, kurių klasė nėra L. Bet kas, krentantis už visų intervalų ribų, yra L — tai ir Unicode numatytasis, ir didžiumos kodinių taškų klasė. Tai lentelę, kuri kitaip išaugtų į tūkstančius įrašų, nuleidžia iki 745 intervalų ir apie 6,7 KB. Operacinė pasekmė verta pasakyti: pereidami prie naujos Unicode versijos, iš naujo paleiskite generatorių. Rankinis include failo redagavimas veiks, ir jis taip pat tyloje nutols nuo duomenų bazės kito atnaujinimo metu
L2 privalo perkelti kodinius taškus, o ne UTF-16 kodinius vienetus
Tai klaida, gaminanti tikrai sugadintą išvestį, ir pirmasis įgyvendinimas ją padarė. L2 sako apversti ištisinius atkarpas kiekviename lygyje nuo aukščiausio žemyn iki žemiausio nelyginio lygio. Parašyta pagal UTF-16 eilutę, „apversti atkarpą“ natūraliai reiškia apversti jos kodinius vienetus. Simboliams pagrindinėje daugiakalbėje plokštumoje tai gerai. RTL simboliui astralinėje plokštumoje, tokiems kaip Kipro ar Senojo Pietų Arabijos blokų ties U+10800, — ne: simbolis yra pakaitinė pora, atkarpos apvertimas padeda žemąjį pakaitą prieš aukštąjį, ir eilutė dabar turi du nesuporuotus pakaitus vietoj vieno simbolio. Niekas toliau grandinėje negali to atkurti
Pataisa — daryti L2 kodinių taškų vienetais. Įgyvendinimas susieja kodinius vienetus į kodinių taškų vienetus, atlieka apvertimus tiems vienetams ir pabaigoje išskleidžia rezultatą atgal į kodinių vienetų indeksus. Todėl PdfBidiVisualOrder priima tekstą, o ne tik lygių masyvą: ji negali pasakyti iš vien lygių, kur pakaitinės ribos. Ta pati pakaitinių porų drausmė eina per teksto APIs apskritai, kaip aprašyta emoji, CJK ir pakaitinių porų straipsnyje
Nusileidimas per lygius privalo įtraukti lygius, kurie nepasitaiko
Antroji klaida subtilesnė ir nesukuria strigties — tik teksto, kuris neperkeltas. L2 sako pradėti nuo aukščiausio esamo lygio ir eiti žemyn iki žemiausio nelyginio lygio. Natūrali optimizacija — surinkti faktiškai pasitaikančių lygių aibę ir iteruoti per ją. Tai neteisinga
Imkite lotyniško teksto eilutę dešinės-kairės įdėjimo viduje. Pastraipos lygis yra 0, įdėjimas nustumia lotynų simbolius į 2 lygį, ir nė vienas simbolis nesėdi ties 1 lygiu. Iteravimas per pasitaikančius lygius randa tik 0 ir 2, ir nėra jokio nelyginio lygio, todėl ciklas neatlieka jokio apvertimo. Tas atsakymas teisingas, bet dėl priežasties, kurios optimizacija nežino: apvertimas ties 2 lygiu, sekamas apvertimu ties 1 lygiu, atšauktųsi tiksliai, todėl nei vieno neatlikimas yra teisingas rezultatas. Pakeiskite įvestį šiek tiek, kad būtų ir 1, ir 3 lygio simboliai, bet nebūtų 2 lygio, — ir aibės paremtas ciklas praleidžia 2 lygio apvertimą, kurio reikalauja algoritmas
// Teisinga: eikite per kiekvieną lygį nuo maksimalaus žemyn iki
// žemiausio nelyginio, įskaitant lygius, kurių iš tikrųjų nėra jokiam simboliui
Level := MaxLevel;
while Level >= LowestOddLevel do
begin
ReverseRunsAtOrAbove(Level); // be veiksmo, kai neatitinka jokia atkarpa
Dec(Level);
end;
Parašyta kaip paprastas mažinamasis ciklas, elgsena atkrenta nemokamai, o be veiksmo iteracijos nieko išmatuojamai nekainuoja. Tai atvejis, kai akivaizdi optimizacija nėra šiek tiek neteisinga — ji neteisinga įvečiai priklausomu būdu, kurio mažas testų korpusas niekada neatskleis
Skliaustai: BD16 su pragmatiška lentele
N0 taisyklė ir BD16 skliaustų porų algoritmas egzistuoja tam, kad skliaustas maišytų krypčių tekste išsiskleistų į to, ką jis dengia, kryptį, o ne į tai, kas atsitiktinai greta. Tam reikia skliaustų porų lentelės. Įgyvendinimas neša bendrai naudojamas poras, o ne visą Unicode skliaustų failo turinį: ASCII, CJK, pilna pločio, matematinius ir dekoratyvinius skliaustus
Neįrašytas skliaustas nėra klaida. Jis išsiskleidžia kaip paprastasis neutralusis per N1 ir N2 — būtent tokia elgsena buvo kiekvienam įgyvendinimui iki Unicode 6.3 įvedus N0. Taigi riba yra „mažiau rafinuota retiem skliaustams“, o ne „neteisinga“. Viena detalė vis dėlto reikalauja aiškaus tvarkymo: kanoninę kampinių skliaustų ties U+2329 ir U+232A bei ties U+3008 ir U+3009 ekvivalentiškumą reikia suvienodinti gretinant poras, kitaip atveriamasis skliaustas, parašytas vienaip, nesuporuosis su užveriamuoju, parašytu kitaip
Kaip tikrinate trisdešimt sąveikaujančių taisyklių
Ne dideliu korpusu, bent jau ne pirmiausia. Vaisingas požiūris buvo šešiolika rankomis patvirtintų atvejų, kiekvienas parinktas pratinti konkrečią taisyklę ir kiekvienas tikrintas pagal lygius, kuriuos UAX #9 sako turintį duoti: pastraipos krypties aptikimas pagal P2 ir P3, silpnųjų tipų taisyklės W2, W3 ir W7, netiesioginių lygių taisyklės I1 ir I2, aiškus įdėjimas per X2 ir X7, izoliatas per X5a ir X6a, L1 atkūrimas galiniam tarpui ir skirtukams, N0 skliausto atvejis ir vienas atvejis su astraliniu simboliu pakaitiniam tvarkymui užfiksuoti
Šešiolika atvejų su žinomai teisingais tikėtinais lygiais pagauna daugiau nei šešiolika šimtų atvejų su tikėtinai atrodančia išvestimi, nes dvišalio įgyvendinimo gedimo režimas yra tekstas, skaitomas beveik teisingai. Kai tie praeina, korpusas naudingas lentelės spragoms ir našumo problemoms rasti — tai kitokios defektų klasės
PDFium komponentės viduje lygiai maitina du vartotojus. Rašymo pusėje jie sako formavimo posistemiui kiekvienos atkarpos kryptį — tai įvestis, kurios reikalauja HarfBuzz. Skaitymo pusėje jie informuoja žymės geometriją ir skaitymo tvarką, nes paspaudimas RTL tekste turi būti atvaizduotas į loginę, o ne vizualinę padėtį; tas atvaizdavimas dengiamas vizualinės eilutės žymės straipsnyje, o skaitymo tvarkos modelis — struktūruotuose teksto blokuose ir skaitymo tvarkoje. Komponentės platformų palaikymo detalės yra PDFium Delphi component produkto puslapyje