Skip to content

feat(html): honour the manual page break, and stop calling the automatic one a break - #809

Merged
andiwand merged 1 commit into
mainfrom
feat/manual-page-breaks
Sep 2, 2026
Merged

feat(html): honour the manual page break, and stop calling the automatic one a break#809
andiwand merged 1 commit into
mainfrom
feat/manual-page-breaks

Conversation

@andiwand

@andiwand andiwand commented Sep 2, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Steps 1 and 2 of the plan in #174 (comment).

The signal was wrong

ElementType::page_break came from ODF's text:soft-page-break, which [OpenDocument] 5.1.1 makes a record of where the producer's layout engine broke pages — informational, not authored. In about.odt one sits mid-heading, between a draw:frame and the word "Features". Rendering it would re-impose LibreOffice's pagination for LibreOffice's page size on a viewport that has nothing to do with it. It was 240 of the 312 elements #805 measured the renderer dropping. It is no longer parsed.

The real break was not modelled

format manual break before now
ODF fo:break-before / fo:break-after on the paragraph style
OOXML w:pageBreakBefore in w:pPr ✗ (docx produced no break at all)
RTF \page ✓ element ✓ element
.doc \x0C ✓ element ✓ element

New BreakType { none, page, column } and ParagraphStyle::break_before/break_after. The explicit none is load-bearing: sample1.docx's TOCHeading is w:basedOn="Heading1", which breaks, and says <w:pageBreakBefore w:val="0"/> to stop — so "off" has to be told from "unsaid" or the derived style inherits a break it turned off.

What the renderer does

The text view wrote one .odr-page-outer sheet. It now closes it and opens the next at a manual break, from either source. This is not pagination: nothing computes where a page ends, so the boxes are of unequal height. A box means "what the author put on a page of its own", never "what fits on a page" — which is why it does not wait for #76, it sidesteps it. A document with no manual break is the one box it always was, and .odr-pages already stacks and centres them with a 16px gap, so no CSS changed.

break-before:page goes out on the paragraph alongside, which is what print and PDF export need. BreakType::none writes nothing — it is the CSS initial value.

mixed-layout.odt, the fixture the JNI, Apple and wasm suites share, now renders as the four sheets it is:

sheet 1: Portrait 1    sheet 2: Portrait 2    sheet 3: Landscape 1    sheet 4: Portrait 3

Verification

  • core: full suite, 1399 passed / 6 skipped, and the emitted tree matches the regenerated reference exactly. Nine new tests: the ODF ones build flat-ODT inline (style read, autonone, the split, no empty leading sheet, soft break is not content, reflow gets css only); the OOXML one proves the on/off inheritance chain.
  • JNI / Python / Apple: all suites pass; BreakType and the two fields are mirrored in each.
  • feat(html): warn on the element types the renderer drops, instead of dropping them silently #805's dropped-element warning now reports zero across the corpus, down from 312.

Reference output

39 files changed, all odt/docx/doc — nothing else moved. Pins advanced to 3dcc4fb / 9e9cc58.

  • Their text is byte-identical after tag stripping, all 39.
  • 13 gained page boxes (785KB.doc 1→65, Vektoranalysis Zusammenfassung.odt 1→107).
  • 14 gained a css break where the box could not be split. Checked through the Python bindings that in every one the break is either on the very first block (where splitting would open an empty sheet) or nested below the text root: zero cases where a top-level break failed to split.
  • 12 differ only in the data-odr-path sibling indices the removed soft breaks used to occupy. No pixel or behaviour change — frontend.cpp selects on the attribute's presence, never its value.

Not in this PR

  • Step 3: docx's inline <w:br w:type="page"/> (66 corpus occurrences) sits inside a run, so honouring it visually means splitting the paragraph. break-before:page from step 1 already handles it for print.
  • A break may also switch master page, so mixed-layout.odt genuinely has two page geometries where text_root_first_master_page gives us one. Per-sheet page layout is its own issue.

…tic one a break

`ElementType::page_break` came from ODF's `text:soft-page-break`, which
[OpenDocument] 5.1.1 makes a record of where the *producer's* layout broke
pages — not a break the author set. Rendering it would re-impose one writer's
pagination on a viewport that has nothing to do with it, and it accounted for
240 of the 312 elements the renderer silently dropped. It is no longer parsed.

The real manual break was not modelled at all: it is `fo:break-before` /
`fo:break-after` on the paragraph style in ODF and `w:pageBreakBefore` in
OOXML, so docx produced no break element whatsoever. New `BreakType` and
`ParagraphStyle::break_before`/`break_after` carry it, with an explicit `none`
for the break a style turns off — `sample1.docx`'s `TOCHeading` derives from a
heading that breaks and says `w:val="0"` to stop.

The text view then closes its page box and opens the next one at a break,
whether it arrives as the style property or, for rtf and `.doc`, as the
`page_break` node those formats write. Not pagination: nothing computes where a
page *ends*, so the boxes are of unequal height and a document with no manual
break is the one box it always was. `.odr-pages` already stacks and centres
them. `break-before:page` goes out alongside, which is what print and PDF
export need.

Mirrored in the JNI, Apple and Python bindings.

Reference output regenerated: 39 files, all odt/docx/doc. Their text is
byte-identical; 13 gained page boxes, 14 gained a css break where the box could
not be split, and 12 differ only in the `data-odr-path` indices the removed
soft breaks used to occupy.

Towards #174.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018cMYRXLJdiCkH65Jm2W9B5
@andiwand
andiwand force-pushed the feat/manual-page-breaks branch from c55628a to 6c65ce5 Compare September 2, 2026 19:06
@andiwand
andiwand merged commit b23533a into main Sep 2, 2026
25 checks passed
@andiwand
andiwand deleted the feat/manual-page-breaks branch September 2, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant