Skip to content

fix(docx): take a cell's last line hanging past its row out of the gap under the row - #827

Merged
DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-editorial-proposal
Oct 3, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-editorial-proposal

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Why

A one-line paragraph held to its inline icon (holdPicturesInTheLine) has an exact Word line that reaches as far below the page's line as the icon's ink does, plus half a point. It gives that room back out of the space above what follows (hangingBelow).

At the end of a table cell there was nothing following it, and writeInCell dropped the hang. Word's cell then ended that much lower than on the page, and so did the row whenever that cell was its tallest.

TimelineMinimal. The header is a row: name on the left, a contact stack on the right. The stack's last line, "GitHub", holds an 11pt icon lowered 2.5pt beside its text.

  • In Word that line's box ends at 88.33pt, where the page's row ends at 86.5.
  • The rule under the header stood 1.8pt low, and the whole page under it with it. The CV's median drift was 1.44pt, its cover letter's 1.30pt.

What changed

  • writeInCell. At the end of a cell, the hang of its last line first comes out of the space the cell owes under it, before that space is written as the last paragraph's space below, as it comes out of the gap above the next block in the flow. Space a pull keeps from being written is not counted as room. What passes that space is left in cellOverhang.
  • writeRow. For each cell, it subtracts the room the row leaves that cell's child (roomBelowInItsRow).
    • The room is the row's content height less the child's height with its margins, from the layout's placements; nothing is taken when the layout does not place both on one page.
    • It does not depend on where the row sets its children: in a row set at its foot or centred, a shorter cell sits lower, and Word's row still grows only when a cell holds more than the row.
    • The largest remainder, less a card's border already standing below another cell (borderBelow, since Word's row is as tall as the taller of the two), is how much taller Word's row is.
    • After the table that becomes hangingBelow, so the next block takes it from its space above as it would from a band's.
  • Baselines. word-windows, libreoffice-windows and their -lines files are rewritten for the three documents this moves.
  • Docs. CHANGELOG.md, and the "Pictures and icons in a line" section of docs/recipes/docx-export.md, which said the row grows.

Verification

  • Template corpus, re-measured in full in both editors. Three documents move; no line is lost and page counts are unchanged.

    Document Editor Median p90 Past 2pt
    TimelineMinimal (CV) Word 1.44 → 0.36pt 1.57 → 0.48pt 0 → 0
    TimelineMinimal (CV) LibreOffice (Windows) 1.40 → 0.45pt 1.40 → 0.50pt 0 → 0
    TimelineMinimal (letter) Word 1.30 → 0.29pt 1.63 → 0.53pt 0 → 0
    TimelineMinimal (letter) LibreOffice (Windows) 1.32 → 0.33pt 1.52 → 0.53pt 0 → 0
    VioletGrid Word 0.35 → 0.23pt 0.81 → 1.30pt 2 → 5
    VioletGrid LibreOffice (Windows) 5.71 → 5.21pt 21.81 → 21.31pt 40 → 39

    Corpus mean |drift| in Word goes 0.450 → 0.430pt, and in LibreOffice 1.807 → 1.783pt.

  • VioletGrid. Its header's contact cell ends with an icon line too, so everything under the header moves 0.48pt up.

    • Most of the page was 0.1–0.6pt low and is now within 0.45pt.
    • The first paragraph under the header goes from +0.11 to −0.37pt.
    • Its education and languages block already stood 0.8–2pt high and goes 0.48pt further. That adds three lines past 2pt. One line, "native", goes from −0.77 to −1.37pt, past the gate's 0.5pt.
    • The 70 lines that move go from a mean of 0.66 to 0.60pt in Word, and from 9.83 to 9.52pt in LibreOffice.
  • New test class, DocxRowOverhangTest, four tests.

    • A row whose taller cell ends with a lowered icon line, held exact to it: the paragraph under the row has its space above shortened by the line's reach below the page's.
    • The same line in the shorter cell, with room under it in the row: nothing is taken.
    • The same in a row set at its foot, where the shorter cell sits at the row's foot on the page: nothing is taken.
    • A cell with 5pt of bottom padding under the line: the padding written below the line is shortened by the reach, and nothing is taken under the row.
  • Each test fails when its mechanism is taken out:

    • the hang carried past the row;
    • the room the row leaves the cell (the bottom-aligned test fails on a room measured from the child's foot);
    • the take from the cell's own space.
  • 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. render-docx runs 839 tests, qa 1814.

Known limits

  • Only a row passes its cells' overhang on. In a stack written as columns, or a panel, a cell's last line still takes the cell's own space under it first, but whatever passes it is not taken from the gap under the table.
  • VioletGrid's first paragraph under its header (−0.37pt) and its education block carry offsets of their own, not found yet.
  • The Linux LibreOffice baseline is not yet rewritten. It will be taken from this PR's CI run.

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

@DemchaAV
DemchaAV merged commit a9a03b3 into 2.5-dev Oct 3, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-editorial-proposal branch October 3, 2026 10:53
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