Välitä arabialainen fraasi يوضح ملف PDF TextOut-funktiolle ja avaa tulos. Kirjaimet kulkevat väärään suuntaan, ja kukin niistä istuu eristetyssä muodossaan näkyvällä väliköllä ennen seuraavaa, ikään kuin joku olisi kirjoittanut englantia takaperin ja painanut välilyöntiä jokaisen merkin välissä. Yhtään poikkeusta (exception) ei laukea. Yhtään varoitusta ei tulostu. Tuloste on yksinkertaisesti väärin, ja se on väärin, koska kaksi erillistä muunnosta, joista arabia on riippuvainen, ei koskaan tapahtunut. Sen tietäminen, mitä nuo kaksi muunnosta ovat, ja mikä kutsu suorittaa ne, on pääosa siitä, mihin monimutkaisten kirjoitusjärjestelmien (complex-script) PDF-tulostus kiteytyy
HotPDF on natiivi VCL PDF -komponentti Delphille ja C++Builderille, ja se tekee oikealta vasemmalle -työn puolestasi erillisen kutsun kautta. Se myös pysähtyy muutamaan tiettyyn paikkaan, joista haluat tietää ennen kuin sitoudut johonkin lokaaliin (locale), joten tämä artikkeli kartoittaa konseptit ja rehelliset rajat; itse kutsun käytännön asetus löytyy RtLTextOut-referenssiartikkelista
Miksi oikea merkkijono tulostuu silti väärin
Unicode pitää tekstin loogisessa järjestyksessä, järjestyksessä, jossa kirjoitat sen ja luet sen ääneen. Renderöijän on asetettava glyyfit (kuviot) visuaaliseen järjestykseen. Vasemmalta oikealle luettavissa kirjoitusjärjestelmissä nämä järjestykset osuvat yhteen, eikä kukaan ajattele asiaa. Arabian ja heprean kohdalla ne eivät osu, ja kun yksi rivi sekoittaa suuntia, esimerkiksi arabialainen lause, joka sisältää latinalaisen sanan "PDF" tai numeroin kirjoitetun hinnan, Unicoden kaksisuuntaisuusalgoritmi (Bidirectional Algorithm, UAX #9) päättää tarkasti, kuinka vasemmalta oikealle -fragmentit pesiytyvät (nest) oikealta vasemmalle -rivin sisään. Se on ensimmäinen muunnos, uudelleenjärjestely (reordering), ja sen ohittaminen on se, mikä kääntää rivin
Toinen on kontekstuaalinen muotoilu (contextual shaping). Arabialainen kirjain piirretään eri tavalla riippuen siitä, mihin kohtaan sanaa se osuu: alkuun, keskelle, loppuun vai seisooko se yksin (isolated). Koodipiste pysyy samana koko ajan; vain glyyfi muuttuu. Liukuhihna (pipeline), joka ojentaa kunkin koodipisteen suoraan sen oletusglyyfille, tuottaa täsmälleen sen irrotetun, eristetyn muodon tulosteen kuin aloituskohtaneessa. Heprea ohittaa tämän vaiheen, koska sen kirjaimet eivät yhdisty, mutta se tarvitsee silti uudelleenjärjestelyn. Arabia tarvitsee molemmat, ja siksi arabia, ei heprea, on merkkijono, jolla testaustyö tehdään
Työpöydällä mikään tästä ei ole sinun ongelmasi. Kun VCL-lomake maalaa arabiaa TEdit-kontrolliin, käyttöjärjestelmän tekstipino (text stack) järjestelee ja muotoilee sen hiljaisesti uudelleen, mikä on nimenomaan syy siihen, miksi merkkijono, joka näyttää täydelliseltä ruudulla, tulee ulos rikkoutuneena naiivissa PDF-tiedostossa. Sisältövirta ei tallenna muokattavaa tekstiä. Se tallentaa paikoitetut glyyfit, joten kuka tahansa, joka tuottaa virran, perii sen muotoilutyön, jota käyttöjärjestelmä ennen hoiti. RtLTextOut on kutsu, joka ottaa tuon työn takaisin
Mitä RtLTextOut muotoilee puolestasi
HotPDF pitää latinalaisen polun ja complex-script-polun kahtena eri menetelmänä. TextOut tulostaa sen, mitä sille annat, siinä järjestyksessä kuin sen annat. RtLTextOut suorittaa ensin molemmat muunnokset – kaksisuuntaisen uudelleenjärjestelyn koko rivin läpi, kontekstuaalisen analyysin yhdistyville kirjoitusjärjestelmille – ja tulostaa sitten. Se, minkä kirjoitusjärjestelmän säännöt pätevät, kulkee sisään kirjasimen merkistön (charset) kautta eikä itse kutsun kautta, joten suunta on eksplisiittinen valinta jokaisella kutsupaikalla (call site) hahmon (character) perusteella tehdyn arvauksen sijaan. Parametri parametriltä tehtävä asennus, merkistöarvot, fonttien rekisteröinnin vaiheet ja täydellinen käännettävä esimerkki ovat kaikki RtLTextOut-referenssiartikkelissa; tämä artikkeli pitäytyy siinä, mitä muunnokset merkitsevät, mihin ne pysähtyvät ja kuinka todistaa, että ne toimivat
Yhdellä käyttösäännöllä on väliä jopa tällä tasolla: syötteen on oltava loogisessa järjestyksessä, koska RtLTextOut suorittaa kääntämisen itse, ja käsin jo kääntämäsi merkkijono tulee ulos tuplakäännettynä — referenssiartikkeli käy läpi kyseisen ansan ja sen siivoamisen. Se, mikä ansaitsee ansalle maininnan tässä, on se, miksi se selviytyy testauksesta. Tuplakäännetty puhdas arabialainen merkkijono voi näyttää täysin oikealta, ja se hajoaa käsiin vasta, kun rivi kantaa latinalaisen sanan tai numeron, koska nuo upotetut osiot (embedded runs) eivät enää pesiydy siten kuin UAX #9 sanelee. Bugi ei ole renderöinnissä; se on sen tekstin syöttämisessä algoritmille, joka oli jo puoliksi käsiteltyä
Tuo sama sekasuuntainen (mixed-direction) käyttäytyminen kompastuttaa arvioijia enemmän kuin se kompastuttaa koodia. Oikealta vasemmalle -rivin sisällä numerot ja upotetut latinalaiset sanat luetaan edelleen vasemmalta oikealle. Joku, joka ei ole työskennellyt kaksisuuntaisen asettelun kanssa, katsoo renderöityä laskua, näkee tilinumeron lukevan "väärään" suuntaan suhteessa ympäröivään arabiaan, ja kirjaa sen bugiksi. Se on spesifikaation mukainen oikea tulos. Lyhyt huomautus hyväksymiskriteereissäsi (acceptance criteria), kirjoitettuna ennen ensimmäistä syntyperäisen puhujan (native-speaker) katselmointia, säästää tuon edestakaisen matkan
Milloin uudelleenjärjestely ja yhdistäminen riittävät, ja milloin eivät
Arabian ja heprean juoksevalle tekstille – raportit, laskut, sopimukset, kirjeet – uudelleenjärjestely plus kontekstuaalinen yhdistäminen (contextual joining) on koko työ, ja RtLTextOut hoitaa sen yksin. Raja tulee vastaan, kun typografia vaatii enemmän kuin yhdistämistä. HotPDF:n vastaus arabian puolella on valinnainen (opt-in) tuottajapuolen muotoilija (producer-side shaper): aseta AutoShapeArabic := True ja komponentti kirjoittaa loogisen järjestyksen osion uudelleen Unicoden esitysmuotoihin (Unicode Presentation Forms) ennen kaksisuuntaisuusläpikäyntiä (bidirectional pass), joten yhdistyvät muodot lasketaan loogisia naapureita vasten ja ligatuurien (ligatures) taitokset leivotaan sisään niihin koodipisteisiin, joita PDF todellisuudessa kantaa, sen sijaan että ne jätettäisiin katseluohjelman ratkaistavaksi. Kytkin on oletuksena pois päältä ja tuloste on tavuvakaa (byte-stable), kun se pysyy pois päältä, joten sen kytkeminen päälle on harkittu päätös dokumenttiliukuhihnaa (document pipeline) kohti, ei globaali päivitys. Sama opt-in-malli ulottuu muihin yhdistyviin oikealta vasemmalle -kirjoitusjärjestelmiin, joita HotPDF muotoilee: Syyria, N'Ko, Adlam ja Hanifi Rohingya, joilla kullakin on oma automuotoilulippunsa, joka peilaa arabian vastaavaa
Valinnaiset OpenType-ominaisuudet ovat jälleen eri mekanismi. Valinnaiset ligatuurit (discretionary ligatures) ja vastaavat yksittäisten korvausten (single-substitution) ominaisuudet menevät GetSingleSubstituteGlyph(GID, 'liga')-kutsun läpi, joka ratkaisee yhden korvauksen kerrallaan — syötteen glyyfitunnus (GID) ensin, ominaisuustägi toisena — ja palauttaa syötteen glyyfin muuttumattomana, kun ominaisuus ei sovellu. Se riittää ohjaamaan tunnettua, rajallista ligatuuriluetteloa, jota ylläpidät itse. Se ei ole täysi GSUB-moottori, ja ero on juuri siinä, mihin kunnianhimoiset locale-suunnitelmat kaatuvat: muotoilupipeline, joka käsittelee arabiaa virheettömästi, on osoittanut uudelleenjärjestelyn ja yhdistämisen, ei muuta
Kattavuus yli kirjoitusjärjestelmien
Arabia harjoittaa molempia muunnoksia, minkä vuoksi se on se merkkijono, jolla testataan, ja miksi arabiankielisen tekstin läpäisy on vahvin yksittäinen todiste siitä, että liukuhihna (pipeline) toimii. Heprea tarvitsee uudelleenjärjestelyn, mutta ei yhdistämistä, koska sen kirjaimet seisovat yksin; jos heprea renderöityy oikein, mutta arabia tulee ulos irrotettuna, kaksisuuntainen puolikas on kunnossa ja kontekstuaalinen puolikas ei koskaan pyörähtänyt. Persia ja urdu ratsastavat arabialaisella aakkostolla ja perivät sen käyttäytymisen, vaikkakin urdun mieltymys Nastaliq-tyyliin on fonttipäätös, jolla on luettavuuteen liittyviä seurauksia, ja jonka syntyperäisen lukijan tulisi arvioida
Thai istuu kokonaan viivan toisella puolella. Se kulkee vasemmalta oikealle, joten se ei tarvitse kaksisuuntaista työtä, ja sen kirjaimet eivät yhdisty, joten se ei tarvitse kontekstuaalista analyysia; thaikieliset merkkijonot kulkevat tavallisen TextOut-polun läpi kuten latina. Mitä thailla puolestaan on, ovat pinotut merkit — vokaalit ja äänensävymerkit (tone marks) pohjakonsonantin (base consonant) ylä- ja alapuolella — ja se, istuvatko ne oikein, riippuu siitä, rakentaako fontti yhdistävät merkkinsä (combining marks) pinoontumaan ilman muotoilumoottorin apua. Useimmat omistautuneet thai-fontit tekevät niin. Testaa juuri sillä fontilla, jonka aiot upottaa, älä samanlaiselta näyttävällä
Devanagari ja muu intialainen perhe ovat rehellinen kova stoppi. Niiden vokaalimerkit järjestyvät uudelleen konsonanttiklustereiden ympärille, ja niiden konjunktit (conjuncts) muodostuvat kontekstista riippuvien korvausten ketjujen kautta, mikä on täyttä GSUB-aluetta, uudelleenjärjestelyn ja yhdistämisen tuolla puolen. Jos intialainen lokaali (Indic locale) on tiekartoituksessa, suorita todellinen pilottikokeilu aidoilla asiakkaiden merkkijonoilla ennen kuin lupaat sen — se, että arabia toimii, ei ole todiste siitä, että devanagari tulee toimimaan. CJK-merkkijonot, vietnamilainen kieli pinoontuvine diakriittisine merkkeineen, sekä sekoitettu eurooppalainen teksti kulkevat kaikki tavallista polkua pitkin ilman kaksisuuntaista analyysia, ja kannattaa pitää nämä kaksi polkua fyysisesti erillään raportointikoodissa, yksi rutiini RTL-osioille ja toinen kaikelle muulle, jotta lokaali-logiikka on näkyvissä kutsupaikassa (call site) sen sijaan, että se olisi piilotettu lipun taakse, jonka joku unohtaa asettaa
Glyyfien kattavuus päätetään ennen kuin muotoilu edes käynnistyy
Muotoilu (Shaping) poimii glyyfejä fontista. Jos fontti ei kanna niitä, ei ole mitään poimittavaa, minkä vuoksi klassinen käyttöönoton epäonnistuminen (deployment failure) — virheetön kehittäjän koneella, tyhjiä laatikoita asiakkaan palvelimella äänettömän fonttikorvauksen (font substitution) jälkeen — on kattavuusongelma, ei muotoiluongelma. Käytännön lääke, toimitettavan fontin rekisteröinti sen sijaan, että luotettaisiin siihen, mitä koneelle on asennettu, käydään läpi askel askeleelta referenssiartikkelissa. Käsitteellinen pointti on se, että kattavuus on vahvistettava ennen kuin mikään muotoiluun liittyvä kysymys on edes mielekäs, ja että se voidaan vahvistaa ohjelmallisesti sen sijaan, että arvioitaisiin tulostetta silmämääräisesti
// RegisterUnicodeTTF:n jälkeen, tarkista kattavuus (audit coverage) niille
// koodipisteille, joita tietosi todellisuudessa käyttävät
GID := Pdf.GetUnicodeGlyphForCodepoint($0628); // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);
Rekisteröinti itsessään sisältää kaksi rajoitetta — PDF 1.5 -pohja upotetun Unicoden käsittelylle ja fontin upotusluvan bitit (embedding-permission bits) — jotka molemmat käsitellään asennusvaiheiden ohella RtLTextOut-referenssiartikkelissa. Tähän kuuluu auditointitapa (audit habit): GetUnicodeGlyphForCodepoint on varhaisvaroitusjärjestelmäsi. Käy läpi ne koodipistealueet, joita tietosi todellisuudessa käyttävät palvelun käynnistyessä, ja lokita mitkä glyyfitunnukset tulevat takaisin. Kattavuusaukko näkyy silloin rivinä käynnistyslokissa käyttöönoton aikana, sen sijaan että se näkyisi puuttuvina merkkeinä laskussa, joka ehti jo saavuttaa asiakkaan
Lukemisjärjestys kuuluu asiakirjalle, ei glyyfeille
Jokaisen glyyfin saaminen oikein jättää silti yhden asian tekemättä. ISO 32000-1 §12.2 määrittelee katseluohjelman asetuksen (viewer preference) nimeltä /Direction, joka ilmoittaa asiakirjan yleisen lukemisjärjestyksen. Se ei kosketa yhtäkään glyyfiä. Mitä se tekee, on kertoa katseluohjelmalle, kuinka järjestää kaksisivuiset aukeamat (two-up spreads), kummalta puolelta aukeaman asettelun pitäisi alkaa, ja kumpaan suuntaan luku-käyttöliittymän (reading UI) tulisi nojata. Mikään tästä ei näy yhdellä sivulla, ja juuri siksi se unohdetaan
// Ilmoita oikealta vasemmalle -lukemisjärjestys asiakirjatasolla
Pdf.Direction := RightToLeft; // lisää vpDirection-ominaisuuden ViewerPreferences-kohtaan
Direction-ominaisuuden asettaminen on koko työ: ominaisuuden asettaja lisää vpDirection-arvon asiakirjan ViewerPreferences-kokoelmaan, joten yksi rivi kantaa mieltymyksen tiedostoon. Jos teksti menee ulos RtLTextOut-kutsun kautta, saat tämän ilmaiseksi, koska kutsu kääntää asiakirjan suunnan sivuvaikutuksena — referenssiartikkeli kattaa tilanteen, jossa sekasuuntainen (mixed) asiakirja vaatii tämän kumoamista. Tapaus, jossa sinun on asetettava se itse, on oikealta vasemmalle luettava asiakirja, joka on tuotettu millä tahansa muulla tavalla, esimerkiksi syötteestä, jonka olet esimuotoillut ohjelmakoodissa ja piirtänyt tavallisen polun kautta. Jätä se pois, ja yksittäinen sivu, jota tuijotat, näyttää identtiseltä kummallakin tavalla; sitten joku tulostaa kaksipuolisen vihkosen (duplex booklet), aukeamat tulevat ulos peilikuvina, ja syy on viikkoja aiemmin puuttumaan jäänyt koodirivi (one-liner)
Muotoillun tulosteen todentaminen (Verifying)
Todenna alusta loppuun, koska sivu voi näyttää oikealta ja olla silti hyödytön kaikelle, mikä tapahtuu myöhemmin. Kolme tarkistusta löytää useimmat ongelmat. Kopioi teksti takaisin ulos Acrobatista ja vertaa koodipisteitä lähdemerkkijonoosi. Suorita katseluohjelman asiakirjan sisäinen haku sanalle, jonka voit nähdä sivulla. Ja avaa tuloste koneella, jolla ei ole kehitysfonttejasi (development fonts), eli koneella, joka todennäköisimmin paljastaa fonttikorvauksen (substitution). Mikään tästä ei korvaa sitä, että syntyperäinen lukija katsoo yhtä aitoa asiakirjaa, mikä nappaa asioita, joita yksikään synteettinen korpus (synthetic corpus) ei saa kiinni. Hanki tuo katselmointi (review) kalenteriin ennen kuin muoto toimitetaan
Valitse testimerkkijonot tarkoituksella sen sijaan, että kierrättäisit mitä tahansa, mitä kääntäjä lähetti viime vuonna. Toimiva minimi per locale: puhtaan kirjoitusjärjestelmän (pure-script) lause, lause jossa on upotettuja latinalaisia tuotemerkkejä, rivi joka kantaa numeroita ja valuuttaa, ja nimiä, joissa on diakriittisia merkkejä tai yhdistäviä merkkejä. Todelliset asiakasnimet rikkovat olettamuksia, joihin täyteteksti ei koske, joten anna regressiojoukon kasvaa yhdellä merkkijonolla joka kerta, kun tukitapaus (support case) paljastaa kaavan, jota et ollut nähnyt
Fontin rekisteröinti, osajoukkojen luonti (subsetting) ja jokapäiväinen tekstin piirtämisen API (text-drawing API) käsitellään artikkelissa raportin tulostuksesta, fonteista ja kuvista HotPDF:llä. Kun samojen asiakirjojen on täytettävä myös esteettömyysprofiilit, PDF/A- ja PDF/UA-validointiartikkelissa olevat kielen koodauksen (language tagging) ja rakenteen säännöt istuvat tässä käsitellyn muotoilutyön päällä
Yllä kuvatut oikealta vasemmalle- ja Unicode-fontti-API:t toimitetaan HotPDF-komponentin mukana Delphille ja C++Builderille; tuotesivulla on linkki koko tekstin tulostuksen referenssiin