Technischer Artikel

Vertikaler CJK-Text in PDF mit Delphi und HotPDF

Setzt man einen japanischen Roman auf die Seite, fällt als Erstes auf, dass der Text die Spalte hinunterläuft statt entlang der Zeile, und dass die Spalten vom rechten Blattrand nach links fortschreiten. Ein damit aufgewachsener Leser empfindet horizontalen Text als leicht klinisch. Das technische Problem ist, dass PDF, wie fast jedes digitale Textsystem, um eine horizontale Grundlinie herum gebaut wurde, die von links nach rechts wächst, und ein Content-Stream kennt kein „schreibe diesen Absatz stattdessen abwärts“. Wenn also eine Delphi-Anwendung ein Zertifikat, ein Gedicht, eine Beschilderung oder ein traditionell formatiertes Rechtsdokument für taiwanesische, japanische oder koreanische Leser erzeugen muss, ist das Layout von Hand zu montieren: ein Zeichen unter dem nächsten, eine Spalte links von der vorigen

HotPDF gibt Ihnen einen Schalter, der die Buchführung pro Zeichen für Sie erledigt. Die gesetzte Schrift trägt ein IsVertical-Flag, und sobald es aktiv ist, stapelt ein einziger TextOut-Aufruf eine ganze Zeichenfolge in eine vertikale Spalte, statt sie entlang einer Grundlinie laufen zu lassen. Die Spaltenplatzierung, die Rechts-nach-links-Reihenfolge und eine still wichtige Glyphenersetzung sind das, was der Rest dieser Seite durcharbeitet

Von HotPDF erzeugte A4-PDF-Seite mit chinesischem, japanischem und koreanischem Text in vertikalen Spalten, die von rechts nach links gelesen werden
Eine einzelne A4-Seite mit chinesischem, japanischem und koreanischem Text in vertikalen Spalten, erzeugt aus einer einzigen Delphi-Prozedur.

Der Schalter sitzt an SetFont

Vertikales Layout ist keine Eigenschaft der Seite oder des Dokuments. Es ist eine Eigenschaft der Schrift, mit der Sie zeichnen, und Sie schalten es mit dem fünften Argument von SetFont ein:

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

Weil das Flag an der Schrift hängt, wechseln Sie zwischen horizontalem und vertikalem Schreiben einfach durch einen erneuten SetFont-Aufruf mit anderem letzten Argument. Eine Seite kann oben einen horizontalen Titel tragen und darunter vertikalen Fließtext, und HotPDF hält die beiden Modi pro Schriftobjekt auseinander statt pro Seite. Das macht gemischte Layouts ohne jede spezielle Modusverwaltung auf Ihrer Seite möglich: Jedes TextOut nach einem vertikalen SetFont stapelt, jedes TextOut nach einem horizontalen läuft entlang der Grundlinie, und das letzte SetFont gewinnt

Das vierte Argument ist der Windows-Zeichensatz, derselbe, den ein horizontaler Aufruf nimmt. Mit DEFAULT_CHARSET löst das System Glyphen pro Zeichenfolge auf, was hier zählt, weil ein vertikales Dokument oft Schriftsysteme mischt. Alles Übrige an der Schrift gilt weiterhin: Sie muss auf der Build-Maschine installiert sein, und Sie wollen fast immer FontEmbedding := True, damit die Datei dieselben CJK-Glyphen auf einem Reader rendert, der Arial Unicode MS nie besessen hat

Ein TextOut-Aufruf ist eine Spalte

Mit aktiver vertikaler Schrift verteilt ein TextOut-Aufruf seine Zeichenfolge nicht mehr seitwärts vom übergebenen Punkt. Er setzt das erste Zeichen oben und führt den Rest gerade nach unten, wobei er pro Glyphe um die Zeilenhöhe der Schrift vorrückt. Das übergebene X fixiert die Spalte; das übergebene Y fixiert, wo die Spaltenoberkante beginnt. Um eine echte Passage zu setzen, geben Sie daher ein TextOut pro Spalte aus und verschieben X zwischen den Aufrufen nach links, denn CJK-Spalten werden von rechts nach links gelesen

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;

Das Gedicht liest sich, wie es soll: zuerst die rechteste Spalte, von oben nach unten, dann springt das Auge nach links zur nächsten Spalte. Nichts an der Zeichenfolge selbst kodiert diese Reihenfolge. Sie kodieren sie in den X-Koordinaten, indem Sie sie Aufruf für Aufruf verringern. Vertauscht man die Richtung, kommt der Vers umgekehrt heraus — der mit Abstand häufigste Fehler beim Portieren eines horizontalen Layouts in die Vertikale

Koordinaten: die Seite hinab, von rechts nach links

Zwei Achsen sind im Spiel, und sie ziehen in entgegengesetzte Richtungen, also lohnt Präzision. HotPDF misst von der linken unteren Ecke der Seite, mit nach oben wachsendem Y, in Punkten. Eine vertikale Spalte beginnt daher bei einem hohen Y (nahe der Blattoberkante), und die Zeichen steigen von dort ab, während HotPDF für jedes eine Zeilenhöhe subtrahiert. Dieses Start-Y setzen Sie einmal pro Spalte, den Abstieg übernimmt die Komponente

Die horizontale Achse ist die, die Sie manuell steuern. Jede Spalte sitzt auf ihrem eigenen X, und aufeinanderfolgende Spalten rücken zu kleineren X-Werten, weil die Leserichtung von rechts nach links geht. Ein vernünftiger Rhythmus ist, das X der rechtesten Spalte zu wählen und dann für jeden weiteren Aufruf einen festen Spaltenabstand zu subtrahieren, wie es das Beispiel mit ColGap tut. Der Abstand ist Ihre Wahl; zu eng, und benachbarte Spalten berühren sich, zu weit, und der Block wirkt spärlich. Für Fließtext von 12 bis 18 Punkt liest sich ein Abstand, der etwas größer als die Schriftgröße ist, angenehm

Glyphen, die ihre Form ändern müssen

Einige Zeichen sind nicht einfach gedrehte Kopien ihres horizontalen Selbst; sie müssen ersetzt werden, wenn der Text vertikal wird. Das eine, das HotPDF für Sie behandelt, ist das japanische Längungszeichen (U+30FC), der Langvokalstrich, der in Katakana-Wörtern wie コーヒー erscheint. Horizontal gezeichnet ist es ein kurzer Strich auf der Grundlinie. Würde man dieselbe Glyphe in eine Spalte stapeln, läge sie quer über der Spalte, was falsch ist: Im vertikalen Japanisch wird das Zeichen zu einem vertikalen Strich, der die beiden Zeichen verbindet, zwischen denen es steht. HotPDF erkennt U+30FC im vertikalen Pfad und rendert es als vertikalen Balken (U+007C), sodass das Langvokalzeichen ohne jedes Zutun von Ihnen in die richtige Richtung weist

Diese eine Ersetzung deckt den Fall ab, an dem die meisten naiven Implementierungen scheitern, doch es lohnt sich zu wissen, wo das allgemeine Problem tiefer geht. Vollständige vertikale Typografie dreht auch lateinische Buchstaben und westliche Interpunktion um neunzig Grad, verschiebt kleine Kana und positioniert Klammern und Kommata in ihre vertikalen Formen um, und eine vollständige Implementierung davon lebt in den OpenType-Vertikalfeatures einer Schrift statt in festen Regeln. HotPDF kann diese Features anzapfen, wenn die Schrift sie trägt: vertikale Alternativen (die GSUB-Features vert und vrt2) und vertikales Kerning (GPOS-Lookups vkrn und vpal) sind Opt-in und werden entlang des vertikalen Pfads nur angewendet, wenn die aktive Schrift sie tatsächlich definiert. Für gemischten CJK-Text in einer einzigen Schrift mit breiter Abdeckung wie Arial Unicode MS genügen die eingebaute U+30FC-Behandlung plus ein gleichmäßiger Zeilenhöhenschritt für korrekte, lesbare Spalten; die OpenType-Features zählen, wenn Sie zu einer für feinen Vertikalsatz entworfenen Schrift wechseln und deren native Kana-Positionierung und Glyphenabstände wollen

Schriftsysteme und Ausrichtungen auf einer Seite mischen

Echte Dokumente sind selten rein. Eine vertikale japanische Seite kann eine horizontale englische Bildunterschrift tragen, eine Seitenzahl am unteren Rand oder einen vertikal gesetzten koreanischen Block neben dem Japanischen. Weil das Vertikal-Flag ein Schalter auf Schriftebene ist, komponieren Sie all das durch abwechselnde SetFont-Aufrufe statt durch die Verwaltung eines seitenweiten Zustands. Setzen Sie eine horizontale Schrift, schreiben Sie Kolumnentitel und Seitenzahl, setzen Sie eine vertikale Schrift, legen Sie die Spalten, setzen Sie für eine Fußzeile wieder eine horizontale Schrift. Jede Region übernimmt den Modus des letzten SetFont, die einzige nötige Disziplin ist also, es bei jedem Richtungswechsel aufzurufen

Ein Detail, das man beim Mischen von Schriftsystemen einplanen sollte: Chinesische, japanische und koreanische Ideogramme sind nahezu quadratisch und stapeln sich in gleichmäßigem Schritt, aber eingebettete lateinische Läufe in einer vertikalen Spalte haben diesen einheitlichen Vorschub nicht. Brauchen Sie ein paar lateinische Wörter innerhalb ansonsten vertikalen Texts, entscheiden Sie bewusst, ob sie gedreht die Spalte hinunterlaufen oder aufrecht als kurzer horizontaler Einschub stehen sollen, und positionieren Sie dieses Fragment mit einem eigenen TextOut, statt es auf dem für Ideogramme gedachten Vertikalschritt mitreiten zu lassen. Den gemischten Lauf als eigenes Platzierungsproblem zu behandeln, hält den Spaltenrhythmus intakt

Vom Beispiel zur Produktion

Die Bausteine sind klein und fügen sich vorhersagbar zusammen. Schalten Sie das Vertikal-Flag in SetFont ein, geben Sie ein TextOut pro Spalte von einem festen oberen Y aus und verringern Sie X über die Aufrufe hinweg, damit die Spalten von rechts nach links lesen. Betten Sie die Schrift ein, damit die CJK-Glyphen die Reise zur Maschine des Lesers überstehen, und überlassen Sie der Komponente das U+30FC-Längungszeichen und, wo die Schrift sie unterstützt, die OpenType-Vertikalfeatures. Von dort ist das Produktivmachen des Layouts überwiegend Arithmetik: Spalten-X-Positionen aus einer gemessenen Blockbreite ableiten, lange Passagen in Spalten brechen, die in die Seitenhöhe passen, und Platz für horizontale Kopfzeilen und Seitenzahlen reservieren

Für die breitere Text- und Schriftoberfläche, auf der dies aufbaut, einschließlich der horizontalen TextOut-Konventionen und der Unicode-Schriftregistrierung, siehe das mehrsprachige Hello-World-Beispiel und das TextOut-Beispiel. Der vertikale SetFont-Schalter und die hier gezeigten TextOut-Aufrufe sind Teil der HotPDF Component für Delphi und C++Builder