Техническая статья

Вертикальный японский и китайский текст в PDF с Delphi

PDFlibPas рисует японский и китайский текст сверху вниз по странице. SetVerticalWritingMode включает вертикальное письмо, после чего обычные вызовы DrawText идут вниз, а GetVerticalWritingMode сообщает текущее состояние. До появления этого режима вертикальная раскладка текста означала размещение каждого знака вручную с надеждой, что интервалы будут выглядеть правильно

Вертикальное письмо — это не горизонтальный текст, повёрнутый на 90 градусов. Знаки остаются вертикальными, продвижение идёт вниз, а не поперёк, и ряд знаков полностью меняет форму — это та часть, которая отличает документ, читающийся естественно, от того, что японский читатель немедленно признаёт машинным

Что меняется внутри PDF

Текст, нарисованный этим способом, проходит через шрифт Type0 в режиме вертикального письма, несущий собственные вертикальные метрики начертания. Это важно в двух направлениях. Читалка продвигает каждый знак на расстояние, задуманное его дизайнером, а не на единый шаг, поэтому колонка имеет ритм, для которого гарнитура и была нарисована. И копирование текста возвращает исходные знаки, потому что вертикальный прогон — это всё ещё настоящий текст с корректным отображением, а не последовательность позиционированных глифов

Начертание, не несущее собственных вертикальных метрик, продвигается на один em на знак — то, что читалка сделала бы по умолчанию. Об этом запасном варианте стоит знать, потому что именно эту разницу вы увидите, когда документ корректно отображается с полноценным CJK-начертанием и выглядит механически расставленным с латинским начертанием, которое просто содержит немного каны

var
  Lib: TPDFlib;
  H: Double;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('MS Mincho', 1);      // 1 = embed the face
    Lib.SetTextSize(12);
    Lib.SetVerticalWritingMode(1);            // ordinary DrawText now runs down
    H := Lib.GetVerticalTextHeight('MS Mincho', 12, '第三章 保守点検');
    Lib.DrawText(480, 72, '第三章 保守点検');
    Lib.SetVerticalWritingMode(0);            // back to horizontal
    Lib.DrawText(72, 72 + H, 'Chapter 3');
    Lib.SaveToFile('manual-ja.pdf');
  finally
    Lib.Free;
  end;
end;

Почему скобки выглядят неправильно в вертикальном тексте?

Потому что у скобки две формы, и лишь одна из них уместна в колонке. Скобки, знак долготы гласной и малая кана рисуются иначе, когда текст идёт вниз по странице: круглые скобки поворачиваются, чтобы идти вдоль колонки, а не лежать поперёк неё, а знак долготы становится вертикальным штрихом. Нарисуйте горизонтальные формы в вертикальной колонке, и каждый из них будет лежать на боку

PDFlibPas берёт формы из собственной вертикальной особенности шрифта, поэтому каждое начертание поставляет то, что нарисовал его дизайнер, а не подстановку, угаданную по символу. Это различие важно для корректности: угаданная таблица подстановок верна для типовых случаев и ошибочна для начертаний, трактующих символ иначе, а начертание, не называющее вертикальных форм, рисуется точно так же, как прежде, вместо того чтобы принудительно проходить через таблицу, о которой оно не просило

GetVerticalTextHeight измеряет формы, которые действительно будут нарисованы, поэтому колонка, чьи знаки меняют форму, всё равно измеряется корректно. Измерение горизонтальных форм при отрисовке вертикальных — классический источник колонок, вылезающих за свой блок на несколько знаков

Рисование одного прогона без смены режима

DrawVerticalText рисует один прогон вертикально, принимая позицию, имя шрифта, размер и текст, и оставляет режим письма нетронутым. Используйте его для вертикального исключения внутри горизонтального документа — корешок, штамп, одну колонку имён — там, где переключение глобального режима вкл/выкл вокруг каждого вызова несёт больше состояния, чем того заслуживает задача

Горизонтальная и вертикальная формы одного начертания хранятся раздельно внутри, поэтому страница может нести обе без взаимных помех. Именно это делает смешанную страницу практичной: страницу японской книги с вертикальным основным текстом и горизонтальными колонтитулами или китайское свидетельство с вертикальным заголовком над горизонтальными строками реквизитов

// One vertical run inside an otherwise horizontal page
Lib.DrawVerticalText(520, 96, 'MS Mincho', 14, '保守点検記録');
// The horizontal text around it is unaffected
Lib.DrawText(72, 96, 'Maintenance inspection record');

Правильный выбор шрифта прежде всего остального

Вертикальное письмо целиком зависит от начертания. CJK-шрифт с корректными вертикальными метриками и вертикальной особенностью даёт корректный вывод без дополнительной работы; шрифт без них даёт вертикальные знаки, продвигающиеся на один em за шаг, без всякой смены форм. Если вертикальный текст выглядит едва заметно неправильным, исследуйте шрифт прежде, чем код

Встраивание следует обычным правилам и обычным затратам. Полное CJK-начертание велико, поэтому подмножество не является необязательным для документов, идущих куда-либо — заметки об оптимизации размера PDF и подмножестве шрифтов описывают, чего ожидать, а разбор встраивания отсутствующих шрифтов в существующий PDF — случай восстановления, когда вертикальный документ приходит без своих шрифтов

Где вертикальный текст по-прежнему требует решения о вёрстке от вас

Порядок колонок. Японский вертикальный текст идёт справа налево по колонкам, поэтому двухколоночная страница начинается у правого края, и никакая настройка режима письма не может вывести это из самого текста. То же относится к порядку страниц в документе, читаемом справа налево как целое, а также к тому, где размещаются фуригана, сноски и подписи к рисункам

Библиотека гарантирует, что каждый прогон расставлен корректно: правильные формы, правильные продвижения, извлекаемый текст. Куда эти прогоны попадают на странице — задача вёрстки, а для документов, собираемых из данных, разбор поиска текста и перечисления элементов страницы полезен для последующей проверки того, что на страницу легло именно то, что вы задумали

PDFlibPas — нативная Pascal-библиотека PDF для Delphi, C++Builder и Lazarus, а вертикальное CJK-писание является частью API рисования, а не надстройкой — см. страницу продукта PDFlibPas для списка текстовых и шрифтовых возможностей