Technisch artikel

CJK-verticale tekst in PDF met Delphi en HotPDF

Zet een Japanse roman op de pagina en het eerste dat u opmerkt is dat de tekst de kolom af loopt in plaats van over de regel, en dat de kolommen van de rechterrand van het vel naar links opschuiven. Een lezer die erop is grootgebracht vindt horizontale tekst vaag klinisch. Het technische probleem is dat PDF, zoals vrijwel elk digitaal tekstsysteem, rond een horizontale basislijn is gebouwd die van links naar rechts groeit, en een content-stream heeft geen notie van "schrijf deze paragraaf in plaats daarvan naar beneden." Dus wanneer een Delphi-applicatie een certificaat, een gedicht, een stuk signalering of een traditioneel-formaats juridisch document moet produceren voor een Taiwanese, Japanse of Koreaanse lezer, moet de layout met de hand worden samengesteld: één teken onder het volgende, één kolom links van de vorige

HotPDF geeft u een schakelaar die de per-teken administratie voor u doet. Het lettertype dat u instelt draagt een IsVertical-vlag, en zodra die aan staat stapelt één TextOut-aanroep een hele string in een verticale kolom in plaats van hem langs een basislijn te laten lopen. De kolomplaatsing, de rechts-naar-links-volgorde, en één stil belangrijke glyph-substitutie zijn wat de rest van deze pagina doorwerkt

A4 PDF page produced by HotPDF showing Chinese, Japanese, and Korean text stacked into vertical columns that read from right to left
Eén A4-pagina met Chinese, Japanse en Koreaanse tekst in verticale kolommen, gegenereerd vanuit één Delphi-procedure.

De schakelaar leeft op SetFont

Verticale layout is geen eigenschap van de pagina of het document. Het is een eigenschap van het lettertype waarmee u tekent, en u zet het aan met het vijfde argument van 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

Omdat de vlag op het lettertype rijdt, schakelt u tussen horizontaal en verticaal schrijven eenvoudig door SetFont opnieuw aan te roepen met een ander laatste argument. Een pagina kan een horizontale titel bovenaan en verticale body-tekst eronder houden, en HotPDF houdt de twee modi apart per font-object in plaats van per pagina. Dat is wat gemengde layouts mogelijk maakt zonder speciale modus-afhandeling aan uw kant: elke TextOut na een verticale SetFont stapelt, elke TextOut na een horizontale loopt langs de basislijn, en de laatste SetFont wint

Het vierde argument is de Windows-tekenset, dezelfde die een horizontale aanroep neemt. DEFAULT_CHARSET doorgeven laat het systeem glyphs per string resolven, wat hier uitmaakt omdat een verticaal document vaak scripts mengt. Al het andere over het lettertype geldt nog steeds: het moet op de build-machine geïnstalleerd zijn, en u wilt vrijwel altijd FontEmbedding := True zodat het bestand dezelfde CJK-glyphs rendert op een reader die nooit Arial Unicode MS had

Eén TextOut-aanroep is één kolom

Met een verticaal lettertype actief spreidt een TextOut-aanroep zijn string niet langer zijwaarts vanuit het gegeven punt. Het plant het eerste teken bovenaan en loopt de rest recht naar beneden, per glyph opschuivend met de regelhoogte van het lettertype. De X die u doorgeeft fixeert de kolom; de Y die u doorgeeft fixeert waar de bovenkant van de kolom begint. Om een echte passage uit te leggen geeft u dus één TextOut per kolom en stapt u X linkswaarts tussen aanroepen, want CJK-kolommen worden rechts naar links gelezen

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;

Het gedicht leest zoals het hoort: de rechtse kolom eerst, van boven naar beneden, dan springt het oog links naar de volgende kolom. Niets aan de string zelf codeert die volgorde. U codeert hem in de X-coördinaten, ze aanroep na aanroep verkleinend. Krijg de richting achterstevoren en het vers komt omgekeerd uit, wat de ene meest voorkomende fout is bij het porten van een horizontale layout naar verticaal

Coördinaten: de pagina af, rechts naar links

Twee assen zijn in het spel en ze trekken in tegengestelde richtingen, dus het is de moeite waard om precies te zijn. HotPDF meet vanuit de linkerbenedenhoek van de pagina, met Y omhoog toenemend, in punten. Een verticale kolom begint dus op een hoge Y (dicht bij de bovenkant van het vel) en de tekens dalen vandaar terwijl HotPDF voor elk een regelhoogte aftrekt. U stelt die start-Y één keer per kolom in en de component handelt de afdaling af

De horizontale as is degene die u handmatig aandrijft. Elke kolom zit op zijn eigen X, en opeenvolgende kolommen stappen naar kleinere X-waarden omdat de leesvolgorde rechts naar links is. Een zinnig ritme is de X van de rechtse kolom te kiezen, dan voor elke volgende aanroep een vaste kolom-gap af te trekken, zoals het voorbeeld doet met ColGap. De gap is van u om te kiezen; te krap en aangrenzende kolommen raken elkaar, te los en het blok looks mager. Voor body-tekst op 12 tot 18 punten leest een gap iets groter dan de lettergrootte comfortabel

Glyphs die van vorm moeten veranderen

Enkele tekens zijn niet gewoon gedraaide kopieën van hun horizontale zelf; ze moeten worden gesubstitueerd wanneer de tekst verticaal wordt. Degene die HotPDF voor u afhandelt is het Japanse prolonged-sound-teken (U+30FC), de lange-klinker-streep die opduikt in katakana-woorden zoals コーヒー. Horizontaal getekend is het een korte streep op de basislijn. Als u diezelfde glyph in een kolom stapelde zou hij plat over de kolom liggen, wat fout is: in verticaal Japans wordt het teken een verticale streek die de twee tekens verbindt waartussen hij zit. HotPDF detecteert U+30FC in het verticale pad en rendert hem als een verticale balk (U+007C) zodat de lange-klinker-streep de goede kant op wijst zonder werk van u

Die enkele substitutie dekt het geval dat de meeste naïeve implementaties breekt, maar het is de moeite waard om te weten waar het algemene probleem dieper gaat. Volledige verticale typografie draait ook Latijnse letters en westerse leestekens negentig graden, verschuift kleine kana, en herpositioneert haakjes en komma's naar hun verticale vormen, en een complete implementatie daarvan leeft in de OpenType-verticale-features van een lettertype in plaats van in vaste regels. HotPDF kan die features tappen wanneer het lettertype ze draagt: verticale alternates (de vert- en vrt2-GSUB-features) en verticale kerning (vkrn- en vpal-GPOS-lookups) zijn opt-in, alleen langs het verticale pad toegepast wanneer het actieve lettertype ze werkelijk definieert. Voor gemengde CJK-tekst in één breed-dekkend gezicht zoals Arial Unicode MS is de ingebouwde U+30FC-afhandeling plus een gelijke regelhoogte-stap genoeg om correcte, leesbare kolommen te produceren; de OpenType-features doen ertoe wanneer u naar een lettertype voor fijne verticale zetting gaat en zijn native kana-positionering en inter-glyph-spatiëring wilt

Scripts en oriëntaties mengen op één pagina

Echte documenten zijn zelden puur. Een verticale Japanse pagina kan een horizontale Engelse caption dragen, een paginanummer langs de onderkant, of een blok Koreaans verticaal gezet naast het Japans. Omdat de verticale vlag een schakelaar op lettertypeniveau is, componeert u deze door SetFont-aanroepen af te wisselen in plaats van enige paginabrede staat te beheren. Stel een horizontaal lettertype in, schrijf de lopende header en folio, stel een verticaal lettertype in, leg de kolommen, stel opnieuw een horizontaal lettertype in voor een footer. Elke regio pikt de modus op van de meest recente SetFont, dus de enige vereiste discipline is hem aan te roepen wanneer u van richting verandert

Eén detail om voor te plannen wanneer scripts mengen: Chinese, Japanse en Koreaanse ideografieën zijn bijna vierkant en stapelen op een gelijke stap, maar Latijn dat in een verticale kolom ingebed loopt heeft die uniforme advance niet. Als u enkele Latijnse woorden binnen verder verticale tekst nodig heeft, beslis bewust of ze moeten worden gedraaid om de kolom af te lopen of rechtop gezet als een korte horizontale inset, en positioneer dat fragment met zijn eigen TextOut in plaats van hem te laten meerijden op de verticale stap die voor ideografieën bedoeld is. De gemengde run als zijn eigen plaatsingsprobleem behandelen houdt het kolomritme intact

Van voorbeeld naar productie

De stukken zijn klein en ze componeren voorspelbaar. Zet de verticale vlag aan in SetFont, geef één TextOut per kolom vanuit een vaste boven-Y, en verklein X over aanroepen zodat de kolommen rechts naar links lezen. Embed het lettertype zodat de CJK-glyphs de reis naar de machine van een lezer overleven, en laat de component de U+30FC lange-klinker-streep afhandelen en, waar het lettertype ze ondersteunt, de OpenType-verticale-features. Van daar af is het productioneren van de layout grotendeels rekenkunde: kolom-X-posities afleiden vanuit een gemeten blokbreedte, lange passages breken in kolommen die in de paginahoogte passen, en ruimte reserveren voor horizontale headers en folio's

Voor het bredere tekst- en lettertype-oppervlak waarop dit voortbouwt, inclusief de horizontale TextOut-conventies en Unicode-lettertype-registratie, zie het meertalige Hello World-voorbeeld en de TextOut-sample. De SetFont-verticale schakelaar en de TextOut-aanroepen die hier getoond worden zijn deel van de HotPDF Component voor Delphi en C++Builder