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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
83dfa40299
commit
d5a7755702
+56
@@ -0,0 +1,56 @@
|
||||
import UIKit
|
||||
|
||||
#if canImport(DTCoreText)
|
||||
import DTCoreText
|
||||
|
||||
/// 支持加密图片的 DTCoreText 图片附件。
|
||||
///
|
||||
/// DTCoreText 解析 `<img>` 时会直接读磁盘文件来取图并推算尺寸;
|
||||
/// 对加密图片该读取会解码失败,表现为 `image == nil` 且 `contentURL` 被保留。
|
||||
/// 本子类在 super 初始化完成后接管这种失败场景:
|
||||
/// 经 `RDEPUBResourceAccessRegistry` 反查 provider 解密数据并调用 `setImage`
|
||||
/// (setter 会自动补齐 originalSize / displaySize,后续 willFlush 阶段的
|
||||
/// 附件规范化与绘制均按明文图片的既有路径工作)。
|
||||
///
|
||||
/// 通过 `DTTextAttachment.registerClass(_:forTagName:)` 全局注册,
|
||||
/// 未启用 provider 的书行为与父类完全一致。
|
||||
final class RDEPUBDecryptingImageAttachment: DTImageTextAttachment {
|
||||
|
||||
private static var didRegisterTagClass = false
|
||||
|
||||
private static let registerLock = NSLock()
|
||||
|
||||
/// 将本类注册为 `<img>` 标签的附件类(进程内一次性,线程安全)
|
||||
static func registerTagClassIfNeeded() {
|
||||
registerLock.lock()
|
||||
defer { registerLock.unlock() }
|
||||
guard !didRegisterTagClass else { return }
|
||||
DTTextAttachment.registerClass(RDEPUBDecryptingImageAttachment.self, forTagName: "img")
|
||||
didRegisterTagClass = true
|
||||
}
|
||||
|
||||
override init!(element: DTHTMLElement!, options: [AnyHashable: Any]! = [:]) {
|
||||
super.init(element: element, options: options)
|
||||
decryptImageIfNeeded()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
private func decryptImageIfNeeded() {
|
||||
// super 已成功取到图(明文图片 / data: 内联图)时不干预
|
||||
guard image == nil,
|
||||
let contentURL,
|
||||
contentURL.isFileURL,
|
||||
let data = RDEPUBResourceAccessRegistry.providedResourceData(at: contentURL.standardizedFileURL),
|
||||
let decrypted = UIImage(data: data) else {
|
||||
return
|
||||
}
|
||||
image = decrypted
|
||||
// 对齐 DTCoreText 本地图片加载成功的行为:置空 contentURL,
|
||||
// 避免下游误当作待加载的远程 / 磁盘图片再次直读密文
|
||||
self.contentURL = nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+13
-3
@@ -89,7 +89,7 @@ struct RDEPUBRenderDiagnosticsCollector {
|
||||
diagnostics.append(resolution.diagnostic)
|
||||
|
||||
if let fileURL = resolution.resolvedFileURL,
|
||||
let css = try? String(contentsOf: fileURL),
|
||||
let css = styleSheetContent(at: fileURL, resourceResolver: resourceResolver),
|
||||
resolution.diagnostic.existsOnDisk {
|
||||
let cssWithResolvedURLs = rewriteCSSResourceURLs(
|
||||
in: css,
|
||||
@@ -106,9 +106,19 @@ struct RDEPUBRenderDiagnosticsCollector {
|
||||
return (rewrittenHTML, inlinedCSSBlocks.reversed().joined(separator: "\n\n"), diagnostics.reversed())
|
||||
}
|
||||
|
||||
|
||||
/// 读取样式表内容:优先经加密资源 provider 解密,明文文件保持原直读路径
|
||||
private static func styleSheetContent(
|
||||
at fileURL: URL,
|
||||
resourceResolver: RDEPUBResourceResolver?
|
||||
) -> String? {
|
||||
if let provided = resourceResolver?.providedResourceData(at: fileURL) {
|
||||
return String(data: provided, encoding: .utf8)
|
||||
?? String(data: provided, encoding: .utf16)
|
||||
}
|
||||
return try? String(contentsOf: fileURL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
static func rewriteCSSResourceURLs(
|
||||
in css: String,
|
||||
styleSheetFileURL: URL
|
||||
|
||||
Reference in New Issue
Block a user