Skip to content

fix(docx): hold a container's content inside its sides - #733

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-panel-padding
Sep 23, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-panel-padding

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Why

A container's margin and padding hold its content in from its edges, and on the page a card's text sits inside the card. The DOCX export carried a container's top and bottom edge as paragraph spacing, but never its sides.

In LibreOffice, a filled card with 14pt padding and a left accent had:

  • its text flush with the page margin, touching the card's edge;
  • the accent bar right against the first letter;
  • wrapping at a different point than the page, because the text had 28pt more width.

What changed

  • Content inset. writeContainerBody accumulates each container's left and right margin plus padding (insetLeft / insetRight), restoring them afterwards. newBodyParagraph writes the total as w:ind (applyInset), so nested containers add up. A paragraph outside any container gets no w:ind, as before.
  • Panel edges at the card's edge, not the text's. Word draws a side border its w:space outside the text and shades the paragraph out to that border. In applyContainerPaint:
    • A panel's left and right borders are spaced by its padding. The accent bar lands at the card's edge and the fill reaches it.
    • A filled side with no border gets a hairline (w:sz="2") in the fill's own colour, spaced the same way, so the band reaches that edge too.
    • w:space stops at 31pt, so a wider padding brings the band's edge in by the difference.
  • Numbered list items. A paragraph's own w:ind replaces its numbering level's, which would drop the hanging indent.
    • indentListItemInside writes the inset plus the level's indent, and keeps the hanging.
    • It spaces the item's left border from where the editor measures a hanging paragraph's border: its first line, one nesting step per level. With the whole level indent, the first attempt put the bar 9pt outside the card on every item (measured).
  • The panel records its edge on entry (ContainerPaint.at), and a side border is spaced by the distance from the text to that edge. A padded section nested in a card therefore keeps the card's bar at the card's edge. Spacing by the panel's own padding stepped the bar in by the inner padding.
  • Rows and tables move in with the text. indentTable writes w:tblInd for body tables and rows once they are written. The value is the inset plus the first cell's written margin: LibreOffice measures w:tblInd to the first cell's text, so the inset alone left the border 4pt short. Measured, the table's border lands on the card's text line, as on the page.
  • Pictures and row fallbacks use the width left between the insets (availableWidth). Before, a full-width picture in a padded card ran past the right margin.
  • A nested numbered item (depth > 0) spaces its border from its first line even when there is no inset.
  • Cells start from their own edge. writeCellNode resets the inset and restores it, because a table's cells are not inset by the containers around the table.
  • Recipe updated. "What a panel keeps and loses" now says what carries, including the 31pt limit. CHANGELOG under v2.5.0.

Not in this change: the panel is still paragraph-by-paragraph. Word has no element that wraps a run of paragraphs, so a filled card is shading and borders on each paragraph. In LibreOffice this leaves several gaps:

  • The accent bar breaks beside rows and tables, and between paragraphs whose indents differ.
  • The band has white gaps where vertical spacing sits between blocks, and none under a table.
  • The top and bottom padding stay outside the shading.

Spaced top and bottom borders were tried and broke the card into pieces around a list. Writing a painted card as a one-cell table would carry all of it; that is proposed as a follow-up. The inset logic here stays useful either way, for unpainted containers and for aligning rows, tables and pictures.

Verification

In an editor. A filled card with 14pt padding and a 3pt left accent, holding two paragraphs and a bullet list, converted with LibreOffice and compared with the engine's PNG:

  • the accent bar is at the card's edge, continuous past the list;
  • the text and the bullets start 14pt in, with the list's continuation lines hanging;
  • lines wrap at the same words as the page.

Gate. Full reactor gate (clean verify over core, render-pdf/docx/pptx, templates, testing, qa, coverage) → BUILD SUCCESS. Examples after install: 93 green. The committed word-export-companion.docx did not move.

New DocxPanelInsetTest (12):

  • a padded panel: w:ind 14pt on both sides, the accent spaced 14, a fill-coloured right hairline spaced 14;
  • an unpainted padded container still indents its text;
  • nested containers add up;
  • a list item in a panel: w:ind is inset + 180 twips, hanging 180, and its border is spaced 14;
  • a paragraph outside any container gets no w:ind;
  • a cell inside a padded panel starts from its own edge;
  • a 40pt padding caps the border space at 31;
  • a padded section inside a panel keeps the panel's bar at the panel's edge (space 24);
  • a row and a table in a padded section get w:tblInd = inset + first-cell margin;
  • a table outside any container has no w:tblInd;
  • an 800pt picture in a padded section fits the 312pt left between the insets;
  • a nested list item's border is spaced inset + one nesting step.

Sabotage checks:

  • spacing list borders by the whole level indent fails the list test;
  • dropping the cell reset fails the cell test;
  • dropping indentTable, or sizing pictures to the page's width, fails the matching tests.

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

The DOCX export never wrote a container's left and right margin or
padding, so a card's text ran flush with the page margin and its
accent bar sat against the text. Paragraphs are now indented by every
enclosing container's margin and padding, and a panel's side borders
are spaced by its padding, so the bar and the band sit at the card's
edge; a filled side with no border gets a fill-coloured hairline to
carry the band there. A numbered item inside keeps its hanging indent,
its border spaced from its first line. Cells start from their own edge.
…s lines

With paragraphs indented by their containers, three things no longer
lined up. A padded section inside a panel spaced the panel's border by
the panel's own padding, so the bar stepped in; the panel now records
where its edge is and spaces by the distance to it. Rows and tables
stayed at the page margin; they are moved in by w:tblInd, measured to
the first cell's text as the editor measures it. Pictures were sized
to the page's full width and ran past the right margin; they are sized
to the width left between the insets. A nested numbered item's border
is spaced from its first line with or without an inset. Stale docs
that called the sides unrepresentable are updated.
@DemchaAV
DemchaAV merged commit 33db4b4 into 2.5-dev Sep 23, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-panel-padding branch September 23, 2026 19:20
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