修复 EPUB 文本分页显示错位并补充分页调试能力

本次提交围绕 DTCoreText 文本页的分页一致性、交互索引和高亮命中进行了集中修复。

主要改动:

1. 文本页显示改为基于整章 attributed string 的上下文布局,只对当前页 range 进行渲染,避免页面子串重新换行导致的页末断行偏差。

2. 页面布局快照与交互控制器统一改为使用 chapter-absolute 索引,修正点击、选区、菜单锚点与高亮矩形在整章上下文下的定位。

3. 修复跨章节高亮串页问题,并调整文本页高亮命中逻辑:保留 CoreText 层绘制,点击时按高亮真实 rect 精确命中,避免重复绘制和整行误判。

4. 收紧 reader 级页面缓存策略,避免预加载同时持有多份整章显示副本带来的内存放大。

5. 新增分页边界校验器、垂直对齐器和 settings-flip 自动化调试入口,用于复现与诊断页范围/显示度量不一致问题。

6. 放宽 inline attachment 的 avoid-break 处理,并补充相关分页问题调查文档与索引。
This commit is contained in:
shenlei
2026-07-08 14:27:36 +09:00
parent d0efe3f2cc
commit 5a41066b66
12 changed files with 2167 additions and 1606 deletions
@@ -130,13 +130,14 @@ struct RDEPUBPageLayoutSnapshot {
return nil
}
// The layout frame is produced in chapter context, so DTCoreText
// string ranges are already chapter-absolute.
var lines: [RDEPUBPageLine] = []
var runs: [RDEPUBPageRun] = []
var attachments: [RDEPUBPageAttachment] = []
let pageOffset = page.pageStartOffset
for dtLine in dtLines {
let lineRange = offset(dtLine.stringRange(), by: pageOffset)
let lineRange = dtLine.stringRange()
let line = RDEPUBPageLine(
stringRange: lineRange,
frame: dtLine.frame,
@@ -149,7 +150,7 @@ struct RDEPUBPageLayoutSnapshot {
if let glyphRuns = dtLine.glyphRuns as? [DTCoreTextGlyphRun] {
for run in glyphRuns {
let runRange = offset(run.stringRange(), by: pageOffset)
let runRange = run.stringRange()
let isAttachment = run.attachment != nil
runs.append(
RDEPUBPageRun(
@@ -177,7 +178,7 @@ struct RDEPUBPageLayoutSnapshot {
}
}
let visibleRange = offset(layoutFrame.visibleStringRange(), by: pageOffset)
let visibleRange = layoutFrame.visibleStringRange()
return RDEPUBPageLayoutSnapshot(
page: page,
@@ -219,11 +220,5 @@ struct RDEPUBPageLayoutSnapshot {
return RDEPUBAttachmentNormalizer.attachmentKind(for: attributes)
}
private static func offset(_ range: NSRange, by offset: Int) -> NSRange {
guard range.location != NSNotFound else {
return range
}
return NSRange(location: range.location + offset, length: range.length)
}
#endif
}