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
@@ -1,14 +1,8 @@
// RDEPUBParser+TOC.swift
// EPUB
// NCXEPUB 2 Navigation DocumentEPUB 3
// 使 NCX Nav Document退 spine
// 使 TOCTreeBuilder
import Foundation
extension RDEPUBParser {
/// OPF
/// NCX Nav Document退 spine
func parseTOC(from packageDocument: OPFPackageDocument, opfURL: URL) -> [EPUBTableOfContentsItem] {
let opfDirectoryURL = opfURL.deletingLastPathComponent()
@@ -31,7 +25,6 @@ extension RDEPUBParser {
return spine.map { EPUBTableOfContentsItem(title: $0.title, href: $0.href) }
}
/// NCXEPUB 2
func parseNCXDocumentItems(at ncxURL: URL) -> [EPUBTableOfContentsItem] {
guard FileManager.default.fileExists(atPath: ncxURL.path),
let parser = XMLParser(contentsOf: ncxURL) else {
@@ -49,7 +42,6 @@ extension RDEPUBParser {
return delegate.items
}
/// Navigation DocumentEPUB 3
func parseNavDocumentItems(at navURL: URL, baseURL: URL) -> [EPUBTableOfContentsItem] {
guard FileManager.default.fileExists(atPath: navURL.path),
let parser = XMLParser(contentsOf: navURL) else {
@@ -67,7 +59,6 @@ extension RDEPUBParser {
return delegate.items
}
/// href fragment
func normalizeTOCHref(_ href: String, relativeTo baseURL: URL) -> String? {
let components = href.split(separator: "#", maxSplits: 1, omittingEmptySubsequences: false)
let pathPart = components.first.map(String.init) ?? href
@@ -91,13 +82,11 @@ extension RDEPUBParser {
}
}
/// href EPUBTableOfContentsItem
private final class TOCNode {
var title = ""
var href: String?
var children: [TOCNode] = []
///
func asItem() -> EPUBTableOfContentsItem? {
let trimmedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines)
guard let href, !href.isEmpty else {
@@ -111,14 +100,12 @@ private final class TOCNode {
}
}
/// beginNode/endNode
private final class TOCTreeBuilder {
///
private var nodeStack: [TOCNode] = []
///
private var rootNodes: [TOCNode] = []
///
var items: [EPUBTableOfContentsItem] {
rootNodes.compactMap { $0.asItem() }
}
@@ -151,7 +138,6 @@ private final class TOCTreeBuilder {
}
}
/// NCX SAX <navPoint><content><text>
private final class NCXParserDelegate: NSObject, XMLParserDelegate {
private let baseURL: URL
private let hrefNormalizer: (String, URL) -> String?
@@ -216,7 +202,6 @@ private final class NCXParserDelegate: NSObject, XMLParserDelegate {
}
}
/// Navigation Document SAX epub:type="toc" <nav>
private final class NavDocumentParserDelegate: NSObject, XMLParserDelegate {
private let baseURL: URL
private let hrefNormalizer: (String, URL) -> String?
@@ -303,7 +288,6 @@ private final class NavDocumentParserDelegate: NSObject, XMLParserDelegate {
}
}
/// <nav> TOC epub:typetyperole
private func isTOCNav(attributes: [String: String]) -> Bool {
if let epubType = attributes["epub:type"]?.lowercased(), epubType.contains("toc") {
return true