AIGC标识 PDF 批注怎么彻底删掉?隐藏≠删除,附验证命令

很多人遇到过这个情况:在 PDF 上写了批注,删掉、保存、发出去,结果对方还是看到了。

先说结论:你在阅读器里"删除批注",很多时候只是让它不再渲染,对象还在文件里。 这篇讲清楚批注到底存在哪、为什么删不掉、怎么验证。

一、批注在文件里的位置

PDF 是对象树结构。批注不是画在页面上的像素,而是独立对象,通过引用挂在页面字典的 /Annots 数组上:

4 0 obj
<< /Type /Page
   /Parent 2 0 R
   /Contents 5 0 R
   /Annots [10 0 R 11 0 R]     % 批注引用
>>
endobj

10 0 obj
<< /Type /Annot
   /Subtype /Text
   /Rect [100 700 120 720]
   /Contents (这里的价格需要重新确认)
   /T (zhangsan@company.com)
   /M (D:20261024153000+08'00')
   /Popup 13 0 R
>>
endobj

三个字段要注意:

  • /T(作者):你用什么账号登录编辑器,这里就留什么,通常是邮箱。
  • /M(修改时间):精确到秒。
  • /Popup:弹出框是独立的引用对象,不是批注的内嵌字段。

最后一条就是"删不干净"的第一个原因:删了主批注对象,Popup 还挂在文件里。

二、为什么"删掉"了对方还能看见

有三个层次的原因,从浅到深:

层次 1:隐藏 ≠ 删除

阅读器里的"隐藏批注"只是不渲染,对象仍在 /Annots 数组里。换个工具扫描批注层,一条不少。

层次 2:增量更新保留了旧字节

PDF 支持增量更新:修改时不重写全文,而是在文件尾部追加一段新的 xref,用 /Prev 指向旧的。

结果是:你"删除"的对象,字节完整地留在文件里。

pdftotext 抽不到,是因为它按最终状态解析。但字节还在,动手就能拿到:

strings 文件.pdf | grep -i "你批注过的关键词"

层次 3:批注层根本不在正文流里

这点最反直觉。批注是页面字典的独立字段,不在 /Contents 指向的内容流中。所以:

  • 扫描内容流找批注 → 找不到(本来就不在那);
  • 只看 /Annots → 才能看到全部。

三、正确的删除方式

步骤 1:用库在对象层操作,别手写字节

直接改字节会破坏交叉引用表(xref 记录每个对象的字节偏移),删了字节后面全错位 → 文件打不开。

用 pdf-lib:

const { PDFDocument, PDFName } = require('pdf-lib');
const fs = require('fs');

const bytes = fs.readFileSync('in.pdf');
const pdfDoc = await PDFDocument.load(bytes);

for (const page of pdfDoc.getPages()) {
  page.node.set(PDFName.of('Annots'), pdfDoc.context.obj([]));
}

fs.writeFileSync('out.pdf', await pdfDoc.save());

步骤 2:处理增量更新

对象层删除之后,旧版本的字节还在。做一次完整重写:

# mupdf:清理未引用对象 + 解压流
mutool clean -g -s -d in.pdf out.pdf

# qpdf:线性化重写
qpdf --linearize in.pdf out.pdf

mutool clean 的参数:-g 垃圾回收(删未引用对象)、-s 清理未使用资源、-d 解压所有流。

步骤 3:验证(这一步不能省)

# 1. 抽全文,搜批注关键词
pdftotext out.pdf - | grep -i "价格需要重新确认"

# 2. 搜原始字节(查增量更新残留)
strings out.pdf | grep -i "价格需要重新确认"

# 3. 看文档属性
pdfinfo out.pdf

两条 grep 都搜不出来,才算真的清干净。

四、Subtype 不同,处理方式不一样

Subtype 含义 注意点
/Text 便签 删对象即可
/Highlight 高亮 只删批注,正文不变
/FreeText 自由文本 看起来像正文,实为浮层
/Widget 表单域 删了表单就不能填,慎删
/Watermark 水印注释 只能对象层删,白块盖不住
/Stamp 图章 通常带外观流(/AP)

/FreeText 是最大的坑:用户打字改了数字,屏幕显示新值,但底下原文一个字节没动。对方框选就能复制出原始数字。

五、顺带说一下检测清单

如果你的需求是"文件外发前扫一遍",批注只是其中一层。完整的有八层:

检测项 技术要点
文字水印 内容流扫描;中文走 Identity-H CID + ToUnicode CMap 还原
图片水印 递归 Form XObject + cm 矩阵复合映射
注释水印 /Subtype /Watermark,对象层删
元数据 作者 / 公司 / 路径 / 软件 / 时间
隐藏文字 白字、被遮挡、图层下
批注与修订 /Annots + /Popup + 增量更新段
隐私信息 身份证 / 手机 / 银行账号(含图片 OCR)
暗水印 频域 / 像素级

六、总结

三句话:

  1. 批注是独立对象,不是页面像素 —— 所以在 /Annots 里,不在内容流里。
  2. "看不见"不等于"不存在" —— 增量更新和 Popup 都会残留。
  3. 验证只需要两条 grep —— 别信工具的"已清除"。
pdftotext 文件.pdf - | grep -i "关键词"
strings 文件.pdf | grep -i "关键词"

相关工具:PDFnoted(pdfnoted.com),做文档安全工作流(扫描 → 脱敏 → 批注 → 导出),元数据清理、批注清理、文字水印清理免费且免登录。

posted @ 2026-10-26 20:13  PDFnoted  阅读(4)  评论(0)    收藏  举报