Tekninen artikkeli

CJK-kielinen pystysuora teksti PDF-tiedostossa Delphillä ja HotPDF:llä

Laita japanilainen romaani sivulle ja ensimmäinen asia, jonka huomaat, on se, että teksti kulkee alaspäin sarakkeessa, ei poikki rivin, ja että sarakkeet etenevät arkin oikeasta reunasta kohti vasenta. Lukija, joka on kasvanut sen parissa, pitää vaakasuoraa tekstiä hieman kliinisenä. Insinööritieteen ongelma on se, että PDF, kuten lähes kaikki digitaaliset tekstijärjestelmät, on rakennettu vaakasuoran perusviivan (baseline) ympärille, joka kasvaa vasemmalta oikealle, eikä sisältövirralla (content stream) ole käsitystä siitä, että "kirjoita tämä kappale sen sijaan alaspäin". Joten kun Delphi-sovelluksen on tuotettava todistus, runo, opaste tai perinteisen muodon lakiasiakirja taiwanilaiselle, japanilaiselle tai korealaiselle lukijalle, asettelu on koottava käsin: yksi merkki toisen alapuolelle, yksi sarake viimeisimmän vasemmalle puolelle

HotPDF antaa sinulle kytkimen (switch), joka tekee merkkikohtaisen kirjanpidon puolestasi. Asettamasi fontti kantaa IsVertical-lippua (flag), ja kun se on päällä, yksittäinen TextOut-kutsu pinoaa koko merkkijonon pystysuoraksi sarakkeeksi sen sijaan, että se juoksuttaisi sen perusviivaa pitkin. Sarakkeen sijoittelu, oikealta vasemmalle -järjestys ja yksi hiljaisen tärkeä glyyfin korvaaminen (glyph substitution) ovat asioita, jotka tämän sivun loppuosa käy läpi

HotPDF:n tuottama A4-kokoinen PDF-sivu, joka näyttää kiinalaista, japanilaista ja korealaista tekstiä pinottuna pystysuoriin sarakkeisiin, joita luetaan oikealta vasemmalle
Yksittäinen A4-sivu kiinalaisella, japanilaisella ja korealaisella pystysuoriin sarakkeisiin ladotulla tekstillä, generoitu yhdestä Delphi-proseduurista.

Kytkin asuu SetFont-kutsussa

Pystysuora asettelu (vertical layout) ei ole sivun tai asiakirjan ominaisuus. Se on piirtämiseen käyttämäsi fontin ominaisuus, ja kytket sen päälle SetFont-funktion viidennellä argumentilla:

// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// 5. argumentti kääntää nykyisen fontin pystysuoraan tilaan.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // vaakasuora
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True);  // pystysuora

Koska lippu (flag) kulkee fontin mukana, vaihdat vaakasuoran ja pystysuoran kirjoituksen välillä yksinkertaisesti kutsumalla SetFont-funktiota uudelleen eri viimeisellä argumentilla. Sivu voi sisältää vaakasuoran otsikon yläosassa ja pystysuoraa leipätekstiä sen alapuolella, ja HotPDF pitää nämä kaksi tilaa erillään fonttiobjekti-kohtaisesti (per font object) eikä sivukohtaisesti (per page). Tämä tekee seka-asetteluista (mixed layouts) mahdollisia ilman mitään erityistä tilankäsittelyä sinun puoleltasi: jokainen TextOut pystysuoran SetFont-kutsun jälkeen pinoontuu, jokainen TextOut vaakasuoran kutsun jälkeen kulkee perusviivaa pitkin, ja viimeisin SetFont voittaa

Neljäs argumentti on Windows-merkistö (character set), sama jonka vaakasuora kutsu ottaa. Antamalla arvon DEFAULT_CHARSET annat järjestelmän ratkaista glyyfit (kuviot) merkkijonokohtaisesti, millä on väliä tässä, koska pystysuora asiakirja sekoittaa usein kirjoitusjärjestelmiä. Kaikki muu fonttiin liittyvä pätee edelleen: sen on oltava asennettuna käännöskoneella (build machine), ja haluat lähes aina, että FontEmbedding := True, jotta tiedosto renderöi samat CJK-glyyfit lukijalla, jolla ei koskaan ollut Arial Unicode MS -fonttia

Yksi TextOut-kutsu on yksi sarake

Kun pystysuora fontti on aktiivinen, TextOut-kutsu ei enää levitä merkkijonoaan sivuttain annetusta pisteestä. Se istuttaa ensimmäisen merkin ylös ja kävelee loput suoraan alaspäin edeten fontin rivikorkeuden verran jokaista glyyfiä kohden. X, jonka välität, kiinnittää sarakkeen; Y, jonka välität, kiinnittää sen, mistä sarakkeen yläosa alkaa. Asetellaksesi todellisen tekstikappaleen (passage) annat siksi yhden TextOut-kutsun saraketta kohti ja askellat X:ää vasemmalle kutsujen välillä, koska CJK-sarakkeita luetaan oikealta vasemmalle

var
  Pdf: THotPDF;
const
  ColTop = 760;   // ensimmäisen glyyfin y-koordinaatti joka sarakkeessa (pisteinä)
  ColGap = 28;    // sarakkeiden välinen vaakasuora etäisyys
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VerticalText.pdf';
    Pdf.FontEmbedding := True;       // upota CJK-kirjasin siirrettävää renderöintiä varten
    Pdf.BeginDoc;
    Pdf.CurrentPage.Size := psA4;

    // Vaakasuora otsikko ensin, tavallisessa kirjoitustilassa.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
    Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');

    // Vaihda fontti pystysuoraan tilaan; jokainen alla oleva TextOut pinoontuu nyt.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);

    // Sarakkeet etenevät oikealta vasemmalle, joten X pienenee jokaisella kutsulla.
    Pdf.CurrentPage.TextOut(520, ColTop, 0, '床前明月光');
    Pdf.CurrentPage.TextOut(520 - ColGap,     ColTop, 0, '疑是地上霜');
    Pdf.CurrentPage.TextOut(520 - ColGap * 2, ColTop, 0, '舉頭望明月');
    Pdf.CurrentPage.TextOut(520 - ColGap * 3, ColTop, 0, '低頭思故鄉');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Runo on luettavissa niin kuin pitääkin: oikeanpuoleisin sarake ensin, ylhäältä alas, sitten silmä hyppää vasemmalle seuraavaan sarakkeeseen. Mikään itse merkkijonossa ei koodaa tuota järjestystä. Sinä koodaat sen X-koordinaatteihin, pienentämällä niitä kutsu kerrallaan. Jos asetat suunnan takaperin, säkeistö tulee ulos käänteisenä, mikä on yksittäinen yleisin virhe siirrettäessä vaakasuoraa asettelua pystysuoraan

Koordinaatit: alas sivua, oikealta vasemmalle

Kaksi akselia on pelissä ja ne vetävät vastakkaisiin suuntiin, joten on syytä olla tarkka. HotPDF mittaa sivun vasemmasta alakulmasta, Y:n kasvaessa ylöspäin, pisteinä (points). Pystysuora sarake siis alkaa suuresta Y-arvosta (lähellä arkin yläreunaa) ja merkit laskeutuvat sieltä alaspäin, kun HotPDF vähentää yhden rivikorkeuden (line height) jokaista merkkiä kohden. Asetat tuon aloitus-Y:n kerran saraketta kohti ja komponentti hoitaa laskeutumisen

Vaakasuora akseli on se, jota ohjaat manuaalisesti. Jokainen sarake istuu omassa X-koordinaatissaan, ja peräkkäiset sarakkeet askeltavat pienempiin X-arvoihin, koska lukemisjärjestys on oikealta vasemmalle. Järkevä rytmi on valita oikeanpuoleisimman sarakkeen X, ja vähentää sitten kiinteä sarakkeiden väli (column gap) jokaista seuraavaa kutsua kohden, kuten esimerkki tekee ColGap-muuttujalla. Väli on sinun valittavissasi; liian tiukka ja vierekkäiset sarakkeet koskettavat toisiaan, liian löysä ja lohko näyttää harvalta. Leipätekstissä (body text) 12–18 pisteen (points) koolla hieman fonttikokoa suurempi väli on miellyttävä lukea

Glyyfit, joiden on muutettava muotoaan

Muutama merkki ei ole yksinkertaisesti kierrettyjä kopioita vaakasuorista itsekseen; ne on korvattava, kun teksti kääntyy pystysuoraan. Se, jonka HotPDF hoitaa puolestasi, on japanilainen pidentävä äänimerkki (U+30FC, long-vowel bar), joka esiintyy katakana-sanoissa kuten コーヒー. Vaakasuunnassa piirrettynä se on lyhyt viiva perusviivalla (baseline). Jos pinoisit tuon saman glyyfin sarakkeeseen, se makaisi litteänä sarakkeen poikki, mikä on väärin: pystysuorassa japanissa merkistä tulee pystysuora veto, joka yhdistää kaksi merkkiä joiden välissä se on. HotPDF havaitsee U+30FC:n pystysuoralla polulla ja renderöi sen pystysuorana palkkina (U+007C), jotta pitkän vokaalin merkki osoittaa oikeaan suuntaan ilman mitään työtä sinun puoleltasi

Tuo yksittäinen korvaus kattaa tapauksen, joka rikkoo useimmat naiivit toteutukset, mutta on hyvä tietää missä yleinen ongelma menee syvemmälle. Täydellinen pystysuora typografia (vertical typography) myös kääntää latinalaiset kirjaimet ja länsimaiset välimerkit yhdeksänkymmentä astetta, siirtää pieniä kana-merkkejä (small kana) ja sijoittaa sulkeet ja pilkut uudelleen niiden pystysuoriin muotoihin, ja täydellinen toteutus siitä asuu fontin OpenType-pystyominaisuuksissa (vertical features) kiinteiden sääntöjen sijaan. HotPDF voi hyödyntää noita ominaisuuksia silloin kun fontti niitä kantaa: pystysuorat vaihtoehdot (vertical alternates, vert- ja vrt2-GSUB-ominaisuudet) ja pystysuora merkkivälin tihennys (vertical kerning, vkrn- ja vpal-GPOS-haut) ovat opt-in -ominaisuuksia, joita sovelletaan pystysuoralla polulla vain silloin, kun aktiivinen fontti todella määrittelee ne. Sekoitetulle CJK-tekstille (mixed CJK text) yhdessä laajan kattavuuden kirjaintyypissä (broad-coverage face) kuten Arial Unicode MS, sisäänrakennettu U+30FC:n käsittely plus tasainen rivikorkeusaskel on tarpeeksi oikeiden, luettavien sarakkeiden tuottamiseen; OpenType-ominaisuuksilla on väliä silloin, kun siirryt fonttiin, joka on suunniteltu hienoon pystysuoraan ladontaan, ja haluat sen natiivin kana-sijoittelun ja glyyfien välisen välistyksen (inter-glyph spacing)

Kirjoitusjärjestelmien ja suuntien sekoittaminen samalla sivulla

Todelliset asiakirjat ovat harvoin puhtaita. Pystysuora japanilainen sivu saattaa sisältää vaakasuoran englanninkielisen kuvatekstin, sivunumeron alareunassa, tai korealaisen tekstilohkon ladottuna pystysuoraan japanin viereen. Koska pystysuora lippu (vertical flag) on fonttitason kytkin, koostat nämä vuorottelemalla SetFont-kutsuja sen sijaan, että hallitsisit mitään sivunlaajuista tilaa (page-wide state). Aseta vaakasuora fontti, kirjoita juokseva ylätunniste (running header) ja sivunumero (folio), aseta pystysuora fontti, aseta sarakkeet, aseta vaakasuora fontti uudelleen alatunnistetta varten. Jokainen alue poimii viimeisimmän SetFont-kutsun tilan, joten ainoa vaadittava kurinalaisuus on kutsua sitä aina, kun vaihdat suuntaa

Yksi yksityiskohta, johon on varauduttava kun kirjoitusjärjestelmät sekoittuvat (scripts mix): kiinalaiset, japanilaiset ja korealaiset ideogrammit (ideographs) ovat lähellä neliötä ja pinoontuvat tasaisin askelin, mutta pystysuoraan sarakkeeseen upotetuilla latinalaisilla osioilla (Latin runs) ei ole sitä tasaista askellusta (uniform advance). Jos tarvitset muutaman latinalaisen sanan muuten pystysuoran tekstin sisään, päätä harkitusti pitäisikö ne kääntää juoksemaan alaspäin sarakkeessa vai ladota pystysuoraksi (upright) lyhyenä vaakasuorana upotuksena (inset), ja sijoita tuo fragmentti omalla TextOut-kutsullaan sen sijaan, että antaisit sen ratsastaa pystysuoralla askeleella, joka on tarkoitettu ideogrammeille. Sekasuuntaisen (mixed) osion käsitteleminen omana sijoitteluongelmanaan pitää sarakkeen rytmin ehjänä

Esimerkistä tuotantoon

Palaset ovat pieniä ja ne koostuvat ennustettavasti. Kytke pystysuora lippu (vertical flag) päälle SetFont-kutsussa, anna yksi TextOut saraketta kohti kiinteästä ylä-Y-koordinaatista (top Y), ja pienennä X:ää kutsujen välillä, jotta sarakkeet luetaan oikealta vasemmalle. Upota fontti, jotta CJK-glyyfit selviävät matkasta lukijan koneelle, ja anna komponentin hoitaa U+30FC pitkän vokaalin merkki (long-vowel mark) ja, missä fontti niitä tukee, OpenType-pystyominaisuudet (vertical features). Tästä eteenpäin asettelun tuotannollistaminen (productionizing) on enimmäkseen aritmetiikkaa: sarakkeiden X-sijaintien (X positions) johtamista mitatusta lohkon leveydestä (block width), pitkien tekstikappaleiden jakamista sarakkeisiin, jotka mahtuvat sivun korkeuteen, ja tilan varaamista vaakasuorille otsikoille ja sivunumeroille (folios)

Lajemmalle tekstin ja fonttien pinnalle, jonka päälle tämä rakentuu, mukaan lukien vaakasuoran TextOut-kutsun konventiot ja Unicode-fonttien rekisteröinti, katso monikielinen Hello World -esimerkki ja TextOut-näyte. Tässä esitetyt pystysuora SetFont-kytkin ja TextOut-kutsut ovat osa HotPDF-komponenttia Delphille ja C++Builderille