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:
shenlei
2026-07-10 12:25:17 +09:00
co-authored by Claude Fable 5
parent 83dfa40299
commit d5a7755702
30 changed files with 2218 additions and 1569 deletions
@@ -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
@@ -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