87 lines
3.7 KiB
Swift
87 lines
3.7 KiB
Swift
// RDEPUBAssetRepository.swift
|
||
// EPUBCore 静态资源仓库
|
||
// 负责从资源包中加载 EPUB 渲染所需的 JS 桥接脚本和固定版式 HTML 模板,
|
||
// 支持模板变量替换({{token}} 占位符)。
|
||
|
||
import Foundation
|
||
|
||
/// EPUB 内置资源枚举,定义桥接脚本和固定版式模板的文件名及扩展名
|
||
enum RDEPUBAsset: String {
|
||
/// Rangy core 等价桥接脚本,负责提供 Web range/selection 基础能力
|
||
case rangyCoreScript = "rangy-core"
|
||
/// Rangy serializer 等价桥接脚本,负责 DOM range 序列化/反序列化
|
||
case rangySerializerScript = "rangy-serializer"
|
||
/// WXRead 对齐的动态 CSS 注入脚本
|
||
case cssInjectorScript = "cssInjector"
|
||
/// WXRead 对齐的 WeReadApi JS-Native 桥接脚本
|
||
case weReadAPIScript = "WeReadApi"
|
||
/// JS 桥接脚本(epub-bridge.js),注入到 WebView 中实现原生-JS 通信
|
||
case bridgeScript = "epub-bridge"
|
||
/// 固定版式 HTML 模板(epub-fixed-layout.html),用于渲染 pre-paginated 类型的 EPUB
|
||
case fixedLayoutTemplate = "epub-fixed-layout"
|
||
/// WXRead 对齐的 default.css
|
||
case wxReadDefaultCSS = "wxread-default"
|
||
/// WXRead 对齐的 replace.css
|
||
case wxReadReplaceCSS = "wxread-replace"
|
||
/// WXRead 对齐的 dark.css
|
||
case wxReadDarkCSS = "wxread-dark"
|
||
/// WXRead 对齐的 replaceForLatinLanguageBook.css
|
||
case wxReadLatinReplaceCSS = "wxread-replace-latin"
|
||
|
||
/// 对应的文件扩展名
|
||
var fileExtension: String {
|
||
switch self {
|
||
case .rangyCoreScript, .rangySerializerScript, .cssInjectorScript, .weReadAPIScript, .bridgeScript:
|
||
return "js"
|
||
case .fixedLayoutTemplate:
|
||
return "html"
|
||
case .wxReadDefaultCSS, .wxReadReplaceCSS, .wxReadDarkCSS, .wxReadLatinReplaceCSS:
|
||
return "css"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 静态资源加载器,负责从资源包中读取模板文件并执行变量替换
|
||
enum RDEPUBAssetRepository {
|
||
/// 加载指定资源文件的内容,并将 {{key}} 占位符替换为对应的值
|
||
/// - Parameters:
|
||
/// - asset: 要加载的资源类型
|
||
/// - replacements: 模板变量字典,key 为占位符名称,value 为替换值
|
||
/// - Returns: 替换后的文件内容字符串
|
||
static func string(for asset: RDEPUBAsset, replacements: [String: String] = [:]) -> String {
|
||
guard let url = resourceBundle.url(forResource: asset.rawValue, withExtension: asset.fileExtension),
|
||
var content = try? String(contentsOf: url, encoding: .utf8) else {
|
||
assertionFailure("Missing EPUB asset: \(asset.rawValue).\(asset.fileExtension)")
|
||
return ""
|
||
}
|
||
|
||
for (token, value) in replacements {
|
||
content = content.replacingOccurrences(of: "{{\(token)}}", with: value)
|
||
}
|
||
return content
|
||
}
|
||
|
||
/// 获取资源所在的 Bundle(优先使用已解析的子 bundle,兜底到宿主 bundle)
|
||
private static var resourceBundle: Bundle {
|
||
if let bundle = resolvedBundle {
|
||
return bundle
|
||
}
|
||
return Bundle(for: RDEPUBAssetBundleToken.self)
|
||
}
|
||
|
||
/// 延迟解析 RDReaderViewAssets.bundle 的位置,在宿主 bundle 和所有 framework 中查找
|
||
private static var resolvedBundle: Bundle? = {
|
||
let hostBundles = [Bundle(for: RDEPUBAssetBundleToken.self), Bundle.main] + Bundle.allFrameworks + Bundle.allBundles
|
||
for hostBundle in hostBundles {
|
||
if let url = hostBundle.url(forResource: "RDReaderViewAssets", withExtension: "bundle"),
|
||
let bundle = Bundle(url: url) {
|
||
return bundle
|
||
}
|
||
}
|
||
return nil
|
||
}()
|
||
}
|
||
|
||
/// 用于定位当前模块 Bundle 的标记类
|
||
private final class RDEPUBAssetBundleToken {}
|