Technical Article

Vertikalno CJK besedilo v PDF z Delphi in HotPDF

Če na stran postavite japonski roman, boste najprej opazili, da besedilo poteka navzdol po stolpcu in ne vodoravno čez vrstico ter da stolpci napredujejo od desnega roba lista proti levemu. Bralcu, ki je odrastel ob tem, se zdi vodoravno besedilo rahlo hladno. Inženirska težava je v tem, da je bil PDF, tako kot skoraj vsak digitalni besedilni sistem, zgrajen okoli vodoravne izhodiščne črte, ki poteka od leve proti desni, tok vsebine pa nima predstave o tem, da bi moral 'ta odstavek namesto tega pisati navzdol'. Ko mora aplikacija v Delphiju ustvariti potrdilo, pesem, napis ali tradicionalni pravni dokument za tajvanskega, japonskega ali korejskega bralca, je treba postavitev sestaviti ročno: en znak pod drugim, en stolpec levo od prejšnjega.

HotPDF vam ponuja stikalo, ki namesto vas opravi delo za vsak posamezen znak. Pisava, ki jo nastavite, vsebuje zastavico IsVertical, in ko je ta vklopljena, en sam klic TextOut zloži celoten niz v navpični stolpec namesto izpisa vzdolž vodoravne izhodiščne črte. Postavitev stolpcev, vrstni red od desne proti levi in ena tiho pomembna zamenjava glifov so tisto, kar obravnava preostanek te strani.

A4 PDF page produced by HotPDF showing Chinese, Japanese, and Korean text stacked into vertical columns that read from right to left
A single A4 page with Chinese, Japanese, and Korean text in vertical columns, generated from one Delphi procedure.

Stikalo se nahaja v SetFont

Navpična postavitev ni lastnost strani ali dokumenta. Je lastnost pisave, s katero rišete, vklopite pa jo s petim argumentom metode 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

Ker zastavica pripada pisavi, med vodoravnim in navpičnim pisanjem preklapljate preprosto s ponovnim klicem SetFont z drugačnim zadnjim argumentom. Stran lahko vsebuje vodoraven naslov na vrhu in navpično besedilo pod njim, HotPDF pa ohranja oba načina ločeno za vsak objekt pisave in ne za celotno stran. To omogoča mešane postavitve brez posebnega upravljanja načinov z vaše strani: vsak TextOut po navpičnem klicu SetFont se zloži navpično, vsak TextOut po vodoravnem pa poteka vzdolž izhodiščne črte, pri čemer velja zadnji klic SetFont.

Četrti argument je nabor znakov Windows (charset), enak kot pri vodoravnem klicu. Posredovanje DEFAULT_CHARSET omogoča sistemu razreševanje glifov na niz, kar je pomembno, saj navpični dokument pogosto meša pisave. Vse ostalo glede pisave še vedno velja: nameščena mora biti na računalniku za gradnjo, skoraj vedno pa boste želeli nastaviti FontEmbedding := True, da se CJK glifi pravilno izrišejo tudi pri bralcu, ki nima nameščene pisave Arial Unicode MS.

En klic TextOut je en stolpec

Ko je aktivna navpična pisava, klic TextOut niza ne širi več vodoravno od podane točke. Prvi znak postavi na vrh, preostale pa naniza navzdol, pri čemer se za vsak glif pomakne za višino vrstice pisave. Posredovani X določa stolpec, posredovani Y pa začetek vrha stolpca. Za postavitev dejanskega besedila zato izvedete en klic TextOut na stolpec in med klici premikate X v levo, saj se stolpci CJK berejo z desne proti levi.

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;

Pesem se bere tako, kot se mora: najprej desni stolpec od zgoraj navzdol, nato pa se oko pomakne levo na naslednji stolpec. Nič v samem nizu ne kodira tega vrstnega reda. Kodirate ga v koordinatah X, ki jih zmanjšujete od klica do klica. Če smer zgrešite, bo vrstni red verzov obrnjen, kar je najpogostejša napaka pri prenosu vodoravne postavitve v navpično.

Koordinate: navzdol po strani, z desne proti levi

V igri sta dve osi, ki vlečeta v nasprotnih smereh, zato je pomembno biti natančen. HotPDF meri od spodnjega levega kota strani, pri čemer Y narašča navzgor, v točkah. Navpični stolpec se zato začne pri visoki vrednosti Y (blizu vrha lista), znaki pa se spuščajo od tam, ko HotPDF za vsakega od them odšteje višino vrstice. Ta začetni Y nastavite enkrat na stolpec, komponenta pa poskrbi za spuščanje.

Vodoravno os upravljate ročno. Vsak stolpec ima svoj X, zaporedni stolpci pa se premikajo k manjšim vrednostim X, saj je smer branja z desne proti levi. Razmen ritem je, da izberete X najbolj desnega stolpca in nato za vsak naslednji klic odštejete fiksni razmik med stolpci, kot to počne primer z ColGap. Razmik izberete sami; če je preozek, se sosednji stolpci stikajo, če je preširok, pa blok deluje redek. Za besedilo velikosti 12 do 18 točk je razmik, ki je nekoliko večji od velikosti pisave, udoben za branje.

Glifi, ki morajo spremeniti obliko

Nekaj znakov ni le preprosto zavrtenih različic njihove vodoravne oblike, temveč jih je treba nadomestiti, ko se besedilo obrne navpično. Znak, ki ga HotPDF ureja namesto vas, je japonski znak za podaljšan zvok (U+30FC), ki se pojavlja v besedah katakane, kot je コーヒー. Izrisan vodoravno je kratek pomišljaj na izhodiščni črti. Če bi ta isti glif zložili v navpični stolpec, bi ležal vodoravno čez stolpec, kar je napačno: v navpični japonščini ta znak postane navpična poteza, ki povezuje znaka, med katerima stoji. HotPDF zazna znak U+30FC in ga izriše kot navpično črto (U+007C), tako da je znak za dolgi samoglasnik obrnjen v pravo smer brez vašega posredovanja.

Ta ena sama zamenjava rešuje težavo, pri kateri večina preprostih implementacij spodleti, vendar je koristno vedeti, kje je splošna težava globlja. Celotna navpična tipografija prav tako zavrti latinske črke in zahodna ločila za devetdeset stopinj, premakne majhne znake kana ter prerazporedi oklepaje in vejice v njihove navpične oblike. Popolna izvedba tega živi v navpičnih funkcijah OpenType pisave in ne v fiksnih pravilih. HotPDF lahko uporabi te funkcije, ko jih pisava vsebuje: navpične alternative (funkciji GSUB vert in vrt2) ter navpični kerning (iskanji GPOS vkrn in vpal) se uporabijo na navpični poti le, če jih aktivna pisava dejansko definira. Za mešano besedilo CJK v eni široko dostopni pisavi, kot je Arial Unicode MS, vgrajeno upravljanje U+30FC in enakomeren korak višine vrstice zadostujeta za pravilne in berljive stolpce. Funkcije OpenType pa postanejo pomembne, ko preidete na pisavo, zasnovano za natančno navpično stavljenje, in želite njeno izvorno pozicioniranje znakov kana ter razmike med glifi.

Mešanje pisav in usmeritev na isti strani

Resnični dokumenti so redko enoviti. Navpična japonska stran lahko vsebuje vodoraven angleški napis, številko strani na dnu ali blok korejščine, postavljen navpično poleg japonščine. Ker je navpična zastavica stikalo na ravni pisave, jih sestavljate z izmeničnimi klici SetFont in ne z upravljanjem stanja celotne strani. Nastavite vodoravno pisavo, zapišite tekočo glavo in številko strani, nastavite navpično pisavo, razporedite stolpce ter znova nastavite vodoravno pisavo za nogo dokumenta. Vsako območje prevzame način zadnjega klica SetFont, zato je edina potrebna disciplina ta, da ga pokličete vsakič, ko spremenite smer.

Ena podrobnost, na katero morate paziti pri mešanju pisav: kitajski, japonski in korejski ideogrami so skoraj kvadratni in se zlagajo z enakomernim korakom, latinski zapisi, vgrajeni v navpični stolpec, pa nimajo tega enotnega napredovanja. Če znotraj sicer navpičnega besedila potrebujete nekaj latinskih besed, se premišljeno odločite, ali naj bodo zavrtene navpično navzdol po stolpcu ali postavljene vodoravno kot kratek vložek, ter ta del pozicionirajte z lastnim klicem TextOut, namesto da ga prepustite navpičnemu koraku za ideograme. Obravnava mešanega zapisa kot samostojnega problema ohranja ritem stolpca nedotaknjen.

Od vzorca do produkcije

Deli so majhni in se sestavljajo predvidljivo. Vklopite navpično zastavico v SetFont, izvedite en klic TextOut na stolpec iz fiksnega vrha Y ter zmanjšujte koordinato X med klici, da se stolpci berejo z desne proti levi. Vgradite pisavo, da znaki CJK preživijo pot do bralčevega računalnika, in prepustite komponenti upravljanje znaka za dolgi samoglasnik U+30FC ter, kjer jih pisava podpira, navpičnih funkcij OpenType. Od tam naprej je prilagoditev postavitve za produkcijo predvsem aritmetika: izpeljava položajev stolpcev X iz izmerjene širine bloka, deljenje dolgih besedil v stolpce, ki ustrezajo višini strani, ter rezervacija prostora za vodoravne glave in številke strani.

Za širše področje besedila in pisav, na katerem to temelji, vključno z vodoravnimi pravili za TextOut in registracijo pisav Unicode, si oglejte večjezični primer Hello World in vzorec za TextOut. Navpično stikalo SetFont in klici TextOut, prikazani tukaj, so del komponente HotPDF Component za Delphi in C++Builder.