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

Экономика размера файла PDF

Spec: ISO 32000-2, §7.5.7

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

Это спутник почему файлы большие к статье Потоки и фильтры, которая рассказывает, как фильтр декодирует. Эта остаётся на бюджете.

Размер файла редко бывает метрикой тщеславия. Это пропускная способность при каждой загрузке, хранилище при каждой архивации и задержка при каждом предпросмотре. Счёт на 12 МБ, который должен был быть 400 КБ, — не косметическая проблема, когда вы генерируете миллион таких в месяц, — это тридцатикратный счёт.

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

Представьте PDF как бюджет, который вы тратите на четыре статьи.

  • Накладные расходы на объект. Каждый косвенный объект несёт конверт N G obj / endobj и отслеживается одной записью перекрёстных ссылок. Документ с множеством страниц имеет тысячи крошечных объектов, и конверты складываются. Объектные потоки сбрасывают этот конверт для целой группы объектов; каждый упакованный объект всё ещё держит собственную запись перекрёстных ссылок, но как компактную запись типа 2.
  • Байты изображений. Для любого документа с фотографиями или сканами изображения доминируют, и единственный крупнейший рычаг — это выбор фильтра: кодек без потерь против кодека изображений с потерями — это разница между мегабайтами и килобайтами.
  • Байты шрифтов. Полный встроенный шрифт — это сотни килобайт глифов, которые вы никогда не используете. Субсеттинг оставляет только те глифы, которые документ действительно рисует.
  • Индекс. Таблица перекрёстных ссылок, которая позволяет программе чтения найти каждый объект, сама может быть сжатым потоком, а не открытым текстом.

Сделайте все четыре правильно — и файл мал. Промахнитесь по одному — и он доминирует над всем остальным, что вы сделали хорошо.

Писатель NextPDF — однопроходный потоковый сериализатор: он добавляет байты каждого объекта по мере их производства и записывает классическую запись перекрёстных ссылок «в использовании» для каждого. Это значение по умолчанию быстро, предсказуемо и производит байт-стабильный файл — но это не наименьшая возможная компоновка, и NextPDF честен об этом.

PDF — это граф косвенных объектов. Большинство из них — небольшие словари: узлы страниц, словари аннотаций, элементы дерева структуры, записи оглавления. Каждый платит фиксированный налог — ключевые слова obj / endobj, номера объекта и поколения и запись перекрёстных ссылок, которая его располагает. На документе с тысячами небольших объектов этот налог — ощутимая доля файла.

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

В NextPDF это поставляется ObjectStreamPacker, самостоятельным постпроцессором, который берёт готовый PDF с потоком перекрёстных ссылок и переписывает подходящие объекты в единый /Type /ObjStm. Правила, которым он следует, идут прямо из стандарта: подходящий объект — это объект нулевого поколения, без потока, после исключения особых объектов, которые должны оставаться напрямую адресуемыми. §7.5.7 запрещает хранить объект-поток внутри объектного потока, так что объекты-потоки — содержимое, шрифты, изображения — держат собственные записи; а ObjectStreamPacker дополнительно отклоняет собственный объект потока перекрёстных ссылок документа (он переписывается) и словарь /Encrypt, оба из которых должны оставаться напрямую адресуемыми. Всё остальное нулевого поколения и без потока упаковывается.

Object stream packing (ObjStm) — edition availability
EditionAvailability
CoreПолная поддержка в открытом ядре через ObjectStreamPacker. Это включаемо: по умолчанию однопроходный писатель выдаёт классические записи «в использовании», так что вывод остаётся побайтово идентичным, пока вы не включите упаковку. Упаковщик детерминирован, со своей воспроизводимой эталонной базой.
ProNot in this edition
EnterpriseNot in this edition

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

Раз объекты живут внутри объектного потока, индекс, который на них указывает, меняет форму. Программа чтения находит упакованный объект через сжатую запись перекрёстных ссылок — запись типа 2, которая называет объектный поток и индекс внутри него (Spec: ISO 32000-2, §7.5.8.3). Поскольку весь перекрёстный поиск сам является потоком /Type /XRef, индекс для тысяч объектов двоично упакован и сдут, а не записан как строки открытого текста. Карта сжимается вместе с территорией.

ObjectStreamPacker пересобирает ровно это: он выдаёт единый объектный поток, затем переписанный поток перекрёстных ссылок, несущий компактную запись типа 1 для каждого сохранённого объекта и запись типа 2 для каждого упакованного, сохраняя каждый номер объекта, чтобы существующие ссылки оставались действительными.

Для любого документа с настоящими изображениями этот рычаг затмевает остальные. Байты — это та же картинка; кодек — это бюджет. Имена фильтров живут в стандартном наборе фильтров (Spec: ISO 32000-2, §7.4), и выбор между ними — это решение о размере:

  • FlateDecode без потерь. Идеален для штриховой графики, скриншотов и всего с плоским цветом — и губителен для фотографии, где «без потерь» означает каждый байт оригинала.
  • DCTDecode — это JPEG: с потерями, и для фотографий — правильный выбор с большим отрывом, часто десятикратное уменьшение за падение качества, которого никто не замечает.
  • JPXDecode — это JPEG 2000: вейвлет-сжатие с другой кривой качество/размер, но неравномерная поддержка и запрет некоторыми архивными профилями.

Это ровно то место, где важно, как фильтр декодирует, и это задача статьи Потоки и фильтры. Точка экономики уже: фотография, хранимая без потерь, — самая распространённая единственная причина без нужды огромного PDF, и никакая упаковка объектных потоков не спасёт файл, чьё настоящее бремя — один не-JPEG’нутый скан.

Встроенный шрифт — это программа. Полная может быть несколько сотен килобайт, потому что несёт каждый глиф, который дизайнер шрифта когда-либо нарисовал, — тысячи символов в письменностях, которые вы никогда не используете в этом документе. Субсеттинг встраивает только те глифы, которые документ действительно рисует, превращая эту программу в небольшую долю себя. Письму на одну страницу не нужен весь CJK-способный шрифт; ему нужны те несколько десятков глифов, которые оно набирает. Механика того, как глифы выбираются и переиндексируются, — отдельный предмет, см. Шрифты: самое сложное.

  1. Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
  2. Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
  3. Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
  4. The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
Куда уходят байты: четыре рычага, на которые автор тратит бюджет размера PDF, в порядке, в котором они обычно доминируют в реальном файле.

Здесь нет сфабрикованного API для показа, потому что самые важные решения о размере принимаются до того, как байты достигают писателя, — а единственный структурный рычаг, который NextPDF предоставляет, — это один включаемый. Концептуально бюджет читается так:

  • Подавайте фотографии как JPEG, чтобы они попадали под DCTDecode, а не были перекодированы без потерь. Крупнейший выигрыш — это выбор об исходных данных, а не флаг писателя.
  • Позвольте писателю субсеттить встроенные шрифты, чтобы отправлялись только нарисованные глифы.
  • Для документа с множеством небольших объектов включите упаковку объектных потоков, которая прогоняет готовый файл через ObjectStreamPacker, чтобы сгруппировать небольшие объекты без потоков и переписать поток перекрёстных ссылок.

Путь по умолчанию — классические записи «в использовании», без ObjStm — это правильная база: детерминированный, байт-стабильный и легко проверяемый. Упаковка — это обдуманное улучшение, когда файл раздувает число объектов, а не вес изображений.

Ловушка — тянуться к кнопке «сжать PDF» и ждать, что она всё исправит. Сжатие — это не один рычаг; это четыре, и они не заменяют друг друга. Упаковка объектных потоков не может уменьшить фотографию — это задача фильтра изображений. Идеальный JPEG не компенсирует шрифт, который вы забыли субсеттить. И ничто из этого не помогает, если настоящее раздувание — это скан на 10 МБ, хранимый без потерь, потому что никто не выбрал для него DCTDecode.

Второе заблуждение — что меньше всегда строго лучше. Это не так. Объектные потоки несовместимы с линеаризацией — компоновкой fast-web-view, которая фиксирует абсолютное размещение объектов, чтобы первая страница приходила потоком рано. А некоторые архивные профили ограничивают, какие фильтры изображений вообще разрешены. Размер — это одна ось. Он торгуется против потоковой передачи, архивного соответствия и воспроизводимости, и правильная точка на кривой зависит от того, для чего документ.

Четыре рычага — это структурная экономика размера файла. Они не универсальная гарантия «сделать меньше», и NextPDF не притворяется переоптимизатором для произвольных входящих PDF.

ObjectStreamPacker — это оптимизация по принципу «как получится», которая никогда не рискует корректностью. Он отклоняет — возвращая вход неизменным, — когда файл не является PDF с потоком перекрёстных ссылок, когда он зашифрован, когда он несёт цифровую подпись (перераскладка объектов сдвинула бы диапазоны байтов, которые защищает подпись), когда он уже содержит объектные потоки или когда нет подходящего объекта для упаковки. Он выдаёт ровно один объектный поток на весь документ; он не разбивает на коллекцию /Extends, что выведено за рамки и соответствует стандарту для размеров документов, которые производит NextPDF.

Рычаги изображений и шрифтов — это в основном решения о входе. Писатель NextPDF не транскодирует неявно растровое изображение без потерь в поток изображения с потерями — перекодирование фотографии в DCTDecode или JPXDecode — это явное вышестоящее решение о кодировании изображения, а не то, что сериализатор делает за вашей спиной, — и он не отвоёвывает глифы у шрифта, который вызывающий попросил встроить целиком. Крупнейшие выигрыши в размере делаются выше байтового сериализатора; задача движка — не тратить впустую бюджет, который вы ему приносите.

  • Объектный поток (ObjStm) — единый поток, который держит многие небольшие косвенные объекты без потоков, сжатые вместе, так что конверт obj / endobj сбрасывается для группы. Каждый упакованный объект всё ещё держит собственную запись перекрёстных ссылок, как компактную запись типа 2. Включаемо в NextPDF.
  • Косвенный объект — пронумерованный объект в графе PDF, обёрнутый в конверт obj / endobj и отслеживаемый индексом перекрёстных ссылок. Конверт — это накладные расходы на объект.
  • Поток перекрёстных ссылок — поток /Type /XRef, который индексирует каждый объект, двоично упакованный и сдутый, а не записанный как строки открытого текста.
  • Сжатая запись (типа 2) — запись перекрёстных ссылок, которая указывает на объект, живущий внутри объектного потока, называя поток и индекс внутри него.
  • Субсеттинг шрифтов — встраивание только тех глифов, которые документ действительно рисует, а не всего шрифта, уменьшающее встроенную программу шрифта.
  • Фильтр без потерь против с потерями — кодек без потерь (FlateDecode) воспроизводит каждый байт; кодек изображений с потерями (DCTDecode или JPXDecode в режиме с потерями) отбрасывает незаметную деталь ради куда меньшего результата. (JPEG 2000 / JPX можно также настроить без потерь.) Выбор — доминирующий рычаг для файла с большим количеством медиа.