Сегодня утром, пролистывая ленты социальных сетей, я уже в который раз встретил утверждение, что у PDF-документа есть максимально допустимый размер.
Подобное утверждение появилось на просторах интернета ещё в 2007 году. Этот твит является характерным примером постов с аналогичным заявлением, в которых оно преподносится как твёрдый факт без каких-либо подтверждающих свидетельств или объяснений. То есть мы должны просто принять, что один PDF может покрыть лишь около половины площади Германии, и нам никак не объясняют, почему его магический предел составляет 381 километр.
Тут мне стало интересно – а создавал ли кто-нибудь такой большой PDF? Насколько это сложно? А можно ли сделать документ ещё больше?
Несколько лет назад я из праздного любопытства немного поигрался с PostScript, предшественником PDF, и это оказалось очень увлекательным! Ранее мне не доводилось изучать внутреннее устройство PDF, так что здесь у меня возник для этого хороший повод.
Приступим!
▍ Откуда происходит это утверждение?
Такие посты зачастую сопровождаются репликами вроде «ну, вообще-то», когда люди в ответах поясняют, что это ограничение конкретного PDF-ридера, а не самого PDF. Обычно они ссылаются на что-нибудь вроде статьи Википедии, в которой сказано:
Сам по себе формат не ограничивает размера страниц. Однако Adobe Acrobat накладывает ограничение в 15,000,000 х 15,000,000 дюймов, то есть 225 триллионов дюймов2 (145,161 км2)[2].
По ссылке вы найдёте спецификацию для PDF 1.7, где в приложении даётся более подробное пояснение (выделение моё):
До версии PDF 1.6 размер базовой единицы пользовательского пространства был установлен равным 1/72 дюйма. В Acrobat Reader до версии 4.0 минимальный допустимый размер страницы составляет 72 х 72 единицы (1 х 1 дюйм); максимальный же размер равен 3240 х 3240 единиц (45 х 45 дюймов). В версиях Acrobat 5.0 и старше минимальный допустимый размер составляет уже 3 х 3 единицы (примерно 0,04 на 0,04 дюйма), а максимальный – 14,400 х 14,400 единиц (200 х 200 дюймов).
Начиная с PDF 1.6, размер базовой единицы пользовательского пространства можно установить через запись
UserUnit
словаря страницы. Acrobat 7.0 поддерживает максимальное значениеUserUnit
равное75,000
, обеспечивая максимальный размер страницы 15,000,000 дюймов (14,400 * 75,000 * 1/72). Минимальное же значениеUserUnit
равно1.0
(по умолчанию).
Пятнадцать миллионов дюймов – это ровно 381 километр, что соответствует числу, указанному в оригинальном твите. И хотя этот лимит впервые появился в PDF 1.6, он относится к 7-й версии Adobe Acrobat. Похоже, что изначальное утверждение основывается именно на этом факте.
А что, если создать PDF, превышающий эти «максимальные» значения?
▍ Внутренняя структура PDF
Я никогда не изучал внутреннюю структуру PDF-документа – время от времени я просматривал некоторые биты в hex-редакторе, но принцип их работы по факту никогда не понимал. Если мне хочется просто поразвлечься, то это хорошая возможность научиться редактировать PDF напрямую, не пользуясь библиотекой.
Я нашёл отличную статью, объясняющую внутреннюю структуру PDF. Её содержание вместе с небольшим допросом ChatGPT помогло мне достаточно разобраться, чтобы начать писать простые файлы вручную.
Я знаю, что по факту возможности PDF очень обширны, так что сформировавшееся у меня представление, пожалуй, будет сильно упрощённым:
Начинаются PDF-документы всегда с номера версии(%PDF-1.6
), а завершаются маркером конца файла (%%EOF
).
После номера версии следует длинный список объектов. Существует много разных их типов для всевозможных элементов, которые можно встретить в PDF-документе, включая страницы, текст и графику.
За этим списком идёт xref
, таблица перекрёстных ссылок. Это таблица поиска объектов, которая указывает на все объекты в файле, сообщая, что объект 1 находится в 10 байтах от начала, объект 2 – в 20 байтах, объект 3 – в 30 байтах и так далее. Просматривая эту таблицу, PDF-ридер понимает, сколько объектов находится в файле и где именно.
Сегмент trailer
содержит метаданные документа, например, количество его страниц и отметку о наличии шифрования.
Наконец, значение startxref
представляет указатель на начало таблицы xref
. Отсюда PDF-ридер начинает чтение документа: он проходит от конца файла, пока не находит значение startxref
, после чего считывает таблицу xref
, узнавая всё о содержащихся в нём объектах.
Опираясь на это базовое представление, я смог написать свой первый PDF-файл вручную. Если вы сохраните следующий код в файл с именем myexample.pdf, то при его открытии в ридере увидите страницу с красным квадратом:
%PDF-1.6
% Первый объект. Начало каждого объекта обозначено так:
%
% <номер объекта> <номер поколения> obj
%
% (Номер поколения используется для версионирования и обычно представлен 0).
%
% Первым идёт объект 1 – он начинается как `1 0 obj`. Второй объект
% будет начинаться с `2 0 obj`, затем идёт `3 0 obj`и так далее.
% Конец каждого объекта обозначается как `endobj`.
%
% Это объект «stream», отрисовывающий фигуру. Сначала я указываю
% его длину (54 байта). Затем выбираю цвет в виде RGB-значения
% (`1 0 0 RG` = красный). После устанавливаю толщину линии (`5 w`)
% и, наконец, задаю для отрисовки этого квадрата координаты:
%
% (100, 100) ----> (200, 100)
% |
% [s = start] |
% ^ |
% | |
% | v
% (100, 200) <---- (200, 200)
%
1 0 obj
<<
/Length 54
>>
stream
1 0 0 RG
5 w
100 100 m
200 100 l
200 200 l
100 200 l
s
endstream
endobj
% Второй объект.
%
% Это будет объект «Page», определяющий одну страницу. Он содержит
% один объект: объект 1, то есть красный квадрат. Это строка `1 0 R`.
%
% «R» означает «Reference» (ссылка), а `1 0 R` гласит «Смотри на
% объект 1 с номером поколения 0» -- а объект 1 – это красный
% квадрат.
% Он также указывает на объект «Pages», содержащий информацию обо
% всех страницах в PDF-документе – это ссылка `3 0 R`.
2 0 obj
<<
/Type /Page
/Parent 3 0 R
/MediaBox [0 0 300 300]
/Contents 1 0 R
>>
endobj
% Третий объект.
%
% Это объект «Pages», который содержит информацию о разных страницах.
% `2 0 R` является ссылкой на объект «Page», определённый выше.
3 0 obj
<<
/Type /Pages
/Kids [2 0 R ]
/Count 1
>>
endobj
% Четвёртый объект.
%
% Это объект «Catalog», формирующий основную структуру документа.
% Он указывает на объект «Pages», содержащий информацию о разных
% страницах – это ссылка `3 0 R`.
4 0 obj
<<
/Type /Catalog
/Pages 3 0 R
>>
endobj
% Таблица xref. Это таблица поиска для всех объектов.
%
% Не уверен, для чего нужна первая запись, но она, похоже, важна.
% Остальные записи соответствуют созданным мной объектам.
xref
0 4
0000000000 65535 f
0000000851 00000 n
0000001396 00000 n
0000001655 00000 n
0000001934 00000 n
% Хвост (trailer). Он содержит метаданные PDF-документа. В данном
% случае в нём находятся две записи, сообщающие нам, что:
%
% - В таблице ‘xref’ присутствует 4 записи.
% - Корнем документа является объект 4, то есть «Catalog».
%
trailer
<<
/Size 4
/Root 4 0 R
>>
% Маркер startxref сообщает, что таблица xref находится в 2196 байтах
% от начала файла.
startxref
2196
% Маркер конца файла.
%%EOF
Я немного поигрался с этим файлом: добавлял разные фигуры, изменял их внешний вид и размещал по разным страницам. Я также пытался реализовать ввод текста, но это оказалось за пределами моих возможностей.
Довольно быстро стало ясно, почему никто не пишет PDF-файлы вручную – очень много волокиты с переделкой таблиц поиска. Но я рад, что проделал этот эксперимент. Управление всеми объектами документа и их ссылками позволило мне понять основную модель PDF. Я открыл несколько «реальных» документов, созданных другими приложениями, где обнаружил намного больше типов объектов – но сейчас я хотя бы мог отчасти разобраться, что там к чему.
Теперь оставалось понять, как применить этот новообретённый навык ручного редактирования PDF-документов для создания их гигантских образцов?
▍ Изменение размера страницы: /MediaBox и /UserUnit
Внутри PDF-документа размер каждой страницы устанавливается в отдельных объектах «Page», что позволяет создавать страницы разных размеров. Мы это уже видели:
<<
/Type /Page
/Parent 3 0 R
/MediaBox [0 0 300 300]
/Contents 1 0 R
>>
Здесь параметр MediaBox
задаёт ширину и высоту страницы – в данном случае квадрат 300 х 300 единиц. По умолчанию размер единицы составляет 1/72 дюйма, значит, одна сторона страницы будет равна 300 × 1/72 = 4,17 дюйма. И если открыть документ в Adobe Acrobat, то эти расчёты подтвердятся:
Изменив значение MediaBox
, мы можем увеличить страницу. Например, если изменить его на 600 600
, Acrobat сообщит, что теперь размер страницы равен 8,33 x 8,33 in
. Прекрасно!
Это значение можно увеличить вплоть до 14400 14400
, максимума, который допускает Acrobat. Теперь в свойствах документа мы видим размер страницы 200.00 x 200.00 in
. (При попытке превысить этот лимит Acrobat выдаёт предупреждение).
Но 200 дюймов это далеко не 381 километр – всё дело в том, что мы используем предустановленный размер единицы 1/72 дюйма. Можно увеличить этот размер, подключив значение /UserUnit
. К примеру, установка этого значения на 2 удвоит размер страницы по обоим направлениям.
<<
/Type /Page
/Parent 3 0 R
/MediaBox [0 0 14400 14400]
/UserUnit 2
/Contents 1 0 R
>>
Теперь Acrobat сообщает, что размер страницы равен 400.00 x 400.00 in
.
Если задрать UserUnit
до максимума в 75 000
, Acrobat покажет, что размер страницы составляет 15,000,000.00 x 15,000,000.00 in
– 381 км вдоль каждой из сторон, и это уже будет соответствовать изначальному заявлению. Если вам любопытно, можете скачать этот PDF.
При попытке создать страницу большего размера путём увеличения значения MediaBox
или UserUnit
, Acrobat просто это проигнорирует. Он продолжит говорить, что размер страницы равен 15 миллионам дюймов, даже если метаданные будут сообщать иное. (И если вы установите UserUnit
больше 75000
, это произойдёт втихую – без какого-либо предупреждения или ошибки, указывающей на превышение максимального размера страницы).
Но это вряд ли можно считать проблемой – не думаю, что значение UserUnit
широко используется на практике. Я нашёл один ответ на Stack Overflow, подтверждающий эту догадку, и ни одного реального примера онлайн. Встроенное в macOS приложение Preview даже не поддерживает это значение – просто его игнорирует и рассматривает все PDF-документы так, будто единица пользовательского пространства равна 1/72 дюйма.
Но, в отличие от Acrobat, приложение Preview не имеет максимального предела для значения MediaBox
. Я без проблем смог указать ширину в виде 1, сопровождаемой 12 нулями:
Если вам интересно, эта ширина примерно соответствует расстоянию между Землёй и Луной. Мне понадобится линейка, чтобы это проверить, но я уверен, что такой документ точно шире Германии.
Эксперимент можно было продолжить, что я и сделал. В конечном итоге я получил документ, размер которого по описанию Preview оказался больше всей Вселенной – примерно 37 триллионов световых лет в квадрате. Конечно, он преимущественно пустой, но и Вселенная тоже. Если вы захотите поиграться с этим файлом – он к вашим услугам здесь.
Только не пытайтесь его распечатать.
Автор: Дмитрий Брайт