Skip to content

fix(docx): paint a table the way it was styled - #532

Open
DemchaAV wants to merge 2 commits into
developfrom
fix/docx-table-paint
Open

fix(docx): paint a table the way it was styled#532
DemchaAV wants to merge 2 commits into
developfrom
fix/docx-table-paint

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Why

DocumentTableStyle carries a fillColor and a stroke, and writeTable read neither. A
zebra body, a header band and a ruled grid all exported on Word's defaults — which is to say
with no fill, and with whatever borders POI puts on a table it creates. Word owns both
(w:shd, w:tcBorders), so nothing here is an approximation of a thing Word cannot do.

What changed

  • Fill maps to w:shd, stroke to w:tcBorders — four edges, w:sz in eighths of a point
    rounded up to at least one so a hairline the author asked for stays a line.
  • The cascade resolves per field. resolveCellTextStyle became
    resolveCellValue(node, placement, field), so the table → column → row → cell order now
    applies to each property on its own: a table-wide rule survives a row that only overrides the
    fill. That matches how TableLayoutSupport.buildStylesGrid merges for the fixed-layout path.
  • A merged cell is painted on every position it covers. A w:vMerge continuation draws its
    own shading, so leaving it unpainted would stripe the region.
  • A stroke of no width says so. DocumentStroke.of(colour, 0) is this codebase's "no
    border" — PdfTableRowFragmentRenderHandler reads the same predicate as draw-nothing, and
    the shipped MintEditorial CV preset uses it. Writing nothing left the cell on the grid Word
    puts on a table by default, so a deliberately borderless design exported ruled. The cell now
    emits STBorder.NIL. A table that says nothing about borders still keeps Word's default:
    the look belongs to Word where the document did not claim it.

What a fill loses is its opacity. w:shd is opaque, and blending it needs a background this
backend does not resolve — Word owning the flow is the whole premise of the semantic export.

The example carries the feature. The catalogue had no DOCX example with a styled table, so
this would have shipped with no render behind it — WordExportExample's table now takes a
header band and a table-wide rule. Both of its committed previews move: the DOCX for the new
markup, and the PDF because the fixed-layout backend paints a style it was never given before.
That second one is the drift guard doing exactly what it was widened for last week.

Verification

./mvnw -B -ntp clean verify -pl :graph-compose-core,…,:graph-compose-coverage -am
BUILD SUCCESS; ./mvnw -B -ntp test -f examples/pom.xmlBUILD SUCCESS, 73 tests, and
no preview drift once both were refreshed.

New DocxTablePaintTest, 6 tests: a fill reaching w:shd while an unpainted cell keeps
Word's default; a stroke becoming four SINGLE edges at the right w:sz and colour; the
cascade resolving fill and stroke from different levels at once; a merged cell painted on both
its positions; a hairline that rounds to one eighth rather than to nothing; and a zero-width
stroke emitting NIL.

Reverting DocxSemanticBackend.java to develop fails all six.

The emitted CT_TcPr was checked against the schema by unzipping a document that carries a
span and paint on one cell: the order comes out w:gridSpan → w:vMerge → w:tcBorders → w:shd, the CT_TcPrBase sequence, because XmlBeans inserts each child at its schema position
rather than in call order.

Documentation

docs/architecture/backend-capability-matrix.md and render-docx/README.md name what the
paint now carries and what it drops. The root README.md DOCX row said fill and border paint
were dropped; that line is the one this series keeps having to correct, and it is corrected
again here.

Not covered

Borders on a w:vMerge continuation are written on all four edges, so a merged region
formally carries interior rules. Word is expected to suppress them; I could not pin that in a
test, so it is named rather than claimed.

Lane: shared-engine — a render backend. No public API change.

Closes #528.

DocumentTableStyle carries a fill and a stroke, and neither reached the file: a
zebra body, a header band and a ruled grid all exported on Word's defaults. The
fill maps to w:shd and the stroke to w:tcBorders, and the cascade that already
resolved a cell's text style now resolves every field on its own — so a
table-wide rule survives a row that only overrides the fill.

A merged cell is painted on every position it covers: a w:vMerge continuation
draws its own shading, and leaving it unpainted would stripe the region.

A stroke of no width is this codebase's way of saying "no border", and the
fixed-layout handler reads the same predicate as draw-nothing. Writing nothing
would leave the cell on the grid Word puts on a table by default, so a
deliberately borderless design — MintEditorial uses exactly this — would export
ruled. The cell now says it has none. A table that says nothing about borders
still keeps Word's default: the look belongs to Word where the document did not
claim it.

What a fill loses is its opacity. w:shd is opaque, and blending it needs a
background this backend does not resolve.

The Word companion example styles its table, so the feature ships with a render
behind it. Both of its previews move — the DOCX for the new markup, the PDF
because the fixed-layout backend paints a style it was never given before.

@DemchaAV DemchaAV left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-

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