文档扫描与图像处理

浏览器里的 PDF 标注与脱敏工作台:六种导出格式怎么选

在线 PDF 与图像标注工作台界面

“在网页里看 PDF”和“在网页里改 PDF”是两件事。前者一个 pdf.js 就够了,后者要处理注释对象模型、页面替换、内容层重写、多格式导出这一整套。在线 PDF 与图像标注工作台建立在一套文档查看器引擎之上,把标注、涂黑脱敏、页面增删与六种导出串成一条完整的文档处理链路。

在线演示

在线 PDF 与图像标注工作台(英文名 Online PDF & Image Annotation Studio):打开就能用,不需要注册,也不需要安装;文档始终留在浏览器里,导出时才在你的设备上生成文件。相机采集需要 HTTPS 或 localhost。

配合本文验证时可以这样走一遍:打开一份 PDF,用「检测」里的条码或 MRZ 各跑一次,再用「涂黑」标出一块区域并执行应用,然后把同一份文档分别导出为可编辑 PDF、扁平化 PDF 和多页 TIFF,对比三者的差别。

关键要点

  • 文档引擎是 Dynamsoft Document Viewer 5.0.0(从 jsDelivr 加载引擎资源),并内置了 Capture Vision JS 版(3.6.3200)用于条码与 MRZ 检测。
  • 输入来源有四条:本地文件(PDF/PNG/JPEG/TIFF/BMP,含加密 PDF)、摄像头拍照、Dynamic Web TWAIN 本地扫描仪服务、以及一个未配置 OAuth 的云端入口(会给出明确提示而不是假装可用)。
  • 新页面一律追加到当前文档,不替换;导入成功/失败都有提示。
  • 涂黑是两段式:先创建涂黑标记用于查看和调整位置,再执行“应用涂黑”,由引擎在内容层删除被覆盖的原始内容。
  • 导出格式六种:可编辑 PDF、扁平化 PDF、图像化 PDF、PNG、JPEG、多页 TIFF。

页面结构:一个空状态加四条通路

打开时是一个空状态卡片:“打开一份文档开始标注”,主按钮是“把文件拖到这里”和“选择一个文件”,下方列出支持的格式(PDF/PNG/JPEG/TIFF/BMP/加密 PDF)。文档载入后,顶栏出现六个动作入口:涂黑盖章删除检测添加云盘,以及主按钮导出。其中前三个在打开页面之前是禁用状态。

检测:三种能力按需触发

“检测”菜单下有三项,各自调用不同的预设模板:

菜单项 模板 结果处理
文档边缘 DetectDocumentBoundaries_Default 取四边形,按 atan2 角度排序成左上/右上/右下/左下,确认后矫正并替换当前页
条码 ReadBarcodes_ReadRateFirst CRIT_BARCODE,每个结果画一个多边形轮廓加一条带码值的小标签
MRZ ReadMRZ CRIT_TEXT_LINE,拼接后过滤非 MRZ 字符并转大写,交给 Code Parser 解析成字段

文档边缘这一项设计的取舍很清楚:没有检测到四边形时不是报错,而是给一个内缩 10% 的默认框并提示“未检测到文档边界,请手动调整”——用户多半只是需要微调,而不是需要一个失败提示。

MRZ 解析前会先规范化文本,这一步不能省:

mrzTexts.join("").replace(/[^A-Z0-9<]/gi, "").toUpperCase()

识别器可能返回带空格或换行的文本行,MRZ 校验位的计算只认 A–Z0–9 和填充符 <,任何多余字符都会让校验失败。

检测结果的覆盖物是作为注释对象画在页面上的,每条结果对应一个多边形轮廓注释(透明填充、2 px 边框)加一个显示码值的小文本框,按 pageUid 记录在映射表里,所以“清除覆盖物”能一次性删掉,也不会动到原始页面内容。

文档边缘的四个角点需要重新排序才能当 ROI 用:先按围绕质心的角度排序,再取 x + y 最小的那个作为左上角,其余依次排成左上/右上/右下/左下。检测器返回的顺序不保证是这个顺序,直接拿去做透视矫正会得到一张翻转或旋转的图。

涂黑脱敏:为什么必须是内容层删除

这是这个工作台最需要讲清楚的一点。很多“网页版 PDF 涂抹工具”的做法是在画布上画一个黑色矩形然后导出成图片——如果导出的是图片没什么问题,但如果导出的是 PDF,原文仍然藏在内容流里,选中、搜索、复制都能拿到。

示例里的“涂黑”按钮只做标记

annotationManager.createAnnotation(pageUid, "redaction", {
  redactionType: "rectangle",
  background: "#000000",
  rects: [{ x, y, width, height }],
  overlayText: {
    text: "REDACTED", color: "#ffffff", textAlign: "center",
    fontSize: 10, repeatText: true, autoFontSize: true
  }
});

创建后会提示:“已添加涂黑标记。移动或调整大小,然后在涂黑工具栏里应用。”真正的删除发生在执行阶段,由文档查看器的涂黑工具栏调用引擎接口 applyRedaction(pageUid, [uid]),引擎重写页面内容。从那之后,原始内容不再存在于文档里,REDACTED 只是给人看的标记。

这个区别在合规场景里是决定性的:涂黑的目的就是让不可读的信息实际上不可得,任何“只是盖住”的实现都不能算完成脱敏。

配套的还有两个动作:盖章创建一个 170×34 的文本框,内容是 REVIEWED <日期>,黄底深字;删除调用 doc.deletePages([index]) 删掉当前页,如果删的是最后一页就关闭文档。

添加页面:文件、拍照、扫描仪

“添加”菜单下三条通路,进入的都是同一个追加流程。

从文件:支持 PDF、PNG、JPEG、TIFF、BMP,可多选。文件以 ArrayBuffer 读入后追加到文档尾部。遇到加密 PDF 会走错误分支,弹出密码对话框。

从摄像头:请求 1920×1080 后置画面,可切换设备;每次拍摄画到画布上并以 JPEG 质量 0.92 编码:

canvas.toBlob(callback, "image/jpeg", 0.92);

拍到的照片以“camera photo N”为名追加成新页,拍摄过程中会维护一条缩略图带,最后一次性“添加照片”。

从扫描仪:按需加载 Dynamic Web TWAIN 脚本,启用本地服务,枚举设备后按固定参数采集:

await scanner.SelectDeviceAsync(device);
await scanner.AcquireImageAsync({
  IfShowUI: false,
  IfCloseSourceAfterAcquire: true,
  IfFeederEnabled: true,       // 使用送纸器
  IfDuplexEnabled: false,      // 单面
  PixelType: TWPT_RGB,         // 彩色
  Resolution: 200              // dpi
});

采集到的页通过 ConvertToBlob(indexes, IT_PDF) 转成一个 PDF 再追加,所以从扫描仪进来的一份多页文档在编辑器里是一页一页的。找不到设备时的提示是具体的:“未找到扫描仪。请确认 Dynamic Web TWAIN 服务正在运行,然后刷新。”

云盘这一项没有实现:点击后给出提示“托管演示未配置 Google Drive 上传——每个项目需要自己的 OAuth 客户端 ID”,并指向教程。一个未配置的入口会明确说出来,而不是转圈或者抛一个看不懂的错误。

导出:六种格式怎么选

导出项 调用 适用场景
PDF — 可编辑 saveToPdf({ saveAnnotation: "annotation" }) 标注要留给对方继续改
PDF — 扁平化 saveToPdf({ saveAnnotation: "flatten" }) 标注定稿,不希望被改;外观与编辑时一致
PDF — 图像化 saveToPdf({ saveAnnotation: "image" }) 版式必须在任何阅读器里一致,可接受不能搜索
PNG saveToPng(pageIndex) 需要无损单页图,多页会逐页下载
JPEG saveToJpeg(pageIndex, { quality: 90 }) 需要体积更小的单页图
TIFF saveToTiff({ compression: TIFF_AUTO }) 归档、传真流,或需要多页位图封装

两个容易忽略的细节:JPEG 质量显式指定为 90(引擎默认是 80);TIFF 压缩方式可切换 TIFF_AUTOTIFF_FAX3TIFF_FAX4TIFF_LZWTIFF_JPEG,其中 FAX 系列对纯黑白文档的压缩率远高于通用压缩。多页 TIFF 是一个文件,PNG 和 JPEG 多页时会输出多个文件。

另外,导出可编辑或扁平化 PDF 时会先检查标注许可是否可用——这两条路径依赖引擎的注释写入能力。

构建与维护:为什么不能手改产物

这个页面是 Vite/TypeScript 构建产物加一层手写外壳。外壳里有两处页面级 CSS 覆盖,都是踩过坑之后加的:

  • 品牌样式表必须加载在打包样式之后,否则覆盖不生效。
  • 打包样式里有个全局 .btn:hover:not(:disabled) 规则,优先级(0,3,0)压过了浅色底按钮自己的悬停规则(0,2,0),导致白色空状态卡片上的主按钮悬停时变成透明白。需要更高优先级的选择器才能压回去。
  • 打包样式对部分标题请求 600/700 字重,而页面只加载了 300/400/500,浏览器会合成“伪加粗”,配合原本的负字距看起来又挤又重。解决办法是固定字重并加上 font-synthesis-weight: none,不允许伪造没加载的字重。

维护上的两条约定:不要构建之后再手改 index.html(下次构建会覆盖),以及替换构建产物时要删掉 assets/ 里旧的内容哈希文件——Vite 每次生成的文件名都不同,留着旧文件是仓库里最常见的垃圾来源。

常见问题

涂黑之后,PDF 里的原文还能被复制或搜索出来吗?

不能。执行“应用涂黑”时是文档引擎在内容层重写页面,把被覆盖区域的原始内容真正删除,而不是在上面画一个黑色矩形。如果只是叠加一层黑框,原文仍然可以被选中、复制或提取——那等于没有脱敏。

可编辑 PDF、扁平化 PDF、图像化 PDF 有什么区别?

可编辑 PDF 保留原生注释对象,收到的人仍能修改或删除标注;扁平化 PDF 把标注“烧”进页面内容,外观不变但不能再单独编辑;图像化 PDF 把每页变成一张位图,体积大、不可搜索,但版式在什么阅读器里都一致。

浏览器能直接调用桌面扫描仪吗?

可以,但需要本机安装 Dynamic Web TWAIN 的本地服务,页面通过它与扫描仪通信。示例请求的是 200 dpi、RGB 彩色、启用送纸器、关闭双面,扫描完成后把结果转成 PDF 追加进当前文档。

加密的 PDF 怎么处理?

打开时会弹出密码输入框,提示“某某文件已加密”,输入正确密码后正常载入;取消则不做任何改动。密码长度上限为 32 个字符。

为什么手机上少了“从扫描仪”这一项?

扫描仪采集依赖桌面端的本地服务,手机上没有可用路径,菜单项在小屏断点下被隐藏,避免留一个点不动的入口。摄像头和文件两条通路在手机上照常可用。

相关阅读