Uniscribe tekee enemmän työtä kuin useimmat kutsujat tajuaavat. ScriptItemize suorittaa kaksisuuntaisen analyysin ja skriptisegmentoinnin yhdessä ajossa, ja ScriptLayout tuottaa tuloksena syntyvien ajojen visuaalisen järjestyksen. HarfBuzz, kannettava korvike, jota ihmiset tavoittelevat, ei tee kumpaakaan: se muotoilee yhden ajon, jonka suunnan ja skriptin joku muu on jo päättänyt. Niinpä vaikea osa Windows PDF -tekstiputken viemisessä Linuxille tai macOS:lle ei ole muotoilukoneen sitominen. Kyse on kaksisuuntaisen algoritmin toimittamisesta, jota Uniscribe tarjosi hiljaa, ja PDFium-komponentissa sitä varten FPdfBidi on
Yksikkö toteuttaa UAX #9:n suoraan: säännöt P2 ja P3 kappaleen suunnalle, X1:stä X10:een eksplisiittisille upotuksille ja isolaateille, W1:stä W7:ään heikoille tyypeille, N0:sta N2:een neutraaleille ja sulkeille, I1 ja I2 implisiittisille tasoille sekä L1 ja L2 lopulliselle uudelleenjärjestelylle. Kaksi funktiota kantaa sen: PdfResolveBidiLevels palauttaa yhden upotustason per UTF-16-koodiyksikkö ja PdfBidiVisualOrder muuntaa kyseiset tasot permutaatioksi, joka sijoittaa koodiyksiköt vasemmalta oikealle
Mitä algoritmi antaa sinulle, ja mitä se ei
Se antaa sinulle lukuja. Parilliset tasot ovat vasemmalta oikealle, parittomat tasot oikealta vasemmalle, ja jokaisen merkin taso koodaa suunnallisten ajojen sisäkkäisyyden, jonka sisällä merkki istuu. Kyseisistä luvuista L2 johtaa permutaation. Se, mitä algoritmi tarkoituksella ei tee, on päättää, mitä fonttia käyttää, muodostaa ligatuureja tai järjestää glyfejä uudelleen klusterin sisällä; ne ovat muotoilun asioita ja kuuluvat vaiheeseen tämän jälkeen
uses
FPdfBidi;
var
Levels: TPdfBidiLevels;
Order: TPdfBidiOrder;
ParagraphLevel: Byte;
Text, Visual: WideString;
I: Integer;
begin
Text := SourceLine;
// pbdAuto soveltaa P2-P3: ensimmäinen vahva merkki päättää
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 lukee nyt vasemmalta oikealle; Levels[] kertoo yhä, mitkä
// ajot ovat RTL, joten muotoilijalle voidaan antaa oikeat suunnat
end;
end;
Merkkiluokkataulukko on generoitu, ei kirjoitettu
Jokaisella koodipisteellä on ominaisuus Bidi_Class, ja algoritmi kysyy sitä jatkuvasti, joten taulukko on perusta, jonka päällä kaikki muu seisoo. Se generoidaan Unicode Character Databasesta käsin ylläpidettävän sijaan: kenttä viisi tiedostossa UnicodeData.txt antaa osoitetut luokat, ja @missing-deklaraatiot tiedostossa DerivedBidiClass.txt antavat oletukset koodipisteille, joita tietokanta ei osoita, mistä syystä varaamattomat lohkot olettavat oikein arvoon R, AL, ET tai BN arvon L sijaan
Pakkaustemppu on emittoida vain ne alueet, joiden luokka ei ole L. Kaikki, mikä putoaa jokaisen alueen ulkopuolelle, on L, mikä on sekä Unicoden oletus että valtaosan koodipisteiden luokka. Se vie taulukon, joka muuten juoksisi tuhansiin merkintöihin, alas 745 alueeseen ja noin 6,7 kilotavuun. Käytännön seuraus kannattaa sanoa: siirryttyäsi uuteen Unicode-versioon aja generaattori uudelleen. Include-tiedoston käsin muokkaaminen toimii, ja se myös erkanee hiljaisesti tietokannasta seuraavassa päivityksessä
L2:n on järjestettävä koodipisteet uudelleen, ei UTF-16-koodiyksiköitä
Tämä on virhe, joka tuottaa aidosti korruptoitunutta tulostetta, ja ensimmäinen toteutus teki sen. L2 sanoo kääntävän yhtenäisiä ajoja kullakin tasolla ylimmästä alhaisimpaan parittomaan tasoon. Kirjoitettuna UTF-16-merkkijonoa vasten ”ajon kääntäminen” tarkoittaa luonnollisesti sen koodiyksiköiden kääntämistä. Perustasolaatikon (Basic Multilingual Plane) merkeille se kelpaa. RTL-merkille astral-tasossa, kuten kyproslaisissa tai vanhoissa eteläarabialaisissa lohkoissa arvon U+10800 lähellä, se ei ole: merkki on surrogaattipari, ajon kääntäminen laittaa matalan surrogaatin korkean eteen, ja merkkijono sisältää nyt kaksi paritonta surrogaattia yhden merkin sijaan. Mikään alavirrassa ei voi palauttaa sitä
Korjaus on tehdä L2 koodipisteyksiköillä. Toteutus yhdistää koodiyksiköt koodipisteyksiköiksi, suorittaa käännökset kyseisille yksiköille ja laajentaa tuloksen takaisin koodiyksikköindekseihin lopussa. Siksi PdfBidiVisualOrder ottaa tekstin eikä ainoastaan tasotaulukkoa: se ei voi kertoa, missä surrogaattirajat ovat pelkistä tasoista. Sama surrogaattiparikuri kulkee teksti-API:en läpi yleisesti, mistä kerrotaan artikkelissa emoji, CJK ja surrogaattiparit
Tasojen laskeutumisen on sisällettävä tasot, joita ei esiinny
Toinen virhe on hienovaraisempi eikä tuota kaatumista, vain tekstiä, jota ei ole järjestetty uudelleen. L2 sanoo aloitettavan ylimmältä läsnä olevalta tasolta ja edettävän alas alhaisimpaan parittomaan tasoon. Luonnollinen optimointi on kerätä joukko tasoja, jotka todella esiintyvät, ja iteroida kyseisen joukon yli. Se on väärin
Harkitse latinalaista tekstiriviä oikealta vasemmalle menevän upotuksen sisällä. Kappaleen taso on 0, upotus työntää latinalaiset merkit tasolle 2, eikä mikään merkki istu tasolla 1. Esiintyvien tasojen yli iterointi löytää vain arvot 0 ja 2, eikä paritonta tasoa ole lainkaan, joten silmukka ei suorita kääntämistä. Tuo vastaus on oikein, mutta syystä, jota optimointi ei tiedä: kääntäminen tasolla 2, jota seuraa kääntäminen tasolla 1, kumoutuisi täsmälleen, joten kummankaan suorittaminen on oikea lopputulos. Muuta syötettä hieman niin, että sekä tason 1 että tason 3 merkkejä on olemassa mutta tasoa 2 ei ole, ja joukkopohjainen silmukka ohittaa tason 2 käännön, jota algoritmi vaatii
// Oikein: kävele jokainen taso maksimista alhaisimpaan parittomaan
// tasoon, mukaan lukien tasot, joita mikään merkki oikeasti ei ole
Level := MaxLevel;
while Level >= LowestOddLevel do
begin
ReverseRunsAtOrAbove(Level); // ei-toiminto, kun mikään ajo ei täsmää
Dec(Level);
end;
Kirjoitettuna tavallisena laskevana silmukkana käyttäytyminen tulee ulos ilmaiseksi, ja ei-toimintokierrokset eivät maksa mitään mitattavaa. Tämä on tapaus, jossa ilmeinen optimointi ei ole hieman väärin, se on väärin syötteestä riippuvalla tavalla, jota pieni testikorpus ei koskaan paljasta
Sulkeet: BD16 käytännöllisellä taulukolla
Sääntö N0 ja BD16-sulkeparialgoritmi on olemassa, jotta sulku sekasuuntaisessa tekstissä ratkeaa sen sisällön suuntaan sen sijaan että ratkaisisi siihen, mikä sattuu olemaan vieressä. Se vaatii sulkeparien taulukon. Toteutus kantaa yleisessä käytössä olevat parit eikä Unicode-sulketiedoston koko sisältöä: ASCII-, CJK-, täysleveät, matemaattiset ja koristeelliset sulkeet
Luettelossa olematon sulku ei ole virhe. Se ratkeaa tavallisena neutraalina sääntöjen N1 ja N2 kautta, mikä on täsmälleen se käyttäytyminen, jollaista jokainen toteutus oli ennen kuin Unicode 6.3 toi N0:n. Niinpä raja on ”vähemmän hienostunut harvinaisille sulkeille”, ei ”väärin”. Yksi yksityiskohta tarvitsee nimenomaisen käsittelyn: kanoninen ekvivalenssi kulmasulkeiden välillä arvoissa U+2329 ja U+232A sekä niissä arvoissa U+3008 ja U+3009 on taitettava pariä täsmäytettäessä, tai toisin kirjoitettu avaava sulku ei onnistu parittumaan toisin kirjoitetun sulkevan sulun kanssa
Miten testaat kolmekymmentä vuorovaikuttavaa sääntöä
Ei suurella korpuksella, ainakaan ensin. Tuottava lähestymistapa oli kuusitoista käsin varmennettua tapausta, joista kukin valittiin harjoittamaan tiettyä sääntöä ja joista kukin tarkistettiin tasojen varalta, joita UAX #9 sanoo sen tuottavan: kappaleen suunnan tunnistus kohteissa P2 ja P3, heikot tyypit -säännöt W2, W3 ja W7, implisiittisten tasojen säännöt I1 ja I2, eksplisiittinen upotus arvojen X2 ja X7 kautta, isolaatit arvojen X5a ja X6a kautta, L1:n nollaus perässä roikkuvasta tyhjästä ja erottimista, N0-sulkutapaus ja yksi tapaus astral-merkillä lukitsemaan surrogaattien käsittely
Kuusitoista tapausta tunnetusti oikeilla odotustasoilla nappaa enemmän kuin kuusitoistasataa tapausta uskottavan näköisellä tulosteella, koska kaksisuuntaisen toteutuksen vikamuoto on teksti, joka lukee lähes oikein. Kun nuo läpäisevät, korpus on hyödyllinen taulukkoaukkojen ja suorituskykyongelmien löytämiseen, jotka ovat eri virheluokkia
PDFium-komponentin sisällä tasot ruokkivat kahta kuluttajaa. Kirjoituspuolella ne kertovat muotoilutaustalle jokaisen ajon suunnan, mikä on syöte, jota HarfBuzz vaatii. Lukupuolella ne informoivat valintageometriaa ja lukujärjestystä, koska napsautus RTL-tekstissä on kartoitettava loogiseen sijaintiin eikä visuaaliseen; kyseinen kartoitus käsitellään artikkelissa visuaalisen rivin valinta ja lukujärjestysmalli artikkelissa rakenteelliset tekstilohkot ja lukujärjestys. Komponentin alustatukien yksityiskohdat ovat PDFium Delphi -komponentin tuotesivulla