Skip to content

fix(docx): keep a band of layers whole on the page the layout moves it to - #933

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-band-opening-page
Oct 21, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-band-opening-page

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Why

The layout moves a layer stack or a shape container to a new page whole: both are atomic there. Word
re-paginates on its own and was told so only for a stack of one layer. Measured in Word 16 and
LibreOffice on Windows, with nothing in the report:

  • a band of two layers (a stack of more than one layer, written one layer after the other) the
    page moved to the next page stood with its first layer at the foot of the page above. This held
    for a text band, a band with a picture as its second layer, one with a margin and one in a section;
  • a shape container of three lines stood with two of them there.

Once the band stands whole on the new page, Word drops the space above its first paragraph there,
as at the top of any page. Its 14pt margin was lost, so was a band's with a backdrop laid first (the
text 14pt above the backdrop the page draws round it), the 14pt margin of the stack of one layer a
band is nested in, and a section's 10pt.

What changed

  • writePlacedNode keeps every layer stack and shape container in the flow (movedWhole)
    through keepOnOnePage, as it kept a stack of one layer: w:keepLines on each paragraph,
    w:keepNext on all but the last. In a table's or a panel's cell the row keeps or breaks them, as
    before. keepThePanelMayBreak uses the same helper.

  • writeOverlayBand counts a band as opening its page in the flow, a stack of one layer
    included (overlayDepth == oneLayerDepth, was == 0). The band's first block written in the body
    clears that: a paragraph, as before, and now a table too (newTable). A table of text cells
    writes no body paragraph, so a caption under one opening the band stood to be held as if it
    opened the page.

  • opensItsPageInTheFlow (new) says when a paragraph, a picture or a spacer holds its top
    edge, and the three now ask it:

    • as before, a block the layout starts on a new page in the flow or a stack of one layer;
    • now also a band's first block where the band opens its page. The band decides, not the block: a
      backdrop drawn first is on that page already, so the paragraph after it starts no page of its
      own.

    The holds are the ones the flow uses: holdAParagraphsTopEdgeOnItsPage for a paragraph and a
    picture, holdTheGapAboveInTheLine for a spacer.

  • holdAParagraphsTopEdgeOnItsPage takes over the space the band resumed (resumeHere())
    before adding the block's own edge. Left to the line's newBodyParagraph, resumeHere() zeroed
    that edge, and the block's own margin would be lost from above the line.

  • Docs:

Verification

  • Probe, before and after, Word and LibreOffice alike:

    • each of seven band kinds at five heights: text, picture second, picture first, own margin,
      a backdrop laid first, nested in a stack of one layer, in a section;
    • a spacer opening a band;
    • a one-layer shape container, A/B with the shape container keep removed.

    Every band the layout moves to page 2 now stands whole there. Its first paragraph, picture or
    spacer stands within 0 to 1.2pt of the page, and the backdrop within 0.3pt.

  • The same probe on the base, five kinds: text, picture second, own margin, nested, in a
    section. Each split, or stood 10 to 14pt high. The band with a backdrop, measured kept whole but
    without its hold, stood 14pt high.

  • Corpus, 62 DOCX against the base:

    • 10 change, in w:keepLines and w:keepNext alone;
    • all 10 re-rendered in Word and LibreOffice: every word on the same page at the same place as
      before;
    • the report's 1246 notes are identical.
  • Example: word-export-companion.docx was regenerated. It differs from the committed one only
    in docProps/core.xml, the export's time.

  • Tests:

    • DocxBandOnANewPageTest (new, 12 tests). Each case that turns on the page first asserts
      the page the block starts on.

      • held: the band's margin over its first paragraph; the band's padding with the paragraph's
        margin, the filler's space whole above the line; a paragraph after a backdrop; a picture
        first, without and with its own margin; a spacer first, kept with what follows; a band nested
        in a stack of one layer; a band in a section;
      • not held: the second layer, the caption under a table opening the band, the same band on its
        page, a band in a row's cell.
    • DocxKeepTogetherTest:

      • a band and a shape container chained into one unit;
      • a band in a row's cell left to its row.

      These replace aBandIsNotChainedHere. The three …IsLeftToTheBand tests, which pinned the band
      writing no line, are gone with that behaviour.

  • 11 sabotages, all caught:

    • one layer only kept;
    • shape containers not kept;
    • kept in a cell;
    • opens its page at the top level only;
    • no band opens a page;
    • the band's first block must start a page itself;
    • a paragraph, picture or spacer opening a band not held (three);
    • the band's space taken over after the block's own edge;
    • a body table leaving the band's opening for the block after it.
  • ./mvnw -B -ntp clean verify: BUILD SUCCESS.

  • Knowledge pack: the API surfaces, stability doc, claims and routes checks pass, and core javadoc
    passes with no warnings.

Notes for review

Measured, and left for their own changes:

  • A list or a row opening the band. A band whose first block is a list or a row still loses its
    top edge. A list opening a page loses it in the plain flow too, 14pt measured. Nor is the edge
    held for a table, a panel or a line pair written first.
  • A later layer reaching above the layers before it. Its margin, or a contained picture's room
    in its box, may reach above where the layers before it end. Such a layer stands that much low,
    13pt measured, as its resume is clamped at zero (DocxLayerColumns.flow). With the keep, such a
    band at the foot of a page the layout keeps it on moves to the next page whole instead of
    splitting. A diagnostic run over the corpus found no such layer.
  • Canvases and alignment wrappers. These are atomic in the layout too and are not kept here:
    Word was not measured splitting them.

Lane: render-docx (semantic backend).

…t to

The layout moves a layer stack or a shape container to a new page
whole, but only a stack of one layer was kept so in Word. Measured in
Word and LibreOffice, a band of two layers the page moved to the next
page stood with its first layer at the foot of the page above, and a
shape container of three lines with two of them there.

Every layer stack and shape container in the flow is now kept on its
page as a block kept together is. Set on the new page whole, a band
then lost the space above its first block, as Word drops it at the top
of a page: its first paragraph, picture or spacer now holds that space
in a line kept with it, as one in the flow does, also for a band in a
stack of one layer and after a backdrop the band draws first.
@DemchaAV
DemchaAV marked this pull request as draft October 21, 2026 03:35
A band opening its page held the top edge of its first paragraph,
picture or spacer until it wrote a body paragraph. A table of text
cells writes none, so the caption under a table that opens the band
was held as if it opened the page, an empty line above it in the
middle of the page.

A table written in the body now ends the band's opening as a paragraph
does, and every test of a block opening the page first checks the page
the layout starts it on.
@DemchaAV
DemchaAV marked this pull request as ready for review October 21, 2026 03:50
@DemchaAV
DemchaAV merged commit 6985b4b into 2.5-dev Oct 21, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-band-opening-page branch October 21, 2026 06:48
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