Skip to content

fix(docx): write a painted box holding text in the body as a panel - #971

Merged
DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-body-shape-container-as-panel
Oct 26, 2026
Merged

DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-body-shape-container-as-panel

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Why

In the body, the DOCX export drew a shape container's outline as a shape of its size, where the page draws it, and wrote its layers in the flow. The layers took the flow's width, not the outline's, and the outline did not grow with them. Measured in Word 16 and LibreOffice 24.2 on the three body shape containers of the fidelity corpus (#970):

  • a note the page wraps over three lines inside a box 220pt wide ran over two, 95pt past the box, and the paragraph under the box stood 12pt high, over it;
  • a box clipped short of its text wrote all of it, 125pt past the outline, and the paragraph under it stood 20pt high, inside it;
  • a card stood where the page sets it, but with text added in Word its outline kept its height and the text ran past its foot.

A shape container composed in a table cell is already written as a panel (writtenAsAPanel): a table of one cell carrying its fill and outline, which holds its text and grows with it. One holding text in the body is written the same way now, but for a badge centring a few letters, drawn as one shape holding them, and a pill holding a title and its dates, written as one line, as before.

What changed

DocxSemanticBackend

  • writtenAsAPanel takes, besides a shape composed in a cell, a painted rectangle or rounded rectangle holding text (holdsWrappedText: a paragraph or a list) that the layout places in the body: outside any table cell, inside no layer stack, shape container or canvas, on one page, and given room in the flow (placedInTheBody); not one written as a band beside a drawing. A frame holding only a picture or drawing, and every other shape, is written as before. Its panel goes through the same writePanel / writePanelPiece:
    • size: the table is the outline's width, half its right border and the editor's point of slack past it (panelWidth), and its row the outline's height (holdTheShapesRow), the shape's padding written round it as a margin, as the page sets it; the row may not split, as the layout moves a shape whole;
    • layers across: each is held in from the cell's edges to where the layout places it in the outline (slotInItsPanel reads the placement for a placed shape, any layer). A section, a container or a list set at the outline's left, unmoved, of no fixed width, is not held (standsAsItsLayer): the page lays it out in the whole room and reports the box of its longest line, and held to that box Word broke the card's line a word early, the cell's right border taking the rest. One given a fixed width is held to it, as the page lays it out in that width, and so is any other block — a shape, a canvas, a table — which has a width of its own;
    • top: Word sets a cell's content below its whole top border, where the page sets the layers from the outline's top, under the border. A shape whose first written layer the page sets at the outline's top (setFromItsTop) — set from its top, or taller than the outline — is set at the cell's top and takes the border out of the space above its first line; without it the card's heading stood 1pt low in both editors. One with nothing above its first line to take it is named;
    • height and clip: the row is held at least the outline's height, so the panel grows with text an edit adds. Where the shape's clip cuts what its layers paint (reportClipCut, which now remembers where it named the clip, clipsCutting) and their boxes run past the outline's foot (pastItsFoot), the row is held at exactly the outline's height less the heavier border, which Word and LibreOffice add to an exact row: both cut a cell's content at an exact row's foot, as the page cuts it at the outline. Measured on the clipped box: held exactly at 58pt it stood 59pt in both, at 60pt 61pt. The clip's note is replaced (report.replace) by one that says the clip is written as an exact height, cuts square where the outline is rounded, and hides text an edit adds there. Layers whose boxes run past the foot with no clip to cut them grow the row, and the report says how far.
  • Report: the corner radius is named (corner radius), as for any panel (roundsItsCorners, shared with the clip note); a turned box is named as written upright. The space above note on the paragraph under the clipped box, measured against lines the page cut and the export wrote whole, is gone.
  • DocxClipInk.TOLERANCE is package-private, so the row reads the same half point the clip measurement does.

Tests

  • DocxBodyShapePanelTest (new, 17).
  • DocxCarriedWithoutReportTest (two cases), DocxClipReportTest and DocxDrawingsTest used a painted box holding text as their example of a drawn outline; they now use a shape that stays drawn — a tile holding a dot, an ellipse — so each keeps pinning what it pinned.
  • DocxNodeFieldLedgerTest (outline, layers, clipPolicy, padding) and DocxValueFieldLedgerTest (CLIP_BOUNDS, CLIP_PATH) describe the body panel.
  • Fidelity baselines: the rows of synthetic-narrow_container, synthetic-clipped_container and synthetic-rounded_card in word-windows*.tsv, libreoffice-windows*.tsv and libreoffice-linux*.tsv are taken from the runs below — the card's Word rows come out as they were; its lines stand 0.5pt further left in LibreOffice 26.8 and 0.5pt further right in LibreOffice 24.2, and the two lines under it 0.6pt higher in 24.2 — and every other row is as it was.

Docs: the DOCX recipe's shape-container entry and the two passages that refer to it, the capability matrix's clip row, the CHANGELOG, the SyntheticDocxCorpus Javadoc.

Measured

Lines found of the page's, then the drift of the found lines (pt; negative is high), before → after:

Document Word 16 (16.0.20527) LibreOffice 26.8 (Windows) LibreOffice 24.2 (docker, CI's build)
synthetic-narrow_container 3/6, −12.02 → 6/6, within 0.03 3/6, −12.02 → 6/6, within 0.02 3/6, paragraph under −12.02, note not found → 6/6, note −0.52, paragraph under −1.02
synthetic-clipped_container 3/10, −19.97 → 10/10: inside +0.99, paragraph under −0.02 3/10, −20.02 → 8/10: inside +0.98, paragraph under −0.02 paragraph under −20.02 → −0.02, inside +0.98
synthetic-rounded_card 7/7, within 0.1 → 7/7, within 0.1 7/7, within 0.02 → 7/7, within 0.02 within 0.02 → card within 0.02 (0.5pt further right), paragraph under −0.62

Read off the editors' PDFs:

  • Narrow box. The note runs over three lines, inside the box, where the page sets them. The box's fill and border end 0.65pt right of the page's outline: the point of slack an editor's face is given (right end).
  • Clipped box. Both editors cut the text at the box's foot and show the five lines the page shows, 1pt low under the top border (named); the paragraph under it stands where the page sets it. Held at least, the box grew to 84pt and everything under it stood 26pt low in both — the reason the clip is written as an exact height.
  • Card. With the editing protocol's sentence added to its text in Word, the card's foot moves from 136.0 to 171.4pt and the paragraph under it down 35pt, below the card.
  • Editing protocol (Word, lengthen-a-paragraph). narrow_container and rounded_card pass. clipped_container fails, as it should: its row is exact, so added text is hidden, as the page hides it.
  • LibreOffice 24.2 counts a cell's borders inside a least row height, where Word and LibreOffice 26.8 add them: the narrow box stands 1pt short there and what follows it 1pt high, and the paragraph under the card 0.62pt high where it stood within 0.02pt — the one line of the change that stands further from the page, in that build alone. LibreOffice 26.8 sets the card's text 0.5pt further left than before, 0.40pt left of the page (within the gate's half point); Word sets it where it did.

Tests

  • DocxBodyShapePanelTest (new, 17): the panel's size, row and keep; the layer held in across from its placement, and inside a padded box; a block at the outline's left not held to its box, one of a fixed width and a shape held to theirs; a later layer's note read against the cell's top; a padded shape read from its outline, at its top and its foot; the top border taken from the space above; the exact row of a clip that cuts, its note, and the same for a square box; a clip that cuts above the foot left at least with its old note; the at-least row and the note of layers past the foot unclipped; the turned box's note; a band kept a band; and the boxes that stay as they were — an ellipse, an unpainted box, a tile, a picture's frame, a box in a layer stack, in a painted section's cell, in a row's cell, and one its margins give no room.
  • Sabotages: each of 25 rules broken alone, the render-docx tests that should catch it run (DocxBodyShapePanelTest, DocxComposedCellTest, DocxClipReportTest), every one caught, the source byte-identical afterwards: the body check, the overlays round the shape, room in the flow, text only, the band guard, the exact row and its one border, the overflow note, the vertical alignment of a layer taller than the outline and the later layers' note read against it, the square box's clip note, the clip note replaced not added, the clip remembered, the top border, a block at the left not held, and one of a fixed width and a shape held, the outline's top and foot read inside the padding, the placed slot, the outline's width and height, the row kept on one page, and both note wordings.
  • Corpus: 70 documents exported; 67 byte-identical to 2.5-dev, the three shape-container documents changed; report notes 1499 → 1498, every note removed or added on those three.
  • Word gate: scripts/docx-visual/word-fidelity.ps1 → the corpus test in word mode passes on all 70 documents against word-windows.tsv, its three rows rewritten (-Update, then those three documents' rows alone taken in).
  • LibreOffice gate, Windows (26.8): the three documents pass against their rewritten rows; invoice-obsidian and proposal-indigo fail as they do on 2.5-dev (test(qa): hold the ways a page breaks inside a panel in the DOCX fidelity corpus #965), each exported byte for byte as 2.5-dev does.
  • LibreOffice gate, Linux (CI): the first run (40398446747) failed on synthetic-rounded_card alone, as measured beforehand in LibreOffice 24.2's docker build: the two lines under the card −0.02 → −0.62pt, past the gate's half point, and none of the other two documents' lines further from the page. The three documents' rows are taken from that run's artifact (docx-fidelity-40398446747) in a commit of their own, every other row as it was; they match the docker build's to the hundredth. Of rounded_card, the two lines under the card stand 0.6pt higher and its four lines 0.5pt further right than before, the four within the half point the gate allows.
  • clean verify of the reactor as CI runs it → BUILD SUCCESS (core 823, render-pdf 339, render-docx 2310, testing 5, render-pptx 142, templates 127, qa 1846); the four knowledge checks and core javadoc (0 warnings) pass; examples 93.

Named, not fixed

  • LibreOffice 24.2 counts a cell's borders inside the least height its row is held at, as it counts the row's margins, where Word and LibreOffice 26.8 add them round it: a body panel whose content is shorter than its outline stands up to its borders short there — the narrow box 1pt, the block under it 1pt high, the block under the card 0.6pt high, where Word sets it within 0.1pt. The same difference is recorded for a chip composed in a cell; the DOCX recipe names it, and the Linux baseline holds it.
  • A section or a list a body panel's layer centres or sets right is held to the box of its longest line, with no room to spare for an editor's wider text, as it was held on the inline path: a line may break a word early in Word. A paragraph layer is given that room. Not in the corpus.
  • An ellipse, a polygon or a star holding text is still drawn, its layers written in the flow; so is a box inside a layer stack, a row's cell or a panel, whose layers' room can still run past a narrow outline. None of these is in the corpus with text past its outline.
  • A layer set at the foot of a body panel may stand a border high in Word, which sets a cell's content above its whole bottom border, where the page sets the layer on the outline's foot: unmeasured, and not in the corpus.
  • The rounded corners are square.

Lane: DOCX backend (render-docx). No public API change.

A shape container shaped as a rectangle or a rounded rectangle, filled
or outlined, holding text and placed in the body, was drawn at its size
with its layers written in the flow: its text took the flow's width and
ran past the outline, the block under it stood over the outline, and the
outline kept its height when text was added in an editor.

It is now written as a panel, as it already was composed in a table
cell: one cell its outline's size, its fill and border on the cell, its
padding round it, the row kept on one page. Its layers stand where the
layout places them in the outline; one the page sets at the outline's
top is set at the cell's top and takes the top border out of the space
above its first line. The row is held at least the outline's height, so
it grows with its text; a clip that cuts what the layers paint past the
outline's foot holds it at exactly that height less one border, which
Word and LibreOffice cut the content at. The report names the square
corners, the clip written as an exact height, and layers running past
the foot unclipped.

The rows of the fidelity corpus's three body shape containers are
rewritten in the Word and Windows LibreOffice baselines.
A block of a fixed width set at a body panel's left was left at the
cell's whole width, as a block the page lays out in the whole room is:
the page wraps it at its own width, and the inline path held it there.
It is held to its width again.

The first layer's height a panel's layer notes compare against is the
one its cell sets it at: the top for a layer taller than the outline.
Tests pin the outline's top and foot read inside a padded shape, and a
fixed-width block at the left.
The rows of narrow_container, clipped_container and rounded_card in the
Linux LibreOffice baseline are taken from the first CI run of this
change (artifact docx-fidelity-40398446747), which matches LibreOffice
24.2's docker build to the hundredth; every other row is as it was.

The narrow and clipped boxes move nearer the page. Of rounded_card, the
two lines under the card stand 0.6pt high there: LibreOffice 24.2 counts
a cell's borders inside the least height its row is held at, as it
counts the row's margins, where Word adds them, and the card's content
is shorter than its outline. Word sets them within 0.1pt. The DOCX
recipe names the difference.
Only a section, a container or a list set at a body panel's left, of no
fixed width, stands where it is written: the page lays it out in the
whole room. A shape, a canvas or a table there has a width of its own
and is held to it, as the inline path held it: a pill at a card's left
centred its label in the card.

The clip's note says Word cuts what the panel's cell holds, which is
what an exact row cuts. The DOCX recipe and the CHANGELOG name the
badge and the line pair as kept, and LibreOffice 24.2's narrow box as
1pt short. Tests read twips through DocxTwips.
@DemchaAV
DemchaAV marked this pull request as ready for review October 26, 2026 22:06
@DemchaAV
DemchaAV merged commit 174d4c5 into 2.5-dev Oct 26, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-body-shape-container-as-panel branch October 26, 2026 22:17
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