Найти тему
Dmitry Sieg | Ваш IT-гуру

Создание PDF-файлов в блокноте, часть 1. Основа документа.

Оглавление

Введение

Привет! Я решил записать свой опыт написания PDF-файла вручную. Вся фишка этого эксперимента в том, что я решил не пользоваться для этого никакими сложными редакторами, наподобие Acrobat Pro, или генерацией файла в коде программы. Почему — это отдельная история. Скажу вкратце, изначально я сверстал свое резюме в Figma, и экспортировал его оттуда в PDF, но меня не устроил размер файла аж в 4Мб. Даже для пересыла по email на современных скоростях это перебор. Я пробовал использовать как различные плагины к Figma для экспорта, так и сайты, предлагающие сжатие PDF, и результат меня все равно не устроил.

Затем мне пришла в голову мысль о том, что полноценный PDF можно сделать прямо в блокноте. Я использовал для этого бесплатно распространяемую программу Notepad++. Ее можно скачать на официальном сайте; софтина достаточно полезная, даже при наличии в арсенале серьезных IDE, в некоторых случаях она выручает.

Началось это открытие с того, что я решил немного "поисследовать" файлы PDF и визуально проинспектировать, что же там внутри. Открыв случайный документ PDF в Notepad++, я увидел такую удручающую картину:

Содержимое какого-то PDF-файла из моих загрузок
Содержимое какого-то PDF-файла из моих загрузок

Ужас! Ладно бы еще эти греческие символы в начале, но внизу мы видим явную бинарщину, набирать которую вручную очень бы не хотелось..

Что ж, обратимся к официальным источникам информации.

Открываем спецификацию формата PDF от Adobe. Вбиваем в гугле "document management — Portable document format — Part 1: PDF 1.7", обнаруживаем первым же в выдаче документ по ссылке.

Содержание спецификации
Содержание спецификации

В подобных документах, я первым же делом лезу в раздел "File Structure". Переходим в соответствующий параграф, и находим абзац:

Последний абзац параграфа 7.5.2 File header
Последний абзац параграфа 7.5.2 File header

Это говорит о том, что файл PDF может быть как бинарным, так и полностью текстовым! Что как раз нам и надо для написания его от начала до конца, вообще не задействуя программный код для генерации бинарного формата.

Заголовок, он же Header

Начнем с части файла, определяющей его суть, а именно с заголовка. В PDF с этим все просто, нет никаких структур данных для этой части, есть только сигнатура, ее мы и введем в нашем новом документе:

Заголовок PDF-файла
Заголовок PDF-файла

Основная часть

Читаем следующий параграф

-5

Здесь описана основная часть PDF-файла, а именно серия объектов с их описаниями и иерархией. Звучит сложно, но на деле это действительно просто пронумерованная последовательность, как, например, список со ссылками.

В одном из PDF, которые я изучал в блокноте, первым же объектом шло описание файла — то, что в спецификации названо Document Information Dictionary (параграф 14.3.3). На самом деле, сначала я его тупо скопировал из другого файла, изменив метаданные под свой документ. И только сейчас я заметил, что в спеке этот раздел указан как необязательный! Но для порядка оставим его в нашем документе.

-6

Сразу замечу, что мы только что написали объект типа Dictionary, он же словарь. Этот тип данных схож с подобными структурами в языках программирования, для непосвященных — просто набор слов "ключ"—"значение". В PDF ключ начинается со слеша, а значение оборачивается в скобки. Словари нам будут встречаться почти во всех описаниях объектов.

Что ж, поехали дальше. Вслед за разделом "File Structure", логично посмотреть "Document Structure" (параграф 7.7).

-7

Наша следующая задача — написать объект Document Catalog. Т.к. мы делаем минимальный документ, который можно открыть в просмотре, например, в браузере Chrome, сделаем ровно одну пустую страницу. Да-да, для первого раза никаких Hello, World-ов! Оставим различные элементы содержимого для следующих статей.

Document Catalog в моем случае состоит из ссылки на массив страниц.

-8

В PDF, как мы видим, интересная нумерация объектов. Возможно я посвящу ей отдельную статью, а сейчас просто будем следовать тому, что я подсмотрел в готовых PDF: объекты нумеруются числами вроде "1 0", а ссылки на них выглядят как "1 0 R".

Следующим простым шагом зададим сам массив страниц, указав их количество и массив ссылок на объекты страниц, состоящий в нашем случае из одного элемента.

-9

Далее задаем нашу единственную первую страницу. Обязательными элементами этого объекта, следуя спецификации являются:

  • Parent — ссылка на объект Pages.
  • MediaBox — описание размеров страницы, в единицах Points. Пока просто скопируем его из существующих PDF размером A4.
  • Resources — содержимое страницы.

А если я хочу сделать пустую страницу? Смотрим описание последнего поля в параграфе 7.7.3.3 Page Objects:

-10

Т.е. в качестве объекта Resources подсунем в наш документ пустой словарь. Получилось так:

-11

Всё, с основным содержимым документа мы закончили.

Таблица ссылок

Следующей обязательной частью PDF является так называемая xref, или таблица ссылок на объекты. Сейчас мы не будем углубляться в подробности, зачем она нужна. Я просто сделал минимальную таблицу с одной записью, следуя спецификации. Этого достаточно, чтобы документ открывался в программах.

-12

Завершающий элемент

В параграфе "File Structure" в качестве обязательного указан также раздел Trailer. Это словарь, в котором указаны суммарные данные для чтения файла. Самая неприятная часть тут — это необходимость указать в байтах смещение от начала файла до объекта xref. Учитывая то, что я не использовал Unicode, 1 символ в нашем файле = 1 байту. Поэтому просто выделим в блокноте всё содержимое до слова xref, и посмотрим внизу в индикатор "Sel: ", в первое значение. Это и укажет нам необходимую величину смещения. В моем случае это оказалось 434 байта.

В самом словаре указывается:

  • /Root — ссылка на объект Document Catalog.
  • /Info — ссылка на объект Info (опять же, как оказалось, опциональная).
  • /Size — общее число объектов в основной части файла.

Затем следует слово startxref и уже указанное количество байт до самого xref-а.

Заканчивается любой PDF-файл записью %%EOF. На всякий случай добавим вслед за ним перенос строки, подсмотренный в конце имеющихся документов. Без него открывать файл я не пробовал, будет любопытно узнать ваш опыт.

В итоге полный файл:

-13

Сохраняем его с расширением .pdf и затаив дыхание открываем в браузере.

-14

Ура! Всё работает. Мы только что сгенерили документ в легендарном формате PDF собственными руками.