Articol tehnic

Text vertical CJK în PDF cu Delphi și HotPDF

Așezați un roman japonez pe pagină și primul lucru pe care îl observați este că textul coboară pe coloană, nu traversează rândul, și că paginile de coloane înaintează de la marginea din dreapta a colii spre stânga. Un cititor crescut cu asta găsește textul orizontal ușor clinic. Problema de inginerie este că PDF-ul, ca aproape orice sistem digital de text, a fost construit în jurul unei linii de bază orizontale care crește de la stânga la dreapta, iar un flux de conținut nu are nicio noțiune de „scrie acest paragraf în jos”. Așa că, atunci când o aplicație Delphi trebuie să producă un certificat, o poezie, o firmă sau un document juridic în format tradițional pentru un cititor taiwanez, japonez sau coreean, aranjamentul trebuie asamblat de mână: un caracter sub celălalt, o coloană la stânga celei dinainte

HotPDF vă oferă un comutator care face contabilitatea pe caracter în locul dumneavoastră. Fontul pe care îl setați poartă un indicator IsVertical, iar odată ce acesta este pornit, un singur apel TextOut stivuiește un întreg șir într-o coloană verticală, în loc să îl întindă de-a lungul unei linii de bază. Plasarea coloanelor, ordinea de la dreapta la stânga și o substituire de glifă discretă, dar importantă, sunt lucrurile pe care le parcurge restul acestei pagini

Pagină PDF A4 produsă de HotPDF, care arată text chinezesc, japonez și coreean stivuit în coloane verticale ce se citesc de la dreapta la stânga
O singură pagină A4 cu text chinezesc, japonez și coreean în coloane verticale, generată dintr-o singură procedură Delphi

Comutatorul stă pe SetFont

Aranjamentul vertical nu este o proprietate a paginii sau a documentului. Este o proprietate a fontului cu care desenați, iar îl porniți cu al cincilea argument al lui SetFont:

// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// Al 5-lea argument comută fontul curent în mod vertical.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // orizontal
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True);  // vertical

Fiindcă indicatorul călătorește pe font, comutați între scrierea orizontală și cea verticală pur și simplu apelând din nou SetFont cu un alt ultim argument. O pagină poate purta un titlu orizontal în partea de sus și text de corp vertical dedesubt, iar HotPDF ține cele două moduri separate pe obiect de font, nu pe pagină. Asta face posibile aranjamentele mixte fără nicio administrare specială de mod din partea dumneavoastră: fiecare TextOut de după un SetFont vertical stivuiește, fiecare TextOut de după unul orizontal merge de-a lungul liniei de bază, iar ultimul SetFont câștigă

Al patrulea argument este setul de caractere Windows, același pe care îl primește un apel orizontal. Transmiterea lui DEFAULT_CHARSET lasă sistemul să rezolve glifele pentru fiecare șir, ceea ce contează aici pentru că un document vertical amestecă adesea scrieri. Tot ce ține de font se aplică în continuare: trebuie să fie instalat pe mașina de compilare și aproape întotdeauna veți vrea FontEmbedding := True, ca fișierul să randeze aceleași glife CJK pe un cititor care nu a avut niciodată Arial Unicode MS

Un apel TextOut înseamnă o coloană

Cu un font vertical activ, un apel TextOut nu își mai întinde șirul lateral față de punctul dat. El plantează primul caracter în vârf și îl duce pe restul drept în jos, avansând cu înălțimea de rând a fontului pentru fiecare glifă. X-ul pe care îl transmiteți fixează coloana; Y-ul pe care îl transmiteți fixează locul unde începe vârful coloanei. Ca să așezați un pasaj real, emiteți deci câte un TextOut pentru fiecare coloană și pășiți cu X spre stânga între apeluri, pentru că paginile de coloane CJK se citesc de la dreapta la stânga

var
  Pdf: THotPDF;
const
  ColTop = 760;   // y-ul primei glife din fiecare coloană (puncte)
  ColGap = 28;    // distanța orizontală între coloane
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VerticalText.pdf';
    Pdf.FontEmbedding := True;       // încorporează fontul CJK pentru randare portabilă
    Pdf.BeginDoc;
    Pdf.CurrentPage.Size := psA4;

    // Întâi un titlu orizontal, în modul obișnuit de scriere.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
    Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');

    // Comută fontul în mod vertical; fiecare TextOut de mai jos stivuiește acum.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);

    // Coloanele înaintează de la dreapta la stânga, deci X scade la fiecare apel.
    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;

Poezia se citește așa cum trebuie: întâi coloana cea mai din dreapta, de sus în jos, apoi ochiul sare la stânga, spre coloana următoare. Nimic din șirul în sine nu codifică acea ordine. Dumneavoastră o codificați în coordonatele X, scăzându-le de la un apel la altul. Greșiți direcția și versurile ies inversate, ceea ce este cea mai frecventă greșeală la portarea unui aranjament orizontal spre unul vertical

Coordonate: în jos pe pagină, de la dreapta la stânga

Sunt în joc două axe și trag în direcții opuse, așa că merită să fim preciși. HotPDF măsoară din colțul din stânga jos al paginii, cu Y crescând în sus, în puncte. O coloană verticală începe deci la un Y mare (lângă partea de sus a colii), iar caracterele coboară de acolo, pe măsură ce HotPDF scade o înălțime de rând pentru fiecare. Setați acel Y de pornire o dată pentru fiecare coloană, iar componenta se ocupă de coborâre

Axa orizontală este cea pe care o conduceți manual. Fiecare coloană stă la propriul X, iar coloanele succesive pășesc spre valori mai mici de X, pentru că ordinea de citire este de la dreapta la stânga. Un ritm rezonabil este să alegeți X-ul coloanei celei mai din dreapta, apoi să scădeți un pas fix de coloană pentru fiecare apel următor, așa cum face exemplul cu ColGap. Pasul este alegerea dumneavoastră; prea strâns și coloanele alăturate se ating, prea larg și blocul pare rarefiat. Pentru text de corp la 12 până la 18 puncte, un pas puțin mai mare decât dimensiunea fontului se citește confortabil

Glife care trebuie să își schimbe forma

Câteva caractere nu sunt pur și simplu copii rotite ale variantelor lor orizontale; ele trebuie substituite atunci când textul se întoarce pe verticală. Cel de care se ocupă HotPDF în locul dumneavoastră este semnul japonez de sunet prelungit (U+30FC), bara de vocală lungă care apare în cuvintele katakana precum コーヒー. Desenat orizontal, este o liniuță scurtă pe linia de bază. Dacă ați stivui aceeași glifă într-o coloană, ea ar sta culcată de-a latul coloanei, ceea ce este greșit: în japoneza verticală, semnul devine o trăsătură verticală care leagă cele două caractere între care stă. HotPDF detectează U+30FC pe calea verticală și îl randează ca bară verticală (U+007C), astfel încât semnul de vocală lungă să indice în direcția corectă fără vreun efort din partea dumneavoastră

Acea singură substituire acoperă cazul care strică majoritatea implementărilor naive, dar merită știut unde problema generală merge mai adânc. Tipografia verticală completă rotește și literele latine și punctuația occidentală cu nouăzeci de grade, deplasează kana mici și repoziționează parantezele și virgulele în formele lor verticale, iar o implementare completă a acestor lucruri stă în funcțiile OpenType verticale ale unui font, nu în reguli fixe. HotPDF poate folosi acele funcții atunci când fontul le poartă: alternativele verticale (funcțiile GSUB vert și vrt2) și kerningul vertical (căutările GPOS vkrn și vpal) sunt opționale, aplicate pe calea verticală doar când fontul activ chiar le definește. Pentru text CJK mixt într-un singur font cu acoperire largă, precum Arial Unicode MS, tratarea internă a lui U+30FC plus un pas uniform de înălțime de rând sunt de ajuns pentru a produce coloane corecte și lizibile; funcțiile OpenType contează când treceți la un font proiectat pentru culegere verticală fină și vreți poziționarea lui nativă a caracterelor kana și spațierea între glife

Amestecul de scrieri și orientări pe o singură pagină

Documentele reale sunt rareori pure. O pagină japoneză verticală poate purta o legendă orizontală în engleză, un număr de pagină în partea de jos sau un bloc de coreeană așezat vertical lângă cel japonez. Fiindcă indicatorul vertical este un comutator la nivel de font, compuneți aceste lucruri alternând apeluri SetFont, nu administrând vreo stare la nivel de pagină. Setați un font orizontal, scrieți antetul curent și numărul de pagină, setați un font vertical, așezați coloanele, setați din nou un font orizontal pentru un subsol. Fiecare regiune preia modul celui mai recent SetFont, deci singura disciplină cerută este să îl apelați ori de câte ori schimbați direcția

Un detaliu de planificat atunci când scrierile se amestecă: ideogramele chinezești, japoneze și coreene sunt aproape pătrate și se stivuiesc cu un pas uniform, dar secvențele latine încorporate într-o coloană verticală nu au acel avans uniform. Dacă aveți nevoie de câteva cuvinte latine în interiorul unui text altfel vertical, hotărâți intenționat dacă ele trebuie rotite ca să coboare pe coloană sau așezate drept, ca o mică inserție orizontală, și poziționați acel fragment cu propriul TextOut, în loc să îl lăsați să urmeze pasul vertical gândit pentru ideograme. Tratarea secvenței mixte ca pe o problemă de plasare de sine stătătoare păstrează intact ritmul coloanei

De la exemplu la producție

Piesele sunt mici și se compun previzibil. Porniți indicatorul vertical în SetFont, emiteți câte un TextOut pentru fiecare coloană de la un Y de vârf fix și scădeți X de la un apel la altul, ca acele coloane să se citească de la dreapta la stânga. Încorporați fontul, ca glifele CJK să supraviețuiască drumului până la mașina unui cititor, și lăsați componenta să se ocupe de semnul de vocală lungă U+30FC și, acolo unde fontul le acceptă, de funcțiile OpenType verticale. De acolo, ducerea aranjamentului în producție este mai ales aritmetică: deducerea pozițiilor X ale coloanelor dintr-o lățime de bloc măsurată, împărțirea pasajelor lungi în coloane care încap în înălțimea paginii și rezervarea de spațiu pentru antete orizontale și numere de pagină

Pentru suprafața mai amplă de text și fonturi pe care se sprijină toate acestea, inclusiv convențiile TextOut orizontale și înregistrarea fonturilor Unicode, vedeți exemplul multilingv Hello World și exemplul TextOut. Comutatorul vertical din SetFont și apelurile TextOut prezentate aici fac parte din HotPDF Delphi Component pentru Delphi și C++Builder