Нарисуйте 「コード」 вниз по странице по одному знаку за вызов, каждый вызов на em ниже предыдущего, — и три глифа из пяти выйдут неправильными. Две угловые скобки сядут в колонку так же, как сидят на горизонтальной строке, раскрываясь вбок, а знак долготы гласной ー — плоское тире, режущее колонку поперёк, там, где японский читатель ждёт короткий вертикальный штрих. Только コ и ド, две обычные катаканы, выглядят так, как поставил бы их наборщик
Поворот всей строки на девяносто градусов чинит скобки и знак долготы скорее случайно и ломает всё остальное: каждый кандзи и каждая кана теперь лежат на боку. Вертикальное письмо делает три вещи разом. Знаки остаются прямые, продвижение идёт вниз по странице, а не поперёк неё, и горстка знаков переключается на другие рисунки. Чтобы все три сошлись, нужен шрифт, несущий вертикальные формы и вертикальные метрики, и PDF-шрифт, настроенный их использовать
Включение вертикального письма в PDF Library for Delphi
PDF Library for Delphi считает вертикальное письмо режимом рисования. SetVerticalWritingMode(1) включает его, каждый вызов DrawText после этого идёт вниз по странице от заданной точки, а SetVerticalWritingMode(0) возвращает горизонтальный текст. Сеттер возвращает режим, действовавший до вызова, а GetVerticalWritingMode читает текущий, поэтому вспомогательная подпрограмма может переключить режим и позже вернуть ровно то, что застала
Две координаты означают не совсем то, что в горизонтальном тексте. Значение x задаёт центральную линию колонки: каждый глиф висит горизонтальной серединой на этой линии, поэтому узкий знак препинания и полноширинный кандзи держатся на одной оси. Значение y — верх первой клеточки знака, и колонка растёт вниз от неё. С SetOrigin(1), помещающим y = 0 на верхний край страницы, арифметика читается так же, как вы бы размечали страницу на бумаге
var
Lib: TPDFlib;
H: Double;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.AddTrueTypeFont('MS Mincho', 1); // 1 = встроить начертание
Lib.SetTextSize(12);
Lib.SetVerticalWritingMode(1); // обычный DrawText теперь идёт вниз
H := Lib.GetVerticalTextHeight('MS Mincho', 12, '第三章 保守点検');
Lib.DrawText(480, 72, '第三章 保守点検'); // колонка начинается на y = 72
Lib.SetVerticalWritingMode(0); // назад к горизонтали
// Origin 1 помещает y = 0 наверх, поэтому 72 + H — это низ колонки;
// горизонтальная подпись сидит на одну строку ниже
Lib.DrawText(456, 72 + H + 18, 'Chapter 3');
Lib.SaveToFile('manual-ja.pdf');
finally
Lib.Free;
end;
end;
GetVerticalTextHeight принимает имя начертания, размер и текст и возвращает длину колонки в пунктах. В примере 72 + H — низ колонки, а английская подпись идёт на 18 пунктов ниже, начинаясь на 24 пункта левее центральной линии колонки, чтобы короткая латинская строка сидела примерно под колонкой. Функция возвращает 0, когда не может открыть начертание с таким именем, а вёрстка, построенная на нулевой высоте, положит подпись поверх колонки, так что проверяйте это до использования значения
Почему скобки лежат на боку в вертикальной колонке?
Потому что шрифт держит два рисунка каждого такого знака, а горизонтальный — по умолчанию. Начертания OpenType перечисляют альтернативы в функции GSUB под именем vert, или vrt2 в тех начертаниях, что несут глифы, уже повёрнутые для вертикального набора. Шейпинг строки текста возвращает горизонтальные глифы, и кто-то должен применить вертикальную функцию следом, глиф за глифом. Угловые скобки, круглые скобки и знак долготы разворачиваются вдоль колонки; малая кана и знаки вроде 、 и 。 переселяются в другой угол своей клетки
PDF Library for Delphi читает эту функцию из начертания, которым рисует, и подменяет каждый глиф вертикальной формой, названной шрифтом. Когда начертание несёт обе функции, побеждает vrt2, а vert игнорируется. Спецификация замышляла vrt2 на замену vert, и чтение обеих может выдать одному знаку два разных вертикальных глифа — смотря какой lookup ответит первым. Начертание без обеих функций сохраняет обычные глифы. Мы предпочитаем такой исход встроенной таблице подстановок: угаданная таблица справляется с типовыми знаками и тихо переспоривает начертания, чьи дизайнеры трактовали знак иначе
Слейте таблицы GSUB копий, что поставляются с Windows, и числа разойдутся далеко: вертикальная функция MS Gothic отображает 979 глифов, Yu Gothic — 2 491, SimSun — 72, а Arial — ни одного (точные числа гуляют вместе с версиями шрифтов). Латинское начертание, в которое случайно попала какая-то кана, нарисует прямую колонку, где каждая скобка и знак долготы останутся в горизонтальной форме, — заменять в шрифте просто нечего
Подмена меняет и измерение. Вертикальная форма может иметь собственное продвижение, отличное от горизонтального глифа, который она заменяет, поэтому GetVerticalTextHeight применяет ту же подмену, прежде чем суммировать продвижения. Меряйте горизонтальные формы, рисуйте вертикальные — и колонка выползет из отведённого ей блока на несколько пунктов за каждую скобку
Ничто из этого не трогает сам текст. Скопируйте колонку из вьювера — и вы получите назад 「コード」, потому что каждый вертикальный глиф отображается на переданный вами знак. Поиск и извлечение текста видят вашу исходную строку, а не повёрнутые презентационные формы
Как вертикальные метрики попадают в PDF
Каждый знак вертикального прогона продвигается на расстояние, которое дают собственные вертикальные метрики шрифта. Откройте вывод в PDF-инспекторе и найдёте шрифт Type0 с кодировкой Identity-V, чей CIDFont несёт запись W2 для каждого использованного глифа: вертикальное продвижение из таблицы vmtx начертания, половину ширины глифа как смещение к центральной линии колонки и вертикальное начало. Эти три числа определяет ISO 32000-1, §9.7.4.3. У начертания без вертикальных метрик массива W2 нет, и значение по умолчанию DW2 продвигает каждый знак ровно на один em
Вертикальное начало — то место, где сторонние шрифты расходятся друг с другом. Таблица vhea имеет поля ascender и descender, выглядящие как очевидный источник, но соглашение о знаке descender не разделяется между начертаниями. Прочтите поле из копий Windows любым просмотрщиком таблиц шрифтов — и получите +140 в MS Gothic и +120 в Yu Gothic против −144 в SimSun и −500 в MingLiU, причём значения могут меняться с версиями шрифтов. Любая позиция, выведенная из этого поля, уплывает на разную величину для каждого начертания. PDF Library for Delphi берёт начало из вертикального ascender, который перечисленные выше CJK-начертания сообщают согласованно, и откатывается к 880 единицам — значению по умолчанию, которое сама спецификация PDF даёт DW2, — когда начертание не сообщает ничего правдоподобного
Ручное позиционирование вертикальных глифов таит свою ловушку. Горизонтальный текст часто правит интервалы числами внутри массива TJ. В режиме вертикального письма ISO 32000-1, §9.4.4 применяет эти числа вдоль колонки, поэтому поправка, вычисленная для горизонтального интервала, толкает каждый знак ещё ниже по странице. Вот почему вертикальный прогон в потоке содержимого — одна простая строка кодов глифов без всяких корректировок, и весь интервал берётся из W2
Проверяя вывод, смотрите на краску, а не на словари. Найти /Identity-V в шрифте — доказать лишь, что кодировка была объявлена; это не говорит, куда реально идёт текст. А вот отрендерить страницу и измерить ограничивающую рамку нарисованных пикселей — говорит: прогон из семи знаков должен выйти примерно в один знак шириной и семь знаков высотой
Один вертикальный прогон на горизонтальной странице
DrawVerticalText рисует одиночный прогон вертикально и не трогает режим письма. Он принимает позицию, имя шрифта, размер и текст, поэтому и текущий выбор шрифта остаётся нетронутым. Корешковые наклейки, печати, одиночная колонка имён рядом с таблицей — это вертикальные исключения внутри горизонтального документа, и дёргать глобальный режим вкл-выкл вокруг каждого — больше состояния, чем заслуживает такая задача
// Один вертикальный прогон внутри в остальном горизонтальной страницы
if Lib.DrawVerticalText(520, 96, 'MS Mincho', 14, '保守点検記録') = 0 then
begin
// Начертание не удалось открыть либо не удалось набрать этот текст.
// LogLayoutProblem — ваша собственная подпрограмма, не часть библиотеки
LogLayoutProblem('vertical label on page ' + IntToStr(Lib.SelectedPage));
end;
// Горизонтальный текст вокруг него не затронут
Lib.DrawText(72, 96, 'Maintenance inspection record');
Страница, смешивающая оба направления, несёт начертание в своих ресурсах дважды — один раз с горизонтальной кодировкой, другой с Identity-V, — построенное из одной шрифтовой программы. У каждого направления свои метрики, поэтому страница японской книги с вертикальным основным текстом и горизонтальными колонтитулами или китайское свидетельство с вертикальным заголовком над горизонтальными строками деталей выходит с правильными интервалами в обоих направлениях
Возвращаемое значение — причина, по которой мы тянемся к DrawVerticalText даже в документах, по большей части вертикальных. В рисовании через режим прогон, который не удаётся набрать вертикально, рисуется обычным горизонтальным способом вместо того, чтобы быть выброшенным, а DrawText сообщает успех в обоих случаях. Для основного текста это лучший вариант по умолчанию: лишняя горизонтальная строка легко ловится на цветной пробе, а пропавший абзац — нет. Для надписи, чьё направление и есть весь смысл, берите DrawVerticalText и считайте 0 сбоем вёрстки
Раскладка колонок справа налево
Порядок колонок решаете вы. Вертикальный текст японского и традиционного китайского читается колонка за колонкой справа налево, поэтому страница начинается у правого поля, и каждая новая колонка садится левее предыдущей. Ни одна настройка режима письма не выведет это из текста, и то же верно для порядка страниц в книге, сшитой справа, и для того, где место фуригане, сноскам и подписям к рисункам
Подпрограмма ниже разбивает строку на колонки, умещающиеся в фиксированную высоту, меряя через GetVerticalTextHeight на ходу и шагая влево на полтора em за колонку:
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
// Предложенная предохранительная проверка, не библиотечная: нулевая высота означает, что начертание
// не удалось открыть, а цикл ниже никогда не сломает колонку
if Lib.GetVerticalTextHeight(Face, Size, Text) <= 0 then
Exit;
X := Right; // первая колонка у правого поля
Col := '';
for I := 1 to Length(Text) do // предполагает отсутствие суррогатных пар
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; // следующая колонка левее
Col := Text[I];
end
else
Col := Trial;
end;
if Col <> '' then
Lib.DrawVerticalText(X, Top, Face, Size, Col);
end;
Повторное измерение пробной строки на каждом знаке медленнее суммирования продвижений, но меряет ровно то, что будет нарисовано, — с подменами. Чего цикл не делает, так это правил кансоку. Закрывающая скобка или 。 в вершине колонки или открывающая скобка, брошенная в её подножии, — для японского читателя ошибка набора, и правка означает вытянуть или втолкнуть знак через разрыв до рисования. Суррогатные пары требуют той же аккуратности: индексация Text[I] режет знак вне базовой многоязычной плоскости пополам, поэтому шагайте по кодовым точкам, если ваш текст может такую содержать
Для документов, собираемых из данных, разбор статьи о поиске текста и перечислении элементов страницы показывает, как прочитать назад то, что легло на каждую страницу, — быстрый способ убедиться, что каждая колонка несёт именно те знаки, что вы имели в виду
Какой шрифт должен использовать вертикальный документ?
CJK-начертание с собственными вертикальными метриками и функцией vert или vrt2. Без них вы получите прямые знаки, шагающие по em за раз, без смены форм. Когда вертикальный текст выглядит едва заметно неправильным, осмотрите шрифт раньше кода. Просмотрщик таблиц, перечисляющий функции GSUB начертания и его таблицы vhea и vmtx, за минуту скажет, способно ли начертание справиться
DrawVerticalText и GetVerticalTextHeight открывают установленный шрифт по имени через Windows. В сборках Lazarus для других платформ вертикальное письмо идёт через SetVerticalWritingMode и DrawText, которые работают от программы загруженного и выбранного вами шрифта, поэтому те же вертикальные формы и метрики применяются, пока этот шрифт — то CJK-начертание, которое вы и имели в виду
Встраивание следует обычным правилам и обычным ценам. Полное CJK-начертание тянет на многие мегабайты, поэтому подмножество — разумный вариант по умолчанию для любого документа, покидающего вашу машину. Чего ожидать, описывают заметки о оптимизации размера PDF и подмножестве шрифтов, а случай ремонта, когда вертикальный документ приходит без своих шрифтов, охватывает разбор статьи о встраивании отсутствующих шрифтов в существующий PDF
Вертикальное CJK-писание живёт в том же API рисования, что и остальные текстовые функции PDF Library for Delphi — нативной Pascal-библиотеки PDF для Delphi, C++Builder и Lazarus. Полный набор шрифтовых и текстовых возможностей перечислен рядом на странице продукта PDF Library for Delphi