Repository navigation
fix(docx): write a paragraph the page reads as markdown as the page sets it - #868
Merged
Merged
Conversation
…ets it A session reads markdown by default, and the page sets a paragraph of plain text holding *, _ or a backtick through its markdown parser: the text its marks style bold or italic, a heading line bold and larger, the marks dropped. The export wrote the text as authored, asterisks and all, and named it. DocxMarkdown reads the text as the page does, line by line through the page's own parser, and the pieces are written one run each where the lines the page laid the paragraph out in hold their letters in the same faces, families, colours and sizes. Elsewhere, and where the lines are not read, the text is written as authored and named, as before. Every path that writes a paragraph does it: the body, a cell, text over the flow, an overlay's line pair, a badge's initials and a page zone. A composed cell's paragraph is matched to its lines by its text as the page reads it, once every paragraph has taken its own text as authored. The font table ships the faces the pieces ask for. A markdown heading the page sets larger than its line, which Word cuts on screen, and marks alone, which the page sets as nothing, are named.
… heading only where it is cut A paragraph composed in a table cell is matched by its text as the page reads it only to a fragment whose lines set its pieces. A plain paragraph of the same text after it may have taken its own; the one left is in another face or size, and held to it, the paragraph's letters were cut in Word with no note but its marks. A markdown heading is named where it is written taller than the line the page sets it in, by its own line's height: an auto-sized paragraph's heading, written at a multiple of its style's size, may fit the line the page fits the text to. Initials that are a heading are written in the flow, as initials in two faces are. The pieces are compared with the page's lines in tracking too, where the page fits no size of its own. Pieces that change nothing - every mark kept, every piece in the paragraph's style - leave the text written as it stands, its white space and tabs with it. Text the parser reads into nothing, a lone `*`, `***` or a line four spaces in, is named for what it is.
DemchaAV
marked this pull request as draft
October 7, 2026 15:16
DemchaAV
marked this pull request as ready for review
October 7, 2026 15:16
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 session reads markdown unless it is told not to (
markdown(false)). The page then reads a paragraph of plain text that holds*,_or a backtick through the engine's markdown parser. Text its marks style is set bold or italic, a heading line bold (the first three levels larger), and the marks are dropped. The DOCX export wrote the text as authored, so Word showed**bold**with its asterisks and none of the bold. The report named it, but a style Word can hold stayed unwritten.TimelineMinimalshows it in the corpus. Its open-source project line reads… billing systems. *(Open source)*. The page sets the line in regular Lato with(Open source)in italic. Word showed the whole line bold, with the asterisks.What changed
DocxMarkdown(new, package-private) reads a paragraph's text as the page does:MarkDownParser;-,*or+and a space keeps that marker, asParagraphWrapping.tokenizeMarkdownLinedoes;DocxMarkdown.laidOutIndecides whether the pieces are written. It compares them with the lines the page laid the paragraph out in:A session with markdown off lays the marks out, so the check fails and the text is written as it stands. If the mirror of the core parse path drifts in letters, faces, families, colours, sizes or tracking, the check fails too: the paragraph is written as authored, and named where the page dropped marks. White space is not compared, because the page drops it where it breaks a line.
markdownPieceskeeps text the parser changes nothing of — every mark kept, every piece in the paragraph's own style,snake_casein a regular paragraph — written as it stands, white space and tabs included.writeParagraphRunswrites a plain-text paragraph as one run per piece wheremarkdownPiecesreturns pieces:runAfter, so they stay in onew:hyperlink;markdownWrittenrecords what was written, so the note leaves out what is written (markdownLost) and Word's outline lists a heading by the text written (outlineTextOf). Every path writes a paragraph before it makes its note. Recording what was written, rather than recomputing it in the report, keeps a path that writes as authored from being called written.Every path that writes a paragraph goes through this:
writeParagraphRuns;zoneLinesOf, now shared withreportZoneLine);badgePieces).**JR**is now a badge of two bold letters.*J*R, in two faces, is written in the flow, as initials in two runs' faces are, and so is a heading.markdownHeadingCutnames a markdown heading written taller than the line the page sets it in. The page sets a heading line as tall as the paragraph's own and draws its letters past it; written in that exact line, Word cuts their tops on screen. The check compares the heading's own line height with the page's line. An auto-sized paragraph's heading, written at a multiple of its style's size, often fits the taller line the page fits the text to, and is then not named.DocxLayoutMetrics.matchComposedTextpairs a paragraph composed in a table cell with a fragment by its text as the page reads it (marks dropped). It accepts only a fragment whose lines set its pieces (setsThePieces). A plain paragraph of the same text later in the table takes the first fragment of that text in the first pass, which may be this one's own. The fragment left over is then in another face or size, and the markdown paragraph takes no line rather than be cut by it. Before this PR, the page dropped the marks, so the authored text never matched and such a paragraph got no lines at all.DocxFontTable.collectFontsadds the faces of the pieces of the paragraphs it reads (outside table cells and page zones, as before). The table is written before any paragraph, so it reads the faces off the text. A session with markdown off therefore ships a face it does not use.parserDropsAMarkreads throughDocxMarkdown.read, line by line and with the list-marker rule, rather than parsing the whole text at once. With no layout,* a_bis no longer named.Still written as authored, and named:
*(an empty list item),***(a rule), a line set four spaces in (a code block it keeps no text of) — which the page sets as nothing. These went unnamed before. Written as nothing, the paragraph would be blank, and the table-row code (takeFromTheFoot) treats a blank paragraph as a cell taking no room; a fixture inDocxLinePairTestthat marks a cell with a lone*shows it;The faces are the page's
The page's markdown parser sets every piece in a face of its own and drops the paragraph's own face. A bold paragraph whose text holds a mark is therefore drawn regular, with only the marked pieces bold or italic. The export follows the page.
TimelineMinimal's project line is a bold paragraph, drawn regular next to its bold neighbours, and Word now draws it regular too. This is engine behaviour, outside this PR.thePiecesStandInTheFacesThePageSetsThemInpins the page's face, so a change in the engine fails that test, and the export, which compares faces with the page's lines, follows.Verification
./mvnw -B -ntp install -pl :graph-compose-render-docx→ BUILD SUCCESS, 1202 tests, 0 failures, 1 skipped.DocxMarkdownTest(6) covers:laidOutIntrue for wrapped lines, auto-sized proportions and a leading prefix;laidOutInfalse for laid-out marks, marks alone, another face, family, colour, tracking or letter, a proportional size where the page fits none, sizes out of proportion, a missing letter, a prefix of other letters, no lines, and a non-text span.DocxSessionMarkdownTest(21) covers:***,*, a line four spaces in), written and named;_, a link, an entity, double spaces, and a tracked heading — each written as the page sets it;**JR**,*J*R, and a heading moved to the flow;DocxMarkdownReportTest: paragraphs, cells and zones are written and not named, a heading is named,* a_bwith no layout is not, and the no-layout and list notes are otherwise unchanged.laidOutIn, tracking included;TimelineMinimalchanges: its project line is written in regular Lato with(Open source)in Lato Italic, and the file ships the italic face (365 KB larger). In Word 16.0 the line renders as the engine PDF draws it, where before it was bold with asterisks. Report notes: 954, from 955 (that line's note).Known limits
Lane: render-docx (DOCX semantic backend) — no public API change.