Skip to content

fix(docx): set what follows a panel where the page does, and name the rest - #873

Merged
DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-name-unabsorbed-tails
Oct 8, 2026
Merged

DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-name-unabsorbed-tails

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Why

Word sets a panel's content below its whole top border and its margin, and ends the row its
margin and its whole bottom border past the content; the page strokes each border on the
panel's edge, taking no room. With the cell margin written as padding − border/2, a panel
reaches past the page's box by border/2 + max(0, border/2 − padding) on each edge — measured
in Word 16 and LibreOffice on Windows alike on a 2pt border: 2pt with no padding, 1pt with 1, 2
or 4pt of it. The export took each border whole out of the space round the panel, so a padded
card with a gap below it stood what followed half a border high, unnamed, and only a row's panel
named the rest.

What changed

All in DocxSemanticBackend:

  • outsideTheRow states the measured reach. writePanelPiece takes it out of the space
    round the panel first (the border stands where the page strokes it), then out of the panel's
    padding (takeOffTheCellMargin — the border drawn that much inside the panel's edge, the
    content where the page sets it), then, above, out of the room over the first line
    (takeTheTopBorderInside). What none of them takes is named: on the panel's node kind for its
    top border, on the block below for its bottom one.
  • What stands a block lower is named on it (nameTheTailNotTaken, subject space above, one
    note with the total): a panel's bottom border reach, a line hanging below the block above, the
    0.1pt paragraph Word keeps between two tables, and a pull — a negative bottom margin, a
    panel's as a paragraph's — that neither the space below it nor the next block's own top edge
    gives back (Word sets no block over another). The bottom reach the space below does not take
    comes out of that panel's padding first (borderBelowCell).
  • A cell no longer carries the border of the card ending it into the flow (carriedBorder):
    its writer decides. A row takes it, with the cell's hanging line, out of the room under that
    cell, then the card's padding; a panel takes it out of the card's padding, then its own; a
    table's composed cell or a column of side-by-side layers — whose content the layout does not
    measure against its row — takes it out of the card's padding and names the rest on the cell's
    content, up to that much where a cell beside it may hold it.
  • holdThePanelsHeight: Word and LibreOffice read a panel's written row height as its
    content's, with its margins and both borders round it (measured: held a point above its
    content, an outlined card stood a point taller). A panel holds the page's height less its
    padding, as a uniformly ruled card already was, and less what the room over its first line gave
    its top border, which used to come off it a second time; a shape container, its layers centred
    in it, holds its outline's height less both borders and its margins (it was held one border
    less only), and nothing of its borders reaches past it.
  • A panel's last line hanging past its content comes out of its padding below, then the space
    under it. A container's top edge above a table counts as room for the table's tail. A panel the
    layout moves to a new page leaves the tail above on its page and holds the space above it there,
    as a table does. A tail or pull left over a block that takes nothing out of a top edge of its
    own — a list item — is named at the next block or at the section's end.

Docs: the recipe's "What a panel keeps and loses" (borders, height, what hangs below, the
measured nesting) and its pull paragraph, the capability matrix's rectangle row, render-docx/ README.md, the field ledger (DocxNodeFieldLedgerTest: panels' stroke/borders and
paragraphs' and panels' margin/padding name what is reported), and four unreleased v2.5.0
CHANGELOG entries that described the old border model (row paint, CobaltRota's chips,
MerchantInvoice, the badge entry).

Verification

  • ./mvnw -B -ntp clean verify on this branch with fix(docx): write a page zone's picture in its line, and stand each part on its own baseline #872 merged → BUILD SUCCESS: render-docx
    1307 run, 0 failures, 1 skipped, qa 1820, core 818.
  • New DocxPanelTailTest (40): borders above and below with and without padding and space, the
    space before the padding, held heights, shape containers, hangs, cards ending a panel, a row's
    cell, a table's cell (alone and beside another) and a column of layers, pulls before a
    paragraph, a spacer, a table and a list, a pull against a card's top border, a panel moved to a
    new page, touching tables and cards named once with the total. Exact values updated in
    DocxContainerSpacingTest, DocxPanelHeightTest, DocxRowPaintTest.
  • 32 sabotages of the rules above, each caught by at least one test.
  • A probe of cards with 2pt borders above, below and all round (padding 0/1/4, with and without
    space round them), cards outlined at 0.5 and 2pt stacked and at 0.5pt in a row's columns, and
    chips in table cells, converted by Word 16 and LibreOffice on Windows: every line stands where
    the page sets it, or as much lower as the report names — within 0.15pt in Word and 0.05pt in
    LibreOffice.
  • DOCX fidelity corpus: 25 of 62 documents change; the report names 15 more notes (14 × the 0.1pt
    between two tables; ObsidianInvoice's totals row, 0.72pt = a 0.62pt border + that 0.1pt). Word
    baseline (word-windows*.tsv, via scripts/docx-visual/word-fidelity.ps1 -Update): 12
    documents nearer the page
    (letter-engineering_resume median 1.27 → 0.13pt,
    cv-engineering_resume 0.58 → 0.24, proposal-modern 0.48 → 0.19, cv-minimal_underlined
    0.71 → 0.44), 8 further (cv-panel 0.34 → 0.83, invoice-subscription 0.23 → 0.61,
    cv-modern_professional 0.09 → 0.32, cv-classic_serif 0.17 → 0.31, four by under 0.2pt).
    LibreOffice on Windows likewise, 13 nearer and 9 further. LibreOffice on Linux
    (libreoffice-linux*.tsv, from CI's docx-fidelity artifact): 24 documents change, 11 nearer
    in median (letter-engineering_resume 1.40 → 0.19pt, invoice-obsidian 1.38 → 0.27pt with 15
    lines over 2pt where there were 25), 9 further — the same documents as on Windows.

Notes for review

Where traced, the documents that move further from the page do so because the whole border taken
out of the space had been making up for errors elsewhere, now exposed: a heading's baseline left
up to half a point from where Word seats it in an exact line (cv-modern_professional's summary
heading stands where the layout sets it, 82.57 against 82.55pt, its baseline 0.36pt lower), a
spacer of no height written as a 0.1pt paragraph (one per card in cv-panel), rounding to the
twip inside a card, a 0.37pt step between a table's header and its first row
(invoice-subscription), and the space above a block moved to a new page written at the page's
top (proposal-editorial). cv-charcoal_gold, cv-nordic_clean and invoice-workspace move by
under 0.06pt in median, not traced. Each traced cause is a defect of its own, outside this change.

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

Merge order: follows #872, merged into this branch; the two touch disjoint parts of the
backend (page zones there, panels here), and no corpus document has a page zone.

… rest

Word sets a panel's content below its whole top border and its margin, and
ends the row its margin and its whole bottom border past the content; the page
strokes each border on the panel's edge. Half a border, and as much of its
inner half as the padding falls short of it, reaches past the panel's box. The
export took each border whole out of the space round the panel: a padded card
with a gap below it stood what followed half a border high, unnamed.

- The reach comes out of the space round the panel, then the panel's padding
  (the cell margin), then, above, the room over its first line; what none of
  them takes is named, on the panel for its top border and on the block below
  for its bottom one ("space above").
- A panel holds the page's height less its padding as its row's height, which
  Word and LibreOffice read as the content's; a shape container holds its
  outline's height less both borders and its margins, its borders inside it.
- What a panel's content leaves below itself - a line held to an icon, a
  card's border ending it - comes out of the room the page leaves under that
  content, then its padding below; a card's border ending a row's cell reaches
  past the row only by what the row's room under that cell does not hold.
- The paragraph Word keeps between two tables is named where the space between
  them does not take it, together with a border reaching the same block; a
  container's top edge above a table counts as that space.
- A margin below zero under a panel pulls what follows up and is no border; a
  panel the layout moves to a new page leaves the tail above on its page; a
  tail left over the last block of a section is named.

Fidelity baselines (Word, LibreOffice on Windows) rewritten: twelve documents
stand nearer the page in Word, eight further where the whole border taken out
of the space had made up for other errors.
…here its writer knows the room

- A pull out of the block above - a negative bottom margin, a panel's as a
  paragraph's - comes out of the space below it before any tail, the space
  above a table included; what that space and the next block's own top edge do
  not give stands the next block lower and is named ("space above").
- A cell no longer carries the border of the card ending it into the flow; the
  cell's writer decides. A row takes it, with the cell's hanging line, out of
  the room under that cell, then the card's padding; a panel takes it out of
  the card's padding, then its own; a table's composed cell and a column of
  side-by-side layers take it out of the card's padding and name the rest, up
  to that much where a cell beside it may hold it.
- A container leaves no room under its content: what its content leaves below
  itself comes out of its padding directly.
- A panel the layout moves to a new page holds the space above it there, as a
  table does.

Fidelity baselines (Word, LibreOffice on Windows) rewritten:
invoice-subscription stands nearer the page than with the previous commit.
…el border reach

Measured by CI's LibreOffice on Linux on this branch: 24 of the 62
documents change, 11 standing nearer the page in median and 9 further,
the same documents and directions as LibreOffice on Windows.
@DemchaAV
DemchaAV merged commit a94d3ce into 2.5-dev Oct 8, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-name-unabsorbed-tails branch October 8, 2026 13:20
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