Skip to content

fix(docx): name a border Word draws at another width than the page, and write a separator of no width - #914

Merged
DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-border-widths
Oct 18, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-border-widths

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

A Word border's w:sz runs from two to ninety-six eighths of a point, and the export held
every border to that range without a word in its report: a panel's borders (a section's, a
container's, a row's, a shape container's panel in a cell), a table cell's rules, a rule written
as a paragraph's border, and a text header's or footer's separator. A 20pt panel border was
written 12pt wide and a 0.1pt hairline a quarter of a point, while the report promises that
what the file does not hold is said. In the corpus, CompactMono's header rule is one:
stroked 0.1pt, written 0.25pt, unreported.

A separator of no width, which the PDF strokes with a line width of 0 — the thinnest line a
device shows — was not written at all, and one set below zero or to NaN was dropped silently.

What changed

  • DocxBorderWidths (new, package-private): eighths(points) replaces the backend's
    ruleEighths at every place a border width becomes w:sz; notHeld(what, plural, widths)
    says which widths fall outside Word's range once rounded to the eighth Word counts in, and
    ofNoWidth(what) what a separator of no width becomes. A width that rounds to an eighth Word
    draws (0.2pt → two eighths) is drawn at it and not named.
  • Notes (border width, APPROXIMATED): once for a panel (on its first piece, as its
    translucency note is), once for a table (its cells' rule widths collected across the grid,
    covered positions too, deduplicated), once for a rule, and once for a separator however many
    headers it is written into (separatorsReported, which now remembers the band for its width
    as well as its colour).
  • Separator: separatorShown/separatorWritten decide it in one place. One of no width is
    written at Word's thinnest and named; its frame holds it, and reachOf — whether a band
    reaches past the page margin — now counts the separator as Word draws it, its width in
    eighths and nothing for one not written (it counted the page's thickness on
    showSeparator alone). One below zero or NaN is named (DROPPED) and not written.
  • DocxNodeFieldLedgerTest: the border-width case is in the entries of a panel's, a row's, a
    table's and a shape container's borders, a line's stroke, a filled bar's height and the
    headersAndFooters option.
  • Docs: the recipe (panel borders, the header and footer section), the capability matrix (the
    shape, line, table and header/footer rows), CHANGELOG v2.5.0 Public API.

Verification

  • ./mvnw -B -ntp test -f render-docx/pom.xml — DocxBorderWidthsTest (new, 16): the eighths
    and the threshold (0.2pt and 12.06pt held; 0.001pt, 0.05pt, 0.1pt, 16pt and 20pt named,
    listed in order); a panel's 20pt border and a 0.1pt side (w:sz 96 and 2), a panel split by a
    page break named once, a row's border, a table's 0.05pt and 20pt rules named once, a divider's
    and a line's 0.1pt rule, borders Word draws as the page does and strokes of no width not
    named; a separator of no width written at two eighths, in its frame and reaching past a
    margin it ends at (the margin written negative), a 20pt one named once though written into
    two headers, a translucent 20pt one named for both, one below zero and one NaN not written,
    named once, and one not written reaching nothing.
  • 20 sabotages, each undoing one part — each note, the table's collection, the zero-width
    separator, its frame and its reach, the below-zero and NaN notes, once per band and per
    panel, both thresholds, held widths, the clamp, strokes of no width — all caught.
  • Corpus: 62/62 DOCX byte-identical; report notes 1244 → 1246, the two CompactMono header
    rules (CV and cover letter).
  • ./mvnw -B -ntp clean verify — BUILD SUCCESS (core 821, render-docx 1736, examples 93,
    qa 1820); the knowledge pack's --check passes.

Lane: render-docx (semantic backend) — report notes and a separator of no width; no layout
change.

…nd write a separator of no width

A Word border is two to ninety-six eighths of a point wide, and the
export held every border to that range without a note: a panel's, a
row's and a table cell's borders, a rule written as a paragraph's
border, and a text header's or footer's separator. A 20pt panel border
was written 12pt wide, a 0.1pt rule a quarter of a point.

DocxBorderWidths holds the range and says what falls outside it, to the
eighth: a border is named (border width) once for a panel, once for a
table, once for a rule, and once for a separator however many headers
it is written into. A width that rounds to an eighth Word draws is
drawn at it, unnamed.

A separator of no width, which the PDF strokes as the thinnest line a
device shows, was not written. It is drawn at Word's thinnest border
and named, and the band's reach past the page margin counts the
separator as Word draws it. One set below zero, or not a number, is
named as not written.
@DemchaAV
DemchaAV merged commit 8eb0ca2 into 2.5-dev Oct 18, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-border-widths branch October 18, 2026 14:18
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