- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
143 lines
5.2 KiB
Swift
143 lines
5.2 KiB
Swift
|
|
import UIKit
|
|
import WebKit
|
|
|
|
extension RDEPUBWebView {
|
|
private var normalSearchOverlayColor: UIColor {
|
|
UIColor(red: 0.21, green: 0.48, blue: 0.95, alpha: 0.16)
|
|
}
|
|
|
|
private var activeSearchOverlayColor: UIColor {
|
|
UIColor(red: 0.14, green: 0.42, blue: 0.95, alpha: 0.34)
|
|
}
|
|
|
|
func applySearchDecorationsIfNeeded(completion: (() -> Void)? = nil) {
|
|
guard let webView else {
|
|
completion?()
|
|
return
|
|
}
|
|
|
|
let presentation: RDEPUBSearchPresentation?
|
|
switch currentRenderRequest {
|
|
case .reflowable(let request):
|
|
presentation = request.searchPresentation
|
|
case .fixed(let request):
|
|
presentation = request.searchPresentation
|
|
case .none:
|
|
presentation = nil
|
|
}
|
|
|
|
let script = RDEPUBJavaScriptBridge.applySearchScript(for: presentation)
|
|
webView.evaluateJavaScript(script) { [weak self] _, error in
|
|
guard let self else {
|
|
completion?()
|
|
return
|
|
}
|
|
if let error {
|
|
self.delegate?.epubWebView(self, didLogJavaScriptError: error.localizedDescription)
|
|
}
|
|
self.refreshNativeDecorationsIfNeeded(completion: completion)
|
|
}
|
|
}
|
|
|
|
func refreshNativeDecorationsIfNeeded(completion: (() -> Void)? = nil) {
|
|
guard let webView else {
|
|
onDecorationsResolved?([])
|
|
completion?()
|
|
return
|
|
}
|
|
|
|
webView.evaluateJavaScript(RDEPUBJavaScriptBridge.resolveDecorationsScript()) { [weak self] result, error in
|
|
guard let self else {
|
|
completion?()
|
|
return
|
|
}
|
|
if let error {
|
|
self.delegate?.epubWebView(self, didLogJavaScriptError: error.localizedDescription)
|
|
self.onDecorationsResolved?([])
|
|
completion?()
|
|
return
|
|
}
|
|
|
|
let decorations = self.nativeDecorations(from: result)
|
|
self.onDecorationsResolved?(decorations)
|
|
completion?()
|
|
}
|
|
}
|
|
|
|
private func nativeDecorations(from result: Any?) -> [RDEPUBTextOverlayDecoration] {
|
|
guard let payload = result as? [String: Any] else { return [] }
|
|
|
|
let highlightDecorations = decorationList(
|
|
from: payload["highlights"] as? [[String: Any]],
|
|
defaultColor: overlayColor(hex: "#F8E16C", alpha: 0.45) ?? UIColor(red: 248 / 255, green: 225 / 255, blue: 108 / 255, alpha: 0.45)
|
|
)
|
|
let searchDecorations = decorationList(
|
|
from: payload["search"] as? [[String: Any]],
|
|
defaultColor: normalSearchOverlayColor
|
|
)
|
|
return highlightDecorations + searchDecorations
|
|
}
|
|
|
|
private func decorationList(
|
|
from items: [[String: Any]]?,
|
|
defaultColor: UIColor
|
|
) -> [RDEPUBTextOverlayDecoration] {
|
|
guard let items else { return [] }
|
|
|
|
return items.enumerated().compactMap { index, item -> RDEPUBTextOverlayDecoration? in
|
|
guard let kindString = item["kind"] as? String,
|
|
let kind = RDEPUBTextOverlayDecoration.Kind(rawValue: kindString),
|
|
let rectPayloads = item["rects"] as? [[String: Any]] else {
|
|
return nil
|
|
}
|
|
|
|
let rects = rectPayloads.compactMap { rectPayload -> CGRect? in
|
|
guard let x = (rectPayload["x"] as? NSNumber)?.doubleValue,
|
|
let y = (rectPayload["y"] as? NSNumber)?.doubleValue,
|
|
let width = (rectPayload["width"] as? NSNumber)?.doubleValue,
|
|
let height = (rectPayload["height"] as? NSNumber)?.doubleValue else {
|
|
return nil
|
|
}
|
|
return CGRect(x: x, y: y, width: width, height: height)
|
|
}
|
|
guard !rects.isEmpty else { return nil }
|
|
|
|
let color: UIColor
|
|
switch kind {
|
|
case .underline:
|
|
let hex = item["color"] as? String ?? "#F8E16C"
|
|
color = overlayColor(hex: hex, alpha: 1) ?? defaultColor
|
|
case .activeSearch:
|
|
color = activeSearchOverlayColor
|
|
case .search:
|
|
color = normalSearchOverlayColor
|
|
case .highlight:
|
|
let hex = item["color"] as? String ?? "#F8E16C"
|
|
color = overlayColor(hex: hex, alpha: 0.45) ?? defaultColor
|
|
case .selection, .locate:
|
|
color = defaultColor
|
|
}
|
|
|
|
return RDEPUBTextOverlayDecoration(
|
|
kind: kind,
|
|
absoluteRange: NSRange(location: index, length: 1),
|
|
rects: rects,
|
|
color: color
|
|
)
|
|
}
|
|
}
|
|
|
|
private func overlayColor(hex: String, alpha: CGFloat) -> UIColor? {
|
|
var value = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
value = value.replacingOccurrences(of: "#", with: "")
|
|
guard value.count == 6, let hexValue = Int(value, radix: 16) else { return nil }
|
|
return UIColor(
|
|
red: CGFloat((hexValue >> 16) & 0xFF) / 255,
|
|
green: CGFloat((hexValue >> 8) & 0xFF) / 255,
|
|
blue: CGFloat(hexValue & 0xFF) / 255,
|
|
alpha: alpha
|
|
)
|
|
}
|
|
}
|