fix(docx): a run keeps the style it carries - #523
Merged
Conversation
A RichText paragraph exported with every run in the paragraph's base style, so a bold segment, an accent-coloured segment and plain text came out identical. The file was valid and nothing warned; the emphasis was simply absent. InlineTextRun documents its style as falling back to the paragraph's when null, and the backend applied that fallback unconditionally with the run in hand. It now uses the run's own style and falls back only when there is none, which is the same whole-style fallback the layout engine already does. Paragraphs and row cells share one walk. A row cell used to be written from the concatenated text in a single style; going through the runs changes no text, because a blank text is filled from exactly the runs the walk visits. A chip reaches Word with its own monospace face as a result, though still without its background. A table cell is written from lines rather than runs and is untouched here. STRIKETHROUGH was the one decoration with no branch in the style mapping and fell through to nothing.
The committed preview was rendered before runs kept their own style, so it shows the flattening: bold and italic segments carry no run properties and two accent colours are written as black. The catalogue renders it with them now, and the drift guard fails on any preview that no longer matches its example.
setStrike(boolean) is deprecated in POI 5.5.1; setStrikeThrough(boolean) is the replacement and sets the same single strike. javac reports the deprecation under -Dmaven.compiler.showDeprecation and stops once the call moves. The export is unchanged: the run-style suite and the committed-asset guard both stay green.
This was referenced Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A
RichTextparagraph exported to DOCX with every run in the paragraph's base style. A boldsegment, an accent-coloured segment and plain text all came out identical — a valid
.docx,no warning, and the emphasis simply absent. From the consumer's side there is nothing to
diagnose: the text is all there, styled as if it had been asked for that way.
The data was never missing.
InlineTextRunis(text, textStyle, linkTarget)and its Javadocstates the style falls back to the paragraph's when null. The backend applied that fallback
unconditionally, inside a loop that already held the run:
STRIKETHROUGHwas separately the oneDocumentTextDecorationwith no branch in the stylemapping, falling through
default -> {}to no decoration at all.What changed
InlineTextRunalready documents. The fallback replaces the whole style rather than mergingfields, which is what
ParagraphWrappingdoes for the same case, so DOCX moves toward thePDF and PPTX behaviour rather than inventing a third rule.
writeParagraphRuns. A row cell was writtenfrom the concatenated
paragraph.text()in a single style; it now goes through the runs.ParagraphNodefills a blanktextbyconcatenating exactly the runs
inlineTextRuns()returns — and because the DSL keepstextand
inlineRunsmutually exclusive, so the diverging state cannot be built. The test assertsthe cell's text as well as its runs.
inlineTextRuns()lowers anInlineHighlightRunto a text run carrying the chip's style, so
inlineCode("run()")now arrives in Courierinstead of the paragraph's family. Its background still does not export.
STRIKETHROUGHmaps tow:strike.Deliberately out of scope, and the remaining halves of #447: table
colSpan/rowSpanandper-cell styling in
writeTable(theTableNodepath, still written from lines in no style),image
fitMode/scale, and hyperlink relationships —linkTargetis still dropped on both arun and a node.
Verification
./mvnw -B -ntp clean verify -pl :graph-compose-core,…,:graph-compose-coverage -am→BUILD SUCCESS, 1563 tests (476 / 144 / 24 / 5 / 90 / 104 / 720), 0 failures, 0 errors.
render-docxgoes 18 → 24.New
DocxRunStyleTest, 6 tests: each run keeping its own style against the paragraph'sfallback; a run without a style still taking the paragraph's;
STRIKETHROUGH;ITALIC,UNDERLINEandBOLD_ITALICper run; a code chip carrying its own face; and a row cellkeeping per-run styling while its text stays byte-identical.
Reverting only
DocxSemanticBackend.javatodevelopturns 3 of the first 4 red —eachRunKeepsItsOwnStyleRatherThanTheParagraphFallback,strikethroughReachesTheDocumentandaTableCellKeepsPerRunStylingAndLosesNoText.aRunWithoutItsOwnStyleTakesTheParagraphStylepasses in both states by design: it pins the fallback that had to survive the fix, not the bug.
Documentation
docs/architecture/backend-capability-matrix.md— the decorations row moves to ✅ for DOCX; theparagraph row stays
linkTargetas the reason rather than run styling.render-docx/README.md— the two bullets that described the flattening and the droppedstrikethrough are gone, and the table-cell bullet now distinguishes a
tablecell (writtenfrom lines, no styling) from a
rowcell (a paragraph, styled per run).Lane: shared-engine — a render backend; no public API change.
Part of #447.