From ab25abd492310ce53c259ab1db351d6c2f58dd39 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Thu, 13 Aug 2026 19:15:21 +0200 Subject: [PATCH] fix: a column gap wider than the text is no gap at all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit w:cols/@w:space is twips, and a producer writing EMU there (360000 = 1cm in EMU) had the importer clamp its 635cm to the model's 5cm ceiling: a two-column section came out with 51.3mm columns on a 152.4mm text width, its second column 24.9mm right of where LibreOffice draws it. Below the text width LibreOffice lays the declared gap out literally (probed from 720 to 8000 twips), so only the unholdable value is dropped — both sides now start the second column at 108.0mm. --- src/lib/import/CLAUDE.md | 5 +++++ src/lib/import/docx.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/import/CLAUDE.md b/src/lib/import/CLAUDE.md index 2638b33..41b2da1 100644 --- a/src/lib/import/CLAUDE.md +++ b/src/lib/import/CLAUDE.md @@ -75,6 +75,11 @@ docDefaults ← the table style ← the cell paragraph's own style chain ← dir style name; read as an override the way a table style zeroing `w:after` reads, every table loses the space after its cells' paragraphs and a long document comes out pages short. +**A `w:cols` gap the section's text cannot hold is no gap at all.** `w:space` is twips, and +a producer writing EMU there (360000 = 1cm in EMU, 635cm read as twips) would otherwise +leave columns of no width. Under that width LibreOffice lays the declared gap out literally +— probed to 10.16cm on a 15.24cm text — so only the unholdable value is dropped. + Body text with no resolved font falls back to the *document's own theme minor font* (`docx.ts` `runMarks`), not the editor default — Word's implicit body default. Headings don't (they keep the editor heading default); DOCX page margins default to Word's 2.54cm. diff --git a/src/lib/import/docx.ts b/src/lib/import/docx.ts index e6989c9..5cc0b5c 100644 --- a/src/lib/import/docx.ts +++ b/src/lib/import/docx.ts @@ -23,6 +23,7 @@ import { applyUniformRunFont, pairAlignedFrames, sinkOffsetFrames, type OdtImpor import { chartDataUrl } from './chart'; import { deobfuscateOdttf, type EmbeddedFont } from '../fonts/embeddedFonts'; import { cellPaddingAttr, DEFAULT_CELL_PADDING, type CellPadding } from '../editor/extensions/tableCellPadding'; +import { clampColumnGap } from '../editor/extensions/columns'; import { astToLatex } from '../math/latex'; import { parseOmml, OMML_NS } from '../math/omml'; @@ -2089,7 +2090,12 @@ function sectPrColumns(sectPr: Element | null, ctx: Ctx): { count: number; gapCm count = 3; } const space = intAttr(cols, W, 'space') ?? (colEls[0] ? intAttr(colEls[0], W, 'space') : null) ?? 283; - return { count, gapCm: Math.min(5, Math.max(0, round2(twipToCm(space)))) }; + // A gap the section's text has no room for is a producer's unit slip — EMU written into + // a twips attribute — and lays out as no gap at all, not as columns too narrow to hold a + // word. Below that LibreOffice honours the declared value literally (probed). + const gapCm = round2(twipToCm(space)); + const fits = gapCm > 0 && (count - 1) * gapCm < sectionContentWidthCm(sectPr); + return { count, gapCm: fits ? clampColumnGap(gapCm) : 0 }; } // Block types a columns node can contain (mirrors import/odt.ts).