feat: a text box anchors its text top, middle or bottom
Both formats carry a vertical anchor on the box body — ODF draw:textarea-vertical-align, DOCX wps:bodyPr/@anchor — and both sides of the I/O hard-coded "top", so an imported box dropped its centering and a saved one never got it. New textVAlign attr, read and written in both formats, rendered by flexing the rotor (the handles are absolute and stay out of that flow), set in the Shape Format tab beside the vertical-text button.
This commit is contained in:
1 parent
fc5c39322f
commit
f33e708b84
13 files changed
+118
-14
No files matched your search
+1
-1
@@ -12,7 +12,7 @@ const ORDERED_DEFAULTS: Record<string, unknown> = {
|
||||
fixed: false, key1: '',
|
||||
joinPrev: false, // pageBreaks.ts's split marker; the export merges the halves
|
||||
marginLeft: 0, marginRight: 0, marginTop: 0, marginBottom: 0, // a table's, once a command has touched them
|
||||
alt: '', inFront: false, paddingCm: 0.15, flipV: false, textVertical: false, // the editor's own picture and box defaults
|
||||
alt: '', inFront: false, paddingCm: 0.15, flipV: false, textVertical: false, textVAlign: 'top', // the editor's own picture and box defaults
|
||||
wrapFromPage: false,
|
||||
// index attrs the DOCX TOC field has no switch for come back at their defaults
|
||||
maxLevel: MAX_HEADING_LEVEL, leader: '.', citationStyle: 'key',
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Where the text sits in a box taller than it is: ODF's draw:textarea-vertical-align,
|
||||
// Word's wps:bodyPr/@anchor.
|
||||
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 doc = (attrs: N): N => ({
|
||||
type: 'doc',
|
||||
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'davor' }] },
|
||||
{ type: 'paragraph', content: [{
|
||||
type: 'textBox',
|
||||
attrs: { width: 200, height: 160, ...attrs },
|
||||
content: [{ type: 'paragraph', content: [{ type: 'text', text: '53%' }] }],
|
||||
}] }],
|
||||
});
|
||||
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 vertically anchored text box', () => {
|
||||
it('round-trips through ODF', async () => {
|
||||
const bytes = await buildOdt(doc({ textVAlign: 'middle' }), margins, 'portrait');
|
||||
expect(strFromU8(unzipSync(bytes)['content.xml'])).toContain('draw:textarea-vertical-align="middle"');
|
||||
expect(boxOf(importOdt(bytes))?.attrs.textVAlign).toBe('middle');
|
||||
});
|
||||
|
||||
it('round-trips through DOCX', async () => {
|
||||
const bytes = await buildDocx(doc({ textVAlign: 'bottom' }), margins, 'portrait');
|
||||
expect(strFromU8(unzipSync(bytes)['word/document.xml'])).toContain('anchor="b"');
|
||||
expect(boxOf(importDocx(bytes))?.attrs.textVAlign).toBe('bottom');
|
||||
});
|
||||
|
||||
it('leaves a top-anchored box alone', async () => {
|
||||
const odt = await buildOdt(doc({}), margins, 'portrait');
|
||||
expect(strFromU8(unzipSync(odt)['content.xml'])).toContain('draw:textarea-vertical-align="top"');
|
||||
expect(boxOf(importOdt(odt))?.attrs.textVAlign).toBeUndefined();
|
||||
const docx = await buildDocx(doc({}), margins, 'portrait');
|
||||
expect(strFromU8(unzipSync(docx)['word/document.xml'])).toContain('anchor="t"');
|
||||
expect(boxOf(importDocx(docx))?.attrs.textVAlign).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user