Skip to content

fix(docx): stand NavySidebar where the page sets it in Word — placed picture size, ring set-in, header margin, list sides - #802

Merged
DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-navy-sidebar
Oct 1, 2026
Merged

DemchaAV merged 4 commits into
2.5-devfrom
fix/docx-navy-sidebar

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Why

In Word, everything in NavySidebar's navy column stood 5.2pt high (3.3pt in LibreOffice). Its photo also stood a point below its ring, and its CERTIFICATIONS heading stood a line above its badge. The column is a cell of the page's row, and its portrait is a 127pt ring round a 123.8pt photo, in a column 123.8pt wide.

  • The photo was too small. It was sized from the width left where it is written, and that width narrows by every container's insets round it. Inside the ring that left 120.6pt, so the photo came out 3.2pt short.
  • The photo lost the ring round it. The ring sets the photo 1.6pt in from its top and its bottom. The photo was written flush with the ring's top, so that 2 × 1.6pt was lost too.
  • The whole page stood a point low. The page's backgrounds ride in an empty header, a point tall, against the page's top. On a page with no top margin, Word moved the body down under it. The ring is anchored to the page and stayed put, so the photo, in the flow, sat below it.
  • The closing lists lost their indent. ACHIEVEMENTS and CERTIFICATIONS are lists indented 30pt to clear the badge beside their heading.
    • A list's own sides were not written, so in Word their markers stood under the badge.
    • The first achievement then fit on one line instead of two.
    • The heading below rose a line above its badge, which is anchored to the page.

What changed

  • writeImage. A picture the layout placed is written at the size it was placed, less its own padding.
    • Its top and bottom padding are its paragraph's space, as before. Its side padding is not written.
    • A picture the layout did not place (one composed in a table cell, or an export with no layout) is sized as before.
  • writeShapeContainer / layerSetIn. A shape container with a single layer keeps, as space above and below that layer, the gap the page leaves between it and the container's content top and bottom.
    • The layer's own margins are left to the layer.
    • A layer moved past either edge leaves the other no more than the two hold together.
    • A paragraph's line moved past the foot is the exception. It is written where the page puts it, and hangBelowItsBox takes its overhang from the gap under the container, as before.
  • blankZone / reachedPast. An empty header or footer against the page edge reaches a point into the page.
    • When the margin on that edge is narrower than that point, it is written negative (−max(1, margin) twips).
    • That holds the body at the margin in Word, the way placeBand already holds it for a text band.
  • writeList / indentListItemInside. A list's own left and right edges (margin and padding) indent its items.
    • This is the rule writeParagraph applies to a paragraph's edges, the same editor slack in a cell included.
    • The level's hanging indent is added on top, as before.
    • In a cell of a row hanging left, an item now moves left by the hang, as the paragraph beside it does.
  • Docs. CHANGELOG.md. In docs/recipes/docx-export.md: the clipped-shape-container paragraph, the page-backgrounds paragraph (header and footer), and the paragraph on a list's own space.

Verification

  • Full reactor gate: ./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am gives BUILD SUCCESS (core 818, render-pdf 339, render-docx 758, templates 142, testing 127, qa 1791).

    • After install, examples run 93 green.
    • The knowledge checks pass.
  • DocxPortraitTest (new).

    • A photo in a ring wider than its column keeps its 123.8pt.
    • The photo stands 1.6pt inside its ring above and below; the heading under it stands its 22pt plus those 1.6pt down.
    • A padded 100×50pt picture is written 100×50pt, its 10pt top padding the space above it.
    • A layer's own 6pt top margin is written once: the picture stands where the page puts it in its frame.
    • A picture moved past its frame's foot leaves the frame its 60pt: all of it above the picture, none below.
    • A picture moved past its frame's top leaves none of the frame above it and all of it below.
    • A paragraph moved past its frame's foot stands where the page puts it.
  • DocxPageBackgroundTest.

    • On a page with no margin, the header carrying the backgrounds writes the top margin as −1 twip and leaves the bottom at 0.
    • A 0.5pt margin is written −10 twips; a 1pt margin, which the header reaches and no further, stays 20.
    • A 20pt margin stays 400 twips.
  • DocxMultiSectionTest.anEmptyFooterDoesNotLiftABodyThatHasNoBottomMargin. A section with no margin and no footer, after one with a footer, writes its bottom margin as −1 twip and keeps its top at 0.

  • DocxListNumberingTest.

    • A 30pt left, 20pt right margin indents the items 30pt plus the level's 9pt, hanging 9pt, and 20pt from the right.
    • A 30pt padding indents both levels, the second its 6pt step further.
    • A markerless list's margin is its paragraphs' indent.
    • In a row's cell, the margin is less the 2pt a cell keeps.
  • DocxHangingLeftTest.aListInACellOfAHangingRowMovesLeftWithTheTextBesideIt. In a hanging row's cell, a list and a title at the cell's padding stand the level's 9pt apart. The title stands where the page starts it, less the hang.

  • DocxContainerSpacingTest.aShapeContainersEdgesAreSpaceAroundWhatItHolds. The paragraph after a row at the top of a 20pt rectangle now stands 7.05pt lower, as on the page: that is the space the outline holds under the row. 22 + 12.95 + 8 + 7.05 = 18 + 26 + 6.

  • Each rule fails its own test when reverted:

    • Not sizing from the placement fails the ring-size test.
    • Not subtracting the padding fails the padded-picture test.
    • Not keeping the set-in fails the set-in test.
    • Not subtracting the layer's margin fails the layer-margin test.
    • Clamping only the top edge fails the moved-past-the-foot test.
    • Clamping a paragraph's line as well fails the line-past-the-foot test.
    • Not writing the top margin negative fails the page-background test; not writing the bottom one fails the multi-section footer test.
    • Not writing the list's sides fails the list test.
    • Dropping the cell slack fails the list-in-a-cell test.
    • Dropping the hang fails the hanging-row test.
  • Template corpus (62 documents), against 2.5-dev. Word, median drift of matched lines:

    Template Before After
    NavySidebar 5.22pt 0.1pt (38 → 0 lines >2pt off)
    SerifHeadline 0.99pt 0.50pt
    ProfessionalSidebar 1.0pt 0.1pt
    SidebarPortrait 1.0pt 0.1pt
    CharcoalGold 1.1pt 0.2pt
    MonogramSidebar 1.7pt 0.8pt
    SlateOrange 1.6pt 0.6pt
    MidnightNavy 1.4pt 0.8pt
    • In Word, NavySidebar's photo now stands at 26.75pt from the page's top. The page puts it at 26.6pt; it stood at 27.7pt before.
    • Its CERTIFICATIONS heading stands at 732.3pt in Word; the page puts it at 732.2pt.
    • The list rule changes NavySidebar alone. The edge clamp and the hang in a list's indent change none of the 62.
    • No page count changes in Word.
  • LibreOffice.

    • The negative margin moves no line of six of the seven documents it changes.
    • The seventh, MidnightNavy, had its page row moved whole onto a second page, leaving the first empty. It now stands on one page, its lines within 0.1pt of where they stood.
    • NavySidebar goes from 3.33pt to 2.87pt with the picture rules and to 2.8pt with the list rule. SerifHeadline goes from 1.03pt to 0.40pt with the picture rules.

Known limits

  • LibreOffice still moves the body about 3pt down under the empty header on a page with no top margin, as docs/recipes/docx-export.md records.
  • A picture moved past its frame's edge is written pulled back inside the frame, so the frame keeps its height. In the foot test it is written 20pt down where the page draws it 40pt down.
  • layerSetIn keeps the set-in for a container with a single layer. A container of more layers, one of them written and the rest drawn, goes through the band path (writeOverlayBand), which this does not change.
  • A list's and a paragraph's own sides are not written under a shape container (overlayDepth > 0), where the layer's box places them; a list in a card keeps the indent of the card's content.

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

@DemchaAV DemchaAV changed the title fix(docx): write a placed picture at its placed size, and keep a shape container's one layer set in from its edges fix(docx): stand NavySidebar where the page sets it in Word — placed picture size, ring set-in, header margin, list sides Oct 1, 2026
@DemchaAV
DemchaAV merged commit d985d79 into 2.5-dev Oct 1, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-navy-sidebar branch October 1, 2026 13:21
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