refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import UIKit
|
||||
|
||||
public final class RDEpubPlainTextBookBuilder {
|
||||
private let renderer: RDEPUBTextRenderer
|
||||
private let layoutConfig: RDEPUBTextLayoutConfig
|
||||
|
||||
public init(
|
||||
renderer: RDEPUBTextRenderer = RDEPUBDTCoreTextRenderer(),
|
||||
layoutConfig: RDEPUBTextLayoutConfig = .default
|
||||
) {
|
||||
self.renderer = renderer
|
||||
self.layoutConfig = layoutConfig
|
||||
}
|
||||
|
||||
public func build(
|
||||
textFileURL: URL,
|
||||
pageSize: CGSize,
|
||||
style: RDEPUBTextRenderStyle
|
||||
) throws -> RDEPUBTextBook {
|
||||
let rawText = rd_decodeTextFile(url: textFileURL)
|
||||
let chapterSpecs = splitChapters(from: rawText)
|
||||
|
||||
var chapters: [RDEPUBTextChapter] = []
|
||||
var flatPages: [RDEPUBTextPage] = []
|
||||
|
||||
for (index, spec) in chapterSpecs.enumerated() {
|
||||
let html = wrapTextAsHTML(spec.content)
|
||||
let request = RDEPUBTextChapterRenderRequest(
|
||||
context: RDEPUBTextChapterContext(
|
||||
href: "chapter_\(index).xhtml",
|
||||
title: spec.title ?? "第 \(index + 1) 章",
|
||||
html: html,
|
||||
baseURL: nil,
|
||||
stylesheet: RDEPUBTextStyleSheetPackage(layers: []),
|
||||
resourceDiagnostics: []
|
||||
),
|
||||
style: style,
|
||||
pageSize: pageSize,
|
||||
layoutConfig: layoutConfig
|
||||
)
|
||||
let rendered = try renderer.renderChapter(request: request)
|
||||
|
||||
let content = NSMutableAttributedString(attributedString: rendered.attributedString)
|
||||
let layoutFrames = content.length > 0 ? content.rd_paginatedFrames(size: pageSize, config: layoutConfig) : []
|
||||
let effectiveFrames = layoutFrames.isEmpty && content.length > 0
|
||||
? [
|
||||
RDEPUBTextLayoutFrame(
|
||||
contentRange: NSRange(location: 0, length: content.length),
|
||||
breakReason: .chapterEnd,
|
||||
blockRange: nil,
|
||||
attachmentRanges: [],
|
||||
attachmentKinds: [],
|
||||
blockKinds: [],
|
||||
semanticHints: [],
|
||||
attachmentPlacements: [],
|
||||
trailingFragmentID: nil,
|
||||
diagnostics: [
|
||||
"page break: chapterEnd",
|
||||
"page range: \(NSStringFromRange(NSRange(location: 0, length: content.length)))"
|
||||
]
|
||||
)
|
||||
]
|
||||
: layoutFrames
|
||||
|
||||
let href = "chapter_\(index).xhtml"
|
||||
let chapterAttributedContent = content.copy() as! NSAttributedString
|
||||
let pages = effectiveFrames.enumerated().map { localPageIndex, frame in
|
||||
let range = frame.contentRange
|
||||
return RDEPUBTextPage(
|
||||
absolutePageIndex: flatPages.count + localPageIndex,
|
||||
chapterIndex: index,
|
||||
spineIndex: index,
|
||||
href: href,
|
||||
chapterTitle: spec.title ?? "第 \(index + 1) 章",
|
||||
pageIndexInChapter: localPageIndex,
|
||||
totalPagesInChapter: effectiveFrames.count,
|
||||
chapterContent: chapterAttributedContent,
|
||||
contentRange: range,
|
||||
pageStartOffset: range.location,
|
||||
pageEndOffset: range.location + max(range.length - 1, 0),
|
||||
metadata: frame.metadata
|
||||
)
|
||||
}
|
||||
|
||||
chapters.append(
|
||||
RDEPUBTextChapter(
|
||||
chapterIndex: index,
|
||||
spineIndex: index,
|
||||
href: href,
|
||||
title: spec.title ?? "第 \(index + 1) 章",
|
||||
attributedContent: chapterAttributedContent,
|
||||
fragmentOffsets: [:],
|
||||
cfiMap: nil,
|
||||
pageBreakReasons: pages.map { $0.metadata.breakReason },
|
||||
pages: pages
|
||||
)
|
||||
)
|
||||
flatPages.append(contentsOf: pages)
|
||||
}
|
||||
|
||||
return RDEPUBTextBook(chapters: chapters, pages: flatPages)
|
||||
}
|
||||
|
||||
private struct ChapterSpec {
|
||||
let title: String?
|
||||
let content: String
|
||||
}
|
||||
|
||||
private func splitChapters(from text: String) -> [ChapterSpec] {
|
||||
let pattern = #"^(第[零一二三四五六七八九十百千万\d]+[章节回卷].*)$"#
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: [.anchorsMatchLines]) else {
|
||||
return [ChapterSpec(title: nil, content: text)]
|
||||
}
|
||||
|
||||
let nsText = text as NSString
|
||||
let matches = regex.matches(in: text, range: NSRange(location: 0, length: nsText.length))
|
||||
guard !matches.isEmpty else {
|
||||
return [ChapterSpec(title: nil, content: text)]
|
||||
}
|
||||
|
||||
var specs: [ChapterSpec] = []
|
||||
for (i, match) in matches.enumerated() {
|
||||
let titleRange = match.range(at: 1)
|
||||
let title = nsText.substring(with: titleRange).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let contentStart = titleRange.location + titleRange.length
|
||||
let contentEnd = (i + 1 < matches.count) ? matches[i + 1].range.location : nsText.length
|
||||
let contentRange = NSRange(location: contentStart, length: contentEnd - contentStart)
|
||||
let content = nsText.substring(with: contentRange).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !content.isEmpty {
|
||||
specs.append(ChapterSpec(title: title, content: content))
|
||||
}
|
||||
}
|
||||
|
||||
if specs.isEmpty {
|
||||
return [ChapterSpec(title: nil, content: text)]
|
||||
}
|
||||
return specs
|
||||
}
|
||||
|
||||
private func wrapTextAsHTML(_ text: String) -> String {
|
||||
let paragraphs = text.components(separatedBy: "\n").filter { !$0.isEmpty }
|
||||
let body = paragraphs.map { "<p>\($0)</p>" }.joined(separator: "\n")
|
||||
return "<html><body>\(body)</body></html>"
|
||||
}
|
||||
|
||||
private func rd_decodeTextFile(url: URL) -> String {
|
||||
if let content = try? NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String {
|
||||
return content
|
||||
}
|
||||
if let content = try? NSString(contentsOf: url, encoding: 0x80000632) as String {
|
||||
return content
|
||||
}
|
||||
if let content = try? NSString(contentsOf: url, encoding: 0x80000631) as String {
|
||||
return content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user