Skip to content

fix(docx): keep a table's top edge on the page the layout moves it to - #821

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-table-space-new-page
Oct 2, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-table-space-new-page

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Why

The layout keeps a block's own top edge where a page break moves the block down. LayoutCompiler starts the new page and places the block at pageTop − margin.top. The gap before it, and the bottom edge of the block above, are used up at the foot of the page above.

Word has no space above a table, so the export writes a table's or a row's top edge below the paragraph before it. Where the page broke between them, that space stayed on the page above.

The long LumaStudioInvoice starts its third page with the notes and payment pair: a row with a 12pt top margin, moved there by the break. In Word, and in LibreOffice, the pair and everything in it stood 12pt high. Those are 20 of Word's corpus lines more than 2pt off.

What changed

  • startsAPageOfItsOwn. A table or row in the body whose placement starts on a later page than the last block written there ended on (lastEndPage).
    • lastEndPage is updated as each block in the body is written, in writeNode, which now wraps writePlacedNode.
    • It is reset at every section, since the layout counts a section's pages from its own first.
  • writeTableWithItsOwnSpacing. For such a block, what is owed below the block above is written on the page above (flushSpacingAfter), a band's resumed gap among it (resumeHere first), and a border or text hanging below that block is left there, as writePageBreak leaves it. Only the block's own top edge is then owed.
  • holdTheSpaceAboveOnItsPage. That edge, with any container edge opening there, becomes the height of a line of its own (w:lineRule="exact"), kept with the table (w:keepNext), its mark a point tall.
    • Measured in Word: a paragraph's space above at the top of a page is dropped, and a line's height is kept. A hairline paragraph with w:before="244" before the table left the pair where it was, and an exact 244-twip line put it where the page has it.
  • Baselines. word-windows, libreoffice-windows and libreoffice-linux are rewritten for LumaStudioInvoice (long), the one document this moves. The Linux one is taken from this PR's CI run (docx-fidelity-37047051867), where it goes from 22 lines past 2pt to 2.
  • Docs. CHANGELOG.md, and the Tables row of docs/recipes/docx-export.md.

Verification

  • Template corpus, re-measured in full. Only the long LumaStudioInvoice changes, in both editors.

    Editor Past 2pt Median p90
    Word 16.0.20430 20 → 0 0.23 → 0.22pt 11.75 → 0.38pt
    LibreOffice (Windows) 22 → 2 0.24 → 0.20pt 11.83 → 0.41pt
  • The third page in Word. "NOTES" stands at 73.68 against the page's 73.43, where it stood 11.75 high.

  • New test class, DocxSpaceOnANewPageTest, four tests.

    • A row the layout moves to a new page writes a line of its 12pt top edge (240 twips, exact, kept with the row). The paragraph above keeps only the 4pt gap below it.
    • A table moved to a new page does the same.
    • A row moved to a new page after a table holds its edge in the line between the two tables, which also keeps them apart.
    • A row on the page of the block before it is written as before, the gap and its edge below that block.
  • Each rule fails the first test when taken out.

    • Without the line, the row's edge is not a line's height.
    • Without the flush, the line holds the gap too.
  • Full reactor gate: ./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am gives BUILD SUCCESS. render-docx runs 817 tests, qa 1814.

Known limits

  • A paragraph moved to a new page does not yet keep its own top edge. ModernProfessional's "Additional Information" opens its second page 7.65pt high in Word, and Executive's second page about 3pt.
    • Writing the gap before such a paragraph on the page above changes where Word breaks the page. Measured: ClassicSerif's second page lost a line to its first, and stood 16pt high.
  • The gap is left at the foot of the page above. Where it does not fit there, the layout carries it to the new page too, and the export writes only the edge.
  • A first row raised above its cell (raiseRows) is not raised on the new page. The spacer is a line, not space above, and gives it nothing to take from. No document in the corpus has one.

Lane: shared-engine (render-docx). No public API change.

@DemchaAV
DemchaAV merged commit fae96d7 into 2.5-dev Oct 2, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-table-space-new-page branch October 2, 2026 21:08
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