ADR-0111:普通文档按页规划、重放与释放
- 状态:接受;验收证据独立记录。
- 日期:2026-11-06。
背景
普通文档的完整布局入口保留全部 Layout IR,绘制入口返回全部 Display List。只换成磁盘 PDF 后端不能释放这两层结果。需要保留总页数、目录、分节页码、未来引用及脚注,同时让导出调用方不必持有完整布局结果。
决策
保留现有 layout/export 的完整返回值,新增 DocumentEngine.exportPaged,接受普通、带语义页区及分节文档。请求选择 PageBackend,返回页数、诊断计数和写出字节等轻量摘要。核心分页仍使用 DocumentLayouter 的原有 Flow,通过同步页回调交付;页绘制会话复用有界图片及边框缓存,消费后释放页面和已消费块的测量缓存。
组件层先规划最终分页及导航,再重放逐页解析字段、绘制、合成页区和输出。规划保留紧凑目标、节页数和脚注分配,不保留全部正文页面。目录偏移、物理页索引与显示页码分别处理。所有遍历在调用线程执行,预算、取消及异常继续沿现有执行上下文传播。
自定义组件必须可以确定性重测;重放核对规划结果,变化使整次导出失败。固定页区以实际最终总页数合成。页面 SPI 无评论及可编辑字段通道,非空声明在打开写出器前拒绝,不能静默丢弃。
PdfSpoolBackend 保留普通渲染器的保存步骤以完成字体子集化,并以有界丢弃流测量单页序列化字节;保存后直接复制仍存活的页对象图,不再写入和重新解析单页 PDF。复制结束才关闭单页文档并解除字体程序固定。原单页字节预算保持,临时工作预算继续保守计入该页的序列化等价大小,磁盘只保存索引。
完整输入与资源复用
Document.Builder 在加入顶层语义表格时,以构建器私有的有界 LRU 复用相等的纯行内单元格和样式,分别最多 4,096/512 项。只处理源 UTF-16 长度至多 1,024、运行至多 16、单个字体角色至多 128,且源、运行、角色和链接合计至多 4,096 个 UTF-16 单位的单元格。块格、图片、字段及超限内容原样保留,不进入缓存;不递归改写组件和嵌套来源。成功构建后清理缓存,文档之间不共享缓存状态。值复用不改变格式、跨度、运行边界、链接或来源 ID。
顶层段落复用同一有界样式池,不缓存段落或文字;运行至多 16、单个字体角色至多 128,源、运行、角色和链接合计至多 4,096 个 UTF-16 单位时才复用样式,保留图片、字段、空运行及原始索引。构建器只在生成自动 ID 时建立既有来源 ID 索引,仍完整遍历和校验加入的块;成功构建后释放索引,继续使用构建器时重新收集既有 ID,自动序号继续递增。
段落与单元格共用混合内容组装和校验,单个文字运行复用既有字符串,校验按原 UTF-16 区间比较,不为验证生成拼接副本。图片与字段的标记、样式和链接约束保持。绘制资源映射继续遍历并检查预算,只在资源或子命令实际变化时重建列表;不可变命令索引与逻辑文字绑定保持。常用黑白文字颜色使用既有不可变值。
FontData.readOnlyBuffer() 为字体解析器提供完整程序的独立只读视图,避免额外复制整个字体;底层字节仍由 FontData 防御复制并持有,视图不能暴露可写数组,游标互不影响。PDF 字体程序缓存的条目、源字节预算及页面固定和关闭顺序保持。
普通 PDF 写出交互时,通过页树迭代器建立本次调用的页面引用索引,外链、内链、书签、批注和表单共用该索引,避免每条交互重复遍历页树并生成临时页面列表。没有交互时不建立索引;页面顺序、目标坐标和外观处理保持。
完整收集布局在放置普通表格行时,以本次 Flow 私有的两个 LRU 分别复用相等的单元格矩形和文字原点,各至多 2,048 项。仍先执行原构造校验,再按完整不可变值精确比较,不舍入坐标;格来源、跨度、文字和内容不进入几何身份。淘汰仅释放缓存引用,已返回的 IR 保留完整几何;逐页和单向源入口不建立这两个缓存。
普通 PDF 保存使用独立间接对象及传统交叉引用表,关闭 PDFBox 的对象流打包,避免保存时另外建立覆盖完整 PDF 对象图的压缩索引。正文、字体及图片流仍使用各自既有压缩;不更换后端、存储方式或返回布局。文件可能增大,精确序列化字节、输出及临时工作预算仍按实际保存结果计费;归档、加密、字体子集及借用流的处理顺序保持。
这些复用减少重复值和额外副本,不把完整模型或完整布局接口变成恒定内存接口。
边界
输入模型仍由调用方完整持有;最大单块、长表、组件准备结果及 keep 链可能随块规模增长。唯一 ID、锚点、导航与脚注索引受预算限制,但不是恒定大小。逐页入口减少完成页的中间对象保留,不能据此声称任意输入恒定内存。
普通 PdfBackend 的文档级 PDFBox 状态仍会累计;需要逐页释放格式对象时显式选择 PdfSpoolBackend。后者继续只支持普通 PDF,不扩展归档、加密、签名或无障碍认证范围。输出流借用,失败可留下部分字节;路径入口同目录原子替换,失败保留旧目标。