// RDEPUBReaderController+ContentDelegates.swift // EPUB 阅读器内容视图代理实现 // 处理 Web 渲染路径(RDEPUBWebContentViewDelegate)和 Native Text 渲染路径 // (RDEPUBTextContentViewDelegate)的位置更新、选区变化、链接跳转、错误日志等事件回调。 import UIKit /// RDEPUBReaderController 内容代理扩展 /// /// 本文件实现 EPUB 阅读器的内容视图代理,处理 Web 渲染路径和 Native Text 渲染路径的 /// 位置更新、选区变化、链接跳转、错误日志等事件回调。 // MARK: - Web 内容视图代理(EPUB 固定布局/Web 渲染路径) extension RDEPUBReaderController: RDEPUBWebContentViewDelegate { /// Web 内容视图位置更新回调,同步阅读上下文与持久化位置 func epubWebContentView(_ contentView: RDEPUBWebContentView, didUpdateLocation location: RDEPUBLocation, spineIndex: Int) { guard readerView.currentPage >= 0, activePages.indices.contains(readerView.currentPage) else { return } let currentPage = activePages[readerView.currentPage] guard readingSession?.pageContains(spineIndex: spineIndex, in: currentPage) == true else { return } persist(location: location) readingSession?.updateReadingContext( pageNumber: readerView.currentPage + 1, location: location, spineIndex: spineIndex, chapterIndex: currentPage.chapterIndex, bookIdentifier: currentBookIdentifier ) } /// Web 内容视图选区变化回调,转换为统一选区模型 func epubWebContentView(_ contentView: RDEPUBWebContentView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int) { if let selection { updateCurrentSelection(scopedSelection(selection, relativeToSpineIndex: spineIndex)) } else { updateCurrentSelection(nil) } } /// Web 内容视图选区菜单操作回调 func epubWebContentView(_ contentView: RDEPUBWebContentView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) { handleSelectionMenuAction(action, selection: currentSelection) } /// Web 内容视图内部链接点击回调,执行页内跳转 func epubWebContentView(_ contentView: RDEPUBWebContentView, didActivateInternalLink location: RDEPUBLocation, fromSpineIndex: Int) { guard let readingSession, let pageNumber = readingSession.queueNavigation( to: location, relativeToSpineIndex: fromSpineIndex, bookIdentifier: currentBookIdentifier ) else { return } readerView.transitionToPage(pageNum: max(pageNumber - 1, 0), animated: true) } /// Web 内容视图外部链接点击回调,使用系统浏览器打开 func epubWebContentView(_ contentView: RDEPUBWebContentView, didActivateExternalLink url: URL) { delegate?.epubReader(self, didActivateExternalLink: url) UIApplication.shared.open(url, options: [:], completionHandler: nil) } /// Web 内容视图 JavaScript 错误日志回调 func epubWebContentView(_ contentView: RDEPUBWebContentView, didLogJavaScriptError message: String) { print("EPUB JS Error: \(message)") } } // MARK: - 文本内容视图代理(Native Text 渲染路径) extension RDEPUBReaderController: RDEPUBTextContentViewDelegate { /// 文本内容视图选区变化回调,标准化后更新当前选区 func textContentView(_ contentView: RDEPUBTextContentView, didChangeSelection selection: RDEPUBSelection?) { guard let selection else { updateCurrentSelection(nil) return } updateCurrentSelection(selection) } /// 文本内容视图选区菜单操作回调 func textContentView( _ contentView: RDEPUBTextContentView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection? ) { handleSelectionMenuAction(action, selection: selection ?? currentSelection) contentView.clearSelection() } func textContentView( _ contentView: RDEPUBTextContentView, didRequestHighlightActions highlight: RDEPUBHighlight, sourceRect: CGRect ) { runtime.presentHighlightActions(for: highlight, sourceView: contentView, sourceRect: sourceRect) } /// 根据 EPUB 位置计算对应的页码 func pageNumber(for location: RDEPUBLocation) -> Int? { if let textBook, let publication { if let anchor = location.rangeAnchor?.start { if let page = textBook.indexTable.pageNumber(for: anchor, in: textBook) { return page + 1 } } let normalizedLocation = publication.resourceResolver.normalizedLocation( location, relativeToSpineIndex: nil, bookIdentifier: currentBookIdentifier ) ?? location return textBook.pageNumber( for: normalizedLocation, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier ) } return readingSession?.queueNavigation( to: location, relativeToSpineIndex: nil, bookIdentifier: currentBookIdentifier ) } /// 根据页码解析对应的文本位置 func resolvedTextLocation(forPageNumber pageNumber: Int) -> RDEPUBLocation? { guard let textBook, let publication, let page = textBook.page(at: pageNumber) else { return nil } let location = textBook.chapterData(forPageNumber: pageNumber)?.location(forPage: page, bookIdentifier: currentBookIdentifier) ?? textBook.location(forPageNumber: pageNumber, bookIdentifier: currentBookIdentifier) guard let location else { return nil } return publication.resourceResolver.normalizedLocation( location, relativeToSpineIndex: nil, bookIdentifier: currentBookIdentifier ) ?? location } /// 同步文本阅读状态到阅读会话(页码、位置、spine、章节等) func synchronizeTextReadingState(pageNumber: Int, location: RDEPUBLocation) { guard let textBook, let page = textBook.page(at: pageNumber) else { readingSession?.transition(to: .idle) return } readingSession?.updateReadingContext( pageNumber: pageNumber, location: location, spineIndex: page.spineIndex, chapterIndex: page.chapterIndex, bookIdentifier: currentBookIdentifier ) } /// 从文本书籍生成原生文本快照(页面列表 + 章节信息) func nativeTextSnapshot(from textBook: RDEPUBTextBook) -> RDEPUBNativeTextSnapshot { let chapters = textBook.chapterInfos let pages = textBook.pages.map { EPUBPage( spineIndex: $0.spineIndex, chapterIndex: $0.chapterIndex, pageIndexInChapter: $0.pageIndexInChapter, totalPagesInChapter: $0.totalPagesInChapter, chapterTitle: $0.chapterTitle, fixedSpread: nil ) } return (pages, chapters) } }