PDF 对比是个”看上去简单、做起来全是坑”的场景——你以为是”文件比对”,实际上 PDF 内部至少有三层独立内容:视觉像素层、文字流层、对象树结构层,每一层都能独立 diff,结果可能完全不一样。
工具默认用哪一层、哪些场景该用哪种、为什么同一份内容也能对出”全篇都改了”——这篇讲清楚。
三种 diff 各看到什么
两份 PDF
│
├─ 像素 diff(图像层)
│ 把每页渲染成位图,逐像素比对颜色
│ 看到:印章位置、签名、手写批注、视觉错位
│ 看不到:选中复制能拷贝出的"内容"是否真的变了
│
├─ 文本 diff(文字流层)
│ 提取每页的文本对象(字符 + 字体 + 坐标)
│ 看到:条款文字增删改、数字改动
│ 看不到:印章、签名、扫描件的内容、字体改动
│
└─ 结构 diff(对象树层)
解析 PDF 的对象图(pages / fonts / images / annotations)
看到:表单字段变化、附件变化、注释变化、PDF 元数据
看不到:肉眼用户场景的差异(太底层)
典型混淆:用户拿到一个”PDF 对比工具”,觉得它就是”对比 PDF”——其实工具实现的是哪一层 diff 决定了它擅长什么场景,差异巨大。
像素 diff:看视觉、看签名、看印章
像素 diff 把每页 PDF 渲染成位图,逐像素比较颜色值。
工作流程:
1. 选定 DPI(如 150)
2. 第一份 PDF 第 1 页 → 渲染成 1240×1754 的位图
3. 第二份 PDF 第 1 页 → 同样渲染
4. 逐像素比较 RGB(或灰度)值
5. 不一致的位置标红,叠加在原图上展示
它擅长:
- 印章 / 签名是否变了位置或样式
- 手写批注(写在打印件上重新扫描)
- 扫描件之间的内容变更
- 视觉排版差异(字体、字号、行间距、页边距)
- 任何”只能看到、没有文字层”的内容
它的坑:
| 坑 | 原因 | 应对 |
|---|---|---|
| 同内容 PDF 标红一片 | 字体子集、对象顺序、坐标精度差 | 统一导出工具 |
| 文字 PDF 对比扫描重新打印 | 字体渲染 vs 扫描噪点天然不同 | 像素 diff 不可用,先 OCR |
| 两次扫描位置错位 | 进纸偏移、压平差异 | 先做图像对齐再 diff |
| 颜色配置不同(sRGB vs CMYK) | 同一红色在不同色彩空间 RGB 值不同 | 统一渲染色彩配置 |
| DPI 不一致 | 渲染分辨率不同 → 抗锯齿不同 | 工具内固定 DPI |
关键观察:像素 diff 对”完全相同来源”敏感得过头。两份真的相同的 PDF(例如同一份扫描件 OCR 处理前后),像素 diff 能查出 0 差异;但内容来源不同(哪怕看上去一致),像素 diff 几乎必然全红。
文本 diff:看条款、看数字、看文字流
文本 diff 提取 PDF 的文本对象(字符 + 字体 + 坐标),按文字序列做差异分析。
工作流程:
1. 用 PDF 引擎(PDF.js / pdftotext / mupdf)提取每页文字
2. 把文字按"阅读顺序"拼成纯文本
3. 用 diff 算法(Myers / Patience / Histogram)对齐两个文字序列
4. 标出"插入 / 删除 / 修改"的片段
它擅长:
- 合同条款增删改
- 金额、日期、姓名、编号等关键字段变更
- 长篇文档的版本对比
- 学术论文 / 报告的修订追踪
它的坑:
1. 阅读顺序 ≠ 视觉顺序
PDF 内部的文字对象按"放置顺序"存储,不是按阅读顺序
多栏排版、图文混排的 PDF 提取出来文字流可能错乱
表格里的文字常被提取成竖向字符串
2. 不可见字符干扰
全角空格、制表符、软连字符(U+00AD)、零宽空格(U+200B)
肉眼看不到,但文本流里实际存在
有的工具默认规范化掉,有的不
3. 字体差异看不到
"粗体 vs 普通"、"红色 vs 黑色"、"宋体 vs 黑体"
文字流相同 → 文本 diff 完全静默
合同里"加粗强调"的改动会被漏掉
4. 印章 / 签名 / 图像无视
文本 diff 不看图层,整页是图也提不出文字
合同最关键的"是否盖章了"它检测不到
5. 中文分词问题
多数 diff 算法按空格分词
中文连续没空格 → 要么按字符(颗粒度太细),要么按段(颗粒度太粗)
词级 diff 几乎用不上
实务建议:文本 diff 是合同 / 文档对比的主力,但永远要叠加像素 diff 复核签名页和图像内容——光看文字不够。
结构 diff:看对象树、看表单、看附件
结构 diff 解析 PDF 的对象图(object graph),比对两个对象树的差异。
PDF 对象树包含:
Document Catalog
├─ Pages tree
│ ├─ Page 1
│ │ ├─ Content stream(绘制指令)
│ │ ├─ Resources(字体、图像、颜色)
│ │ └─ Annotations(注释、链接、表单字段)
│ └─ Page 2 ...
├─ Fonts
├─ Images
├─ Forms(AcroForm / XFA)
├─ Embedded files(附件)
└─ Metadata(标题、作者、创建时间)
它擅长:
- 表单字段值变化(AcroForm 填写差异)
- 注释 / 高亮 / 批注的增删
- 附件变更
- PDF 元数据(作者、标题、修改历史)
它的坑:
| 坑 | 原因 |
|---|---|
| 几乎所有对象都标”差异” | 创建工具不同 → 对象 ID、流压缩方式、xref 排序都不同 |
| 看不懂的内部结构差异 | 对象树是给 PDF 引擎看的,不是给人看的 |
| 工具罕见 | 多数 PDF 对比工具不暴露这层 |
典型用途:法务取证、PDF 数字签名验证、AcroForm 表单审计。普通合同审查不需要。
三种 diff 的能力对照
| 场景 | 像素 diff | 文本 diff | 结构 diff |
|---|---|---|---|
| 文字内容改动 | ✓(间接,标红整片) | ✓✓(精准定位) | △(看流但难解读) |
| 数字 / 日期改动 | ✓(间接) | ✓✓ | △ |
| 字体 / 字号 / 加粗变化 | ✓(视觉看得到) | ✗ | ✓(资源对象差异) |
| 颜色变化 | ✓ | ✗ | △ |
| 印章 / 签名变化 | ✓✓ | ✗ | ✗ |
| 手写批注 | ✓✓ | ✗ | ✗ |
| 扫描件内容 | ✓(图像对齐后) | ✗ | ✗ |
| 表单字段值 | ✓(视觉值变了) | △(看是否被渲染) | ✓✓(直接读 AcroForm) |
| 附件 / 嵌入文件 | ✗ | ✗ | ✓✓ |
| PDF 元数据(作者 / 标题) | ✗ | ✗ | ✓✓ |
决策树
我要对比两份 PDF,怎么选?
├─ 是扫描件 / 图像 PDF(鼠标选不中文字)?
│ └─ 像素 diff(先尝试图像对齐,否则只能肉眼)
│
├─ 是文字 PDF,看"内容"改了什么?
│ ├─ 主要看条款 / 数字改动 → 文本 diff
│ ├─ 还要看签名页 / 印章 → 文本 diff + 像素 diff 联用
│ └─ 还要看字体 / 颜色 / 加粗 → 像素 diff 补充
│
├─ 是表单 PDF(AcroForm)?
│ └─ 结构 diff(直接读字段值,最准)
│
├─ 两份不是同一次导出(来源不同的"相同内容")?
│ └─ 文本 diff(像素 diff 必然假阳性)
│
└─ 法务 / 取证 / 数字签名验证?
└─ 结构 diff(看对象树、修订历史、签名链)
工具选型
| 类型 | 推荐 | 备注 |
|---|---|---|
| 浏览器内 / 隐私优先 | PDF 对比 | wasm 本地,文件不上传 |
| 桌面专业 | Adobe Acrobat 的”比较文件” | 文本 + 像素双轨,最稳 |
| 桌面专业 | Foxit PhantomPDF Compare | 文本 diff 强 |
| 命令行 | diff-pdf(GitHub) | 像素 diff,CI 集成方便 |
| 命令行 | pdf-diff (Python) | 文本 diff,规则化 |
| 命令行 | pdftotext + 普通 diff | 简陋但够用 |
| 在线(敏感场景慎用) | DiffPDF / Draftable | 上传到对方服务器 |
实务清单
✅ 必做:
- 对比前先判断 PDF 是文字还是扫描——决定走哪条路
- 合同 / 重要文档 → 文本 diff 主审 + 像素 diff 复核签名页
- 来源不同的”相同内容” → 必须用文本 diff,像素 diff 全是假阳性
- 隐私敏感 → 用浏览器内 wasm 工具或桌面工具
- 看不到的差异(字体、颜色、加粗)→ 切换到像素 diff
❌ 避免:
- 用纯像素 diff 对比”内容相同但来源不同”的 PDF(必假阳性)
- 用纯文本 diff 审签名页 / 印章页(漏检)
- 把扫描件直接送进文本 diff(提不出文字)
- 在线工具传敏感合同
- 把 PDF 入 git 期望 git diff 能看(只看到压缩流乱码)
- 因为”颜色变了 / 加粗了”用文本 diff 找半天找不到(要切像素 diff)
PDF 对比的真相是:没有”一键对比”——选对 diff 类型才是关键。三种 diff 各看一面,合起来才是完整图景;用错一种,看到的全是噪声或漏报关键改动。