Technical Article

CJK vertikalus tekstas PDF failuose su Delphi ir HotPDF

Paimkite japonišką romaną ir pirmas dalykas, kurį pastebėsite – tekstas eina žemyn stulpeliu, o ne išilgai eilutės, o patys stulpeliai slenka nuo dešiniojo lapo krašto kairiojo link. Tokioje kultūroje užaugusiam skaitytojui horizontalus tekstas atrodo šiek tiek neįprastas ar pernelyg oficialus. Inžinerinė problema yra ta, kad PDF, kaip ir beveik kiekviena skaitmeninio teksto sistema, buvo sukurta remiantis horizontalia bazine linija, kuri driekiasi iš kairės į dešinę, o turinio srautas neturi jokio supratimo apie užduotį „vietoj to rašyti šią pastraipą žemyn“. Todėl kai Delphi programai reikia sugeneruoti sertifikatą, eilėraštį, iškabą ar tradicinio formato teisinį dokumentą Taivano, Japonijos ar Korėjos skaitytojui, maketą tenka rinkti rankomis: dėlioti po vieną simbolį žemiau kito, o naujus stulpelius – į kairę nuo ankstesnių.

HotPDF suteikia jungiklį, kuris atlieka šį simbolių išdėstymo darbą už jus. Jūsų nustatytas šriftas turi IsVertical vėliavėlę, ir kai ji yra įjungta, vienas TextOut iškvietimas išdėsto visą eilutę į vertikalų stulpelį, užuot rašęs ją išilgai bazinės linijos. Stulpelių išdėstymas, tvarka iš dešinės į kairę ir vienas mažas, bet svarbus glifų pakeitimas yra tai, ką išsamiai aptarsime šiame puslapyje.

HotPDF sugeneruotas A4 PDF puslapis, rodantis kinų, japonų ir korėjiečių kalbų tekstą, išdėstytą vertikaliais stulpeliais, kurie skaitomi iš dešinės į kairę
Vienas A4 formato puslapis su kinų, japonų ir korėjiečių kalbų tekstu vertikaliuose stulpeliuose, sugeneruotas naudojant vieną Delphi procedūrą.

Jungiklis yra SetFont metode

Vertikalus maketas nėra puslapio ar dokumento savybė. Tai yra šrifto, kuriuo piešiate, savybė, ir ją įjungiate naudodami penktąjį SetFont argumentą:

// 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

Kadangi vėliavėlė yra priskirta šriftui, pereiti nuo horizontalaus prie vertikalaus rašymo galite tiesiog iš naujo iškviesdami SetFont su kitu paskutiniu argumentu. Puslapyje viršuje gali būti horizontali antraštė, o po ja – vertikalus tekstas, ir HotPDF šiuos du režimus valdo šrifto objekto, o ne puslapio lygmeniu. Tai leidžia kurti mišrius maketus be jokio specialaus režimų valdymo iš jūsų pusės: kiekvienas TextOut po vertikalaus SetFont išdėsto tekstą vertikaliai, o po horizontalaus – rašo išilgai bazinės linijos, ir laimi paskutinis SetFont.

Ketvirtasis argumentas yra Windows simbolių rinkinys – tas pats, kurį naudoja ir horizontalus iškvietimas. Perdavus DEFAULT_CHARSET, sistema leidžia parinkti glifus pagal eilutę, kas yra svarbu, nes vertikaliame dokumente dažnai maišomi skirtingi rašmenys. Visi kiti šrifto nustatymai išlieka galioti: jis turi būti įdiegtas kūrimo kompiuteryje, o FontEmbedding := True parinktis yra beveik visada rekomenduojama, kad failas atvaizduotų tuos pačius CJK glifus skaitytojui, kuris neturi Arial Unicode MS.

Vienas TextOut iškvietimas yra vienas stulpelis

Kai aktyvus vertikalus šriftas, TextOut iškvietimas nebeištęsia eilutės horizontaliai nuo nurodyto taško. Jis padeda pirmąjį simbolį viršuje ir leidžiasi žemyn, kiekvienam glifui pasislinkdamas per šrifto eilutės aukštį. Perduotas X apibrėžia stulpelį, o Y – vietą, kur prasideda stulpelio viršus. Norėdami išdėstyti ilgesnį tekstą, atliekate po vieną TextOut iškvietimą kiekvienam stulpeliui ir mažinate X koordinatę tarp iškvietimų, nes CJK stulpeliai skaitomi iš dešinės į kairę.

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;

Eilėraštis skaitomas taip, kaip ir turėtų būti: pirmiausia dešiniausias stulpelis iš viršaus į apačią, o tada akys nukrypsta į kairę prie kito stulpelio. Pati eilutė savyje neturi jokios informacijos apie šią tvarką. Jūs ją užkoduojate X koordinatėse, mažindami jas su kiekvienu iškvietimu. Jei sumaišysite kryptį, eilėraštis bus atspausdintas atvirkščiai – tai yra pati dažniausia klaida perkeliant horizontalų maketą į vertikalų.

Koordinatės: žemyn puslapiu, iš dešinės į kairę

Čia veikia dvi ašys, kurios traukia priešingomis kryptimis, todėl svarbu išlaikyti tikslumą. HotPDF matuoja nuo apatinio kairiojo puslapio kampo taškais (points), o Y didėja į viršų. Todėl vertikalus stulpelis prasideda ties didele Y reikšme (netoli puslapio viršaus), o simboliai leidžiasi žemyn, nes HotPDF atima eilutės aukštį kiekvienam iš jų. Jūs nustatote tą pradinį Y vieną kartą stulpeliui, o komponentas pasirūpina nusileidimu.

Horizontali ašis yra ta, kurią valdote rankiniu būdu. Kiekvienas stulpelis turi savo X koordinatę, o paskesni stulpeliai pereina prie mažesnių X reikšmių, nes skaitymo kryptis yra iš dešinės į kairę. Logiškas sprendimas – pasirinkti dešiniausio stulpelio X, o tada atimti fiksuotą tarpą tarp stulpelių kiekvienam kitam iškvietimui, kaip pavyzdyje daroma su ColGap. Tarpą galite pasirinkti patys: jei jis per mažas, gretimi stulpeliai liesis, jei per didelis – tekstas atrodys per daug išsklaidytas. Pagrindiniam 12–18 taškų šrifto tekstui tarpas, kuris yra šiek tiek didesnis už šrifto dydį, leidžia skaityti patogiai.

Glifai, kurie turi pakeisti formą

Keli simboliai nėra tiesiog pasuktos horizontalios savo versijos; jie turi būti pakeisti kitais, kai tekstas tampa vertikalus. Vienintelis, kurį HotPDF sutvarko už jus, yra japonų kalbos ilgojo garso ženklas (U+30FC), naudojamas katakana žodžiuose, pavyzdžiui, コーヒー. Nubraižytas horizontaliai, jis yra trumpas brūkšnelis ant bazinės linijos. Jei tą patį glifą tiesiog padėtumėte stulpelyje, jis gulėtų skersai stulpelio, kas būtų neteisinga: vertikaliame japonų kalbos rašte šis ženklas virsta vertikaliu brūkšniu, jungiančiu du simbolius, tarp kurių jis stovi. HotPDF atpažįsta U+30FC simbolį vertikaliame tekste ir atvaizduoja jį kaip vertikalų brūkšnį (U+007C), todėl ilgojo garso ženklas nukreipiamas teisinga kryptimi be jokių jūsų pastangų.

Šis vienas pakeitimas išsprendžia problemą, kuri sugadina daugelį paprastų sprendimų, tačiau verta žinoti, kad ši tema yra kur kas gilesnė. Pilnavertė vertikali tipografija taip pat pasuka lotyniškas raides ir vakarų skyrybos ženklus 90 laipsnių kampu, paslenka mažąsias kana raides, o skliaustelius bei kablelius perkelia į jų vertikalias formas. Pilnas viso to palaikymas yra šrifto OpenType vertikaliosiose funkcijose, o ne fiksuotose taisyklėse. HotPDF gali naudoti šias funkcijas, jei šriftas jas palaiko: vertikalieji pakaitai (vert ir vrt2 GSUB funkcijos) ir vertikalusis tarpų derinimas (vkrn ir vpal GPOS paieškos) pritaikomi tik tada, kai aktyvus šriftas jas apibrėžia. Mišriam CJK tekstui, naudojant vieną plataus palaikymo šriftą, pavyzdžiui, Arial Unicode MS, pakanka integruoto U+30FC apdorojimo ir tolygaus eilutės aukščio žingsnio, kad gautumėte teisingus, įskaitomus stulpelius. OpenType funkcijos tampa svarbios tai, kai pereinate prie šrifto, skirto tiksliam vertikaliam braižymui, ir norite naudoti jo savitą kana padėties nustatymą bei tarpus tarp glifų.

Skirtingų raštų ir krypčių maišymas viename puslapyje

Tikri dokumentai retai būna vienalyčiai. Vertikaliame japonų kalbos puslapyje gali būti horizontali anglų kalbos išnaša, puslapio numeris apačioje arba vertikalaus korėjiečių teksto blokas šalia japonų teksto. Kadangi vertikali vėliavėlė yra šrifto lygio jungiklis, juos sujungiate tiesiog kaitaliodami SetFont iškvietimus, o ne valdydami kokią nors viso puslapio būseną. Nustatykite horizontalų šriftą, parašykite antraštę, nustatykite vertikalų šriftą, išdėstykite stulpelius, vėl nustatykite horizontalų šriftą poraštei. Kiekviena sritis perima paskutinio SetFont režimą, todėl vienintelė taisyklė – iškviesti jį kiekvieną kartą, kai keičiate kryptį.

Viena detalė, kurią reikia numatyti maišant rašmenis: kinų, japonų ir korėjiečių ideogramos yra beveik kvadratinės ir išdėstomos vienodais žingsniais, tačiau lotyniški žodžiai, įterpti į vertikalų stulpelį, neturi tokio vienodo žingsnio. Jei jums reikia kelių lotyniškų žodžių vertikaliame tekste, sąmoningai nuspręskite, ar jie turėtų būti pasukti ir eiti žemyn stulpeliu, ar pateikiami horizontaliai kaip nedidelis įterptas fragmentas, ir nustatykite šios dalies vietą atskiru TextOut iškvietimu, užuot leidę jai naudoti ideogramoms skirtą vertikalų žingsnį. Dvikrypčio teksto traktavimas kaip atskiros problemos išlaiko stulpelio ritmą nepakeistą.

Nuo pavyzdžio iki realaus naudojimo

Visi šie elementai yra nedideli ir derinami nuspėjamai. Įjunkite vertikalią vėliavėlę SetFont metode, atlikite po vieną TextOut iškvietimą stulpeliui nuo fiksuoto viršutinio Y taško ir mažinkite X tarp iškvietimų, kad stulpeliai būtų skaitomi iš dešinės į kairę. Įterpkite šriftą, kad CJK glifai būtų teisingai atvaizduojami vartotojo kompiuteryje, ir leiskite komponentui sutvarkyti U+30FC ilgojo garso ženklą bei (jei šriftas palaiko) OpenType vertikaliąsias funkcijas. Toliau maketo paruošimas gamybai yra tiesiog aritmetika: stulpelių X padėties skaičiavimas pagal nustatytą bloko plotį, ilgų pastraipų skaidymas į stulpelius, kurie telpa puslapyje, ir vietos skyrimas horizontalioms antraštėms bei puslapių numeriams.

Daugiau informacijos apie tekstą ir šriftus, kuriais remiasi šis metodas (įskaitant horizontalias TextOut konvencijas ir Unicode šriftų registraciją), rasite daugiakalbiame Hello World pavyzdyje ir TextOut pavyzdyje. Čia parodytas vertikalus SetFont jungiklis ir TextOut iškvietimai yra HotPDF komponento, skirto Delphi ir C++Builder, dalis.