fix: pagination measures the flow, not the paint correction

A block spaced above single is drawn half a leading above where it flows,
and offsetTop reports the drawn position — so every block a break pushed
to a page top landed that half low. Take the shift back off where the
document is measured.
This commit is contained in:
Steffen Becker committed 2026-08-11 07:43:48 +00:00
1 parent 12f2e7a623
commit 3f6b64cd95
2 files changed
+7 -2

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ Five topics are large enough to have their own deep-dive — read the file befor
- **`fontColor.ts`** (`FontColor`) — `color` attr on the TextStyle mark; also emits `data-color` so theme CSS (allBlack) can target color-bearing spans.
- **`fontWeight.ts`** (`FontWeight`) — `fontWeight` attr on TextStyle (set `normal` to un-bold a heading without changing the node type).
- **`lineHeight.ts`** (`LineHeight`) — `lineHeight` attr on paragraph/heading. `LINE_HEIGHT_RATIO = 1.15`: ODF line spacing multiplies the font's *natural* line height (Liberation Serif ≈1.15× em), CSS multiplies the font size — so the on-screen value is scaled to match what LibreOffice renders. A spacing above single puts **all** of its extra leading below each line (probed: at 150% LibreOffice starts line 1 flush under the block above and adds 6.9pt after every line, the last included); CSS half-leads it, so `editor.css` shifts the block up by that half with `position:relative; top:` — flow, and so pagination, unchanged.
- **`lineHeight.ts`** (`LineHeight`) — `lineHeight` attr on paragraph/heading. `LINE_HEIGHT_RATIO = 1.15`: ODF line spacing multiplies the font's *natural* line height (Liberation Serif ≈1.15× em), CSS multiplies the font size — so the on-screen value is scaled to match what LibreOffice renders. A spacing above single puts **all** of its extra leading below each line (probed: at 150% LibreOffice starts line 1 flush under the block above and adds 6.9pt after every line, the last included); CSS half-leads it, so `editor.css` shifts the block up by that half with `position:relative; top:`, which leaves the flow alone — but **`offsetTop` reports the drawn position, not the flow one**, so `pageBreaks.ts` (`topWithin`) takes the shift back off; without that a block pushed to a page top landed half a leading low.
- **`paragraphSpacing.ts`** (`ParagraphSpacing`) — `spaceBefore`/`spaceAfter` in **pt**, round-tripping 1:1 to `fo:margin-top`/`fo:margin-bottom`. Space after is a margin, space before rides `--space-before` (`storage/spacingModel.ts` decides padding vs margin); pageBreaks.ts drops it where a page break put the block at a page top, as LibreOffice does.
- **`blockFontSize.ts`** — the font of the paragraph mark (Word `w:pPr/w:rPr`, ODF the paragraph's own text properties).
- **`pageBreak.ts`** — the text-flow attrs of a paragraph/heading: `breakBefore: 'page'`, plus `keepNext`/`keepLines` (see `docs/architecture/pagination.md`). Owns `Mod-Enter`.
+6 -1
View File
@@ -627,7 +627,12 @@ export const PageBreaks = Extension.create({
let top = 0;
let node: HTMLElement | null = el;
while (node && node !== dom) {
top += node.offsetTop;
// A block spaced above single is drawn half a leading above its flow
// position (editor.css), and offsetTop reports where it is drawn — so the
// shift comes back off, or the flow inherits a paint correction.
const cs = getComputedStyle(node);
const shift = cs.position === 'relative' ? parseFloat(cs.top) : 0;
top += node.offsetTop - (Number.isFinite(shift) ? shift : 0);
node = node.offsetParent as HTMLElement | null;
}
return top;