Skip to content

fix(docx): break a paragraph's lines where the page does, though Word sets its size to the half point - #804

Merged
DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-engineering-skills
Oct 1, 2026
Merged

DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-engineering-skills

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Why

Word states a type size in half points, so a size the page sets to the tenth is set a little larger or smaller, and each of its lines that much wider or narrower. Lines then break at other words than on the page.

In Word, EngineeringResume's columns stood 8 to 9pt low (median drift 7.9pt):

  • its 7.8pt profile, set at 8pt, took a line more;
  • its 6.9pt skills, set at 7pt, broke "SQL" onto a line of its own.

Scaling the glyphs (w:w) was ruled out before, as a scale stays on the text a reader types next.

What changed

  • measureAtWordsSize / wordsMeasure (called from writeParagraph). A paragraph set flush left gets a measure as much wider or narrower as Word sets its text, through its right indent.
    • The share is taken per line the page laid out, from its spans' widths and sizes. Pictures and shapes in a line are written at their own size, and tracking in points, so neither grows. The measure grows by the share of the line that grows most. EngineeringResume's projects mix a 7.35pt title (set at 7.5) and 7.1pt prose (set at 7). Averaged over the paragraph, the measure narrowed, and the title's line broke at another word in both editors.
    • A paragraph of several lines is held a point short of the full share, but never less than a point past its widest line, which wins where the two meet. A line the page broke because the next word missed by a fraction of a point misses by as little in Word once grown in proportion: CompactMono's "and", 0.1pt off on the page, fitted in Word.
    • A paragraph of one line is never narrowed, and keeps the full share when it grows. It broke no word, and OrangeOps' phone number, as wide as its column, broke in LibreOffice a point narrower.
    • A paragraph whose sizes are all on the half point keeps the page's measure. With no layout, the runs are weighed by their letters.
    • It is taken from the paragraph's own room, the cell's measure in a cell.
    • The glyphs are left as Word sets them. A reader's new text wraps at that measure.
  • Not applied to a centred or right-aligned paragraph (moving its other edge would move its lines), a right-to-left one, one made only of pictures, or one letTheLineStandOut already gave room past its box.
  • Docs. CHANGELOG.md; the half-point paragraph of docs/recipes/docx-export.md, which said the rounding was not made up for.

Verification

  • Full reactor gate: ./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 gives BUILD SUCCESS (core 818, render-pdf 339, render-docx 768, templates 142, testing 127, qa 1791).

    • After install, examples run 93 green.
    • The knowledge checks pass.
  • DocxWordSizeMeasureTest (new, 10 tests).

    • A line of 7.8pt widens the measure by 8/7.8.
    • Several lines of 9.2pt narrow it by 9/9.2 less a point; one line of 9.2pt is not narrowed.
    • 9.5pt leaves it alone, and so does centring.
    • A 7.35pt title over 7.1pt prose: at a page width found so the title's line, as Word sets it, is wider than the letter-averaged measure, Word's measure fits it with a point to spare.
    • At a page width found so a line and its next word miss by under 0.2pt, Word's measure is wider than the page's, fits every line, and misses the next word by a point.
    • A picture and 7.1pt text: over 120 page widths, every line, picture and all, keeps at least 0.99pt to spare in Word's measure.
    • A 7.8pt run under a 12pt Normal is written w:sz=16 and widened.
    • A paragraph in a 200pt column is widened by no more than its share of it.
  • Each piece fails its own test when taken out. Leaving pictures out of the line fails the picture test. Averaging over the letters fails the title test (and four others). Narrowing a line of one fails the one-line test. Dropping the point of clearance fails the near-tie test. Dropping the alignment guard fails the centred test.

  • Template corpus (62 documents), against 2.5-dev. Word, lines more than 2pt from the page:

    Before After
    All 62 documents 324 249
    EngineeringResume 37 (median 7.9pt) 0 (median 0.6pt)
    NordicClean 21 0
    ClassicSerif 8 0
    SerifHeadline 8 0
    • LibreOffice goes from 781 to 736 lines more than 2pt off.
    • Line by line against 2.5-dev, lines lost to another word counted too:
      • Word: 77 lines move nearer the page, 85 that broke at other words now break where the page does, none moves further, none is lost.
      • LibreOffice: 54 nearer, 86 now found, none further, none lost.
    • No page count changes.
    • With test(qa): hold every template's DOCX export to a line-by-line corpus baseline #805's corpus gate merged in locally, DocxFidelityCorpusTest passes against 2.5-dev's baseline. That gate caught the averaged share's lost lines in EngineeringResume, and the first clearance's in OrangeOps.
    • No text reaches the page's edge: the nearest any line comes to it in Word is 8.5pt (ClassicSerif), against 12.4pt before.

Known limits

  • A list's items, a line pair's line, text over the flow, and a header's or footer's line are written elsewhere and keep the page's measure, so they can still break at another word.
  • The measure is one number for the whole paragraph. A line of its smaller runs gets the room the line that grows most needs, a little more than its own, and can take a word more.
  • Tracking is taken as one step per letter of a span, which can be one step off the engine's count.

Lane: shared-engine (render-docx). No public API change.

…ize takes, so they break where the page breaks them
…d a wrapped paragraph's a point clear of the next word
@DemchaAV
DemchaAV merged commit dc9dbd0 into 2.5-dev Oct 1, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-engineering-skills branch October 1, 2026 16:38
DemchaAV added a commit that referenced this pull request Oct 1, 2026
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