mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Fix rect annotations: host-side overlay instead of in-iframe SVG
The in-iframe SVG overlay was invisible on PDFs for two reasons: - pdf.js applies transform: scale(1/devicePixelRatio) to the iframe's <html>; an overlay inside that document shrinks into the top-left corner on any dpr != 1 display. The transform is also applied only after the page render, so load-time-injected overlays were sized before the scale factor existed. - Fraction rect geometry itself was correct (selection rects and the denominator are both post-transform), but nothing inside the iframe can escape its html transform. Render annotations host-side instead: a div inside the frame's wrapper element with percentage-positioned children. The wrapper box always equals the visible page area for both formats — comics (iframe CSS-scaled inside it) and PDFs (re-rendered at true scale) — so the overlay is immune to the html transform, zoom re-renders, iframe scaling, and host pan/zoom, still with zero re-anchoring. The in-iframe click hit-test is unchanged (it compares fractions against the same post-transform denominator).
This commit is contained in:
+43
-35
@@ -773,48 +773,55 @@ export class FixedLayout extends HTMLElement {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full-bleed SVG in the page iframe with a 0-100 viewBox and
|
// Host-side overlay: a div inside the frame's wrapper element, children
|
||||||
// preserveAspectRatio:none: fraction-space rects render at any scale,
|
// positioned in percentages of the element box (which always equals the
|
||||||
// so iframe CSS-zoom (comics) and PDF re-renders need no re-anchoring.
|
// visible page area). This is immune to iframe-internal transforms
|
||||||
#ensureOverlay(doc) {
|
// (pdf.js scales <html> by 1/devicePixelRatio, which would shrink an
|
||||||
if (!doc?.body) return null;
|
// in-document overlay), comic iframe CSS-scaling, PDF hi-res re-renders,
|
||||||
let svg = doc.getElementById("foliate-rect-overlay");
|
// and the host transform-based pan/zoom — no re-anchoring anywhere.
|
||||||
if (!svg) {
|
#ensureOverlay(frame) {
|
||||||
const NS = "http://www.w3.org/2000/svg";
|
if (!frame?.element) return null;
|
||||||
svg = doc.createElementNS(NS, "svg");
|
let overlay = frame.element.querySelector(
|
||||||
svg.id = "foliate-rect-overlay";
|
":scope > .foliate-rect-overlay",
|
||||||
svg.setAttribute("viewBox", "0 0 100 100");
|
);
|
||||||
svg.setAttribute("preserveAspectRatio", "none");
|
if (!overlay) {
|
||||||
svg.style.cssText = `position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;`;
|
overlay = frame.element.ownerDocument.createElement("div");
|
||||||
doc.body.appendChild(svg);
|
overlay.className = "foliate-rect-overlay";
|
||||||
|
overlay.style.cssText =
|
||||||
|
"position:absolute;inset:0;pointer-events:none;";
|
||||||
|
frame.element.style.position = "relative";
|
||||||
|
frame.element.appendChild(overlay);
|
||||||
}
|
}
|
||||||
return svg;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
#renderAnnotationsInto(doc, index) {
|
#renderAnnotationsIntoFrame(frame) {
|
||||||
const svg = this.#ensureOverlay(doc);
|
if (!frame || frame.blank || frame.index == null) return;
|
||||||
if (!svg) return;
|
const overlay = this.#ensureOverlay(frame);
|
||||||
const NS = "http://www.w3.org/2000/svg";
|
if (!overlay) return;
|
||||||
svg.replaceChildren();
|
const doc = frame.element.ownerDocument;
|
||||||
|
overlay.replaceChildren();
|
||||||
for (const a of this.#rectAnnotations.values()) {
|
for (const a of this.#rectAnnotations.values()) {
|
||||||
if (a.index !== index) continue;
|
if (a.index !== frame.index) continue;
|
||||||
for (const [x, y, w, h] of a.rects) {
|
for (const [x, y, w, h] of a.rects) {
|
||||||
const rect = doc.createElementNS(NS, "rect");
|
const rect = doc.createElement("div");
|
||||||
rect.setAttribute("x", String(x * 100));
|
Object.assign(rect.style, {
|
||||||
rect.setAttribute("y", String(y * 100));
|
position: "absolute",
|
||||||
rect.setAttribute("width", String(w * 100));
|
left: `${x * 100}%`,
|
||||||
rect.setAttribute("height", String(h * 100));
|
top: `${y * 100}%`,
|
||||||
rect.setAttribute("fill", a.color || "#ffd54f");
|
width: `${w * 100}%`,
|
||||||
rect.setAttribute("fill-opacity", "0.35");
|
height: `${h * 100}%`,
|
||||||
svg.appendChild(rect);
|
backgroundColor: a.color || "#ffd54f",
|
||||||
|
opacity: "0.35",
|
||||||
|
borderRadius: "2px",
|
||||||
|
});
|
||||||
|
overlay.appendChild(rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#renderAnnotationsForIndex(index) {
|
#renderAnnotationsForIndex(index) {
|
||||||
const frame = this.#frameForIndex(index);
|
this.#renderAnnotationsIntoFrame(this.#frameForIndex(index));
|
||||||
const doc = frame?.iframe?.contentDocument;
|
|
||||||
if (doc) this.#renderAnnotationsInto(doc, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addRectAnnotation({ key, index, rects, color }) {
|
addRectAnnotation({ key, index, rects, color }) {
|
||||||
@@ -865,9 +872,10 @@ export class FixedLayout extends HTMLElement {
|
|||||||
iframe,
|
iframe,
|
||||||
index,
|
index,
|
||||||
});
|
});
|
||||||
// Re-render persisted annotations into the fresh document
|
// Re-render persisted annotations into the fresh frame (frames
|
||||||
// (frames are recreated on every spread change).
|
// are recreated on every spread change; the spread fields aren't
|
||||||
this.#renderAnnotationsInto(doc, index);
|
// assigned yet, so pass the frame under construction directly).
|
||||||
|
this.#renderAnnotationsIntoFrame({ element, iframe, index });
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
element,
|
element,
|
||||||
|
|||||||
Reference in New Issue
Block a user