Skip to content

fix(docx): hold a shape and a middle table row to the rules Word counts - #880

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-rule-tails
Oct 11, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-rule-tails

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Why

Two row heights were still counted differently from the way Word counts rules.

  • A shape's borders came off at the width the page strokes them. A shape container composed in a table cell, or held whole in the flow, holds its outline's height less its margins and borders. Word draws a border in eighths of a point and a row counts it two and a half twips an eighth, rounded down (the rule eighthsInTwips already applies to table cells). holdThePanelsHeight and setOnTheShapesMiddle took the lighter border off at the authored width instead, and the label's room in cutTheLabelToItsOutline took both; the heavier border already came off through verticalMargins, at the width written.
    • A 0.7pt outline is drawn 0.75pt wide. A 20.3pt chip outlined at 0.7pt stood a twip taller in Word and LibreOffice on Windows, and six of them in a table's rows stood six twips lower below them.
    • At 0.9pt, drawn 0.875pt, the chip was held a twip short, and the row round it held it.
  • A row between two others was held less its own rule alone. Its margins already give up the rest of its own rule above it and half the rule of the cell under it (clearOfTheRules). The height it holds did not: outerRulesBeyondOne returned 0 for every row but the first and the last.
    • Over a row ruled at 1.5pt, a row ruled at 0.5pt was held half a point too tall.
    • Word grows the row to that height. Its centred text stood a quarter of a point low, and everything under it half a point low.

Both were measured on a probe document, page against Word 16, LibreOffice on Windows and LibreOffice 24.2: four outline widths, in a table cell and in the flow, and five-row tables with one row ruled unlike the rest, both ways round.

What changed

In DocxSemanticBackend:

  • holdThePanelsHeight, cutTheLabelToItsOutline and setOnTheShapesMiddle read the borders as written. They read them from the cell (rule(cell, top)), in whole twips, and no longer take the authored stroke. The unused borders parameters are removed.
  • outerRulesBeyondOne covers every row and is renamed rulesBeyondOne. Each cell holds the rest of its own rule above it, or the whole rule in the first row, and half the rule of the cell under it, or its own rule whole in the last row. The row is held less the most any one cell takes, its margins and those rules together: two maxima taken from different cells would hold a row ruled cell by cell short.
    • For a table ruled alike row to row, a middle row comes to 0, as before.
    • A table ruled alike throughout holds every row, the first and the last included, as before. Where a row's cells are ruled unlike each other, or a cell spans columns ruled unlike each other, rows are now held cell by cell.
    • The value goes below zero where a row is ruled heavier than the next.
    • The cell under is found by grid column (ruleAt, gridSpan), the one whose rule clearOfTheRules already takes off the padding; a spanning cell's is its first column's. A table ruled column by column, alike row to row, is held as before.
  • paintCellSide writes a panel's border from 2 to 96 eighths (ruleEighths), as a table cell's rule is, so that a row counts it at the width written. Before, a hairline thinner than 0.19pt was written at one eighth, and a border past 12pt above the range.
  • writeTableRows holds the rows once the whole table is written. A row's share of the rule under it is the next row's. The first row already waited for the second; every other row now waits too.

Docs: CHANGELOG.md (v2.5.0), the DOCX recipe's table and panel-border paragraphs, and the capability matrix's table and panel rows.

Verification

  • ./mvnw -B -ntp clean verify → BUILD SUCCESS: render-docx 1382 run, 0 failures, 1 skipped (+8), qa 1820, core 818, examples 93.

  • DocxComposedCellTest:

    • a 20.3pt chip is held at 376 twips with a 0.7pt outline (two borders of 15) and 372 with a 0.9pt one (two of 17);
    • an 11.8pt chip outlined at 0.7pt has its label's line and its row at 206;
    • a chip whose cell's margins hold just what Word grows it by, 14 and 15 twips, gives it all back, and its label is seated on its middle;
    • the 9.2pt chip with a 1.125pt outline is held at 140, two borders of 22. It was 139, and the old expectation encoded the defect;
    • the odd give-back case moves to a 9.195pt chip, so that it still gives back 29 twips.
  • DocxRuledRowHeightTest:

    • five rows ruled at 0.5pt with the middle one at 1.5pt, and the other way round, are each held at their line;
    • a table ruled column by column at 0.5pt and 1.5pt holds every row at its line;
    • a cell after a span of two takes the rule of the third column's cell under it;
    • a row ruled cell by cell, 1.5pt and 0.5pt over the other way round, is held less the 270 twips its most demanding cell takes.
  • DocxContainerPaintTest: a panel's 0.1pt border is written at 2 eighths and a 14pt one at 96.

  • Eleven sabotages, each caught by its own test:

    • the authored width in the hold, in the room and in the middle test;
    • no lighter border taken off;
    • middle rows back to 0;
    • the heaviest of the next row instead of the cell under;
    • rows held as they are written;
    • spans ignored;
    • the value clamped at zero;
    • two maxima taken from different cells;
    • a panel's border written unbounded.
  • Probe, page against the editors, in twips over the whole block:

    Word before → after LibreOffice on Windows before → after LibreOffice 24.2 before → after
    six chips in table rows, 0.7pt / 1.2pt outline +8.2 → +1.0 +6 → 0 −84 → −90 / −144 → −150
    six chips in table rows, 0.9pt / 1.125pt outline +1.0 → +1.0 0 → 0 −108 → −102 / −138 → −132
    six chips in the flow, every width +1.0 → +1.0 0 → 0 0 → 0
    five rows, one ruled unlike the rest (6 cases) +0.2…+12.2 → +0.2 +20…+30 → +20 +20 → +20
  • DOCX fidelity corpus: all 62 documents are byte-identical to 2.5-dev, so the Word and LibreOffice baselines, Linux included, do not move. The change adds or alters no report note.

Notes for review

  • LibreOffice makes room at the edge between two rows for the heavier of their rules, where Word makes it for the lower row's. Under a row ruled heavier than it, a row and everything below it stand the difference lower in LibreOffice: 1pt for 1.5pt over 0.5pt, on Windows and in 24.2. Word stands them where the page sets them. Written so as to suit one editor, the edge is wrong in the other, so it is documented as an editor difference in the recipe and the matrix.
  • A panel's border of one eighth or past 96 is not measured in Word. It is written in the 2–96 range of a line border in ECMA-376, as a table cell's rule has been since the table text cell seat change.
  • LibreOffice 24.2 sets every chip held this way one written border shorter: 15, 17, 22 and 25 twips a chip at 0.7, 0.9, 1.125 and 1.2pt. It is the same way 24.2 counts a cell's margins inside an at-least row height, so it is not a write defect. It goes with the placement of a chip's box in its row, which is the next change.
  • The space after ClassicInvoice's summary table is not a gap. The footer below it stands 0.2–0.33pt low in all three editors, and that is the seat of each line in the footer's column cells: Word sets its 10.15pt label 0.22pt low. In LibreOffice, the flow under the table is within 0.05pt of the page's. Seating lines in such cells is its own change.
  • A cell opening with a table keeps its margins, as before. Two such cells padded differently in one row can still round their margins a twip apart, which is under one step of Word's 0.12pt grid. No document of the corpus has such a row.

Lane: shared-engine (DOCX backend) — render-docx; no public API.

- A shape container held whole takes its borders off its row's
  height, and the room it leaves its label, as wide as Word draws
  them: a border is written in eighths of a point and a row counts it
  two and a half twips an eighth, rounded down. Taken off at the
  width the page strokes, a 20.3pt chip outlined at 0.7pt (drawn
  0.75pt) stood a twip taller in Word and LibreOffice on Windows, and
  six of them in a table's rows six twips lower below them.
- A table's row between two others is held less the rest of its own
  rule above it and half the rule of the cell under it, as its
  margins already give them up. Held less its own rule alone, a row
  ruled at 0.5pt over one ruled at 1.5pt stood half a point taller in
  Word. A row is held less the most any one of its cells takes, its
  margins and those rules together. Rows are held once the whole table
  is written, since a row's share of the rule under it is the next
  row's.
- A panel's border is written from two to ninety-six eighths of a
  point, as a table cell's rule is, so a row counts it at the width
  written.
- Docs: the CHANGELOG entry, the recipe's table and shape-border
  paragraphs, and the capability matrix's table and panel rows.
- Tests: DocxComposedCellTest covers a chip held, its label's room and
  its middle counted less borders drawn wider and narrower than the
  page strokes them; DocxRuledRowHeightTest covers a middle row ruled
  unlike the next, both ways round, a table ruled column by column and
  a cell after a span and a row ruled cell by cell; DocxContainerPaintTest
  covers a panel's border range.
…BeyondOne

It covers every row of a table now, not the outer ones alone.
@DemchaAV
DemchaAV merged commit 0ea5117 into 2.5-dev Oct 11, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-rule-tails branch October 11, 2026 17:55
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