Artykuł techniczny

Pionowy tekst CJK w PDF za pomocą Delphi i HotPDF

Umieść japońską powieść na stronie, a pierwszą rzeczą, którą zauważysz, jest to, że tekst biegnie w dół kolumny, a nie w poprzek wiersza, a kolumny postępują od prawej krawędzi arkusza w lewo. Czytelnik wychowany w takiej konwencji uważa poziomy tekst za nieco chłodny i sterylny. Problem inżynieryjny polega na tym, że PDF, podobnie jak prawie każdy cyfrowy system tekstowy, został zbudowany wokół poziomej linii bazowej rosnącej od lewej do prawej, a strumień treści nie posiada pojęcia "zapisz ten akapit w dół". Kiedy więc aplikacja Delphi musi wygenerować certyfikat, wiersz, znak informacyjny lub dokument prawny w tradycyjnym formacie dla czytelnika z Tajwanu, Japonii lub Korei, układ musi zostać złożony ręcznie: jeden znak pod drugim, jedna kolumna na lewo od poprzedniej

HotPDF oferuje przełącznik, który wykonuje za Ciebie całą księgowość dla każdego znaku. Ustawiona czcionka niesie ze sobą flagę IsVertical i po jej włączeniu pojedyncze wywołanie TextOut układa cały ciąg w pionową kolumnę, zamiast prowadzić go wzdłuż linii bazowej. Umiejscowienie kolumn, kolejność od prawej do lewej i jedno dyskretnie ważne zastąpienie glifów to tematy, które omówiono w dalszej części tej strony

Pojedyncza strona A4 z chińskim, japońskim i koreańskim tekstem w pionowych kolumnach, wygenerowana z jednej procedury Delphi.
Pojedyncza strona A4 z chińskim, japońskim i koreańskim tekstem w pionowych kolumnach, wygenerowana z jednej procedury Delphi.

Przełącznik znajduje się w SetFont

Układ pionowy nie jest właściwością strony ani dokumentu. Jest właściwością czcionki, którą rysujesz, i włącza się go piątym argumentem 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

Ponieważ flaga zależy od czcionki, przełączanie między pismem poziomym a pionowym odbywa się po prostu przez ponowne wywołanie SetFont z innym ostatnim argumentem. Strona może zawierać poziomy tytuł na górze i pionowy tekst główny poniżej, a HotPDF oddziela oba tryby na obiekt czcionki, a nie na stronę. To właśnie sprawia, że mieszane układy są możliwe bez konieczności obsługi specjalnego trybu z Twojej strony: każde TextOut po pionowym SetFont układa się pionowo, każde TextOut po poziomym biegnie wzdłuż linii bazowej, a wygrywa ostatnie SetFont

Czwarty argument to zestaw znaków Windows, ten sam, który przyjmuje wywołanie poziome. Przekazanie DEFAULT_CHARSET pozwala systemowi rozpoznać glify dla każdego ciągu, co ma tutaj znaczenie, ponieważ pionowy dokument często miesza skrypty. Wszystko inne dotyczące czcionki nadal obowiązuje: musi być zainstalowana na maszynie budującej i prawie zawsze będziesz chciał, aby FontEmbedding := True po to, by plik renderował te same glify CJK u czytelnika, który nigdy nie posiadał czcionki Arial Unicode MS

Jedno wywołanie TextOut to jedna kolumna

Z aktywną czcionką pionową wywołanie TextOut nie rozkłada już swojego ciągu na boki od zadanego punktu. Umieszcza pierwszy znak na górze i prowadzi resztę prosto w dół, przesuwając się o wysokość linii czcionki dla każdego glifu. Wartość X, którą przekazujesz, ustala kolumnę; wartość Y ustala miejsce, w którym zaczyna się góra kolumny. Aby ułożyć prawdziwy fragment, wykonujesz jedno wywołanie TextOut na kolumnę i przesuwasz X w lewo między wywołaniami, ponieważ kolumny CJK czyta się od prawej do lewej

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;

Wiersz czyta się tak, jak należy: najpierw skrajna prawa kolumna, z góry na dół, potem wzrok skacze w lewo do następnej kolumny. Nic w samym ciągu nie koduje tej kolejności. Kodujesz ją we współrzędnych X, zmniejszając je z każdym wywołaniem. Zmiana kierunku sprawi, że wers wyjdzie odwrócony, co jest zdecydowanie najczęstszym błędem podczas przenoszenia układu z poziomego na pionowy

Współrzędne: w dół strony, od prawej do lewej

W grę wchodzą dwie osie i ciągną one w przeciwnych kierunkach, więc warto być precyzyjnym. HotPDF mierzy od lewego dolnego rogu strony, gdzie Y rośnie w górę, w punktach. Kolumna pionowa zatem zaczyna się przy wysokim Y (blisko góry arkusza), a znaki opadają stamtąd, gdy HotPDF odejmuje wysokość linii dla każdego z nich. Początkowe Y ustalasz raz na kolumnę, a komponent zajmuje się schodzeniem w dół

Oś pozioma to ta, którą sterujesz ręcznie. Każda kolumna ma swoje własne X, a kolejne kolumny przechodzą do mniejszych wartości X, ponieważ kierunek czytania to od prawej do lewej. Rozsądnym rytmem jest wybranie X najdalszej prawej kolumny, a następnie odjęcie stałego odstępu między kolumnami dla każdego kolejnego wywołania, tak jak w przykładzie z ColGap. Przerwę wybierasz sam; jeśli będzie zbyt ciasna, sąsiednie kolumny będą się stykać, a zbyt luźna sprawi, że blok będzie wyglądał na rzadki. W przypadku tekstu podstawowego od 12 do 18 punktów, przerwa nieco większa od rozmiaru czcionki czyta się komfortowo

Glify, które muszą zmienić kształt

Kilka znaków nie jest po prostu obróconymi kopiami ich poziomych wersji; po zmianie tekstu na pionowy muszą zostać podmienione. Tym, z którym HotPDF radzi sobie za Ciebie, jest japoński znak przedłużenia dźwięku (U+30FC), kreska długiej samogłoski występująca w słowach zapisanych w katakanie, takich jak コーヒー. Narysowany poziomo, jest to krótka myślnik na linii bazowej. Gdybyś ułożył ten sam glif w kolumnie, leżałby płasko w poprzek kolumny, co byłoby błędem: w pionowym japońskim znak ten staje się pionową kreską łączącą dwa sąsiadujące znaki. HotPDF wykrywa U+30FC na ścieżce pionowej i renderuje go jako pionową kreskę (U+007C), więc znak przedłużenia samogłoski wskazuje we właściwą stronę bez najmniejszego wysiłku z Twojej strony

To jedno zastąpienie pokrywa przypadek, który rozbija większość naiwnych implementacji, ale warto wiedzieć, gdzie ogólny problem sięga głębiej. Pełna pionowa typografia obraca również o dziewięćdziesiąt stopni litery łacińskie i zachodnią interpunkcję, przesuwa małą kanę oraz zmienia pozycje nawiasów i przecinków do ich pionowych form, a pełna realizacja tych funkcji spoczywa w pionowych cechach OpenType czcionki, a nie w ustalonych regułach. HotPDF może skorzystać z tych cech, gdy czcionka je obsługuje: pionowe alternatywy (cechy vert i vrt2 GSUB) oraz pionowy kerning (wywołania vkrn i vpal GPOS) to rozwiązania opcjonalne, stosowane wzdłuż pionowej ścieżki tylko wtedy, gdy aktywna czcionka faktycznie je definiuje. Do tekstu mieszanego CJK w pojedynczej, powszechnie stosowanej czcionce, jak Arial Unicode MS, wbudowana obsługa U+30FC oraz równe przesunięcie wysokości linii wystarczą, aby wygenerować poprawne, czytelne kolumny; funkcje OpenType stają się ważne, gdy przechodzisz na czcionkę zaprojektowaną z myślą o precyzyjnym pionowym ustawieniu i oczekujesz jej oryginalnego pozycjonowania kany oraz odpowiednich odstępów między glifami

Mieszanie skryptów i orientacji na jednej stronie

Prawdziwe dokumenty rzadko są jednorodne. Pionowa strona w języku japońskim może zawierać poziomy podpis po angielsku, numer strony na dole lub blok po koreańsku umieszczony pionowo obok japońskiego tekstu. Ponieważ flaga pionowa jest przełącznikiem na poziomie czcionki, budujesz je poprzez naprzemienne wywołania SetFont zamiast kontrolowania jakiegokolwiek stanu na poziomie strony. Ustawiasz poziomą czcionkę, wypisujesz nagłówek bieżący oraz folio, ustawiasz pionową czcionkę, układasz kolumny i znów przypisujesz poziomą czcionkę do stopki. Każdy region przejmuje tryb od ostatniego wywołania SetFont, więc jedyną wymaganą dyscypliną jest wywołanie tej procedury za każdym razem, gdy zmieniasz kierunek

Jeden detal, który należy przewidzieć przy mieszaniu skryptów: chińskie, japońskie i koreańskie ideogramy są niemal kwadratowe i układają się równomiernie, ale wstawki znaków łacińskich osadzone w pionowej kolumnie nie mają tego jednolitego postępu. Jeśli potrzebujesz kilku łacińskich słów wewnątrz tekstu pionowego, celowo zdecyduj, czy powinny zostać obrócone, by biec w dół kolumny, czy umieszczone prosto jako krótki poziomy wtręt, i ustaw ten fragment za pomocą własnego polecenia TextOut, zamiast pozwalać, aby posługiwał się on pionowym podziałem przeznaczonym dla ideogramów. Potraktowanie wstawki tekstu mieszanego jako własnego problemu pozycjonowania zachowuje rytm kolumny w stanie nienaruszonym

Od próbki po wdrożenie na produkcję

Elementy są małe i komponują się przewidywalnie. Włącz flagę pionową w SetFont, wykonaj jedno TextOut na kolumnę od określonego górnego Y, a następnie zmniejszaj X w kolejnych wywołaniach, aby kolumny czytało się od prawej do lewej. Osadź czcionkę, aby glify CJK przetrwały podróż na maszynę czytelnika, i pozwól komponentowi obsługiwać znak długiej samogłoski U+30FC, a tam gdzie pozwala czcionka, również pionowe funkcje OpenType. Z tego miejsca wdrożenie produkcyjne to głównie arytmetyka: wyliczanie położenia X na podstawie zmierzonej szerokości bloku, rozbijanie długich fragmentów na kolumny pasujące do wysokości strony oraz rezerwowanie miejsca dla poziomych nagłówków i folio

Szersze omówienie kwestii związanych z powierzchnią tekstu i czcionek, na których to wszystko się opiera, w tym poziome konwencje TextOut oraz rejestrację czcionek Unicode, można znaleźć w wielojęzycznym przykładzie Hello World oraz w przykładzie TextOut. Pionowy przełącznik SetFont i wywołania TextOut pokazane tutaj są częścią komponentu HotPDF dla Delphi i C++Builder