Writer:PDF 2.0 序列化器 + xref
Writer 模块会把文档序列化为 PDF 字节。它选择版本策略,写出对象图,并生成交叉引用结构和尾部(trailer)。
composer require nextpdf/core:^3概念概览
标题为“概念概览”的章节PdfWriter 是入口点。write() 方法接收一个 DocumentData 值对象,并以字节串返回完整的 PDF。写入器会组装对象图、分配对象编号、记录字节偏移量,并最终写出交叉引用结构。
写入器每次调用都会使用一个序列化策略。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 会在配置档为 Pdf14 时拒绝 PDF 2.0 特性。
交叉引用流会将每个对象编号映射到其字节偏移量,见 ISO 32000-2 §7。增量更新会将新对象追加到文件末尾,见 ISO 32000-2 §7.5.6。写入器通过规范的 PdfStringEscaper::escapeLiteral() 统一入口来转义每一个字面字符串,该入口遵循 ISO 32000-2 §7.3.4.2 中的规范转义表(ADR-015)。
写入器支持确定性输出。setDeterministicMode() 会固定对象标识符和字典键顺序。setReproducibleClock() 会固定文档时间戳。两者同时固定后,相同输入会产生字节一致的输出。writeChunked() 方法返回一个生成器,按固定大小的分块产出 PDF。对于超出内存预算的文档,Streaming/StreamingPdfWriter 会逐页写入调用方提供的流。
Linearizer 会把已完成的 PDF 重写为线性化布局。它会把第一页前置,使查看器在完整下载完成前即可显示该页。shadowValidate() 会在不改动输入的情况下检查这次重写。
注意。
PdfWriter.php和Linearizer.php涉及字节偏移和对象图,属于关键代码(清单中标注的危险区)。在没有 Writer 黄金套件的情况下,请勿更改对象编号或 xref 偏移量计算。
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 | PDF 2.0 xref 流策略 |
Pdf17TableStrategy | writeHeader() → %PDF-1.7,xref 表 | PDF 1.7 xref 表策略 |
Pdf14TableStrategy | writeHeader() → %PDF-1.4,xref 表 | PDF 1.4 xref 表策略 |
PdfOutputProfile(枚举) | Pdf20、Pdf17、Pdf14;headerVersion()、catalogVersion()、allowsObjectStreams() | 目标版本选择器 |
PdfXrefWriter | generateFileId()、finalizeTrailerAndXref() | File ID + trailer/xref 最终化 |
Linearizer | linearize(string): string、shadowValidate(string): array | 快速 Web 查看重写器 |
Streaming\StreamingPdfWriter | open()、newPage()、close() | 单遍流式写入器 |
运行 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()调用只运行一个策略。写入器会在每次调用时根据配置档重置策略;先前调用的版本不会泄漏到后续调用。 - 归档一致性模式会覆盖请求的配置档。PDF/A-3 构建会强制使用 PDF 1.7。PDF/A-4 构建会强制使用 PDF 2.0。
- 字节一致的输出需要两个固定项。请同时设置确定性模式和一个可复现时钟;只设置其中一个还不够。
writeChunked()会产出一个生成器。请完整消费它;部分读取会产生截断且无效的 PDF。Linearizer会重写交叉引用偏移量。在无法容忍重写失败的流水线中,请先运行shadowValidate()。Pdf14TableStrategy是final readonly的。PDF 1.4 路径通过Pdf14FeatureGuard拒绝 PDF 2.0 特性,而不会对其降级。
序列化的时间复杂度与对象数量和总字节大小线性相关。交叉引用流会为对象表增加一遍扫描。writeChunked() 会持有组装好的文档,但会以有界切片产出,因此峰值内存为文档大小加一个分块。Streaming\StreamingPdfWriter 不会持有整个文档;它是处理超出内存预算输入的路径。参考工作负载预算为 1500 ms 实际耗时和 64 MB 峰值。线性化会额外增加第二遍完整扫描和一遍测量扫描。请为它显式预留预算。
安全说明
标题为“安全说明”的章节写入器序列化的是可信的内存中对象图。主要威胁位于输入端。每一个字面字符串都会经由规范的 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 一致性是经外部预言机(oracle)验证的完整文档属性,而不是序列化器本身的属性。只有在预言机确认的地方才会断言端到端一致性:tests/Integration/Accessibility/VeraPdfUa2GoldenTest 针对 PDF/UA-2 使用 veraPDF 验证一个生成的测试夹具,而 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 配置档验证」,而不要断言无条件一致性。