ADR-0112:普通内容块的按需输入与显式重放
- 状态:已采纳
- 日期:2026-11-06
背景
普通文档的 exportPaged(Document) 会释放完成页的布局及绘制结果,但调用方仍需先构造完整输入。大量内容因此在开始排版前就占用内存;需要最终目录及未来引用的通用路径还会执行规划、预检和输出。
已有 TableRowSource 面向长表行源。普通段落、标题、表格、分栏和组件的混合输入需要独立的内容块来源,不能通过把所有内容包进一个巨大表格改变其排版语义。
决定
模型层增加 BlockSource,按调用线程同步返回一个不可变 Block,空结果永久结束。工厂在导出开始时打开来源,导出拥有并关闭该次来源;字体、后端及目标输出流保持借用。低层布局借用来源,通过共享 DocumentLayouter.Flow 分页。
输入窗口仅保留当前页所需来源、跨页当前块和必要的 keep 链预读。完成块的测量结果与不再使用的来源引用一起释放;来源 ID 和累计预算保留到结束。页回调得到当前页原始来源的短暂快照,以便解析外链,无须重新建立全量来源索引。
组件入口分为两个明确契约:
exportSemantic(PageSettings, BlockSource.Factory):单次读取,要求准确的正数expectedPages,只执行一遍正文布局。完成时核对总数,来源成功关闭后才结束写出器。exportReplay(PageSettings, BlockSource.Factory):显式打开同一稳定快照两次。首遍确定分页及来源/布局摘要,第二遍解析最终页字段并输出;不隐式重新查询业务数据,不收集完整正文。
重放比较原始 UTF-16 来源、无绘制声明及最终布局,不能只比较文字哈希码。自定义组件实例无须可序列化,其稳定块声明和测量后的语义、几何及绘制结果共同参与检查。每页的表源摘要缓存只在该页存活。
标题、列表及题注采用保留编号状态的逐块语义转换。正文 PAGE/PAGES、页眉页脚、普通图片和外链使用最终分页信息。此入口不处理跨节页码、目录、未来引用、脚注重排、评论及可编辑表单;这些声明明确拒绝,继续使用原有完整/逐页入口。
PDF 磁盘后端仍逐页最终化字体并验证精确单页字节预算。磁盘索引的数值批量读写及 xref 编码只减少 I/O 和对象分配,不改变对象编号、偏移、预算或关闭契约。
结果及边界
调用方可在排版过程中产生并释放普通内容块,不必预先创建整份模型。单遍和显式两遍的成本与通用完整语义入口分别衡量,不改变原入口的支持范围及返回值。
最大块、长表、组件准备及 keep 链仍需内存;全局唯一性索引和重放页摘要也随受限节点/页数增长。单个巨大表格的输入按行释放仍使用 TableRowSource。不能把按需输入解释为任意内容恒定内存。
路径输出保留同目录原子替换,来源变化、关闭、预算、取消或写出失败均保留旧目标;借用流失败可能已有部分字节。输出与容量验收使用独立结构检查和渲染,具体结果另行登记。