Перейти к содержимому
getnextpdf.com

Анатомия файла PDF

Откройте любой PDF в обычном текстовом редакторе, и первое, что вы увидите, обнадёживает: заголовок %PDF-1.x или %PDF-2.0. Последнее, что вы увидите, — %%EOF. Всё между этими двумя строками — это аккуратная маленькая машина для поиска объектов по номеру. Эта страница — препарирование. Мы вскрываем файл, называем каждый орган и показываем, как они соединены.

Это структурный спутник двух соседей. Что на самом деле представляет собой PDF трактует файл как объектный граф; инкрементные обновления рассказывают, как он растёт со временем. Эта страница держится близко к байтам — физические области, по которым проходит парсер, в том порядке, в каком они лежат на диске.

Вам почти никогда это не нужно, чтобы использовать PDF. Это нужно в тот день, когда что-то идёт не так. Файл открывается в одной программе просмотра и не открывается в другой; валидатор сообщает о «повреждённой таблице перекрёстных ссылок»; подписанный документ внезапно не проходит проверку. Ничто из этого не загадка, как только вы умеете читать анатомию. Это число, которое больше не совпадает с позицией, область не на своём месте или хвост, указывающий в никуда.

Знание схемы превращает «PDF повреждён» в диагноз, по которому можно действовать. Это разница между беспомощным пожиманием плечами перед чёрным ящиком и указанием на конкретный байт, который лжёт.

Соответствующий стандарту PDF имеет четыре физические части в этом файловом порядке (Spec: ISO 32000-2, §7.5.1):

  1. Заголовок — одна строка, %PDF-2.0, называющая версию.
  2. Тело — основная масса файла: последовательность нумерованных косвенных объектов.
  3. Раздел перекрёстных ссылок — индекс от номера объекта к смещению в байтах, по которому живёт этот объект. Классические PDF используют текстовую таблицу; PDF 2.0 использует сжатый поток перекрёстных ссылок (xref stream).
  4. Трейлер — небольшой словарь, называющий точку входа, за которым следуют startxref, смещение и %%EOF.

Подвох: программа чтения не начинает сверху. Она начинает снизу, читает startxref, чтобы найти индекс, и использует этот индекс, чтобы добраться до любого объекта напрямую. Файл записывается от начала к концу, но читается от конца к началу.

Давайте пройдём четыре области по порядку, держа байты перед глазами.

Заголовок — это одна строка. NextPDF пишет %PDF-2.0 и, по соглашению, вторую строку-комментарий из байтов со старшим битом, чтобы наивные инструменты переноса трактовали файл как двоичный, а не текст. Эта вторая строка и есть причина, по которой PDF, открытый как обычный текст, показывает небольшую кашу сразу после версии.

Тело — это место, где живёт документ. Каждый косвенный объект — это номер, генерация, ключевое слово obj, значение и endobj (Spec: ISO 32000-2, §7.3.10). Значение — это одна из нескольких форм, но две несут почти весь вес:

  • Словарь, << /Key value … >>, — это отображение имён в значения. Дерево страниц, каталог, дескрипторы шрифтов — всё это словари.
  • Поток — это словарь, за которым следуют stream, блок произвольных байтов и endstream. Содержимое страниц, встроенные шрифты и изображения — это потоки, почти всегда сжатые. (Их фильтры — отдельная история, рассказанная в потоки и фильтры.)

Объекты указывают друг на друга косвенной ссылкой2 0 R означает «объект 2, генерация 0». Это и есть та проводка, которая превращает плоский список объектов в граф.

Раздел перекрёстных ссылок — это часть, которую большинство людей никогда правильно не представляют. В классической форме это обычный текст: ключевое слово xref, затем подразделы из строк фиксированной ширины по 20 байт (Spec: ISO 32000-2, §7.5.4). Каждая строка — это десятизначное смещение в байтах, пятизначная генерация и один флаг — n для используемого, f для свободного — дополненные до ровно двадцати байт, чтобы программа чтения могла перейти к любой записи одной лишь арифметикой. PDF 2.0 заменяет это потоком перекрёстных ссылок: тот же индекс, но двоичный и сжатый внутри объекта-потока, помеченного /Type /XRef (Spec: ISO 32000-2, §7.5.8). Меньше по размеру и способен описывать объекты, упакованные внутри потоков объектов.

Трейлер — это оглавление файла (Spec: ISO 32000-2, §7.5.5). Он называет /Root — каталог документа, тот единственный объект, с которого свисает всё остальное, — и /Size, число объектов. Затем идёт рукопожатие, которое делает возможным чтение в обратном порядке: startxref, смещение в байтах на собственной строке и %%EOF. Программа чтения переходит в конец, читает это смещение, прыгает прямо к разделу перекрёстных ссылок и отправляется в путь.

  1. HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
  2. BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
  3. Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
  4. TrailerNames /Root and /Size, then startxref + offset + %%EOF.
  5. Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

Есть пятая область, которую долгоживущий файл наращивает: инкрементное обновление. Изменение не записывается на месте. Изменённые объекты, свежий раздел перекрёстных ссылок и новый трейлер добавляются после первого %%EOF, и этот новый трейлер несёт /Prev — смещение предыдущего раздела перекрёстных ссылок (Spec: ISO 32000-2, §7.5.6). Разделы образуют обратную цепочку; для любого номера объекта побеждает самая новая запись. Поскольку исходные байты никогда не сдвигаются, криптографическая проверка по диапазону байтов, который подпись фактически охватывает, по-прежнему держится после обновления. Более позднее инкрементное обновление всё ещё может изменить то, что валидатор сообщает о документе в целом — разрешены ли изменения после подписания и что подпись считается удостоверяющей, — но оно не может изменить сами подписанные байты. Это свойство — целиком предмет инкрементных обновлений.

Вот вся анатомия в одном минимальном файле. Числа под xref — это смещения в байтах, и они должны быть точными: укажите на один символ дальше начала объекта — и строгая программа чтения сдаётся.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

Читайте его так, как это делает парсер. Последняя строка: %%EOF. Над ней: startxref 186, поэтому переходим к байту 186, где начинается xref. Таблица говорит, что объект 1 живёт по байту 9. /Root 1 0 R трейлера указывает туда — каталог — и из каталога вы идёте по /Pages к дереву страниц и находите единственную страницу. Объект 0 всегда является головой списка свободных с генерацией 65535, окаменелостью первого замысла формата, которую каждая программа чтения по-прежнему ожидает увидеть.

Ловушка — читать PDF как историю: сверху вниз, по порядку. Это не история; это индекс с обратным указателем. Номера объектов не обязаны быть последовательными в файле, объекты могут идти в любом физическом порядке, и программа чтения никогда не полагается на их позицию. Единственная авторитетная схема — это раздел перекрёстных ссылок, и единственный способ найти эту схему — смещение startxref в самом конце.

Следствие удивляет людей. PDF с безупречным телом и одной неправильной цифрой в startxref нечитаем — программа чтения не может найти индекс. PDF с объектами в перепутанном порядке, но с правильным разделом перекрёстных ссылок совершенно в порядке. Физическая позиция не несёт смысла. Записанная позиция несёт его весь.

Эта страница описывает физическую структуру, а не содержимое страницы. Как метки ложатся на страницу — операторы потока содержимого, отображение текста, состояние графики — это отдельный предмет. Она также описывает корректно сформированный файл. Реальные PDF часто бывают чуть-чуть сломаны и выживают лишь потому, что снисходительные программы просмотра перестраивают таблицу перекрёстных ссылок, сканируя ключевые слова obj. Это спасение — поведение программы просмотра, а не то, что гарантирует формат.

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF — это модуль записи. Он записывает каждое смещение из буфера вывода в момент выдачи каждого объекта, поэтому файлы, которые он производит, имеют раздел перекрёстных ссылок, совпадающий с телом по построению.
ProРазбор, реконструкция или восстановление повреждённой таблицы перекрёстных ссылок в файле, который NextPDF не записывал, вне области охвата во всех редакциях.
EnterpriseДля инспекции структуры существующего файла используйте выделенный парсер или валидатор; NextPDF гарантирует корректность для того, что он пишет, а не для того, что он читает.

Зачем у PDF есть строка двоичного маркера после заголовка? Некоторые старые инструменты переноса искажали файл, который считали обычным текстом. Комментарий со старшим битом делает файл однозначно двоичным, поэтому он переживает путешествие без изменений.

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

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

  • Заголовок — первая строка, %PDF-2.0, называющая версию; обычно за ней следует комментарий двоичного маркера.
  • Косвенный объект — нумерованный объект в теле, записанный N G obj … endobj, где N — номер объекта, а G — генерация.
  • Словарь — отображение << /Key value … >> имён в значения; самая распространённая форма объекта.
  • Поток — словарь плюс блок байтов между stream и endstream, используемый для содержимого, шрифтов и изображений.
  • Таблица перекрёстных ссылок (xref) — индекс от номера объекта к смещению в байтах; классически текстовая таблица по 20 байт на запись, поток /Type /XRef в PDF 2.0.
  • Трейлер — словарь, который называет /Root и /Size, находимый через смещение startxref в конце файла.
  • Инкрементное обновление — изменённые объекты, новый раздел перекрёстных ссылок и новый трейлер, добавленные после %%EOF, с /Prev, ведущим назад к предыдущему разделу.