Files
edentext/tests/unit/mirror-master-pair.test.ts
Steffen Becker 430bef5795 fix: two masters naming each other are a mirrored pair, not a title page
LibreOffice's Left Page / Right Page chain into each other through
style:next-style-name, which was read as the different-first-page idiom: one
master's geometry and zones governed the whole section, so a book-style body
lost its running head on every second page and its inner/outer margins with
it. A next-style cycle now becomes one mirrored setup — the right page's
geometry, the left page's zones as the even variant — and style:page-usage
left/right marks the margins as mirrored.
2026-09-04 19:30:06 +02:00

62 lines
3.2 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { zipSync, strToU8 } from 'fflate';
import { importOdt } from '../../src/lib/import/odt';
// Two masters naming each other through style:next-style-name are LibreOffice's Left
// Page / Right Page: one mirrored setup whose left half is the even-page variant, not a
// title page handing over to a running one.
const NS =
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" ' +
'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" ' +
'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" ' +
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"';
function odt(chained: boolean): Uint8Array {
const layout = (name: string, usage: string, left: string, right: string) =>
`<style:page-layout style:name="${name}" style:page-usage="${usage}"><style:page-layout-properties
fo:page-width="21cm" fo:page-height="29.7cm" fo:margin-top="2cm" fo:margin-bottom="2cm"
fo:margin-left="${left}" fo:margin-right="${right}"/></style:page-layout>`;
const next = (n: string) => (chained ? ` style:next-style-name="${n}"` : '');
const styles = `<?xml version="1.0"?><office:document-styles ${NS}><office:styles>
<style:style style:name="Standard" style:family="paragraph"/>
<style:style style:name="Body" style:family="paragraph" style:master-page-name="Right"/>
</office:styles><office:automatic-styles>
${layout('pmR', 'right', '3.6cm', '2.7cm')}${layout('pmL', 'left', '2.7cm', '3.6cm')}
</office:automatic-styles><office:master-styles>
<style:master-page style:name="Right" style:page-layout-name="pmR"${next('Left')}>
<style:header><text:p text:style-name="Standard">odd head</text:p></style:header></style:master-page>
<style:master-page style:name="Left" style:page-layout-name="pmL"${next('Right')}>
<style:header><text:p text:style-name="Standard">even head</text:p></style:header></style:master-page>
</office:master-styles></office:document-styles>`;
const content = `<?xml version="1.0"?><office:document-content ${NS}><office:body><office:text>
<text:p text:style-name="Standard">first</text:p>
<text:p text:style-name="Body">second</text:p>
</office:text></office:body></office:document-content>`;
return zipSync({ 'content.xml': strToU8(content), 'styles.xml': strToU8(styles) });
}
const zoneText = (doc: unknown): string =>
JSON.stringify(doc).match(/"text":"([^"]*)"/)?.[1] ?? '';
describe('a left/right master pair', () => {
const section = importOdt(odt(true)).hfSections?.[1];
it('is one mirrored page setup, stored as the right (odd) page', () => {
expect(section?.margins).toEqual({ top: 2, bottom: 2, left: 3.6, right: 2.7, mirrored: true });
expect(section?.marginsFirst).toBeNull();
});
it('makes the left master the even-page variant', () => {
expect(section?.differentOddEven).toBe(true);
expect(section?.differentFirstPage).toBe(false);
expect(zoneText(section?.header)).toBe('odd head');
expect(zoneText(section?.headerEven)).toBe('even head');
});
it('leaves an unchained pair alone', () => {
const plain = importOdt(odt(false)).hfSections?.[1];
expect(plain?.differentOddEven).toBe(false);
expect(zoneText(plain?.header)).toBe('odd head');
});
});