ContentStream: генератор потоков содержимого PDF
Краткий обзор
Заголовок раздела «Краткий обзор»Модуль ContentStream генерирует операторы маркированного содержимого Portable Document Format (PDF). Он открывает и закрывает структурные теги и артефакты, отслеживает глубину вложенности и возвращает буфер с операторами.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»ContentStreamBuilder — единственный класс модуля. Он формирует слой маркированного содержимого в потоке содержимого страницы. Поток содержимого кодирует содержимое страницы как последовательность операторов — ISO 32000-2 §8. Затем построитель генерирует вокруг этого содержимого операторы маркированного содержимого.
append() добавляет необработанные байты операторов дословно. Построитель не экранирует эти входные данные. За их корректность отвечаете вы. Используйте эту границу, когда HTML-конвейеру и модулю Graphics нужно чередовать собственные операторы.
beginTag() открывает последовательность со структурным тегом. Он генерирует оператор BDC со списком свойств MCID согласно ISO 32000-2 §14.6. endTag() генерирует соответствующий оператор EMC. Построитель подсчитывает глубину вложенности. Если вызвать endTag(), когда открытых последовательностей нет, он выбросит PageLayoutException вместо записи несбалансированного EMC.
beginArtifact() открывает последовательность артефакта. Используйте артефакты для элементов оформления макета: колонтитулов, номеров страниц и линеек. Они должны оставаться вне дерева структуры согласно ISO 32000-2 §14.8.2.2. Подтип — это одно из четырёх значений ISO: Pagination, Layout, Page или Background. Лучше использовать типизированное перечисление ArtifactSubtype. Строковая перегрузка проверяется по перечислению, поэтому при нестандартном значении вызов сразу завершается ошибкой.
relabelTag() перезаписывает ранее сгенерированный тег на месте. finish() возвращает полный буфер и выбрасывает исключение, если маркированное содержимое несбалансированно. drain() возвращает накопленный буфер без проверки баланса для инкрементальной потоковой передачи. peek() возвращает буфер, не потребляя его. reset() очищает состояние.
Поверхность API
Заголовок раздела «Поверхность API»| Метод | Сигнатура | Назначение |
|---|---|---|
append() | append(string $raw): void | Добавляет необработанные байты операторов дословно (без экранирования) |
beginTag() | beginTag(string $structType, int $mcid): void | Открывает последовательность BDC со структурным тегом |
endTag() | endTag(): void | Закрывает самую внутреннюю последовательность оператором EMC |
beginArtifact() | beginArtifact(ArtifactSubtype|string $type): void | Открывает последовательность артефакта |
endArtifact() | endArtifact(): void | Закрывает самый внутренний артефакт |
getMarkedContentDepth() | getMarkedContentDepth(): int | Возвращает текущую глубину вложенности |
relabelTag() | relabelTag(string $old, string $new, int $mcid): void | Перезаписывает сгенерированный тег на месте |
finish() | finish(): string | Возвращает полный буфер; выбрасывает исключение при несбалансированности |
drain() | drain(): string | Возвращает буфер без проверки баланса |
peek() | peek(): string | Возвращает буфер, не потребляя его |
reset() | reset(): void | Очищает всё состояние |
Выполните composer docs:generate-api-php -- --module=ContentStream, чтобы сгенерировать полную таблицу PHPDoc.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\ContentStream\ContentStreamBuilder;
$builder = new ContentStreamBuilder();
$builder->beginTag('P', mcid: 0);$builder->append("BT /F1 12 Tf 72 720 Td (Hello) Tj ET\n");$builder->endTag();
$pageContent = $builder->finish();Пример кода — продакшен
Заголовок раздела «Пример кода — продакшен»Используйте этот шаблон, чтобы заключить абзац в структурный тег, а нижний колонтитул — в артефакт. Шаблон передаёт буфер инкрементально с помощью drain().
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Accessibility\ArtifactSubtype;use NextPDF\ContentStream\ContentStreamBuilder;
$builder = new ContentStreamBuilder();
$builder->beginTag('H1', mcid: 0);$builder->append($titleOperators);$builder->endTag();
$builder->beginArtifact(ArtifactSubtype::Pagination);$builder->append($footerOperators);$builder->endArtifact();
if ($builder->getMarkedContentDepth() !== 0) { throw new RuntimeException('Unbalanced marked content before flush.');}
$chunk = $builder->drain();Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»append()не экранирует входные данные. Передавайте только корректные байты операторов. Построитель доверяет вызывающему коду.endTag()иendArtifact()выбрасывают исключение, если открытых последовательностей нет. Никогда не закрывайте последовательность, которую не открывали.finish()проверяет баланс и выбрасывает исключение, когда глубина не равна нулю.drain()не проверяет. Используйтеdrain()только для инкрементальной потоковой передачи.- Счётчик глубины не различает теги и артефакты.
EMCзакрывает самую внутреннюю последовательность любого вида. Вкладывайте последовательности в строгом порядке. - Строковая перегрузка
beginArtifact()проверяется по перечислению. При нестандартном подтипе вызов завершается ошибкой, а не формирует ошибочные выходные данные. relabelTag()перезаписывает сгенерированный тег. Используйте тот жеmcid, с которым он был сгенерирован.
Производительность
Заголовок раздела «Производительность»Каждая операция добавляет строку со сложностью O(1), кроме relabelTag(), где перезапись выполняется со сложностью O(buffer). Модуль хранит один строковый буфер и один целочисленный счётчик глубины. Он не выполняет синтаксический разбор и выделяет память только под буфер. Бюджет эталонной нагрузки — 1500 мс реального времени и 64 МБ пиковой памяти. Модуль остаётся значительно ниже этих значений.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»append() — это граница доверия. Построитель записывает байты дословно, поэтому вышестоящий код должен экранировать любую строку до того, как она попадёт в оператор строкового литерала. Канонический механизм экранирования — PdfStringEscaper::escapeLiteral() (ADR-015). Никогда не передавайте неэкранированный пользовательский текст через append(). Проверки баланса в endTag(), endArtifact() и finish() не дают некорректному дереву маркированного содержимого попасть в Writer. Модель угроз документа см. в /modules/core/security/.
Соответствие стандартам
Заголовок раздела «Соответствие стандартам»Модуль генерирует структуры операторов маркированного содержимого, соответствующие ISO 32000-2: пары BDC/EMC со списком свойств MCID согласно §14.6 и последовательности артефактов согласно §14.8.2.2. Это факты реализации. Их подтверждают src/ContentStream/ContentStreamBuilder.php, перечисление src/Accessibility/ArtifactSubtype.php, tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest, а также ContentStreamBuilderRelabelTagInvariantTest. Это не заявление о сквозном соответствии PDF/UA-2 или PDF 2.0. Внешний оракул проверяет структуру тегированного PDF, где используются эти операторы: tests/Integration/Accessibility/VeraPdfUa2GoldenTest проверяет сгенерированную фикстуру с помощью veraPDF на соответствие профилю PDF/UA-2. Этот тест-оракул пропускается при отсутствии бинарного файла veraPDF, поэтому он работает как опциональный контрольный барьер. Указывайте, что этот модуль “создаёт структуры маркированного содержимого; соответствие PDF/UA-2 проверяется veraPDF”, вместо безоговорочного утверждения о соответствии.