Skip to content

fix(docx): name a panel's right border held in from its cell's edge - #939

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-corpus-gaps
Oct 22, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-corpus-gaps

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Why

Word draws a nested table's right border outside the table and, on screen, cuts off whatever passes its
cell's edge. A bordered panel in a table cell is a nested table. When its right border reaches the cell's
text edge, writePanelPiece writes the table narrower, out of the panel's right margin and no more than
that margin holds, so the border ends half a point short of the edge and the text keeps its place and
width. For a panel as wide as its cell, that is half of each side's border, the whole border and the half
point: 3.5pt for a 1.5pt border.

Neither outcome was in the report:

  • Narrowed. The border moves in from where the page draws it.
  • Still past the edge. Where the margin is too narrow, the border still passes the cell's edge. A
    shape container written as a panel is always in this case: its padding is set round its outline.

The field ledgers held the case as a known gap.

The corpus has 28 such panels. 19 are written narrower. Measured in Word on Windows, their right borders
stood:

Document Table narrower than the page Word's border, from the page's
Obsidian invoice 1.74pt 0.77 and 0.88pt left
Editorial proposal (glance card) 1.92pt 0.68pt left
Panel CV 1.39pt 0.56 and 0.59pt left
Terracotta CV 1.39 / 1.40pt 1.10 and 1.14pt left
Metered invoice 1.94, 1.93 / 2.41pt 1.37, 1.39 / 0.85pt left
Workspace invoice 2.29 / 2.32pt 0.90 / 0.83pt left
CompactMono CV (Experience) 0.19pt (1.19pt taken, 1pt of it the editor's slack) 0.43pt right

The other 9 pass the edge, by about 1.0 to 1.7pt:

  • rota-cobalt's 7 outlined chips;
  • the payment panels of the Merchant and Platform invoices.

In Word's PDF the outlined chips' right border is drawn whole, about 0.75pt right of the page's, so the
cut is a screen one.

What changed

  • DocxSemanticBackend.writePanelPiece names both outcomes on a panel's first piece, in one
    APPROXIMATED note with subject right border (rightBorderHeldIn):

    • Narrowed, above a hundredth of a point: "its table written Xpt narrower than the page draws it,
      out of its right margin, so its right border, which Word draws outside the table, ends short of its
      table cell's edge, past which Word cuts it off on screen".
      • X is exact: what the table gives up, less the editor's slack.
      • That slack is what panelWidth gives a panel sized round its content past the page's width, so it
        is not narrower than the page.
      • panelWidth now returns that slack with the width (PanelWidth).
    • Still past the edge, above a hundredth of a point: "…reaching about Ypt past its table cell's
      edge, where Word cuts it off on screen", after the narrowed clause where there is one.
      • Y is reckoned as Word drew them: the table starting at the cell's text, its whole border outside
        it.
      • It is measured against the cell's edge, so the cell's own right margin, what holds the panel in from
        it, and the half point left clear of the gridline are taken off first.
      • A cell too narrow to take any of the border in is reckoned too.
      • A border reaching into a padded table cell stays inside the cell and is not named.
    • Panels, painted rows and shape containers written as panels share this path.
  • Ledgers: every entry this case reaches moves from GAP to REPORTED:

    • ContainerNode, SectionNode and RowNode stroke and borders;
    • ShapeContainerNode.stroke;
    • DocumentStroke.width and DocumentBorders.right.

    RIGHT_BORDER now states the narrowing and what is named, and leaves the list of known gaps, so a GAP
    note that still opens with it fails the ledger.

  • Docs and CHANGELOG:

    • the recipe's panel section and the capability matrix's shape row describe both notes;
    • CHANGELOG (Public API).

Verification

  • DocxPanelRightBorderTest (new, 11 tests). The figures are pinned to the geometry, worked out by
    hand in each test, and the narrowed one also against the written w:tblW.
    • Named:
      • a card as wide as its 523pt cell, 3.5pt narrower;
      • a card 0.6pt narrower than its cell, given that much slack, 2.9pt narrower;
      • a card whose 1pt padding gives 0.25pt, its border about 1.25pt past the edge;
      • a card with no right padding, about 2.25pt past;
      • a shape container in an unpadded table cell, about 2.25pt past;
      • a shape container in a cell too narrow to take any of its border in;
      • a painted row, 3.5pt narrower.
    • Not named:
      • a card with room to spare;
      • a card with no right border;
      • a card in the body;
      • a shape container whose border reaches 2.25pt into its 2.5pt-padded cell.
  • Fail-closed: thirteen sabotages, each run alone and each caught:
    • the note never added;
    • either outcome left unnamed;
    • a note where nothing is taken;
    • either figure wrong;
    • the narrowed clause dropped where both apply;
    • the cell's right margin left out of the past figure;
    • the half point clear of the gridline counted as past;
    • the editor's slack counted as narrower;
    • a cell too narrow to take any in, not reckoned;
    • a node entry left GAP;
    • a value entry left GAP.
  • Corpus: all 62 DOCX byte-identical before and after. Notes go from 1246 to 1274, and the 28 new
    ones are all right border.
  • ./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
    → BUILD SUCCESS (render-docx 2000 tests, qa 1828). The JDK 17 checks pass: the knowledge pack, @Beta
    stability doc, claims and routes, and core javadoc with 0 warnings.

Notes for review

  • No path on composed content. A note on content composed in a table cell carries no path: the
    layout addresses that content under its table. Every note on such content shares this, and so do the
    7 rota-cobalt notes.
  • The cut is a screen one. The 9 corpus borders passing the edge were checked in Word's PDF, where
    the border is drawn whole.
  • The narrowing is conservative. room ends at the cell's text, so a panel whose border the cell's
    own right margin would hold is narrowed all the same. The note is still true there. Changing that
    changes the DOCX, which this PR does not.

Lane: shared DOCX backend (render-docx report). No public API change; the DOCX bytes do not change.

Word draws a nested table's right border outside the table and, on
screen, cuts off what passes its cell's edge. A bordered panel reaching
that edge is written narrower, out of its right margin, its border up
to its width and half a point in; where the margin is too narrow, as a
shape container's always is, the rest still passes the edge. Nothing in
the report said either.

The panel's note (right border) now names how much nearer its text the
border is written and what of it still passes the cell's edge, past the
cell's own right margin. A painted row and a shape container written as
a panel are named the same way.

The ledgers held the case as a gap; every entry it reaches is now
reported, and the case leaves the list of known gaps.
@DemchaAV
DemchaAV marked this pull request as draft October 22, 2026 00:25
The right-border note named how much nearer its text the border was
written by the points its table gave up. Those points include the
editor's slack a panel sized round its content is given past the page's
width, and, where the right margin is too narrow, the left side's give
back moves the border further out, not nearer. The note now names how
much narrower than the page draws the panel its table is written, the
slack not counted (panelWidth returns it), and about how far the border
still passes the cell's edge, reckoned too in a cell too narrow to take
any of it in. Docs and the ledger state the narrowing as the code takes
it: the border ends half a point short of the edge.
@DemchaAV
DemchaAV marked this pull request as ready for review October 22, 2026 00:43
private static double held(DocxExportReport.Note note) {
Matcher held = HELD.matcher(note.detail());
assertThat(held.matches()).as("the note: %s", note.detail()).isTrue();
return Double.parseDouble(held.group(1));
private static double[] notHeld(DocxExportReport.Note note) {
Matcher past = NOT_HELD.matcher(note.detail());
assertThat(past.matches()).as("the note: %s", note.detail()).isTrue();
return new double[]{past.group(1) == null ? Double.NaN : Double.parseDouble(past.group(1)),
Matcher past = NOT_HELD.matcher(note.detail());
assertThat(past.matches()).as("the note: %s", note.detail()).isTrue();
return new double[]{past.group(1) == null ? Double.NaN : Double.parseDouble(past.group(1)),
Double.parseDouble(past.group(2))};
@DemchaAV
DemchaAV merged commit de38bd4 into 2.5-dev Oct 22, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-corpus-gaps branch October 22, 2026 07:36
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