fix: a hoisted box keeps its anchor paragraph's alignment

An as-char figure frame is centred by the paragraph it sits in, and the
importer lifts it out of that paragraph — so a frame whose own style says
"from-left" landed at the margin instead of the middle of the column.
This commit is contained in:
Steffen Becker committed 2026-08-10 11:20:32 +00:00
1 parent a021bc42aa
commit 6b6ee22f5b
3 files changed
+19 -6

No files matched your search

+6 -4
View File
@@ -38,7 +38,7 @@ export interface TextBoxAttrs {
wrap: WrapMode;
wrapOffset: number | null; // cm from the text column's left edge
wrapOffsetY: number | null; // cm below the anchor paragraph
wrapAlign: string | null; // 'center' = set against the middle of the column
wrapAlign: string | null; // 'center'/'right' = set against the middle/far end
paddingCm: number; // inset ring around the text (ODF fo:padding)
shapeKind: ShapeKind;
fillColor: string | null;
@@ -383,10 +383,12 @@ class TextBoxView {
} else if (a.wrap === 'topBottom') {
d.style.clear = 'both';
}
// Set against the middle of the column, unless the file placed it by coordinate.
if (a.wrapAlign === 'center' && a.wrapOffset == null && a.wrap !== 'left' && a.wrap !== 'right') {
// Set against the middle or the far end of the column, unless the file placed the
// box by coordinate.
if ((a.wrapAlign === 'center' || a.wrapAlign === 'right')
&& a.wrapOffset == null && a.wrap !== 'left' && a.wrap !== 'right') {
d.style.marginLeft = 'auto';
d.style.marginRight = 'auto';
if (a.wrapAlign === 'center') d.style.marginRight = 'auto';
}
}
+2 -2
View File
@@ -590,7 +590,7 @@ function textBoxDescriptor(node: TiptapNode): TextBoxExport {
wrap: wrapAttr === 'left' || wrapAttr === 'right' || wrapAttr === 'topBottom' ? wrapAttr : 'inline',
wrapOffsetCm: typeof a.wrapOffset === 'number' ? round3(a.wrapOffset) : null,
wrapOffsetYCm: typeof a.wrapOffsetY === 'number' ? round3(a.wrapOffsetY) : null,
wrapAlign: a.wrapAlign === 'center' ? 'center' : null,
wrapAlign: a.wrapAlign === 'center' || a.wrapAlign === 'right' ? a.wrapAlign : null,
paddingCm: typeof a.paddingCm === 'number' ? round3(a.paddingCm) : TEXTBOX_PADDING_CM,
shapeKind: kind === 'roundRect' || kind === 'ellipse' ? kind : 'textbox',
fill: typeof a.fillColor === 'string' && a.fillColor ? a.fillColor : null,
@@ -3029,7 +3029,7 @@ function textBoxGraphicStyle(box: TextBoxExport, index: number): string {
// An as-char frame keeps only its horizontal-pos: that is what centres a figure
// frame, and the anchor paragraph this export mints carries no alignment.
const wrap = box.wrap === 'inline'
? (box.wrapAlign === 'center' ? ' style:horizontal-pos="center" style:horizontal-rel="paragraph-content"' : '')
? (box.wrapAlign ? ` style:horizontal-pos="${box.wrapAlign}" style:horizontal-rel="paragraph-content"` : '')
: ` ${imageWrapProps(box.wrap, box.wrapOffsetCm, box.wrapAlign)} style:number-wrapped-paragraphs="no-limit"` +
` style:horizontal-rel="paragraph-content"` +
` style:vertical-pos="${box.wrapOffsetYCm != null ? 'from-top' : 'top'}" style:vertical-rel="paragraph"`;
+11
View File
@@ -800,6 +800,17 @@ function convertBlocks(elements: Element[], ctx: Ctx, kind: BlockKind, boldByDef
if (anchor && !(pending.length && anchorIsEmpty)) out.push(anchor);
if (!pending.length) return;
if (kind === 'body') {
// A box lifted out of its anchor paragraph keeps the alignment that paragraph gave
// it: an as-char figure frame is set against the middle of the column by the
// paragraph it sits in, not by anything of its own.
const align = anchor?.attrs?.textAlign;
if (align === 'center' || align === 'right') {
for (const b of pending) {
if (b.type === 'textBox' && b.attrs && b.attrs.wrapOffset == null && !b.attrs.wrapAlign) {
b.attrs.wrapAlign = align;
}
}
}
out.push(...pending);
} else {
ctx.warnings.add('Text boxes nested in table cells or other text boxes were flattened');