- PaginationCoordinator: 新增 textReflowable 快速进入路径,支持增量章节构建 与 staged 合并策略,避免整书一次性构建的主线程阻塞 - TextBookBuilder: 重构为支持单章构建与增量合并模式 - ChapterPageCounter: 优化分页计数逻辑,支持章节级分页缓存 - PageFrameFactory: 新增 CoreText 页面帧工厂,提升排版性能 - ReaderContext/ReaderRuntime: 适配增量分页状态管理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
155 lines
5.9 KiB
Swift
155 lines
5.9 KiB
Swift
// RDEPUBReaderController+DataSource.swift
|
|
// EPUB 阅读器数据源与代理实现
|
|
// 实现 RDReaderDataSource 和 RDReaderDelegate 协议,为阅读器视图提供页面数量、
|
|
// 页面内容视图、页码变化通知、屏幕方向变化处理等核心数据与事件支持。
|
|
|
|
import UIKit
|
|
|
|
/// RDEPUBReaderController 数据源与代理扩展
|
|
///
|
|
/// 本文件实现 RDReaderDataSource 和 RDReaderDelegate 协议,为阅读器视图提供页面数量、
|
|
/// 页面内容视图、页码变化通知、屏幕方向变化处理等核心数据与事件支持。
|
|
|
|
// MARK: - RDReaderView 数据源与代理
|
|
|
|
extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
|
/// 返回阅读器总页数
|
|
public func pageCountOfReaderView(readerView: RDReaderView) -> Int {
|
|
textBook?.pages.count ?? activePages.count
|
|
}
|
|
|
|
/// 为指定页码创建或复用内容视图(优先文本渲染,回退 Web 渲染)
|
|
public func pageContentView(readerView: RDReaderView, pageNum: Int, containerView: UIView?) -> UIView {
|
|
if let textBook, let page = textBook.page(at: pageNum + 1) {
|
|
let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView()
|
|
contentView.delegate = self
|
|
contentView.configure(
|
|
page: page,
|
|
pageNumber: pageNum + 1,
|
|
totalPages: textBook.pages.count,
|
|
configuration: configuration,
|
|
highlights: textHighlights(for: page),
|
|
searchState: searchState
|
|
)
|
|
return contentView
|
|
}
|
|
|
|
guard let publication,
|
|
let request = request(for: pageNum) else {
|
|
return containerView ?? UIView()
|
|
}
|
|
|
|
let contentView = (containerView as? RDEPUBWebContentView) ?? RDEPUBWebContentView()
|
|
contentView.delegate = self
|
|
contentView.configure(
|
|
publication: publication,
|
|
request: request,
|
|
pageNumber: pageNum + 1,
|
|
totalPages: activePages.count,
|
|
theme: configuration.theme
|
|
)
|
|
return contentView
|
|
}
|
|
|
|
/// 返回页面内容视图的重用标识符(区分文本与 Web 渲染)
|
|
public func pageIdentifier(readerView: RDReaderView, pageNum: Int) -> String? {
|
|
textBook == nil
|
|
? NSStringFromClass(RDEPUBWebContentView.self)
|
|
: NSStringFromClass(RDEPUBTextContentView.self)
|
|
}
|
|
|
|
/// 获取指定文本页面上的高亮标注列表
|
|
private func textHighlights(for page: RDEPUBTextPage) -> [RDEPUBHighlight] {
|
|
if let textBook,
|
|
let chapterData = textBook.chapterData(for: page.href) {
|
|
return chapterData.highlights(on: page, from: activeHighlights)
|
|
}
|
|
|
|
guard let publication else {
|
|
return activeHighlights.filter { $0.location.href == page.href }
|
|
}
|
|
let pageHref = publication.resourceResolver.normalizedHref(page.href) ?? page.href
|
|
return activeHighlights.filter {
|
|
(publication.resourceResolver.normalizedHref($0.location.href) ?? $0.location.href) == pageHref
|
|
}
|
|
}
|
|
|
|
/// 根据规范化 href 获取文本章节数据
|
|
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
|
readerContext.textChapterData(forNormalizedHref: href)
|
|
}
|
|
|
|
/// 返回阅读器顶部工具视图
|
|
public func topToolView(readerView: RDReaderView) -> UIView? {
|
|
topToolView
|
|
}
|
|
|
|
/// 返回阅读器底部工具视图
|
|
public func bottomToolView(readerView: RDReaderView) -> UIView? {
|
|
bottomToolView
|
|
}
|
|
|
|
/// 页码变化回调:清除选区、同步阅读状态、检测到达末尾
|
|
public func pageNum(readerView: RDReaderView, pageNum: Int) {
|
|
readerContext.markUserNavigationActivity()
|
|
updateCurrentSelection(nil)
|
|
reconcileTextPaginationSizeIfNeeded(for: pageNum)
|
|
|
|
let totalPages = pageCountOfReaderView(readerView: readerView)
|
|
if totalPages > 0, pageNum == totalPages - 1 {
|
|
delegate?.epubReaderDidReachEnd(self)
|
|
}
|
|
|
|
if textBook != nil,
|
|
let location = resolvedTextLocation(forPageNumber: pageNum + 1) {
|
|
persist(location: location)
|
|
synchronizeTextReadingState(pageNumber: pageNum + 1, location: location)
|
|
return
|
|
}
|
|
|
|
if let location = fallbackLocation(for: pageNum) {
|
|
persist(location: location)
|
|
}
|
|
if readingSession?.navigatorState == .jumping || readingSession?.navigatorState == .moving {
|
|
readingSession?.transition(to: .idle)
|
|
}
|
|
}
|
|
|
|
/// 屏幕方向即将变化回调,捕获待恢复的展示位置
|
|
public func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool) {
|
|
_ = isLandscape
|
|
runtime.viewportMonitor.capturePendingPresentationRestoreLocation()
|
|
}
|
|
|
|
/// 检测页面尺寸变化并触发重新分页(避免布局错乱)
|
|
private func reconcileTextPaginationSizeIfNeeded(for pageNum: Int) {
|
|
guard textBook != nil,
|
|
!isRepaginating,
|
|
!isReconcilingTextPaginationSize,
|
|
pageNum >= 0,
|
|
let lastTextPaginationPageSize else {
|
|
return
|
|
}
|
|
|
|
let resolvedSize = readerView.resolvedSinglePageSize(pageNum: pageNum)
|
|
guard resolvedSize.width > 0,
|
|
resolvedSize.height > 0 else {
|
|
return
|
|
}
|
|
|
|
let sizeChanged = abs(resolvedSize.width - lastTextPaginationPageSize.width) > 0.5
|
|
|| abs(resolvedSize.height - lastTextPaginationPageSize.height) > 0.5
|
|
guard sizeChanged else {
|
|
return
|
|
}
|
|
|
|
isReconcilingTextPaginationSize = true
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self else { return }
|
|
self.isReconcilingTextPaginationSize = false
|
|
guard self.textBook != nil, !self.isRepaginating else { return }
|
|
self.repaginatePreservingCurrentLocation()
|
|
}
|
|
}
|
|
}
|