Введение
Продолжаю эксперимент с написанием PDF-файлов вручную. В этот раз я покажу, как добавить текст в наш документ.
Как и в предыдущий раз, проведем исследование, используя спецификацию PDF от Adobe, найденную нами в предыдущей статье. Документ расположен по этой ссылке. Пробежимся по оглавлению и обратим внимание на подзаголовок "Text". Пролистав чуть вниз, сразу находим пример использования:
Заметив загадочные похожие слова "Tf, Td, Tj", начинаю искать подробности о них, и вижу упоминание "Tf operator". Похоже, что для задания текста в документе используется конструкция вида BT...ET, а внутри набор операторов с параметрами. Но что же это за тип объекта, который не похож на словарь?
Потоки
Проведя поиск по спеке по конструкциям "Tf" и "BT", я обнаруживаю следующий пример (Example 3 из раздела 8.7.3.3):
Итак, конструкция, задающая текст, должна находится в объекте типа stream, он же поток. В спецификации можно найти раздел 7.8, посвященный этому типу данных.
Там же находим подтверждение тому, что текст должен находится в потоке.
Осталось понять, как связать нашу пустую страницу, объект ее содержимого, шрифт текста и сам текст.
Шрифты
Помните, в предыдущей статье я задал поле /Resources на странице как пустой словарь? Теперь пришло время добавить ресурсов в наш документ.
Значение поля оставим неизменным:
А в пустой словарь "5 0" добавим ссылку на объект со шрифтом (я скопировал код из Example 2 части 9.2.2 "Basics of Showing Text"):
Вслед за ним, я добавил сам объект шрифта:
Там же, чуть выше Example 2, находим краткое описание данного словаря:
Здесь дается намек на то, что шрифт может быть включен в тело самого документа, и что в нашем случае имя шрифта указывает на имеющийся в системе шрифт "Helvetica".
Объект с текстом
Итак, настал момент добавления самого текста. Я скомбинировал примеры из 8.7.3.3 и 9.2.2 и на всякий случай добавил словарь с полем /Length, отмерив длину стрима от BT до ET включительно уже знакомым приемом с индикатором Sel в блокноте. Вот, что у меня получилось:
Оператор Tf в этом потоке задает используемый шрифт, Td — смещение относительно начала страницы, и Tj — сам текст.
Больше новых объектов нам не потребуется. Но необходимо скорректировать некоторые поля в PDF, т.к. мы изменили его длину и следовательно смещение xref. Изменения, которые я сделал в конце файла:
Открываем полученный документ, но в нужном месте текста не видно... он оказался внизу документа!
Однако, замечаем, что отступы от нижней и от левой части равны. Можно догадаться, что система координат в PDF начинается по умолчанию от нижнего левого края страницы, и оси координат имеют направления "вправо-вверх" как в классической геометрии. Под примером 2 из 9.2.2 есть такое замечание:
Похоже, мы можем управлять системой координат и даже матрицей ее преобразования.
Заглянем в оглавление, и от него в раздел 8.3 "Coordinate Systems". Упоминалась система координат "user system", находим соответствующее ей описание:
Пробую поэкспериментировать с упомянутым полем Rotate, чтобы координаты задавались от левого верхнего угла, как в привычной системе координат в программировании, но при значении 180 развернутыми оказываются не оси координат, а страница и текст, как при повороте бумажного листа.
Это совсем не то, что я ожидал увидеть. Поискав в интернете по ключевым словам "pdf coordinate system axis", я нашел вопрос на stackoverflow, посвященный данной проблеме. Там разработчик также задается вопросом: как сменить точку отсчета и направление осей в стандартной системе координат PDF. В одном из ответов упоминается оператор потока cm, служащий для смены матрицы трансформации (transformation matrix) на текущей странице.
Справку по этому оператору я нашел в спецификации в разделе 8.4.4 "Graphics State Operators", где также дается ссылка на описание всей математической части 8.3.2 "Coordinate Spaces" и 8.3.4 "Transformation Matrices". Не особо мудрствуя, я добавил в наш поток матрицу по аналогии с ответом на stackoverflow:
Где -1 означает смену направления оси Y, а 841.89 — смещение точки отсчета на одну высоту страницы вверх (относительно старого направления).
Скорректировав смещение xref, получаем следующую картину:
Это уже намного ближе к нашей цели! Осталось скорректировать направление текста. Там же, на stackoverflow, упоминалось о необходимости смены матрицы направления текста (text transformation matrix). Зададим эту матрицу с фактором scalingY = -1, и смещением по оси Y, равное высоте шрифта. Оператор для задания текстовой матрицы называется Tm.
К сожалению, мне не удалось задать матрицы преобразований так, чтобы в операторе Td ось Y была повернута вниз. Но хотя бы точку отсчета мы сдвинули, и можем пользоваться привычной системой координат, только изменяя знак по оси Y. Теперь надпись находится в нужном месте и выглядит корректно, при этом по заданным координатам находится верхний левый угол текста, аналогично разметке HTML:
Конечно, мы могли бы не устраивать возню с матрицами, и просто высчитывать нужные координаты в стандартной системе, вычитая Y из размера страницы. Но в нашем примере, мы облегчили работу с привычными координатами, а заодно посмотрели возможности формата PDF и затронули раздел, связанный с графикой, что пригодится в дальнейшем.
В следующей статье я расскажу больше о верстке и, возможно, о включении графических объектов в наш документ.
P.S. актуальную версию документа с историей версий я выложил на Github по ссылке, откуда его можно скачать и поэкспериментировать.