feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化

- 实现EPUB阅读器搜索功能及选中注释功能
- 优化CFI模块,修复代码审查发现的11个问题
- 实现大书远距目录跳转与后台补全优化方案
- 优化设置面板与章节运行时联动
- 重构及大量改进优化
This commit is contained in:
shenlei
2026-06-22 20:26:34 +08:00
parent f50495ad91
commit c65c190b71
178 changed files with 11380 additions and 6728 deletions
+483
View File
@@ -0,0 +1,483 @@
# 公开 API 参考手册
> 最后更新:2026-06-18
本文档列出 ReadViewSDK 所有公开 API 的完整签名、参数说明和使用方法。
---
## 1. RDEPUBReaderController — 入口控制器
**文件**`Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift` 及扩展
### 1.1 初始化
```swift
// EPUB
init(
epubURL: URL, // EPUB
configuration: RDEPUBReaderConfiguration = .default, //
persistence: RDEPUBReaderPersistence? = nil, //
dependencies: RDEPUBReaderDependencies? = nil //
)
// TextBook .txt
init(
textBook: RDEPUBTextBook, //
bookIdentifier: String, //
title: String, //
textFileURL: URL? = nil, //
configuration: RDEPUBReaderConfiguration = .default,
persistence: RDEPUBReaderPersistence? = nil,
dependencies: RDEPUBReaderDependencies? = nil
)
```
### 1.2 公开属性
| 属性 | 类型 | 读写 | 说明 |
|------|------|------|------|
| `delegate` | `RDEPUBReaderDelegate?` | 读写 | 委托代理 |
| `configuration` | `RDEPUBReaderConfiguration` | 读写 | 阅读配置,设置后触发重新分页 |
| `currentLocation` | `RDEPUBLocation?` | 只读 | 当前阅读位置 |
| `currentPageNumber` | `Int?` | 只读 | 当前页码(1-based |
| `currentSelection` | `RDEPUBSelection?` | 只读 | 当前文本选区 |
| `highlights` | `[RDEPUBHighlight]` | 只读 | 所有高亮标注 |
| `bookmarks` | `[RDEPUBBookmark]` | 只读 | 所有书签 |
| `annotations` | `[RDEPUBAnnotation]` | 只读 | 合并排序后的统一标注列表 |
| `tableOfContents` | `[EPUBTableOfContentsItem]` | 只读 | 目录树(嵌套结构) |
| `flattenedTableOfContents` | `[RDEPUBReaderTableOfContentsItem]` | 只读 | 扁平化目录列表 |
| `currentTableOfContentsItem` | `RDEPUBReaderTableOfContentsItem?` | 只读 | 当前所在目录项 |
### 1.3 导航方法
```swift
///
func reloadBook()
///
func go(to location: RDEPUBLocation)
/// 1-based
/// - Returns:
@discardableResult
func go(toPageNumber pageNumber: Int, animated: Bool = false) -> Bool
/// EPUBTableOfContentsItem
@discardableResult
func go(toTableOfContentsItem item: EPUBTableOfContentsItem, animated: Bool = true) -> Bool
/// RDEPUBReaderTableOfContentsItem
@discardableResult
func go(toTableOfContentsItem item: RDEPUBReaderTableOfContentsItem, animated: Bool = true) -> Bool
/// href fragment "chapter1.xhtml#section2"
@discardableResult
func go(toTableOfContentsHref href: String, animated: Bool = true) -> Bool
///
@discardableResult
func go(toHighlightID id: String, animated: Bool = true) -> Bool
///
@discardableResult
func go(toBookmarkID id: String, animated: Bool = true) -> Bool
```
### 1.4 高亮方法
```swift
/// 使
/// - Parameters:
/// - selection: nil 使 currentSelection
/// - color: "#F8E16C"
/// - note:
/// - Returns: nil
@discardableResult
func addHighlight(
from selection: RDEPUBSelection? = nil,
color: String = "#F8E16C",
note: String? = nil
) -> RDEPUBHighlight?
/// 线
@discardableResult
func addAnnotation(
from selection: RDEPUBSelection? = nil,
style: RDEPUBHighlightStyle,
color: String = "#F8E16C",
note: String? = nil
) -> RDEPUBHighlight?
///
@discardableResult
func upsertHighlight(_ highlight: RDEPUBHighlight) -> RDEPUBHighlight?
///
@discardableResult
func removeHighlight(id: String) -> RDEPUBHighlight?
///
@discardableResult
func updateHighlightNote(id: String, note: String?) -> RDEPUBHighlight?
///
func removeAllHighlights()
/// ID
func highlight(withID id: String) -> RDEPUBHighlight?
```
### 1.5 书签方法
```swift
/// 使
@discardableResult
func addBookmark(note: String? = nil) -> RDEPUBBookmark?
///
@discardableResult
func toggleBookmark(note: String? = nil) -> RDEPUBBookmark?
///
@discardableResult
func removeBookmark(id: String) -> RDEPUBBookmark?
/// ID
func bookmark(withID id: String) -> RDEPUBBookmark?
```
### 1.6 搜索方法
```swift
///
func search(keyword: String)
///
/// - Returns:
@discardableResult
func searchNext() -> Bool
///
@discardableResult
func searchPrevious() -> Bool
///
func clearSearch()
```
### 1.7 其他方法
```swift
///
func clearSelection()
///
func nativeTextSemanticSummary() -> String?
```
---
## 2. RDEPUBReaderDelegate — 委托协议
**文件**`Sources/RDReaderView/EPUBUI/RDEPUBReaderDelegate.swift`
所有方法均有默认空实现,可按需实现。
```swift
public protocol RDEPUBReaderDelegate: AnyObject {
///
func epubReader(_ reader: UIViewController, didOpen publication: RDEPUBPublication)
///
func epubReader(_ reader: UIViewController, didUpdateLocation location: RDEPUBLocation)
///
func epubReaderDidReachEnd(_ reader: UIViewController)
/// nil
func epubReader(_ reader: UIViewController, didChangeSelection selection: RDEPUBSelection?)
///
func epubReader(_ reader: UIViewController, didUpdateHighlights highlights: [RDEPUBHighlight])
///
func epubReader(_ reader: UIViewController, didUpdateBookmarks bookmarks: [RDEPUBBookmark])
///
func epubReader(_ reader: UIViewController, didUpdateSearchResult result: RDEPUBSearchResult?)
///
func epubReader(_ reader: UIViewController, didChangeCurrentSearchMatch match: RDEPUBSearchMatch?)
///
func epubReader(_ reader: UIViewController, didUpdateCurrentTableOfContentsItem item: RDEPUBReaderTableOfContentsItem?)
///
func epubReader(_ reader: UIViewController, didActivateExternalLink url: URL)
/// false
func epubReader(_ reader: UIViewController, shouldOpenExternalURL url: URL) -> Bool
///
func epubReader(_ reader: UIViewController, didFailWithError error: Error)
///
func epubReader(_ reader: UIViewController, configureTopToolView topToolView: RDEPUBReaderTopToolView)
}
```
---
## 3. RDEPUBReaderPersistence — 持久化协议
**文件**`Sources/RDReaderView/EPUBUI/RDEPUBReaderPersistence.swift`
```swift
public protocol RDEPUBReaderPersistence: AnyObject {
func loadLocation(for bookIdentifier: String) -> RDEPUBLocation?
func saveLocation(_ location: RDEPUBLocation, for bookIdentifier: String)
func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark]
func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String)
func loadHighlights(for bookIdentifier: String) -> [RDEPUBHighlight]
func saveHighlights(_ highlights: [RDEPUBHighlight], for bookIdentifier: String)
func loadReaderSettings() -> RDEPUBReaderSettings?
func saveReaderSettings(_ settings: RDEPUBReaderSettings)
}
```
默认实现 `RDEPUBUserDefaultsPersistence` 使用 UserDefaults 存储:
| 数据 | Key 格式 |
|------|----------|
| 阅读位置 | `ssreader.epub.location.{bookID}` |
| 书签 | `ssreader.epub.bookmarks.{bookID}` |
| 高亮 | `ssreader.epub.highlights.{bookID}` |
| 全局设置 | `ssreader.epub.settings` |
---
## 4. RDEPUBReaderConfiguration — 配置模型
**文件**`Sources/RDReaderView/EPUBUI/Settings/RDEPUBReaderConfiguration.swift`
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `fontSize` | `CGFloat` | `15` | 字体大小(pt |
| `lineHeightMultiple` | `CGFloat` | `1.6` | 行距倍数 |
| `fontChoice` | `RDEPUBReaderFontChoice` | `.system` | 字体选择(system/serif/rounded/monospaced |
| `numberOfColumns` | `Int` | `1` | 每页列数(1 或 2 |
| `columnGap` | `CGFloat` | `20` | 列间距 |
| `displayType` | `RDReaderView.DisplayType` | `.pageCurl` | 翻页模式 |
| `landscapeDualPageEnabled` | `Bool` | `true` | 横屏双页 |
| `showsTableOfContents` | `Bool` | `true` | 显示目录 |
| `allowsHighlights` | `Bool` | `true` | 允许高亮 |
| `showsSettingsPanel` | `Bool` | `true` | 显示设置面板 |
| `reflowableContentInsets` | `UIEdgeInsets` | `(40,16,40,16)` | 重排内容内边距 |
| `fixedContentInset` | `UIEdgeInsets` | `.zero` | 固定布局内边距 |
| `theme` | `RDEPUBReaderTheme` | `.light` | 阅读主题 |
| `darkImageAdjustmentEnabled` | `Bool` | `true` | 暗色模式图片调整 |
| `darkImageBlendRatio` | `CGFloat` | `0.15` | 暗色图片混合比例(0-0.35) |
| `fixedLayoutFit` | `RDEPUBFixedLayoutFit` | `.page` | 固定布局适配方式 |
| `fixedLayoutSpreadMode` | `RDEPUBFixedLayoutSpreadMode` | `.automatic` | 固定布局跨页模式 |
| `textRenderingEngine` | `RDEPUBTextRenderingEngine` | `.dtCoreText` | 文本渲染引擎 |
| `onDemandChapterWindowSize` | `Int` | `3` | 按需加载窗口大小(奇数,3-15) |
| `metadataParsingConcurrency` | `Int` | CPU 核心数 | 后台解析并发数 |
| `jumpSessionPolicy` | `RDEPUBJumpSessionPolicy` | `.default` | 远距跳转策略 |
| `allowedExternalURLSchemes` | `Set<String>` | `["https"]` | 允许的外部 URL 协议 |
| `requiresExternalLinkConfirmation` | `Bool` | `true` | 外部链接需确认 |
| `allowsInspectableWebViews` | `Bool` | `false` | WebView 可调试 |
| `enablesVerboseWebViewLogging` | `Bool` | `false` | WebView 详细日志 |
**计算属性**
- `chapterWindowRadius: Int` — 内存缓存窗口半径(`onDemandChapterWindowSize / 2`
---
## 5. 翻页容器协议
**文件**`Sources/RDReaderView/ReaderView/RDReaderViewProtocols.swift`
### 5.1 RDReaderPageProvider(推荐)
```swift
@objc public protocol RDReaderPageProvider: NSObjectProtocol {
///
func numberOfPages(in readerView: RDReaderView) -> Int
///
/// - Parameters:
/// - index: 0-based
/// - reusableView: nil
func readerView(_ readerView: RDReaderView, viewForPageAt index: Int, reusableView: UIView?) -> UIView
///
@objc optional func pageIdentifier(in readerView: RDReaderView, index: Int) -> String?
///
@objc optional func readerViewTopChrome(_ readerView: RDReaderView) -> UIView?
///
@objc optional func readerViewBottomChrome(_ readerView: RDReaderView) -> UIView?
}
```
### 5.2 RDReaderDelegate
```swift
@objc public protocol RDReaderDelegate: NSObjectProtocol {
///
func pageNum(readerView: RDReaderView, pageNum: Int)
///
@objc optional func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool)
}
```
### 5.3 RDReaderPageNavigating
```swift
public protocol RDReaderPageNavigating: AnyObject {
///
var currentPage: Int { get }
///
func reloadPages()
///
func transition(to page: Int, animated: Bool)
}
```
### 5.4 RDReaderDataSource(遗留)
```swift
@objc public protocol RDReaderDataSource: NSObjectProtocol {
func pageCountOfReaderView(readerView: RDReaderView) -> Int
func pageContentView(readerView: RDReaderView, pageNum: Int, containerView: UIView?) -> UIView
func pageIdentifier(readerView: RDReaderView, pageNum: Int) -> String?
@objc optional func topToolView(readerView: RDReaderView) -> UIView?
@objc optional func bottomToolView(readerView: RDReaderView) -> UIView?
}
```
通过 `RDReaderLegacyDataSourceAdapter` 自动适配到 `RDReaderPageProvider`
---
## 6. 数据模型速查
### 6.1 RDEPUBLocation
```swift
public struct RDEPUBLocation: Codable, Equatable {
public var bookIdentifier: String? //
public var href: String //
public var progression: Double // 0.0-1.0
public var lastProgression: Double? //
public var fragment: String? // ID #section1
public var rangeAnchor: RDEPUBTextRangeAnchor? //
}
```
### 6.2 RDEPUBBookmark
```swift
public struct RDEPUBBookmark: Codable, Equatable, Identifiable {
public let id: String //
public let bookIdentifier: String?
public let location: RDEPUBLocation
public let chapterTitle: String?
public let note: String?
public let createdAt: Date
}
```
### 6.3 RDEPUBHighlight
```swift
public struct RDEPUBHighlight: Codable, Equatable, Identifiable {
public let id: String
public let bookIdentifier: String?
public let location: RDEPUBLocation
public let text: String //
public let style: RDEPUBHighlightStyle // .highlight / .underline
public let color: String //
public let note: String?
public let rangeInfo: String? // CoreText
public let createdAt: Date
public var uiColor: UIColor { ... } //
}
```
### 6.4 RDEPUBAnnotation
```swift
public struct RDEPUBAnnotation: Codable, Equatable, Identifiable {
public let id: String
public let bookIdentifier: String?
public let kind: RDEPUBAnnotationKind // .bookmark / .highlight / .underline
public let location: RDEPUBLocation
public let text: String?
public let rangeInfo: String?
public let chapterTitle: String?
public let createdAt: Date
public var bookmark: RDEPUBBookmark? { ... } // kind == .bookmark
public var highlight: RDEPUBHighlight? { ... } // kind == .highlight/.underline
}
```
### 6.5 RDEPUBSelection
```swift
public struct RDEPUBSelection: Equatable {
public let bookIdentifier: String?
public let location: RDEPUBLocation
public let text: String //
public let rangeInfo: String? // CoreText
public let createdAt: Date
}
```
### 6.6 EPUBPage / EPUBChapterInfo
```swift
public struct EPUBPage: Equatable {
public let spineIndex: Int
public let chapterIndex: Int
public let pageIndexInChapter: Int
public let totalPagesInChapter: Int
public let chapterTitle: String
public let fixedSpread: EPUBFixedSpread?
}
public struct EPUBChapterInfo: Equatable {
public let spineIndex: Int
public let title: String
public let pageCount: Int
}
```
### 6.7 RDEPUBSearchResult / RDEPUBSearchMatch
```swift
public struct RDEPUBSearchResult: Equatable {
public let keyword: String
public let matches: [RDEPUBSearchMatch]
}
public struct RDEPUBSearchMatch: Equatable {
public let spineIndex: Int
public let progression: Double
public let previewText: String
public let rangeAnchor: RDEPUBTextRangeAnchor?
}
```