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 {
/// 页面帧宽度;传 0 时回退为分页入口传入的 pageSize.width
public var frameWidth: CGFloat
/// 页面帧高度;传 0 时回退为分页入口传入的 pageSize.height
public var frameHeight: CGFloat
/// 页面内容内边距,对标 WXRead 的 WRCoreTextLayoutConfig.edgeInsets
public var edgeInsets: UIEdgeInsets
/// 栏数,对标 WXRead 的 numberOfColumns
public var numberOfColumns: Int
/// 栏间距,对标 WXRead 的 columnGap
public var columnGap: CGFloat
/// 是否避免孤行(段落最后一行单独在下一页顶部)
public var avoidOrphans: Bool
/// 是否避免寡行(段落第一行单独在上一页底部)
public var avoidWidows: Bool
/// 是否启用 avoidPageBreakInside 保护(对标 WXRead 的行级回退扫描)
public var avoidPageBreakInsideEnabled: Bool
/// 是否启用连字符断字,对标 WXRead 的 hyphenation
public var hyphenation: Bool
/// 图片最大高度占页面高度的比例
public var imageMaxHeightRatio: CGFloat
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
) {
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
}
/// 默认配置
public static let `default` = RDEPUBTextLayoutConfig()
/// 结合调用方 pageSize 解析后的实际页面尺寸。
public func resolvedFrameSize(fallback pageSize: CGSize) -> CGSize {
CGSize(
width: max(frameWidth > 0 ? frameWidth : pageSize.width, 1),
height: max(frameHeight > 0 ? frameHeight : pageSize.height, 1)
)
}
/// 实际内容区域;对标 WXRead 的 frame + edgeInsets 组合。
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.. 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 富文本导入失败"
}
}
}