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) } }