diff --git a/package.json b/package.json index 42d8583..d1354c2 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dev": "npm run build:ts:dev && npm run build:css" }, "dependencies": { - "@bookhoard/foliate-js": "git+https://github.com/john-okeefe/foliate-js.git#d065495", + "@bookhoard/foliate-js": "git+https://github.com/john-okeefe/foliate-js.git#86e234d", "alpinejs": "^3.15.8", "chart.js": "^4.5.1", "highlight.js": "^11.11.1", diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 8e97401..ad6fde8 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -702,14 +702,26 @@ document.addEventListener("alpine:init", () => { const range = sel.getRangeAt(0); const text = sel.toString().replace(/\s+/g, " ").trim(); if (!text) return; - // Denominator in the same (transform-inclusive) coordinate - // space as getClientRects so the devicePixelRatio transform - // pdf.js applies to cancels in the fraction. + // 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 + // is dpr× too small and would inflate every fraction (highlight + // shifted right/oversized on any dpr != 1 display). The canvas's + // rect is in the same transform-inclusive space as the textLayer + // span rects, so the dpr scaling cancels exactly. const denom = - (doc.querySelector("img") as HTMLElement) || - doc.documentElement; - const dr = denom.getBoundingClientRect(); - if (!dr.width || !dr.height) return; + (doc.querySelector("#canvas canvas") as HTMLElement) || + (doc.querySelector("img") as HTMLElement); + let dr = denom?.getBoundingClientRect(); + if (!dr || !dr.width || !dr.height) { + const vw = doc.defaultView; + dr = { + left: 0, + top: 0, + width: vw?.innerWidth || 1, + height: vw?.innerHeight || 1, + } as DOMRect; + } const rects: number[][] = []; for (const r of range.getClientRects()) { const x = (r.left - dr.left) / dr.width; @@ -719,8 +731,10 @@ document.addEventListener("alpine:init", () => { if (w > 0 && h > 0) rects.push([x, y, w, h]); } if (!rects.length) return; - // Map the first rect to host-space for popover placement, - // accounting for the iframe's own scale factor. + // Map the first rect to host-space for popover placement. + // The canvas's screen rect maps 1:1 onto the host iframe box + // (the visible page fills the iframe), so sx/sy are 1 for PDFs; + // kept general for the comic img fallback. const frame = doc.defaultView?.frameElement as HTMLElement | null; if (!frame) return; const fr = frame.getBoundingClientRect();