From 98530afe3b68332d08182c655384f21922614c5a Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Fri, 11 Sep 2026 00:23:36 +0200 Subject: [PATCH] fix: a divider line is saved flat, not across its frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A line shape carries no height, and both exporters stood in the 96px default a text box gets — so a horizontal divider was written across the frame's diagonal and came back as one. A line kind keeps the 0 it has. Claude-Session: https://claude.ai/code/session_01Wzs2qG6Y5qm7mKmJHfu44G --- src/lib/export/docx.ts | 7 +++++-- src/lib/export/odt.ts | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/export/docx.ts b/src/lib/export/docx.ts index de1d947..0972693 100644 --- a/src/lib/export/docx.ts +++ b/src/lib/export/docx.ts @@ -17,7 +17,7 @@ import type { import { unzipSync, zipSync, strFromU8, strToU8 } from 'fflate'; import { isSvgDataUrl, svgToPngDataUrl } from '../import/imageFormats'; import { TEXTBOX_PADDING_CM } from '../editor/extensions/textBox'; -import { SHAPES, isShapeKind, drawingMlPath, type ShapeKind } from '../utils/shapes'; +import { SHAPES, isShapeKind, isLineKind, drawingMlPath, type ShapeKind } from '../utils/shapes'; import { cellFormatCode, isCellFormat } from '../utils/cellFormat'; import { DEFAULT_MARGINS, type PageMargins } from '../storage/pageMargins'; import type { Orientation } from '../storage/pageOrientation'; @@ -976,9 +976,12 @@ type TextBoxDocx = { function textBoxDocxDescriptor(node: TiptapNode): TextBoxDocx { const a = node.attrs ?? {}; const wrapAttr = a.wrap; + // A line has no box (see odt.ts): standing in the default height for a divider of + // none draws it across the frame's diagonal. + const line = isShapeKind(a.shapeKind) && isLineKind(a.shapeKind); return { widthPx: typeof a.width === 'number' && a.width > 0 ? Math.round(a.width) : 280, - heightPx: typeof a.height === 'number' && a.height > 0 ? Math.round(a.height) : 96, + heightPx: typeof a.height === 'number' && a.height > 0 ? Math.round(a.height) : line ? 0 : 96, rotationDeg: typeof a.rotation === 'number' ? a.rotation : 0, wrap: wrapAttr === 'left' || wrapAttr === 'right' || wrapAttr === 'topBottom' || wrapAttr === 'through' ? wrapAttr : 'inline', offsetCm: typeof a.wrapOffset === 'number' ? a.wrapOffset : null, diff --git a/src/lib/export/odt.ts b/src/lib/export/odt.ts index 16ed36a..3a85656 100644 --- a/src/lib/export/odt.ts +++ b/src/lib/export/odt.ts @@ -1001,9 +1001,12 @@ function textBoxDescriptor(node: TiptapNode): TextBoxExport { const pxToCm = (px: number) => round3((px * 2.54) / 96); const a = node.attrs ?? {}; const wrapAttr = a.wrap; + // A line has no box: a frame of no height is the horizontal divider both word + // processors write, and standing in the default for it draws a diagonal instead. + const line = isShapeKind(a.shapeKind) && isLineKind(a.shapeKind); return { widthCm: pxToCm(typeof a.width === 'number' && a.width > 0 ? a.width : 280), - heightCm: pxToCm(typeof a.height === 'number' && a.height > 0 ? a.height : 96), + heightCm: pxToCm(typeof a.height === 'number' && a.height > 0 ? a.height : line ? 0 : 96), rotationDeg: typeof a.rotation === 'number' ? a.rotation : 0, wrap: wrapAttr === 'left' || wrapAttr === 'right' || wrapAttr === 'topBottom' || wrapAttr === 'through' ? wrapAttr : 'inline', wrapOffsetCm: typeof a.wrapOffset === 'number' ? round3(a.wrapOffset) : null,