diff --git a/src/lib/editor/extensions/CLAUDE.md b/src/lib/editor/extensions/CLAUDE.md index 7287a2f..92e6dad 100644 --- a/src/lib/editor/extensions/CLAUDE.md +++ b/src/lib/editor/extensions/CLAUDE.md @@ -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`. diff --git a/src/lib/editor/extensions/pageBreaks.ts b/src/lib/editor/extensions/pageBreaks.ts index 263c5e0..9746dbe 100644 --- a/src/lib/editor/extensions/pageBreaks.ts +++ b/src/lib/editor/extensions/pageBreaks.ts @@ -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;