From 5615d5b2c102d87f5b9aecb2eeb654eb6eb788c5 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Mon, 10 Aug 2026 07:39:13 +0000 Subject: [PATCH] fix: a paragraph's fo:break-after breaks the page after it The LibreOffice "page break" paragraph style carries the break on its own end, not on the block that follows; unread, a fixture's title page ran straight into the copyright page. --- src/lib/import/odt.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/import/odt.ts b/src/lib/import/odt.ts index 8dc87f2..f43775e 100644 --- a/src/lib/import/odt.ts +++ b/src/lib/import/odt.ts @@ -829,6 +829,17 @@ function convertBlocks(elements: Element[], ctx: Ctx, kind: BlockKind, boldByDef } } } + // A block's own "break after" becomes the next block's break before — the same page + // break, and the only one the editor stores. A trailing one has nothing left to break, + // and only a paragraph/heading carries the attr. + for (let i = 0; i < out.length; i++) { + if (out[i].attrs?.breakAfter !== 'page') continue; + delete out[i].attrs!.breakAfter; + const next = out[i + 1]; + if (next && (next.type === 'paragraph' || next.type === 'heading')) { + next.attrs = { ...next.attrs, breakBefore: 'page' }; + } + } return out; } @@ -1219,6 +1230,9 @@ function blockAttrs(paraProps: PropMap, textProps: PropMap, defaults: BlockDefau // (pageBreaks.ts forces them to the next page top); the editor has no column // breaks, so only "page". Cell/list blocks can't carry it. if (kind === 'body' && paraProps['fo:break-before'] === 'page') attrs.breakBefore = 'page'; + // fo:break-after is the same break seen from the block above; convertBlocks moves it + // onto the next block, which is what the editor can express. + if (kind === 'body' && paraProps['fo:break-after'] === 'page') attrs.breakAfter = 'page'; // Widow-orphan control: LibreOffice writes 0 for "off", absent means the XSL-FO // default of 2 (on) — so only an explicit 0 disables it.