131 lines
5.0 KiB
Swift
131 lines
5.0 KiB
Swift
// RDEPUBWebView+FixedLayout.swift
|
||
// RDEPUBWebView 的固定版式渲染扩展
|
||
// 处理固定版式(Fixed Layout)的加载流程:生成 HTML 模板、
|
||
// 计算加载签名(去重)、设置固定版式就绪回退定时器等。
|
||
|
||
import UIKit
|
||
import WebKit
|
||
|
||
extension RDEPUBWebView {
|
||
/// 便捷方法:加载固定版式的 spread 页面
|
||
public func loadFixedSpread(
|
||
parser: RDEPUBParser,
|
||
spread: EPUBFixedSpread,
|
||
viewportSize: CGSize,
|
||
contentInset: UIEdgeInsets,
|
||
backgroundColor: UIColor?
|
||
) {
|
||
let request = RDEPUBRenderRequest.fixed(
|
||
RDEPUBFixedRenderRequest(
|
||
spread: spread,
|
||
viewportSize: viewportSize,
|
||
contentInset: contentInset,
|
||
backgroundColorCSS: backgroundColor?.ss_hexString,
|
||
fit: .page,
|
||
searchPresentation: nil
|
||
)
|
||
)
|
||
load(publication: parser.makePublication(), request: request)
|
||
}
|
||
|
||
/// 处理固定版式加载:设置状态、生成 HTML、加载到 WebView
|
||
func handleFixedLayoutLoad(
|
||
publication: RDEPUBPublication,
|
||
request: RDEPUBFixedRenderRequest,
|
||
loadSignature: String,
|
||
in webView: WKWebView
|
||
) {
|
||
currentPageIndex = 0
|
||
currentTotalPagesInChapter = 1
|
||
viewportSize = request.viewportSize
|
||
currentPadding = request.contentInset
|
||
currentFontSize = 16
|
||
currentLineHeightMultiple = 1.5
|
||
currentThemeBackgroundColor = request.backgroundColorCSS
|
||
currentThemeTextColor = nil
|
||
targetLocation = nil
|
||
pendingHighlights = []
|
||
fixedSpread = request.spread
|
||
isFixedLayout = true
|
||
pendingProgressionRequest = false
|
||
currentLoadSignature = loadSignature
|
||
scheduleFixedLayoutReadyFallback()
|
||
|
||
let html = RDEPUBFixedLayoutTemplate.html(for: request, publication: publication)
|
||
RDEPUBWebViewDebug.log(debugScope, message: "load fixed spread resources=\(request.spread.resources.map(\.href).joined(separator: ",")) fit=\(request.fit.rawValue)")
|
||
webView.loadHTMLString(
|
||
html,
|
||
baseURL: URL(string: "\(RDEPUBResourceURLSchemeHandler.scheme)://\(RDEPUBResourceURLSchemeHandler.host)/")
|
||
)
|
||
}
|
||
|
||
/// 生成固定版式加载签名(用于去重相同请求)
|
||
func fixedLayoutLoadSignature(publicationKey: String, request: RDEPUBFixedRenderRequest) -> String {
|
||
let searchSignature = [
|
||
request.searchPresentation?.keyword ?? "",
|
||
request.searchPresentation?.resources.map {
|
||
[
|
||
$0.href,
|
||
String($0.matchCount),
|
||
String($0.activeLocalMatchIndex ?? -1)
|
||
].joined(separator: "|")
|
||
}.joined(separator: ",") ?? ""
|
||
].joined(separator: "#")
|
||
return [
|
||
publicationKey,
|
||
"fixed",
|
||
request.spread.resources.map(\.href).joined(separator: "|"),
|
||
String(format: "%.2f", request.viewportSize.width),
|
||
String(format: "%.2f", request.viewportSize.height),
|
||
String(format: "%.2f", request.contentInset.top),
|
||
String(format: "%.2f", request.contentInset.left),
|
||
String(format: "%.2f", request.contentInset.bottom),
|
||
String(format: "%.2f", request.contentInset.right),
|
||
request.backgroundColorCSS ?? "",
|
||
request.fit.rawValue,
|
||
searchSignature
|
||
].joined(separator: "#")
|
||
}
|
||
|
||
/// 调度固定版式就绪回退定时器(1 秒后如果未收到 ready 消息则手动触发渲染完成)
|
||
func scheduleFixedLayoutReadyFallback() {
|
||
let workItem = DispatchWorkItem { [weak self] in
|
||
RDEPUBWebViewDebug.log(self?.debugScope ?? "ReaderWebView", message: "fixed ready fallback fired")
|
||
self?.applyFixedPresentationIfNeeded {
|
||
self?.refreshNativeDecorationsIfNeeded {
|
||
self?.rendered()
|
||
}
|
||
}
|
||
}
|
||
fixedLayoutReadyWorkItem = workItem
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
||
}
|
||
|
||
/// 取消固定版式就绪回退定时器
|
||
func cancelFixedLayoutReadyFallback() {
|
||
fixedLayoutReadyWorkItem?.cancel()
|
||
fixedLayoutReadyWorkItem = nil
|
||
}
|
||
|
||
func applyFixedPresentationIfNeeded(completion: (() -> Void)? = nil) {
|
||
guard let webView,
|
||
let currentRenderRequest,
|
||
case .fixed(let request) = currentRenderRequest else {
|
||
completion?()
|
||
return
|
||
}
|
||
|
||
let script = RDEPUBJavaScriptBridge.applyFixedPresentationScript(for: request)
|
||
webView.evaluateJavaScript(script) { [weak self] _, error in
|
||
guard let self else {
|
||
completion?()
|
||
return
|
||
}
|
||
if let error {
|
||
self.delegate?.epubWebView(self, didLogJavaScriptError: error.localizedDescription)
|
||
}
|
||
self.applySearchDecorationsIfNeeded(completion: completion)
|
||
}
|
||
}
|
||
}
|