Skip to content

fix(docx): give a table's rules out of its cells' padding where Word makes room for them - #790

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-table-row-borders
Sep 30, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-table-row-borders

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Why

In Word, every ruled table ran taller than on the page, row by row. EditorialProposal's timeline and investment tables pushed its investment block, with the acceptance under it, onto a third page. NorthlineProposal's acceptance heading stood 12pt below its badge, which is drawn where the page puts it.

The page draws a cell's rules on its edges and steps its rows by padding and content alone. Word makes room for a table's horizontal rules. Measured through Word COM on a probe of 12.35pt text with 7pt padding:

rules row step one-row table
none 26.35pt —
0.75pt 27.1pt —
1.5pt 27.85pt +3pt, text 1.5pt lower

So a rule between two rows takes half its width from each, and the rules above and below the table take their whole width from their row. Between two rows ruled differently, Word makes room for the lower row's rule: a 1.5pt header over 0.5pt rows stepped as 0.5pt rules do, and a 1.5pt row under 0.5pt ones as 1.5pt rules do.

A table that states no rule was left on Word's own grid. That grid is thinner than the engine's default 1pt black rule the page draws, and gives its rows other heights.

What changed

  • clearOfTheRules. A table cell's padding gives up, above and below, the room Word makes for the rules there. That is half the rule between two rows (the lower row's where the two differ), and the whole rule above the first row and below the last. In a table ruled alike throughout, with padding enough, every row then steps and stands as the page sets it. A padding thinner than its share gives what it has, and its row stands that much taller. The sides keep their padding, as the columns' widths are fixed.
  • resolveCellStroke / ENGINE_DEFAULT_CELL_STROKE. A table that states no rule is written with the engine's default 1pt black rule, as resolveCellPadding and the cell fill already fall back to the engine's defaults. This is a visible change for such tables: their rules in Word are now the page's weight.
  • Docs. CHANGELOG.md, the tables row of docs/recipes/docx-export.md and the table-rows row of backend-capability-matrix.md are updated. The committed word-export-companion.docx preview is re-rendered, as its table is ruled.

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 are green (93 tests, with the refreshed preview), and the knowledge checks and extract-api --check pass.

  • DocxCellPaddingTest:

    • A two-row 1.5pt-ruled table is written 110/125 and 125/110 twips above and below, with the sides untouched.
    • A table stating no rule gets the 1pt single rule, pinned to TableCellLayoutStyle.DEFAULT.
    • The padding tests now include the default rule's share. New cases: a 1.5pt header over 0.5pt rows gives the body's half at the edge; a cell spanning to the last row gives up the rule below the table whole; a zero-width rule gives up nothing; a padding thinner than its share goes to 0. Each rule of the model, when removed, fails its own test.
  • DocxComposedCellTest: the row-height hold subtracts the margins as now written.

  • Template corpus (62 documents), converted to PDF by Word, baselines matched by text:

    median drift lines > 2pt off
    all 62 (3614 lines), before → after 0.94 → 0.56pt 1341 → 858
    NorthlineProposal 0.8 → 0.4pt 50 → 7
    InvoiceClassic long 6.9 → 0.5pt 123 → 3
    InvoiceModern long 6.9 → 0.4pt 119 → 2
    • EditorialProposal is 2 pages in Word again; it was 3. In LibreOffice the corpus median falls from 1.59pt to 0.55.
    • Two other rule models were measured over the same corpus: half a rule off each side reached 0.60pt, and a whole rule below every row 0.74pt.
    • InvoiceWorkspace's median rises from 3.8 to 4.8pt. Its table now stands level; its rows had been growing by as much as its bill-to block runs 10pt high, and that block is a separate defect.
  • Word on screen: a capture of Word's window shows NorthlineProposal's investment, terms and acceptance headings level with their badges, and EditorialProposal on two pages.

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

…makes room for them

Word makes room in a table for its horizontal rules: a rule between two rows half in each, the rules above and below the table whole in their row. The page does not, so every ruled table grew row by row. A table that states no rule is written with the engine's default 1pt rule, as the page draws it.
… differently

Measured, Word makes room at a shared edge for the lower row's rule: a 1.5pt header over 0.5pt rows steps as 0.5pt rules do. A cell's padding below now gives up half the rule of the row under it.
@DemchaAV
DemchaAV merged commit ed57e32 into 2.5-dev Sep 30, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-table-row-borders branch September 30, 2026 13:14
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