PDF 涂黑后还能复制?用 ComPDF 检查敏感文字是否真正删除

PDF 中的一段文字被黑色矩形盖住后,画面上可能看不见,文件里的文字却未必被删除。判断处理是否生效,要检查导出后的文件。

下面用 ComPDF Web SDK 对同一份教学 PDF 做三种处理:普通矩形遮挡、仅创建密文标记、应用密文。随后重新读取输出,核对目标文字和需要保留的内容。

先看输出结果

教学文件含有两个标记:TEST-SECRET-ALPHA 是本次处理目标,KEEP-PUBLIC-BETA 应保持原样。它们是虚构教学文字,不是账号、密钥或客户信息。

处理方式 目标文字能否提取 保留标记是否还在
普通黑色矩形 能 在
创建密文标记,未应用 能 在
应用密文后导出 本次未提取到 在

图:三个真实输出 PDF 的独立渲染与文字提取核对。仅创建密文标记不会删除正文;导出文件在其他阅读器中的标记外观可能不同,不能靠是否出现黑块判断已经应用。

这组对照使用 Web SDK 2.9.4。关键 PDF 处理由 ComPDF 完成,后面的 Python 读取仅用于独立核验输出。

1. 准备最小项目

本例在 Node.js 24.18.0、Vite 7.1.9 下运行。创建目录并安装依赖:

sh 复制代码
mkdir compdf-redaction-demo
cd compdf-redaction-demo
npm init -y
npm install --save-exact @compdfkit_pdf_sdk/webviewer@2.9.4 vite@7.1.9
mkdir -p public/webviewer public/samples
cp -a node_modules/@compdfkit_pdf_sdk/webviewer/dist/. public/webviewer

从SDK 试用许可取得 Web 许可,在项目根目录创建 .env.local:

dotenv 复制代码
VITE_COMPDF_LICENSE=YOUR_WEB_SDK_LICENSE

这是 Web SDK 许可,不是 Cloud API 的 x-api-key。许可配置见添加 Web 许可密钥。公开示例只保留占位符,实际许可单独配置。

把待检查的 PDF 放到 public/samples/redaction-source.pdf。如果需要快速制作与下文坐标对应的最小教学输入,可以运行:

sh 复制代码
python3 -m pip install reportlab
python 复制代码
# make_sample.py
from reportlab.pdfgen import canvas

c = canvas.Canvas('public/samples/redaction-source.pdf', pagesize=(595, 842))
c.setFont('Helvetica', 18)
c.drawString(60, 500, 'TEST-SECRET-ALPHA')
c.drawString(60, 410, 'KEEP-PUBLIC-BETA')
c.save()
sh 复制代码
python3 make_sample.py

配图中的输入另加了品牌和中文教学说明,两个标记的位置与上面的最小输入相同。实际文件需要按自己的页面坐标选区域,不能照搬矩形。

2. 添加普通遮挡,再创建密文标记

在根目录保存下面的 index.html:

html 复制代码
<!doctype html>
<html lang="zh-CN">
<meta charset="UTF-8">
<title>PDF 密文验证</title>
<style>
  body { font-family: sans-serif; margin: 16px; }
  button { margin: 0 6px 10px 0; padding: 8px; }
  #viewer { height: 80vh; }
</style>
<button id="reset" disabled>重新打开原件</button>
<button id="cover" disabled>添加普通遮挡</button>
<button id="mark" disabled>创建密文标记</button>
<button id="apply" disabled>应用密文</button>
<button id="save" disabled>下载当前 PDF</button>
<p id="status">正在加载</p>
<div id="viewer"></div>
<script type="module">
  import WebViewer from '@compdfkit_pdf_sdk/webviewer';

  const status = document.querySelector('#status');
  const core = await WebViewer.init({
    path: '/',
    pdfUrl: '/samples/redaction-source.pdf',
    license: import.meta.env.VITE_COMPDF_LICENSE
  }, document.querySelector('#viewer'));
  const doc = core.docViewer;
  let stage = 'source';

  doc.addEvent('documentloaded', () => {
    document.querySelectorAll('button').forEach(b => b.disabled = false);
    status.textContent = '原件已打开';
  });

  // Web SDK 的页面坐标以左上角为原点,pageIndex 从 0 开始。
  const rect = { left: 55, top: 320, right: 310, bottom: 353 };

  document.querySelector('#reset').onclick = async () => {
    await core.UI.loadDocument('/samples/redaction-source.pdf');
    stage = 'source';
  };
  document.querySelector('#cover').onclick = async () => {
    await doc.addAnnotations({
      type: 'square', pageIndex: 0, rect,
      borderWidth: 0, opacity: 1, fillTransparency: 1,
      fillColor: '#000000', color: '#000000',
      borderColor: '#000000', borderStyle: 'solid', dashes: []
    });
    stage = 'covered';
    status.textContent = '已添加普通矩形';
  };
  document.querySelector('#mark').onclick = async () => {
    await doc.addAnnotations({
      type: 'redact', pageIndex: 0, rect,
      fillColor: '#000000', erasure: false
    });
    stage = 'marked';
    status.textContent = '已标记,尚未应用';
  };
  document.querySelector('#apply').onclick = async () => {
    await doc.applyRedactions('all');
    stage = 'redacted';
    status.textContent = '已应用;请导出后核查';
  };
  document.querySelector('#save').onclick = async () => {
    const data = await doc.exportPDF(true);
    const url = URL.createObjectURL(new Blob([data], { type: 'application/pdf' }));
    const a = document.createElement('a');
    a.href = url;
    a.download = stage + '.pdf';
    a.click();
    setTimeout(() => URL.revokeObjectURL(url), 10000);
  };
</script>
</html>

启动:

sh 复制代码
npx vite --host 127.0.0.1

打开终端显示的本地地址。这里将 SDK 静态资源复制到 public/webviewer,初始化的 path 使用 /,使阅读器入口落到 /webviewer/index.html;部署到子目录时,应一起调整资源与 PDF 路径。

按下面顺序操作,确保三份输出来自同一原件:

  1. 添加普通遮挡,下载 covered.pdf。
  2. 重新打开原件,创建密文标记,先下载 marked.pdf。
  3. 对当前标记应用密文,再下载 redacted.pdf。

ComPDF 的密文处理分为标记和应用两个步骤。addAnnotations({ type: 'redact', ... }) 只是标记区域;本例随后调用 applyRedactions('all') 才处理标记内容。保留原件,在副本上完成验证。

图:本地教学项目嵌入 ComPDF Web SDK 的实际操作界面,目标区域已应用密文。下文仍检查导出文件。

3. 独立核对导出文件

把三份文件放在同一目录,安装读取工具:

sh 复制代码
python3 -m pip install pypdf
python 复制代码
# verify_outputs.py
from pathlib import Path
from pypdf import PdfReader

for name in ['covered', 'marked', 'redacted']:
    text = '\n'.join(page.extract_text() or ''
                     for page in PdfReader(Path(name + '.pdf')).pages)
    print(name, {
        'target_found': 'TEST-SECRET-ALPHA' in text,
        'retained_found': 'KEEP-PUBLIC-BETA' in text,
    })
sh 复制代码
python3 verify_outputs.py

本次结果:

text 复制代码
covered  target_found=True  retained_found=True
marked   target_found=True  retained_found=True
redacted target_found=False retained_found=True

再用另一阅读器打开输出,检查目标区域和周围内容。文字提取结果用于核验这组教学标记,不证明图片、附件、表单、元数据或历史内容都已经清理。

实际文件还需要检查什么

这里处理的是电子 PDF 中已知位置的文字区域。扫描图片、隐藏 OCR 层、多页重复信息以及页面外的附件等,要作为独立检查项处理。也不要把删除一个关键词当作完整脱敏:检查全部目标出现位置、非目标内容是否保留,以及接收方拿到的最终文件。

先用自己的教学副本跑通"标记 → 应用 → 导出 → 重开核查",确认结果符合任务,再接入业务流程。

相关推荐
飞塔老梅子2 小时前
21. 按页数或章节分割大PDF文件 ❀ 老梅子学AI
pdf·分割·basic·pdfsam·裁切
开开心心就好2 小时前
文件关联被破坏怎么修?免费工具一键修复
java·服务器·开发语言·pdf·ocr·启发式算法·宽度优先
黄一一9112433 小时前
吾爱大佬开发,离线可用!PDF文件处理工具,本地网页版文件-浏览器里就能搞定,所有操作系统可用
windows·安全·阿里云·pdf·.net·开源软件
开开心心就好3 小时前
Windows视频播放器推荐,精简版体积更小
java·服务器·开发语言·eclipse·pdf·ocr·音视频
开开心心就好3 小时前
A3的PDF怎么切成A4?拖一下分割线就行
服务器·开发语言·python·算法·pdf·ocr·tornado
PaperData3 小时前
2004-2025年中国农产品价格调查年鉴EXCEL+PDF
大数据·pdf
蓝星空20003 小时前
如何将 PDF 转换为 .md 格式的 Markdown 文件?无需上传,3 步完成
人工智能·pdf·编辑器
qiushijin9617 小时前
【2026.12】日语能力考试N1、N2、N3、N4、N5历年真题及答案合集PDF(2010-2026年)
经验分享·pdf
工具派21 小时前
PDF里没有表格,PDF转Excel行列是靠什么对上的?
pdf·excel