docs: 补充注释、修正过时文档、清理重复内容

源码注释:
- 为 ~60 个 Swift 文件补充缺失的 doc comment(file header、类型、属性、方法)
- 修正 4 处错误注释:翻页模式数量、搜索行为描述、手势识别器描述、悬空文档块

文档维护:
- 删除重复文档:WXRead/读书EPUB阅读器实现架构.md(与微信读书版完全一致)
- 合并重叠文档:阅读器规划.md → 阅读器功能开发计划.md(单一真值)
- 修正过时内容:所有文档中"四种翻页模式"→"三种",移除 horizontalCoverScroll
- 更新架构图:补齐 EPUBUI/ReaderController、Paging/、Typesetter/ 等子目录
- 更新 index.md 索引:新增开发计划和架构对比文档引用
This commit is contained in:
shen
2026-06-01 09:33:23 +08:00
parent 1efb9d172f
commit 948004eed1
56 changed files with 1691 additions and 792 deletions
@@ -1,8 +1,19 @@
// 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 {
@@ -24,6 +35,7 @@ extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
)
}
/// Web
func epubWebContentView(_ contentView: RDEPUBWebContentView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int) {
if let selection {
updateCurrentSelection(scopedSelection(selection, relativeToSpineIndex: spineIndex))
@@ -32,10 +44,12 @@ extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
}
}
/// 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(
@@ -48,11 +62,13 @@ extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
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)")
}
@@ -61,6 +77,7 @@ extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
// MARK: - Native Text
extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
///
func textContentView(_ contentView: RDEPUBTextContentView, didChangeSelection selection: RDEPUBSelection?) {
guard let selection else {
updateCurrentSelection(nil)
@@ -69,6 +86,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
updateCurrentSelection(normalizedTextSelection(selection))
}
///
func textContentView(
_ contentView: RDEPUBTextContentView,
didRequestSelectionAction action: RDEPUBAnnotationMenuAction,
@@ -79,6 +97,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
contentView.clearSelection()
}
///
private func normalizedTextSelection(_ selection: RDEPUBSelection) -> RDEPUBSelection? {
guard let textBook,
let chapterData = textBook.chapterData(for: selection.location.href) else {
@@ -103,6 +122,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
)
}
/// EPUB
func pageNumber(for location: RDEPUBLocation) -> Int? {
if let textBook, let publication {
if let anchor = location.rangeAnchor?.start {
@@ -130,6 +150,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
)
}
///
func resolvedTextLocation(forPageNumber pageNumber: Int) -> RDEPUBLocation? {
guard let textBook,
let publication,
@@ -148,6 +169,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
) ?? location
}
/// spine
func synchronizeTextReadingState(pageNumber: Int, location: RDEPUBLocation) {
guard let textBook,
let page = textBook.page(at: pageNumber) else {
@@ -164,6 +186,7 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
)
}
/// +
func nativeTextSnapshot(from textBook: RDEPUBTextBook) -> RDEPUBNativeTextSnapshot {
let chapters = textBook.chapterInfos
let pages = textBook.pages.map {
@@ -1,12 +1,24 @@
// 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()
@@ -39,12 +51,14 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
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) {
@@ -60,18 +74,22 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
}
}
/// 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) {
updateCurrentSelection(nil)
reconcileTextPaginationSizeIfNeeded(for: pageNum)
@@ -96,11 +114,13 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
}
}
///
public func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool) {
_ = isLandscape
runtime.viewportMonitor.capturePendingPresentationRestoreLocation()
}
///
private func reconcileTextPaginationSizeIfNeeded(for pageNum: Int) {
guard textBook != nil,
!isRepaginating,
@@ -1,3 +1,6 @@
// RDEPUBReaderController+PublicAPI.swift
// RDEPUBReaderController API
import UIKit
// MARK: - Public Reader Commands
@@ -30,14 +33,22 @@ extension RDEPUBReaderController {
runtime.clearSelection()
}
///
/// - Parameter id: ID
/// - Returns: nil
public func bookmark(withID id: String) -> RDEPUBBookmark? {
runtime.bookmark(withID: id)
}
///
/// - Parameter id: ID
/// - Returns: nil
public func highlight(withID id: String) -> RDEPUBHighlight? {
runtime.highlight(withID: id)
}
/// 访
/// - Returns: nil
public func nativeTextSemanticSummary() -> String? {
guard let textBook,
let page = textBook.page(at: max(readerView.currentPage + 1, 1)) ?? textBook.pages.first else {
@@ -73,6 +84,13 @@ extension RDEPUBReaderController {
runtime.addHighlight(from: selection, color: color, note: note)
}
///
/// - Parameters:
/// - selection: 使 currentSelection
/// - style: 线
/// - color: CSS
/// - note:
/// - Returns: nil
@discardableResult
public func addAnnotation(
from selection: RDEPUBSelection? = nil,
@@ -83,40 +101,72 @@ extension RDEPUBReaderController {
runtime.addAnnotation(from: selection, style: style, color: color, note: note)
}
///
/// - Parameter highlight:
/// - Returns:
@discardableResult
public func upsertHighlight(_ highlight: RDEPUBHighlight) -> RDEPUBHighlight? {
runtime.upsertHighlight(highlight)
}
///
/// - Parameter id: ID
/// - Returns: nil
@discardableResult
public func removeHighlight(id: String) -> RDEPUBHighlight? {
runtime.removeHighlight(id: id)
}
///
/// - Parameters:
/// - id: ID
/// - note: nil
/// - Returns: nil
@discardableResult
public func updateHighlightNote(id: String, note: String?) -> RDEPUBHighlight? {
runtime.updateHighlightNote(id: id, note: note)
}
///
/// - Parameters:
/// - id: ID
/// - animated:
/// - Returns:
@discardableResult
public func go(toHighlightID id: String, animated: Bool = true) -> Bool {
runtime.go(toHighlightID: id, animated: animated)
}
///
public func removeAllHighlights() {
runtime.removeAllHighlights()
}
///
/// - Parameters:
/// - item: EPUB
/// - animated:
/// - Returns:
@discardableResult
public func go(toTableOfContentsItem item: EPUBTableOfContentsItem, animated: Bool = true) -> Bool {
go(toTableOfContentsHref: item.href, animated: animated)
}
///
/// - Parameters:
/// - item: RDReader
/// - animated:
/// - Returns:
@discardableResult
public func go(toTableOfContentsItem item: RDEPUBReaderTableOfContentsItem, animated: Bool = true) -> Bool {
go(toTableOfContentsHref: item.href, animated: animated)
}
/// href href
/// - Parameters:
/// - href: #fragment
/// - animated:
/// - Returns:
@discardableResult
public func go(toTableOfContentsHref href: String, animated: Bool = true) -> Bool {
guard publication != nil else { return false }
@@ -133,42 +183,61 @@ extension RDEPUBReaderController {
return restoreReadingLocation(location, animated: animated)
}
///
/// - Parameter note:
/// - Returns: nil
@discardableResult
public func addBookmark(note: String? = nil) -> RDEPUBBookmark? {
runtime.addBookmark(note: note)
}
///
/// - Parameter note:
/// - Returns:
@discardableResult
public func toggleBookmark(note: String? = nil) -> RDEPUBBookmark? {
runtime.toggleBookmark(note: note)
}
///
/// - Parameter id: ID
/// - Returns: nil
@discardableResult
public func removeBookmark(id: String) -> RDEPUBBookmark? {
runtime.removeBookmark(id: id)
}
///
/// - Parameters:
/// - id: ID
/// - animated:
/// - Returns:
@discardableResult
public func go(toBookmarkID id: String, animated: Bool = true) -> Bool {
runtime.go(toBookmarkID: id, animated: animated)
}
///
///
/// - Parameter keyword:
public func search(keyword: String) {
runtime.search(keyword: keyword)
}
///
/// - Returns:
@discardableResult
public func searchNext() -> Bool {
runtime.searchNext()
}
///
/// - Returns:
@discardableResult
public func searchPrevious() -> Bool {
runtime.searchPrevious()
}
///
public func clearSearch() {
runtime.clearSearch()
}
@@ -1,30 +1,47 @@
// RDEPUBReaderController+RenderSupport.swift
// EPUB
//
// 宿退
import UIKit
/// RDEPUBReaderController
///
///
/// 退
extension RDEPUBReaderController {
///
func currentLayoutContext() -> RDEPUBNavigatorLayoutContext {
readerContext.currentLayoutContext()
}
///
func currentPreferences() -> RDEPUBPreferences {
readerContext.currentPreferences()
}
///
func currentTextPageSize() -> CGSize {
readerContext.currentTextPageSize()
}
///
func currentTextRenderStyle() -> RDEPUBTextRenderStyle {
readerContext.currentTextRenderStyle()
}
///
func currentTextLayoutConfig(pageSize: CGSize) -> RDEPUBTextLayoutConfig {
readerContext.currentTextLayoutConfig(pageSize: pageSize)
}
///
func resolvedTextRenderer() -> RDEPUBTextRenderer {
readerContext.resolvedTextRenderer()
}
/// 宿
func ensurePaginationHostView() -> UIView {
let viewportSize = currentLayoutContext().viewportSize
let hostFrame = CGRect(x: -viewportSize.width - 32, y: 0, width: viewportSize.width, height: viewportSize.height)
@@ -36,6 +53,7 @@ extension RDEPUBReaderController {
return paginationHostView
}
///
func request(for pageIndex: Int) -> RDEPUBRenderRequest? {
guard let publication, activePages.indices.contains(pageIndex) else {
return nil
@@ -52,11 +70,13 @@ extension RDEPUBReaderController {
)
}
/// 退使
func fallbackLocation(for pageIndex: Int) -> RDEPUBLocation? {
guard activePages.indices.contains(pageIndex) else { return nil }
return readingSession?.fallbackLocation(for: activePages[pageIndex], bookIdentifier: currentBookIdentifier)
}
///
private func highlights(for page: EPUBPage) -> [RDEPUBHighlight] {
guard let publication else { return [] }
if let spread = page.fixedSpread {
@@ -1,6 +1,17 @@
// RDEPUBReaderController+RuntimeBridge.swift
// EPUB
// runtime
//
import UIKit
/// RDEPUBReaderController
///
/// runtime
///
extension RDEPUBReaderController {
/// //
func applyReaderViewConfiguration() {
let resolvedDirection = resolvedPageDirection()
let presentationDidChange = readerView.currentDisplayType != configuration.displayType
@@ -21,14 +32,17 @@ extension RDEPUBReaderController {
}
}
///
func startInitialLoadIfNeeded() {
runtime.startInitialLoadIfNeeded()
}
/// EPUB
func loadPublication() {
runtime.loadPublication()
}
///
func applyParsedPublication(
parser: RDEPUBParser,
publication: RDEPUBPublication,
@@ -47,14 +61,17 @@ extension RDEPUBReaderController {
)
}
///
func paginatePublication(restoreLocation: RDEPUBLocation?) {
runtime.paginatePublication(restoreLocation: restoreLocation)
}
///
func applyTextBook(_ textBook: RDEPUBTextBook, restoreLocation: RDEPUBLocation?) {
runtime.applyTextBook(textBook, restoreLocation: restoreLocation)
}
///
func applyPaginationSnapshot(
_ snapshot: (pages: [EPUBPage], chapters: [EPUBChapterInfo]),
restoreLocation: RDEPUBLocation?
@@ -62,35 +79,43 @@ extension RDEPUBReaderController {
runtime.applyPaginationSnapshot(snapshot, restoreLocation: restoreLocation)
}
///
func finishPagination(restoreLocation: RDEPUBLocation?) {
runtime.finishPagination(restoreLocation: restoreLocation)
}
///
func repaginatePreservingCurrentLocation() {
runtime.repaginatePreservingCurrentLocation()
}
///
@discardableResult
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool = false) -> Bool {
runtime.restoreReadingLocation(location, animated: animated)
}
///
func currentVisibleLocation() -> RDEPUBLocation? {
runtime.currentVisibleLocation()
}
///
func persistenceLocation() -> RDEPUBLocation? {
readerContext.persistenceLocation()
}
///
func persist(location: RDEPUBLocation) {
readerContext.persist(location: location)
}
///
func updateCurrentSelection(_ selection: RDEPUBSelection?) {
runtime.annotationCoordinator.updateCurrentSelection(selection)
}
/// spine
func scopedSelection(
_ selection: RDEPUBSelection,
relativeToSpineIndex spineIndex: Int?
@@ -98,53 +123,65 @@ extension RDEPUBReaderController {
runtime.annotationCoordinator.scopedSelection(selection, relativeToSpineIndex: spineIndex)
}
///
func refreshVisibleContentPreservingLocation() {
runtime.refreshVisibleContentPreservingLocation()
}
///
func rebuildExternalTextBook() {
runtime.rebuildExternalTextBook()
}
/// UI
func updateReaderChrome() {
runtime.updateReaderChrome()
}
///
func updateBookmarkChrome() {
runtime.updateBookmarkChrome()
}
///
func presentBookmarksManager() {
runtime.presentBookmarksManager()
}
///
func presentHighlightsManager() {
runtime.presentHighlightsManager()
}
///
func presentAnnotationCreation() {
runtime.presentAnnotationCreation()
}
///
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) {
runtime.handleSelectionMenuAction(action, selection: selection)
}
///
func presentSettings() {
runtime.presentSettings()
}
/// 使
func updateConfiguration(_ update: (inout RDEPUBReaderConfiguration) -> Void) {
var nextConfiguration = configuration
update(&nextConfiguration)
configuration = nextConfiguration
}
///
func setScreenBrightness(_ brightness: CGFloat) {
currentBrightness = max(0, min(1, brightness))
persistReaderSettingsIfNeeded()
}
///
func persistReaderSettingsIfNeeded() {
let settings = RDEPUBReaderSettings.capture(
configuration: configuration,
@@ -153,6 +190,7 @@ extension RDEPUBReaderController {
persistence?.saveReaderSettings(settings)
}
///
func requiresRepagination(
from oldConfiguration: RDEPUBReaderConfiguration,
to newConfiguration: RDEPUBReaderConfiguration
@@ -169,6 +207,7 @@ extension RDEPUBReaderController {
oldConfiguration.textRenderingEngine != newConfiguration.textRenderingEngine
}
///
func requiresVisibleRefresh(
from oldConfiguration: RDEPUBReaderConfiguration,
to newConfiguration: RDEPUBReaderConfiguration
@@ -178,14 +217,17 @@ extension RDEPUBReaderController {
oldConfiguration.darkImageBlendRatio != newConfiguration.darkImageBlendRatio
}
///
func presentTableOfContents() {
runtime.presentTableOfContents()
}
///
func handleBackAction() {
runtime.handleBackAction()
}
///
func handle(error: Error) {
isRepaginating = false
hideLoading()
@@ -197,19 +239,23 @@ extension RDEPUBReaderController {
delegate?.epubReader(self, didFailWithError: error)
}
///
func showLoading() {
errorLabel.isHidden = true
loadingIndicator.startAnimating()
}
///
func hideLoading() {
loadingIndicator.stopAnimating()
}
///
func currentViewportSignature() -> RDEPUBViewportSignature? {
runtime.currentViewportSignature()
}
///
func handleViewportChangeIfNeeded(
reason: RDEPUBViewportChangeReason,
viewportSignature: RDEPUBViewportSignature? = nil
@@ -217,18 +263,21 @@ extension RDEPUBReaderController {
runtime.handleViewportChangeIfNeeded(reason: reason, viewportSignature: viewportSignature)
}
///
func searchPresentation(for page: EPUBPage) -> RDEPUBSearchPresentation? {
runtime.searchPresentation(for: page)
}
///
private func resolvedPageDirection() -> RDReaderView.PageDirection {
publication?.readingProgression == .rtl ? .rightToLeft : .leftToRight
}
}
// MARK: - NavigationController
// MARK: -
extension RDEPUBReaderController: UIGestureRecognizerDelegate {
/// true
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
@@ -1,6 +1,17 @@
// RDEPUBReaderController+TableOfContents.swift
// EPUB
// Table of Contents
// 线
import Foundation
/// RDEPUBReaderController
///
/// Table of Contents
/// 线
extension RDEPUBReaderController {
/// href
func resolvedCurrentTableOfContentsItem() -> RDEPUBReaderTableOfContentsItem? {
let items = flattenedTableOfContents
guard !items.isEmpty else { return nil }
@@ -46,6 +57,7 @@ extension RDEPUBReaderController {
}
}
/// 线
func flattenedTableOfContentsItems(
from items: [EPUBTableOfContentsItem],
depth: Int = 0
@@ -3,11 +3,17 @@ import UIKit
///
///
struct RDEPUBViewportSignature: Equatable {
/// pt
let width: CGFloat
/// pt
let height: CGFloat
///
let safeTop: CGFloat
///
let safeLeft: CGFloat
///
let safeBottom: CGFloat
///
let safeRight: CGFloat
func differsSignificantly(from other: RDEPUBViewportSignature, threshold: CGFloat = 1) -> Bool {
@@ -22,8 +28,11 @@ struct RDEPUBViewportSignature: Equatable {
///
enum RDEPUBViewportChangeReason {
/// Safe Area
case viewLayout
///
case orientationTransition
}
///
typealias RDEPUBNativeTextSnapshot = (pages: [EPUBPage], chapters: [EPUBChapterInfo])
@@ -1,6 +1,7 @@
import UIKit
/// WebView
/// EPUB WebView
/// WebView 线
/// WXRead JS rect CGContext
final class RDEPUBWebDecorationOverlayView: UIView {
private var decorations: [RDEPUBTextOverlayDecoration] = []
@@ -17,6 +18,8 @@ final class RDEPUBWebDecorationOverlayView: UIView {
fatalError("init(coder:) has not been implemented")
}
///
/// - Parameter decorations: 线
func applyDecorations(_ decorations: [RDEPUBTextOverlayDecoration]) {
self.decorations = decorations.filter { !$0.rects.isEmpty }
setNeedsDisplay()
@@ -1,5 +1,12 @@
import UIKit
/// EPUB
///
///
/// - highlightannotation
/// - bookmark
/// -
/// -
final class RDEPUBReaderAnnotationCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -11,16 +18,19 @@ final class RDEPUBReaderAnnotationCoordinator {
context.controller
}
/// ID
func bookmark(withID id: String) -> RDEPUBBookmark? {
guard let controller else { return nil }
return controller.activeBookmarks.first { $0.id == id }
}
/// ID
func highlight(withID id: String) -> RDEPUBHighlight? {
guard let controller else { return nil }
return controller.activeHighlights.first { $0.id == id }
}
///
func updateCurrentSelection(_ selection: RDEPUBSelection?) {
guard let controller else { return }
controller.currentSelection = selection?.isEmpty == false ? selection : nil
@@ -34,6 +44,7 @@ final class RDEPUBReaderAnnotationCoordinator {
controller.delegate?.epubReader(controller, didChangeSelection: controller.currentSelection)
}
///
@discardableResult
func addHighlight(
from selection: RDEPUBSelection? = nil,
@@ -43,6 +54,7 @@ final class RDEPUBReaderAnnotationCoordinator {
addAnnotation(from: selection, style: .highlight, color: color, note: note)
}
/// /线/
@discardableResult
func addAnnotation(
from selection: RDEPUBSelection? = nil,
@@ -85,6 +97,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return newHighlight
}
/// upsert ID
@discardableResult
func upsertHighlight(_ highlight: RDEPUBHighlight) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -101,6 +114,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return scopedHighlight
}
/// ID
@discardableResult
func removeHighlight(id: String) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -112,6 +126,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return removed
}
///
@discardableResult
func updateHighlightNote(id: String, note: String?) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -123,6 +138,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return controller.activeHighlights[index]
}
///
@discardableResult
func go(toHighlightID id: String, animated: Bool = true) -> Bool {
guard let controller else { return false }
@@ -132,6 +148,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return controller.restoreReadingLocation(highlight.location, animated: animated)
}
///
func removeAllHighlights() {
guard let controller else { return }
guard !controller.activeHighlights.isEmpty else { return }
@@ -139,6 +156,7 @@ final class RDEPUBReaderAnnotationCoordinator {
persistHighlightsAndRefreshContent()
}
/// spine
func scopedSelection(
_ selection: RDEPUBSelection,
relativeToSpineIndex spineIndex: Int?
@@ -166,6 +184,7 @@ final class RDEPUBReaderAnnotationCoordinator {
)
}
///
func presentHighlightsManager() {
guard let controller else { return }
guard controller.configuration.allowsHighlights else { return }
@@ -196,6 +215,7 @@ final class RDEPUBReaderAnnotationCoordinator {
controller.present(navigationController, animated: true)
}
/// /线/
func presentAnnotationCreation() {
guard let controller else { return }
guard controller.configuration.allowsHighlights,
@@ -205,6 +225,7 @@ final class RDEPUBReaderAnnotationCoordinator {
presentAnnotationActionSheet(for: currentSelection)
}
///
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) {
guard let selection else { return }
switch action {
@@ -218,6 +239,7 @@ final class RDEPUBReaderAnnotationCoordinator {
}
}
///
@discardableResult
func addBookmark(note: String? = nil) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -239,6 +261,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return newBookmark
}
///
@discardableResult
func toggleBookmark(note: String? = nil) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -254,6 +277,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return addBookmark(note: note)
}
/// ID
@discardableResult
func removeBookmark(id: String) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -265,6 +289,7 @@ final class RDEPUBReaderAnnotationCoordinator {
return removed
}
///
@discardableResult
func go(toBookmarkID id: String, animated: Bool = true) -> Bool {
guard let controller else { return false }
@@ -274,12 +299,14 @@ final class RDEPUBReaderAnnotationCoordinator {
return controller.restoreReadingLocation(bookmark.location, animated: animated)
}
/// UI
func updateBookmarkChrome() {
guard let controller else { return }
controller.topToolView.setBookmarkSelected(currentBookmark() != nil)
controller.bottomToolView.setBookmarksEnabled(!controller.activeBookmarks.isEmpty)
}
///
func presentBookmarksManager() {
guard let controller else { return }
guard !controller.activeBookmarks.isEmpty else { return }
@@ -1,5 +1,11 @@
import UIKit
/// EPUB UI
///
///
/// - readerViewloadingIndicatorerrorLabel
/// -
/// -
final class RDEPUBReaderAssemblyCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -7,6 +13,7 @@ final class RDEPUBReaderAssemblyCoordinator {
self.context = context
}
/// readerViewloadingIndicatorerrorLabel
func assembleInterface() {
guard let controller = context.controller,
let readerView = context.readerView else { return }
@@ -18,6 +25,7 @@ final class RDEPUBReaderAssemblyCoordinator {
controller.delegate?.epubReader(controller, configureTopToolView: controller.topToolView)
}
///
func finishExternalTextBookLaunchIfNeeded() {
guard let runtime = context.runtime,
context.isExternalTextBook else {
@@ -1,5 +1,13 @@
import UIKit
/// EPUB Chrome /
///
///
/// -
/// -
/// -
/// -
/// -
final class RDEPUBReaderChromeCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -11,6 +19,7 @@ final class RDEPUBReaderChromeCoordinator {
context.controller
}
///
func makeTopToolView() -> RDEPUBReaderTopToolView {
let toolView = RDEPUBReaderTopToolView()
toolView.onBack = { [weak self] in
@@ -22,6 +31,7 @@ final class RDEPUBReaderChromeCoordinator {
return toolView
}
///
func makeBottomToolView() -> RDEPUBReaderBottomToolView {
let toolView = RDEPUBReaderBottomToolView()
toolView.onShowTableOfContents = { [weak self] in
@@ -42,6 +52,7 @@ final class RDEPUBReaderChromeCoordinator {
return toolView
}
///
func updateReaderChrome() {
guard let controller else { return }
controller.topToolView.apply(theme: controller.configuration.theme)
@@ -67,6 +78,7 @@ final class RDEPUBReaderChromeCoordinator {
controller.updateBookmarkChrome()
}
///
func presentSettings() {
guard let controller else { return }
guard controller.configuration.showsSettingsPanel else { return }
@@ -101,6 +113,7 @@ final class RDEPUBReaderChromeCoordinator {
controller.present(navigationController, animated: true)
}
///
func presentTableOfContents() {
guard let controller else { return }
guard controller.configuration.showsTableOfContents else { return }
@@ -124,6 +137,7 @@ final class RDEPUBReaderChromeCoordinator {
controller.present(navigationController, animated: true)
}
/// pop dismiss
func handleBackAction() {
guard let controller else { return }
close(controller)
@@ -11,37 +11,61 @@ import UIKit
final class RDEPUBReaderContext {
// MARK: -
/// UIKit
weak var controller: RDEPUBReaderController?
///
weak var readerView: RDReaderView?
///
var dependencies: RDEPUBReaderDependencies = .live
/// 便访
var runtime: RDEPUBReaderRuntime? {
controller?.runtime
}
// MARK: -
/// EPUB
var parser: RDEPUBParser?
///
var publication: RDEPUBPublication?
///
var readingSession: RDEPUBReadingSession?
///
var textBook: RDEPUBTextBook?
///
var activeBookmarks: [RDEPUBBookmark] = []
///
var activeHighlights: [RDEPUBHighlight] = []
///
var currentBookIdentifier: String?
///
var paginationToken = UUID()
/// Web
var paginator: RDEPUBPaginator?
///
var searchState: RDEPUBSearchState?
///
var lastTextPaginationPageSize: CGSize?
///
var currentSelection: RDEPUBSelection?
// MARK: - controller
///
var configuration: RDEPUBReaderConfiguration = .default
///
var persistence: RDEPUBReaderPersistence?
/// EPUB URL
var epubURL: URL = URL(string: "about:blank")!
///
var isRepaginating: Bool = false
///
var didStartInitialLoad: Bool = false
///
var isExternalTextBook: Bool = false
/// URL
var textFileURL: URL?
///
var textBookCache = RDEPUBTextBookCache()
// MARK: -
@@ -53,6 +77,7 @@ final class RDEPUBReaderContext {
// MARK: - 便
/// readerView
func currentLayoutContext() -> RDEPUBNavigatorLayoutContext {
let containerSize = readerView?.bounds.size ?? .zero
let viewSize = controller?.view.bounds.size ?? containerSize
@@ -66,10 +91,12 @@ final class RDEPUBReaderContext {
)
}
///
func currentPreferences() -> RDEPUBPreferences {
configuration.makePreferences()
}
/// readerView
func currentTextPageSize() -> CGSize {
let pageNum = (readerView?.currentPage ?? -1) >= 0 ? readerView?.currentPage : nil
if let readerView, let pageNum {
@@ -81,6 +108,7 @@ final class RDEPUBReaderContext {
return currentLayoutContext().viewportSize
}
///
func currentTextRenderStyle() -> RDEPUBTextRenderStyle {
let font = configuration.fontChoice.font(ofSize: configuration.fontSize)
let lineSpacing = max(font.lineHeight * (configuration.lineHeightMultiple - 1), 4)
@@ -92,6 +120,7 @@ final class RDEPUBReaderContext {
)
}
///
func currentTextLayoutConfig(pageSize: CGSize) -> RDEPUBTextLayoutConfig {
return RDEPUBTextLayoutConfig(
frameWidth: max(pageSize.width, 1),
@@ -108,61 +137,75 @@ final class RDEPUBReaderContext {
)
}
///
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()
}
/// EPUB
func makeParser() -> RDEPUBParser {
dependencies.makeParser()
}
/// Web
func makePaginator() -> RDEPUBPaginator {
dependencies.makePaginator()
}
/// EPUB
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)
}
/// href
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
guard let textBook, let publication else { return nil }
let normalizedHref = publication.resourceResolver.normalizedHref(href) ?? href
@@ -171,38 +214,47 @@ final class RDEPUBReaderContext {
.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()
}
/// UI
func updateBookmarkChrome() {
controller?.updateBookmarkChrome()
}
@@ -1,10 +1,17 @@
// RDEPUBReaderDependencies.swift
// EPUB
import UIKit
///
public protocol RDEPUBReaderDisplayEnvironment: AnyObject {
/// 0.0 ~ 1.0
var currentBrightness: CGFloat { get set }
///
var fallbackViewportSize: CGSize { get }
}
/// UIScreen
public final class RDEPUBUIScreenEnvironment: RDEPUBReaderDisplayEnvironment {
public init() {}
@@ -18,14 +25,29 @@ public final class RDEPUBUIScreenEnvironment: RDEPUBReaderDisplayEnvironment {
}
}
/// EPUB 便
public struct RDEPUBReaderDependencies {
///
public var environment: any RDEPUBReaderDisplayEnvironment
/// EPUB
public var makeParser: () -> RDEPUBParser
///
public var makePaginator: () -> RDEPUBPaginator
///
public var makeTextBookBuilder: (RDEPUBTextRenderer, RDEPUBTextBookCache?, RDEPUBTextLayoutConfig) -> RDEPUBTextBookBuilder
///
public var makePlainTextBookBuilder: (RDEPUBTextRenderer, RDEPUBTextLayoutConfig) -> RDPlainTextBookBuilder
///
public var makeTextRenderer: (RDEPUBTextRenderingEngine) -> RDEPUBTextRenderer
///
/// - Parameters:
/// - environment:
/// - makeParser: EPUB
/// - makePaginator:
/// - makeTextBookBuilder:
/// - makePlainTextBookBuilder:
/// - makeTextRenderer:
public init(
environment: any RDEPUBReaderDisplayEnvironment,
makeParser: @escaping () -> RDEPUBParser,
@@ -42,6 +64,7 @@ public struct RDEPUBReaderDependencies {
self.makeTextRenderer = makeTextRenderer
}
/// 使
public static var live: RDEPUBReaderDependencies {
RDEPUBReaderDependencies(
environment: RDEPUBUIScreenEnvironment(),
@@ -1,5 +1,11 @@
import Foundation
/// EPUB EPUB
///
///
/// -
/// - EPUB Publication
/// -
final class RDEPUBReaderLoadCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -7,6 +13,7 @@ final class RDEPUBReaderLoadCoordinator {
self.context = context
}
///
func startInitialLoadIfNeeded() {
guard let controller = context.controller,
let readerView = context.readerView,
@@ -19,6 +26,7 @@ final class RDEPUBReaderLoadCoordinator {
loadPublication()
}
/// EPUB 线
func loadPublication() {
guard let controller = context.controller else { return }
context.showLoading()
@@ -57,6 +65,7 @@ final class RDEPUBReaderLoadCoordinator {
}
}
/// //
func applyParsedPublication(
parser: RDEPUBParser,
publication: RDEPUBPublication,
@@ -1,5 +1,12 @@
import Foundation
/// EPUB
///
///
/// -
/// -
/// -
/// -
final class RDEPUBReaderLocationCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -7,6 +14,7 @@ final class RDEPUBReaderLocationCoordinator {
self.context = context
}
///
@discardableResult
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool = false) -> Bool {
guard let controller = context.controller,
@@ -30,6 +38,7 @@ final class RDEPUBReaderLocationCoordinator {
return true
}
///
func currentVisibleLocation() -> RDEPUBLocation? {
guard let controller = context.controller,
let readerView = context.readerView else {
@@ -41,6 +50,7 @@ final class RDEPUBReaderLocationCoordinator {
return context.readingSession?.currentReadingLocation(bookIdentifier: context.currentBookIdentifier)
}
///
func persistenceLocation() -> RDEPUBLocation? {
guard let controller = context.controller,
let currentBookIdentifier = context.currentBookIdentifier else {
@@ -49,6 +59,7 @@ final class RDEPUBReaderLocationCoordinator {
return controller.persistence?.loadLocation(for: currentBookIdentifier)
}
///
func persist(location: RDEPUBLocation) {
guard let controller = context.controller,
let currentBookIdentifier = context.currentBookIdentifier else { return }
@@ -1,5 +1,13 @@
import Foundation
/// EPUB
///
///
/// - /Fixed Layout/Web
/// -
/// -
/// -
/// -
final class RDEPUBReaderPaginationCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -7,6 +15,7 @@ final class RDEPUBReaderPaginationCoordinator {
self.context = context
}
/// TextBookBuilderFixed Layout Web Paginator
func paginatePublication(restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller,
let parser = context.parser,
@@ -79,6 +88,7 @@ final class RDEPUBReaderPaginationCoordinator {
}
}
///
func applyTextBook(_ textBook: RDEPUBTextBook, restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller else { return }
context.textBook = textBook
@@ -93,6 +103,7 @@ final class RDEPUBReaderPaginationCoordinator {
finishPagination(restoreLocation: restoreLocation)
}
/// Fixed Layout Web
func applyPaginationSnapshot(
_ snapshot: (pages: [EPUBPage], chapters: [EPUBChapterInfo]),
restoreLocation: RDEPUBLocation?
@@ -109,6 +120,7 @@ final class RDEPUBReaderPaginationCoordinator {
finishPagination(restoreLocation: restoreLocation)
}
///
func finishPagination(restoreLocation: RDEPUBLocation?) {
guard let controller = context.controller,
let readerView = context.readerView else { return }
@@ -125,6 +137,7 @@ final class RDEPUBReaderPaginationCoordinator {
context.runtime?.viewportMonitor.processPendingChangeAfterPagination()
}
/// 使
func repaginatePreservingCurrentLocation() {
guard context.publication != nil else { return }
let restoreLocation = context.runtime?.viewportMonitor.consumePendingPresentationRestoreLocation()
@@ -133,6 +146,7 @@ final class RDEPUBReaderPaginationCoordinator {
paginatePublication(restoreLocation: restoreLocation)
}
///
func refreshVisibleContentPreservingLocation() {
guard let readerView = context.readerView else { return }
let restoreLocation = context.currentVisibleLocation() ?? context.persistenceLocation()
@@ -142,6 +156,7 @@ final class RDEPUBReaderPaginationCoordinator {
}
}
///
func rebuildExternalTextBook() {
guard let controller = context.controller,
let textFileURL = controller.textFileURL else { return }
@@ -1,5 +1,8 @@
import UIKit
/// EPUB
///
/// Facade
final class RDEPUBReaderRuntime {
private unowned let context: RDEPUBReaderContext
@@ -15,18 +18,22 @@ final class RDEPUBReaderRuntime {
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
@@ -44,10 +51,20 @@ final class RDEPUBReaderRuntime {
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,
@@ -73,6 +90,7 @@ final class RDEPUBReaderRuntime {
return true
}
///
func clearSelection() {
annotationCoordinator.updateCurrentSelection(nil)
}
@@ -168,20 +186,24 @@ final class RDEPUBReaderRuntime {
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()
}
@@ -190,26 +212,32 @@ final class RDEPUBReaderRuntime {
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,
@@ -228,14 +256,17 @@ final class RDEPUBReaderRuntime {
)
}
/// 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?
@@ -243,35 +274,43 @@ final class RDEPUBReaderRuntime {
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
@@ -1,5 +1,6 @@
import Foundation
///
final class RDEPUBReaderSearchCoordinator {
private unowned let context: RDEPUBReaderContext
@@ -11,6 +12,8 @@ final class RDEPUBReaderSearchCoordinator {
context.controller
}
///
/// - Parameter keyword:
func search(keyword: String) {
guard let controller else { return }
let normalizedKeyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -34,16 +37,21 @@ final class RDEPUBReaderSearchCoordinator {
}
}
///
/// - Returns:
@discardableResult
func searchNext() -> Bool {
advanceSearch(by: 1)
}
///
/// - Returns:
@discardableResult
func searchPrevious() -> Bool {
advanceSearch(by: -1)
}
///
func clearSearch() {
guard let controller else { return }
controller.searchState = nil
@@ -51,6 +59,9 @@ final class RDEPUBReaderSearchCoordinator {
controller.refreshVisibleContentPreservingLocation()
}
///
/// - Parameter page:
/// - Returns: nil
func searchPresentation(for page: EPUBPage) -> RDEPUBSearchPresentation? {
guard let controller else { return nil }
guard let searchState = controller.searchState,
@@ -1,5 +1,7 @@
import UIKit
///
///
final class RDEPUBReaderViewportMonitor {
private unowned let context: RDEPUBReaderContext
@@ -16,6 +18,7 @@ final class RDEPUBReaderViewportMonitor {
context.controller
}
///
func viewDidLayoutSubviews() {
guard let controller else { return }
guard let viewportSignature = currentViewportSignature() else { return }
@@ -38,6 +41,8 @@ final class RDEPUBReaderViewportMonitor {
handleViewportChangeIfNeeded(reason: .viewLayout, viewportSignature: viewportSignature)
}
///
/// - Parameter coordinator:
func viewWillTransition(with coordinator: UIViewControllerTransitionCoordinator) {
guard let controller else { return }
guard controller.didStartInitialLoad else { return }
@@ -52,6 +57,7 @@ final class RDEPUBReaderViewportMonitor {
}
}
///
func resetForReload() {
lastAppliedViewportSignature = currentViewportSignature()
pendingViewportChangeReason = nil
@@ -59,16 +65,19 @@ final class RDEPUBReaderViewportMonitor {
isWaitingForViewportTransitionCompletion = false
}
///
func consumePendingPresentationRestoreLocation() -> RDEPUBLocation? {
defer { pendingPresentationRestoreLocation = nil }
return pendingPresentationRestoreLocation
}
/// 使
func capturePendingPresentationRestoreLocation() {
guard let controller else { return }
pendingPresentationRestoreLocation = controller.currentVisibleLocation() ?? controller.persistenceLocation()
}
///
func processPendingChangeAfterPagination() {
guard let pendingReason = pendingViewportChangeReason else { return }
pendingViewportChangeReason = nil
@@ -77,6 +86,7 @@ final class RDEPUBReaderViewportMonitor {
}
}
///
func currentViewportSignature() -> RDEPUBViewportSignature? {
guard let controller else { return nil }
let containerSize = controller.readerView.bounds.size == .zero ? controller.view.bounds.size : controller.readerView.bounds.size
@@ -92,6 +102,7 @@ final class RDEPUBReaderViewportMonitor {
)
}
/// TextBook
func handleViewportChangeIfNeeded(
reason: RDEPUBViewportChangeReason,
viewportSignature: RDEPUBViewportSignature? = nil
@@ -1,10 +1,12 @@
import UIKit
/// UITextView UIMenuItem
/// UITextView
/// UIMenuItem `onSelectionAction`
final class RDEPUBSelectableTextView: UITextView {
///
///
var onSelectionAction: ((RDEPUBAnnotationMenuAction) -> Void)?
///
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(rd_copy(_:)),
@@ -1,7 +1,13 @@
import UIKit
///
///
final class RDEPUBTextAnnotationOverlay: RDEPUBSelectionOverlayView {
///
/// - Parameters:
/// - highlights:
/// - content:
/// - page:
/// - contentBaseOffset:
func applyHighlights(
_ highlights: [RDEPUBHighlight],
to content: NSMutableAttributedString,
@@ -38,6 +44,12 @@ final class RDEPUBTextAnnotationOverlay: RDEPUBSelectionOverlayView {
}
}
///
/// - Parameters:
/// - content:
/// - page:
/// - searchState:
/// - contentBaseOffset:
func applySearchHighlights(
to content: NSMutableAttributedString,
page: RDEPUBTextPage,
@@ -65,6 +77,13 @@ final class RDEPUBTextAnnotationOverlay: RDEPUBSelectionOverlayView {
}
}
/// 线
/// - Parameters:
/// - page:
/// - highlights:
/// - searchState:
/// - interactionController:
/// - Returns:
func buildDecorations(
page: RDEPUBTextPage,
highlights: [RDEPUBHighlight],
@@ -1,4 +1,5 @@
import UIKit
///
///
/// RDEPUBSelectionOverlayView
final class RDEPUBTextPageDecorationView: RDEPUBSelectionOverlayView {}
@@ -1,6 +1,11 @@
import UIKit
/// UIColor HEX 便
extension UIColor {
///
/// - Parameters:
/// - rdHexString: HEX "#" "#FF5733" "FF5733" 6
/// - alpha: 0.0 ~ 1.0
convenience init?(rdHexString: String, alpha: CGFloat) {
var value = rdHexString.trimmingCharacters(in: .whitespacesAndNewlines)
value = value.replacingOccurrences(of: "#", with: "")