Skip to content

fix(docx): name a table its rules stand taller in Word - #917

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-cell-rule-past-padding
Oct 18, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-cell-rule-past-padding

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

Word makes room in a table's rows for its horizontal rules — half of a rule between two rows to
each, the rules above and below the table whole to their row — and the page draws them over the
cells' edges. clearOfTheRules takes that room off the cell's top and bottom padding; where the
padding is thinner than its share, it gives what it has and the row stood taller in Word, with
nothing in the report.

Measured in Word and LibreOffice on Windows — the paragraph under the table against the same table
unruled (an unruled table already sits ~2.65pt lower in both, a font-metric offset), to a tenth of
a point:

Table Word LibreOffice
4 rows, no padding, 3pt rules +15.0 +15.0
4 rows, no padding, 1pt rules +4.9 +5.0
4 rows, padding 1, 3pt rules +7.0 +7.0
4 rows, padding 3, 3pt rules 0 0
3 rows, 2pt rules: two-line cell unpadded beside a one-line cell padded 2 +8.0 +8.0
the same, the two-line cell the padded one 0 0
2 rows, 2pt rules, padding 5 above and none below +3.0 +3.0

The two mixed rows show how Word counts: each cell at its content and, on each side, its padding
or its share where that is more; the row at its tallest cell. A short cell whose padding falls
short beside a taller cell that sets the row adds nothing; "the most any cell holds beside the
tallest content" would have said 12 for the 8.

What changed

  • DocxRuleRoom (new, package-private): counts each text cell of a row — count(row, placement, padding, round, textHeight) — and sums, row by row, what the tallest cell comes to past the
    page's height (taller). A row the layout does not tell the height of, or whose counted text it
    does not measure, counts the most its text cells' padding falls short of taking, and the note
    says "up to"; a header row repeated on every page counts once for each page the layout places it
    on.
  • DocxSemanticBackend: ruleShareAbove/ruleShareBelow (shared with clearOfTheRules),
    roundTheContent (padding or share, each side), textHeight (the engine's own cell content
    height: lines × the measured line height plus their spacing), and nameTheRoomForTheRules —
    APPROXIMATED, subject cell rule: "…and sets the table about Xpt taller than the page".
  • Not counted: cells built from a node and cells spanning rows; empty cells where the row is
    held at the page's height (their line is cut to the row's room, fitBlankLinesToTheRow). A cell
    of a space is written as a run and is counted. In the corpus's MerchantInvoice,
    ObsidianInvoice and CobaltRota, whose rows are built from nodes or hold empty cells and are
    ruled past their padding, the rows stood within half a point of the page's in Word, where
    counting those cells said 2–4pt.
  • DocxNodeFieldLedgerTest: the cell paint entry names the case. Docs: the recipe's table row, the
    capability matrix's table row, CHANGELOG v2.5.0.

A row whose margins are evened to one a cell keeps (evenTheRowsMargins: a cell opening with a
table, or in a vertical merge) is counted as though each cell kept its own, which is not measured;
the Javadoc says so.

Verification

  • ./mvnw -B -ntp test -f render-docx/pom.xml — DocxCellRuleRoomTest (new, 10, each with-layout
    case asserting the layout stood behind it): the measured tables above; a quarter-point rule
    (0.75pt in all); padding that takes the rules, no rules and the engine's defaults not named;
    line spacing part of a cell's height; a 2pt header over 0.5pt rows; a header repeated over pages
    counting again on each; cells built from a node and empty cells not counted, a cell of a space
    counted; a cell spanning rows not counted; a table composed in a cell and one with no layout
    named "up to", a composed table of node cells not at all. DocxRuleRoomTest (new, 5): a measured
    row, a row not told counting only itself at most, an unmeasured cell, an empty cell held and not,
    a row placed on three pages.
  • 16 sabotages, each undoing one part — the note, the node and span exclusions, the room round the
    text, the measured branch, the bound, the threshold, padding past a share, the share between
    rows, a space, an empty cell where held and in the bound, the header's repeats, line spacing, an
    unmeasured cell, the bound for one row only — all caught.
  • Corpus: 62/62 DOCX byte-identical; report notes 1246 → 1246.
  • ./mvnw -B -ntp clean verify — BUILD SUCCESS (core 821, render-docx 1770, examples 93, qa 1820); the knowledge pack's --check
    passes.

Lane: render-docx (semantic backend) — a report note; no output change.

Word makes room in a table's rows for its horizontal rules, half of a
rule between two rows to each and the rules above and below the table
whole to their row, where the page draws them over the cells' edges.
The export takes that room off the cell's padding, and where the
padding is thinner than its share, the row stood taller in Word and the
report said nothing. Measured in Word and LibreOffice on Windows, four
rows of text with no padding and 3pt rules set the paragraph under the
table 15pt lower than under the same table unruled.

Word sets each cell at its content and, on each side, its padding or
its share of the rules where that is more, and the row at its tallest
cell, which DocxRuleRoom counts row by row against the page's height:
a short cell beside a taller one that sets its row adds nothing, as
measured, and a header row repeated on every page counts once a page.
The report names the table (cell rule) with about how much taller, or
with the most it can be where the layout does not tell a row's height
or measure its text. Cells built from a node, cells spanning rows, and
empty cells in rows held at the page's height are not counted: in the
corpus's invoices and rota, whose rows are built from nodes or hold
empty cells, the rows stood within half a point of the page's in Word.
@DemchaAV
DemchaAV marked this pull request as draft October 18, 2026 22:34
The test reading how much taller a note names parsed whatever was left
of the note when the figure was not in it; it now finds the figure, and
fails naming the note where there is none. The composed table's cases
assert the layout stood behind the export, as the others do. The note's
Javadoc says empty cells, which is what is not counted, and two long
lines are wrapped.
@DemchaAV
DemchaAV marked this pull request as ready for review October 18, 2026 22:37
@DemchaAV
DemchaAV merged commit fa710c7 into 2.5-dev Oct 18, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-cell-rule-past-padding branch October 18, 2026 22:58
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