- 新增 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>
176 lines
6.0 KiB
Swift
176 lines
6.0 KiB
Swift
|
||
import Foundation
|
||
|
||
public final class RDEPUBResourceResolver {
|
||
|
||
private let parser: RDEPUBParser
|
||
|
||
public init(parser: RDEPUBParser) {
|
||
self.parser = parser
|
||
}
|
||
|
||
public var opfDirectoryURL: URL? {
|
||
parser.opfDirectoryURL
|
||
}
|
||
|
||
public func fileURL(forRelativePath relativePath: String) -> URL? {
|
||
parser.fileURL(forRelativePath: relativePath)
|
||
}
|
||
|
||
public func resourceURL(forRelativePath relativePath: String) -> URL? {
|
||
parser.resourceURL(forRelativePath: relativePath)
|
||
}
|
||
|
||
public func fileURL(forResourceURL resourceURL: URL) -> URL? {
|
||
parser.fileURL(forResourceURL: resourceURL)
|
||
}
|
||
|
||
/// 是否配置了加密资源数据提供者
|
||
public var hasResourceDataProvider: Bool {
|
||
parser.resourceDataProvider != nil
|
||
}
|
||
|
||
/// 仅经 provider 获取解密数据;无 provider 或 provider 判定无需解密时返回 nil
|
||
public func providedResourceData(at fileURL: URL) -> Data? {
|
||
parser.providedResourceData(at: fileURL)
|
||
}
|
||
|
||
/// 统一资源读取入口:优先 provider 解密,其次磁盘明文
|
||
public func resourceData(at fileURL: URL) -> Data? {
|
||
parser.resourceData(at: fileURL)
|
||
}
|
||
|
||
public func normalizedHref(_ href: String, relativeToSpineIndex spineIndex: Int? = nil) -> String? {
|
||
guard let opfDirectoryURL else {
|
||
return href.components(separatedBy: "#").first
|
||
}
|
||
|
||
let pathPart = href.components(separatedBy: "#").first ?? href
|
||
if pathPart.isEmpty {
|
||
guard let spineIndex, parser.spine.indices.contains(spineIndex) else {
|
||
return nil
|
||
}
|
||
return parser.spine[spineIndex].href.components(separatedBy: "#").first
|
||
}
|
||
|
||
let baseURL: URL
|
||
if let spineIndex, parser.spine.indices.contains(spineIndex) {
|
||
baseURL = opfDirectoryURL
|
||
.appendingPathComponent(parser.spine[spineIndex].href)
|
||
.deletingLastPathComponent()
|
||
} else {
|
||
baseURL = opfDirectoryURL
|
||
}
|
||
|
||
guard let resolvedURL = URL(string: pathPart, relativeTo: baseURL)?.standardizedFileURL else {
|
||
return pathPart
|
||
}
|
||
|
||
let opfPath = opfDirectoryURL.standardizedFileURL.path + "/"
|
||
let resolvedPath = resolvedURL.path
|
||
if resolvedPath.hasPrefix(opfPath) {
|
||
return String(resolvedPath.dropFirst(opfPath.count))
|
||
}
|
||
return pathPart
|
||
}
|
||
|
||
public func normalizedHref(_ href: String, relativeToHref baseHref: String) -> String? {
|
||
guard let opfDirectoryURL else {
|
||
return href.components(separatedBy: "#").first
|
||
}
|
||
|
||
let pathPart = href.components(separatedBy: "#").first ?? href
|
||
let basePath = baseHref.components(separatedBy: "#").first ?? baseHref
|
||
let baseURL = opfDirectoryURL
|
||
.appendingPathComponent(basePath)
|
||
.deletingLastPathComponent()
|
||
|
||
guard let resolvedURL = URL(string: pathPart, relativeTo: baseURL)?.standardizedFileURL else {
|
||
return pathPart
|
||
}
|
||
|
||
let opfPath = opfDirectoryURL.standardizedFileURL.path + "/"
|
||
if resolvedURL.path.hasPrefix(opfPath) {
|
||
return String(resolvedURL.path.dropFirst(opfPath.count))
|
||
}
|
||
return pathPart
|
||
}
|
||
|
||
public func fileURL(forReference href: String, relativeToHref baseHref: String) -> URL? {
|
||
guard let normalizedHref = normalizedHref(href, relativeToHref: baseHref) else {
|
||
return nil
|
||
}
|
||
return fileURL(forRelativePath: normalizedHref)
|
||
}
|
||
|
||
public func normalizedLocation(
|
||
_ location: RDEPUBLocation,
|
||
relativeToSpineIndex spineIndex: Int? = nil,
|
||
bookIdentifier: String?
|
||
) -> RDEPUBLocation? {
|
||
let rawHref = location.href
|
||
let hrefParts = rawHref.split(separator: "#", maxSplits: 1, omittingEmptySubsequences: false)
|
||
let pathPart = hrefParts.first.map(String.init) ?? rawHref
|
||
let fragment = location.fragment ?? (hrefParts.count > 1 ? String(hrefParts[1]) : nil)
|
||
|
||
let normalizedTargetHref: String
|
||
if pathPart.isEmpty {
|
||
guard let spineIndex, parser.spine.indices.contains(spineIndex) else {
|
||
return nil
|
||
}
|
||
normalizedTargetHref = parser.spine[spineIndex].href.components(separatedBy: "#").first ?? parser.spine[spineIndex].href
|
||
} else {
|
||
guard let href = normalizedHref(pathPart, relativeToSpineIndex: spineIndex) else {
|
||
return nil
|
||
}
|
||
normalizedTargetHref = href
|
||
}
|
||
|
||
return RDEPUBLocation(
|
||
bookIdentifier: bookIdentifier,
|
||
href: normalizedTargetHref,
|
||
progression: location.progression,
|
||
lastProgression: location.lastProgression,
|
||
fragment: fragment,
|
||
rangeAnchor: location.rangeAnchor,
|
||
cfi: location.cfi,
|
||
lastCFI: location.lastCFI,
|
||
rangeCFI: location.rangeCFI
|
||
)
|
||
}
|
||
|
||
public func spineIndex(forNormalizedHref normalizedHref: String) -> Int? {
|
||
parser.spine.firstIndex { item in
|
||
self.normalizedHref(item.href) == normalizedHref
|
||
}
|
||
}
|
||
|
||
public func spineIndex(for location: RDEPUBLocation) -> Int? {
|
||
guard let normalizedHref = normalizedHref(location.href) else {
|
||
return nil
|
||
}
|
||
return spineIndex(forNormalizedHref: normalizedHref)
|
||
}
|
||
|
||
public func href(forSpineIndex spineIndex: Int) -> String? {
|
||
guard parser.spine.indices.contains(spineIndex) else {
|
||
return nil
|
||
}
|
||
return parser.spine[spineIndex].href
|
||
}
|
||
|
||
public func title(forSpineIndex spineIndex: Int) -> String? {
|
||
guard parser.spine.indices.contains(spineIndex) else {
|
||
return nil
|
||
}
|
||
return parser.spine[spineIndex].title
|
||
}
|
||
|
||
public func manifestItem(forHref href: String) -> RDEPUBManifestItem? {
|
||
let targetHref = normalizedHref(href) ?? href.components(separatedBy: "#").first ?? href
|
||
return parser.manifest.values.first {
|
||
normalizedHref($0.href) == targetHref
|
||
}
|
||
}
|
||
}
|