ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBModels.swift
shen 6f75b083f7 feat: EPUB 阅读器搜索、选中注释、书签 chrome 状态及大量重构优化
- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态
- 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试
- 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证)
- 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互
- 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache)
- 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy
- 更新 epub-bridge.js 与 JS bridge 通信协议
- 全面更新现有 UI 测试以适配新的 helper 和状态管理
2026-06-13 22:48:56 +08:00

238 lines
8.7 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>
/// IDMIME
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?
/// IDEPUB 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
/// 穿
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)"
}
}
}