ADR-0026:原文簇、段落方向与塑形上下文
日期:2026-10-16。状态:已采纳。
问题
复杂文字原有两条路径:公共 OpenTypeParagraphLayouter 与组件 OpenTypeText。测量使用 TextMeasurer,但后续轮廓生成又按局部范围塑形,存在可复现差异:
a加组合标记缺字并替换为?后,在替换串上重新分簇会允许从原文组合簇中间断开。- Java 21 的 ROOT Character BreakIterator 可在天城文 virama 连接链中间给出边界;实际物理字体可能把两侧合成一个字形。
- 续行单独创建 Bidi 会丢失段落基方向,改变仅含括号/数字/中性符号的续行顺序。
- 组件两端对齐时逐原文簇重新塑形会改变阿拉伯 lam-alef 连字;公共路径缩成当前行 char 数组也会丢失 TextMeasurer 使用的上下文。
逻辑文字被 ActualText 保存、两个 PDF 渲染器一致,都不能单独证明这些轮廓与测量一致。
决策
- 共享
TextClusters保存原文 UTF-16 边界。采用 JDK ROOT 边界,并保守保持天城文 consonant/virama/ZWJ 链;不宣称完整 Unicode 分簇。 - 缺字替换同时保存原文簇到替换串边界。天然、紧急及中文禁则断行都须属于允许边界交集。原文簇或实际字形簇放不进区域时明确失败。
- 同一物理字体范围按实际 GlyphVector 进一步约束字形簇边界。不得以恰好没有拆开代理对作为完整簇保障。
- 续行从整个显式换行段的 Bidi 派生,使用完整段落的字符上下文。显式 CR/LF/CRLF 仍建立新段方向。
- 可共同塑形的同字体范围先共同塑形,再分配样式与装饰。实际字形簇跨越不兼容样式时拒绝;不拆连字或合成假字形。
- 字距、对齐与分页继续由布局决定;PDF 后端不再次塑形或换行。来源索引、图片/字段、缺字诊断、固定行框和取消传播保持既有契约。
验证与边界
行为测试对照显式物理字体的 JDK TextLayout 几何,检查占位替换、连接簇、上下文、续行方向、样式及来源。输出语料分别经过语义段落与组件,使用固定字体;独立解析、全部页面双渲染和视觉检查之外,单独记录提取、查询与选区结果。
PDF 当前轮廓文字使用 ActualText 与不可见锚点。PDFium 的查询/复制文字可在样本中成功,pypdf 提取仍可能只得到锚点;选区不是精确逐字字形几何。该限制不能通过放宽逻辑来源或渲染断言消除。完整 UAX #14/#29、emoji、其他塑形器/操作系统以及通用阅读器兼容仍须独立验收。