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,71 +1,54 @@
import UIKit
// MARK: -
/// EPUBTextRendering 使 NSAttributedString
/// `RDEPUBTextRendererSupport`
/// `RDEPUBTextLayouter`
public extension NSAttributedString.Key {
/// NSString NSRange
static let rdPageBlockRange = NSAttributedString.Key("com.rdreader.epub.pageBlockRange")
///
static let rdPageBlockIndex = NSAttributedString.Key("com.rdreader.epub.pageBlockIndex")
/// fragment ID
static let rdPageFragmentID = NSAttributedString.Key("com.rdreader.epub.pageFragmentID")
/// /
static let rdPageAttachmentKind = NSAttributedString.Key("com.rdreader.epub.pageAttachmentKind")
/// ///
static let rdPageBlockKind = NSAttributedString.Key("com.rdreader.epub.pageBlockKind")
///
static let rdPageSemanticHints = NSAttributedString.Key("com.rdreader.epub.pageSemanticHints")
/// /线/
static let rdPageAttachmentPlacement = NSAttributedString.Key("com.rdreader.epub.pageAttachmentPlacement")
}
// MARK: -
/// HTML
public enum RDEPUBTextBlockKind: String, Codable, Equatable, CaseIterable {
case paragraph // <p>
case list // <ul>, <ol>, <li>
case table // <table>
case code // <pre>, <code>
case blockquote // <blockquote>
case attachment // figure
case generic // <div>, <h1>-<h6>
case paragraph
case list
case table
case code
case blockquote
case attachment
case generic
}
// MARK: -
/// /
public enum RDEPUBTextSemanticHint: String, Codable, Equatable, CaseIterable {
case avoidPageBreakInside //
case keepWithNext //
case pageBreakBefore //
case pageBreakAfter //
case pageRelate //
case avoidPageBreakInside
case keepWithNext
case pageBreakBefore
case pageBreakAfter
case pageRelate
}
// MARK: -
///
public enum RDEPUBTextAttachmentPlacement: String, Codable, Equatable {
case inline //
case baseline // 线
case centered //
case inline
case baseline
case centered
}
// MARK: -
///
public struct RDEPUBTextRenderStyle {
/// EPUB
public var font: UIFont
/// pt
public var lineSpacing: CGFloat
/// nil 使 EPUB
public var textColor: UIColor?
/// nil
public var backgroundColor: UIColor?
public init(font: UIFont, lineSpacing: CGFloat, textColor: UIColor? = nil, backgroundColor: UIColor? = nil) {
@@ -76,31 +59,28 @@ public struct RDEPUBTextRenderStyle {
}
}
// MARK: -
///
public struct RDEPUBTextLayoutConfig: Equatable {
/// 0 退 pageSize.width
public var frameWidth: CGFloat
/// 0 退 pageSize.height
public var frameHeight: CGFloat
/// WXRead WRCoreTextLayoutConfig.edgeInsets
public var edgeInsets: UIEdgeInsets
/// WXRead numberOfColumns
public var numberOfColumns: Int
/// WXRead columnGap
public var columnGap: CGFloat
///
public var avoidOrphans: Bool
///
public var avoidWidows: Bool
/// avoidPageBreakInside WXRead 退
public var avoidPageBreakInsideEnabled: Bool
/// WXRead hyphenation
public var hyphenation: Bool
///
public var imageMaxHeightRatio: CGFloat
/// pageSize viewport
public var fallbackViewportSize: CGSize
public init(
@@ -129,10 +109,8 @@ public struct RDEPUBTextLayoutConfig: Equatable {
self.fallbackViewportSize = fallbackViewportSize
}
///
public static let `default` = RDEPUBTextLayoutConfig()
/// pageSize
public func resolvedFrameSize(fallback pageSize: CGSize) -> CGSize {
CGSize(
width: max(frameWidth > 0 ? frameWidth : pageSize.width, 1),
@@ -140,13 +118,11 @@ public struct RDEPUBTextLayoutConfig: Equatable {
)
}
/// WXRead frame + edgeInsets
public func contentRect(fallback pageSize: CGSize) -> CGRect {
let size = resolvedFrameSize(fallback: pageSize)
return CGRect(origin: .zero, size: size).inset(by: edgeInsets)
}
///
public func columnRects(fallback pageSize: CGSize) -> [CGRect] {
let rect = contentRect(fallback: pageSize)
let columns = max(1, numberOfColumns)
@@ -161,7 +137,6 @@ public struct RDEPUBTextLayoutConfig: Equatable {
}
}
/// /使
public var cacheSignature: String {
[
String(format: "%.3f", frameWidth),
@@ -183,18 +158,14 @@ public struct RDEPUBTextLayoutConfig: Equatable {
}
}
// MARK: - CSS
/// CSS
public enum RDEPUBTextStyleSheetLayerKind: String, CaseIterable, Equatable {
case `default` // marginpadding
case replace //
case dark //
case epub // EPUB
case user //
case `default`
case replace
case dark
case epub
case user
}
/// CSS CSS
public struct RDEPUBTextStyleSheetLayer: Equatable {
public var kind: RDEPUBTextStyleSheetLayerKind
public var css: String
@@ -205,9 +176,6 @@ public struct RDEPUBTextStyleSheetLayer: Equatable {
}
}
/// CSS CSS
///
/// CSS default replace dark epub user
public struct RDEPUBTextStyleSheetPackage: Equatable {
public var layers: [RDEPUBTextStyleSheetLayer]
@@ -215,7 +183,6 @@ public struct RDEPUBTextStyleSheetPackage: Equatable {
self.layers = layers
}
/// CSS
public var combinedCSS: String {
layers
.filter { !$0.css.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
@@ -226,15 +193,11 @@ public struct RDEPUBTextStyleSheetPackage: Equatable {
}
}
// MARK: -
///
public enum RDEPUBTextResourceReferenceKind: String, Equatable {
case stylesheet
case image
}
/// EPUB
public struct RDEPUBTextResourceReferenceDiagnostic: Equatable {
public var kind: RDEPUBTextResourceReferenceKind
public var chapterHref: String
@@ -260,9 +223,6 @@ public struct RDEPUBTextResourceReferenceDiagnostic: Equatable {
}
}
// MARK: -
/// HTML
public struct RDEPUBTextChapterContext: Equatable {
public var href: String
public var title: String
@@ -270,6 +230,7 @@ public struct RDEPUBTextChapterContext: Equatable {
public var baseURL: URL?
public var stylesheet: RDEPUBTextStyleSheetPackage
public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
public var styleCompatibilityReport: RDEPUBCSSCompatibilityReport
public init(
href: String,
@@ -277,7 +238,8 @@ public struct RDEPUBTextChapterContext: Equatable {
html: String,
baseURL: URL?,
stylesheet: RDEPUBTextStyleSheetPackage,
resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic],
styleCompatibilityReport: RDEPUBCSSCompatibilityReport = RDEPUBCSSCompatibilityReport()
) {
self.href = href
self.title = title
@@ -285,16 +247,16 @@ public struct RDEPUBTextChapterContext: Equatable {
self.baseURL = baseURL
self.stylesheet = stylesheet
self.resourceDiagnostics = resourceDiagnostics
self.styleCompatibilityReport = styleCompatibilityReport
}
}
/// `RDEPUBTextRenderer`
public struct RDEPUBTextChapterRenderRequest {
public var context: RDEPUBTextChapterContext
public var style: RDEPUBTextRenderStyle
/// page size
public var pageSize: CGSize?
/// WXRead
public var layoutConfig: RDEPUBTextLayoutConfig?
public init(
@@ -310,15 +272,12 @@ public struct RDEPUBTextChapterRenderRequest {
}
}
// MARK: -
/// fragment
public struct RDEPUBRenderedChapterContent {
///
public var attributedString: NSAttributedString
/// fragment ID
public var fragmentOffsets: [String: Int]
///
public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
public init(
@@ -332,18 +291,12 @@ public struct RDEPUBRenderedChapterContent {
}
}
// MARK: -
/// EPUB HTML NSAttributedString
///
/// `RDEPUBDTCoreTextRenderer` DTCoreText
public protocol RDEPUBTextRenderer {
///
func renderChapter(
request: RDEPUBTextChapterRenderRequest
) throws -> RDEPUBRenderedChapterContent
/// 便 HTML
func renderChapter(
html: String,
baseURL: URL?,
@@ -351,7 +304,6 @@ public protocol RDEPUBTextRenderer {
) throws -> RDEPUBRenderedChapterContent
}
/// 便
public extension RDEPUBTextRenderer {
func renderChapter(
html: String,
@@ -370,12 +322,9 @@ public extension RDEPUBTextRenderer {
}
}
// MARK: -
/// EPUB
public enum RDEPUBTextRenderingError: LocalizedError {
case htmlEncodingFailed // HTML Data
case htmlImportFailed // HTML
case htmlEncodingFailed
case htmlImportFailed
public var errorDescription: String? {
switch self {