import Foundation /// EPUB 阅读器分页协调器:负责出版物的分页计算和页面数据更新。 /// /// 职责: /// - 根据出版物类型(文本重排/Fixed Layout/Web 内容)选择分页策略 /// - 后台构建文本图书模型并应用分页快照 /// - 重新分页时保持当前阅读位置 /// - 刷新可见内容并保持位置 /// - 重建外部纯文本图书 final class RDEPUBReaderPaginationCoordinator { private unowned let context: RDEPUBReaderContext init(context: RDEPUBReaderContext) { self.context = context } /// 对出版物执行分页:文本重排走 TextBookBuilder,Fixed 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) } } }