ADR-0036:单向长表的有界纯富文本与字体角色
- 日期:2026-10-16
- 状态:已采纳;实施及本地验收通过,范围与证据单独记录
背景
ADR-0031的单向长表已能逐行读取、逐页测量与同步输出,但只接受普通文字。完整报告可用 TableCell.content/rich 表达状态颜色、强调和真实字体角色,同样的格不能直接进入长表路径。为保留局部样式退回全量模型,会同时增加调用分支和驻留规模。
现有 ParagraphLayouter、StyledSpan、ParagraphLayout 及单页绘制已保存字体、颜色、偏移、装饰和 UTF-16 来源。纯文字样式不依赖未来行、最终页数或导航,所以可以在既有单向契约内扩展。
决策
单向路径复用 TableCell 的有序 InlineText,只接入纯文字富文本及显式字体角色,不创建新格模型。每个运行映射为项目自有 StyledSpan,保留字号、RGB、基线偏移、下划线和删除线;空角色使用默认组,非空角色使用调用方注册的 FontGroup。各角色组保留自己的缺字策略,禁止系统字体扫描、合成字体变体或暗中扩大固定行框。
StreamingTableLayouter.layout 增加 Map<String, FontGroup> 重载,旧签名委托空 Map。入口验证并快照角色映射,整个调用期间使用同一快照。未知角色以 TEXT_LAYOUT_FAILED 失败,定位表 ID、物理页、源行列及运行起始 UTF-16,不回显正文或完整资源路径。
DocumentEngine 的单向导出传入构造时已有的不可变角色映射,不再因为配置了角色而拒绝。规格、源工厂、请求配置快照、页交付、源关闭、finish/close 和原子替换仍沿用原契约;页模板、导航与注释继续在打开源前拒绝。
StreamingTableLimits 增加正的 maxCellInlines/maxRowInlines。每个源格和源行分别累计运行数,包含空运行;普通无运行文字按一个有效运行计费。表头与正文同样检查,重复表头不重新计入源运行额度。旧四参数构造器保留并填入默认运行预算,Builder 和 toBuilder 保存新值。record 分量变化的反射/解构及二进制兼容边界须单独说明。
每行先完成所有格的结构、运行数、角色、链接及源码点验证,再构造运行列表或塑形。源码点只扫描格原文一次;不能因运行和连接原文并存重复计费。运行遍历包括空运行均有同步执行检查点,避免零码点的巨量运行绕过预算与取消。
每格只换行一次,每行只测量一次;页不足时移动已测行。表头复用测量结果;输出行、字体信息/替换诊断仍按每次实际呈现计费。布局继续只保留有限表头、当前页和一个待放置行,不保存全量源、IR、Display List 或诊断列表。
拒绝与范围
InlineText.hyperlink 非空必须明确拒绝,包含空文字链接。当前同步单页后端不接收 DocumentExtras,不能在放开运行后只绘制标签并丢弃链接。图片、字段、块格、合并、自动宽、拆行、未来引用、总页数、目录、脚注与分栏继续拒绝或保持未接入状态。
固定行框必须容纳全部运行字号、偏移和真实字体度量;行超出新页可用高时明确失败,不先交付表头空页。复杂文字、字形簇、样式边界、严格中文禁则与 UTF-16 规则直接复用共享文字契约,不建立流式专用塑形或换行算法。
普通文字仍走旧入口,以保留旧字体度量、颜色、几何和摘要。运行数预算限制富文本准备规模,不限制调用方在返回行之前自行分配的内存,也不代表 PDF 恒内存;PDFBox 继续保留累计页树/COS 对象。
替代方案
没有把 TableRowSource 展开为完整 Table,否则违背单次消费和逐页释放。没有仅删除 inlines 的拒绝条件,否则链接会静默丢失、空运行可绕过码点额度。没有引入新富文本后端或竞争库实现,避免共享文字与绘制行为分叉。
GAP-04 的完整语义主题另行设计。给 StyleScope 添加名称不能表达语义块、行内显式样式和字体角色的全局优先级,不能据此关闭主题缺口。
验证要求
行为测试须覆盖普通入口兼容、运行属性与真实字体角色、全行预检、格/行空运行额度、角色映射快照及未知角色来源、复杂文字、固定行框、超高整行、重复表头身份与逐页诊断、最多一行预读、回调失败、取消/超时和作用域恢复。引擎测试保留关闭顺序、suppressed、失败旧目标与临时清理。
小样须独立检查最终字体/颜色/装饰、格边界、原文顺序及全部渲染页,同时验证隔离调用方。新容量必须分别采样布局丢弃页与真实 PDF,记录每格运行数量、角色字体摘要及执行范围;不复用普通 ASCII 容量结果冒充富文本容量,不自动刷新视觉基线。
本决策不新增模块、运行依赖或外部资源。