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 层、多页重复信息以及页面外的附件等,要作为独立检查项处理。也不要把删除一个关键词当作完整脱敏:检查全部目标出现位置、非目标内容是否保留,以及接收方拿到的最终文件。

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

相关推荐
FinelyYang12 小时前
uniapp使用kkfileview在线预览ppt,excel,word,pdf类型文件
pdf·uni-app·powerpoint
WORLDPO92261 天前
QTE-020-01-L/F/C-D-A-K-TR_实物_数据手册PDF_国产替代
pdf
asdzx671 天前
C# 操作 PDF 元数据:从属性写入到字段提取的完整流程
前端·pdf·c#
WORLDPO92261 天前
SFSDT-10/15/20/25/35-28-G-19.69-SR_实物图_数据手册PDF_国产替代
pdf·pcb工艺·pcb
梦想出海-Phoebe1 天前
Gemini新增文件编辑功能:PDF、Word、Excel处理体验分享
pdf·word·excel
开开心心就好1 天前
网页图片批量下载工具,输入链接就保存
服务器·算法·leetcode·支持向量机·pdf·ocr·1024程序员节
汤姆宝库2 天前
法考历年真题及答案解析合集pdf版(2002-2025年)
pdf·法考
优化控制仿真模型2 天前
2026【英一】考研英语一历年真题及答案解析PDF电子版(1980-2025年)
考研·pdf
云月呀3 天前
《死亡回忆录》藏汉双语版电子版 PDF,穆迪博士 150 例濒死体验访谈,一部关于生死的温柔读本
pdf·死亡回忆录
xiaolinudao1234 天前
给pdf文件盖骑缝章的软件有哪些
pdf