feat: a table keeps the cell margins its file gave it

The cell padding was a constant in three places that disagreed with each
other, so a narrow column lost 0.38cm it did not have: a bibliography
marker column of 0.71cm wrapped "[2] " onto three lines and every entry
grew with it, costing a fixture a whole page.

The table now carries its own margins (w:tblCellMar, direct or from the
table style; fo:padding on the ODF cell style) and both exports write
them back. A side within a twentieth of a millimetre of the default is
the default, or Word's 108 twips would tag every table.
This commit is contained in:
Steffen Becker committed 2026-08-08 12:18:16 +00:00
1 parent e4b679ad8b
commit db0fe71cdb
11 files changed
+174 -16

No files matched your search

+31
View File
@@ -4,6 +4,10 @@
import { describe, it, expect } from 'vitest';
import { unzipSync, strFromU8 } from 'fflate';
import { buildOdt } from '../../src/lib/export/odt';
import { importOdt } from '../../src/lib/import/odt';
import { buildDocx } from '../../src/lib/export/docx';
import { importDocx } from '../../src/lib/import/docx';
import { cellPaddingAttr } from '../../src/lib/editor/extensions/tableCellPadding';
type N = any;
@@ -37,4 +41,31 @@ describe('table cell padding', () => {
expect(props).toMatch(/fo:padding-bottom="0cm"/);
}
});
it('collapses a producer-rounded default, keeps a real value', () => {
// Word's 108 twips and LibreOffice's 0.191cm are both our 0.19cm default.
expect(cellPaddingAttr([0, 0.1905, 0, 0.191])).toBeNull();
expect(cellPaddingAttr([0.026, 0.026, 0.026, 0.026])).toEqual([0.026, 0.026, 0.026, 0.026]);
});
it('round-trips a table’s own cell margins', async () => {
const tight: N = structuredClone(doc);
tight.content[1].attrs = { cellPadding: [0.03, 0.05, 0.03, 0.05] };
const bytes = await buildOdt(tight, { top: 2, bottom: 2, left: 2, right: 2 }, 'portrait');
const xml = strFromU8(unzipSync(bytes)['content.xml']);
expect(xml).toMatch(/fo:padding-left="0\.05cm"/);
expect(xml).toMatch(/fo:padding-top="0\.03cm"/);
const back = await importOdt(bytes);
const table = back.content.content!.find((n: N) => n.type === 'table') as N;
expect(table.attrs?.cellPadding).toEqual([0.03, 0.05, 0.03, 0.05]);
const docx = await buildDocx(tight, { top: 2, bottom: 2, left: 2, right: 2 }, 'portrait');
const backDocx = importDocx(docx);
const docxTable = backDocx.content.content!.find((n: N) => n.type === 'table') as N;
// Word measures in twips, so 0.05cm comes back a twentieth of a point off.
for (const [i, cm] of [0.03, 0.05, 0.03, 0.05].entries()) {
expect(docxTable.attrs?.cellPadding[i]).toBeCloseTo(cm, 2);
}
});
});