Artykuł techniczny

Pionowy tekst japoński i chiński w PDF z Delphi

Ustaw 「コード」 w dół strony, rysując znak po znaku, każde wywołanie o jedno em niżej niż poprzednie, a trzy z pięciu glifów wyjdą źle. Dwa narożne nawiasy siedzą w kolumnie tak, jak siedzą na poziomym wierszu — otwierają się na boki, a długi znak samogłoski ー to płaski kreskowiec przecinający kolumnę tam, gdzie japoński czytelnik oczekuje krótkiej pionowej kreski. Tylko コ i ド, dwie zwykłe katakany, wyglądają tak, jak ustawiłby je zecer

Obrót całego wiersza o dziewięćdziesiąt stopni naprawia nawiasy i długi znak samogłoski mniej więcej przez przypadek — i psuje wszystko inne: każdy kanji i kana leży teraz na boku. Pismo pionowe robi trzy rzeczy naraz. Znaki stoją prosto, postęp biegnie w dół strony zamiast w poprzek, a garstka znaków przełącza się na inne rysunki. Trafienie we wszystkie trzy wymaga kroju, który wozi pionowe formy i pionowe metryki, i fontu PDF ustawionego tak, żeby ich używać

Diagram PDF Library for Delphi porównujący obrócony poziomy wiersz tekstu z prawdziwym pionowym trybem pisania, gdzie glify pozostają proste, a postęp biegnie w dół strony
Pismo pionowe ustawia znaki prosto z pionowymi metrykami danego kroju zamiast obracać poziomy wiersz, a skopiowana kolumna czyta się z powrotem znak w znak

Włączanie pisma pionowego w PDF Library for Delphi

PDF Library for Delphi traktuje pismo pionowe jako tryb rysowania. SetVerticalWritingMode(1) włącza je, każde wywołanie DrawText po tym biegnie w dół strony od podanego punktu, a SetVerticalWritingMode(0) przywraca tekst poziomy. Setter zwraca tryb obowiązujący przed wywołaniem, a GetVerticalWritingMode odczytuje bieżący, więc rutyna pomocnicza może przełączyć tryb i później odłożyć dokładnie to, co zastała

Obie współrzędne znaczą coś nieco innego niż przy tekście poziomym. Wartość x nazywa linię środkową kolumny: każdy glif wisi swoją poziomą środkową na tej linii, więc wąski znak interpunkcyjny i pełnej szerokości kanji trzymają się jednej osi. Wartość y to górna krawędź pierwszej komórki znakowej, a kolumna rośnie stamtąd w dół. Przy SetOrigin(1), które kładzie y = 0 na górnej krawędzi strony, arytmetyka czyta się tak, jak układałbyś stronę na papierze

var
  Lib: TPDFlib;
  H: Double;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('MS Mincho', 1);      // 1 = osadź krój
    Lib.SetTextSize(12);
    Lib.SetVerticalWritingMode(1);            // zwykłe DrawText teraz biegnie w dół
    H := Lib.GetVerticalTextHeight('MS Mincho', 12, '第三章 保守点検');
    Lib.DrawText(480, 72, '第三章 保守点検');     // kolumna startuje na y = 72
    Lib.SetVerticalWritingMode(0);            // powrót do poziomego
    // Origin 1 kładzie y = 0 u góry, więc 72 + H to stopka kolumny;
    // poziomy podpis siedzi o jeden wiersz niżej
    Lib.DrawText(456, 72 + H + 18, 'Chapter 3');
    Lib.SaveToFile('manual-ja.pdf');
  finally
    Lib.Free;
  end;
end;

GetVerticalTextHeight bierze nazwę kroju, rozmiar i tekst, a zwraca długość kolumny w punktach. W przykładzie 72 + H to stopka kolumny i angielski podpis idzie 18 punktów pod nią, startując 24 punkty na lewo od linii środkowej kolumny, żeby krótki łaciński wiersz siedział mniej więcej pod kolumną. Funkcja zwraca 0, gdy nie może otworzyć kroju o tej nazwie, a układ zbudowany na zerowej wysokości położy podpis na czubku kolumny, więc sprawdź to, zanim użyjesz wartości

Dlaczego nawiasy leżą na boku w pionowej kolumnie?

Ponieważ font trzyma dwa rysunki każdego takiego znaku, a poziomy jest domyślny. Kroje OpenType wypisują alternatywy w cesze GSUB zwanej vert, albo vrt2 w krojach, które wożą glify już obrócone do składu pionowego. Kształtowanie wiersza tekstu zwraca glify poziome i coś musi nałożyć cesę pionową po fakcie, glif za glif. Narożne nawiasy, nawiasy okrągłe i długi znak samogłoski obracają się, by biec wzdłuż kolumny; małe kana i interpunkcja, jak 、 i 。, przesuwają się do innego narożnika swojej kwadratowej komórki

PDF Library for Delphi czyta tę cesę z kroju, którym rysuje, i podmienia każdy glif na pionową formę nazwaną przez font. Gdy krój wozi obie cesy, vrt2 wygrywa, a vert jest ignorowane. Specyfikacja chce, by vrt2 zastępowało vert, a czytanie obu może wręczyć jednemu znakowi dwa różne pionowe glify, zależnie od tego, która tabela odpowiedziałaby pierwsza. Krój bez żadnej z nich zostawia zwykłe glify. Wolimy taki wynik niż wbudowaną tabelę podstawień: zgadywana tabela trafi w zwykłe znaki i po cichu nadpisze kroje, których projektanci potraktowali znak inaczej

Zrzuć tabele GSUB egzemplarzy dołączonych do Windows, a liczby rozjadą się daleko: cesa pionowa MS Gothic mapuje 979 glifów, Yu Gothic 2 491, SimSun 72, a Arial ani jednego (dokładne liczby wędrują wraz z wersjami fontów). Krój łaciński, który przy okazji zawiera trochę kany, narysuje prostą kolumnę z każdym nawiasem i długim znakiem samogłoski w formie poziomej, bo w foncie nie ma czego podstawiać

Diagram PDF Library for Delphi pokazujący, jak funkcja pionowa czcionki dostarcza form nawiasów i znaku przedłużonego dźwięku leżących wzdłuż pionowej kolumny, podczas gdy zwykłe znaki stoją niezmienione, wszystko mierzone przez GetVerticalTextHeight
Własna funkcja pionowa kroju decyduje, które glify zmieniają formę w kolumnie, a GetVerticalTextHeight mierzy formy, które faktycznie zostaną narysowane

Podstawienie zmienia też pomiar. Pionowa forma może mieć własny postęp, różny od poziomego glifu, którego zastępuje, więc GetVerticalTextHeight nakłada to samo podstawienie, zanim zsumuje postępy. Zmierz formy poziome, narysuj pionowe, a kolumna przecieśnie rezerwację o kilka punktów na każdy nawias

Nic z tego nie dotyka samego tekstu. Skopiuj kolumnę z przeglądarki, a dostaniesz z powrotem 「コード」, bo każdy pionowy glif mapuje się na znak, który podałeś. Wyszukiwanie i ekstrakcja tekstu widzą twój łańcuch źródłowy, nigdy obrócone formy prezentacyjne

Jak metryki pionowe trafiają do PDF

Każdy znak w pionowym przebiegu postępuje o odległość, którą dają mu własne metryki pionowe fontu. Otwórz wyjście w inspektorze PDF, a znajdziesz font Type0 z kodowaniem Identity-V, którego CIDFont niesie wpis W2 dla każdego użytego glifu: pionowy postęp z tabeli vmtx kroju, połowę szerokości glifu jako offset do linii środkowej kolumny i pionowy początek. Te trzy liczby definiuje ISO 32000-1 §9.7.4.3. Dla kroju bez metryk pionowych nie ma tablicy W2 i domyślne DW2 postępuje każdy znak dokładnie o jedno em

O pionowym początku trzecie fonty spierają się między sobą. Tabela vhea ma pola ascender i descender wyglądające na oczywiste źródło, ale konwencja znaku descendera nie jest wspólna dla krojów. Przeczytaj pole z egzemplarzy z Windows dowolną przeglądarką tabel fontów i wyszły +140 w MS Gothic i +120 w Yu Gothic, wobec −144 w SimSun i −500 w MingLiU, a wartości mogą się zmieniać z wersjami fontów. Każda pozycja wyprowadzona z tego pola dryfuje o inną wielkość dla każdego kroju. PDF Library for Delphi bierze początek z pionowego ascendera, który wymienione wyżej kroje CJK raportują zgodnie, i pada na 880 jednostek — domyślną wartość, którą sama specyfikacja PDF daje DW2 — gdy krój nie raportuje niczego sensownego

Ręczne pozycjonowanie pionowych glifów ma własną pułapkę. Tekst poziomy często ustala odstępy liczbami wewnątrz tablicy TJ. Pod trybem pisma pionowego ISO 32000-1 §9.4.4 nakłada te liczby wzdłuż kolumny, więc korekta policzona dla odstępów poziomych zepchnie każdy znak głębiej w dół strony. Dlatego pionowy przebieg w strumieniu treści to jeden goły łańcuch kodów glifów bez jakichkolwiek korekt, a cały odstęp pochodzi z W2

Kontrolując wyjście, patrz na tusz, nie na słowniki. Obecność /Identity-V w foncie dowodzi deklaracji kodowania; nie mówi nic o tym, w którą stronę tekst faktycznie biegnie. Wyrenderowanie strony i zmierzenie ramki narysowanych pikseli mówi: przebieg siedmiu znaków powinien wyjść około jeden znak szeroki i siedem znaków wysoki

Jeden pionowy przebieg na poziomej stronie

DrawVerticalText rysuje pojedynczy przebieg pionowo i zostawia tryb pisania w spokoju. Bierze pozycję, nazwę fontu, rozmiar i tekst, więc bieżący wybór czcionki też zostaje nietknięty. Etykiety na grzbietach, pieczęcie, jedna kolumna nazwisk obok tabeli — to pionowe wyjątki wewnątrz poziomego dokumentu, a przełączanie globalnego trybu wokół każdego z nich to więcej stanu, niż zasługuje ta robota

Diagram PDF Library for Delphi DrawVerticalText umieszczającego jeden prosty pionowy przebieg obok zwykłych poziomych linii DrawText na wspólnej stronie bez dotykania globalnego trybu pisania
DrawVerticalText niesie własną czcionkę, rozmiar i pozycję, więc pojedyncza pionowa kolumna współistnieje z poziomymi wywołaniami wokół niej
// Jeden pionowy przebieg wewnątrz poza tym poziomej strony
if Lib.DrawVerticalText(520, 96, 'MS Mincho', 14, '保守点検記録') = 0 then
begin
  // Krój nie dał się otworzyć albo nie dał się ustawić dla tego tekstu.
  // LogLayoutProblem to twoja własna rutyna, nie część biblioteki
  LogLayoutProblem('vertical label on page ' + IntToStr(Lib.SelectedPage));
end;
// Poziomy tekst wokół niego pozostaje niezmieniony
Lib.DrawText(72, 96, 'Maintenance inspection record');

Strona mieszająca oba kierunki wozi krój w swoich zasobach dwa razy — raz z kodowaniem poziomym, raz z Identity-V — zbudowany z tego samego programu fontu. Każdy kierunek trzyma własne metryki, więc strona japońskiej książki z pionowym tekstem głównym i poziomymi żywymi nagłówkami albo chiński certyfikat z pionowym tytułem nad poziomymi liniami szczegółów wychodzi z oboma kierunkami rozstawionymi poprawnie

Wartością zwracaną jest powód, dla którego sięgamy po DrawVerticalText nawet w dokumentach w większości pionowych. W rysowaniu opartym na trybie przebieg, którego nie da się złożyć pionowo, jest rysowany zwykłą drogą poziomą zamiast być porzucony, a DrawText raportuje sukces tak czy owak. Dla tekstu głównego to lepszy domyślny wybór, bo zbłąkany poziomy wiersz łatwo wychwycić na próbce wydruku, a brakujący akapit nie. Dla etykiety, której kierunek jest całym sensem, użyj DrawVerticalText i traktuj 0 jako porażkę składu

Układanie kolumn z prawej na lewo

Kolejność kolumn jest twoja do rozstrzygnięcia. Japoński i tradycyjny chiński w pionie czytają kolumna po kolumnie od prawej do lewej, więc strona startuje od prawego marginesu i każda nowa kolumna siedzi na lewo od poprzedniej. Żadne ustawienie trybu pisania nie wywnioskuje tego z tekstu, i to samo dotyczy kolejności stron w książce oprawionej od prawej oraz miejsc furigany, przypisów i podpisów rysunków

Rutyna poniżej łamie łańcuch na kolumny mieszczące się w stałej wysokości, mierząc po drodze przez GetVerticalTextHeight i stając w lewo o półtora em na kolumnę:

procedure DrawColumns(Lib: TPDFlib; const Face: WideString; Size: Double;
  const Text: WideString; Right, Top, BoxH: Double);
var
  Col, Trial: WideString;
  I: Integer;
  X: Double;
begin
  // Straż jako sugestia, nie kontrola biblioteki: zerowa wysokość znaczy, że krój
  // nie dał się otworzyć, a pętla poniżej nigdy nie złamałaby kolumny
  if Lib.GetVerticalTextHeight(Face, Size, Text) <= 0 then
    Exit;
  X := Right;                                  // pierwsza kolumna przy prawym marginesie
  Col := '';
  for I := 1 to Length(Text) do                // zakłada brak par surrogate
  begin
    Trial := Col + Text[I];
    if (Col <> '') and (Lib.GetVerticalTextHeight(Face, Size, Trial) > BoxH) then
    begin
      Lib.DrawVerticalText(X, Top, Face, Size, Col);
      X := X - Size * 1.5;                     // następna kolumna w lewo
      Col := Text[I];
    end
    else
      Col := Trial;
  end;
  if Col <> '' then
    Lib.DrawVerticalText(X, Top, Face, Size, Col);
end;

Ponowne mierzenie próbnego łańcucha przy każdym znaku jest wolniejsze od sumowania postępów, ale mierzy dokładnie to, co zostanie narysowane, z podstawieniami włącznie. Czego pętla nie robi, to zasad kinsoku. Zamykający nawias albo 。 na szczycie kolumny albo otwierający nawias u jej stopy to dla japońskiego czytelnika błąd składu, a naprawa oznacza przeciągnięcie lub odepchnięcie znaku przez łamanie, zanim narysujesz. Pary surrogate potrzebują tej samej troski: indeksowanie Text[I] przecina znak z poza Basic Multilingual Plane na pół, więc krokuj po punkcie kodu, jeśli twój tekst może taki zawierać

Dla dokumentów składanych z danych przejście przez artykuł przeszukiwanie tekstu i wyliczanie elementów strony pokazuje, jak odczytać z powrotem to, co wylądowało na każdej stronie — szybki sposób, by potwierdzić, że każda kolumna niesie znaki, które miała nieść

Którego fontu powinien używać dokument pionowy?

Kroju CJK z własnymi metrykami pionowymi i cesą vert albo vrt2. Bez nich dostajesz stojące prosto znaki postępujące po jednym em i żadnych zmian kształtów. Gdy tekst pionowy wygląda subtelnie nie tak, obejrzyj font, zanim obejrzysz kod. Przeglądarka tabel wypisująca cesy GSUB kroju i jego tabele vhea oraz vmtx powie ci w minutę, czy krój da radę

DrawVerticalText i GetVerticalTextHeight otwierają zainstalowany krój po nazwie przez Windows. W kompilacjach Lazarus dla innych platform pismo pionowe idzie przez SetVerticalWritingMode i DrawText, które pracują z programu fontu, który wczytałeś i wybrałeś — te same pionowe formy i metryki obowiązują, dopóki tym fontem jest krój CJK, o który ci chodziło

Osadzanie idzie zwykłymi regułami i zwykłymi kosztami. Pełny krój CJK ciągnie wiele megabajtów, więc subsetowanie to rozsądny domyślny wybór dla każdego dokumentu, który opuści twoją maszynę. Notki o artykule optymalizacja rozmiaru PDF i subsetowanie fontów mówią, czego się spodziewać, a przejście przez artykuł osadzanie brakujących fontów w istniejącym PDF pokrywa przypadek naprawczy, gdy dokument pionowy przychodzi bez swoich fontów

Pionowe pismo CJK siedzi w tym samym API rysowania co reszta funkcji tekstowych PDF Library for Delphi, natywnej biblioteki PDF w Pascalu dla Delphi, C++Builder i Lazarus. Pełny zestaw cech fontowych i tekstowych wypisuje obok niej strona produktu PDF Library for Delphi