Skip to content

fix(docx): take what a spacer's line and a taller paragraph reach past their block out of the space below - #874

Merged
DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-unmasked-panel-drifts
Oct 8, 2026
Merged

DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-unmasked-panel-drifts

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Why

Two things reach past their block in Word, and neither was taken out of the space below it or
named: what followed stood lower than the page sets it, silently.

  • A spacer shorter than its line. A spacer in the flow is written as an empty paragraph a
    tenth of a point tall, its height less that tenth owed below it. A spacer of no height — a width
    anchor, as Panel's and PanelLetter's cards open with — kept the whole tenth: every card stood
    that much taller in Word.
  • A paragraph whose lines Word sets taller than the page. Word sets every line of a paragraph at
    one height; for lines in more than one size the export writes the tallest line's, and the page
    sets each line at its own, so a wrapped line after a large first one stands closer on the page.
    What a paragraph fell short of the page was owed below it; what it passed the page by was
    not. CompactMono's skills, two lines to a paragraph and each written 0.12pt taller than the
    page, ended its sidebar a point low in Word.

What changed

All in DocxSemanticBackend:

  • writeSpacer: a spacer shorter than its hairline leaves the rest of that tenth as
    hangingBelow — the mechanism a line held to its icon already uses. The next block takes it out
    of the space owed above it, then out of its own top edge, and names what is left (space above). Ending a cell, the space the cell owes below takes it first, then the cell's overhang,
    which a row or a panel takes (its padding below). The spacer is still written, so a band, a
    layer column or a shape container measures and writes it as before.
  • settleTheLinesWithThePage (was oweWhatTheLinesFallShort): a paragraph whose written lines
    pass the page by more than a tenth of a point hangs that below itself the same way.
  • LINES_ROUNDING_TOLERANCE = 0.1pt both ways. Measured: Word sets baselines on a grid 0.12pt
    (1/600in) apart — a 194-twip exact line steps 9.72pt six times and 9.60 once — so a correction
    under a tenth of a point moves a line a step or nothing: at 0.01pt, 13 documents moved nearer the
    page in Word and 13 further. Past that, the two directions are measured, not assumed:
    • lines rounded up to the twip, over enough of them, hang: OrangeOps' achievement lines, a
      0.08pt gap between them written as a tenth, and SlateOrange's profile stand nearer the page
      in Word with it (median 0.29 → 0.22pt and 0.48 → 0.40pt; without it, as on 2.5-dev);
    • what rounding takes from a paragraph written at its tallest line is still not owed (the
      lineIsNotTheTallest gate stays): owed, it stood the lines below ModernInvoice's parties a
      grid step or two lower, and four other documents further from the page.
  • holdAParagraphsTopEdgeOnItsPage, leaveThePageAbove and a spacer opening a page now name
    what a block on the page above never took (nameAHangLeftOver) before they forget the hang, pull
    and border below it — and the paragraph one forgets them before its early return for a paragraph
    with no top edge to hold, which used to leave the hang to be named, falsely, on the next page.

Docs: the recipe's Spacers and Line height rows, the "A spacer is its height alone" paragraph and
the one on text running past a block's foot; CHANGELOG.md (v2.5.0).

Verification

  • ./mvnw -B -ntp clean verify → BUILD SUCCESS: render-docx 1318 run, 0 failures, 1
    skipped
    (+11), qa 1820, core 818.
  • DocxLineSurplusTest (7): the space below takes a taller paragraph's surplus; what it cannot
    take is named; a paragraph ending a page leaves it there; what a list item ending a page never
    took is named there; the space below takes what a one-size paragraph's rounding adds; a paragraph
    written at its tallest line owes nothing for what its rounding takes; a spacer of no height with
    no space below it is named. DocxContainerSpacingTest (+3): the spacer in the flow, opening a
    card, and ending a padded card (its padding below takes the tenth). DocxSpaceAboveOnANewPageTest
    (+1): a spacer of no height opening a page holds the gap in its line, and the block after it
    takes the tenth.
  • Six sabotages, each caught by its own test: the spacer's hang dropped (five tests), the
    paragraph's surplus dropped (four), the shortfall's gate dropped so rounding is owed, the reset
    moved back after the page-top return, rounding's surplus left, the page-top leftover forgotten
    unnamed.
  • DOCX fidelity corpus: 9 of 62 documents change, and the report names nothing more (969 notes,
    as on 2.5-dev). Word baseline (word-windows*.tsv, via scripts/docx-visual/word-fidelity.ps1 -Update): all 9 nearer the page in median — cv-panel 0.83 → 0.46pt, cv-compact_mono p90
    0.91 → 0.36pt (its sidebar's foot from about +0.95pt to −0.05pt), cv-nordic_clean 0.23 → 0.16,
    cv-monogram_sidebar 0.28 → 0.20, cv-orange_ops 0.29 → 0.22, cv-slate_orange 0.48 → 0.40;
    letter-panel 0.16 → 0.14 in median, further in its p90 (0.21 → 0.29pt, below). LibreOffice on
    Windows (libreoffice-windows*.tsv): 7 nearer, cv-orange_ops unchanged, letter-panel 0.13 →
    0.19pt. LibreOffice on Linux (libreoffice-linux*.tsv, from CI's docx-fidelity artifact): 7
    nearer — cv-orange_ops 0.26 → 0.18pt among them — and letter-panel 0.13 → 0.19pt, as on
    Windows.

Notes for review

  • letter-panel's text now starts where the page sets its card; what is left is how far both
    editors seat its body text off the page's baseline in an exact line — 0.12pt higher in Word, as
    in LibreOffice — a separate defect.
  • Places that still drop a hang without naming it, each for a line held to its icon already and
    now for these two sources too, left for their own change: a band's later layer and a layer
    column resuming after another (resumeHere); a band's foot; a composed table cell or a layer
    column, whose cell overhang nothing reads; and a block other than a paragraph, a panel, a table,
    a row, a band or a spacer that the layout opens a page with — a list, a line pair, a picture —
    which takes the hang from the page above and names it falsely.

Lane: shared-engine (DOCX backend) — render-docx only, no public API.

…t their block out of the space below

- A spacer shorter than its hairline - a width anchor of no height, as
  Panel's and PanelLetter's cards open with - leaves the rest of that tenth
  of a point hanging below it: the next block takes it out of the space
  between them, or names it (space above). It stood every card that much
  taller in Word.
- A paragraph Word sets taller than the page - written at its tallest line,
  where the page sets a wrapped line after a large first one at its own
  height - hangs what it passes the page by below itself the same way.
- Both by more than a tenth of a point only: Word sets lines on a grid
  0.12pt apart, and a paragraph written at its tallest line still owes
  nothing for its twip rounding.
- A paragraph the layout opens a page with, with no top edge of its own,
  leaves the hang, pull and border below the block above on the page above.

Fidelity baselines (Word, LibreOffice on Windows) rewritten: nine documents
nearer the page in Word, none further.
…w page forgets it

- A paragraph or a spacer the layout opens a page with, and a panel, table
  or row it moves there, name a hang left over a block on the page above -
  a list item, a line pair, which take nothing out of a top edge of their
  own - before they forget it, as the next block would have.
- oweWhatTheLinesFallShort is settleTheLinesWithThePage: it owes a shortfall
  and hangs a surplus. Its Javadoc, and the CHANGELOG and recipe, state the
  rounding rule as measured: what rounding adds over a paragraph's lines
  hangs, what it takes from one written at its tallest line is not owed.
- Tests: a one-size paragraph's rounding surplus, a list item's leftover
  named at a page end, a spacer of no height named with no space below it,
  ending a padded card, and taken by the block after it on a new page.
…cer and line hangs

Measured by CI's LibreOffice on Linux on this branch: 8 of the 62
documents change, 7 standing nearer the page in median and letter-panel
further, as on Windows.
@DemchaAV
DemchaAV merged commit 5a466d5 into 2.5-dev Oct 8, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-unmasked-panel-drifts branch October 8, 2026 15:46
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