diff --git a/src/lib/import/odt.ts b/src/lib/import/odt.ts
index 1b53e5f..575b5bb 100644
--- a/src/lib/import/odt.ts
+++ b/src/lib/import/odt.ts
@@ -2881,8 +2881,12 @@ function tableMargins(el: Element, ctx: Ctx): { marginLeft?: number; marginRight
let left = lengthToCm(props['fo:margin-left']);
let right = lengthToCm(props['fo:margin-right']);
+ const align = props['table:align'];
+ // "margins" is ODF for a table filling the space between its own: the side it does
+ // not declare is 0, and style:width is the width the producer's page had, not one to
+ // measure a margin against.
+ if (align === 'margins') { left ??= 0; right ??= 0; }
if (left == null && width != null) {
- const align = props['table:align'];
left = align === 'center' ? (content - width) / 2 : align === 'right' ? content - width : 0;
}
left = left ?? 0; // negative is real: a table may hang into the page margin
diff --git a/tests/unit/table-align-margins.test.ts b/tests/unit/table-align-margins.test.ts
new file mode 100644
index 0000000..5093166
--- /dev/null
+++ b/tests/unit/table-align-margins.test.ts
@@ -0,0 +1,56 @@
+import { describe, it, expect } from 'vitest';
+import { zipSync, strToU8 } from 'fflate';
+import { importOdt } from '../../src/lib/import/odt';
+
+// table:align="margins" is a table stretched between the text margins. Its style:width
+// is the width the producing page had, so deriving a margin from it against ours leaves
+// the table narrow — measured 12.4cm on a 14.7cm text width.
+const NS =
+ 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ' +
+ 'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" ' +
+ 'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" ' +
+ 'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" ' +
+ 'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"';
+
+function odtWithTable(tableProps: string): Uint8Array {
+ const styles = `
+
+
+
+
+
+ `;
+ const content = `
+
+
+
+
+ a
+
+ `;
+ return zipSync({ 'content.xml': strToU8(content), 'styles.xml': strToU8(styles) });
+}
+
+const attrsOf = (tableProps: string) =>
+ (importOdt(odtWithTable(tableProps)).content.content?.[0] as { attrs?: Record }).attrs ?? {};
+
+describe('a table aligned to the margins', () => {
+ it('keeps the full text width whatever width the producer wrote', () => {
+ const attrs = attrsOf('style:width="14.7cm" table:align="margins"');
+ expect(attrs.marginLeft).toBeUndefined();
+ expect(attrs.marginRight).toBeUndefined();
+ });
+
+ it('still honours the margins the file declares itself', () => {
+ const attrs = attrsOf('style:width="12cm" table:align="margins" fo:margin-left="1.5cm"');
+ expect(attrs.marginLeft).toBe(1.5);
+ expect(attrs.marginRight).toBeUndefined();
+ });
+
+ it('leaves a centred table centred', () => {
+ const attrs = attrsOf('style:width="10.7cm" table:align="center"');
+ expect(attrs.marginLeft).toBe(2);
+ expect(attrs.marginRight).toBe(2);
+ });
+});