fix: unscale the line-number measurements by the zoom factor

Client rects come back in zoom-scaled screen space while the layer,
the page boxes and the per-page restart all work in document px. At
any zoom but 100% every number drifted by the factor: printed beside
the wrong line, restarts fired on the wrong page, and a mark could
land in the page gap.
This commit is contained in:
Steffen Becker committed 2026-08-30 10:06:42 +00:00
1 parent 1c50d2484b
commit 6144b9fde0
1 file changed
+13 -7
+13 -7
View File
@@ -56,8 +56,9 @@
return () => paper?.removeEventListener('pm-pagecount', schedule);
});
// The tops of the lines a block renders, in document px relative to .tiptap.
function lineTops(block: Element, origin: number): { top: number; height: number }[] {
// The tops of the lines a block renders, in document px relative to .tiptap. Client
// rects are in zoom-scaled screen space; `scale` converts back to document px.
function lineTops(block: Element, origin: number, scale: number): { top: number; height: number }[] {
const range = document.createRange();
range.selectNodeContents(block);
let rects = Array.from(range.getClientRects()).filter((r) => r.height > 0);
@@ -71,7 +72,7 @@
}
if (!rects.length) {
const r = block.getBoundingClientRect();
return r.height > 0 ? [{ top: r.top - origin, height: r.height }] : [];
return r.height > 0 ? [{ top: (r.top - origin) / scale, height: r.height / scale }] : [];
}
// One rect per run per line. A run shifted off the baseline (super/subscript, a
// formula, a rotated char) still overlaps its line's span, so rects merge into one
@@ -87,13 +88,18 @@
lines.push({ top: r.top, bottom: r.bottom });
}
}
return lines.map((l) => ({ top: l.top - origin, height: l.bottom - l.top }));
return lines.map((l) => ({ top: (l.top - origin) / scale, height: (l.bottom - l.top) / scale }));
}
function measure(): void {
const view = editor?.view;
if (!view || !lineNumbering.on || !host?.isConnected) { marks = []; return; }
const origin = view.dom.getBoundingClientRect().top;
const dom = view.dom as HTMLElement;
const rect = dom.getBoundingClientRect();
// The zoom transform scales every measured rect; page boxes and the layer's own
// coordinates are unscaled document px, so divide the scale back out.
const scale = dom.offsetWidth ? rect.width / dom.offsetWidth : 1;
const origin = rect.top;
const out: { top: number; label: string }[] = [];
let count = 0;
let page = 1;
@@ -111,8 +117,8 @@
const anchor = block.classList.contains('textbox-node');
const empty = anchor || !block.textContent?.trim();
const lines = anchor
? [{ top: block.getBoundingClientRect().top - origin, height: 18 }]
: lineTops(block, origin);
? [{ top: (block.getBoundingClientRect().top - origin) / scale, height: 18 }]
: lineTops(block, origin, scale);
for (const line of lines) {
const linePage = pageAt(line.top);
if (lineNumbering.restart === 'page' && linePage !== page) { page = linePage; count = 0; }