feat(wxread): align pagination, rendering, and docs

This commit is contained in:
shen
2026-05-24 15:36:01 +08:00
parent a318c0e3d0
commit 2a94921e88
103 changed files with 6124 additions and 4623 deletions
@@ -17,8 +17,10 @@ struct RDEPUBTextOverlayDecoration {
}
final class RDEPUBSelectionOverlayView: UIView {
private var page: RDEPUBTextPage?
private var selectionRange: NSRange?
private(set) var page: RDEPUBTextPage?
private var snapshot: RDEPUBPageLayoutSnapshot?
private(set) var selectionRange: NSRange?
private var selectionRects: [CGRect] = []
private var decorations: [RDEPUBTextOverlayDecoration] = []
private let selectionVerticalAdjustment: CGFloat = -1
@@ -39,16 +41,28 @@ final class RDEPUBSelectionOverlayView: UIView {
fatalError("init(coder:) has not been implemented")
}
func configure(page: RDEPUBTextPage, selectionColor: UIColor) {
func configure(page: RDEPUBTextPage, selectionColor: UIColor, snapshot: RDEPUBPageLayoutSnapshot? = nil) {
self.page = page
self.snapshot = snapshot
self.selectionColor = selectionColor
selectionRange = nil
decorations = []
setNeedsDisplay()
}
func updateSnapshot(_ snapshot: RDEPUBPageLayoutSnapshot?) {
self.snapshot = snapshot
}
func updateSelection(absoluteRange: NSRange?) {
selectionRange = absoluteRange
selectionRects = []
setNeedsDisplay()
}
func updateSelection(absoluteRange: NSRange?, rects: [CGRect]) {
selectionRange = absoluteRange
selectionRects = rects
setNeedsDisplay()
}
@@ -62,6 +76,32 @@ final class RDEPUBSelectionOverlayView: UIView {
}
func absoluteRange(at point: CGPoint) -> NSRange? {
if let selectionRange,
selectionRects.contains(where: { $0.insetBy(dx: -4, dy: -4).contains(point) }) {
return selectionRange
}
if let snapshot,
let attachment = snapshot.attachment(at: point) {
return attachment.stringRange
}
if let snapshot {
let hitDecorations = snapshot.rects(containing: point, in: resolvedDecorations)
if let mostSpecific = hitDecorations.min(by: { lhs, rhs in
lhs.absoluteRange.length < rhs.absoluteRange.length
}) {
return mostSpecific.absoluteRange
}
}
for decoration in resolvedDecorations {
for rect in decoration.rects {
if rect.insetBy(dx: -4, dy: -4).contains(point) {
return decoration.absoluteRange
}
}
}
return nil
}
@@ -110,21 +150,21 @@ final class RDEPUBSelectionOverlayView: UIView {
}
private var resolvedDecorations: [RDEPUBTextOverlayDecoration] {
guard let page else { return [] }
guard page != nil else { return [] }
let nonSelection = decorations.filter { !$0.rects.isEmpty }
guard let selectionRange else { return nonSelection }
var result = decorations.filter { !$0.rects.isEmpty }
let selectionRects:[CGRect] = []
guard !selectionRects.isEmpty else { return nonSelection }
return [
RDEPUBTextOverlayDecoration(
kind: .selection,
absoluteRange: selectionRange,
rects: selectionRects,
color: selectionColor
if let selectionRange, !selectionRects.isEmpty {
result.append(
RDEPUBTextOverlayDecoration(
kind: .selection,
absoluteRange: selectionRange,
rects: selectionRects,
color: selectionColor
)
)
]
}
return result
}
}