Skip to content

fix(docx): keep the space above a stack of one layer opening a page - #929

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-one-layer-page-top
Oct 20, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-one-layer-page-top

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Why

Word drops a paragraph's space above at the top of a page. The export holds that space for a
paragraph or a picture the layout moves to a new page in an empty line kept with it
(holdAParagraphsTopEdgeOnItsPage), but only when overlayDepth == 0: in the flow, outside every
layer stack. A stack of one layer is written in the flow too. It raises overlayDepth and
oneLayerDepth together, so the first block opening one on a new page lost everything above it.

Measured in Word 16 and LibreOffice on Windows, the first line (or a picture's top) on page 2:

Stack of one layer opening page 2 Page Before, Word / LibreOffice After, Word / LibreOffice
paragraph, 20pt margin 106.2 85.3 / 85.3 105.3 / 105.3
picture, 20pt margin (top) 92 72 / 72 92 / 92
stack margin 14pt 100.2 85.3 / 85.3 99.3 / 99.3
stack padding 9pt, paragraph margin 6pt 101.2 85.3 / 85.3 100.3 / 100.3
paragraph margin 10pt after a 12pt flow gap 96.2 85.3 / 85.3 95.2 / 95.3
plain stack, no edge (control) 86.2 85.3 / 85.3 85.3 / 85.3

Nothing was named. A paragraph's baseline stands 0.9–1.0pt higher than the page's in each
case, as under the plain control. In every case the page broke where it did before.

What changed

  • writeParagraph and writeImage hold the top edge where overlayDepth == oneLayerDepth:
    in the flow, a stack of one layer's included. A band, or anything else laid over the flow, is
    left as it was.
  • The layer's offset in its stack is not held. Word does not write that offset, and
    reportTheLayerMoved names it: "Word writes it where the stack starts".
    • layerOffsetDown holds the offset arithmetic the report used inline. writeContainerBody
      adds the offset to layerOffsetNotWritten while it writes the stack's layer, and restores
      it after.
    • holdAParagraphsTopEdgeOnItsPage takes the offset off the page's space above. Without
      that, the line would hold the offset wherever space is owed above the layer, against the
      layer's own note.
    • Measured: such a layer stays where the stack starts in both editors, with or without space
      owed above it, as named.
  • A pull, a border or a hanging line left by the block above now stays on the page above
    for a stack's first paragraph or picture opening the next page, as in the flow.
    • Before, a plain paragraph opening a stack on a new page, after a paragraph pulling 5pt up,
      was named as standing 5pt lower ("space above").
    • On the new page that was false.
  • Docs:
    • the recipe's paragraph and image rows;
    • the capability matrix's image row;
    • CHANGELOG v2.5.0.

Verification

  • Corpus, 62 DOCX before and after: all 62 are byte-identical, and 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.
  • DocxStackOfOneLayerOnANewPageTest (new, 11 tests), each with the premise that the stack
    opens the second page where it matters:
    • held in a line kept with it:
      • a paragraph's margin, with the space owed above staying above the line;
      • a picture's margin;
      • the stack's margin;
      • the stack's padding with the paragraph's margin;
    • only the first block of the stack gets a line;
    • a pull out of the paragraph above: named on that paragraph's page, not on the next;
    • layer offsets:
      • an offset down: the line holds the margin alone, and the offset is still named;
      • an offset up: the stack's whole margin is held;
      • an offset alone: no line;
      • an offset ends with its stack;
    • a paragraph opening a band moved to a new page: left to the band.
  • 6 sabotages, all caught:
    • a paragraph held in the flow only;
    • a picture held in the flow only;
    • the offset held;
    • a picture held in any overlay;
    • a paragraph held in any overlay;
    • the offset outliving its stack.
  • ./mvnw -B -ntp clean verify: BUILD SUCCESS on the first commit.
    • The second commit adds the pull test and a CHANGELOG line.
    • After it: render-docx's 1870 tests pass, qa's 27 documentation and guard tests pass, and so
      do the knowledge checks.
  • Knowledge pack: the API surfaces, stability doc, claims and routes checks pass, and core
    javadoc passes with no warnings.

Two cases this leaves as they were, not measured:

  • A spacer opening a stack of one layer on a new page: writeSpacer still holds the gap
    above in its line only outside every stack.
  • A band nested in a stack of one layer: writeOverlayBand still marks the page it opens
    only outside every stack.

Lane: render-docx (semantic backend). DOCX output changes only for a paragraph or a picture
opening a stack of one layer that the layout moves to a new page. One with space above it gains
the line holding it. Without such space, a pull, border or hanging line left by the block above
no longer reaches it.

A stack of one layer is written in the flow, and Word drops its first
paragraph's space above at the top of a page as any other's, but the
line holding that space was written only outside every stack. Measured
in Word and LibreOffice, a paragraph or a picture with a 20pt margin
opening such a stack on a new page stood 20pt high, and one in a stack
with a 14pt margin 14pt high.

A paragraph or a picture opening one is now held as in the flow, with
the stack's own edges. A layer's offset in its stack, which is named
and not written, is not held either.
@DemchaAV
DemchaAV marked this pull request as draft October 20, 2026 10:08
A plain paragraph opening a stack of one layer on a new page now leaves
a pull out of the block above on that block's page, as one in the flow
does: the report no longer names it as standing lower there. The test
pins both sides: named on the paragraph's page, not on the next.
@DemchaAV
DemchaAV marked this pull request as ready for review October 20, 2026 10:15
@DemchaAV
DemchaAV merged commit e42c2bb into 2.5-dev Oct 20, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-one-layer-page-top branch October 20, 2026 11:05
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