Skip to content

fix(docx): keep two tables in a row from merging into one - #732

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

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-adjacent-tables

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Why

Word and LibreOffice treat two w:tbl elements with nothing between them as one table. They join the second table's rows onto the first table's column grid.

The export wrote consecutive tables back to back. A row (addRow) is written as a one-row table, so two rows in a row merged the same way.

Measured in LibreOffice on a filled card holding a zebra table followed by a narrower table: the card's table came out at half its width, with text broken letter by letter ("Ro / w / one"). Found by swapping tables in the XML — each table rendered correctly on its own, and any table placed directly before the narrower one collapsed.

While fixing that, a second defect showed. Space owed below a table landed on the last paragraph written, which sits above the table. A card ending in a table followed by a page break, the end of the document or a following table put its bottom padding between the card's title and its table.

What changed

  • newTable: separate from a table directly above. When the body's last element is a table, a separator paragraph goes in first (separateFromTheTableAbove).
    • It is w:lineRule="exact" with a height of a tenth of a point.
    • It carries the rest of the gap the layout keeps between the two tables as w:before, so the second table starts where the page starts it.
    • It belongs to the gap, not to either table. It takes a panel's fill so the band is unbroken. It does not take the panel's borders: on a lone paragraph between tables, a stroked card would get a rule across it at every junction.
    • It keeps with the table below it (w:keepNext). A keepWithNext block ending in a table therefore stays kept, and a bookmark opened on the separator counts the page the table lands on.
  • Why a tenth of a point. The first version was a full point. Measured in LibreOffice against the engine's PDF:
    • 1pt added 2.5pt / 1.75pt at the two junctions;
    • 0.1pt adds 1.6pt / 0.85pt;
    • 0.05pt adds 1.55pt / 0.8pt, so going thinner stops mattering.
    • The remainder is LibreOffice adding the border width to row height, which is a known separate item.
  • newTable: the paragraph above no longer carries later space. lastBodyParagraph is reset once a table is written. Space owed after the table becomes the next paragraph's w:before, or is dropped when nothing follows, instead of going back above the table.
  • In a cell, a nested table is already followed by the paragraph Word requires, so no separator is needed there. That paragraph now carries space owed after the nested table: a padded card ending in a nested table keeps its bottom padding inside the cell instead of losing it.
  • Recipe tables row and CHANGELOG under v2.5.0.

Verification

In an editor. The card probe rendered in LibreOffice beside the engine's PNG:

  • the zebra table, the narrower table and the row come out as three separate tables at the engine's widths, in the engine's order and spacing;
  • before the change, the first two merged at half width.

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 has no adjacent tables and did not move.

New DocxAdjacentTablesTest (9):

  • table, table → table / 0.1pt separator / table;
  • row, row → two tables with a separator;
  • the separator's before is the layout's 12pt gap less its own tenth (238 twips);
  • a paragraph already between two tables adds nothing;
  • a card ending in a table, then a page break: the title carries no w:after;
  • a separator inside a filled card is shaded;
  • a separator in a filled, stroked card is shaded and has no pBdr, while the card's own paragraphs keep theirs;
  • a separator after a keepWithNext block ending in a table has keepNext;
  • a padded card ending in a nested table in a cell writes its padding after the nested table.

Sabotage checks:

  • disabling the separator fails 4 tests;
  • dropping the lastBodyParagraph reset fails the page-break test;
  • dropping the border removal, the keepNext or the cell carrier fails the matching new test.

The 0.1pt height is measured in LibreOffice. Word may raise so short an exact line to its own minimum, which is still under the 1pt this started at.

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

Word and LibreOffice join two tables with nothing between them into
one, laying the second one's rows on the first one's grid; a row is
written as a table, so two rows in a row merged too. A paragraph a
tenth of a point tall now separates them, carrying the rest of the
gap the layout keeps and the fill of the panel they sit in.

Space owed below a table no longer lands on the paragraph above it:
after a table is written the paragraph before it stops being the one
that carries owed space, so a card ending in a table no longer opens
its bottom padding between its title and the table.
On a lone paragraph between two tables a stroked panel's borders drew
a rule across the card at every junction; the separator now keeps the
fill only. It also keeps with the table below it, so a keepWithNext
block ending in a table stays kept and a bookmark opened on the
separator counts the page the table lands on. After a table nested in
a cell, space owed below it goes to the paragraph that closes the
cell instead of being dropped.
@DemchaAV
DemchaAV force-pushed the fix/docx-adjacent-tables branch from b570ffb to 8cc8361 Compare September 23, 2026 16:51
@DemchaAV
DemchaAV merged commit 62fac81 into 2.5-dev Sep 23, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-adjacent-tables branch September 23, 2026 17:03
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