Technický článek

Vertikální CJK text v PDF pomocí Delphi a HotPDF

Položte na stůl stránku japonského románu a první věc, které si všimnete, je, že text běží dolů ve sloupci, nikoli napříč řádkem, a že sloupce postupují od pravého okraje listu směrem doleva. Čtenáři, který na tom vyrostl, přijde horizontální text tak trochu neosobní (klinický). Inženýrský problém spočívá v tom, že formát PDF, stejně jako téměř každý digitální textový systém, byl postaven na horizontální účaří (baseline), která roste zleva doprava, a záznam obsahu (content stream) nemá žádný koncept pro instrukci typu „tento odstavec místo toho napiš dolů“. Takže když aplikace v Delphi musí vyprodukovat certifikát, báseň, kousek značení nebo tradičně formátovaný právní dokument pro tchajwanského, japonského nebo korejského čtenáře, musí se rozvržení poskládat ručně: jeden znak pod druhý, jeden sloupec nalevo od předchozího

HotPDF vám nabízí přepínač (switch), který účetnictví o každém znaku obstará za vás. Font, který nastavíte, s sebou nese příznak (flag) IsVertical a jakmile je zapnutý, jediné volání TextOut poskládá celý řetězec do svislého sloupce místo toho, aby ho vedlo po horizontální účaří (baseline). Umístění sloupců, pořadí zprava doleva a jedno tiše důležité nahrazení znaku – to je přesně to, čím se zabývá zbytek této stránky

A4 PDF page produced by HotPDF showing Chinese, Japanese, and Korean text stacked into vertical columns that read from right to left
Jediná stránka A4 s čínským, japonským a korejským textem ve svislých sloupcích, vygenerovaná z jedné procedury Delphi.

Přepínač žije v SetFont

Vertikální rozložení není vlastností stránky ani celého dokumentu. Je to vlastnost písma (fontu), kterým kreslíte, a zapínáte ji pátým argumentem metody 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

Protože příznak jede s písmem, přepínáte mezi vodorovným a svislým způsobem psaní jednoduše tím, že znovu zavoláte SetFont s jiným posledním argumentem. Stránka může mít napříč horní částí vodorovný nadpis a pod ním svislý hlavní text. HotPDF udržuje tyto dva režimy oddělené podle objektu fontu (per font object) spíše než podle stránky (per page). Právě to umožňuje smíšená rozložení bez nutnosti jakéhokoliv speciálního ošetřování režimů z vaší strany: každý TextOut po vertikálním volání SetFont se vrství shora dolů (stacks), každý TextOut po tom horizontálním běží podél účaří a poslední volání SetFont má vždy přednost (vyhrává)

Čtvrtým argumentem je znaková sada (character set) Windows, což je ten samý parametr, jaký přebírá horizontální volání. Předání DEFAULT_CHARSET ponechává systému vyřešení glyphů řetězec po řetězci, což je zde důležité, protože vertikální dokument často míchá různá písma (scripts). Vše ostatní týkající se fontu stále platí: musí být nainstalován na počítači, na kterém probíhá kompilace, a téměř vždy budete chtít použít FontEmbedding := True, aby soubor na počítači čtenáře (který písmo Arial Unicode MS možná nikdy neměl) vykreslil úplně stejné glyphy CJK (čínština, japonština, korejština)

Jedno volání TextOut je jeden sloupec

Se zapnutým vertikálním fontem volání TextOut již neroztahuje svůj řetězec z daného bodu do šířky. Posadí první znak na vršek a se zbytkem schází přímo dolů, postupujíc u každého piktogramu (glyph) o výšku řádku fontu. Souřadnice X, kterou předáte, zafixuje polohu sloupce; souřadnice Y, kterou předáte, určuje, kde nahoře sloupec začíná. Abyste rozvrhli skutečnou pasáž textu, vydáte tedy jedno volání TextOut pro každý sloupec a s každým voláním postoupíte v ose X směrem doleva, protože sloupce CJK se čtou zprava doleva

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ň se pak čte tak, jak se sluší a patří: nejprve sloupec nejvíce vpravo, shora dolů, a pak oko přeskočí doleva na další sloupec. V samotném řetězci (string) nic z toho pořadí kódováno není. Kódujete ho do souřadnic X tak, že je s každým dalším voláním zmenšujete. Zmýlíte-li se a uděláte směr obráceně, verš nakonec vyjde naopak, což je vlastně ta jediná a nejčastější chyba, které se lidé dopouštějí, když předělávají horizontální rozvržení (layout) na vertikální

Souřadnice: dolů po stránce, zprava doleva

Ve hře jsou dvě osy, které táhnou opačnými směry, takže se vyplatí být precizní. HotPDF měří vše od levého dolního rohu stránky (bottom-left corner) přičemž osa Y roste směrem vzhůru a udává se v tiskových bodech (points). Vertikální sloupec proto začíná na vysokém čísle Y (těsně u vršku stránky) a znaky odtamtud sestupují směrem dolů, jak z něj knihovna HotPDF odčítá výšku řádku pro každý z nich. Počáteční Y nastavíte u každého sloupce jenom jednou a daná komponenta (HotPDF) následný sestup zvládne sama

Horizontální osa je tou, kterou řídíte manuálně vy sami. Každý sloupec sedí na své vlastní souřadnici X, po sobě jdoucí sloupce kráčejí do vždy menších hodnot pro X (skutečné souřadnice doleva), poněvadž pořadí samotného čtení obvykle probíhá přesně zprava doleva (right to left). Praktickým rytmem pro tuto akci (sensible rhythm) bývá prvotní zvolení X podle nejpravějšího možného sloupce, z kterého u každého následujícího z volání následně rovnou fixní mezeru pro sloupec odečtete přesně tak, jak ukazuje náš příklad použitím konstanty ColGap. Jaká ta mezera bude je ale již pouze na vás. Příliš těsná totiž způsobí to, že sousedící (adjacent columns) sloupce se začnou dotýkat a přílišná volnost blok opticky vyprázdní. Děláte-li (For body text) hlavní řádky (12 do 18 bodů), pak mezera těsně (a little larger) převyšující přímo velikost textu, se vizuálně čte celkově úplně (comfortably) s největší přirozeností

Znaky (glyphy), které musí změnit tvar

Několik znaků není pouhými otočenými kopiemi svých horizontálních verzí; při vertikálním textu musí být nahrazeny. Tím, který za vás HotPDF vyřeší, je japonská značka pro prodloužení zvuku (U+30FC) – čárka pro dlouhou samohlásku, která se objevuje ve slovech z katakany, jako je コーヒー. Při horizontálním psaní jde o krátkou pomlčku na účaří. Pokud byste tento stejný glyph vložili do sloupce, ležel by naplocho přes sloupec, což je špatně: ve vertikální japonštině se tato značka mění na svislou čáru spojující dva znaky, mezi kterými se nachází. HotPDF detekuje znak U+30FC ve vertikální cestě a vykreslí jej jako svislou čáru (U+007C), takže značka dlouhé samohlásky směřuje správně bez jakékoliv práce z vaší strany

Toto jediné nahrazení pokrývá případ, který rozbíjí většinu naivních implementací. Je však dobré vědět, kam tento obecný problém sahá hlouběji. Plnohodnotná vertikální typografie také rotuje latinská písmena a západní interpunkci o devadesát stupňů, posouvá malou kanu a přemisťuje závorky a čárky do jejich vertikálních podob. Kompletní implementace tohoto chování se nachází ve vertikálních funkcích formátu OpenType samotného fontu, nikoli v pevných pravidlech programu. HotPDF dokáže tyto funkce využít, pokud je font obsahuje: vertikální alternativy (funkce GSUB vert a vrt2) a vertikální kerning (vyhledávání GPOS vkrn a vpal) jsou volitelné (opt-in) a uplatňují se ve vertikální cestě pouze tehdy, když je aktivní font skutečně definuje. Pro smíšený CJK text v jednom fontu se širokým pokrytím (např. Arial Unicode MS) stačí vestavěné ošetření U+30FC a rovnoměrný krok výšky řádku k vytvoření správných a čitelných sloupců. Funkce OpenType začnou být důležité ve chvíli, kdy přejdete k fontu navrženému pro jemnou vertikální sazbu a chcete využít jeho nativní pozicování kany a mezery mezi glyphy

Míchání písem a orientací na jedné straně

Skutečné dokumenty jsou málokdy čistě jednolité. Vertikální japonská stránka může obsahovat horizontální anglický popisek, číslo stránky dole nebo blok korejštiny sázený vertikálně vedle japonštiny. Protože je vertikální příznak přepínačem na úrovni fontu, skládáte tyto prvky střídáním volání SetFont namísto správy nějakého celostránkového stavu. Nastavte horizontální font, zapište průběžné záhlaví (running header) a číslo stránky, nastavte vertikální font, rozložte sloupce, a znovu nastavte horizontální font pro zápatí. Každá oblast přebírá režim z posledního volání SetFont, takže jedinou potřebnou disciplínou je zavolat jej, kdykoli změníte směr

Při míchání písem je třeba naplánovat ještě jeden detail: čínské, japonské a korejské ideogramy jsou téměř čtvercové a skládají se s rovnoměrným krokem, ale latinka vložená do vertikálního sloupce tento jednotný posun nemá. Pokud potřebujete do jinak vertikálního textu vložit pár latinských slov, promyšleně se rozhodněte, zda by měla být otočena a běžet dolů sloupcem, nebo zda je necháte vzpřímená jako krátkou horizontální vložku. Tento fragment pak umístěte vlastním voláním TextOut namísto toho, abyste ho nechali "svézt se" na vertikálním kroku určeném pro ideogramy. Pokud k této smíšené části přistoupíte jako k samostatnému problému umístění, udržíte tím rytmus sloupce neporušený

Od ukázky k produkčnímu nasazení

Dílky jsou to malé a skládají se předvídatelně. Zapněte vertikální příznak v SetFont, vydávejte jedno volání TextOut na sloupec z pevné horní souřadnice Y a při každém dalším volání zmenšujte X, aby se sloupce četly zprava doleva. Zahrňte do PDF samotný font (embed), aby glyphy CJK přežily cestu k počítači čtenáře, a nechte komponentu, ať se postará o značku dlouhé samohlásky U+30FC a – pokud to font podporuje – i o vertikální funkce OpenType. Odtud je nasazení rozvržení do produkce už většinou jen aritmetika: odvození pozic X sloupců ze změřené šířky bloku, rozdělování dlouhých pasáží do sloupců tak, aby se vešly na výšku stránky, a vyhrazení místa pro horizontální záhlaví a čísla stránek (folios)

Širší souvislosti s textem a fonty, na kterých toto vše staví (včetně konvencí horizontálního volání TextOut a registrace Unicode fontů), najdete ve vícejazyčném příkladu Hello World a v ukázce k použití TextOut. Vertikální přepínač v SetFont a volání TextOut, která jsou zde uvedena, tvoří součást komponenty HotPDF pro Delphi a C++Builder