fix(sync): anchor device highlight ranges to selection text
KOReader highlights converted to wrong or unresolvable web CFIs whenever a selection crossed text-node boundaries. Three compounding defects: ParseCREXPointer discarded the text()[N] node index and applied .offset as a global block offset; the end anchor converted with an empty context (blind structural landing, zero verification); and the block-containment check accepted wrong-node landings because a selection's text always occurs somewhere in its own block. pos0 and pos1 were converted independently, yielding mismatched start/end base paths and backwards ranges - highlights stored but never painted, or painted at the wrong location, in the web reader. Add ConvertHighlightRange: both endpoints are anchored by locating the selection text in the flattened document text (whitespace-normalized, crossing block boundaries), with the device locators demoted to disambiguation hints. A single document match yields a same-spine, ordered start/end pair by construction. Falls back to the legacy per-endpoint path when the text cannot be located, so behavior is never worse than before. Also fix the shared flatten stream: zero-width format characters (ZWSP/ZWNJ/BOM) and whitespace-only inline spans (<span> </span>) now act as word separators instead of fusing adjacent words, which prevented text matching in real books. Verified against 28 real device highlights from production: converter output now matches the text-search oracle on all of them (previously 8/28 correct, 18 with mismatched base paths, 2 backwards).
This commit is contained in:
1 parent
cbdddc095e
commit
bb3aef517c
4 files changed
+227
-5
No files matched your search
@@ -93,7 +93,22 @@ func (h *KOReaderHandler) convertHighlightPositions(ec annotationEpub, pos0, pos
|
||||
if pos0 == "" || !ec.convertible() {
|
||||
return "", ""
|
||||
}
|
||||
startLoc := wsync.ConvertToCanonical(wsync.LocatorSourceKOReader, pos0, percentage, contextText, ec.mediaItem.FormatGroup, ec.epubPath, "")
|
||||
// Primary: anchor both endpoints by the selection text itself. One
|
||||
// document match yields a same-spine ordered pair — the invariant the
|
||||
// web reader's range-CFI builder needs. Independent per-endpoint
|
||||
// conversion mis-anchors any selection past a block's first text node
|
||||
// (crengine text-node indices don't survive to the raw XHTML).
|
||||
startLoc := wsync.ConvertHighlightRangeToCanonical(ec.epubPath, ec.mediaItem.FormatGroup, pos0, pos1, contextText, percentage)
|
||||
if startLoc.Precision == "exact" || startLoc.Precision == "structural" {
|
||||
startCFI := webUsableCFI(startLoc)
|
||||
endCFI := ""
|
||||
if startCFI != "" && startLoc.EndCFI != "" {
|
||||
endCFI = webUsableCFI(wsync.CanonicalLocator{CFI: startLoc.EndCFI, Precision: startLoc.Precision})
|
||||
}
|
||||
return startCFI, endCFI
|
||||
}
|
||||
// Fallback: legacy independent per-endpoint conversion.
|
||||
startLoc = wsync.ConvertToCanonical(wsync.LocatorSourceKOReader, pos0, percentage, contextText, ec.mediaItem.FormatGroup, ec.epubPath, "")
|
||||
startCFI := webUsableCFI(startLoc)
|
||||
endCFI := ""
|
||||
// A text-search start matched the selection text itself: its extent
|
||||
|
||||
Reference in new issue
Block a user