case attachment // 附件块(图片、figure 等)
case generic // 通用块(,
- 等)
}
// MARK: - 语义提示枚举
/// 分页语义提示,指导分页引擎在何处/何处不进行分页。
public enum RDEPUBTextSemanticHint: String, Codable, Equatable, CaseIterable {
case avoidPageBreakInside // 禁止在块内分页(如代码块、表格)
case keepWithNext // 与下一段保持同页(如标题)
case pageBreakBefore // 在此元素前强制分页
case pageBreakAfter // 在此元素后强制分页
case pageRelate // 微信读书式跨页关联元素
}
// MARK: - 附件布局方式枚举
/// 富文本附件(图片等)的垂直布局方式。
public enum RDEPUBTextAttachmentPlacement: String, Codable, Equatable {
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) {
self.font = font
self.lineSpacing = lineSpacing
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
// MARK: - 布局配置
/// 分页引擎的布局控制参数。
public struct RDEPUBTextLayoutConfig: Equatable {
/// 是否避免孤行(段落最后一行单独在下一页顶部)
public var avoidOrphans: Bool
/// 是否避免寡行(段落第一行单独在上一页底部)
public var avoidWidows: Bool
/// 是否启用 avoidPageBreakInside 保护(对标 WXRead 的行级回退扫描)
public var avoidPageBreakInsideEnabled: Bool
/// 图片最大高度占页面高度的比例
public var imageMaxHeightRatio: CGFloat
public init(
avoidOrphans: Bool = true,
avoidWidows: Bool = true,
avoidPageBreakInsideEnabled: Bool = true,
imageMaxHeightRatio: CGFloat = 0.85
) {
self.avoidOrphans = avoidOrphans
self.avoidWidows = avoidWidows
self.avoidPageBreakInsideEnabled = avoidPageBreakInsideEnabled
self.imageMaxHeightRatio = imageMaxHeightRatio
}
/// 默认配置
public static let `default` = RDEPUBTextLayoutConfig()
}
// MARK: - CSS 样式表层级
/// 样式表层级类型,用于 CSS 层叠优先级管理。
public enum RDEPUBTextStyleSheetLayerKind: String, CaseIterable, Equatable {
case `default` // 基础重置样式(margin、padding 等)
case replace // 元素替换样式(图片居中、标题分页等)
case dark // 暗色模式覆盖
case epub // EPUB 原始样式表
case user // 用户自定义样式(字号、行距、颜色等)
}
/// 单个 CSS 样式表层,包含层级类型和 CSS 内容。
public struct RDEPUBTextStyleSheetLayer: Equatable {
public var kind: RDEPUBTextStyleSheetLayerKind
public var css: String
public init(kind: RDEPUBTextStyleSheetLayerKind, css: String) {
self.kind = kind
self.css = css
}
}
/// CSS 样式表包,管理多层 CSS 的合并与注入顺序。
///
/// CSS 层叠顺序(从低到高):default → replace → dark → epub → user
public struct RDEPUBTextStyleSheetPackage: Equatable {
public var layers: [RDEPUBTextStyleSheetLayer]
public init(layers: [RDEPUBTextStyleSheetLayer]) {
self.layers = layers
}
/// 合并所有非空层的 CSS,每层添加注释头标记
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")
}
}
// MARK: - 资源引用诊断
/// 资源引用类型(样式表或图片)
public enum RDEPUBTextResourceReferenceKind: String, Equatable {
case stylesheet
case image
}
/// 单个资源引用的诊断信息,用于检测 EPUB 中的资源是否正确引用。
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
}
}
// MARK: - 章节渲染上下文与请求
/// 章节渲染上下文:包含 HTML 源码、样式表和资源诊断信息。
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 init(
href: String,
title: String,
html: String,
baseURL: URL?,
stylesheet: RDEPUBTextStyleSheetPackage,
resourceDiagnostics: [RDEPUBTextResourceReferenceDiagnostic]
) {
self.href = href
self.title = title
self.html = html
self.baseURL = baseURL
self.stylesheet = stylesheet
self.resourceDiagnostics = resourceDiagnostics
}
}
/// 章节渲染请求:打包上下文和渲染样式,传递给 `RDEPUBTextRenderer`。
public struct RDEPUBTextChapterRenderRequest {
public var context: RDEPUBTextChapterContext
public var style: RDEPUBTextRenderStyle
public init(context: RDEPUBTextChapterContext, style: RDEPUBTextRenderStyle) {
self.context = context
self.style = style
}
}
// MARK: - 渲染结果
/// 章节渲染的输出结果,包含富文本、fragment 偏移量和资源诊断。
public struct RDEPUBRenderedChapterContent {
/// 渲染后的富文本
public var attributedString: NSAttributedString
/// fragment ID → 字符偏移量映射
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
}
}
// MARK: - 渲染器协议
/// EPUB 文本渲染器协议,定义 HTML → NSAttributedString 的转换接口。
///
/// 默认实现:`RDEPUBDTCoreTextRenderer`(基于 DTCoreText 库)
public protocol RDEPUBTextRenderer {
/// 渲染单个章节
func renderChapter(
request: RDEPUBTextChapterRenderRequest
) throws -> RDEPUBRenderedChapterContent
/// 便捷方法:直接渲染 HTML 字符串
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))
}
}
// MARK: - 渲染错误
/// EPUB 文本渲染过程中可能出现的错误类型。
public enum RDEPUBTextRenderingError: LocalizedError {
case htmlEncodingFailed // HTML 字符串编码为 Data 失败
case htmlImportFailed // HTML 富文本解析失败
public var errorDescription: String? {
switch self {
case .htmlEncodingFailed:
return "HTML 编码失败"
case .htmlImportFailed:
return "HTML 富文本导入失败"
}
}
}