跳转到内容

Writer:PDF 2.0 序列化器 + xref

Writer 模块会把文档序列化为 PDF 字节。它选择版本策略,写出对象图,并生成交叉引用结构和尾部(trailer)。

Terminal window
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 表示三个目标版本:Pdf20Pdf17Pdf14。该枚举提供 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.phpLinearizer.php 涉及字节偏移和对象图,属于关键代码(清单中标注的危险区)。在没有 Writer 黄金套件的情况下,请勿更改对象编号或 xref 偏移量计算。

关键方法角色
PdfWriterwrite(DocumentData): stringwriteChunked(DocumentData, int): GeneratorsetDeterministicMode()setReproducibleClock()setOutputColorProfile()getLastXrefOffset()getFileId()主序列化器
PdfSerializationStrategy(接口)writeHeader()getCatalogVersion()writeXrefAndTrailer()usesXrefStream()版本策略契约
Pdf20StreamStrategywriteHeader()%PDF-2.0getCatalogVersion()/2.0usesXrefStream()truePDF 2.0 xref 流策略
Pdf17TableStrategywriteHeader()%PDF-1.7,xref 表PDF 1.7 xref 表策略
Pdf14TableStrategywriteHeader()%PDF-1.4,xref 表PDF 1.4 xref 表策略
PdfOutputProfile(枚举)Pdf20Pdf17Pdf14headerVersion()catalogVersion()allowsObjectStreams()目标版本选择器
PdfXrefWritergenerateFileId()finalizeTrailerAndXref()File ID + trailer/xref 最终化
Linearizerlinearize(string): stringshadowValidate(string): array快速 Web 查看重写器
Streaming\StreamingPdfWriteropen()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()
  • Pdf14TableStrategyfinal 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.phpsrc/Writer/PdfSerializationStrategy.php,以及 src/Writer/PdfWriter.php 中的策略选择,并由 tests/Unit/Writer/(192 个测试,包括 Pdf20StreamStrategyPdfXrefWriterLinearizer* 套件)及 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 配置档验证」,而不要断言无条件一致性。