feat(epub): align text rendering with WXRead
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import UIKit
|
||||
|
||||
struct RDEPUBTextOverlayDecoration {
|
||||
enum Kind: String {
|
||||
case selection
|
||||
case highlight
|
||||
case underline
|
||||
case search
|
||||
case activeSearch
|
||||
case locate
|
||||
}
|
||||
|
||||
var kind: Kind
|
||||
var absoluteRange: NSRange
|
||||
var rects: [CGRect]
|
||||
var color: UIColor
|
||||
}
|
||||
|
||||
final class RDEPUBSelectionOverlayView: UIView {
|
||||
private var page: RDEPUBTextPage?
|
||||
private var selectionRange: NSRange?
|
||||
private var decorations: [RDEPUBTextOverlayDecoration] = []
|
||||
private let selectionVerticalAdjustment: CGFloat = -1
|
||||
|
||||
var selectionColor: UIColor = UIColor(red: 70 / 255, green: 140 / 255, blue: 1, alpha: 0.24) {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
isOpaque = false
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(page: RDEPUBTextPage, selectionColor: UIColor) {
|
||||
self.page = page
|
||||
self.selectionColor = selectionColor
|
||||
selectionRange = nil
|
||||
decorations = []
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
func updateSelection(absoluteRange: NSRange?) {
|
||||
selectionRange = absoluteRange
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
func applyDecorations(_ decorations: [RDEPUBTextOverlayDecoration]) {
|
||||
self.decorations = decorations
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
func clearSelection() {
|
||||
updateSelection(absoluteRange: nil)
|
||||
}
|
||||
|
||||
func absoluteRange(at point: CGPoint) -> NSRange? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decorationSummary() -> String {
|
||||
let counts = Dictionary(grouping: resolvedDecorations, by: \.kind).mapValues(\.count)
|
||||
let selectionLabel = selectionRange.map(NSStringFromRange) ?? "none"
|
||||
let highlightCount = counts[.highlight, default: 0]
|
||||
let underlineCount = counts[.underline, default: 0]
|
||||
let searchCount = counts[.search, default: 0]
|
||||
let activeSearchCount = counts[.activeSearch, default: 0]
|
||||
let locateLabel = resolvedDecorations.first(where: { $0.kind == .locate }).map { NSStringFromRange($0.absoluteRange) } ?? "none"
|
||||
return [
|
||||
"selection \(selectionLabel)",
|
||||
"highlight \(highlightCount)",
|
||||
"underline \(underlineCount)",
|
||||
"search \(searchCount)",
|
||||
"activeSearch \(activeSearchCount)",
|
||||
"locate \(locateLabel)"
|
||||
].joined(separator: " · ")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
for decoration in resolvedDecorations {
|
||||
switch decoration.kind {
|
||||
case .underline:
|
||||
context.setStrokeColor(decoration.color.cgColor)
|
||||
context.setLineWidth(2)
|
||||
for underlineRect in decoration.rects {
|
||||
let y = underlineRect.maxY - 1
|
||||
context.move(to: CGPoint(x: underlineRect.minX, y: y))
|
||||
context.addLine(to: CGPoint(x: underlineRect.maxX, y: y))
|
||||
context.strokePath()
|
||||
}
|
||||
default:
|
||||
context.setFillColor(decoration.color.cgColor)
|
||||
for selectionRect in decoration.rects {
|
||||
let adjustedRect = selectionRect.offsetBy(dx: 0, dy: selectionVerticalAdjustment)
|
||||
let path = UIBezierPath(roundedRect: adjustedRect.insetBy(dx: -1, dy: -1), cornerRadius: 4)
|
||||
context.addPath(path.cgPath)
|
||||
context.fillPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var resolvedDecorations: [RDEPUBTextOverlayDecoration] {
|
||||
guard let page else { return [] }
|
||||
|
||||
let nonSelection = decorations.filter { !$0.rects.isEmpty }
|
||||
guard let selectionRange else { return nonSelection }
|
||||
|
||||
let selectionRects:[CGRect] = []
|
||||
guard !selectionRects.isEmpty else { return nonSelection }
|
||||
|
||||
return [
|
||||
RDEPUBTextOverlayDecoration(
|
||||
kind: .selection,
|
||||
absoluteRange: selectionRange,
|
||||
rects: selectionRects,
|
||||
color: selectionColor
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user