Skip to content

fix(docx): set a layer across where its offset moves it - #923

Merged
DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-layer-offsets
Oct 19, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-layer-offsets

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

A layer's offset moves it on the page, and the DOCX export lost it without a word in two places.
Measured in Word 16 and LibreOffice on Windows, on a 400×600pt page with 72pt margins:

Where Page Before, in Word / LibreOffice After, in Word / LibreOffice
a stack's one layer, position(text, 40, 10) x 112 x 72 / 72 x 112 / 112
a band's layer, position(text, -20, 5) x 52 x 72 / 72 x 52 / 52

After the change, a stack's one layer of three lines moved 40pt in stands at x 112 in both editors.
Its lines break at the page's words.

  • A stack of one layer is written as its contents, in the flow, and its layer was written at the
    stack's left edge.
  • A band holds each layer in to its box (holdIn), but only inward: a layer the page sets left of
    the stack was held at the stack's edge.

The one layer's offset down also moves it: the page sets the text 10pt lower than the stack starts.
The stack makes no room for it, so the page lays the text over what follows. Word's flow cannot
lay one line over another, and that loss was unsaid too.

What changed

  • holdIn holds a block to its box on both sides. Where the page sets it past an edge, it
    stands out past it as far, by an indent below zero.

    • applyInset already writes a paragraph's indents below zero outside a table cell, and a
      table's w:tblInd too.
    • A layer moved whole runs as far past the other edge. It keeps the page's measure, so its lines
      break at the page's words.
    • Past an edge the block keeps its box's own width. The room to spare on the side its text does
      not lean on widened the three-line layer by 12.8pt, and it took two lines in both editors.
    • A table cell's text starts no further left than the cell's own. There a block stands left only
      as far as the indents round it give. The rest is named (layer offset), unless the block only
      draws. Its right side stays at the edge, as before: a line the page sets past it gets its room
      from the paragraph itself (letTheLineStandOut).
  • writeContainerBody: a stack of one layer holds its layer through placeAcross, as it
    already holds an alignment's child. In a table cell the moved layer would run past the cell's
    right edge, so it is written where the stack starts.

  • reportTheLayerMoved names the one layer, by more than half a point, in three cases:

    • set lower or higher than its stack starts;
    • in a table cell, set left or right of the stack's edge;
    • composed in a table's cell, where the stack has no place of its own in the layout, by its
      offset as given.

    It skips a layer that only draws. It also skips the vertical case where a band or a column sets
    the next block at the page's distance below what is above it, since that distance holds the
    offset.

  • Docs:

    • the recipe: a bullet of its own for a stack of one layer, and the band bullet;
    • the capability matrix's layer-stack row;
    • the layers entries of DocxNodeFieldLedgerTest for the stack and the shape container;
    • CHANGELOG v2.5.0.

Verification

  • Corpus, 62 DOCX before and after: all 62 are byte-identical, and the report's 1246 notes are
    identical.
    • A scan of the corpus's layers finds no stack of one layer with an offset, and no written layer
      set left of its stack.
    • The one negative offset, in invoice-payments, belongs to the masthead's accent bar, which is
      drawn.
  • Example: word-export-companion.docx was regenerated. It differs from the committed one only
    in docProps/core.xml, the export's time.
  • DocxLayerOffsetTest (new, 14 tests):
    • one layer moved across, both indents and no note;
    • moved down, named;
    • moved up, named;
    • set in by its margin and the stack's padding, not named;
    • one that only draws, in the flow and in a band, not named;
    • one a band writes first, set down by the band, not named;
    • a band's layer left of the stack written 20pt out, while a layer inside is held in and a
      right-aligned one at the edge is not pushed past it;
    • in a row's cell: written at the edge and named; 12pt of 20 given by the indents round it and
      the rest named; 24pt round it and not named; a drawing not named; one layer moved across,
      written at the stack's edge and named;
    • in a table's composed cell, named by its offset.
  • 16 sabotages. 15 are caught. The 16th moves the past-the-edge threshold from −0.5 to +0.5pt,
    which changes an indent by less than half a point.
  • ./mvnw -B -ntp clean verify: BUILD SUCCESS (core 821, render-docx 1819, examples 93, qa 1820).
    The knowledge pack's --check passes.

Lane: render-docx (semantic backend). DOCX output changes only where a layer's box stands past
its stack's edge, or a stack's one layer is moved across. The report gains notes only where an
offset is not written.

A stack of one layer is written as its contents, and its layer stood at
the stack's left edge however far its offset moved it. A band held each
layer in to its box, but only inward, so a layer the page sets left of
the stack stood at the stack's edge. Measured in Word and LibreOffice,
text moved 40pt in, and text set 20pt into the margin, stood at the
margin in both.

holdIn now holds a block to its box on both sides, past an edge by an
indent below zero, with no room to spare added there. A layer moved
whole keeps the page's measure. A stack's one layer is held the same
way through placeAcross. In a table cell, whose text starts no further
left than the cell's own, a block stands left only as far as the
indents round it give, and the rest is named.

An offset down or up moves a stack's one layer without making room for
it, which Word's flow cannot do. It is named (layer offset), as is one
moved across in a table cell, or in a table's composed cell, where it
is written where the stack starts. Not where a band or a column sets
the next block at the page's distance, which holds the offset.
@DemchaAV
DemchaAV merged commit 5f6465b into 2.5-dev Oct 19, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-layer-offsets branch October 19, 2026 14:41
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