diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 1a580954..8329d5d7 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -1798,8 +1798,12 @@ document.addEventListener("alpine:init", () => { if (!map || !r) return; const vertical = edge === "left" || edge === "right"; // Band: the rect's y-range widened to catch neighbouring line - // boundaries (horizontal-edge drags snap to line tops/bottoms). - const bandPad = vertical ? 0.002 : Math.max(r[3] * 0.75, 0.008); + // boundaries — but only ±quarter height now: a vertical drag adjusts + // THIS line box's height and can no longer swallow a neighbouring + // line (that produced sliver rects whose text recompute swept + // fragments — "some 14ng that scholars…"). Multi-line changes go + // through re-selection below. + const bandPad = vertical ? 0.002 : Math.max(r[3] * 0.25, 0.004); const bandTop = r[1] - bandPad; const bandBottom = r[1] + r[3] + bandPad; const band = this.pdfCharBoxes(map.doc, bandTop, bandBottom); @@ -2150,6 +2154,47 @@ document.addEventListener("alpine:init", () => { const range = sel.getRangeAt(0); const text = sel.toString().replace(/\s+/g, " ").trim(); if (!text) return; + // Adjust re-selection (v2): a selection made during a rect-edge + // session REPLACES the working extent — any lines, partial lines + // (the exact gap the grips could not express). Rects rebuild from + // the selection's line boxes, text re-reads, saved immediately: + // the grips' release-to-save parity. + if (this.pdfAdjust) { + const canvasRect = + (doc.querySelector("#canvas canvas") as HTMLElement) || + (doc.querySelector("img") as HTMLElement); + const dr = canvasRect?.getBoundingClientRect(); + if (!dr || !dr.width || !dr.height) return; + const rects: number[][] = []; + for (const r of range.getClientRects()) { + const x = (r.left - dr.left) / dr.width; + const y = (r.top - dr.top) / dr.height; + const w = r.width / dr.width; + const h = r.height / dr.height; + if (w > 0 && h > 0) rects.push([x, y, w, h]); + } + if (!rects.length) return; + const t = this.pdfTextForRects(doc, rects); + if (!t) { + showToast("Highlights can't be empty", "error"); + return; + } + const session = this.pdfAdjust; + session.committed = true; + this.cancelPdfAdjust(); + const p = this.selectionPopover; + p.id = session.id; + p.pdfPage = session.page; + p.pdfRects = rects; + p.text = t; + this.saveHighlightChanges(); + try { + sel.removeAllRanges(); + } catch { + /* doc may be gone */ + } + return; + } // Denominator: the element whose post-transform screen rect IS // the visible page. For PDFs that's the rendered canvas — pdf.js // scales by 1/devicePixelRatio, so documentElement's rect