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,337 @@
|
||||
import UIKit
|
||||
|
||||
public extension NSAttributedString.Key {
|
||||
|
||||
static let rdPageBlockRange = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockRange")
|
||||
|
||||
static let rdPageBlockIndex = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockIndex")
|
||||
|
||||
static let rdPageFragmentID = NSAttributedString.Key("com.RDEpubReader.epub.pageFragmentID")
|
||||
|
||||
static let rdPageAttachmentKind = NSAttributedString.Key("com.RDEpubReader.epub.pageAttachmentKind")
|
||||
|
||||
static let rdPageBlockKind = NSAttributedString.Key("com.RDEpubReader.epub.pageBlockKind")
|
||||
|
||||
static let rdPageSemanticHints = NSAttributedString.Key("com.RDEpubReader.epub.pageSemanticHints")
|
||||
|
||||
static let rdPageAttachmentPlacement = NSAttributedString.Key("com.RDEpubReader.epub.pageAttachmentPlacement")
|
||||
}
|
||||
|
||||
public enum RDEPUBTextBlockKind: String, Codable, Equatable, CaseIterable {
|
||||
case paragraph
|
||||
case list
|
||||
case table
|
||||
case code
|
||||
case blockquote
|
||||
case attachment
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum RDEPUBTextSemanticHint: String, Codable, Equatable, CaseIterable {
|
||||
case avoidPageBreakInside
|
||||
case keepWithNext
|
||||
case pageBreakBefore
|
||||
case pageBreakAfter
|
||||
case pageRelate
|
||||
}
|
||||
|
||||
public enum RDEPUBTextAttachmentPlacement: String, Codable, Equatable {
|
||||
case inline
|
||||
case baseline
|
||||
case centered
|
||||
}
|
||||
|
||||
public struct RDEPUBTextRenderStyle {
|
||||
|
||||
public var font: UIFont
|
||||
|
||||
public var lineSpacing: CGFloat
|
||||
|
||||
public var textColor: UIColor?
|
||||
|
||||
public var backgroundColor: UIColor?
|
||||
|
||||
public init(font: UIFont, lineSpacing: CGFloat, textColor: UIColor? = nil, backgroundColor: UIColor? = nil) {
|
||||
self.font = font
|
||||
self.lineSpacing = lineSpacing
|
||||
self.textColor = textColor
|
||||
self.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDEPUBTextLayoutConfig: Equatable {
|
||||
|
||||
public var frameWidth: CGFloat
|
||||
|
||||
public var frameHeight: CGFloat
|
||||
|
||||
public var edgeInsets: UIEdgeInsets
|
||||
|
||||
public var numberOfColumns: Int
|
||||
|
||||
public var columnGap: CGFloat
|
||||
|
||||
public var avoidOrphans: Bool
|
||||
|
||||
public var avoidWidows: Bool
|
||||
|
||||
public var avoidPageBreakInsideEnabled: Bool
|
||||
|
||||
public var hyphenation: Bool
|
||||
|
||||
public var imageMaxHeightRatio: CGFloat
|
||||
|
||||
public var fallbackViewportSize: CGSize
|
||||
|
||||
public init(
|
||||
frameWidth: CGFloat = 0,
|
||||
frameHeight: CGFloat = 0,
|
||||
edgeInsets: UIEdgeInsets = .zero,
|
||||
numberOfColumns: Int = 1,
|
||||
columnGap: CGFloat = 20,
|
||||
avoidOrphans: Bool = true,
|
||||
avoidWidows: Bool = true,
|
||||
avoidPageBreakInsideEnabled: Bool = true,
|
||||
hyphenation: Bool = true,
|
||||
imageMaxHeightRatio: CGFloat = 0.85,
|
||||
fallbackViewportSize: CGSize = CGSize(width: 375, height: 667)
|
||||
) {
|
||||
self.frameWidth = frameWidth
|
||||
self.frameHeight = frameHeight
|
||||
self.edgeInsets = edgeInsets
|
||||
self.numberOfColumns = max(1, numberOfColumns)
|
||||
self.columnGap = max(0, columnGap)
|
||||
self.avoidOrphans = avoidOrphans
|
||||
self.avoidWidows = avoidWidows
|
||||
self.avoidPageBreakInsideEnabled = avoidPageBreakInsideEnabled
|
||||
self.hyphenation = hyphenation
|
||||
self.imageMaxHeightRatio = imageMaxHeightRatio
|
||||
self.fallbackViewportSize = fallbackViewportSize
|
||||
}
|
||||
|
||||
public static let `default` = RDEPUBTextLayoutConfig()
|
||||
|
||||
public func resolvedFrameSize(fallback pageSize: CGSize) -> CGSize {
|
||||
CGSize(
|
||||
width: max(frameWidth > 0 ? frameWidth : pageSize.width, 1),
|
||||
height: max(frameHeight > 0 ? frameHeight : pageSize.height, 1)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
guard columns > 1 else { return [rect] }
|
||||
|
||||
let totalGap = CGFloat(columns - 1) * columnGap
|
||||
let columnWidth = max((rect.width - totalGap) / CGFloat(columns), 1)
|
||||
|
||||
return (0..<columns).map { index in
|
||||
let originX = rect.minX + CGFloat(index) * (columnWidth + columnGap)
|
||||
return CGRect(x: originX, y: rect.minY, width: columnWidth, height: rect.height)
|
||||
}
|
||||
}
|
||||
|
||||
public var cacheSignature: String {
|
||||
[
|
||||
String(format: "%.3f", frameWidth),
|
||||
String(format: "%.3f", frameHeight),
|
||||
String(format: "%.3f", edgeInsets.top),
|
||||
String(format: "%.3f", edgeInsets.left),
|
||||
String(format: "%.3f", edgeInsets.bottom),
|
||||
String(format: "%.3f", edgeInsets.right),
|
||||
String(numberOfColumns),
|
||||
String(format: "%.3f", columnGap),
|
||||
avoidOrphans ? "1" : "0",
|
||||
avoidWidows ? "1" : "0",
|
||||
avoidPageBreakInsideEnabled ? "1" : "0",
|
||||
hyphenation ? "1" : "0",
|
||||
String(format: "%.3f", imageMaxHeightRatio),
|
||||
String(format: "%.3f", fallbackViewportSize.width),
|
||||
String(format: "%.3f", fallbackViewportSize.height)
|
||||
].joined(separator: "|")
|
||||
}
|
||||
}
|
||||
|
||||
public enum RDEPUBTextStyleSheetLayerKind: String, CaseIterable, Equatable {
|
||||
case `default`
|
||||
case replace
|
||||
case dark
|
||||
case epub
|
||||
case user
|
||||
}
|
||||
|
||||
public struct RDEPUBTextStyleSheetLayer: Equatable {
|
||||
public var kind: RDEPUBTextStyleSheetLayerKind
|
||||
public var css: String
|
||||
|
||||
public init(kind: RDEPUBTextStyleSheetLayerKind, css: String) {
|
||||
self.kind = kind
|
||||
self.css = css
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDEPUBTextStyleSheetPackage: Equatable {
|
||||
public var layers: [RDEPUBTextStyleSheetLayer]
|
||||
|
||||
public init(layers: [RDEPUBTextStyleSheetLayer]) {
|
||||
self.layers = layers
|
||||
}
|
||||
|
||||
public var combinedCSS: String {
|
||||
layers
|
||||
.filter { !$0.css.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||
.map { layer in
|
||||
"/* \(layer.kind.rawValue) */\n\(layer.css)"
|
||||
}
|
||||
.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
public enum RDEPUBTextResourceReferenceKind: String, Equatable {
|
||||
case stylesheet
|
||||
case image
|
||||
}
|
||||
|
||||
public struct RDEPUBTextResourceReferenceDiagnostic: Equatable {
|
||||
public var kind: RDEPUBTextResourceReferenceKind
|
||||
public var chapterHref: String
|
||||
public var originalReference: String
|
||||
public var normalizedHref: String?
|
||||
public var resolvedFileURL: URL?
|
||||
public var existsOnDisk: Bool
|
||||
|
||||
public init(
|
||||
kind: RDEPUBTextResourceReferenceKind,
|
||||
chapterHref: String,
|
||||
originalReference: String,
|
||||
normalizedHref: String?,
|
||||
resolvedFileURL: URL?,
|
||||
existsOnDisk: Bool
|
||||
) {
|
||||
self.kind = kind
|
||||
self.chapterHref = chapterHref
|
||||
self.originalReference = originalReference
|
||||
self.normalizedHref = normalizedHref
|
||||
self.resolvedFileURL = resolvedFileURL
|
||||
self.existsOnDisk = existsOnDisk
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDEPUBTextChapterContext: Equatable {
|
||||
public var href: String
|
||||
public var title: String
|
||||
public var html: String
|
||||
public var baseURL: URL?
|
||||
public var stylesheet: RDEPUBTextStyleSheetPackage
|
||||
public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
|
||||
public var styleCompatibilityReport: RDEPUBCSSCompatibilityReport
|
||||
|
||||
public init(
|
||||
href: String,
|
||||
title: String,
|
||||
html: String,
|
||||
baseURL: URL?,
|
||||
stylesheet: RDEPUBTextStyleSheetPackage,
|
||||
resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic],
|
||||
styleCompatibilityReport: RDEPUBCSSCompatibilityReport = RDEPUBCSSCompatibilityReport()
|
||||
) {
|
||||
self.href = href
|
||||
self.title = title
|
||||
self.html = html
|
||||
self.baseURL = baseURL
|
||||
self.stylesheet = stylesheet
|
||||
self.resourceDiagnostics = resourceDiagnostics
|
||||
self.styleCompatibilityReport = styleCompatibilityReport
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDEPUBTextChapterRenderRequest {
|
||||
public var context: RDEPUBTextChapterContext
|
||||
public var style: RDEPUBTextRenderStyle
|
||||
|
||||
public var pageSize: CGSize?
|
||||
|
||||
public var layoutConfig: RDEPUBTextLayoutConfig?
|
||||
|
||||
public init(
|
||||
context: RDEPUBTextChapterContext,
|
||||
style: RDEPUBTextRenderStyle,
|
||||
pageSize: CGSize? = nil,
|
||||
layoutConfig: RDEPUBTextLayoutConfig? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.style = style
|
||||
self.pageSize = pageSize
|
||||
self.layoutConfig = layoutConfig
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDEPUBRenderedChapterContent {
|
||||
|
||||
public var attributedString: NSAttributedString
|
||||
|
||||
public var fragmentOffsets: [String: Int]
|
||||
|
||||
public var resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
|
||||
|
||||
public init(
|
||||
attributedString: NSAttributedString,
|
||||
fragmentOffsets: [String: Int],
|
||||
resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic] = []
|
||||
) {
|
||||
self.attributedString = attributedString
|
||||
self.fragmentOffsets = fragmentOffsets
|
||||
self.resourceDiagnostics = resourceDiagnostics
|
||||
}
|
||||
}
|
||||
|
||||
public protocol RDEPUBTextRenderer {
|
||||
|
||||
func renderChapter(
|
||||
request: RDEPUBTextChapterRenderRequest
|
||||
) throws -> RDEPUBRenderedChapterContent
|
||||
|
||||
func renderChapter(
|
||||
html: String,
|
||||
baseURL: URL?,
|
||||
style: RDEPUBTextRenderStyle
|
||||
) throws -> RDEPUBRenderedChapterContent
|
||||
}
|
||||
|
||||
public extension RDEPUBTextRenderer {
|
||||
func renderChapter(
|
||||
html: String,
|
||||
baseURL: URL?,
|
||||
style: RDEPUBTextRenderStyle
|
||||
) throws -> RDEPUBRenderedChapterContent {
|
||||
let context = RDEPUBTextChapterContext(
|
||||
href: "",
|
||||
title: "",
|
||||
html: html,
|
||||
baseURL: baseURL,
|
||||
stylesheet: RDEPUBTextStyleSheetPackage(layers: []),
|
||||
resourceDiagnostics: []
|
||||
)
|
||||
return try renderChapter(request: RDEPUBTextChapterRenderRequest(context: context, style: style))
|
||||
}
|
||||
}
|
||||
|
||||
public enum RDEPUBTextRenderingError: LocalizedError {
|
||||
case htmlEncodingFailed
|
||||
case htmlImportFailed
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .htmlEncodingFailed:
|
||||
return "HTML 编码失败"
|
||||
case .htmlImportFailed:
|
||||
return "HTML 富文本导入失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user