PDF 对比的三种方式:像素 diff / 文本 diff / 结构 diff,哪种适合什么场景

· 约 6 分钟 🔀 PDF 对比

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上传到对方服务器

实务清单

必做

  1. 对比前先判断 PDF 是文字还是扫描——决定走哪条路
  2. 合同 / 重要文档 → 文本 diff 主审 + 像素 diff 复核签名页
  3. 来源不同的”相同内容” → 必须用文本 diff,像素 diff 全是假阳性
  4. 隐私敏感 → 用浏览器内 wasm 工具或桌面工具
  5. 看不到的差异(字体、颜色、加粗)→ 切换到像素 diff

避免

  1. 用纯像素 diff 对比”内容相同但来源不同”的 PDF(必假阳性)
  2. 用纯文本 diff 审签名页 / 印章页(漏检)
  3. 把扫描件直接送进文本 diff(提不出文字)
  4. 在线工具传敏感合同
  5. 把 PDF 入 git 期望 git diff 能看(只看到压缩流乱码)
  6. 因为”颜色变了 / 加粗了”用文本 diff 找半天找不到(要切像素 diff)

PDF 对比的真相是:没有”一键对比”——选对 diff 类型才是关键。三种 diff 各看一面,合起来才是完整图景;用错一种,看到的全是噪声或漏报关键改动。

❓ 常见问题

我两份"内容"完全一样的 PDF 一对,为什么显示几乎全篇都改了?

多半是像素 diff,且两份 PDF 不是同一次导出。同一份 .docx 用 Word 导出两次,PDF 内部的字体子集 ID、对象顺序、坐标精度都会有微小差异——视觉上肉眼相同,但渲染到像素时每个字符位置可能差零点几个像素,diff 工具按像素颜色比对就会标红。判断方式:(1) 切换工具的"对比模式",看有没有"文本对比 / 内容对比"选项;(2) 如果只有一种"图像对比"模式,那就是像素 diff,对"内容相同但来源不同"的 PDF 几乎必然误报;(3) 真要看内容差异,导出时统一用同一个工具、同一份字体源、再做对比。

公司发来一前一后两版合同要审,应该用哪种 diff?

复合用法主体走文本 diff——找出条款增删改、金额数字改动、关键日期变更,这些都是文字流层面的事;签名页 / 盖章页 / 手写批注 / 扫描页面走像素 diff——这些没有文字层,只有像素能看出"印章位置变了"、"签名变了"、"加了一行手写批注"。实务:(1) 先文本 diff 全篇,扫一遍条款级改动;(2) 翻到签名页 / 任何含图像的页用像素 diff 复核;(3) 文本 diff 通过但像素 diff 报警的页面,肉眼最终判断;(4) 极重要的合同 → 让对方明确说改了哪些条款,对比是辅助验证不是替代沟通。

扫描件的 PDF 能对比吗?文本 diff 是不是直接没用?

只能像素 diff,文本 diff 真的没用。扫描件 PDF 内部是一张张图片,没有文本对象,文本 diff 提取出来什么都没有(或者全是空白)。像素 diff 的局限:(1) 两次扫描的对齐误差——哪怕是同一张纸第二次扫描,扫描仪进纸位置、压平程度、扫描分辨率都会有差异,像素层面几乎全篇标红;(2) 解决方法是先做"图像对齐"(image registration)——把第二份扫描旋转 / 平移 / 缩放到与第一份匹配再做差异检测,但多数 PDF 对比工具不带这步;(3) 更稳的做法:先对扫描件跑 OCR 拿到文字层,再做文本 diff。

文本 diff 显示一致,但我肉眼看到两份明显不一样,为什么?

几个可能源。(1) 不可见字符——空格 / 制表符 / 全角空格 / 软连字符(U+00AD)/ 零宽空格(U+200B)肉眼看不出,文本提取阶段有的工具会规范化掉、有的不会;(2) 字体 / 字号 / 颜色 / 加粗变化——文本流相同但渲染样式不同,文本 diff 完全静默;(3) 图像 / 印章 / 签名变了——文本 diff 不看图层;(4) 页边距 / 排版 / 换页位置——内容相同但分页不同;(5) 文字阅读顺序——PDF 内部文字流的顺序未必等于视觉阅读顺序,多栏排版、绝对定位的文字提取出来可能"完全相同"但视觉布局完全不同。真要查这种"视觉差异"必须叠加像素 diff

PDF 对比工具怎么把两份 PDF 对齐?页数不同会怎么样?

三种对齐策略。(1) 按页号对齐(最简单)——两份 PDF 第 1 页比第 1 页、第 2 页比第 2 页,页数不同时多出来的页全标"新增 / 删除"。问题:如果中间插了一页,后面所有页都会全标红。(2) 按文字流对齐(智能)——把两份 PDF 的全文 token 化,用 Myers diff 之类的算法对齐文字序列,再反向映射回页面,能正确识别"中间插入一段"。(3) 按结构对齐——按章节标题、书签、PDF 结构树对齐。准但需要 PDF 本身有完整目录。实务:好的对比工具默认走文字流对齐,复杂场景还可以手动指定锚点;简陋的工具只支持按页对齐,遇到插入 / 删除整段就乱套。

我只改了一个标点符号为什么对比报告整段都标红?

diff 算法的"颗粒度"问题。文本 diff 算法(Myers / Patience / Histogram)默认按"行"或"段"为单位对齐,标红的是发生差异的整段,不是只标改动的字符。控制颗粒度的几种做法:(1) 用支持"字符级 diff"的工具——把文本流拆成字符序列后再对齐,颗粒度最细但慢;(2) 用支持"词级 diff"的工具——按空格 / 标点切分,比段级更精确;(3) 看工具是否支持"显示颗粒度"切换。陷阱:中文没空格分词,多数工具对中文要么按字符要么按段,"词级 diff"几乎用不上;想要更精细需要先分词再 diff。

PDF 对比涉及隐私(合同 / 财务),能完全本地跑吗?

三种方式都能本地。(1) 像素 diff:用 PDF.js / mupdf 把每页渲染成 canvas → ImageData 比对像素 → 浏览器内 100% 本地;(2) 文本 diff:PDF.js 提取文字流 → 走 JS 端 diff 算法(diff-match-patch / jsdiff)→ 也是 100% 本地;(3) 结构 diff:解析 PDF 对象树需要更深的 PDF 引擎,pdf-lib / mupdf-wasm 都能做,仍然本地。警惕:在线版 PDF 对比工具几乎都要上传文件——敏感场景一律选浏览器内 PDF 工具或桌面工具(Adobe Acrobat / Foxit 桌面版),本站 PDF 对比 全程 wasm 本地处理,文件不上传。

为什么不直接用 git diff 看两份 PDF?

git diff 不认识 PDF。PDF 是二进制 + 流压缩格式,git 默认按字节比对,看到的是 zlib 压缩后的乱码。几个补救方式:(1) git diff --textconv 配合 pdftotext,把 PDF 转成纯文本再 diff——失去格式但能看内容改动;(2) 用专门的 git PDF diff 驱动(如 git config diff.pdf.textconv pdftotext);(3) 在 .gitattributes 里给 *.pdf 配 textconv;(4) 更彻底:PDF 不该进 git——把源文件(.docx / .tex / .md)入库,PDF 作为构建产物。

🔀 打开 PDF 对比 两份 PDF 像素级对比·红色高亮差异·并排同步滚动·本地处理