textBox becomes an inline node carrying block content, like the frame both formats write, so "in line with text" really is in the line and a box reaches a table cell or a list item through their paragraphs. Both exports write it where it sits (an as-char draw:frame / a wp:inline drawing) and both imports leave it there, which drops the hoisting machinery on either side. Two guards the schema cannot express: no box inside a box, and a selection reaching into a box grows to the box's bounds rather than dissolving it.
61 lines
2.9 KiB
TypeScript
61 lines
2.9 KiB
TypeScript
// A text box whose text runs top-to-bottom. ODF keeps it as the style's own writing
|
|
// mode — a text frame in its graphic properties (where LibreOffice writes it), a
|
|
// drawing shape in its style's paragraph properties (probed both) — Word as `vert`.
|
|
import { describe, it, expect } from 'vitest';
|
|
import { unzipSync, strFromU8 } from 'fflate';
|
|
import { buildOdt } from '../../src/lib/export/odt';
|
|
import { buildDocx } from '../../src/lib/export/docx';
|
|
import { importOdt } from '../../src/lib/import/odt';
|
|
import { importDocx } from '../../src/lib/import/docx';
|
|
|
|
type N = any;
|
|
|
|
const box = (attrs: N): N => ({
|
|
type: 'textBox',
|
|
attrs: { width: 200, height: 160, ...attrs },
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Senkrecht' }] }],
|
|
});
|
|
const doc: N = {
|
|
type: 'doc',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'davor' }] },
|
|
{ type: 'paragraph', content: [box({ textVertical: true })] }],
|
|
};
|
|
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
|
|
|
|
const boxOf = (r: N): N => {
|
|
const find = (n: N): N => n?.type === 'textBox' ? n : (n?.content ?? []).map(find).find(Boolean);
|
|
return find((r.content as N));
|
|
};
|
|
|
|
describe('a text box with vertical text', () => {
|
|
it('round-trips through ODF', async () => {
|
|
const bytes = await buildOdt(doc, margins, 'portrait');
|
|
const xml = strFromU8(unzipSync(bytes)['content.xml']);
|
|
expect(xml).toMatch(/<style:graphic-properties [^>]*style:writing-mode="tb-rl"/);
|
|
expect(boxOf(importOdt(bytes))?.attrs.textVertical).toBe(true);
|
|
});
|
|
|
|
it('keeps the writing mode of a shape in its paragraph properties', async () => {
|
|
const shaped: N = { ...doc, content: [doc.content[0], { type: 'paragraph', content: [box({ textVertical: true, shapeKind: 'roundRect' })] }] };
|
|
const xml = strFromU8(unzipSync(await buildOdt(shaped, margins, 'portrait'))['content.xml']);
|
|
expect(xml).toContain('<style:paragraph-properties style:writing-mode="tb-rl"/>');
|
|
expect(boxOf(importOdt(await buildOdt(shaped, margins, 'portrait')))?.attrs.textVertical).toBe(true);
|
|
});
|
|
|
|
it('round-trips through DOCX', async () => {
|
|
const bytes = await buildDocx(doc, margins, 'portrait');
|
|
const xml = strFromU8(unzipSync(bytes)['word/document.xml']);
|
|
expect(xml).toContain('vert="vert"');
|
|
expect(boxOf(importDocx(bytes))?.attrs.textVertical).toBe(true);
|
|
});
|
|
|
|
it('leaves a horizontal box alone', async () => {
|
|
const flat: N = { ...doc, content: [doc.content[0], { type: 'paragraph', content: [box({})] }] };
|
|
const odt = strFromU8(unzipSync(await buildOdt(flat, margins, 'portrait'))['content.xml']);
|
|
expect(odt).not.toContain('writing-mode="tb-rl"');
|
|
const docx = strFromU8(unzipSync(await buildDocx(flat, margins, 'portrait'))['word/document.xml']);
|
|
expect(docx).toContain('vert="horz"');
|
|
expect(boxOf(importOdt(await buildOdt(flat, margins, 'portrait')))?.attrs.textVertical).toBeUndefined();
|
|
});
|
|
});
|