Technical Article

Zvislý CJK text v PDF s Delphi a HotPDF

Otvorte japonský román na ľubovoľnej strane a prvá vec, ktorú si všimnete, je, že text beží zhora nadol v stĺpcoch, nie vodorovne v riadkoch, a že stĺpce postupujú od pravého okraja listu smerom k ľavému. Pre čitateľa, ktorý na tom vyrastal, pôsobí horizontálny text tak trochu neosobne. Technický problém spočíva v tom, že PDF, podobne ako takmer každý digitálny textový systém, bol postavený na horizontálnom účaští, ktoré rastie zľava doprava, a dátový tok obsahu nemá žiadnu predstavu o príkaze typu „napíš tento odsek smerom nadol“. Keď teda aplikácia v Delphi potrebuje vytvoriť certifikát, báseň, nápis alebo právny dokument v tradičnom formáte pre taiwanského, japonského alebo kórejského čitateľa, rozloženie sa musí poskladať ručne: jeden znak pod druhý, jeden stĺpec naľavo od predchádzajúceho.

HotPDF vám ponúka prepínač, ktorý urobí výpočet pozícií pre každý znak za vás. Písmo, ktoré nastavíte, nesie príznak IsVertical, a keď je aktívny, jediné volanie TextOut uloží celý reťazec do zvislého stĺpca namiesto toho, aby ho vykreslilo pozdĺž účaštia. Umiestnenie stĺpcov, poradie sprava doľava a jedna tichá, no dôležitá náhrada glyfu sú témy, ktorým sa venuje zvyšok tejto stránky.

Stránka formátu A4 vytvorená pomocou HotPDF, ktorá zobrazuje čínsky, japonský a kórejský text usporiadaný do zvislých stĺpcov čítaných sprava doľava
Jediná stránka A4 s čínskym, japonským a kórejským textom vo zvislých stĺpcoch, vygenerovaná z jednej procedúry v Delphi.

Prepínač sa nachádza v SetFont

Zvislé rozloženie nie je vlastnosťou stránky ani dokumentu. Je to vlastnosť písma, ktorým kreslíte, a zapína sa pomocou piateho argumentu funkcie SetFont:

// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// The 5th argument flips the current font into vertical mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // horizontal
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True);  // vertical

Keďže príznak je viazaný na písmo, prepínanie medzi vodorovným a zvislým písaním dosiahnete jednoducho opätovným volaním SetFont s iným posledným argumentom. Stránka môže obsahovať vodorovný nadpis hore a zvislý text tela pod ním. HotPDF udržuje tieto dva režimy oddelené pre každý objekt písma samostatne, nie pre celú stránku. Vďaka tomu sú zmiešané rozloženia možné bez potreby špeciálneho spracovania režimov na vašej strane: každé volanie TextOut po zvislom SetFont ukladá znaky na seba, každé volanie TextOut po vodorovnom SetFont beží pozdĺž účaštia, pričom vyhráva posledné volané SetFont.

Štvrtým argumentom je znaková sada systému Windows, rovnaká, akú prijíma horizontálne volanie. Odovzdanie DEFAULT_CHARSET umožňuje systému vyhodnocovať glyfy pre každý reťazec samostatne, čo je dôležité, pretože zvislý dokument často kombinuje rôzne sady znakov. Všetko ostatné o písme platí rovnako: musí byť nainštalované na zostavovacom stroji a takmer vždy budete chcieť nastaviť FontEmbedding := True, aby sa súbor s rovnakými glyfmi CJK správne zobrazil aj čitateľovi, ktorý nikdy nemal nainštalovaný font Arial Unicode MS.

Jedno volanie TextOut je jeden stĺpec

Pri aktívnom zvislom písme už volanie TextOut nerozširuje svoj reťazec do strán od daného bodu. Umiestni prvý znak na začiatok a zvyšok ukladá priamo nadol, pričom pri každom glyfe sa posunie o výšku riadku písma. Súradnica X, ktorú odovzdáte, určuje stĺpec. Súradnica Y určuje miesto, kde začína vrch stĺpca. Na rozloženie skutočného textu preto použijete jedno volanie TextOut pre každý stĺpec a medzi volaniami posúvate súradnicu X doľava, pretože stĺpce CJK sa čítajú sprava doľava.

var
  Pdf: THotPDF;
const
  ColTop = 760;   // y of the first glyph in every column (points)
  ColGap = 28;    // horizontal distance between columns
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VerticalText.pdf';
    Pdf.FontEmbedding := True;       // embed the CJK face for portable rendering
    Pdf.BeginDoc;
    Pdf.CurrentPage.Size := psA4;

    // A horizontal heading first, in the ordinary writing mode.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
    Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');

    // Switch the font into vertical mode; every TextOut below now stacks.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);

    // Columns advance right to left, so X decreases with each call.
    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;

Báseň sa číta správne: najprv stĺpec úplne vpravo zhora nadol, potom oko preskočí doľava na ďalší stĺpec. Samotný reťazec toto poradie nijako nekóduje. Kódujete ho vy v súradniciach X, ktoré s každým volaním znižujete. Ak si pomýlite smer, verše sa vykreslia v opačnom poradí, čo je vôbec najčastejšia chyba pri prekladaní vodorovného rozloženia na zvislé.

Súradnice: nadol po stránke, sprava doľava

V hre sú dve osi a tie ťahajú opačnými smermi, preto je dôležité byť presný. HotPDF meria od dolného ľavého rohu stránky v bodoch, pričom Y rastie smerom nahor. Zvislý stĺpec preto začína na vysokej hodnote Y (blízko vrchu strany) a znaky odtiaľ klesajú nadol, ako HotPDF odčítava výšku riadku pre každý z nich. Toto počiatočné Y nastavíte raz pre každý stĺpec a komponent sa postará o zostup.

Horizontálnu os riadite manuálne. Každý stĺpec má svoje vlastné X a nasledujúce stĺpce sa posúvajú k menším hodnotám X, pretože poradie čítania je sprava doľava. Vhodným postupom je určiť X pre stĺpec úplne vpravo a pre každé ďalšie volanie odčítať fixný odstup stĺpcov, ako to robí príklad s premennou ColGap. Odstup je na vašom rozhodnutí; príliš tesný spôsobí, že sa susedné stĺpce budú dotýkať, príliš voľný a blok bude pôsobiť prázdno. Pre bežný text s veľkosťou 12 až 18 bodov je dobre čitateľný odstup o niečo väčší ako veľkosť samotného písma.

Glyfy, ktoré musia zmeniť tvar

Niektoré znaky nie sú len jednoducho otočenými kópiami ich horizontálnych verzií. Pri otočení textu do zvislej polohy sa musia nahradiť. Tým, ktorý za vás rieši HotPDF, je japonský znak predĺženej samohlásky (U+30FC), čo je vodorovná čiara používaná v katakane v slovách ako コーヒー. Pri horizontálnom kreslení je to krátka pomlčka na účaští. Ak by ste ten istý glyf uložili do zvislého stĺpca, ležal by naprieč stĺpcom, čo je nesprávne: vo zvislej japončine sa tento znak mení na zvislú čiaru spájajúcu dva znaky, medzi ktorými leží. HotPDF deteguje U+30FC v zvislej ceste a vykreslí ho ako zvislú čiaru (U+007C), takže označenie dlhej samohlásky smeruje správnym smerom bez akejkoľvek práce z vašej strany.

Táto jediná substitúcia rieši prípad, na ktorom zlyháva väčšina naivných implementácií, ale je dobré vedieť, že všeobecný problém siaha hlbšie. Kompletná zvislá typografia tiež otáča latinské písmená a západnú interpunkciu o deväťdesiat stupňov, posúva malú kanu a mení polohu zátvoriek a čiarok na ich zvislé formy. Plná implementácia tohto správania sa nachádza v OpenType funkciách pre zvislý smer daného písma a nie vo fixných pravidlách. HotPDF dokáže tieto funkcie využiť, ak ich písmo obsahuje: zvislé alternatívy (funkcie GSUB vert a vrt2) a zvislý kerning (vyhľadávania GPOS vkrn a vpal) sú voliteľné a aplikujú sa na zvislá cestu iba vtedy, keď ich aktívne písmo skutočne definuje. Pre zmiešaný CJK text v jednom univerzálnom písme, ako je Arial Unicode MS, stačí zabudované spracovanie U+30FC spolu s rovnomerným krokom výšky riadku na vytvorenie správnych, čitateľných stĺpcov; funkcie OpenType sú dôležité vtedy, keď prejdete na písmo navrhnuté špeciálne pre jemné zvislé nastavenia a požadujete jeho natívne umiestnenie kany a rozostupy medzi glyfmi.

Miešanie písiem a orientácií na jednej stránke

Reálne dokumenty sú málokedy čisté. Zvislá japonská stránka môže obsahovať vodorovný anglický popisok, číslo stránky dole alebo blok kórejského textu umiestnený zvisle vedľa japončiny. Keďže príznak zvislého smeru je prepínačom na úrovni písma, tieto rozloženia skladáte striedaním volaní SetFont namiesto správy stavu celej stránky. Nastavte horizontálne písmo, napíšte priebežnú hlavičku a číslo strany, nastavte vertikálne písmo, vyskladajte stĺpce a znova nastavte horizontálne písmo pre pätičku. Každá oblasť preberá režim posledného volania SetFont, takže jedinou potrebnou disciplínou je zavolať ho vždy, keď meníte smer.

Jeden detail, s ktorým treba pri miešaní písiem počítať: čínske, japonské a kórejské ideogramy sú takmer štvorcové a ukladajú sa v rovnomerných krokoch, avšak latinské texty vložené do zvislého stĺpca nemajú tento jednotný posun. Ak potrebujete niekoľko latinských slov vo vnútri inak zvislého textu, zámerne sa rozhodnite, či sa majú otočiť tak, aby bežali nadol v stĺpci, alebo sa majú nastaviť vzpriamene ako krátky horizontálny vklad, a umiestnite tento fragment pomocou vlastného volania TextOut, namiesto toho, aby ste ho nechali ísť po zvislých krokoch určených pre ideogramy. Považovanie zmiešaného behu za samostatnú úlohu umiestnenia zachová rytmus stĺpca neporušený.

Od príkladu k produkcii

Tieto časti skladačky sú malé a skladajú sa predvídateľne. Zapnite príznak zvislého písania v SetFont, zavolajte jedno TextOut pre každý stĺpec z fixného horného Y a znižujte X medzi volaniami, aby sa stĺpce čítali sprava doľava. Vložte písmo priamo do súboru, aby glyfy CJK prežili prenos na počítač čitateľa, a nechajte komponent spracovať japonský znak predĺženej samohlásky U+30FC a (ak ich písmo podporuje) OpenType funkcie pre zvislý smer. Odtiaľ je príprava rozloženia pre produkciu najmä otázkou aritmetiky: odvodenie pozícií X pre stĺpce z nameranej šírky bloku, rozdelenie dlhých pasáží do stĺpcov zodvedajúcich výške stránky a vyhradenie miesta pre horizontálne hlavičky a čísla strán.

Pre širšie informácie o texte a písmach, na ktorých toto riešenie stavia, vrátane konvencií pre horizontálny TextOut a registráciu Unicode písiem, si pozrite viacjazyčný príklad Hello World a príklad s TextOut. Vertikálny prepínač pre SetFont a volania TextOut zobrazené v tomto článku sú súčasťou komponentu HotPDF pre Delphi a C++Builder.