chore: checkpoint current milestone work

This commit is contained in:
shen
2026-05-22 13:28:53 +08:00
parent 6d196d64e5
commit 5698aeaead
68 changed files with 5694 additions and 3291 deletions
@@ -1,5 +1,15 @@
import UIKit
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 sampleNotes: [String]
}
public struct RDEPUBTextPage: Equatable {
public var absolutePageIndex: Int
public var chapterIndex: Int
@@ -12,6 +22,7 @@ public struct RDEPUBTextPage: Equatable {
public var contentRange: NSRange
public var pageStartOffset: Int
public var pageEndOffset: Int
public var metadata: RDEPUBTextPageMetadata
}
public struct RDEPUBTextChapter: Equatable {
@@ -21,6 +32,7 @@ public struct RDEPUBTextChapter: Equatable {
public var title: String
public var attributedContent: NSAttributedString
public var fragmentOffsets: [String: Int]
public var pageBreakReasons: [RDEPUBTextPageBreakReason]
public var pages: [RDEPUBTextPage]
}
@@ -80,6 +92,8 @@ public struct RDEPUBTextBook: Equatable {
public final class RDEPUBTextBookBuilder {
private let renderer: RDEPUBTextRenderer
public private(set) var lastBuildResourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] = []
public private(set) var lastBuildPaginationDiagnostics: [RDEPUBTextChapterPaginationDiagnostic] = []
public init(renderer: RDEPUBTextRenderer) {
self.renderer = renderer
@@ -97,6 +111,8 @@ public final class RDEPUBTextBookBuilder {
) throws -> RDEPUBTextBook {
var chapters: [RDEPUBTextChapter] = []
var flatPages: [RDEPUBTextPage] = []
lastBuildResourceDiagnostics = []
lastBuildPaginationDiagnostics = []
for (spineIndex, item) in publication.spine.enumerated() where item.linear {
guard item.mediaType.contains("html") || item.mediaType.contains("xhtml"),
@@ -105,12 +121,16 @@ public final class RDEPUBTextBookBuilder {
}
let chapterTitle = resolvedChapterTitle(for: item, toc: publication.tableOfContents)
let normalizedHTML = normalizeHTML(rawHTML)
let rendered = try renderer.renderChapter(
html: normalizedHTML,
let request = RDEPUBTextRendererSupport.makeChapterRenderRequest(
href: item.href,
title: chapterTitle,
rawHTML: rawHTML,
baseURL: parser.fileURL(forRelativePath: item.href)?.deletingLastPathComponent(),
style: style
style: style,
resourceResolver: publication.resourceResolver
)
let rendered = try renderer.renderChapter(request: request)
lastBuildResourceDiagnostics.append(contentsOf: rendered.resourceDiagnostics)
let plainText = rendered.attributedString.string.trimmingCharacters(in: .whitespacesAndNewlines)
if shouldSkipChapter(item: item, text: plainText) {
@@ -119,24 +139,41 @@ public final class RDEPUBTextBookBuilder {
let chapterIndex = chapters.count
let content = NSMutableAttributedString(attributedString: rendered.attributedString)
let pageRanges = content.length > 0 ? content.ss_pageRanges(size: pageSize) : []
let effectivePageRanges = pageRanges.isEmpty && content.length > 0
? [NSRange(location: 0, length: content.length)]
: pageRanges
let layoutFrames = content.length > 0
? content.rd_paginatedFrames(size: pageSize, fragmentOffsets: rendered.fragmentOffsets)
: []
let effectiveFrames = layoutFrames.isEmpty && content.length > 0
? [
RDEPUBTextLayoutFrame(
contentRange: NSRange(location: 0, length: content.length),
breakReason: .chapterEnd,
blockRange: nil,
attachmentRanges: [],
attachmentKinds: [],
trailingFragmentID: nil,
diagnostics: [
"page break: chapterEnd",
"page range: \(NSStringFromRange(NSRange(location: 0, length: content.length)))"
]
)
]
: layoutFrames
let pages = effectivePageRanges.enumerated().map { localPageIndex, range in
RDEPUBTextPage(
let pages = effectiveFrames.enumerated().map { localPageIndex, frame in
let range = frame.contentRange
return RDEPUBTextPage(
absolutePageIndex: flatPages.count + localPageIndex,
chapterIndex: chapterIndex,
spineIndex: spineIndex,
href: item.href,
chapterTitle: chapterTitle,
pageIndexInChapter: localPageIndex,
totalPagesInChapter: effectivePageRanges.count,
totalPagesInChapter: effectiveFrames.count,
content: content.attributedSubstring(from: range),
contentRange: range,
pageStartOffset: range.location,
pageEndOffset: range.location + max(range.length - 1, 0)
pageEndOffset: range.location + max(range.length - 1, 0),
metadata: frame.metadata
)
}
@@ -148,9 +185,25 @@ public final class RDEPUBTextBookBuilder {
title: chapterTitle,
attributedContent: content.copy() as! NSAttributedString,
fragmentOffsets: rendered.fragmentOffsets,
pageBreakReasons: pages.map(\.metadata.breakReason),
pages: pages
)
)
lastBuildPaginationDiagnostics.append(
RDEPUBTextChapterPaginationDiagnostic(
href: item.href,
title: chapterTitle,
pageCount: pages.count,
breakReasons: pages.map(\.metadata.breakReason),
attachmentPageCount: pages.filter { !$0.metadata.attachmentKinds.isEmpty }.count,
blockAdjustedPageCount: pages.filter { $0.metadata.breakReason == .blockBoundary || $0.metadata.breakReason == .attachmentBoundary }.count,
sampleNotes: Array(
pages
.flatMap(\.metadata.diagnostics)
.prefix(4)
)
)
)
flatPages.append(contentsOf: pages)
}
@@ -180,26 +233,4 @@ public final class RDEPUBTextBookBuilder {
}
return false
}
private func normalizeHTML(_ html: String) -> String {
var cleanedHTML = html
let replacements: [(pattern: String, template: String)] = [
(#"<hr\s+lang="zh-CN">分页符</hr>"#, ""),
(#"\r"#, "\n"),
(#"\n+"#, "\n")
]
for replacement in replacements {
if let regex = try? NSRegularExpression(pattern: replacement.pattern, options: [.caseInsensitive]) {
cleanedHTML = regex.stringByReplacingMatches(
in: cleanedHTML,
options: [],
range: NSRange(location: 0, length: cleanedHTML.utf16.count),
withTemplate: replacement.template
)
}
}
return cleanedHTML
}
}
}