- 新增 RDEPUBResourceDataProvider 解密钩子协议 + 访问登记表,收敛章节 HTML/ 图片/内联 CSS/脚注/封面/图片查看器/正文取图等读取点,scheme handler 对加密书 禁用流式分支;新增 RDEPUBDecryptingImageAttachment 解密 DTCoreText 图片附件; RDEPUBReaderDependencies.live(resourceDataProvider:) 便捷注入。明文书零影响。 - 试读墙:configuration.trialPolicy + delegate epubReaderTrialWallView/ DidReachTrialWall,UI 由宿主提供(RDEPUBReaderController+Trial)。 - 工具栏定制:RDEPUBReaderTop/BottomToolViewProtocol 协议 + dependencies 工厂注入, 内置栏已 conform,configureTopToolView 改协议类型。 - 朝向锁定:RDEPUBMetadata.orientation(OPF rendition:orientation 主, iBooks display-options 兜底)+ RDEPUBReaderController+Orientation 重写支持朝向、 打开后主动转向。 注:RDEPUBReaderController+LocationResolution / PaginationCoordinator 为本次改动前 即存在的工作区修改,一并纳入。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
214 lines
5.0 KiB
Swift
214 lines
5.0 KiB
Swift
|
||
import Foundation
|
||
|
||
public enum RDEPUBLayout: String, Codable {
|
||
case reflowable
|
||
case fixed
|
||
}
|
||
|
||
public enum RDEPUBReadingProfile: String, Codable {
|
||
case webInteractive
|
||
case webFixedLayout
|
||
case textReflowable
|
||
}
|
||
|
||
public enum RDEPUBReadingProgression: String, Codable {
|
||
case ltr
|
||
case rtl
|
||
case auto
|
||
}
|
||
|
||
public enum RDEPUBPageSpread: String, Codable {
|
||
case left
|
||
case right
|
||
case center
|
||
}
|
||
|
||
/// EPUB 渲染朝向(rendition:orientation / iBooks display-options)。
|
||
public enum RDEPUBOrientation: String, Codable {
|
||
case portrait
|
||
case landscape
|
||
case auto
|
||
}
|
||
|
||
public struct RDEPUBMetadata: Codable, Equatable {
|
||
|
||
public var identifier: String?
|
||
|
||
public var title: String
|
||
|
||
public var author: String?
|
||
|
||
public var language: String?
|
||
|
||
public var version: String?
|
||
|
||
public var layout: RDEPUBLayout
|
||
|
||
public var spread: String?
|
||
|
||
public var readingProgression: RDEPUBReadingProgression
|
||
|
||
/// 渲染朝向;nil = 未声明(不锁定,跟随系统)。
|
||
/// 来源优先级:OPF rendition:orientation > META-INF/com.apple.ibooks.display-options.xml。
|
||
public var orientation: RDEPUBOrientation?
|
||
|
||
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,
|
||
orientation: RDEPUBOrientation? = nil
|
||
) {
|
||
self.identifier = identifier
|
||
self.title = title
|
||
self.author = author
|
||
self.language = language
|
||
self.version = version
|
||
self.layout = layout
|
||
self.spread = spread
|
||
self.readingProgression = readingProgression
|
||
self.orientation = orientation
|
||
}
|
||
}
|
||
|
||
public struct RDEPUBManifestItem: Codable, Equatable {
|
||
|
||
public var id: String
|
||
|
||
public var href: String
|
||
|
||
public var mediaType: String
|
||
|
||
public var properties: [String]
|
||
|
||
public var fallback: String?
|
||
|
||
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
|
||
}
|
||
|
||
public var isNavigationDocument: Bool {
|
||
properties.contains("nav")
|
||
}
|
||
|
||
public var isNCX: Bool {
|
||
mediaType == "application/x-dtbncx+xml"
|
||
}
|
||
}
|
||
|
||
public struct RDEPUBSpineItem: Codable, Equatable {
|
||
|
||
public var idref: String
|
||
|
||
public var href: String
|
||
|
||
public var mediaType: String
|
||
|
||
public var title: String
|
||
|
||
public var linear: Bool
|
||
|
||
public var properties: [String]
|
||
|
||
public var pageSpread: RDEPUBPageSpread?
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
public struct EPUBTableOfContentsItem: Codable, Equatable {
|
||
|
||
public var title: String
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
public enum RDEPUBParserError: LocalizedError {
|
||
|
||
case archiveOpenFailed(URL)
|
||
|
||
case missingContainerXML
|
||
|
||
case missingRootFile
|
||
|
||
case invalidRootFilePath(String)
|
||
|
||
case invalidXML(URL)
|
||
|
||
case missingManifestItem(idref: String)
|
||
|
||
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)"
|
||
}
|
||
}
|
||
} |