ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBWebView.swift
shen 54798ba578 refactor: 添加中文注释 + 优化模块结构
- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
2026-05-25 10:19:14 +08:00

220 lines
8.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// RDEPUBWebView.swift
// EPUB WebView
// WKWebView EPUB Resource View
// spineIndexpageIndex
//
// +Configuration+FixedLayout+Reflowable+JavaScriptBridge+Search
import UIKit
import WebKit
/// RDEPUBWebView WebView
public protocol RDEPUBWebViewDelegate: AnyObject {
///
func epubWebView(_ webView: RDEPUBWebView, didUpdateLocation location: RDEPUBLocation, spineIndex: Int)
///
func epubWebView(_ webView: RDEPUBWebView, didChangeSelection selection: RDEPUBSelection?, spineIndex: Int)
///
func epubWebView(_ webView: RDEPUBWebView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction)
///
func epubWebView(_ webView: RDEPUBWebView, didActivateInternalLink location: RDEPUBLocation, fromSpineIndex: Int)
///
func epubWebView(_ webView: RDEPUBWebView, didActivateExternalLink url: URL)
/// JavaScript
func epubWebView(_ webView: RDEPUBWebView, didLogJavaScriptError message: String)
///
func epubWebViewDidFinishRendering(_ webView: RDEPUBWebView)
}
///
public extension RDEPUBWebViewDelegate {
func epubWebView(_ webView: RDEPUBWebView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {}
}
/// WKWebView //
final class RDEPUBAnnotationWebView: WKWebView {
///
var onSelectionAction: ((RDEPUBAnnotationMenuAction) -> Void)?
override var canBecomeFirstResponder: Bool {
true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(rd_copy(_:)),
#selector(rd_highlight(_:)),
#selector(rd_annotate(_:)):
return true
default:
return false
}
}
@objc func rd_copy(_ sender: Any?) {
onSelectionAction?(.copy)
}
@objc func rd_highlight(_ sender: Any?) {
onSelectionAction?(.highlight)
}
@objc func rd_annotate(_ sender: Any?) {
onSelectionAction?(.annotate)
}
}
/// EPUB WebView WKWebView EPUB
/// Resource View
public final class RDEPUBWebView: UIView {
///
public weak var delegate: RDEPUBWebViewDelegate?
/// delegate 使
public var onRendered: (() -> Void)?
///
var publication: RDEPUBPublication?
///
var currentRenderRequest: RDEPUBRenderRequest?
/// WKWebView
var webView: WKWebView?
/// URL Scheme
var schemeHandler: RDEPUBResourceURLSchemeHandler?
/// WebView
var configuredPublicationKey: String?
///
var currentLoadSignature: String?
/// spine
var currentSpineIndex = 0
/// href
var currentHref = ""
/// 0
var currentPageIndex = 0
///
var currentTotalPagesInChapter = 1
///
var viewportSize: CGSize = .zero
///
var currentPadding: UIEdgeInsets = .zero
///
var currentFontSize: CGFloat = 16
///
var currentLineHeightMultiple: CGFloat = 1.5
///
var currentThemeBackgroundColor: String?
///
var currentThemeTextColor: String?
///
var targetLocation: RDEPUBLocation?
///
var pendingHighlights: [RDEPUBHighlight] = []
/// spread
var fixedSpread: EPUBFixedSpread?
///
var isFixedLayout = false
///
var pendingProgressionRequest = false
/// 退
var fixedLayoutReadyWorkItem: DispatchWorkItem?
///
var didRenderCurrentRequest = false
///
let debugScope = "ReaderWebView"
public override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
teardownWebView()
}
public override func layoutSubviews() {
super.layoutSubviews()
webView?.frame = bounds
}
/// WebView
public func reset() {
delegate = nil
onRendered = nil
publication = nil
currentRenderRequest = nil
currentSpineIndex = 0
currentHref = ""
currentPageIndex = 0
currentTotalPagesInChapter = 1
viewportSize = .zero
currentPadding = .zero
currentFontSize = 16
currentLineHeightMultiple = 1.5
currentThemeBackgroundColor = nil
currentThemeTextColor = nil
targetLocation = nil
pendingHighlights = []
fixedSpread = nil
isFixedLayout = false
pendingProgressionRequest = false
didRenderCurrentRequest = false
teardownWebView()
}
/// WebView
public func load(publication: RDEPUBPublication, request: RDEPUBRenderRequest) {
configureWebViewIfNeeded(publication: publication)
guard let webView else { return }
self.publication = publication
self.currentRenderRequest = request
self.currentSpineIndex = request.primarySpineIndex
self.currentHref = request.primaryHref
self.didRenderCurrentRequest = false
cancelFixedLayoutReadyFallback()
let publicationKey = publication.parser.opfURL?.path ?? publication.parser.extractionRootURL?.path ?? ""
let loadSignature = pageLoadSignature(publicationKey: publicationKey, request: request)
RDEPUBWebViewDebug.log(
debugScope,
message: "load request=\(request.isFixedLayout ? "fixed" : "reflowable") signature=\(loadSignature) webView=\(RDEPUBWebViewDebug.webViewID(webView))"
)
switch request {
case .reflowable(let reflowableRequest):
handleReflowableLoad(publication: publication, request: reflowableRequest, loadSignature: loadSignature, in: webView)
case .fixed(let fixedRequest):
handleFixedLayoutLoad(publication: publication, request: fixedRequest, loadSignature: loadSignature, in: webView)
}
}
///
func pageLoadSignature(
publicationKey: String,
request: RDEPUBRenderRequest
) -> String {
switch request {
case .reflowable(let request):
return reflowableLoadSignature(publicationKey: publicationKey, request: request)
case .fixed(let request):
return fixedLayoutLoadSignature(publicationKey: publicationKey, request: request)
}
}
/// 100ms
func rendered() {
guard !didRenderCurrentRequest else { return }
didRenderCurrentRequest = true
cancelFixedLayoutReadyFallback()
RDEPUBWebViewDebug.log(debugScope, message: "rendered spine=\(currentSpineIndex) href=\(currentHref) fixed=\(isFixedLayout)")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self else { return }
self.delegate?.epubWebViewDidFinishRendering(self)
self.onRendered?()
}
}
}