ADR-0027:有界静态 PDF 页面操作适配器
- 状态:已采纳
- 日期:2026-10-16
- 关联:GAP-10、PDF 适配器 API
背景
PDF 输出后端只负责把 Display List 和交互数据写成新文件。既有 PDF 的文字提取、物理页选择和合并缺少公共入口,调用方需直接使用 PDFBox。把这些操作放进布局引擎会混淆语义建模与既有文件处理,也会把第三方对象带入核心。
PDF 页面并非独立图片。直接复制内容流会丢失继承字体、页面框或其他资源;合并包含表单、批注、签名、标签或图层的文件,还涉及全文档关系和合规性。通过“默认忽略”建立一个看似通用的接口会产生不可见损失。
决策
新增 export4j-adapter-pdf,仅公开自有不可变值类型与模型中的同步执行控制。生产依赖为 export4j-model 和已审查的 PDFBox;核心及输出后端不反向依赖适配器。
首版提供:显式字节/路径/借用流的有界快照、逐页几何与启发式文字读取、零基有序页面选择、静态页复制及合并。新目标使用自有 PDFBox 文档,复制页面字典、内容与资源,并显式落下继承页面框、旋转及资源。文件版本至少采用输入中的最高版本。
支持单 Flate、普通无滤镜流和 DCT XObject 图片。对未知/多滤镜、Type3、BI 行内图像、未知操作符以及交互、签名、附件、图层、结构树和归档 OutputIntents 明确失败。未保持的 Info/XMP/显示属性返回诊断。加密复制默认拒绝;显式允许后仍须标准安全 handler 认证的 owner 密码,并报告输出已移除加密。PDFBox 3.0.8 的 isOwnerPermission() 只检查权限位,不能单独证明 owner 身份;还要求认证后权限对象未被 user 分支锁定只读。只读位属于私有 Java 状态,输入字典不能设定,适配器不修改该对象;非标准 handler 的复制拒绝。由底层完成 R6 Unicode 密码规范化,避免复制密码算法。
编码输入、页、对象/解析工作、深度、资源、解压流、文字、操作符、声明像素及编码输出均设预算。PDFBox 会在返回解压流前分配展开内容,因此不能只在返回的流上限长读取。内部 PDFParser 在 XRef/ObjStm 解码前,用 JDK 流式 zlib 预检规模;普通流在解释前同样检查。
PDFBox 内容解析会容忍部分未闭合结构,并可能捕获数组/字典异常后返回 EOF。延迟 COS 对象读取也会捕获检查异常、记录日志后返回空对象。适配器先进行有界词法平衡检查及操作符白名单验证,再在内容解析入口计数、限深,并在延迟读取边界传播解析失败,避免预算或格式错误变为成功。白名单同时避免把任意输入操作符名加入 PDFBox 静态操作符缓存。
公共结果不宣称准确恢复阅读顺序。/ActualText 暂不作嵌套替换;发现它时返回 ACTUAL_TEXT_NOT_APPLIED,保留普通字形提取结果。路径文字及不可见锚点的提取必须由调用方结合诊断解释。页面复制保留原来的标记内容。
所有调用同步执行并关闭自身拥有的 PDFBox 文档;借用流不关闭。Path 输出使用同目录临时文件和原子替换,不作非原子降级。失败的流输出由调用方丢弃。
后果与边界
常见静态页操作有独立、易用且不泄漏第三方类型的入口,能够通过实际输出和稳定异常验证。严格拒绝范围使首版不能处理一般交互或归档 PDF,但避免对未实现的全文档保留能力作承诺。
这些预算不是安全沙箱或精确内存上限。对象流内部节点、字体及部分底层解析仍由 PDFBox 完成;对象流先限制展开字节,随后检查图节点。DCT 不在适配器内解码,声明像素检查不等同完整 JPEG 验证。处理不可信输入的服务须自行采用进程隔离和操作系统限制。
通用 PDF 编辑、交互保留、OCR、重建语义模型、标签化、签名验证和合规转换仍为独立工作。新增支持须补行为测试、独立结构与渲染证据,并收紧而非静默扩大损失边界。