ContentStream:PDF 内容流发射器
ContentStream 模块负责发射标记内容运算符。它会打开和关闭结构标签与构件、跟踪嵌套深度,并返回运算符缓冲区。
composer require nextpdf/core:^3概念总览
标题为“概念总览”的章节ContentStreamBuilder 是此模块中唯一的类。它负责构建页面内容流的标记内容层。内容流会把页面内容编码成一连串运算符——ISO 32000-2 §8。builder 会在这些内容外围发射标记内容运算符。
append() 会按原样追加原始运算符字节。builder 不会对这段输入做转义处理,其有效性由调用方自行负责。这正是 HTML pipeline 与 Graphics 模块交错插入各自运算符的位置。
beginTag() 会打开一个带结构标签的序列。它会发射一个 BDC 运算符,并附带一份 MCID 属性列表,依照 ISO 32000-2 §14.6。endTag() 会发射对应的 EMC 运算符。builder 会维护嵌套深度。若在没有打开序列的情况下调用 endTag(),会抛出 PageLayoutException,而不会写出未配对的 EMC。
beginArtifact() 会打开一个构件序列。构件用来承载分页装饰——页眉、页脚、页码、分隔线——这些内容必须排除在结构树之外,依照 ISO 32000-2 §14.8.2.2。子类型是四个 ISO 值之一:Pagination、Layout、Page 或 Background。建议优先使用强类型的 ArtifactSubtype enum。字符串重载会通过该 enum 进行校验,因此非标准值会立即导致调用失败。
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()不会转义输入。只能传入有效的运算符字节;builder 信任调用方。endTag()与endArtifact()会在发生下溢时抛出异常。绝不可关闭一个尚未打开的序列。finish()会检查是否配对,并在深度不为零时抛出异常。drain()不会检查。请仅在增量流式处理时使用drain()。- 深度计数器不会区分标签与构件。
EMC会关闭二者之中最内层的序列。请严格按顺序嵌套它们。 - 传给
beginArtifact()的字符串重载会通过该 enum 进行校验。非标准的子类型会在调用时失败,而不会出现在输出中。 relabelTag()会改写已发射的标签。请使用与最初发射时相同的mcid。
每个操作都是 O(1) 字符串追加,relabelTag() 则是 O(buffer) 的改写。此模块只持有一个字符串缓冲区和一个整数深度计数器。除缓冲区本身外,不执行任何解析,也不做额外内存分配。参考工作负载的预算为 1500 ms 墙钟时间和 64 MB 峰值内存。此模块远低于该预算。
安全性说明
标题为“安全性说明”的章节append() 是信任边界。builder 会按原样写出字节,因此上游代码必须转义任何会送入字面字符串运算符的字符串。标准转义工具是 PdfStringEscaper::escapeLiteral()(ADR-015)。绝不可通过 append() 传入未转义的用户文本。endTag()、endArtifact() 与 finish() 中的配对检查,可防止格式错误的标记内容树传到 Writer。文档的威胁模型请参阅 /modules/core/security/ 一节。
符合性
标题为“符合性”的章节此模块发射的标记内容运算符结构符合 ISO 32000-2:按 §14.6 生成 BDC/EMC 配对,并附带一份 MCID 属性列表;按 §14.8.2.2 生成构件序列。这些属于实现事实。佐证包括 src/ContentStream/ContentStreamBuilder.php、src/Accessibility/ArtifactSubtype.php enum,以及 tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest 和 ContentStreamBuilderRelabelTagInvariantTest。它们并不是端到端 PDF/UA-2 或 PDF 2.0 符合性的主张。这些运算符参与形成的标记式 PDF 结构,由外部 oracle 校验:tests/Integration/Accessibility/VeraPdfUa2GoldenTest 会针对 PDF/UA-2 规范,用 veraPDF 检查一份生成的 fixture。该 oracle 测试在找不到 veraPDF 可执行文件时会跳过,因此它是可选启用的 gate。请陈述此模块「会生成标记内容结构;PDF/UA-2 符合性由 veraPDF 校验」,而不要主张无条件的符合性。