Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
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)
|
||||
func epubWebView(_ webView: RDEPUBWebView, didLogJavaScriptError message: String)
|
||||
func epubWebViewDidFinishRendering(_ webView: RDEPUBWebView)
|
||||
}
|
||||
|
||||
public extension RDEPUBWebViewDelegate {
|
||||
func epubWebView(_ webView: RDEPUBWebView, didRequestSelectionAction action: RDEPUBAnnotationMenuAction) {}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
public final class RDEPUBWebView: UIView {
|
||||
public weak var delegate: RDEPUBWebViewDelegate?
|
||||
public var onRendered: (() -> Void)?
|
||||
|
||||
var publication: RDEPUBPublication?
|
||||
var currentRenderRequest: RDEPUBRenderRequest?
|
||||
var webView: WKWebView?
|
||||
var schemeHandler: RDEPUBResourceURLSchemeHandler?
|
||||
var configuredPublicationKey: String?
|
||||
var currentLoadSignature: String?
|
||||
var currentSpineIndex = 0
|
||||
var currentHref = ""
|
||||
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] = []
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user