本次提交围绕 DTCoreText 文本页的分页一致性、交互索引和高亮命中进行了集中修复。 主要改动: 1. 文本页显示改为基于整章 attributed string 的上下文布局,只对当前页 range 进行渲染,避免页面子串重新换行导致的页末断行偏差。 2. 页面布局快照与交互控制器统一改为使用 chapter-absolute 索引,修正点击、选区、菜单锚点与高亮矩形在整章上下文下的定位。 3. 修复跨章节高亮串页问题,并调整文本页高亮命中逻辑:保留 CoreText 层绘制,点击时按高亮真实 rect 精确命中,避免重复绘制和整行误判。 4. 收紧 reader 级页面缓存策略,避免预加载同时持有多份整章显示副本带来的内存放大。 5. 新增分页边界校验器、垂直对齐器和 settings-flip 自动化调试入口,用于复现与诊断页范围/显示度量不一致问题。 6. 放宽 inline attachment 的 avoid-break 处理,并补充相关分页问题调查文档与索引。
85 lines
3.5 KiB
Swift
85 lines
3.5 KiB
Swift
import UIKit
|
|
|
|
/// Debug-only automation (`--demo-settings-flip`) that replays the user
|
|
/// gesture sequence suspected of producing stale page tables: open the
|
|
/// settings panel, change the line height, close the panel, flip pages —
|
|
/// with several timing variants aimed at the preview-repagination and
|
|
/// in-flight chapter build races. Combine with
|
|
/// `--demo-pagination-validate` to detect any resulting metric mismatch.
|
|
enum RDEPUBSettingsFlipAutomation {
|
|
|
|
static let isEnabled = ProcessInfo.processInfo.arguments.contains("--demo-settings-flip")
|
|
|
|
private(set) static var hasStarted = false
|
|
|
|
static func startIfNeeded(controller: RDEPUBReaderController) {
|
|
guard isEnabled, !hasStarted else { return }
|
|
hasStarted = true
|
|
print("[SETTINGS-FLIP] scheduled")
|
|
|
|
// Pass 1 starts while progressive pagination of the freshly opened
|
|
// book is still running, so prefetch builds are in flight.
|
|
run(after: 2.0) { [weak controller] in
|
|
guard let controller else { return }
|
|
pass(controller: controller, index: 1, lineHeight: 1.8, changeToCloseDelay: 1.2) {
|
|
pass(controller: controller, index: 2, lineHeight: 1.6, changeToCloseDelay: 0.05) {
|
|
pass(controller: controller, index: 3, lineHeight: 1.8, changeToCloseDelay: 0.3) {
|
|
print("[SETTINGS-FLIP] finished all passes")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One panel round-trip: present → change line height → close after
|
|
/// `changeToCloseDelay` → flip pages forward and back.
|
|
private static func pass(
|
|
controller: RDEPUBReaderController,
|
|
index: Int,
|
|
lineHeight: CGFloat,
|
|
changeToCloseDelay: TimeInterval,
|
|
completion: @escaping () -> Void
|
|
) {
|
|
print("[SETTINGS-FLIP] pass \(index) present panel")
|
|
controller.presentSettings()
|
|
|
|
run(after: 0.7) { [weak controller] in
|
|
guard let controller else { return }
|
|
print("[SETTINGS-FLIP] pass \(index) set lineHeightMultiple=\(lineHeight)")
|
|
controller.updateConfiguration { $0.lineHeightMultiple = lineHeight }
|
|
|
|
run(after: changeToCloseDelay) { [weak controller] in
|
|
guard let controller else { return }
|
|
print("[SETTINGS-FLIP] pass \(index) close panel")
|
|
controller.dismiss(animated: true) { [weak controller] in
|
|
controller?.runtime.settingsPanelDidDisappear()
|
|
run(after: 1.0) { [weak controller] in
|
|
guard let controller else { return }
|
|
flipPages(controller: controller, index: index, completion: completion)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func flipPages(
|
|
controller: RDEPUBReaderController,
|
|
index: Int,
|
|
completion: @escaping () -> Void
|
|
) {
|
|
let startPage = controller.readerView.currentPage + 1
|
|
let offsets = [1, 2, 3, 2, 1, 0]
|
|
print("[SETTINGS-FLIP] pass \(index) flip pages from \(startPage)")
|
|
for (step, offset) in offsets.enumerated() {
|
|
run(after: 0.6 * Double(step + 1)) { [weak controller] in
|
|
_ = controller?.go(toPageNumber: startPage + offset, animated: false)
|
|
}
|
|
}
|
|
run(after: 0.6 * Double(offsets.count + 1) + 0.5, block: completion)
|
|
}
|
|
|
|
private static func run(after delay: TimeInterval, block: @escaping () -> Void) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: block)
|
|
}
|
|
}
|