Files
ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBModels.swift
T
shenandshen 54798ba578 refactor: 添加中文注释 + 优化模块结构
- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
2026-05-25 10:19:14 +08:00

234 lines
8.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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 文档中 <metadata> 节点的内容
/// 包含书籍的基本信息:标识符、标题、作者、语言、版本、布局类型等
public struct RDEPUBMetadata: Codable, Equatable {
/// 书籍唯一标识符(对应 <dc:identifier>
public var identifier: String?
/// 书名(对应 <dc:title>
public var title: String
/// 作者(对应 <dc:creator>
public var author: String?
/// 语言代码(对应 <dc:language>,如 "zh"、"en"
public var language: String?
/// EPUB 版本号(如 "2.0"、"3.0"
public var version: String?
/// 版面布局类型(reflowable 或 fixed
public var layout: RDEPUBLayout
/// spread 模式设置(对应 <meta property="rendition: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 文档 <manifest> 中的每个 <item>
/// 描述一个资源文件的 ID、路径、MIME 类型和附加属性
public struct RDEPUBManifestItem: Codable, Equatable {
/// 清单项的唯一标识符(对应 <item id="...">
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(对应 <item fallback="...">
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> 中的每个 <itemref>
/// 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 DocumentEPUB 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 中未找到 <rootfile> 元素
case missingRootFile
/// OPF 根文件路径无效(文件不存在)
case invalidRootFilePath(String)
/// XML 解析失败(格式错误或编码问题)
case invalidXML(URL)
/// spine 中引用的 manifest 项不存在(idref 不匹配)
case missingManifestItem(idref: String)
/// 构建完成的 spine 为空(无可阅读的内容)
case emptySpine
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 为空"
}
}
}