Skip to content

fix(docx): hold the space above a list, a line pair and a band's table on a new page - #935

Merged
DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-list-page-top
Oct 21, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-list-page-top

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

Word drops the space above a paragraph at the top of a page. A paragraph, a picture and a spacer
the layout moves to a new page already hold that space in an exact line kept with them
(holdAParagraphsTopEdgeOnItsPage, holdTheGapAboveInTheLine). Three kinds of block did not.

Measured in Word 16 and LibreOffice on Windows, against the PDF. Word and LibreOffice set every line
on these pages 0.9pt above the PDF, so a block is measured against that:

  • A list wrote its margin and padding as the space above its first item, which Word dropped:
    • a list with a 14pt margin stood 14pt high;
    • one padded 10pt, or opening a section padded 10pt, stood 10pt high;
    • one opening a stack of one layer or a band with a 14pt margin stood 14pt high.
  • A title and its dates set as one line (DocxLinePair) in a stack with a 14pt margin stood
    14pt high, in the flow and opening a band.
  • A row, a table or a panel opening a band the layout moves to a new page stood 14pt high where
    the band had a 14pt margin. leaveThePageAbove wrote the band's whole resumed space, its margin
    included, as the space below the last line of the page above.

What changed

  • writeList: a list that opens its page in the flow (opensItsPageInTheFlow, the same test a
    paragraph uses) holds its top edge through holdTheTopEdgeAboveItsLine. That method wraps the
    paragraph hold for a block that writes its own edge above its first line. It returns how much of
    that edge the line took, and the list no longer owes that part above its first item. A margin
    below zero is still owed, as a paragraph's is.
  • applyItemLineGap: a list's wrapped first item takes the gap between its lines out of the line
    holding the list's edge (listEdgeLine), as a paragraph does through topEdgeLineOver. Left in
    the item's lines, a first item with a 4pt gap stood 2.2pt low. It now stands where a paragraph
    moved the same way does.
  • writeLinePair: a pair that opens its page in the flow holds its edge the same way. The hold
    covers the overlay's margin and the room above the line inside it (pair.above()). A pair whose
    text the page sets above its overlay's top has no such room to hold, and is left as it was.
  • leaveThePageAboveTheBand, used by writeTableWithItsOwnSpacing and writePanel for a
    band's first block where the band opens its page:
    • the page above keeps what the block before the band owes below itself (bandGapBefore,
      recorded where the band measures its space), as for such a block moved in the flow;
    • the band's own top edge is carried down as a container edge opening with the block, and
      holdTheSpaceAboveOnItsPage holds it with the block's own. The edge is the space the page
      leaves above the block's own margin there (spaceAboveOnItsPage).
    • The band measures its resume to the first leaf it writes, past a row's or a panel's own edges.
      The block writes those itself, so that part of the resume is not owed again.
  • holdAParagraphsTopEdgeOnItsPage now returns whether it wrote a line, for the new wrapper.
    Nothing else changes for its existing callers.

Verification

  • Probe, 23 cases, each moved to a new page, in Word and LibreOffice. The base was measured from
    a separate checkout of 2.5-dev. Every number below is the editor's distance from the PDF; the
    paragraph control stands at −0.9 in both.

    Case Base Branch
    list, 14pt margin −14.9 −0.9
    list, 10pt padding −10.9 −1.0 / −0.9
    list opening a section padded 10pt −10.9 −1.0 / −0.9
    list opening a stack of one layer, 14pt margin −14.9 −0.9
    list opening a band, 14pt margin −14.9 −0.9
    list, two items −14.9 −0.9
    list, wrapped first item, 4pt gap (paragraph control −1.7) −11.7 −1.7
    line pair, stack with a 14pt margin −14.9 −0.9
    line pair opening a band −14.9 −0.9
    row opening a band (row in the flow −0.9) −14.9 −0.9
    table opening a band (table in the flow −1.3 / −1.2) −15.2 −1.3 / −1.2
    panel opening a band (panel in the flow −1.3 / −1.2) −15.3 −1.3 / −1.2

    The controls (a paragraph, a row, a table and a panel in the flow, a row and a table in a stack of
    one layer, a painted row) are byte-identical to the base. The page above a panel opening a band
    ended with 20pt below its last line, the band's margin and the panel's own padding; it now ends
    with none, as nothing above the band owed any.

  • Corpus: all 62 DOCX byte-identical to 2.5-dev, and the 1246 notes unchanged.

  • Tests:

    • DocxBlockOpeningAPageTest (new, 19 tests):
      • a list's margin and padding held in a line kept with its first item;
      • a section's padding;
      • a band's margin;
      • a wrapped first item's gap taken from the line;
      • no line for a list on its page or in a row's cell;
      • a table, a row and a panel opening a band, and a table after the band's backdrop;
      • the gap before such a band, and only the gap, left at the foot of the page above, for a
        table, a row with its own padding, and a panel with its own margin;
      • a line pair in the flow, opening a band, and set down inside its stack;
      • a pair whose text starts above its overlay, left unheld;
      • a list holding no line takes nothing from an earlier picture's line.
    • Four existing tests pinned the old space above a block opening a page, written where Word
      drops it:
      • two in DocxListItemLowerTest now assert that the line holds it, and that the first item is
        still not lowered;
      • one each in DocxBandRaiseTest and DocxLinePairRaiseTest now assert the line holds it, and
        that the first line is still not raised.
      • The two raise tests each gain a twin with no edge to hold, where the gap stays above the line
        and the guard against raising into it is still tested.
  • 15 sabotages, all caught, each by a test that names it:

    • lists: no list hold; the first item's gap not taken from the line; a stale hold line kept; the
      list's edge owed twice;
    • a band's first block: the band's edge left above a table; the whole resume held; the edge not
      carried down; the band flag not checked after a backdrop; the whole resume, or nothing, left on
      the page above; no panel hold;
    • line pairs: no pair hold; the pair's room not held; a pair set above its overlay held.
  • Gate: ./mvnw -B -ntp clean verify on the reactor: BUILD SUCCESS (render-docx 1943, qa 1828),
    plus the JDK 17 knowledge checks and core Javadoc.

Known, not changed here

  • A line pair whose text starts above its overlay's top keeps the old space above, which Word
    drops at a page top. The hold would cover the overlay's margin, not the space above the text.
  • A later layer under a panel opening a band stands 5.7pt low against the panel, in the base
    and on the branch, with nothing named. The panel itself now stands where it should.
  • Other blocks still open a page or a band without a hold: a stack written as side-by-side
    columns, a rule, a barcode, a contents line, a chart's fallback. None of these was measured.

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

…e on a new page

Word drops the space above a paragraph at the top of a page. A paragraph,
a picture and a spacer the layout moves to a new page already hold theirs
in a line kept with them; a list, a title and its dates set as one line,
and a table, a row or a panel opening a band did not. Measured in Word and
LibreOffice, a list with a 14pt margin stood 14pt high, one padded 10pt or
opening a section padded 10pt stood 10pt high, and so did one opening a
stack of one layer or a band with a 14pt margin, and such a line pair. A
row, a table or a panel opening a band with a 14pt margin stood 14pt high:
the band's margin was left at the foot of the page above.

A list and a line pair now hold their top edge as a paragraph does, in the
flow, in a stack of one layer and first in a band opening its page, and a
list's wrapped first item takes the gap between its lines out of that
line. A pair whose text starts above its overlay's top is left as it was.
A table, a row or a panel opening such a band carries the band's own top
edge down and holds it with its own, as one in the flow holds the edges of
the containers opening with it. The page above keeps what the block before
the band owes below itself, and only that. Each now stands within 0.1pt of
the same block, or a paragraph, moved to a new page in the flow.
@DemchaAV
DemchaAV merged commit 874ac56 into 2.5-dev Oct 21, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-list-page-top branch October 21, 2026 12:04
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