ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBParser+Resources.swift
shenlei d5a7755702 feat: 支持加密 EPUB、试读墙、工具栏定制与朝向锁定
- 新增 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>
2026-07-10 12:25:17 +09:00

102 lines
3.8 KiB
Swift

import Foundation
import UIKit
extension RDEPUBParser {
public func manifestItem(for id: String) -> RDEPUBManifestItem? {
manifest[id]
}
public func manifestItem(forHref href: String) -> RDEPUBManifestItem? {
let target = href.components(separatedBy: "#").first ?? href
return manifest.values.first { item in
normalize(href: item.href, relativeTo: opfDirectoryURL ?? URL(fileURLWithPath: "/")) == target
}
}
public func href(forSpineIndex index: Int) -> String? {
guard spine.indices.contains(index) else { return nil }
return spine[index].href
}
public func fileURL(forRelativePath relativePath: String) -> URL? {
guard let opfDirectoryURL else { return nil }
let path = relativePath.components(separatedBy: "#").first ?? relativePath
guard !path.isEmpty else { return nil }
let resolvedURL = URL(fileURLWithPath: path, relativeTo: opfDirectoryURL).standardizedFileURL
guard let rootURL = extractionRootURL?.standardizedFileURL else {
return resolvedURL
}
guard resolvedURL.path.hasPrefix(rootURL.path) else {
return nil
}
return resolvedURL
}
public func resourceURL(forRelativePath relativePath: String) -> URL? {
let normalizedPath = relativePath.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
guard !normalizedPath.isEmpty else { return nil }
guard let encodedPath = normalizedPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
return nil
}
var components = URLComponents()
components.scheme = RDEPUBResourceURLSchemeHandler.scheme
components.host = RDEPUBResourceURLSchemeHandler.host
components.percentEncodedPath = "/" + encodedPath
return components.url
}
public func fileURL(forResourceURL resourceURL: URL) -> URL? {
guard resourceURL.scheme == RDEPUBResourceURLSchemeHandler.scheme,
resourceURL.host == RDEPUBResourceURLSchemeHandler.host else {
return nil
}
let relativePath = resourceURL.path.removingPercentEncoding?.trimmingCharacters(in: CharacterSet(charactersIn: "/")) ?? ""
return fileURL(forRelativePath: relativePath)
}
public func coverImage() -> UIImage? {
let coverCandidates = manifest.values.filter { item in
item.properties.contains("cover-image") || item.id.lowercased().contains("cover")
}
for item in coverCandidates {
if let fileURL = fileURL(forRelativePath: item.href),
let data = resourceData(at: fileURL),
let image = UIImage(data: data) {
return image
}
}
return nil
}
public func htmlString(forSpineIndex index: Int) -> String? {
guard spine.indices.contains(index) else {
return nil
}
return htmlString(forRelativePath: spine[index].href)
}
public func htmlString(forRelativePath relativePath: String) -> String? {
guard let fileURL = fileURL(forRelativePath: relativePath) else {
return nil
}
if let provided = providedResourceData(at: fileURL) {
return String(data: provided, encoding: .utf8)
?? String(data: provided, encoding: .utf16)
}
return try? String(contentsOf: fileURL)
}
func normalize(href: String, relativeTo directoryURL: URL) -> String {
guard let resolvedURL = URL(string: href, relativeTo: directoryURL)?.standardizedFileURL else {
return href
}
let prefix = directoryURL.standardizedFileURL.path + "/"
if resolvedURL.path.hasPrefix(prefix) {
return String(resolvedURL.path.dropFirst(prefix.count))
}
return href
}
}