Skip to content

fix(docx): end a rule where the page ends it in Word - #946

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-subscription-rules
Oct 23, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-subscription-rules

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Why

A horizontal line, a divider or a thin filled bar is exported as an empty paragraph whose bottom border is the stroke (writeRule). The rule's ends are the paragraph's left and right indents. Word draws a paragraph's border past both indents, and LibreOffice draws it between them. So in Word every rule stood about 1.4pt longer at its left and 1.5pt at its right. Subscription Invoice's separators stood 1.32pt left and 1.57pt right of the page's, and its four-coloured strip's segments ran that far into each other.

Measured on Windows, on nine synthetic rules in the body (half a point to 4pt thick, indented from nothing to 61.7pt), Word drew the border:

  • 1.36 to 1.49pt past the left indent;
  • 1.44 to 1.59pt past the right indent.

Neither the size of the paragraph's mark nor a left and right border written as none/nil changed it. Rules of 1 and 3pt in a row's column and in a table cell fell in the same range, and a dashed (4/2) and a dotted (1/1) rule reached as far as a solid one. In the corpus there are 304 paragraph bottom borders in 43 of the 62 documents.

One indent cannot put a rule right in both editors, so the export now writes it for Word.

What changed

  • wordsRuleReach: each of a rule's indents is taken in by Word's reach.
    • The reach is WORD_RULE_REACH_LEFT_POINTS (1.42pt) and WORD_RULE_REACH_RIGHT_POINTS (1.51pt), the means of the measurements.
    • A rule shorter than both keeps a tenth of a point of its paragraph (SHORTEST_RULE_PARAGRAPH_POINTS). Measured, rules of 1, 2 and 3pt then stood 3.0, 2.88 and 3.12pt long in Word, each centred within 0.05pt of where the page centres it.
  • writeRule applies it in both branches: a rule composed in a cell, written where its cell drawing is, and a rule in the flow.
    • A rule running past the width it is written in. It stays held at that width's edge, as before, and only what Word draws further than the page ends it is taken in. The reach goes inside the clamp (max(0, gap + reach)), so such a rule does not end short.
    • Where the width is Word's (a cell whose grid this export did not write), the right indent the containers round the rule already gave it (applyInset) is kept, and the reach is added to it.
  • LibreOffice now sets each end of a rule further in: by 1.42pt at the left and 1.51pt at the right. The recipe and the capability matrix say so.
  • A header's or footer's separator is its text's own paragraph border, so its indents stay its text's. Word still draws it past the margins, and the recipe says so.
  • Example preview: assets/readme/examples/word-export-companion.docx is re-rendered. Its divider's indents are the only change: left 0 → 28 twips, right 1580 → 1610.
  • Docs:
    • the recipe's rule passage;
    • the capability matrix's Line row;
    • writeRule's javadoc;
    • the CHANGELOG (Public API).

Verification

  • DocxRuleTest (+6 new tests, 4 updated). The updated ones now expect the indents plus the reach. The new ones cover:
    • the two reaches, pinned at 28 and 30 twips;
    • a 2pt rule, taken in by 0.95pt at each end;
    • a rule hung 1pt left of its row column's text, whose left indent is 8 twips: the reach is added before the cell holds it at its text;
    • a 355pt rule 6pt in, in 360pt, which keeps a right indent of 10 twips;
    • with no layout and an auto column, a fill rule in a cell whose width is Word's, alone and inside an 8pt-padded section, whose right indent is the reach, and the padding and the reach.
  • DocxCellRuleTest (2 updated) and DocxHangingLeftTest (1 updated) expect the reach. The hung dash's test also pins its left indent: the reach, from its cell's text.
  • Fail-closed: 9 sabotages, each run alone, all caught:
    • no reach at the left, or at the right;
    • a short rule taken in whole;
    • no reach where the width is unknown, or the containers' inset dropped there;
    • a cell's drawn rule not taken in;
    • the reach taken out instead of in;
    • the reach added outside the edge's clamp, at the right or after a cell's clamp at the left.
  • Word and LibreOffice on Windows, the 43 changed documents against the export before:
    • Word: 280 strokes stand nearer the page's, 13 further, and 4 move without getting nearer or further. Subscription's separators went from −1.32/+1.57 to +0.12/+0.13, and its strip's segments are within 0.12pt.
    • The 13 further: each is a rule whose column the export already starts right of the page's, which Word's reach had partly covered:
      • Receipt Modern's dotted leaders and Violet Grid's separators sit in the weight column after a row's auto column given an editor's point (withRowEditorSlack). They now start 1pt right where they started 0.45pt left.
      • Two of Charcoal Gold's rules and two of Violet Grid's start 7.8 to 10.8pt right of the page's in both versions.
    • LibreOffice: 281 rules move in. 249 move 1.4pt at the left and 1.5pt at the right; 21 move at the left only, being held at their width's edge; the rest move by less at the right.
    • No word moves in either editor.
  • Corpus: 43 of 62 DOCX change. Notes are unchanged at 1363.
  • Gate:
    • ./mvnw -B -ntp clean verify -pl <core, render-pdf, render-docx, render-pptx, templates, testing, qa, coverage> -am → BUILD SUCCESS: core 821, render-pdf 339, render-docx 2068, testing 5, render-pptx 142, templates 127, qa 1828.
    • The JDK 17 knowledge checks pass, and core javadoc has 0 warnings.
    • ./mvnw -f examples/pom.xml test → BUILD SUCCESS, 93 tests. CommittedAssetDriftTest failed before the preview was re-rendered.

Notes for review:

  • The drawn-cell branch clamps its right indent with the same expression as the flow branch, max(0, gap + reach). No test builds a cell drawing that ends in its cell's margin.
  • Moving a row's editor point out of its weight columns is a separate change, the row counterpart of the table auto-column fix.

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

- A line, a divider or a thin bar is written as an empty paragraph's
  bottom border, its ends the paragraph's indents. Word draws a
  paragraph's border past both indents, so every rule stood about 1.4pt
  longer at its left and 1.5pt at its right.
- Each indent is taken in by Word's reach (wordsRuleReach): 1.42pt at
  the left, 1.51pt at the right, measured on nine rules 0.5 to 4pt
  thick in the body, a row's column and a table cell (1.36 to 1.49,
  1.44 to 1.59). A rule running past its width stays held at the
  width's edge, less only what Word draws further than the page ends
  it. A rule shorter than both keeps a tenth of a point of its
  paragraph. LibreOffice, which draws the border between the indents,
  sets each rule that much short at each end.
- Re-render the Word export example's DOCX preview: its divider's
  indents.
- Measured on the corpus: in Word 280 strokes nearer the page's, 13
  further, rules whose column already starts right of the page's; in
  LibreOffice 281 rules moved in; no word moves.
- Tests: indents with the reach, its two values, a short rule, a rule
  past its width, a rule in a cell whose width is Word's, with and
  without a padded container.
@DemchaAV
DemchaAV marked this pull request as draft October 23, 2026 00:33
- LibreOffice now sets each end of a rule further in by Word's reach,
  not short of the page's by it: in the body it drew a rule's right end
  short of its indent already.
- The reach is stated as measured: nine rules in the body, half a point
  to 4pt thick; rules of 1 and 3pt in a row's column and a table cell in
  the same range; a dashed and a dotted rule reaching as far as a solid
  one.
- Tests: a rule hung 1pt left of its row column's text takes the reach
  in before the cell holds it at its text; the hung dash's left indent
  is the reach.
@DemchaAV
DemchaAV marked this pull request as ready for review October 23, 2026 00:42
@DemchaAV
DemchaAV merged commit eedefdb into 2.5-dev Oct 23, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-subscription-rules branch October 23, 2026 02:45
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