Skip to content

fix(docx): hold a drawing's room in a row nested through a one-layer stack - #822

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-drawing-room-in-nested-row
Oct 2, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-drawing-room-in-nested-row

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Why

A row cannot sit in a row cell, so templates wrap one in a layer stack of one layer to nest it (layeredRow). Nothing is laid over anything in such a stack, and the export writes the row in the flow, as a nested table.

A drawing in that row — a ShapeContainer or layer stack holding only drawing — is drawn where the page puts it, and takes no room in Word's flow. The export holds a drawing's room in the flow (holdTheSpaceOf), but only outside every overlay (overlayDepth == 0), and a one-layer stack counts as one.

IndigoProposal's meta tiles and feature tiles are such rows: in each cell a disc or a rounded tile, then a label and a value. In Word and in LibreOffice every label stood a disc's or a tile's height higher than the page, 24.4pt for the meta tiles and 27.8pt for the feature tiles. Those are all 16 of the document's lines more than 2pt off.

What changed

  • The drawing-only branch of the node dispatch now holds the room inside layer stacks of one layer only (overlayDepth == oneLayerDepth) as well. There it does so only for a drawing with a later sibling in its parent (DocxLayoutMetrics.followedInItsParent).
    • A drawing last in its parent is left as it was: an icon in a section of its own beside a heading, like PaymentsInvoice's and WorkspaceInvoice's card heads. Its row is already held as tall as the page makes it (holdRowAtLeast). Measured on the corpus without this check, holding its room in its cell as well set the lines under those cards 0.24pt lower in Word.
    • Every other way into an overlay — text over the flow, a line pair, bands, layers drawn over columns — raises the overlay depth alone, so the branch does not reach it.
  • followedInItsParent looks up the highest child index the layout placed under each parent, built once per section. The cheap checks come first, so only drawing-only nodes ask.
  • Baselines. word-windows, libreoffice-windows and libreoffice-linux are rewritten for IndigoProposal, the one document this moves. The Linux one is taken from this PR's CI run (docx-fidelity-37068842402), where it goes from 16 lines past 2pt to none.
  • Docs. CHANGELOG.md, and the drawing paragraph of docs/recipes/docx-export.md.

Verification

  • Template corpus, re-measured in full. Only IndigoProposal changes, in both editors.

    Editor Past 2pt Median p90
    Word 16.0.20430 16 → 0 0.43 → 0.40pt 27.75 → 0.90pt
    LibreOffice (Windows) 16 → 0 0.57 → 0.36pt 27.88 → 0.79pt
  • What the gate reads as further from the page is a sign changing. Every line below the intro row moved down by 0.96 to 1.08pt in Word and by 1.05 in LibreOffice: in Word from between −0.04 and −0.97pt to between +0.01 and +0.98.

    • Before, the intro row was held at least as tall as the page (holdRowAtLeast), its right column 24pt short, and it ended 0.76pt short.
    • Now its right column holds the discs, and the row ends 0.32pt below the page's, as the lines above it do.
    • The step-by-step drift below it is unchanged.
  • New test class, DocxNestedRowDrawingTest, two tests.

    • A disc over a label, in a section of a row nested through a one-layer stack, puts its 24pt and the label's own 6pt above the label.
    • A disc alone in its section beside a heading puts nothing of its room in its own cell.
  • Each rule fails its test when taken out.

    • With the old overlayDepth == 0 gate, the label has only its own 6pt.
    • Without followedInItsParent, the lone disc's cell holds its room.
  • 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 819 tests, qa 1814.

Known limits

  • A drawing last in its section with text above it (label, then disc) still takes no room inside a one-layer stack. So does a bare shape, or a text badge, there.
  • followedInItsParent applies inside one-layer stacks only. Outside every overlay a drawing alone in its section beside a heading still holds its room in its cell as before.
  • "A one-layer stack is in the flow" is now asked in three places (inTheFlow, holdsARule, this branch), each comparing the overlay depth with the one-layer depth. Setting it once where the stack is entered would move every layeredRow preset, so it needs a corpus re-measure of its own.
  • No test covers a drawing in a multi-layer stack nested in a one-layer one. There the depths differ and the room is not held.
  • IndigoProposal's about band stands 27.6pt right of the page's in Word. The band's fill is at x=27.6 where the page has 0, and its text at 55.3 where the page has 27.7. That is horizontal, which the fidelity gate does not measure, and this change does not touch it.

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

: null;

assertThat(icon).isNotNull();
for (XWPFParagraph paragraph : icon.getParagraphs()) {
@DemchaAV
DemchaAV merged commit 87045dc into 2.5-dev Oct 2, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-drawing-room-in-nested-row branch October 2, 2026 22:06
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.

2 participants