fix: import of columns

This commit is contained in:
Steffen Becker committed 2026-07-08 19:17:42 +00:00
1 parent e6fbf9703c
commit 15f7e50976
5 files changed
+80 -12

No files matched your search

+1 -1
View File
@@ -916,7 +916,7 @@ function pushColumnRuns(inner: Node[], cols: { count: number; gapCm: number }, o
if (COLUMNS_ALLOWED.has(block.type)) {
run.push(block);
} else {
ctx.warnings.add('Tables, text boxes and nested sections inside a multi-column section were moved out of the columns');
ctx.warnings.add('Tables and text boxes inside a multi-column layout were moved out of the columns');
flush();
out.push(block);
}
+16 -5
View File
@@ -286,9 +286,18 @@ export function importOdt(bytes: Uint8Array): OdtImportResult {
if (!body) throw new Error('Not a text document (no office:text body).');
const ctx: Ctx = { resolver, warnings, files, imageCache: new Map(), pendingBlocks: [] };
const blocks = convertBlocks(Array.from(body.children), ctx, 'body');
let blocks = convertBlocks(Array.from(body.children), ctx, 'body');
if (blocks.length === 0) blocks.push({ type: 'paragraph' });
// Whole-document columns declared on the page layout (Word-style) instead of a
// text:section: wrap the body's wrappable runs the way a section would be.
const pageCols = resolver.pageColumns();
if (pageCols) {
const wrapped: Node[] = [];
pushColumnRuns(blocks, pageCols, wrapped, ctx);
blocks = wrapped;
}
const hf = resolver.masterPageHF();
if (hf.hasVariants) {
warnings.add('Per-page header/footer variants (first/even pages) are not supported — the default one was used');
@@ -372,9 +381,9 @@ function parseXml(xml: string): Document {
// Block types a columns section (columns.ts) can contain.
const COLUMNS_ALLOWED = new Set(['paragraph', 'heading', 'bulletList', 'orderedList']);
// Wrap a multi-column <text:section>'s converted blocks into columns nodes: maximal
// runs of allowed types become one node each; anything else (tables, text boxes,
// nested sections) is emitted between the runs at top level with a warning.
// Wrap a multi-column region's converted blocks into columns nodes: maximal runs of
// allowed types become one node each; anything else is emitted between the runs at
// top level — with a warning, except columns nodes (they keep their own layout).
function pushColumnRuns(inner: Node[], cols: { count: number; gapCm: number }, out: Node[], ctx: Ctx): void {
let count = cols.count;
if (count > 3) {
@@ -390,7 +399,9 @@ function pushColumnRuns(inner: Node[], cols: { count: number; gapCm: number }, o
if (COLUMNS_ALLOWED.has(block.type)) {
run.push(block);
} else {
ctx.warnings.add('Tables, text boxes and nested sections inside a multi-column section were moved out of the columns');
if (block.type !== 'columns') {
ctx.warnings.add('Tables and text boxes inside a multi-column layout were moved out of the columns');
}
flush();
out.push(block);
}
+15 -4
View File
@@ -272,12 +272,23 @@ export class StyleResolver {
}
// A section style's column layout: { count, gapCm } when it declares more than one
// column, else null. The gap falls back to the per-column indent form (first
// column's fo:end-indent + second's fo:start-indent) LibreOffice sometimes writes.
// column, else null.
sectionColumns(name: string | null): { count: number; gapCm: number } | null {
const el = name ? this.sectionStyleEls.get(name) : null;
if (!el) return null;
const props = el.getElementsByTagNameNS(NS.style, 'section-properties')[0];
const props = el?.getElementsByTagNameNS(NS.style, 'section-properties')[0];
return this.columnsFrom(props ?? null);
}
// Column layout of the master page's page layout — Word-produced files put
// whole-document columns here instead of in a text:section.
pageColumns(): { count: number; gapCm: number } | null {
return this.columnsFrom(this.pageLayoutEl()?.getElementsByTagNameNS(NS.style, 'page-layout-properties')[0] ?? null);
}
// <style:columns> under a properties element: { count, gapCm } when it declares more
// than one column, else null. The gap falls back to LibreOffice's per-column indent
// form (first column's fo:end-indent + second's fo:start-indent).
private columnsFrom(props: Element | null): { count: number; gapCm: number } | null {
const cols = props?.getElementsByTagNameNS(NS.style, 'columns')[0];
if (!cols) return null;
const count = parseInt(cols.getAttributeNS(NS.fo, 'column-count') ?? '', 10);