refactor: 添加中文注释 + 优化模块结构

- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
This commit is contained in:
shen
2026-05-25 10:19:14 +08:00
parent 5af90c1148
commit 54798ba578
88 changed files with 2492 additions and 4469 deletions
@@ -1,48 +1,80 @@
import UIKit
// MARK: -
///
/// /
public struct RDEPUBTextChapterPaginationDiagnostic: Equatable {
public var href: String
public var title: String
public var pageCount: Int
///
public var breakReasons: [RDEPUBTextPageBreakReason]
///
public var attachmentPageCount: Int
/// /
public var blockAdjustedPageCount: Int
public var blockKinds: [RDEPUBTextBlockKind]
public var semanticHints: [RDEPUBTextSemanticHint]
public var attachmentPlacements: [RDEPUBTextAttachmentPlacement]
/// 4
public var sampleNotes: [String]
}
// MARK: -
///
///
/// `RDEPUBTextPage` `RDEPUBTextLayoutFrame`
/// `contentRange`
public struct RDEPUBTextPage: Equatable {
/// 0
public var absolutePageIndex: Int
public var chapterIndex: Int
public var spineIndex: Int
public var href: String
public var chapterTitle: String
/// 0
public var pageIndexInChapter: Int
public var totalPagesInChapter: Int
///
public var chapterContent: NSAttributedString
///
public var content: NSAttributedString
///
public var contentRange: NSRange
public var pageStartOffset: Int
public var pageEndOffset: Int
///
public var metadata: RDEPUBTextPageMetadata
}
// MARK: -
///
public struct RDEPUBTextChapter: Equatable {
public var chapterIndex: Int
public var spineIndex: Int
public var href: String
public var title: String
///
public var attributedContent: NSAttributedString
/// fragment ID
public var fragmentOffsets: [String: Int]
public var pageBreakReasons: [RDEPUBTextPageBreakReason]
public var pages: [RDEPUBTextPage]
}
// MARK: -
/// EPUB
///
/// EPUBTextRendering `RDEPUBTextBookBuilder.build()`
/// `chapterData(for:)` `chapterData(atChapterIndex:)` `RDEPUBChapterData`
public struct RDEPUBTextBook {
public var chapters: [RDEPUBTextChapter]
public var pages: [RDEPUBTextPage]
/// fileIndex/row/column
public let indexTable: RDEPUBTextIndexTable
public init(chapters: [RDEPUBTextChapter], pages: [RDEPUBTextPage]) {
@@ -55,16 +87,19 @@ public struct RDEPUBTextBook {
lhs.chapters == rhs.chapters && lhs.pages == rhs.pages
}
/// href 访
public func chapterData(for href: String) -> RDEPUBChapterData? {
guard let chapter = chapters.first(where: { $0.href == href }) else { return nil }
return RDEPUBChapterData(chapter: chapter, indexTable: indexTable)
}
/// 访
public func chapterData(atChapterIndex index: Int) -> RDEPUBChapterData? {
guard chapters.indices.contains(index) else { return nil }
return RDEPUBChapterData(chapter: chapters[index], indexTable: indexTable)
}
/// 1
public func page(at pageNumber: Int) -> RDEPUBTextPage? {
guard pageNumber > 0, pages.indices.contains(pageNumber - 1) else {
return nil
@@ -72,6 +107,12 @@ public struct RDEPUBTextBook {
return pages[pageNumber - 1]
}
/// 1
///
///
/// 1. rangeAnchor
/// 2. fragment ID
/// 3. navigationProgression 退
public func pageNumber(for location: RDEPUBLocation, resolver: RDEPUBResourceResolver, bookIdentifier: String?) -> Int? {
guard let normalizedLocation = resolver.normalizedLocation(location, bookIdentifier: bookIdentifier),
let chapter = chapters.first(where: { $0.href == normalizedLocation.href }) else {
@@ -98,6 +139,7 @@ public struct RDEPUBTextBook {
return chapter.pages.last.map { $0.absolutePageIndex + 1 }
}
/// RDEPUBLocation
public func location(forPageNumber pageNumber: Int, bookIdentifier: String?) -> RDEPUBLocation? {
guard let page = page(at: pageNumber),
let chapter = chapters.first(where: { $0.chapterIndex == page.chapterIndex }) else {
@@ -121,14 +163,29 @@ public struct RDEPUBTextBook {
}
}
// MARK: -
/// EPUB publication
///
/// spine HTML / RDEPUBTextBook
///
///
/// - WXRead
/// - /
/// -
/// -
public final class RDEPUBTextBookBuilder {
private let renderer: RDEPUBTextRenderer
private let cache: RDEPUBTextBookCache?
private let sampler: RDEPUBTextPerformanceSampler
///
public private(set) var lastBuildResourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] = []
///
public private(set) var lastBuildPaginationDiagnostics: [RDEPUBTextChapterPaginationDiagnostic] = []
///
public private(set) var lastBuildPerformanceSamples: [RDEPUBTextPerformanceSample] = []
/// /
public private(set) var lastBuildCacheStats: (hits: Int, misses: Int) = (0, 0)
public init(renderer: RDEPUBTextRenderer, cache: RDEPUBTextBookCache? = nil) {
@@ -137,10 +194,12 @@ public final class RDEPUBTextBookBuilder {
self.sampler = RDEPUBTextPerformanceSampler()
}
/// 使 DTCoreText
public convenience init() {
self.init(renderer: RDEPUBDTCoreTextRenderer())
}
/// Phase 7
public func phase7SemanticSummary(title: String? = nil) -> String? {
guard !lastBuildPaginationDiagnostics.isEmpty else { return nil }
@@ -164,6 +223,16 @@ public final class RDEPUBTextBookBuilder {
return parts.joined(separator: " · ")
}
/// EPUB publication
///
///
/// 1.
/// 2. spine 线 HTML
/// 3. HTML NSAttributedString
/// 4. /
/// 5. 使 CoreText
/// 6.
/// 7. RDEPUBTextBook
public func build(
parser: RDEPUBParser,
publication: RDEPUBPublication,
@@ -180,7 +249,7 @@ public final class RDEPUBTextBookBuilder {
let buildStart = CFAbsoluteTimeGetCurrent()
// Cache lookup WXRead style: load per-chapter page ranges
// WXRead
let bookID = publication.metadata.identifier ?? publication.metadata.title
let cacheKey = makeCacheKey(bookID: bookID, pageSize: pageSize, style: style)
let cachedPagination = cacheKey.flatMap { cache?.load(key: $0) }
@@ -191,6 +260,7 @@ public final class RDEPUBTextBookBuilder {
continue
}
//
let chapterTitle = resolvedChapterTitle(for: item, toc: publication.tableOfContents)
let request = RDEPUBTextRendererSupport.makeChapterRenderRequest(
href: item.href,
@@ -201,6 +271,7 @@ public final class RDEPUBTextBookBuilder {
resourceResolver: publication.resourceResolver
)
// HTML NSAttributedString
let renderStart = CFAbsoluteTimeGetCurrent()
let rendered = try renderer.renderChapter(request: request)
let renderDuration = CFAbsoluteTimeGetCurrent() - renderStart
@@ -211,6 +282,7 @@ public final class RDEPUBTextBookBuilder {
if item.href.lowercased().contains("cover") {
print("[EPUB][Cover] rendered href=\(item.href) textLength=\(plainText.count) attrLength=\(rendered.attributedString.length) attachments=\(attachmentCount(in: rendered.attributedString))")
}
// /
if shouldSkipChapter(item: item, content: rendered.attributedString, text: plainText) {
if item.href.lowercased().contains("cover") {
print("[EPUB][Cover] skipped href=\(item.href)")
@@ -221,10 +293,12 @@ public final class RDEPUBTextBookBuilder {
let chapterIndex = chapters.count
let content = NSMutableAttributedString(attributedString: rendered.attributedString)
//
let paginateStart = CFAbsoluteTimeGetCurrent()
let layoutFrames: [RDEPUBTextLayoutFrame]
let isCacheHit: Bool
if isAttachmentOnlyCoverChapter(item: item, content: content, plainText: plainText) {
//
layoutFrames = [
RDEPUBTextLayoutFrame(
contentRange: NSRange(location: 0, length: content.length),
@@ -245,7 +319,7 @@ public final class RDEPUBTextBookBuilder {
]
isCacheHit = false
} else if let cached = cachedPagination?[item.href] {
// Cache hit: use cached page ranges, skip rd_paginatedFrames
// 使 CoreText
layoutFrames = cached.pageRanges.enumerated().map { idx, range in
let breakReason = idx < cached.breakReasons.count ? cached.breakReasons[idx] : .frameLimit
return RDEPUBTextLayoutFrame(
@@ -263,6 +337,7 @@ public final class RDEPUBTextBookBuilder {
}
isCacheHit = true
} else {
// CoreText
layoutFrames = content.length > 0
? content.rd_paginatedFrames(size: pageSize, fragmentOffsets: rendered.fragmentOffsets)
: []
@@ -270,6 +345,7 @@ public final class RDEPUBTextBookBuilder {
}
let paginateDuration = CFAbsoluteTimeGetCurrent() - paginateStart
//
let normalizedFrames = normalizeTrailingFrames(
layoutFrames,
content: content,
@@ -298,6 +374,7 @@ public final class RDEPUBTextBookBuilder {
print("[EPUB][Cover] paginated href=\(item.href) pages=\(effectiveFrames.count) firstRange=\(effectiveFrames.first.map { NSStringFromRange($0.contentRange) } ?? "none")")
}
//
sampler.record(RDEPUBTextPerformanceSample(
chapterHref: item.href,
renderDuration: renderDuration,
@@ -312,6 +389,7 @@ public final class RDEPUBTextBookBuilder {
lastBuildCacheStats.misses += 1
}
//
let chapterAttributedContent = content.copy() as! NSAttributedString
let pages = effectiveFrames.enumerated().map { localPageIndex, frame in
let range = frame.contentRange
@@ -369,6 +447,7 @@ public final class RDEPUBTextBookBuilder {
sampler.totalBuildDuration = CFAbsoluteTimeGetCurrent() - buildStart
//
if let cacheKey {
let paginationCache = chapters.map { chapter in
let pageRanges = chapter.pages.map(\.contentRange)
@@ -390,6 +469,9 @@ public final class RDEPUBTextBookBuilder {
return book
}
// MARK: -
/// 退 spine item title href
private func resolvedChapterTitle(for item: RDEPUBSpineItem, toc: [EPUBTableOfContentsItem]) -> String {
if let title = flattenedTOCItems(from: toc).first(where: { tocItem in
tocItem.href.components(separatedBy: "#").first == item.href
@@ -400,12 +482,16 @@ public final class RDEPUBTextBookBuilder {
return trimmedTitle.isEmpty ? item.href : trimmedTitle
}
///
private func flattenedTOCItems(from items: [EPUBTableOfContentsItem]) -> [EPUBTableOfContentsItem] {
items.flatMap { item in
[item] + flattenedTOCItems(from: item.children)
}
}
// MARK: -
/// /
private func shouldSkipChapter(item: RDEPUBSpineItem, content: NSAttributedString, text: String) -> Bool {
let lowercasedHref = item.href.lowercased()
var hasAttachment = false
@@ -422,6 +508,9 @@ public final class RDEPUBTextBookBuilder {
return false
}
// MARK: -
///
private func attachmentCount(in content: NSAttributedString) -> Int {
guard content.length > 0 else { return 0 }
var count = 0
@@ -433,6 +522,7 @@ public final class RDEPUBTextBookBuilder {
return count
}
/// NSRange
private func attachmentRanges(in content: NSAttributedString) -> [NSRange] {
guard content.length > 0 else { return [] }
var ranges: [NSRange] = []
@@ -444,6 +534,9 @@ public final class RDEPUBTextBookBuilder {
return ranges
}
// MARK: -
/// href cover
private func isAttachmentOnlyCoverChapter(
item: RDEPUBSpineItem,
content: NSAttributedString,
@@ -455,6 +548,11 @@ public final class RDEPUBTextBookBuilder {
return attachmentCount(in: content) > 0 && trimmed.count <= 1
}
// MARK: -
///
/// 1.
/// 2. 2
private func normalizeTrailingFrames(
_ frames: [RDEPUBTextLayoutFrame],
content: NSAttributedString,
@@ -464,6 +562,7 @@ public final class RDEPUBTextBookBuilder {
var normalized = frames
//
while let lastFrame = normalized.last,
shouldDropWhitespaceOnlyTrailingFrame(lastFrame, in: content) {
normalized.removeLast()
@@ -476,6 +575,7 @@ public final class RDEPUBTextBookBuilder {
}
}
//
guard normalized.count > 1,
let lastFrame = normalized.last,
let previousFrame = normalized.dropLast().last,
@@ -489,6 +589,7 @@ public final class RDEPUBTextBookBuilder {
return normalized
}
///
private func shouldDropWhitespaceOnlyTrailingFrame(
_ frame: RDEPUBTextLayoutFrame,
in content: NSAttributedString
@@ -500,6 +601,8 @@ public final class RDEPUBTextBookBuilder {
return visibleCharacterCount(in: content, range: frame.contentRange) == 0
}
///
/// 2 8 12
private func shouldMergeShortTrailingFrame(
_ trailingFrame: RDEPUBTextLayoutFrame,
previousFrame: RDEPUBTextLayoutFrame,
@@ -522,6 +625,7 @@ public final class RDEPUBTextBookBuilder {
return previousVisibleCount >= max(visibleCount * 8, 12)
}
///
private func mergeTrailingFrame(
_ previousFrame: RDEPUBTextLayoutFrame,
with trailingFrame: RDEPUBTextLayoutFrame
@@ -547,6 +651,7 @@ public final class RDEPUBTextBookBuilder {
)
}
///
private func visibleCharacterCount(
in content: NSAttributedString,
range: NSRange
@@ -560,6 +665,9 @@ public final class RDEPUBTextBookBuilder {
return filteredScalars.count
}
// MARK: -
///
private func uniqueValues<T: Equatable>(from values: [T]) -> [T] {
values.reduce(into: [T]()) { result, value in
if !result.contains(value) {
@@ -568,6 +676,7 @@ public final class RDEPUBTextBookBuilder {
}
}
/// NSRange
private func uniqueRanges(from ranges: [NSRange]) -> [NSRange] {
ranges.reduce(into: [NSRange]()) { result, value in
if !result.contains(value) {
@@ -576,6 +685,7 @@ public final class RDEPUBTextBookBuilder {
}
}
///
private func attachmentCount(in content: NSAttributedString, range: NSRange) -> Int {
guard content.length > 0, range.length > 0 else { return 0 }
var count = 0
@@ -587,6 +697,9 @@ public final class RDEPUBTextBookBuilder {
return count
}
// MARK: -
/// ID SHA256
private func makeCacheKey(
bookID: String,
pageSize: CGSize,