Skip to content

fix(docx): settle a list item's exact lines with the page, and what a cell's lines carry at its end - #897

Merged
DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-panel-cell-end
Oct 14, 2026
Merged

DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-panel-cell-end

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Why

Word sets each paragraph's lines at an exact written height, rounded to the twip, and the page sets each line at its own height. settleTheLinesWithThePage reconciles the two for a paragraph:

Two places were left out.

  • List items. Each item is written as a paragraph of its own (applyItemLineGap), and its lines were never settled with the page. Panel's skills are six one-item lists of two lines each. Its projects are items too: a name, then a description of three or four lines. Every line is written a fiftieth of a point taller than the page's. In Word the skills ran 0.21pt taller and the projects 0.35pt. Each card's row grew by as much, and every card under them stood that much lower.
  • A cell's end. What a cell's lines still carried at its end was dropped with the cell. A row of columns is as tall in Word as its tallest column, so a column's last 0.08pt grew Panel's experience card by as much.

The two were measured in Word on Panel's card edges. Each card's content was summed paragraph by paragraph and compared with the page: Skills +0.21, Experience +0.09, Projects +0.35. These match the rounding the export leaves to the hundredth. LibreOffice on Windows piles it up the same way, about +0.04 per two-line item.

What changed

  • Items settled. settleTheLinesWithThePage now delegates to settleTheLines(target, page, lineCount, startPage, endPage), and settleTheItemsLines calls it for each item from applyItemLineGap. An item's lines are owed, hung or carried with the paragraphs' around them, kept to their page by the item's own page.
  • Mixed item heights. A list's items are written at its tallest line, so an item the page sets shorter now hangs what it passes the page by out of the space below it. Where that space cannot hold it, the rest is named, as for a paragraph written at its tallest line.
  • Pictures in items. makeRoomForPicturesInItsLines takes back what an item owed for its lines once its pictures grow them, as a paragraph's is. The paragraph path now calls the same method.
  • Item heights read with their gap. DocxLayoutMetrics.ItemText carries the gap the page sets between its lines. ItemText.linesHeight() reads its lines' height as linesHeight(DocumentNode) reads a paragraph's, through one shared method.
  • The cell's end settles what is left. settleWhatTheCellsLinesCarry, called by writeInCell before the cell's hang is taken, settles it however little:
    • a carry short of the page is owed below the cell's last block;
    • a carry past the page is added to the hang below the cell. The writer of the cell takes it or lets it go as it does a line hanging there: a row, a stack's column or a panel takes it, a table's composed cell lets it go.
  • Tests isolated. DocxRowOverhangTest and DocxLayerColumnsOverhangTest fill the taller column with 8pt lines, 7.4pt and a whole number of twips. Their 7pt filler's own rounding now hangs below its cell, and these tests are about the icon line beside it. DocxPanelTailTest's named hangs now include the contact's first line's carried rounding, read from the export, and its numbers are compared to the hundredth with a tolerance.

Measured

  • 48 of the corpus's 62 DOCX change.

  • Lines that moved by more than 0.05pt:

    Editor Nearer the page Further from it
    Word 602 282
    LibreOffice on Windows 576 253
  • Of those, the items alone change 10 DOCX: 153 lines nearer and 44 further in Word. The cell's end adds 526 nearer and 251 further.

  • Medians:

    Template Word LibreOffice on Windows
    Panel 0.37 → 0.21pt (p90 0.79 → 0.43) 0.34 → 0.21pt (p90 0.71 → 0.33)
    TerracottaRail 0.30 → 0.17pt 0.25 → 0.17pt
    ConsultingInvoice 0.25 → 0.17pt 0.14 → 0.08pt
    ClassicSerif 0.16 → 0.07pt 0.07 → 0.03pt
    SerifHeadline 0.33 → 0.26pt 0.26 → 0.19pt
  • Further lines. A tenth of a point settled moves a line by one step of Word's 0.12pt grid, or not at all. Where another error already stood the lines high, they now stand a step higher.

    • ObsidianInvoice: the masthead's right column carries 0.064pt past the page at its end. Taken out of the space under the masthead as two twips, it moves the parties and items up 0.12pt. Median 0.21 → 0.33pt in Word, 0.26 → 0.33pt in LibreOffice.
    • VioletGrid: 0.21 → 0.27pt.
    • MidnightNavy: 0.16 → 0.22pt in Word.
  • No document's found lines or lines past 2pt change.

  • The report's 969 notes over the corpus are unchanged.

  • This PR's CI run measures LibreOffice 24.2 on Linux. 53 of 62 documents pass, up from 52: invoice-merchant's lines past 2pt drop under the limit, and invoice-payments' go from 45 to 40. Lines nearer the page / further: 625 / 303. Every document it moves is one of the 48.

  • The Word and Windows LibreOffice baselines are rewritten for the 48 documents. The Linux LibreOffice baseline follows from that run.

  • assets/readme/examples/word-export-companion.docx is re-rendered.

Verification

  • DocxLinesRoundingCarryTest gains 9 tests:

    • the items of one list;
    • lists of an item each;
    • wrapped items with a gap between their lines;
    • items written at a larger item's line;
    • an item whose pictures grow its lines;
    • a page an item opens;
    • a row's cell whose lines fall short at its end;
    • a row's cell whose lines pass the page at its end;
    • a panel whose padding takes what its lines pass.

    Each measures where Word sets the lines under them against the page, rows included.

  • Undoing any one part fails the suite:

    • items not settled;
    • the cell's end not settled;
    • only its owe dropped;
    • only its hang dropped;
    • the items' gap left out;
    • the items' page not noted;
    • an item's pictures not taking back what it owed;
    • a paragraph's pictures not taking back what it owed.
  • ./mvnw -B -ntp clean verify → BUILD SUCCESS.

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

… cell's lines carry at its end

Each list item is written as a paragraph of its own, and its exact lines were
never compared with the page's: Panel's skills and projects, items of two
lines and more, each line written a fiftieth of a point taller than the
page's, ran 0.21 and 0.35pt taller in Word and grew their cards by as much.
An item's lines are now owed, hung or carried on with the paragraphs' around
them, and taken back where its pictures grow them, as a paragraph's are.

What a cell's lines still carried at its end went with the cell. The cell's
end now settles it: owed below its last block where the lines fall short of
the page, hung below the cell where they pass it, as a line hanging there is.
…rite the Word and Windows LibreOffice baselines

The line height row of the DOCX recipe and the changelog describe the items'
lines settled with the page and the cell's end settling what its lines carry.
The Word and Windows LibreOffice fidelity baselines are rewritten for the 48
documents the change moves, and the Word export companion is re-rendered.
… parsed

assertReads now checks each number the note states against the expected one
written to the hundredth, or the hundredth either side, instead of parsing it.
@DemchaAV
DemchaAV merged commit d85aa59 into 2.5-dev Oct 14, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-panel-cell-end branch October 14, 2026 10:52
DemchaAV added a commit that referenced this pull request Oct 14, 2026
… run of #897 (#898)

Since d85aa59 a list item's exact lines are settled with the page, and a
cell's end settles what its lines carry. The Linux baseline still held the
40 documents it moves there as they stood before. Their rows are taken from
CI run 38551609338 on that change; with them the baseline is that run's
measurement byte for byte.
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.

2 participants