Writer: сериализатор PDF 2.0 и xref
Модуль Writer сериализует документ в байтовое представление Portable Document Format (PDF). Он выбирает стратегию версии, записывает граф объектов и формирует структуру перекрёстных ссылок и трейлер.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»Используйте PdfWriter как точку входа. Передайте объект-значение DocumentData методу write(). Метод возвращает готовый PDF в виде байтовой строки. Writer собирает граф объектов, присваивает номера объектов, фиксирует байтовые смещения и записывает структуру перекрёстных ссылок в последнюю очередь.
При каждом вызове Writer использует одну стратегию сериализации. Интерфейс PdfSerializationStrategy задаёт четыре метода: writeHeader(), getCatalogVersion(), writeXrefAndTrailer() и usesXrefStream(). Реализаций три. Pdf20StreamStrategy записывает заголовок %PDF-2.0, задаёт версию каталога /2.0 и формирует поток перекрёстных ссылок. Pdf17TableStrategy записывает %PDF-1.7 и классическую таблицу перекрёстных ссылок. Pdf14TableStrategy записывает %PDF-1.4 и таблицу перекрёстных ссылок. PdfWriter выбирает стратегию через match по DocumentData::$outputProfile. По умолчанию используется Pdf20StreamStrategy.
Перечисление PdfOutputProfile содержит три целевые версии: Pdf20, Pdf17 и Pdf14. Оно предоставляет headerVersion(), catalogVersion(), allowsObjectStreams() и usesXrefStream(). Режим архивного соответствия переопределяет выбранный профиль ещё до выбора стратегии. Pdf14FeatureGuard отклоняет возможности PDF 2.0, если выбран профиль Pdf14.
Поток перекрёстных ссылок сопоставляет каждый номер объекта с его байтовым смещением согласно ISO 32000-2 §7. Инкрементные обновления добавляют новые объекты в конец файла согласно ISO 32000-2 §7.5.6. Writer экранирует каждую литеральную строку единым каноническим способом, через PdfStringEscaper::escapeLiteral(), в соответствии с нормативной таблицей экранирования ISO 32000-2 §7.3.4.2 (ADR-015).
Writer поддерживает детерминированный вывод. setDeterministicMode() фиксирует идентификаторы объектов и порядок ключей словаря. setReproducibleClock() фиксирует временную метку документа. Когда включены обе фиксации, фиксированный вход даёт побайтово идентичный вывод. Метод writeChunked() возвращает генератор, который выдаёт PDF фрагментами фиксированного размера. Streaming/StreamingPdfWriter записывает по одной странице за раз в поток, предоставленный вызывающей стороной, для документов, превышающих бюджет памяти.
Linearizer переписывает готовый PDF в линеаризованную структуру. Он размещает первую страницу в начале, чтобы средство просмотра могло показать её до завершения полной загрузки. shadowValidate() проверяет результат перезаписи, не изменяя входные данные.
Внимание.
PdfWriter.phpиLinearizer.phpкритически важны для байтовых смещений и графа объектов (опасные зоны манифеста). Не меняйте нумерацию объектов или вычисление смещений xref без эталонного набора тестов Writer.
Поверхность API
Заголовок раздела «Поверхность API»| Класс | Основные методы | Роль |
|---|---|---|
PdfWriter | write(DocumentData): string, writeChunked(DocumentData, int): Generator, setDeterministicMode(), setReproducibleClock(), setOutputColorProfile(), getLastXrefOffset(), getFileId() | Основной сериализатор |
PdfSerializationStrategy (интерфейс) | writeHeader(), getCatalogVersion(), writeXrefAndTrailer(), usesXrefStream() | Контракт стратегии версии |
Pdf20StreamStrategy | writeHeader() → %PDF-2.0, getCatalogVersion() → /2.0, usesXrefStream() → true | Стратегия xref-потока PDF 2.0 |
Pdf17TableStrategy | writeHeader() → %PDF-1.7, таблица xref | Стратегия таблицы xref PDF 1.7 |
Pdf14TableStrategy | writeHeader() → %PDF-1.4, таблица xref | Стратегия таблицы xref PDF 1.4 |
PdfOutputProfile (перечисление) | Pdf20, Pdf17, Pdf14; headerVersion(), catalogVersion(), allowsObjectStreams() | Селектор целевой версии |
PdfXrefWriter | generateFileId(), finalizeTrailerAndXref() | Идентификатор файла + завершение trailer/xref |
Linearizer | linearize(string): string, shadowValidate(string): array | Перезапись для быстрого веб-просмотра |
Streaming\StreamingPdfWriter | open(), newPage(), close() | Однопроходный потоковый writer |
Запустите composer docs:generate-api-php -- --module=Writer, чтобы сформировать полную таблицу PHPDoc.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»Источник: examples/02-pdf-factory.php.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Writer\PdfWriter;
$writer = new PdfWriter();$pdfBytes = $writer->write($documentData);
file_put_contents('out.pdf', $pdfBytes);По умолчанию используется профиль PDF 2.0. Вывод начинается с %PDF-2.0 и заканчивается потоком перекрёстных ссылок.
Пример кода — продакшен
Заголовок раздела «Пример кода — продакшен»В этом примере включается детерминированный режим, задаются фиксированные часы для побайтово идентичного вывода, а результат затем передаётся потоком фрагментами фиксированного размера.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use DateTimeImmutable;use NextPDF\Writer\PdfWriter;use NextPDF\Writer\ReproducibleClock;
$pinned = new DateTimeImmutable('2026-01-01T00:00:00Z');
$writer = new PdfWriter();$writer->setDeterministicMode($pinned, 'nextpdf-fixed-file-id');$writer->setReproducibleClock(new ReproducibleClock($pinned));
$out = fopen('php://output', 'wb');foreach ($writer->writeChunked($documentData, chunkSize: 65536) as $chunk) { fwrite($out, $chunk);}fclose($out);Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- В рамках одного вызова
write()выполняется только одна стратегия. При каждом вызове writer заново выбирает стратегию на основе профиля. Предыдущий вызов не переносит свою версию в следующий. - Режим архивного соответствия переопределяет запрошенный профиль. Сборка PDF/A-3 принудительно использует PDF 1.7. Сборка PDF/A-4 принудительно использует PDF 2.0.
- Для побайтово идентичного вывода требуются обе фиксации. Задайте детерминированный режим и воспроизводимые часы. Одной фиксации недостаточно.
writeChunked()возвращает генератор. Обработайте его полностью. Частичное чтение даёт усечённый и недействительный PDF.Linearizerпереписывает смещения перекрёстных ссылок. Если конвейер не допускает неудачной перезаписи, сначала запуститеshadowValidate().Pdf14TableStrategyобъявлен какfinal readonly. Путь PDF 1.4 отклоняет возможности PDF 2.0 черезPdf14FeatureGuard; он не переводит их на более низкий уровень.
Производительность
Заголовок раздела «Производительность»Сериализация линейна относительно числа объектов и общего размера в байтах. Поток перекрёстных ссылок добавляет один проход по таблице объектов. writeChunked() держит собранный документ в памяти, но выдаёт его ограниченными порциями, поэтому пиковое потребление памяти равно размеру документа плюс один фрагмент. Streaming\StreamingPdfWriter не держит документ целиком; используйте его для входных данных, превышающих бюджет памяти. Бюджет эталонной нагрузки составляет 1500 мс общего времени и 64 МБ пикового потребления памяти. Линеаризация добавляет ещё один полный проход и проход измерения. Учитывайте это в бюджете явно.
Примечания по безопасности
Заголовок раздела «Примечания по безопасности»Writer сериализует доверенный граф объектов из памяти. Основная граница угроз проходит по его входным данным. Каждая литеральная строка проходит через канонический PdfStringEscaper::escapeLiteral() (ADR-015), поэтому встроенные управляющие байты не выходят за пределы строкового токена. Шифрование подключено через PdfEncryptionWriter и запись трейлера /Encrypt. Шифрование с открытым ключом явно отклоняется исключением, а не понижается без уведомления. Детерминированный режим и режим воспроизводимых часов устраняют из вывода побочные каналы по временным меткам и порядку. Модель угроз документа и границы доверия шифрования см. в /modules/core/security/.
Соответствие стандартам
Заголовок раздела «Соответствие стандартам»Writer формирует файловые структуры PDF 2.0: заголовок %PDF-2.0, версию каталога /2.0, поток перекрёстных ссылок и экранирование литеральных строк согласно таблице экранирования ISO 32000-2 §7.3.4.2. Это факты реализации. Их подтверждают src/Writer/Pdf20StreamStrategy.php, src/Writer/PdfSerializationStrategy.php и выбор стратегии в src/Writer/PdfWriter.php. Такое поведение проверяется набором tests/Unit/Writer/ (192 теста, включая наборы Pdf20StreamStrategy, PdfXrefWriter и Linearizer*) и эталоном tests/Golden/PdfWriter/PdfWriterGoldenBaselineSmokeTest.
Это не заявление о полном соответствии PDF 2.0. Полное соответствие ISO 32000-2 — свойство готового документа, проверенного внешним оракулом, а не самого сериализатора. Сквозное соответствие утверждается только там, где его подтверждает оракул: tests/Integration/Accessibility/VeraPdfUa2GoldenTest проверяет сгенерированную фикстуру через veraPDF на соответствие PDF/UA-2, а tests/Standards/Profile/PdfRConformanceTest охватывает профиль PDF/R. Эталонный тест veraPDF пропускается, когда двоичный файл veraPDF отсутствует на исполнителе, поэтому это опциональный оракул-шлюз, а не безусловная проверка. Чтобы запустить его, задайте VERAPDF_BINARY. Выбор архивного профиля (PDF/A-3 → PDF 1.7, PDF/A-4 → PDF 2.0) определяется ADR-011 и режимом соответствия и проверяется наборами соответствия в /modules/core/conformance/. За пределами этих подтверждённых оракулом профилей указывайте, что Writer “формирует структуры PDF 2.0; соответствие проверяется veraPDF для профиля PDF/UA-2”, а не утверждайте безоговорочное соответствие.