Files
ReadViewSDK/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderPaginationCoordinator.swift
T
shenandshen 948004eed1 docs: 补充注释、修正过时文档、清理重复内容
源码注释:
- 为 ~60 个 Swift 文件补充缺失的 doc comment(file header、类型、属性、方法)
- 修正 4 处错误注释:翻页模式数量、搜索行为描述、手势识别器描述、悬空文档块

文档维护:
- 删除重复文档:WXRead/读书EPUB阅读器实现架构.md(与微信读书版完全一致)
- 合并重叠文档:阅读器规划.md → 阅读器功能开发计划.md(单一真值)
- 修正过时内容:所有文档中"四种翻页模式"→"三种",移除 horizontalCoverScroll
- 更新架构图:补齐 EPUBUI/ReaderController、Paging/、Typesetter/ 等子目录
- 更新 index.md 索引:新增开发计划和架构对比文档引用
2026-06-01 09:33:23 +08:00

172 lines
7.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// EPUB 阅读器分页协调器:负责出版物的分页计算和页面数据更新。
///
/// 职责:
/// - 根据出版物类型(文本重排/Fixed Layout/Web 内容)选择分页策略
/// - 后台构建文本图书模型并应用分页快照
/// - 重新分页时保持当前阅读位置
/// - 刷新可见内容并保持位置
/// - 重建外部纯文本图书
final class RDEPUBReaderPaginationCoordinator {
private unowned let context: RDEPUBReaderContext
init(context: RDEPUBReaderContext) {
self.context = context
}
/// 对出版物执行分页:文本重排走 TextBookBuilderFixed Layout 直接生成快照,Web 内容走 Paginator。
func paginatePublication(restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller,
let parser = context.parser,
let publication = context.publication,
let readingSession = context.readingSession else {
return
}
controller.isRepaginating = true
controller.errorLabel.isHidden = true
controller.showLoading()
let token = UUID()
context.paginationToken = token
if publication.readingProfile == .textReflowable {
let pageSize = controller.currentTextPageSize()
context.lastTextPaginationPageSize = pageSize
let layoutConfig = controller.currentTextLayoutConfig(pageSize: pageSize)
let builder = context.makeTextBookBuilder(layoutConfig: layoutConfig)
let renderStyle = controller.currentTextRenderStyle()
DispatchQueue.global(qos: .userInitiated).async { [weak controller] in
guard let controller else { return }
do {
let textBook = try builder.build(
parser: parser,
publication: publication,
pageSize: pageSize,
style: renderStyle
)
DispatchQueue.main.async {
guard self.context.paginationToken == token else { return }
self.context.runtime?.applyTextBook(textBook, restoreLocation: restoreLocation)
}
} catch {
DispatchQueue.main.async {
guard self.context.paginationToken == token else { return }
self.context.handle(error: error)
}
}
}
return
}
if publication.layout == .fixed {
let snapshot = readingSession.makePaginationSnapshot(
pageCounts: Array(repeating: 1, count: publication.spine.count),
preferences: controller.currentPreferences(),
layoutContext: controller.currentLayoutContext()
)
context.runtime?.applyPaginationSnapshot(snapshot, restoreLocation: restoreLocation)
return
}
let paginator = context.makePaginator()
context.paginator = paginator
paginator.calculate(
parser: parser,
hostingView: controller.ensurePaginationHostView(),
presentation: controller.currentPreferences().presentationStyle(viewportSize: controller.currentLayoutContext().viewportSize)
) { [weak controller] pageCounts in
guard let controller, self.context.paginationToken == token else { return }
let snapshot = readingSession.makePaginationSnapshot(
pageCounts: pageCounts,
preferences: controller.currentPreferences(),
layoutContext: controller.currentLayoutContext()
)
self.context.paginator = nil
self.context.runtime?.applyPaginationSnapshot(snapshot, restoreLocation: restoreLocation)
}
}
/// 应用文本图书模型:生成分页快照并完成分页流程。
func applyTextBook(_ textBook: RDEPUBTextBook, restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller else { return }
context.textBook = textBook
let snapshot = controller.nativeTextSnapshot(from: textBook)
context.replaceActiveSnapshot(snapshot)
guard !textBook.pages.isEmpty else {
context.handle(error: RDEPUBParserError.emptySpine)
return
}
finishPagination(restoreLocation: restoreLocation)
}
/// 应用分页快照(Fixed Layout 或 Web 内容),并完成分页流程。
func applyPaginationSnapshot(
_ snapshot: (pages: [EPUBPage], chapters: [EPUBChapterInfo]),
restoreLocation: RDEPUBLocation?
) {
guard let controller = context.controller else { return }
context.textBook = nil
context.replaceActiveSnapshot(snapshot)
guard !snapshot.pages.isEmpty else {
context.handle(error: RDEPUBParserError.emptySpine)
return
}
finishPagination(restoreLocation: restoreLocation)
}
/// 分页完成后的收尾:刷新视图、恢复阅读位置、处理待定视口变更。
func finishPagination(restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller,
let readerView = context.readerView else { return }
controller.isRepaginating = false
controller.hideLoading()
readerView.reloadData()
if let targetLocation = restoreLocation {
controller.restoreReadingLocation(targetLocation)
} else {
readerView.transitionToPage(pageNum: 0)
context.readingSession?.transition(to: .idle)
}
context.runtime?.viewportMonitor.processPendingChangeAfterPagination()
}
/// 重新分页并保持当前阅读位置(优先使用待恢复位置,其次当前位置,最后持久化位置)。
func repaginatePreservingCurrentLocation() {
guard context.publication != nil else { return }
let restoreLocation = context.runtime?.viewportMonitor.consumePendingPresentationRestoreLocation()
?? context.currentVisibleLocation()
?? context.persistenceLocation()
paginatePublication(restoreLocation: restoreLocation)
}
/// 刷新可见内容并保持当前阅读位置不变。
func refreshVisibleContentPreservingLocation() {
guard let readerView = context.readerView else { return }
let restoreLocation = context.currentVisibleLocation() ?? context.persistenceLocation()
readerView.reloadData()
if let restoreLocation {
_ = context.restoreReadingLocation(restoreLocation)
}
}
/// 重建外部纯文本图书(布局变更后重新排版)。
func rebuildExternalTextBook() {
guard let controller = context.controller,
let textFileURL = controller.textFileURL else { return }
let restoreLocation = context.currentVisibleLocation() ?? context.persistenceLocation()
let pageSize = controller.currentTextPageSize()
let style = controller.currentTextRenderStyle()
let builder = context.makePlainTextBookBuilder(layoutConfig: controller.currentTextLayoutConfig(pageSize: pageSize))
if let newBook = try? builder.build(textFileURL: textFileURL, pageSize: pageSize, style: style) {
context.runtime?.applyTextBook(newBook, restoreLocation: restoreLocation)
}
}
}