import UIKit // MARK: - 文本渲染引擎 /// EPUB 文本渲染引擎枚举 /// 定义了当前支持的文本渲染方式,未来可扩展为多引擎选择 public enum RDEPUBTextRenderingEngine: Equatable { /// 基于 DTCoreText 的渲染引擎,将 HTML/CSS 转换为 NSAttributedString case dtCoreText } // MARK: - 阅读字体 /// 阅读器内置字体选项。 /// 第一版优先使用系统授权字体,后续可扩展为 bundle 内置字体注册。 public enum RDEPUBReaderFontChoice: String, Codable, CaseIterable, Equatable { /// 系统默认无衬线字体 case system /// 系统衬线字体 case serif /// 系统圆体字体 case rounded /// 系统等宽字体 case monospaced public var displayName: String { switch self { case .system: return "系统" case .serif: return "宋体" case .rounded: return "圆体" case .monospaced: return "等宽" } } public func font(ofSize size: CGFloat) -> UIFont { switch self { case .system: return UIFont.systemFont(ofSize: size) case .serif: let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) .withDesign(.serif) ?? UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) return UIFont(descriptor: descriptor, size: size) case .rounded: let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) .withDesign(.rounded) ?? UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) return UIFont(descriptor: descriptor, size: size) case .monospaced: return UIFont.monospacedSystemFont(ofSize: size, weight: .regular) } } } // MARK: - 阅读器配置 /// EPUB 阅读器的完整配置结构体 /// 作为 EPUBUI 层的入口配置,控制阅读器的外观、行为和功能开关 /// 调用方在创建 RDEPUBReaderController 前可自定义此配置 public struct RDEPUBReaderConfiguration: Equatable { // MARK: 阅读排版参数 /// 正文字号(单位:pt),默认 15 public var fontSize: CGFloat /// 行距倍数,默认 1.6(即行距为字号的 1.6 倍) public var lineHeightMultiple: CGFloat /// 正文字体,默认系统字体 public var fontChoice: RDEPUBReaderFontChoice /// 栏数,默认单栏 public var numberOfColumns: Int /// 栏间距,默认 20pt public var columnGap: CGFloat /// 翻页显示模式:.pageCurl(仿真翻页)或 .scroll(滚动阅读) public var displayType: RDReaderView.DisplayType /// 横屏时是否启用双页显示,默认开启 public var landscapeDualPageEnabled: Bool // MARK: UI 功能开关 /// 是否显示目录入口,默认开启 public var showsTableOfContents: Bool /// 是否允许文本高亮功能,默认开启 public var allowsHighlights: Bool /// 是否显示设置面板入口,默认开启 public var showsSettingsPanel: Bool // MARK: 内容内边距 /// 流式排版(reflowable)内容的内边距,上下 40pt、左右 16pt public var reflowableContentInsets: UIEdgeInsets /// 固定布局(fixed layout)内容的内边距,默认为零 public var fixedContentInset: UIEdgeInsets // MARK: 主题与布局 /// 当前阅读主题(浅色/深色/护眼等),默认 .light public var theme: RDEPUBReaderTheme /// 暗色主题下是否柔化正文图片,降低夜间阅读刺眼感 public var darkImageAdjustmentEnabled: Bool /// 暗色主题图片柔化混合比例,0 表示不处理,推荐 0.12 ~ 0.2 public var darkImageBlendRatio: CGFloat /// 固定布局的适配模式:按页适配或按宽度适配 public var fixedLayoutFit: RDEPUBFixedLayoutFit /// 固定布局的跨页模式:自动/单页/双页 public var fixedLayoutSpreadMode: RDEPUBFixedLayoutSpreadMode /// 文本渲染引擎,默认使用 DTCoreText public var textRenderingEngine: RDEPUBTextRenderingEngine /// 章节按需加载窗口大小(总章节数,包含当前章),默认 3,范围 3...15,偶数自动向上取奇 public var onDemandChapterWindowSize: Int /// 后台元数据解析并发数,默认为 CPU 核心数。 /// 若 profiling 显示 renderTotalMs ≈ wallClockMs(渲染受限),维持核心数即可; /// 若 writeTotalMs 占比显著(I/O 等待),可试探 cpuCount * 1.25~1.5 以填充 I/O 等待间隙。 public var metadataParsingConcurrency: Int // MARK: 安全策略 /// 允许直接打开的外部 URL scheme 集合,默认仅允许 https public var allowedExternalURLSchemes: Set /// 打开外部链接前是否需要用户确认,默认 true public var requiresExternalLinkConfirmation: Bool /// 是否允许正文和离屏分页 WebView 开启 inspectable,默认 false public var allowsInspectableWebViews: Bool /// 是否允许输出完整 WebView 消息体日志,默认 false public var enablesVerboseWebViewLogging: Bool // MARK: 初始化 /// 创建阅读器配置,所有参数均提供合理的默认值 /// - Parameters: /// - fontSize: 正文字号,默认 15pt /// - lineHeightMultiple: 行距倍数,默认 1.6 /// - fontChoice: 正文字体,默认系统字体 /// - displayType: 翻页模式,默认 .pageCurl /// - landscapeDualPageEnabled: 横屏双页,默认 true /// - showsTableOfContents: 显示目录入口,默认 true /// - allowsHighlights: 允许高亮,默认 true /// - showsSettingsPanel: 显示设置面板,默认 true /// - reflowableContentInsets: 流式排版内边距 /// - fixedContentInset: 固定布局内边距 /// - theme: 阅读主题,默认 .light /// - darkImageAdjustmentEnabled: 暗色主题下是否柔化正文图片 /// - darkImageBlendRatio: 暗色主题图片柔化混合比例 /// - fixedLayoutFit: 固定布局适配模式 /// - fixedLayoutSpreadMode: 固定布局跨页模式 /// - textRenderingEngine: 文本渲染引擎 /// - onDemandChapterWindowSize: 章节按需加载窗口大小(总章节数 3...15,偶数自动向上取奇) /// - metadataParsingConcurrency: 后台元数据解析并发数,默认 CPU 核心数 /// - allowedExternalURLSchemes: 允许直接打开的外部 URL scheme 集合,默认仅 https /// - requiresExternalLinkConfirmation: 打开外部链接前是否需要确认,默认 true /// - allowsInspectableWebViews: 是否允许开启 inspectable,默认 false /// - enablesVerboseWebViewLogging: 是否允许输出完整 WebView 消息体日志,默认 false public init( fontSize: CGFloat = 15, lineHeightMultiple: CGFloat = 1.6, fontChoice: RDEPUBReaderFontChoice = .system, numberOfColumns: Int = 1, columnGap: CGFloat = 20, displayType: RDReaderView.DisplayType = .pageCurl, landscapeDualPageEnabled: Bool = true, showsTableOfContents: Bool = true, allowsHighlights: Bool = true, showsSettingsPanel: Bool = true, reflowableContentInsets: UIEdgeInsets = UIEdgeInsets(top: 40, left: 16, bottom: 40, right: 16), fixedContentInset: UIEdgeInsets = .zero, theme: RDEPUBReaderTheme = .light, darkImageAdjustmentEnabled: Bool = true, darkImageBlendRatio: CGFloat = 0.15, fixedLayoutFit: RDEPUBFixedLayoutFit = .page, fixedLayoutSpreadMode: RDEPUBFixedLayoutSpreadMode = .automatic, textRenderingEngine: RDEPUBTextRenderingEngine = .dtCoreText, onDemandChapterWindowSize: Int = 3, metadataParsingConcurrency: Int = ProcessInfo.processInfo.activeProcessorCount, allowedExternalURLSchemes: Set = ["https"], requiresExternalLinkConfirmation: Bool = true, allowsInspectableWebViews: Bool = false, enablesVerboseWebViewLogging: Bool = false ) { self.fontSize = fontSize self.lineHeightMultiple = lineHeightMultiple self.fontChoice = fontChoice self.numberOfColumns = max(1, numberOfColumns) self.columnGap = max(0, columnGap) self.displayType = displayType self.landscapeDualPageEnabled = landscapeDualPageEnabled self.showsTableOfContents = showsTableOfContents self.allowsHighlights = allowsHighlights self.showsSettingsPanel = showsSettingsPanel self.reflowableContentInsets = reflowableContentInsets self.fixedContentInset = fixedContentInset self.theme = theme self.darkImageAdjustmentEnabled = darkImageAdjustmentEnabled self.darkImageBlendRatio = max(0, min(0.35, darkImageBlendRatio)) self.fixedLayoutFit = fixedLayoutFit self.fixedLayoutSpreadMode = fixedLayoutSpreadMode self.textRenderingEngine = textRenderingEngine self.onDemandChapterWindowSize = Self.normalizedChapterWindowSize(onDemandChapterWindowSize) self.metadataParsingConcurrency = max(1, metadataParsingConcurrency) self.allowedExternalURLSchemes = allowedExternalURLSchemes self.requiresExternalLinkConfirmation = requiresExternalLinkConfirmation self.allowsInspectableWebViews = allowsInspectableWebViews self.enablesVerboseWebViewLogging = enablesVerboseWebViewLogging } /// 默认配置实例,使用所有参数的默认值 public static let `default` = RDEPUBReaderConfiguration() } extension RDEPUBReaderConfiguration { static func normalizedChapterWindowSize(_ size: Int) -> Int { let clamped = max(3, min(15, size)) return clamped % 2 == 0 ? clamped + 1 : clamped } var chapterWindowRadius: Int { onDemandChapterWindowSize / 2 } } // MARK: - 配置转换 extension RDEPUBReaderConfiguration { /// 将用户可见的配置转换为底层排版引擎所需的 RDEPUBPreferences /// - Returns: 排版偏好设置实例,供 Core 层的渲染管道使用 func makePreferences() -> RDEPUBPreferences { RDEPUBPreferences( fontSize: fontSize, lineHeightMultiple: lineHeightMultiple, reflowableContentInsets: reflowableContentInsets, fixedContentInset: fixedContentInset, numberOfColumns: numberOfColumns, columnGap: columnGap, themeBackgroundColor: theme.themeBackgroundColorCSS, themeTextColor: theme.themeTextColorCSS, fixedBackgroundColor: theme.themeBackgroundColorCSS, fixedLayoutFit: fixedLayoutFit, fixedLayoutSpreadMode: fixedLayoutSpreadMode ) } }