Анатомия файла PDF
Откройте любой PDF в обычном текстовом редакторе, и первое, что вы увидите, обнадёживает:
заголовок %PDF-1.x или %PDF-2.0. Последнее, что вы увидите, — %%EOF.
Всё между этими двумя строками — это аккуратная маленькая машина для поиска объектов
по номеру. Эта страница — препарирование. Мы вскрываем файл, называем каждый орган и
показываем, как они соединены.
Это структурный спутник двух соседей. Что на самом деле представляет собой PDF трактует файл как объектный граф; инкрементные обновления рассказывают, как он растёт со временем. Эта страница держится близко к байтам — физические области, по которым проходит парсер, в том порядке, в каком они лежат на диске.
Почему это важно
Заголовок раздела «Почему это важно»Вам почти никогда это не нужно, чтобы использовать PDF. Это нужно в тот день, когда что-то идёт не так. Файл открывается в одной программе просмотра и не открывается в другой; валидатор сообщает о «повреждённой таблице перекрёстных ссылок»; подписанный документ внезапно не проходит проверку. Ничто из этого не загадка, как только вы умеете читать анатомию. Это число, которое больше не совпадает с позицией, область не на своём месте или хвост, указывающий в никуда.
Знание схемы превращает «PDF повреждён» в диагноз, по которому можно действовать. Это разница между беспомощным пожиманием плечами перед чёрным ящиком и указанием на конкретный байт, который лжёт.
Если коротко
Заголовок раздела «Если коротко»Соответствующий стандарту PDF имеет четыре физические части в этом файловом порядке (Spec: ISO 32000-2, §7.5.1ISO 32000-2 §7.5.1):
- Заголовок — одна строка,
%PDF-2.0, называющая версию. - Тело — основная масса файла: последовательность нумерованных косвенных объектов.
- Раздел перекрёстных ссылок — индекс от номера объекта к смещению в байтах, по которому живёт этот объект. Классические PDF используют текстовую таблицу; PDF 2.0 использует сжатый поток перекрёстных ссылок (xref stream).
- Трейлер — небольшой словарь, называющий точку входа, за которым следуют
startxref, смещение и%%EOF.
Подвох: программа чтения не начинает сверху. Она начинает снизу,
читает startxref, чтобы найти индекс, и использует этот индекс, чтобы добраться до любого объекта
напрямую. Файл записывается от начала к концу, но читается от конца к началу.
Как это реализовано в NextPDF
Заголовок раздела «Как это реализовано в NextPDF»Давайте пройдём четыре области по порядку, держа байты перед глазами.
Заголовок — это одна строка. NextPDF пишет %PDF-2.0 и, по соглашению,
вторую строку-комментарий из байтов со старшим битом, чтобы наивные инструменты переноса трактовали файл
как двоичный, а не текст. Эта вторая строка и есть причина, по которой PDF, открытый как обычный текст, показывает
небольшую кашу сразу после версии.
Тело — это место, где живёт документ. Каждый косвенный объект — это номер,
генерация, ключевое слово obj, значение и endobj
(Spec: ISO 32000-2, §7.3.10ISO 32000-2 §7.3.10). Значение — это одна из нескольких
форм, но две несут почти весь вес:
- Словарь,
<< /Key value … >>, — это отображение имён в значения. Дерево страниц, каталог, дескрипторы шрифтов — всё это словари. - Поток — это словарь, за которым следуют
stream, блок произвольных байтов иendstream. Содержимое страниц, встроенные шрифты и изображения — это потоки, почти всегда сжатые. (Их фильтры — отдельная история, рассказанная в потоки и фильтры.)
Объекты указывают друг на друга косвенной ссылкой — 2 0 R означает «объект 2,
генерация 0». Это и есть та проводка, которая превращает плоский список объектов в
граф.
Раздел перекрёстных ссылок — это часть, которую большинство людей никогда правильно не
представляют. В классической форме это обычный текст: ключевое слово xref, затем
подразделы из строк фиксированной ширины по 20 байт (Spec: ISO 32000-2, §7.5.4ISO 32000-2 §7.5.4). Каждая строка — это десятизначное смещение в байтах, пятизначная
генерация и один флаг — n для используемого, f для свободного — дополненные до
ровно двадцати байт, чтобы программа чтения могла перейти к любой записи одной лишь арифметикой.
PDF 2.0 заменяет это потоком перекрёстных ссылок: тот же индекс, но
двоичный и сжатый внутри объекта-потока, помеченного /Type /XRef
(Spec: ISO 32000-2, §7.5.8ISO 32000-2 §7.5.8). Меньше по размеру и способен
описывать объекты, упакованные внутри потоков объектов.
Трейлер — это оглавление файла (Spec: ISO
32000-2, §7.5.5ISO
32000-2 §7.5.5). Он называет /Root — каталог документа, тот
единственный объект, с которого свисает всё остальное, — и /Size, число объектов.
Затем идёт рукопожатие, которое делает возможным чтение в обратном порядке:
startxref, смещение в байтах на собственной строке и %%EOF. Программа чтения переходит в
конец, читает это смещение, прыгает прямо к разделу перекрёстных ссылок и
отправляется в путь.
- HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
- BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
- Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
- TrailerNames /Root and /Size, then startxref + offset + %%EOF.
- Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
Есть пятая область, которую долгоживущий файл наращивает: инкрементное обновление.
Изменение не записывается на месте. Изменённые объекты, свежий раздел перекрёстных
ссылок и новый трейлер добавляются после первого %%EOF, и этот
новый трейлер несёт /Prev — смещение предыдущего раздела перекрёстных
ссылок (Spec: ISO 32000-2, §7.5.6ISO 32000-2 §7.5.6). Разделы образуют
обратную цепочку; для любого номера объекта побеждает самая новая запись. Поскольку
исходные байты никогда не сдвигаются, криптографическая проверка по диапазону байтов, который
подпись фактически охватывает, по-прежнему держится после обновления. Более позднее инкрементное
обновление всё ещё может изменить то, что валидатор сообщает о документе в
целом — разрешены ли изменения после подписания и что подпись
считается удостоверяющей, — но оно не может изменить сами подписанные байты.
Это свойство — целиком предмет инкрементных
обновлений.
Практический пример
Заголовок раздела «Практический пример»Вот вся анатомия в одном минимальном файле. Числа под xref — это
смещения в байтах, и они должны быть точными: укажите на один символ дальше начала
объекта — и строгая программа чтения сдаётся.
%PDF-2.01 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>endobjxref0 40000000000 65535 f0000000009 00000 n0000000058 00000 n0000000115 00000 ntrailer<< /Size 4 /Root 1 0 R >>startxref186%%EOFЧитайте его так, как это делает парсер. Последняя строка: %%EOF. Над ней: startxref 186,
поэтому переходим к байту 186, где начинается xref. Таблица говорит, что объект 1 живёт по
байту 9. /Root 1 0 R трейлера указывает туда — каталог — и из
каталога вы идёте по /Pages к дереву страниц и находите единственную страницу. Объект 0
всегда является головой списка свободных с генерацией 65535, окаменелостью
первого замысла формата, которую каждая программа чтения по-прежнему ожидает увидеть.
Распространённое заблуждение
Заголовок раздела «Распространённое заблуждение»Ловушка — читать PDF как историю: сверху вниз, по порядку. Это не
история; это индекс с обратным указателем. Номера объектов не обязаны быть
последовательными в файле, объекты могут идти в любом физическом порядке, и программа чтения
никогда не полагается на их позицию. Единственная авторитетная схема — это
раздел перекрёстных ссылок, и единственный способ найти эту схему — смещение startxref
в самом конце.
Следствие удивляет людей. PDF с безупречным телом и одной неправильной
цифрой в startxref нечитаем — программа чтения не может найти индекс. PDF
с объектами в перепутанном порядке, но с правильным разделом перекрёстных ссылок
совершенно в порядке. Физическая позиция не несёт смысла. Записанная позиция
несёт его весь.
Ограничения и границы
Заголовок раздела «Ограничения и границы»Эта страница описывает физическую структуру, а не содержимое страницы. Как метки ложатся на
страницу — операторы потока содержимого, отображение текста, состояние графики — это отдельный
предмет. Она также описывает корректно сформированный файл. Реальные PDF
часто бывают чуть-чуть сломаны и выживают лишь потому, что снисходительные программы просмотра перестраивают
таблицу перекрёстных ссылок, сканируя ключевые слова obj. Это спасение — поведение
программы просмотра, а не то, что гарантирует формат.
| Edition | Availability |
|---|---|
| Core | NextPDF — это модуль записи. Он записывает каждое смещение из буфера вывода в момент выдачи каждого объекта, поэтому файлы, которые он производит, имеют раздел перекрёстных ссылок, совпадающий с телом по построению. |
| Pro | Разбор, реконструкция или восстановление повреждённой таблицы перекрёстных ссылок в файле, который NextPDF не записывал, вне области охвата во всех редакциях. |
| Enterprise | Для инспекции структуры существующего файла используйте выделенный парсер или валидатор; NextPDF гарантирует корректность для того, что он пишет, а не для того, что он читает. |
Мини-FAQ
Заголовок раздела «Мини-FAQ»Зачем у PDF есть строка двоичного маркера после заголовка? Некоторые старые инструменты переноса искажали файл, который считали обычным текстом. Комментарий со старшим битом делает файл однозначно двоичным, поэтому он переживает путешествие без изменений.
Поток xref — это просто таблица поменьше? В основном, с одной дополнительной возможностью. Помимо того что он сжат, поток xref может описывать объекты, хранящиеся внутри потоков объектов — записи, которые классическая 20-байтовая текстовая таблица никак не способна выразить.
Можно ли иметь в одном файле и таблицу, и поток? Одна редакция использует то или другое. Но гибридный файл может сочетать классическую таблицу для старых программ чтения с потоком перекрёстных ссылок для новых, так что каждый вид программы чтения находит понятный ему индекс.
Связанные документы
Заголовок раздела «Связанные документы»- Что на самом деле представляет собой PDF — те же четыре части, увиденные как объектный граф, а не физическая схема.
- Инкрементные обновления и зачем они нужны — как добавленная пятая область наращивает файл и защищает подписи.
- Потоки и фильтры — что находится внутри объектов-потоков тела и как они сжаты.
- PDF 2.0: что изменилось — почему поток перекрёстных ссылок является структурой по умолчанию, которую пишет NextPDF.
Глоссарий
Заголовок раздела «Глоссарий»- Заголовок — первая строка,
%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, ведущим назад к предыдущему разделу.