Pro 版本
Diff
NextPDF\Pro\Diff 比较两份 PDF 文档并报告变更内容。快速路径产出一份按页对齐的文本差异;结构化路径在此之上增加图像和元数据变更检测,并将结果格式化为 JSON 或 HTML。
可用性与授权
标题为“可用性与授权”的章节此功能随 NextPDF Pro(nextpdf/pro)一同发布,并通过 Pro 级授权信封激活。不具备该授权的部署不会加载此功能的类。比较各版本并获取授权。
没有任何运行时能力标志对 Diff 类进行门控;只要安装了 Pro 包,它们即存在。
composer require nextpdf/pro:^3概念概述
标题为“概念概述”的章节PdfDiffer::compare() 从每份文档中逐页提取文本,将其拆分为行,并对每个页面对运行一次 Myers 行差异算法,产出新增、删除和修改的区域。文本提取解析 ISO 32000-2:2020 §9.4 的文本显示运算符(Tj、TJ、')。
StructuredDiffer 在此之上构建:它将文本区域归组为段落级变更,比较嵌入图像,比较元数据,并产出一个带聚合摘要的 StructuredDiffResult。DiffFormatter 将该结果序列化为 JSON 字符串或 HTML 报告片段。
当安装了可选的 Artisan PDF 读取器时,文本提取会使用它以获得逐页精确的内容;否则会用一种有界的字节级回退方式直接扫描内容流。
为何如此设计
标题为“为何如此设计”的章节差异比较器比较的是提取出的文本和结构,而非渲染后的像素。结构化比较具有确定性、成本低廉,并能对应到审阅者所关心的编辑性变更。而像素差异反而会把抗锯齿和字体微调噪声当作内容标记出来。由于 PDF 存储的是字形和定位信息,而非可直接阅读的字符,因此每次比较都会先从内容流中重建文本。正是这一提取步骤,才使得 Artisan 读取器能提升精度,使得有界的 FlateDecode 回退以覆盖率换取安全性,也使得扫描页面几乎无法比对出差异。页面对齐保持基于索引,以确保可预测性,因此插入一页会呈现为清晰的下游位移。
设计背景:为何 PDF 中的文本并非真正的文本。
行为契约
标题为“行为契约”的章节- 输入。 源 PDF 和目标 PDF 的原始字节。不以
%PDF开头的缓冲区会抛出InvalidArgumentException。 - 输出(快速路径)。
DiffResult,包含added、removed、modified区域列表,以及isIdentical()、hasDifferences()、totalChanges()。 - 输出(结构化路径)。
StructuredDiffResult,包含段落差异、图像差异、元数据变更,以及一个DiffSummary。 - 报告输出。
DiffFormatter发出一个 JSON 字符串或一个 HTML 片段。它不产出可视化的并排红线对照 PDF。 - 资源边界。 解压后的内容流大小设有上限,以防解压炸弹;字节级扫描器避免在精心构造的输入上发生灾难性的正则回溯。
- 确定性。 对于相同的输入,差异区域和格式化后的输出是稳定的。
公共 API 接口
标题为“公共 API 接口”的章节| 类型 | 种类 | 关键成员 |
|---|---|---|
NextPDF\Pro\Diff\PdfDiffer | final class | static compare(string $sourcePdf, string $targetPdf): DiffResult, static compareTexts(array $sourcePages, array $targetPages): DiffResult, static extractText(string $contentStream): string |
NextPDF\Pro\Diff\StructuredDiffer | final class | __construct(?ImageDiffer $imageDiffer = null, ?MetadataDiffer $metadataDiffer = null), compare(string $sourcePdf, string $targetPdf): StructuredDiffResult |
NextPDF\Pro\Diff\DiffFormatter | final class | toJson(StructuredDiffResult $result): string, toHtml(StructuredDiffResult $result): string |
NextPDF\Pro\Diff\DiffResult | final readonly class | array $added, array $removed, array $modified, isIdentical(): bool, hasDifferences(): bool, totalChanges(): int |
NextPDF\Pro\Diff\StructuredDiffResult | final readonly class | text diff, paragraphs, images, metadata changes, summary |
NextPDF\Pro\Diff\DiffType | enum | Added, Removed, Modified, Unchanged |
代码示例 —— 快速上手
标题为“代码示例 —— 快速上手”的章节<?php
declare(strict_types=1);
use NextPDF\Pro\Diff\PdfDiffer;
$diff = PdfDiffer::compare( file_get_contents('v1.pdf'), file_get_contents('v2.pdf'),);
if ($diff->hasDifferences()) { echo $diff->totalChanges(), " text changes detected\n";}代码示例 —— 生产环境
标题为“代码示例 —— 生产环境”的章节<?php
declare(strict_types=1);
use NextPDF\Pro\Diff\DiffFormatter;use NextPDF\Pro\Diff\StructuredDiffer;
function reviewReport(string $oldPdf, string $newPdf): string{ $result = (new StructuredDiffer())->compare($oldPdf, $newPdf);
// JSON for machine consumption; toHtml() for a review UI fragment. return (new DiffFormatter())->toJson($result);}边界情况与注意事项
标题为“边界情况与注意事项”的章节- 差异按索引逐页对齐。在前面插入一页会令其后所有页面发生位移,并报告大量下游变更 —— 这对于按索引对齐的比较是预期行为。
- 图像比较检测新增、删除和修改的嵌入图像;它不是感知层面的可视差异,也不会对页面进行像素渲染。
- 扫描件(仅含图像)PDF 几乎不产出或不产出文本差异,因为没有执行 OCR。
- 没有可选的 Artisan 读取器时,提取会使用有界回退方式;高度压缩的文档可能产出较少的文本覆盖。
文本提取随文档字节呈线性;对于相似文档,Myers 差异接近线性,在最坏情况下每个页面对呈二次方。解压上限约束了内存。参见 performance_budget。
安全说明
标题为“安全说明”的章节字节级回退使用基于 strpos 的扫描而非无界正则,以避免在精心构造的 PDF 上发生灾难性回溯,并对解压输出设限。差异比较不执行嵌入脚本。参见 Core 安全模型。
一致性
标题为“一致性”的章节| 主张 | 规范条款 | 状态 |
|---|---|---|
解析 Tj 文本运算符以进行提取 | ISO 32000-2:2020 §9.4 | 已验证(单元测试套件) |
解析 TJ 数组文本运算符以进行提取 | ISO 32000-2:2020 §9.4 | 已验证(单元测试套件) |
| 可视化并排红线对照 PDF 输出 | — | 不支持(仅 JSON/HTML) |
Core 回退/替代方案
标题为“Core 回退/替代方案”的章节文档比较没有 Core 等价物。可选的 Artisan 读取器在安装后可提高提取精度,但并非必需。
Enterprise 边界说明
标题为“Enterprise 边界说明”的章节这是一个内容变更检测器。它不是取证级差异分析器,也不产出证据性或篡改归因报告;这些事项不在本模块的范围之内。
发布边界
标题为“发布边界”的章节本页仅记录外部可观察的行为以及受支持的公共 API 接口。内部命名空间路径、辅助类、机制表、运行手册文件名和工单前缀均不在范围之内。