Что такое xref
Xref — таблица перекрёстных ссылок: список байтовых смещений, по которому просмотрщик мгновенно находит нужный объект, не читая документ целиком. Программа открывает файл и читает не начало, а конец: там записано ключевое слово startxref и адрес таблицы. Дальше она прыгает по этому адресу, берёт из трейлера ссылку на каталог, из каталога добирается до дерева страниц. Сдвиньте байты — и документ перестанет открываться, хотя внутри цело всё до последнего объекта.
Коротко о цифрах
- Где записан адрес таблицы
- ключевое слово startxref в конце файла
- Что хранит запись
- смещение объекта в байтах от начала документа
- Две реализации
- классическая таблица и xref-поток, начиная с PDF 1.5
- Успешный код qpdf
- 3 — предупреждения были, файл собран; 2 — отказ
- Метка в отчёте о починке
- XREF_REBUILT — таблица построена заново
Путь от последней строки файла до первой страницы
- Просмотрщик читает хвост файла и ищет там startxref — последнюю осмысленную запись перед маркером конца.
- За ключевым словом стоит число: смещение таблицы в байтах, отсчитанное от самого первого байта документа.
- По этому адресу читается таблица — по строке на каждый объект, с его смещением и признаком «занят» или «свободен».
- Рядом лежит трейлер со ссылкой на каталог документа, а в каталоге — ссылка на дерево страниц.
- Нужная страница достаётся одним переходом по смещению. Именно поэтому документ на тысячу листов открывается так же быстро, как на две.
Две формы одной таблицы
Классическая таблица — обычный текст: заголовок подраздела, а под ним записи фиксированной длины, где смещение дополнено нулями слева. Её видно в любом текстовом редакторе, и именно она стоит в файлах, которые сохранены без сжатия структуры.
Начиная с PDF 1.5 таблицу разрешено хранить как обычный объект-поток, упакованный тем же фильтром, что и содержимое страниц. Это компактнее и позволяет прятать мелкие объекты внутрь общих контейнеров, но глазами такую таблицу уже не прочесть. Встречаются и гибридные файлы, где обе формы лежат рядом ради старых просмотрщиков.
Отчего смещения оказываются неверными
- Файл правили как текст: заменили одну строку на строку другой длины, и всё, что стоит после неё, уехало.
- Закачка оборвалась. Конец файла не пришёл, а значит, не пришли ни startxref, ни сама таблица.
- Два PDF слепили конкатенацией: во втором смещения считаны от его собственного начала, а не от начала общего файла.
- Инкрементальное сохранение прервалось на середине, и цепочка ссылок на предыдущую таблицу оборвалась.
- Генератор документа сам записал неверное смещение — редкий, но не экзотический случай у самописных выгрузок.
Что делает восстановление
Починка не пытается исправить сбитые числа. Она читает файл насквозь, отмечает каждое место, где начинается объект, и строит таблицу заново по тому, что нашла. Смещения после этого верны по построению — они получены измерением, а не переписаны из старого файла.
У qpdf, который делает эту работу, код возврата 3 означает «предупреждения были, документ собран», и это успех, а не отказ. Настоящая ошибка — код 2: она приходит, когда сплошной просмотр не нашёл ни трейлера, ни связного графа объектов. В отчёте о починке перестроенная таблица помечается как XREF_REBUILT, и это самая частая находка из всех.
Частые вопросы
Можно ли починить xref вручную в текстовом редакторе?
Теоретически да, практически нет. Любая правка сдвигает все последующие смещения, поэтому пересчитывать придётся всю таблицу целиком, а не одну строку. Сплошной просмотр файла делает то же самое за один проход и без арифметики.
Что означает сообщение «can't find startxref»?
Программа дочитала до конца файла и не нашла там указателя на таблицу. Чаще всего это оборванная закачка или обрезанный при копировании файл, реже — мусор, дописанный в хвост почтовым шлюзом или архиватором.
Влияет ли таблица на вес документа?
Заметно только на мелких файлах. Каждая запись занимает два десятка байт, поэтому у документа с сотней объектов таблица весит около двух килобайт, а у сотни страниц с картинками она теряется на фоне самих изображений.
Почему после восстановления файл изменился в размере?
Документ записан заново: объекты уложены подряд, старые версии страниц из инкрементальных сохранений выброшены, таблица построена с нуля. Файл может стать и меньше исходного, и немного больше, если структура была упакована сильнее.