Repository navigation
fix(docx): grow a paragraph of runs of several depths where every line is as deep - #959
Merged
Merged
Conversation
…e is as deep - DocxGrownLines.mayGrow(CTP, double[]) lets a paragraph whose runs reach to different depths grow where every line, as the page breaks them, is as deep as its deepest run and mark, and no break is written in a run. - DocxGrownLines.depthOf reads a run's depth at the size it is written at, to the half point; depthOfLine takes a line's deepest run where it is also its tallest, by which the page sets the line. - letTheLinesGrow passes the line depths for a paragraph without a bulletOffset prefix; list items and table text cells keep one depth. - Tests: one line or wrapped lines each holding the deepest run grow; a line of the shallower runs alone, a written break, a prefix, a deeper mark or a tallest run shallower than the deepest keep it exact.
DemchaAV
marked this pull request as draft
October 25, 2026 10:36
The page chooses a line's tallest run, which sets its depth, among the runs before the spaces ending it; DocxGrownLines.depthOfLine counted those spaces too, so a line ending in spaces of a larger size read as their depth where the page set it at its text's. They now count among the deepest runs only, as Word is not known to pass over them.
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 line is written at a least height (
w:lineRule="atLeast"), which Word grows when a reader makes the text larger, where it is at least as tall as its runs' own line (#955–#958). A paragraph whose runs reach to different depths below the baseline — a bold project title beside its lighter stack, a weekday beside its date — stayed exact:DocxGrownLines.mayGrowasked every run and the mark to share one descent. Word stands each least line's text its own deepest run's descent above its foot, and one written height for a paragraph's lines does not say which runs each line holds, so a line without the deepest run would stand its text at another depth. The corpus holds 51 exact paragraphs of runs in more than one face or size at least as tall as their faces' own line.Measured in Word 16 and LibreOffice 24.2 (CI's build), on 8pt bold Lato beside 7.5pt Lato, exact and least, at 10.9 and 12pt, one line or two (a break between), the bold run in every line, only the first or only the last, the mark 7.5pt or 8pt bold:
What changed
DocxGrownLines.mayGrow(CTP, double[] lineDepths)also lets a paragraph of runs of several depths grow where every line's depth, as the page breaks the lines, equals the paragraph's reach below the baseline, its mark included, toEPSILON. Not where a break is written in a run: the page set an inline run's\non the line it ends, where the export writes aw:brWord breaks at.mayGrow(CTP)keeps the single-depth rule for callers that do not know the lines.DocxGrownLines.depthOf(TextStyle)is the depth Word sets a run at: its face's descent at the size written, rounded to the half point asw:szis, NaN for a face not shipped or with no line. The layout's own line depth is the tallest run's descent at the page's size: a 7.35pt run written at 7.5pt is 0.03pt shallower there, which would keep lines Word sets as deep exact.DocxGrownLines.depthOfLineis a line's depth as Word sets it: its deepest run's, read so, where that run is also the line's tallest, by which the page sets the line; NaN where the two differ — a small run of a deep face beside a taller run of a shallow one — or a run is not read. Runs of spaces ending a line, which the page passes over in choosing the line's tallest run (ParagraphWrapping.trimTrailingWhitespaceTokens), count among the deepest and not the tallest, as Word is not known to pass over them.DocxSemanticBackend.letTheLinesGrowpasses each layout line's depth (lineDepths) for a paragraph, and not for one with abulletOffsetprefix: the page sets the prefix as a run of the paragraph's style before the lines it names, and the export writes it as an indent, so those lines hold a run Word does not see. A list's items, whose lines hold the indent and marker the page sets, and a table's text cells keep the single-depth rule.Line heightrow, the CHANGELOG, and the older least-line entry's depth clause.Verification
DocxGrownLinesTest,runsOfSeveralDepthsMayGrowWhereEveryLineIsAsDeepAsTheDeepest: 10pt and 8pt Lato runs grow on one line or two as deep as the 10pt run, not with a line as deep as the 8pt run, a line not read, a written break, or no lines known.aRunsDepthIsItsFacesDescentAtTheSizeItIsWrittenAt: a 7.35pt run reads at 7.5pt; a bold face not shipped and Helvetica read NaN.aLineIsAsDeepAsItsDeepestRunWhereThatIsItsTallest: 8pt beside 10pt Lato reads the 10pt descent, and so does 10pt text before 8pt spaces; 7pt Spectral beside 10pt Lato, 8pt text before 10pt spaces, spaces alone, a run not read and an empty line read NaN.DocxGrownLinesExportTest,runsOfDifferentDepthsGrowWhereEveryLineIsAsDeepAsTheDeepest(wasrunsOfDifferentDepthsAreWrittenExact): a one-line title beside lighter text, and a wrapped one with the bold run in every line, are least with no position; a wrapped one whose later lines hold the light runs alone, one with a written break, and one whose later lines abulletOffsetprefix opens stay exact. The wrapped ones' line counts are read from the session's layout as premises.CobaltRota's weekdays,NordicClean's andEngineeringResume's roles,ClassicSerif's,SidebarPortrait's andVioletGrid's project titles). The report changes only in theirline heightcounts.restyle-body-through-normal: on the six documents squeezed paragraphs 109 → 107; corpus-wide 921 of 1832, from 923. Most of the 20 are set in a size of their own, whichNormaldoes not move.ClassicSerif's project titles stand one step of Word's 0.12pt grid lower;VioletGrid's project titles 0.24pt nearer,EngineeringResume's roles 0.24../mvnw -B -ntp clean verify -pl <core, render-pdf, render-docx, render-pptx, templates, testing, qa, coverage> -am→ BUILD SUCCESS: core 823, render-pdf 339, render-docx 2185, testing 5, render-pptx 142, templates 127, qa 1830../mvnw -f examples/pom.xml test→ BUILD SUCCESS, 93 tests.Lane: shared-engine (render-docx). No public API change. Mixed-depth paragraphs' line rules change in the file.