源码注释: - 为 ~60 个 Swift 文件补充缺失的 doc comment(file header、类型、属性、方法) - 修正 4 处错误注释:翻页模式数量、搜索行为描述、手势识别器描述、悬空文档块 文档维护: - 删除重复文档:WXRead/读书EPUB阅读器实现架构.md(与微信读书版完全一致) - 合并重叠文档:阅读器规划.md → 阅读器功能开发计划.md(单一真值) - 修正过时内容:所有文档中"四种翻页模式"→"三种",移除 horizontalCoverScroll - 更新架构图:补齐 EPUBUI/ReaderController、Paging/、Typesetter/ 等子目录 - 更新 index.md 索引:新增开发计划和架构对比文档引用
321 lines
10 KiB
Swift
321 lines
10 KiB
Swift
import UIKit
|
|
|
|
/// EPUB 阅读器运行时总协调器。
|
|
/// 统一持有并分发给加载、分页、定位、搜索、工具栏、批注、视口监测等子协调器,
|
|
/// 作为阅读器控制器的门面(Facade),简化外部调用。
|
|
final class RDEPUBReaderRuntime {
|
|
private unowned let context: RDEPUBReaderContext
|
|
|
|
lazy var loadCoordinator = RDEPUBReaderLoadCoordinator(context: context)
|
|
lazy var paginationCoordinator = RDEPUBReaderPaginationCoordinator(context: context)
|
|
lazy var locationCoordinator = RDEPUBReaderLocationCoordinator(context: context)
|
|
lazy var searchCoordinator = RDEPUBReaderSearchCoordinator(context: context)
|
|
lazy var chromeCoordinator = RDEPUBReaderChromeCoordinator(context: context)
|
|
lazy var annotationCoordinator = RDEPUBReaderAnnotationCoordinator(context: context)
|
|
lazy var viewportMonitor = RDEPUBReaderViewportMonitor(context: context)
|
|
|
|
init(context: RDEPUBReaderContext) {
|
|
self.context = context
|
|
}
|
|
|
|
/// 创建顶部工具栏视图
|
|
func makeTopToolView() -> RDEPUBReaderTopToolView {
|
|
chromeCoordinator.makeTopToolView()
|
|
}
|
|
|
|
/// 创建底部工具栏视图
|
|
func makeBottomToolView() -> RDEPUBReaderBottomToolView {
|
|
chromeCoordinator.makeBottomToolView()
|
|
}
|
|
|
|
/// 若尚未加载则启动首次加载流程
|
|
func startInitialLoadIfNeeded() {
|
|
loadCoordinator.startInitialLoadIfNeeded()
|
|
}
|
|
|
|
/// 重新加载当前书籍,清空解析器、分页、批注等状态后从头初始化
|
|
func reloadBook() {
|
|
guard let readerView = context.readerView else { return }
|
|
context.didStartInitialLoad = false
|
|
context.parser = nil
|
|
context.publication = nil
|
|
context.clearActiveSnapshot()
|
|
context.readingSession = nil
|
|
context.textBook = nil
|
|
context.activeBookmarks = []
|
|
context.activeHighlights = []
|
|
context.searchState = nil
|
|
viewportMonitor.resetForReload()
|
|
annotationCoordinator.updateCurrentSelection(nil)
|
|
readerView.reloadData()
|
|
startInitialLoadIfNeeded()
|
|
}
|
|
|
|
/// 跳转到指定阅读位置
|
|
/// - Parameters:
|
|
/// - location: 目标位置
|
|
/// - animated: 是否动画过渡
|
|
/// - Returns: 跳转是否成功
|
|
func go(to location: RDEPUBLocation, animated: Bool = false) -> Bool {
|
|
locationCoordinator.restoreReadingLocation(location, animated: animated)
|
|
}
|
|
|
|
/// 跳转到指定页码
|
|
/// - Parameters:
|
|
/// - pageNumber: 目标页码(从 1 开始)
|
|
/// - animated: 是否动画过渡
|
|
/// - Returns: 跳转是否成功
|
|
@discardableResult
|
|
func go(toPageNumber pageNumber: Int, animated: Bool = false) -> Bool {
|
|
guard let controller = context.controller,
|
|
let readerView = context.readerView,
|
|
pageNumber > 0 else {
|
|
return false
|
|
}
|
|
|
|
if context.textBook != nil {
|
|
guard let location = controller.resolvedTextLocation(forPageNumber: pageNumber) else {
|
|
return false
|
|
}
|
|
return locationCoordinator.restoreReadingLocation(location, animated: animated)
|
|
}
|
|
|
|
guard context.activePages.indices.contains(pageNumber - 1) else {
|
|
return false
|
|
}
|
|
readerView.transitionToPage(pageNum: pageNumber - 1, animated: animated)
|
|
if let location = locationCoordinator.currentVisibleLocation() {
|
|
context.persist(location: location)
|
|
}
|
|
return true
|
|
}
|
|
|
|
/// 清除当前选区
|
|
func clearSelection() {
|
|
annotationCoordinator.updateCurrentSelection(nil)
|
|
}
|
|
|
|
func bookmark(withID id: String) -> RDEPUBBookmark? {
|
|
annotationCoordinator.bookmark(withID: id)
|
|
}
|
|
|
|
func highlight(withID id: String) -> RDEPUBHighlight? {
|
|
annotationCoordinator.highlight(withID: id)
|
|
}
|
|
|
|
@discardableResult
|
|
func addHighlight(
|
|
from selection: RDEPUBSelection? = nil,
|
|
color: String = "#F8E16C",
|
|
note: String? = nil
|
|
) -> RDEPUBHighlight? {
|
|
annotationCoordinator.addHighlight(from: selection, color: color, note: note)
|
|
}
|
|
|
|
@discardableResult
|
|
func addAnnotation(
|
|
from selection: RDEPUBSelection? = nil,
|
|
style: RDEPUBHighlightStyle,
|
|
color: String = "#F8E16C",
|
|
note: String? = nil
|
|
) -> RDEPUBHighlight? {
|
|
annotationCoordinator.addAnnotation(from: selection, style: style, color: color, note: note)
|
|
}
|
|
|
|
@discardableResult
|
|
func upsertHighlight(_ highlight: RDEPUBHighlight) -> RDEPUBHighlight? {
|
|
annotationCoordinator.upsertHighlight(highlight)
|
|
}
|
|
|
|
@discardableResult
|
|
func removeHighlight(id: String) -> RDEPUBHighlight? {
|
|
annotationCoordinator.removeHighlight(id: id)
|
|
}
|
|
|
|
@discardableResult
|
|
func updateHighlightNote(id: String, note: String?) -> RDEPUBHighlight? {
|
|
annotationCoordinator.updateHighlightNote(id: id, note: note)
|
|
}
|
|
|
|
@discardableResult
|
|
func go(toHighlightID id: String, animated: Bool = true) -> Bool {
|
|
annotationCoordinator.go(toHighlightID: id, animated: animated)
|
|
}
|
|
|
|
func removeAllHighlights() {
|
|
annotationCoordinator.removeAllHighlights()
|
|
}
|
|
|
|
@discardableResult
|
|
func addBookmark(note: String? = nil) -> RDEPUBBookmark? {
|
|
annotationCoordinator.addBookmark(note: note)
|
|
}
|
|
|
|
@discardableResult
|
|
func toggleBookmark(note: String? = nil) -> RDEPUBBookmark? {
|
|
annotationCoordinator.toggleBookmark(note: note)
|
|
}
|
|
|
|
@discardableResult
|
|
func removeBookmark(id: String) -> RDEPUBBookmark? {
|
|
annotationCoordinator.removeBookmark(id: id)
|
|
}
|
|
|
|
@discardableResult
|
|
func go(toBookmarkID id: String, animated: Bool = true) -> Bool {
|
|
annotationCoordinator.go(toBookmarkID: id, animated: animated)
|
|
}
|
|
|
|
func updateBookmarkChrome() {
|
|
annotationCoordinator.updateBookmarkChrome()
|
|
}
|
|
|
|
func presentBookmarksManager() {
|
|
annotationCoordinator.presentBookmarksManager()
|
|
}
|
|
|
|
func presentHighlightsManager() {
|
|
annotationCoordinator.presentHighlightsManager()
|
|
}
|
|
|
|
func presentAnnotationCreation() {
|
|
annotationCoordinator.presentAnnotationCreation()
|
|
}
|
|
|
|
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) {
|
|
annotationCoordinator.handleSelectionMenuAction(action, selection: selection)
|
|
}
|
|
|
|
/// 按关键词搜索全文
|
|
func search(keyword: String) {
|
|
searchCoordinator.search(keyword: keyword)
|
|
}
|
|
|
|
/// 跳转到下一个搜索匹配项
|
|
@discardableResult
|
|
func searchNext() -> Bool {
|
|
searchCoordinator.searchNext()
|
|
}
|
|
|
|
/// 跳转到上一个搜索匹配项
|
|
@discardableResult
|
|
func searchPrevious() -> Bool {
|
|
searchCoordinator.searchPrevious()
|
|
}
|
|
|
|
/// 清除搜索状态
|
|
func clearSearch() {
|
|
searchCoordinator.clearSearch()
|
|
}
|
|
|
|
func searchPresentation(for page: EPUBPage) -> RDEPUBSearchPresentation? {
|
|
searchCoordinator.searchPresentation(for: page)
|
|
}
|
|
|
|
/// 更新阅读器工具栏显示状态
|
|
func updateReaderChrome() {
|
|
chromeCoordinator.updateReaderChrome()
|
|
}
|
|
|
|
/// 弹出阅读设置面板
|
|
func presentSettings() {
|
|
chromeCoordinator.presentSettings()
|
|
}
|
|
|
|
/// 弹出目录面板
|
|
func presentTableOfContents() {
|
|
chromeCoordinator.presentTableOfContents()
|
|
}
|
|
|
|
/// 处理返回操作
|
|
func handleBackAction() {
|
|
chromeCoordinator.handleBackAction()
|
|
}
|
|
|
|
/// 启动 Publication 加载流程
|
|
func loadPublication() {
|
|
loadCoordinator.loadPublication()
|
|
}
|
|
|
|
/// 将已解析的 Publication 应用到控制器
|
|
func applyParsedPublication(
|
|
parser: RDEPUBParser,
|
|
publication: RDEPUBPublication,
|
|
bookIdentifier: String,
|
|
restoreLocation: RDEPUBLocation?,
|
|
bookmarks: [RDEPUBBookmark],
|
|
highlights: [RDEPUBHighlight]
|
|
) {
|
|
loadCoordinator.applyParsedPublication(
|
|
parser: parser,
|
|
publication: publication,
|
|
bookIdentifier: bookIdentifier,
|
|
restoreLocation: restoreLocation,
|
|
bookmarks: bookmarks,
|
|
highlights: highlights
|
|
)
|
|
}
|
|
|
|
/// 对 Publication 执行分页计算
|
|
func paginatePublication(restoreLocation: RDEPUBLocation?) {
|
|
paginationCoordinator.paginatePublication(restoreLocation: restoreLocation)
|
|
}
|
|
|
|
/// 应用外部 TextBook 并恢复阅读位置
|
|
func applyTextBook(_ textBook: RDEPUBTextBook, restoreLocation: RDEPUBLocation?) {
|
|
paginationCoordinator.applyTextBook(textBook, restoreLocation: restoreLocation)
|
|
}
|
|
|
|
/// 应用分页快照并恢复阅读位置
|
|
func applyPaginationSnapshot(
|
|
_ snapshot: (pages: [EPUBPage], chapters: [EPUBChapterInfo]),
|
|
restoreLocation: RDEPUBLocation?
|
|
) {
|
|
paginationCoordinator.applyPaginationSnapshot(snapshot, restoreLocation: restoreLocation)
|
|
}
|
|
|
|
/// 完成分页流程并恢复阅读位置
|
|
func finishPagination(restoreLocation: RDEPUBLocation?) {
|
|
paginationCoordinator.finishPagination(restoreLocation: restoreLocation)
|
|
}
|
|
|
|
/// 重新分页并保持当前阅读位置不变
|
|
func repaginatePreservingCurrentLocation() {
|
|
paginationCoordinator.repaginatePreservingCurrentLocation()
|
|
}
|
|
|
|
/// 刷新当前可见内容,保持阅读位置不变
|
|
func refreshVisibleContentPreservingLocation() {
|
|
paginationCoordinator.refreshVisibleContentPreservingLocation()
|
|
}
|
|
|
|
/// 重建外部 TextBook 数据
|
|
func rebuildExternalTextBook() {
|
|
paginationCoordinator.rebuildExternalTextBook()
|
|
}
|
|
|
|
/// 恢复到指定阅读位置
|
|
@discardableResult
|
|
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool = false) -> Bool {
|
|
locationCoordinator.restoreReadingLocation(location, animated: animated)
|
|
}
|
|
|
|
/// 获取当前可见页面的阅读位置
|
|
func currentVisibleLocation() -> RDEPUBLocation? {
|
|
locationCoordinator.currentVisibleLocation()
|
|
}
|
|
|
|
/// 获取当前视口签名快照
|
|
func currentViewportSignature() -> RDEPUBViewportSignature? {
|
|
viewportMonitor.currentViewportSignature()
|
|
}
|
|
|
|
/// 视口变化时检查是否需要重新分页
|
|
func handleViewportChangeIfNeeded(
|
|
reason: RDEPUBViewportChangeReason,
|
|
viewportSignature: RDEPUBViewportSignature? = nil
|
|
) {
|
|
viewportMonitor.handleViewportChangeIfNeeded(reason: reason, viewportSignature: viewportSignature)
|
|
}
|
|
}
|