// // RDEPUBModels.swift // RDReaderView // // EPUBCore 层核心数据模型定义文件。 // 包含 EPUB 解析过程中所需的全部基础枚举和数据结构: // 布局类型、阅读方向、页面分布、元数据、清单项、Spine 项、目录项及解析错误类型。 // 这些模型由 RDEPUBParser 解析后填充,并由 RDEPUBPublication 聚合对外暴露。 // import Foundation /// EPUB 版面布局类型 /// - reflowable: 可重排布局,内容根据屏幕尺寸和字号自动分页(小说类 EPUB) /// - fixed: 固定版式布局,页面尺寸固定不变(漫画、绘本类 EPUB) public enum RDEPUBLayout: String, Codable { case reflowable case fixed } /// 阅读配置文件类型,决定使用哪种渲染路径 /// - webInteractive: 可重排 + 含交互脚本,使用 WKWebView 渲染 /// - webFixedLayout: 固定版式 EPUB(漫画、绘本),使用 WKWebView 渲染 /// - textReflowable: 纯文本可重排 EPUB,使用 DTCoreText 渲染(性能更优) public enum RDEPUBReadingProfile: String, Codable { case webInteractive case webFixedLayout case textReflowable } /// 阅读方向(文字排版方向) /// - ltr: 从左到右(英文、中文横排) /// - rtl: 从右到左(日文竖排、阿拉伯文) /// - auto: 根据语言自动判断 public enum RDEPUBReadingProgression: String, Codable { case ltr case rtl case auto } /// 固定版式页面在 spread(双页展开)中的位置 /// - left: 左页 /// - right: 右页 /// - center: 居中(跨页或单页居中) public enum RDEPUBPageSpread: String, Codable { case left case right case center } /// EPUB 出版物元数据,对应 OPF 文档中 节点的内容 /// 包含书籍的基本信息:标识符、标题、作者、语言、版本、布局类型等 public struct RDEPUBMetadata: Codable, Equatable { /// 书籍唯一标识符(对应 ) public var identifier: String? /// 书名(对应 ) public var title: String /// 作者(对应 ) public var author: String? /// 语言代码(对应 ,如 "zh"、"en") public var language: String? /// EPUB 版本号(如 "2.0"、"3.0") public var version: String? /// 版面布局类型(reflowable 或 fixed) public var layout: RDEPUBLayout /// spread 模式设置(对应 ) public var spread: String? /// 阅读方向(ltr / rtl / auto) public var readingProgression: RDEPUBReadingProgression public init( identifier: String? = nil, title: String = "", author: String? = nil, language: String? = nil, version: String? = nil, layout: RDEPUBLayout = .reflowable, spread: String? = nil, readingProgression: RDEPUBReadingProgression = .auto ) { self.identifier = identifier self.title = title self.author = author self.language = language self.version = version self.layout = layout self.spread = spread self.readingProgression = readingProgression } } /// EPUB 清单项,对应 OPF 文档 中的每个 /// 描述一个资源文件的 ID、路径、MIME 类型和附加属性 public struct RDEPUBManifestItem: Codable, Equatable { /// 清单项的唯一标识符(对应 ) public var id: String /// 资源文件路径,相对于 OPF 文件所在目录 public var href: String /// MIME 类型(如 "application/xhtml+xml"、"image/jpeg"、"text/css") public var mediaType: String /// 属性列表(如 "nav" 表示导航文档、"scripted" 表示含脚本、"cover-image" 表示封面) public var properties: [String] /// 回退项 ID(对应 ) public var fallback: String? /// 媒体叠加文件 ID(EPUB 3 有声书功能) public var mediaOverlay: String? /// 资源标题(用于搜索结果展示等辅助用途) public var title: String? public init( id: String, href: String, mediaType: String, properties: [String] = [], fallback: String? = nil, mediaOverlay: String? = nil, title: String? = nil ) { self.id = id self.href = href self.mediaType = mediaType self.properties = properties self.fallback = fallback self.mediaOverlay = mediaOverlay self.title = title } /// 是否为 EPUB 3 导航文档(Nav Document) /// 通过检查 properties 中是否包含 "nav" 标记判断 public var isNavigationDocument: Bool { properties.contains("nav") } /// 是否为 NCX 目录文件(EPUB 2 格式) /// 通过 MIME 类型 "application/x-dtbncx+xml" 判断 public var isNCX: Bool { mediaType == "application/x-dtbncx+xml" } } /// EPUB Spine 项,对应 OPF 文档 中的每个 /// Spine 定义了阅读器中章节内容的线性阅读顺序 public struct RDEPUBSpineItem: Codable, Equatable { /// 对应 manifest 中的 item id(用于关联资源) public var idref: String /// 标准化后的资源路径(相对于 OPF 文件所在目录) public var href: String /// MIME 类型 public var mediaType: String /// 章节标题(从目录或 manifest 推断) public var title: String /// 是否为线性内容("no" 表示补充材料,不影响主线阅读进度) public var linear: Bool /// 属性列表(如 rendition:layout-* 用于单页布局覆盖) public var properties: [String] /// 页面在 spread 中的位置偏好(左页/右页/居中) public var pageSpread: RDEPUBPageSpread? /// 单页布局覆盖(显式属性可覆盖出版级别的 layout 设置) public var layout: RDEPUBLayout? public init( idref: String, href: String, mediaType: String, title: String, linear: Bool = true, properties: [String] = [], pageSpread: RDEPUBPageSpread? = nil, layout: RDEPUBLayout? = nil ) { self.idref = idref self.href = href self.mediaType = mediaType self.title = title self.linear = linear self.properties = properties self.pageSpread = pageSpread self.layout = layout } } /// EPUB 目录项,支持树形层级结构 /// 来源于 NCX 文件(EPUB 2)或 Nav Document(EPUB 3)的解析结果 public struct EPUBTableOfContentsItem: Codable, Equatable { /// 目录项标题(显示在目录面板中) public var title: String /// 目标资源路径(含可能的 fragment 锚点) public var href: String /// 子目录项列表(递归结构,支持多级目录) public var children: [EPUBTableOfContentsItem] public init(title: String, href: String, children: [EPUBTableOfContentsItem] = []) { self.title = title self.href = href self.children = children } } /// EPUB 解析器错误类型,涵盖从 ZIP 解压到 OPF 解析全链路的异常情况 public enum RDEPUBParserError: LocalizedError { /// ZIP 文件无法打开(文件损坏或格式不支持) case archiveOpenFailed(URL) /// 缺少 META-INF/container.xml 文件(EPUB 格式不合规) case missingContainerXML /// container.xml 中未找到 元素 case missingRootFile /// OPF 根文件路径无效(文件不存在) case invalidRootFilePath(String) /// XML 解析失败(格式错误或编码问题) case invalidXML(URL) /// spine 中引用的 manifest 项不存在(idref 不匹配) case missingManifestItem(idref: String) /// 构建完成的 spine 为空(无可阅读的内容) case emptySpine /// 归档条目路径包含路径穿越或非法字符 case invalidArchiveEntryPath(String) public var errorDescription: String? { switch self { case .archiveOpenFailed(let url): return "无法打开 EPUB 压缩包: \(url.lastPathComponent)" case .missingContainerXML: return "EPUB 缺少 META-INF/container.xml" case .missingRootFile: return "container.xml 中未找到 rootfile" case .invalidRootFilePath(let path): return "OPF 路径无效: \(path)" case .invalidXML(let url): return "XML 解析失败: \(url.lastPathComponent)" case .missingManifestItem(let idref): return "spine itemref 找不到对应 manifest 项: \(idref)" case .emptySpine: return "OPF spine 为空" case .invalidArchiveEntryPath(let path): return "归档条目路径非法: \(path)" } } }