Skip to content

fix(docx): take a block's upward pull out of the space above it, and hold a row's padding at the top of its column - #793

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-payments-masthead
Sep 30, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-payments-masthead

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Why

In Word, PaymentsInvoice stood 10pt off its page, the worst of the 62 templates. There are two causes, and in both Word has nowhere to write space that the page has.

  • The header rule. The header is a layer stack. Its diagonal band runs 16.4pt past the header's content, and the rule under the stack is pulled back up by a -16.4pt top margin. Word has no negative space above a paragraph, and applyVerticalSpacing writes an edge only where it is positive. So the pull was dropped, and the rule, BILL TO and the line items stood about 10pt low: the 16.4pt pull less the 6.3pt the metadata grid lost above it.
  • The metadata grid. The grid is a row inside a column padded 6.2pt down. Word has no space above a table, and at the top of a cell there is no paragraph to carry it. So the grid stood 6.3pt above the issuer beside it. MerchantInvoice's metadata did the same.

What changed

  • dispatchNode → standsIntoTheSpaceAbove. A paragraph's, page reference's or rule's negative top edge is added to carriedSpacingBefore, the way a container's negative edge already is. newBodyParagraph then nets it against everything owed above, as the page sums it.
    • What that space cannot give stays unwritten, as before.
    • Text laid over the flow (overTheFlowDepth > 0) is skipped: it is set in text boxes and owes no space.
  • writeTableWithItsOwnSpacing. A RowNode opening any cell except a table's composed cell now calls holdTheSpaceAboveATable, which writes a paragraph a tenth of a point tall. Before, it did so only at the top of a panel.
    • holdTheSpaceAboveATable now counts the containers' carried edges as well as the space owed.
    • A table's cell is excluded, via the per-export tablesCells set filled in writeCellContent. The table holds that row at the page's height; held again, ObsidianInvoice's line items stood 6pt low.
  • Tests. The fix(docx): carry the space above a table that opens the body #781 test aTableOpeningACellIsWrittenAsBefore is narrowed to a row opening a table's cell: its ObsidianInvoice measurement was of line items in table cells. It is renamed aRowOpeningATablesCellIsWrittenAsBefore.
  • Docs. CHANGELOG.md and docs/recipes/docx-export.md are updated. The recipe's space-above-a-table paragraph now says where the hairline carries the space and where a table still loses it.

Verification

  • 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 (1791 + 127 tests).
    • After install, examples run 93 green.
    • The knowledge checks and extract-api --check pass.
    • render-docx runs 691 tests.
  • DocxSpaceAboveTest (new, 5 tests):
    • A rule pulled up 8pt under 20pt owed is written with 12pt above it; so is a page reference.
    • A paragraph pulled up past the space owed has none above it.
    • A paragraph pulled up 3pt inside a section pulled up 4pt, under 10pt owed, gets 3pt.
    • A row opening a column padded 6pt keeps 6pt above it.
  • DocxOverTheFlowTest, 1 new test: a line pulled up in a text box takes nothing from the section's space above the flow after it.
  • Each rule fails its own test when reverted:
    • leaving the pull out, or leaving page references out of it;
    • taking it with takeBackSpaceAbove, which cancels a container's pull;
    • dropping the over-the-flow guard;
    • dropping the carried edges from the hairline;
    • holding only in panels;
    • holding in a table's cell.
  • Template corpus (62 documents), converted to PDF by Word:
    • Six invoices change; every other document is byte-identical, and page counts are unchanged.
    • Lines more than 2pt off: 705 → 572. Median drift: 0.52 → 0.49pt.
    • PaymentsInvoice: median 10.3 → 0.5pt, lines off 68 → 2.
    • MerchantInvoice: lines off 38 → 27. Its metadata goes from -6.0 to +0.2pt and its totals from -9.9 to +2.1pt.
    • MeteredInvoice, PlatformInvoice and SubscriptionInvoice go to 0 lines off; WorkspaceInvoice goes from 6 to 0.
    • In LibreOffice, lines more than 2pt off fall from 1066 to 933.

Known limits

  • A table (not a row) opening a cell still loses the space above it.
  • A paragraph opening a layer column or a band still loses its own pull: resumeHere swaps in the space resumed there, as before this change.
  • A first paragraph whose own pull passes the space above it is not raised inside its line (riseIntoItsLine rises only by the containers' pull). It stands that much less low than before.

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

@DemchaAV
DemchaAV merged commit 4e243a8 into 2.5-dev Sep 30, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-payments-masthead branch September 30, 2026 18:18
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