210 lines
7.1 KiB
Swift
210 lines
7.1 KiB
Swift
import UIKit
|
||
|
||
/// 阅读器共享状态中心:所有 coordinator 通过 context 访问业务状态和便捷方法。
|
||
///
|
||
/// context 持有:
|
||
/// - 业务状态(parser、publication、textBook、pages 等)
|
||
/// - UI 配置(configuration、brightness)
|
||
/// - 持久化策略(persistence)
|
||
/// - 便捷方法(renderStyle、layoutConfig 等)
|
||
/// - 弱引用 controller(仅用于 UIKit 呈现操作)
|
||
final class RDEPUBReaderContext {
|
||
// MARK: - 引用
|
||
|
||
weak var controller: RDEPUBReaderController?
|
||
weak var readerView: RDReaderView?
|
||
var dependencies: RDEPUBReaderDependencies = .live
|
||
var runtime: RDEPUBReaderRuntime? {
|
||
controller?.runtime
|
||
}
|
||
|
||
// MARK: - 业务状态
|
||
|
||
var parser: RDEPUBParser?
|
||
var publication: RDEPUBPublication?
|
||
var readingSession: RDEPUBReadingSession?
|
||
var textBook: RDEPUBTextBook?
|
||
var activeBookmarks: [RDEPUBBookmark] = []
|
||
var activeHighlights: [RDEPUBHighlight] = []
|
||
var currentBookIdentifier: String?
|
||
var paginationToken = UUID()
|
||
var paginator: RDEPUBPaginator?
|
||
var searchState: RDEPUBSearchState?
|
||
var lastTextPaginationPageSize: CGSize?
|
||
var currentSelection: RDEPUBSelection?
|
||
|
||
// MARK: - 控制器状态(从 controller 下沉)
|
||
|
||
var configuration: RDEPUBReaderConfiguration = .default
|
||
var persistence: RDEPUBReaderPersistence?
|
||
var epubURL: URL = URL(string: "about:blank")!
|
||
var isRepaginating: Bool = false
|
||
var didStartInitialLoad: Bool = false
|
||
var isExternalTextBook: Bool = false
|
||
var textFileURL: URL?
|
||
var textBookCache = RDEPUBTextBookCache()
|
||
|
||
// MARK: - 初始化
|
||
|
||
init(controller: RDEPUBReaderController) {
|
||
self.controller = controller
|
||
self.readerView = controller.readerView
|
||
}
|
||
|
||
// MARK: - 便捷方法
|
||
|
||
func currentLayoutContext() -> RDEPUBNavigatorLayoutContext {
|
||
let containerSize = readerView?.bounds.size ?? .zero
|
||
let viewSize = controller?.view.bounds.size ?? containerSize
|
||
let resolvedSize = containerSize == .zero ? viewSize : containerSize
|
||
return RDEPUBNavigatorLayoutContext(
|
||
containerSize: resolvedSize,
|
||
pagesPerScreen: readerView?.pagesPerScreen ?? 1,
|
||
safeAreaInsets: controller?.view.safeAreaInsets ?? .zero,
|
||
userInterfaceIdiom: controller?.traitCollection.userInterfaceIdiom ?? .phone,
|
||
reflowableContentInsets: configuration.reflowableContentInsets
|
||
)
|
||
}
|
||
|
||
func currentPreferences() -> RDEPUBPreferences {
|
||
configuration.makePreferences()
|
||
}
|
||
|
||
func currentTextPageSize() -> CGSize {
|
||
let pageNum = (readerView?.currentPage ?? -1) >= 0 ? readerView?.currentPage : nil
|
||
if let readerView, let pageNum {
|
||
let resolvedSize = readerView.resolvedSinglePageSize(pageNum: pageNum)
|
||
if resolvedSize.width > 0, resolvedSize.height > 0 {
|
||
return resolvedSize
|
||
}
|
||
}
|
||
return currentLayoutContext().viewportSize
|
||
}
|
||
|
||
func currentTextRenderStyle() -> RDEPUBTextRenderStyle {
|
||
let font = UIFont.systemFont(ofSize: configuration.fontSize)
|
||
let lineSpacing = max(font.lineHeight * (configuration.lineHeightMultiple - 1), 4)
|
||
return RDEPUBTextRenderStyle(
|
||
font: font,
|
||
lineSpacing: lineSpacing,
|
||
textColor: configuration.theme.contentTextColor,
|
||
backgroundColor: configuration.theme.contentBackgroundColor
|
||
)
|
||
}
|
||
|
||
func currentTextLayoutConfig(pageSize: CGSize) -> RDEPUBTextLayoutConfig {
|
||
return RDEPUBTextLayoutConfig(
|
||
frameWidth: max(pageSize.width, 1),
|
||
frameHeight: max(pageSize.height, 1),
|
||
edgeInsets: configuration.reflowableContentInsets,
|
||
numberOfColumns: configuration.numberOfColumns,
|
||
columnGap: configuration.columnGap,
|
||
avoidOrphans: true,
|
||
avoidWidows: true,
|
||
avoidPageBreakInsideEnabled: true,
|
||
hyphenation: true,
|
||
imageMaxHeightRatio: 0.85,
|
||
fallbackViewportSize: dependencies.environment.fallbackViewportSize
|
||
)
|
||
}
|
||
|
||
func resolvedTextRenderer() -> RDEPUBTextRenderer {
|
||
dependencies.makeTextRenderer(configuration.textRenderingEngine)
|
||
}
|
||
|
||
var activePages: [EPUBPage] {
|
||
readingSession?.activePages ?? []
|
||
}
|
||
|
||
var activeChapters: [EPUBChapterInfo] {
|
||
readingSession?.activeChapters ?? []
|
||
}
|
||
|
||
var currentBrightness: CGFloat {
|
||
get { dependencies.environment.currentBrightness }
|
||
set { dependencies.environment.currentBrightness = newValue }
|
||
}
|
||
|
||
func replaceActiveSnapshot(_ snapshot: RDEPUBReadingSession.PaginationSnapshot) {
|
||
readingSession?.setActiveSnapshot(snapshot)
|
||
}
|
||
|
||
func clearActiveSnapshot() {
|
||
readingSession?.resetRuntimeState()
|
||
}
|
||
|
||
func makeParser() -> RDEPUBParser {
|
||
dependencies.makeParser()
|
||
}
|
||
|
||
func makePaginator() -> RDEPUBPaginator {
|
||
dependencies.makePaginator()
|
||
}
|
||
|
||
func makeTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDEPUBTextBookBuilder {
|
||
dependencies.makeTextBookBuilder(resolvedTextRenderer(), textBookCache, layoutConfig)
|
||
}
|
||
|
||
func makePlainTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDPlainTextBookBuilder {
|
||
dependencies.makePlainTextBookBuilder(resolvedTextRenderer(), layoutConfig)
|
||
}
|
||
|
||
func currentVisibleLocation() -> RDEPUBLocation? {
|
||
controller?.currentVisibleLocation()
|
||
}
|
||
|
||
func persistenceLocation() -> RDEPUBLocation? {
|
||
guard let currentBookIdentifier else { return nil }
|
||
return persistence?.loadLocation(for: currentBookIdentifier)
|
||
}
|
||
|
||
func persist(location: RDEPUBLocation) {
|
||
guard let currentBookIdentifier else { return }
|
||
persistence?.saveLocation(location, for: currentBookIdentifier)
|
||
}
|
||
|
||
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
||
guard let textBook, let publication else { return nil }
|
||
let normalizedHref = publication.resourceResolver.normalizedHref(href) ?? href
|
||
return textBook.chapters.lazy
|
||
.first(where: { (publication.resourceResolver.normalizedHref($0.href) ?? $0.href) == normalizedHref })
|
||
.flatMap { textBook.chapterData(for: $0.href) }
|
||
}
|
||
|
||
func showLoading() {
|
||
controller?.showLoading()
|
||
}
|
||
|
||
func hideLoading() {
|
||
controller?.hideLoading()
|
||
}
|
||
|
||
func handle(error: Error) {
|
||
controller?.handle(error: error)
|
||
}
|
||
|
||
func updateReaderChrome() {
|
||
controller?.updateReaderChrome()
|
||
}
|
||
|
||
func refreshVisibleContentPreservingLocation() {
|
||
controller?.refreshVisibleContentPreservingLocation()
|
||
}
|
||
|
||
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool = false) -> Bool {
|
||
controller?.restoreReadingLocation(location, animated: animated) ?? false
|
||
}
|
||
|
||
func repaginatePreservingCurrentLocation() {
|
||
controller?.repaginatePreservingCurrentLocation()
|
||
}
|
||
|
||
func applyReaderViewConfiguration() {
|
||
controller?.applyReaderViewConfiguration()
|
||
}
|
||
|
||
func updateBookmarkChrome() {
|
||
controller?.updateBookmarkChrome()
|
||
}
|
||
}
|