@bendyline/squisq-formats 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +19 -0
  3. package/README.md +2 -2
  4. package/dist/{chunk-NNHKUXKA.js → chunk-26ISNJ7Y.js} +85 -65
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-KURGXM4I.js → chunk-4V3KCHAP.js} +3 -4
  7. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  8. package/dist/{chunk-EW54IRRS.js → chunk-6S6GU3ZG.js} +5 -6
  9. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  10. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  11. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  12. package/dist/{chunk-SC67HYQJ.js → chunk-EJTNGKEA.js} +5 -8
  13. package/dist/{chunk-WQSHGBLN.js → chunk-G6J326JW.js} +193 -42
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-GVS2XXV6.js → chunk-GYIVES2E.js} +485 -59
  16. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  17. package/dist/{chunk-RLU7UFYU.js → chunk-IPN56VLW.js} +83 -58
  18. package/dist/{chunk-DTDF6QDP.js → chunk-JE6LSIHE.js} +81 -20
  19. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  20. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  21. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  22. package/dist/chunk-OGS5VCGJ.js +446 -0
  23. package/dist/{chunk-74GO3FVS.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-QFLDYKCR.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-7ARKUCQT.js → chunk-X2DEAXNK.js} +54 -2
  27. package/dist/container/index.js +1 -2
  28. package/dist/csv/index.d.ts +27 -2
  29. package/dist/csv/index.js +1 -2
  30. package/dist/docx/index.d.ts +5 -1
  31. package/dist/docx/index.js +9 -11
  32. package/dist/epub/index.d.ts +2 -0
  33. package/dist/epub/index.js +5 -6
  34. package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
  35. package/dist/extract-MN7LA3NL.js +13 -0
  36. package/dist/html/index.d.ts +11 -4
  37. package/dist/html/index.js +3 -4
  38. package/dist/images-ESPQKVTW.js +6 -0
  39. package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
  40. package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -26
  43. package/dist/infer/index.d.ts +3 -3
  44. package/dist/infer/index.js +7 -9
  45. package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
  46. package/dist/layouts-DRWZGSPD.js +10 -0
  47. package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
  48. package/dist/ooxml/index.d.ts +3 -3
  49. package/dist/ooxml/index.js +14 -13
  50. package/dist/pdf/index.d.ts +18 -0
  51. package/dist/pdf/index.js +2 -3
  52. package/dist/pptx/index.d.ts +4 -4
  53. package/dist/pptx/index.js +11 -13
  54. package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
  55. package/dist/registry/index.d.ts +21 -5
  56. package/dist/registry/index.js +9 -6
  57. package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
  58. package/dist/xlsx/index.d.ts +3 -3
  59. package/dist/xlsx/index.js +6 -7
  60. package/package.json +10 -4
  61. package/dist/chunk-4VUWTSGM.js.map +0 -1
  62. package/dist/chunk-6M7Z25LA.js +0 -46
  63. package/dist/chunk-6M7Z25LA.js.map +0 -1
  64. package/dist/chunk-74GO3FVS.js.map +0 -1
  65. package/dist/chunk-7ARKUCQT.js.map +0 -1
  66. package/dist/chunk-DTDF6QDP.js.map +0 -1
  67. package/dist/chunk-EW54IRRS.js.map +0 -1
  68. package/dist/chunk-FE6OJV6O.js.map +0 -1
  69. package/dist/chunk-GVS2XXV6.js.map +0 -1
  70. package/dist/chunk-KURGXM4I.js.map +0 -1
  71. package/dist/chunk-MLX2BOJC.js.map +0 -1
  72. package/dist/chunk-NKAJPJ4G.js.map +0 -1
  73. package/dist/chunk-NNHKUXKA.js.map +0 -1
  74. package/dist/chunk-O3GVVND4.js.map +0 -1
  75. package/dist/chunk-ODL3SSPT.js.map +0 -1
  76. package/dist/chunk-PN52A5AA.js.map +0 -1
  77. package/dist/chunk-QFLDYKCR.js.map +0 -1
  78. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  79. package/dist/chunk-RLU7UFYU.js.map +0 -1
  80. package/dist/chunk-SC67HYQJ.js.map +0 -1
  81. package/dist/chunk-SSUPBUF5.js.map +0 -1
  82. package/dist/chunk-U4MRIFKL.js.map +0 -1
  83. package/dist/chunk-UGYF5AZE.js +0 -275
  84. package/dist/chunk-UGYF5AZE.js.map +0 -1
  85. package/dist/chunk-WQSHGBLN.js.map +0 -1
  86. package/dist/chunk-YRT7GQ5Y.js +0 -28
  87. package/dist/chunk-YRT7GQ5Y.js.map +0 -1
  88. package/dist/container/index.js.map +0 -1
  89. package/dist/csv/index.js.map +0 -1
  90. package/dist/docx/index.js.map +0 -1
  91. package/dist/epub/index.js.map +0 -1
  92. package/dist/extract-OJ7ZQV6P.js +0 -15
  93. package/dist/extract-OJ7ZQV6P.js.map +0 -1
  94. package/dist/html/index.js.map +0 -1
  95. package/dist/images-7FBWPKE3.js +0 -7
  96. package/dist/images-7FBWPKE3.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/infer/index.js.map +0 -1
  99. package/dist/layouts-5VDIRPIJ.js +0 -12
  100. package/dist/layouts-5VDIRPIJ.js.map +0 -1
  101. package/dist/mapTheme-IR27S6IV.js.map +0 -1
  102. package/dist/ooxml/index.js.map +0 -1
  103. package/dist/pdf/index.js.map +0 -1
  104. package/dist/pptx/index.js.map +0 -1
  105. package/dist/registry/index.js.map +0 -1
  106. package/dist/xlsx/index.js.map +0 -1
  107. package/src/__tests__/container.test.ts +0 -230
  108. package/src/__tests__/convert.test.ts +0 -495
  109. package/src/__tests__/csvImport.test.ts +0 -84
  110. package/src/__tests__/docxExport.test.ts +0 -491
  111. package/src/__tests__/docxImport.test.ts +0 -531
  112. package/src/__tests__/epub.test.ts +0 -649
  113. package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
  114. package/src/__tests__/formatRegistry.test.ts +0 -174
  115. package/src/__tests__/html.test.ts +0 -439
  116. package/src/__tests__/htmlImport.test.ts +0 -57
  117. package/src/__tests__/inferTheme.test.ts +0 -135
  118. package/src/__tests__/lossyWarnings.test.ts +0 -146
  119. package/src/__tests__/ooxml.test.ts +0 -271
  120. package/src/__tests__/ooxmlCancellation.test.ts +0 -113
  121. package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
  122. package/src/__tests__/pdfExport.test.ts +0 -322
  123. package/src/__tests__/pdfImport.test.ts +0 -384
  124. package/src/__tests__/plainHtml.test.ts +0 -417
  125. package/src/__tests__/plainHtmlBundle.test.ts +0 -253
  126. package/src/__tests__/pptxExport.test.ts +0 -138
  127. package/src/__tests__/pptxImport.test.ts +0 -145
  128. package/src/__tests__/pptxInferFixtures.ts +0 -314
  129. package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
  130. package/src/__tests__/roundTrip.test.ts +0 -201
  131. package/src/__tests__/roundTripAssets.test.ts +0 -50
  132. package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
  133. package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
  134. package/src/__tests__/roundTripMatrix.test.ts +0 -142
  135. package/src/__tests__/sharedContainer.test.ts +0 -41
  136. package/src/__tests__/sharedImages.test.ts +0 -61
  137. package/src/__tests__/xlsxExport.test.ts +0 -164
  138. package/src/__tests__/xlsxImport.test.ts +0 -80
  139. package/src/__tests__/zipSafety.test.ts +0 -317
  140. package/src/container/index.ts +0 -94
  141. package/src/csv/index.ts +0 -188
  142. package/src/docx/export.ts +0 -1375
  143. package/src/docx/import.ts +0 -1250
  144. package/src/docx/index.ts +0 -26
  145. package/src/docx/styles.ts +0 -145
  146. package/src/epub/export.ts +0 -968
  147. package/src/epub/index.ts +0 -20
  148. package/src/html/docsHtmlBundle.ts +0 -373
  149. package/src/html/htmlTemplate.ts +0 -385
  150. package/src/html/imageUtils.ts +0 -61
  151. package/src/html/import.ts +0 -297
  152. package/src/html/index.ts +0 -212
  153. package/src/html/plainHtml.ts +0 -790
  154. package/src/html/plainHtmlBundle.ts +0 -421
  155. package/src/index.ts +0 -109
  156. package/src/infer/extract.ts +0 -127
  157. package/src/infer/index.ts +0 -199
  158. package/src/infer/mapTheme.ts +0 -176
  159. package/src/infer/types.ts +0 -27
  160. package/src/ooxml/index.ts +0 -111
  161. package/src/ooxml/namespaces.ts +0 -217
  162. package/src/ooxml/readUtils.ts +0 -44
  163. package/src/ooxml/reader.ts +0 -318
  164. package/src/ooxml/themeReader.ts +0 -197
  165. package/src/ooxml/types.ts +0 -103
  166. package/src/ooxml/writer.ts +0 -339
  167. package/src/ooxml/xmlUtils.ts +0 -123
  168. package/src/pdf/export.ts +0 -1084
  169. package/src/pdf/import.ts +0 -1164
  170. package/src/pdf/index.ts +0 -29
  171. package/src/pdf/styles.ts +0 -180
  172. package/src/pptx/export.ts +0 -1184
  173. package/src/pptx/import.ts +0 -455
  174. package/src/pptx/index.ts +0 -52
  175. package/src/pptx/layouts.ts +0 -1222
  176. package/src/pptx/styles.ts +0 -96
  177. package/src/pptx/templates.ts +0 -187
  178. package/src/registry/convert.ts +0 -433
  179. package/src/registry/defaultFormats.ts +0 -413
  180. package/src/registry/errors.ts +0 -46
  181. package/src/registry/index.ts +0 -43
  182. package/src/registry/registry.ts +0 -48
  183. package/src/registry/types.ts +0 -170
  184. package/src/shared/boundedZipArchive.ts +0 -383
  185. package/src/shared/container.ts +0 -28
  186. package/src/shared/fidelity.ts +0 -130
  187. package/src/shared/images.ts +0 -44
  188. package/src/shared/inlineRuns.ts +0 -99
  189. package/src/shared/text.ts +0 -41
  190. package/src/shared/zipEntryCount.ts +0 -151
  191. package/src/shared/zipLimits.ts +0 -296
  192. package/src/shared/zipSafety.ts +0 -19
  193. package/src/xlsx/export.ts +0 -253
  194. package/src/xlsx/import.ts +0 -160
  195. package/src/xlsx/index.ts +0 -35
@@ -1,138 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import type { MarkdownBlockNode, MarkdownDocument } from '@bendyline/squisq/markdown';
3
- import type { Transition } from '@bendyline/squisq/schemas';
4
- import { TRANSITION_TYPES } from '@bendyline/squisq/schemas';
5
- import { markdownDocToPptx } from '../pptx/export';
6
- import { openPackage, getPartXml } from '../ooxml/reader';
7
- import { NS_PML, NS_PML_2010 } from '../ooxml/namespaces';
8
-
9
- function docWithSecondSlideTransition(transition: Transition): MarkdownDocument {
10
- return {
11
- type: 'document',
12
- children: [
13
- {
14
- type: 'heading',
15
- depth: 2,
16
- children: [{ type: 'text', value: 'One' }],
17
- },
18
- {
19
- type: 'paragraph',
20
- children: [{ type: 'text', value: 'Intro' }],
21
- },
22
- {
23
- type: 'heading',
24
- depth: 2,
25
- children: [{ type: 'text', value: 'Two' }],
26
- attributes: {
27
- params: {
28
- transition: transition.type,
29
- ...(transition.duration !== undefined
30
- ? { transitionDuration: String(transition.duration) }
31
- : {}),
32
- ...(transition.direction ? { transitionDirection: transition.direction } : {}),
33
- },
34
- blockMeta: { transition },
35
- },
36
- },
37
- {
38
- type: 'paragraph',
39
- children: [{ type: 'text', value: 'Body' }],
40
- },
41
- ],
42
- };
43
- }
44
-
45
- function docWithAllTransitions(): MarkdownDocument {
46
- const children: MarkdownBlockNode[] = [
47
- {
48
- type: 'heading',
49
- depth: 2,
50
- children: [{ type: 'text', value: 'Transition Gallery' }],
51
- },
52
- {
53
- type: 'paragraph',
54
- children: [{ type: 'text', value: 'Intro slide before transition checks.' }],
55
- },
56
- ];
57
-
58
- for (const type of TRANSITION_TYPES) {
59
- const transition: Transition = { type, duration: 0.8 };
60
- children.push(
61
- {
62
- type: 'heading',
63
- depth: 2,
64
- children: [{ type: 'text', value: type }],
65
- attributes: {
66
- params: { transition: type, transitionDuration: '0.8' },
67
- blockMeta: { transition },
68
- },
69
- },
70
- {
71
- type: 'paragraph',
72
- children: [{ type: 'text', value: `Transition token: ${type}` }],
73
- },
74
- );
75
- }
76
-
77
- return { type: 'document', children };
78
- }
79
-
80
- describe('markdownDocToPptx transitions', () => {
81
- it('writes valid transition XML for every shared transition token', async () => {
82
- const pkg = await openPackage(await markdownDocToPptx(docWithAllTransitions()));
83
-
84
- for (let i = 0; i < TRANSITION_TYPES.length; i++) {
85
- const type = TRANSITION_TYPES[i];
86
- const slide = await getPartXml(pkg, `ppt/slides/slide${i + 2}.xml`);
87
- expect(slide, type).not.toBeNull();
88
-
89
- const transitions = slide!.getElementsByTagNameNS(NS_PML, 'transition');
90
- if (type === 'cut') {
91
- expect(transitions, type).toHaveLength(0);
92
- continue;
93
- }
94
-
95
- expect(transitions, type).toHaveLength(1);
96
- expect(transitions[0].getAttribute('spd'), type).toBe('med');
97
-
98
- const childElements = Array.from(transitions[0].childNodes).filter(
99
- (node): node is Element => node.nodeType === 1,
100
- );
101
- expect(childElements, type).toHaveLength(1);
102
- expect([NS_PML, NS_PML_2010], type).toContain(childElements[0].namespaceURI);
103
-
104
- if (childElements[0].namespaceURI === NS_PML_2010) {
105
- expect(slide!.documentElement.getAttribute('mc:Ignorable'), type).toContain('p14');
106
- }
107
- }
108
- });
109
-
110
- it('writes standard PresentationML transition XML', async () => {
111
- const md = docWithSecondSlideTransition({
112
- type: 'checkerboard',
113
- duration: 1.3,
114
- direction: 'vertical',
115
- });
116
-
117
- const pkg = await openPackage(await markdownDocToPptx(md));
118
- const slide = await getPartXml(pkg, 'ppt/slides/slide2.xml');
119
- const transition = slide!.getElementsByTagNameNS(NS_PML, 'transition')[0];
120
- const checker = transition.getElementsByTagNameNS(NS_PML, 'checker')[0];
121
-
122
- expect(transition.getAttribute('spd')).toBe('slow');
123
- expect(checker.getAttribute('dir')).toBe('vert');
124
- });
125
-
126
- it('writes PowerPoint 2010 transition XML when needed', async () => {
127
- const md = docWithSecondSlideTransition({ type: 'flash', duration: 0.4 });
128
-
129
- const pkg = await openPackage(await markdownDocToPptx(md));
130
- const slide = await getPartXml(pkg, 'ppt/slides/slide2.xml');
131
- const transition = slide!.getElementsByTagNameNS(NS_PML, 'transition')[0];
132
- const flash = transition.getElementsByTagNameNS(NS_PML_2010, 'flash')[0];
133
-
134
- expect(transition.getAttribute('spd')).toBe('fast');
135
- expect(flash).toBeDefined();
136
- expect(slide!.documentElement.getAttribute('mc:Ignorable')).toBe('p14');
137
- });
138
- });
@@ -1,145 +0,0 @@
1
- /**
2
- * Tests for PPTX import: pptxToMarkdownDoc. Builds a minimal .pptx fixture with
3
- * the shared OOXML writer, then imports it.
4
- */
5
-
6
- import { describe, expect, it } from 'vitest';
7
- import type {
8
- MarkdownDocument,
9
- MarkdownHeading,
10
- MarkdownList,
11
- MarkdownParagraph,
12
- MarkdownText,
13
- } from '@bendyline/squisq/markdown';
14
- import { readCustomThemesFromFrontmatter } from '@bendyline/squisq/doc';
15
- import { NS_DRAWINGML, NS_PML, NS_R, REL_OFFICE_DOCUMENT, REL_SLIDE } from '../ooxml/namespaces';
16
- import { createPackage } from '../ooxml/writer';
17
- import { xmlDeclaration } from '../ooxml/xmlUtils';
18
- import { markdownDocToPptx } from '../pptx/export';
19
- import { pptxToContainer, pptxToMarkdownDoc } from '../pptx/import';
20
-
21
- /** Distinctive PNG-signature-prefixed payload so round-trip fidelity is checkable. */
22
- const IMAGE_BYTES = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3, 4, 5]);
23
-
24
- async function buildTestPptx(): Promise<ArrayBuffer> {
25
- const pkg = createPackage();
26
-
27
- pkg.addPart(
28
- 'ppt/presentation.xml',
29
- `${xmlDeclaration()}<p:presentation xmlns:p="${NS_PML}" xmlns:r="${NS_R}">` +
30
- `<p:sldIdLst><p:sldId id="256" r:id="rId1"/></p:sldIdLst></p:presentation>`,
31
- 'application/xml',
32
- );
33
- pkg.addPart(
34
- 'ppt/slides/slide1.xml',
35
- `${xmlDeclaration()}<p:sld xmlns:p="${NS_PML}" xmlns:a="${NS_DRAWINGML}"><p:cSld><p:spTree>` +
36
- `<p:sp><p:nvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr>` +
37
- `<p:txBody><a:p><a:r><a:t>My Title</a:t></a:r></a:p></p:txBody></p:sp>` +
38
- `<p:sp><p:txBody>` +
39
- `<a:p><a:r><a:t>First bullet</a:t></a:r></a:p>` +
40
- `<a:p><a:r><a:t>Second bullet</a:t></a:r></a:p>` +
41
- `</p:txBody></p:sp>` +
42
- `</p:spTree></p:cSld></p:sld>`,
43
- 'application/xml',
44
- );
45
-
46
- pkg.addRelationship('', {
47
- id: 'rId1',
48
- type: REL_OFFICE_DOCUMENT,
49
- target: 'ppt/presentation.xml',
50
- });
51
- pkg.addRelationship('ppt/presentation.xml', {
52
- id: 'rId1',
53
- type: REL_SLIDE,
54
- target: 'slides/slide1.xml',
55
- });
56
-
57
- return pkg.toArrayBuffer();
58
- }
59
-
60
- describe('pptxToMarkdownDoc', () => {
61
- it('imports a slide as a heading + bullet list', async () => {
62
- const doc = await pptxToMarkdownDoc(await buildTestPptx());
63
- expect(doc.type).toBe('document');
64
-
65
- const heading = doc.children[0] as MarkdownHeading;
66
- expect(heading.type).toBe('heading');
67
- expect(heading.depth).toBe(2);
68
- expect((heading.children[0] as MarkdownText).value).toBe('My Title');
69
-
70
- const list = doc.children[1] as MarkdownList;
71
- expect(list.type).toBe('list');
72
- expect(list.children).toHaveLength(2);
73
- const firstItemPara = list.children[0]!.children[0] as MarkdownParagraph;
74
- expect((firstItemPara.children[0] as MarkdownText).value).toBe('First bullet');
75
- });
76
- });
77
-
78
- describe('pptxToContainer (embedded image import)', () => {
79
- const deckWithImage: MarkdownDocument = {
80
- type: 'document',
81
- children: [
82
- { type: 'heading', depth: 2, children: [{ type: 'text', value: 'Picture Slide' }] },
83
- { type: 'paragraph', children: [{ type: 'image', url: 'photo.png', alt: 'A photo' }] },
84
- ],
85
- };
86
-
87
- async function buildDeck(): Promise<ArrayBuffer> {
88
- return markdownDocToPptx(deckWithImage, {
89
- images: new Map([['photo.png', IMAGE_BYTES.buffer]]),
90
- });
91
- }
92
-
93
- it('extracts embedded images into the container and references them', async () => {
94
- const container = await pptxToContainer(await buildDeck());
95
-
96
- // The extracted image exists at the docx-style images/imageN.ext path.
97
- expect(await container.exists('images/image1.png')).toBe(true);
98
-
99
- // The bytes survived the round-trip verbatim.
100
- const bytes = await container.readFile('images/image1.png');
101
- expect(bytes).not.toBeNull();
102
- expect(new Uint8Array(bytes!)).toEqual(IMAGE_BYTES);
103
-
104
- // The markdown references the extracted image (no dangling ref).
105
- const markdown = await container.readDocument();
106
- expect(markdown).toContain('images/image1.png');
107
- });
108
-
109
- it('does not emit dangling image refs without extractImages', async () => {
110
- const doc = await pptxToMarkdownDoc(await buildDeck());
111
- const hasImage = doc.children.some(
112
- (b) => b.type === 'paragraph' && b.children.some((c) => c.type === 'image'),
113
- );
114
- expect(hasImage).toBe(false);
115
- });
116
- });
117
-
118
- describe('theme inference defaults (ON)', () => {
119
- const simpleDoc: MarkdownDocument = {
120
- type: 'document',
121
- children: [{ type: 'heading', depth: 2, children: [{ type: 'text', value: 'Hello' }] }],
122
- };
123
-
124
- it('carries the deck theme in frontmatter on an export → import round trip', async () => {
125
- const deck = await markdownDocToPptx(simpleDoc, {});
126
- const imported = await pptxToMarkdownDoc(deck);
127
- // The exporter writes the stock Office theme part; import infers it.
128
- expect(imported.frontmatter?.['squisq-theme']).toBe('custom-office-theme');
129
- const themes = readCustomThemesFromFrontmatter(imported.frontmatter);
130
- expect(themes).toHaveLength(1);
131
- expect(themes![0]!.seedColors?.primary).toBe('#4472c4');
132
- expect(themes![0]!.seedColors?.background).toBe('#ffffff');
133
- });
134
-
135
- it('produces the legacy frontmatter-free import when inference is disabled', async () => {
136
- const deck = await markdownDocToPptx(simpleDoc, {});
137
- const imported = await pptxToMarkdownDoc(deck, { inferTheme: false, inferLayouts: false });
138
- expect(imported.frontmatter).toBeUndefined();
139
- });
140
-
141
- it('imports decks without theme parts cleanly (no frontmatter, no failure)', async () => {
142
- const doc = await pptxToMarkdownDoc(await buildTestPptx());
143
- expect(doc.frontmatter).toBeUndefined();
144
- });
145
- });
@@ -1,314 +0,0 @@
1
- /**
2
- * Programmatic OOXML fixtures for theme/layout inference tests: themed PPTX
3
- * decks (theme + master + layouts + slides, fully wired relationships) plus
4
- * minimal themed DOCX/XLSX packages. Built with the package's own writer —
5
- * no binary fixtures checked in.
6
- */
7
-
8
- import {
9
- CONTENT_TYPE_DOCX_DOCUMENT,
10
- CONTENT_TYPE_PPTX_PRESENTATION,
11
- CONTENT_TYPE_PPTX_SLIDE,
12
- CONTENT_TYPE_PPTX_SLIDE_LAYOUT,
13
- CONTENT_TYPE_PPTX_SLIDE_MASTER,
14
- CONTENT_TYPE_PPTX_THEME,
15
- CONTENT_TYPE_XLSX_WORKBOOK,
16
- NS_DRAWINGML,
17
- NS_PML,
18
- NS_R,
19
- NS_WML,
20
- REL_OFFICE_DOCUMENT,
21
- REL_SLIDE,
22
- REL_SLIDE_LAYOUT,
23
- REL_SLIDE_MASTER,
24
- REL_THEME,
25
- } from '../ooxml/namespaces';
26
- import { createPackage } from '../ooxml/writer';
27
- import { xmlDeclaration } from '../ooxml/xmlUtils';
28
-
29
- export const SLDSZ = { cx: 12192000, cy: 6858000 };
30
-
31
- // ── Theme XML ────────────────────────────────────────────────────────
32
-
33
- export interface ThemeXmlOptions {
34
- name?: string;
35
- dk1?: string; // inner color XML, e.g. '<a:srgbClr val="111111"/>'
36
- lt1?: string;
37
- dk2?: string;
38
- lt2?: string;
39
- accents?: string[]; // hex values without '#', accent1..N (missing slots omitted)
40
- hlink?: string;
41
- majorFont?: string;
42
- minorFont?: string;
43
- omitFontScheme?: boolean;
44
- }
45
-
46
- export function buildThemeXml(opts: ThemeXmlOptions = {}): string {
47
- const accents = opts.accents ?? ['4472c4', 'ed7d31', 'a5a5a5', 'ffc000', '5b9bd5', '70ad47'];
48
- const accentXml = accents
49
- .map((hex, i) => `<a:accent${i + 1}><a:srgbClr val="${hex}"/></a:accent${i + 1}>`)
50
- .join('');
51
- const fontScheme = opts.omitFontScheme
52
- ? ''
53
- : `<a:fontScheme name="Test">` +
54
- `<a:majorFont><a:latin typeface="${opts.majorFont ?? 'Playfair Display'}"/></a:majorFont>` +
55
- `<a:minorFont><a:latin typeface="${opts.minorFont ?? 'Aptos'}"/></a:minorFont>` +
56
- `</a:fontScheme>`;
57
- return (
58
- xmlDeclaration() +
59
- `<a:theme xmlns:a="${NS_DRAWINGML}" name="${opts.name ?? 'Fixture Theme'}">` +
60
- `<a:themeElements>` +
61
- `<a:clrScheme name="Test">` +
62
- `<a:dk1>${opts.dk1 ?? '<a:srgbClr val="1a1a2e"/>'}</a:dk1>` +
63
- `<a:lt1>${opts.lt1 ?? '<a:srgbClr val="fdfdf8"/>'}</a:lt1>` +
64
- `<a:dk2>${opts.dk2 ?? '<a:srgbClr val="30304a"/>'}</a:dk2>` +
65
- `<a:lt2>${opts.lt2 ?? '<a:srgbClr val="efefe4"/>'}</a:lt2>` +
66
- accentXml +
67
- `<a:hlink>${opts.hlink ?? '<a:srgbClr val="0563c1"/>'}</a:hlink>` +
68
- `<a:folHlink><a:srgbClr val="954f72"/></a:folHlink>` +
69
- `</a:clrScheme>` +
70
- fontScheme +
71
- `</a:themeElements>` +
72
- `</a:theme>`
73
- );
74
- }
75
-
76
- // ── PresentationML fragments ─────────────────────────────────────────
77
-
78
- export interface PhSpOptions {
79
- /** ph@type; omit for a plain body placeholder. */
80
- type?: string;
81
- idx?: number;
82
- /** EMU geometry; omit xfrm entirely when absent. */
83
- rect?: { x: number; y: number; cx: number; cy: number };
84
- /** a:lvl1pPr defRPr attributes, e.g. 'sz="4400" b="1"'. */
85
- lvl1DefRPr?: string;
86
- /** ph@orient value (e.g. 'vert'). */
87
- orient?: string;
88
- }
89
-
90
- /** A placeholder `p:sp` for a layout/master/slide part. */
91
- export function phSp(opts: PhSpOptions, id = 2): string {
92
- const typeAttr = opts.type ? ` type="${opts.type}"` : '';
93
- const idxAttr = opts.idx !== undefined ? ` idx="${opts.idx}"` : '';
94
- const orientAttr = opts.orient ? ` orient="${opts.orient}"` : '';
95
- const xfrm = opts.rect
96
- ? `<a:xfrm><a:off x="${opts.rect.x}" y="${opts.rect.y}"/>` +
97
- `<a:ext cx="${opts.rect.cx}" cy="${opts.rect.cy}"/></a:xfrm>`
98
- : '';
99
- const lstStyle = opts.lvl1DefRPr
100
- ? `<a:lstStyle><a:lvl1pPr><a:defRPr ${opts.lvl1DefRPr}/></a:lvl1pPr></a:lstStyle>`
101
- : `<a:lstStyle/>`;
102
- return (
103
- `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="ph${id}"/><p:cNvSpPr/>` +
104
- `<p:nvPr><p:ph${typeAttr}${idxAttr}${orientAttr}/></p:nvPr></p:nvSpPr>` +
105
- `<p:spPr>${xfrm}</p:spPr>` +
106
- `<p:txBody><a:bodyPr/>${lstStyle}<a:p><a:endParaRPr/></a:p></p:txBody></p:sp>`
107
- );
108
- }
109
-
110
- export interface LayoutXmlOptions {
111
- name?: string;
112
- type?: string;
113
- /** Raw p:sp strings (use phSp / decorSp). */
114
- shapes?: string[];
115
- }
116
-
117
- export function layoutXml(opts: LayoutXmlOptions = {}): string {
118
- const typeAttr = opts.type ? ` type="${opts.type}"` : '';
119
- return (
120
- xmlDeclaration() +
121
- `<p:sldLayout xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"${typeAttr}>` +
122
- `<p:cSld${opts.name !== undefined ? ` name="${opts.name}"` : ''}><p:spTree>` +
123
- `<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>` +
124
- (opts.shapes ?? []).join('') +
125
- `</p:spTree></p:cSld></p:sldLayout>`
126
- );
127
- }
128
-
129
- export interface SlideTextShape {
130
- phType?: string;
131
- phIdx?: number;
132
- texts: string[];
133
- }
134
-
135
- /** A slide with a title shape plus arbitrary placeholder text shapes. */
136
- export function slideXml(title: string | null, shapes: SlideTextShape[] = []): string {
137
- const titleSp = title
138
- ? `<p:sp><p:nvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr>` +
139
- `<p:txBody><a:p><a:r><a:t>${title}</a:t></a:r></a:p></p:txBody></p:sp>`
140
- : '';
141
- const bodySps = shapes
142
- .map((s) => {
143
- const typeAttr = s.phType ? ` type="${s.phType}"` : '';
144
- const idxAttr = s.phIdx !== undefined ? ` idx="${s.phIdx}"` : '';
145
- const paras = s.texts.map((t) => `<a:p><a:r><a:t>${t}</a:t></a:r></a:p>`).join('');
146
- return (
147
- `<p:sp><p:nvSpPr><p:nvPr><p:ph${typeAttr}${idxAttr}/></p:nvPr></p:nvSpPr>` +
148
- `<p:txBody>${paras}</p:txBody></p:sp>`
149
- );
150
- })
151
- .join('');
152
- return (
153
- xmlDeclaration() +
154
- `<p:sld xmlns:p="${NS_PML}" xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}"><p:cSld><p:spTree>` +
155
- titleSp +
156
- bodySps +
157
- `</p:spTree></p:cSld></p:sld>`
158
- );
159
- }
160
-
161
- // ── Whole-deck builder ───────────────────────────────────────────────
162
-
163
- export interface FixtureSlide {
164
- xml: string;
165
- /** Index into `layouts`; omit for a slide with no layout relationship. */
166
- layoutIndex?: number;
167
- /** Extra relationships for this slide (e.g. image embeds). */
168
- rels?: Array<{ id: string; type: string; target: string }>;
169
- /** Binary media parts referenced by `rels`, keyed by package path. */
170
- media?: Array<{ path: string; data: Uint8Array; contentType: string }>;
171
- }
172
-
173
- export interface ThemedPptxOptions {
174
- themeXml?: string | null; // null = omit the theme part entirely
175
- /** Attribute string for <p:clrMap …/>; default light mapping. */
176
- clrMapAttrs?: string;
177
- /** Raw p:sp strings placed in the master's spTree (for inheritance tests). */
178
- masterShapes?: string[];
179
- layouts?: string[]; // layout part XMLs (layoutXml())
180
- slides?: FixtureSlide[];
181
- sldSz?: { cx: number; cy: number };
182
- }
183
-
184
- export async function buildThemedPptx(opts: ThemedPptxOptions = {}): Promise<ArrayBuffer> {
185
- const pkg = createPackage();
186
- const slides = opts.slides ?? [{ xml: slideXml('My Title', [{ texts: ['First bullet'] }]) }];
187
- const layouts = opts.layouts ?? [];
188
- const sldSz = opts.sldSz ?? SLDSZ;
189
-
190
- const sldIds = slides.map((_, i) => `<p:sldId id="${256 + i}" r:id="rIdS${i + 1}"/>`).join('');
191
- pkg.addPart(
192
- 'ppt/presentation.xml',
193
- `${xmlDeclaration()}<p:presentation xmlns:p="${NS_PML}" xmlns:r="${NS_R}">` +
194
- `<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rIdM1"/></p:sldMasterIdLst>` +
195
- `<p:sldIdLst>${sldIds}</p:sldIdLst>` +
196
- `<p:sldSz cx="${sldSz.cx}" cy="${sldSz.cy}"/>` +
197
- `</p:presentation>`,
198
- CONTENT_TYPE_PPTX_PRESENTATION,
199
- );
200
- pkg.addRelationship('', {
201
- id: 'rId1',
202
- type: REL_OFFICE_DOCUMENT,
203
- target: 'ppt/presentation.xml',
204
- });
205
- pkg.addRelationship('ppt/presentation.xml', {
206
- id: 'rIdM1',
207
- type: REL_SLIDE_MASTER,
208
- target: 'slideMasters/slideMaster1.xml',
209
- });
210
-
211
- // Master (carries the clrMap + optional inheritable placeholder geometry).
212
- const layoutIds = layouts
213
- .map((_, i) => `<p:sldLayoutId id="${2147483649 + i}" r:id="rIdL${i + 1}"/>`)
214
- .join('');
215
- pkg.addPart(
216
- 'ppt/slideMasters/slideMaster1.xml',
217
- `${xmlDeclaration()}<p:sldMaster xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}">` +
218
- `<p:cSld name="Fixture Master"><p:spTree>` +
219
- `<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>` +
220
- (opts.masterShapes ?? []).join('') +
221
- `</p:spTree></p:cSld>` +
222
- `<p:clrMap ${opts.clrMapAttrs ?? 'bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2"'}` +
223
- ` accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4"` +
224
- ` accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>` +
225
- `<p:sldLayoutIdLst>${layoutIds}</p:sldLayoutIdLst>` +
226
- `</p:sldMaster>`,
227
- CONTENT_TYPE_PPTX_SLIDE_MASTER,
228
- );
229
-
230
- if (opts.themeXml !== null) {
231
- pkg.addPart('ppt/theme/theme1.xml', opts.themeXml ?? buildThemeXml(), CONTENT_TYPE_PPTX_THEME);
232
- pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {
233
- id: 'rIdT1',
234
- type: REL_THEME,
235
- target: '../theme/theme1.xml',
236
- });
237
- }
238
-
239
- layouts.forEach((xml, i) => {
240
- const path = `ppt/slideLayouts/slideLayout${i + 1}.xml`;
241
- pkg.addPart(path, xml, CONTENT_TYPE_PPTX_SLIDE_LAYOUT);
242
- pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {
243
- id: `rIdL${i + 1}`,
244
- type: REL_SLIDE_LAYOUT,
245
- target: `../slideLayouts/slideLayout${i + 1}.xml`,
246
- });
247
- pkg.addRelationship(path, {
248
- id: 'rIdM1',
249
- type: REL_SLIDE_MASTER,
250
- target: '../slideMasters/slideMaster1.xml',
251
- });
252
- });
253
-
254
- slides.forEach((slide, i) => {
255
- const path = `ppt/slides/slide${i + 1}.xml`;
256
- pkg.addPart(path, slide.xml, CONTENT_TYPE_PPTX_SLIDE);
257
- pkg.addRelationship('ppt/presentation.xml', {
258
- id: `rIdS${i + 1}`,
259
- type: REL_SLIDE,
260
- target: `slides/slide${i + 1}.xml`,
261
- });
262
- if (slide.layoutIndex !== undefined) {
263
- pkg.addRelationship(path, {
264
- id: 'rIdLo1',
265
- type: REL_SLIDE_LAYOUT,
266
- target: `../slideLayouts/slideLayout${slide.layoutIndex + 1}.xml`,
267
- });
268
- }
269
- for (const rel of slide.rels ?? []) pkg.addRelationship(path, rel);
270
- for (const media of slide.media ?? []) {
271
- pkg.addBinaryPart(media.path, media.data, media.contentType);
272
- }
273
- });
274
-
275
- return pkg.toArrayBuffer();
276
- }
277
-
278
- // ── DOCX / XLSX minimal themed packages ──────────────────────────────
279
-
280
- export async function buildThemedDocx(themeXml?: string): Promise<ArrayBuffer> {
281
- const pkg = createPackage();
282
- pkg.addPart(
283
- 'word/document.xml',
284
- `${xmlDeclaration()}<w:document xmlns:w="${NS_WML}"><w:body>` +
285
- `<w:p><w:r><w:t>Hello</w:t></w:r></w:p></w:body></w:document>`,
286
- CONTENT_TYPE_DOCX_DOCUMENT,
287
- );
288
- pkg.addPart('word/theme/theme1.xml', themeXml ?? buildThemeXml(), CONTENT_TYPE_PPTX_THEME);
289
- pkg.addRelationship('', { id: 'rId1', type: REL_OFFICE_DOCUMENT, target: 'word/document.xml' });
290
- pkg.addRelationship('word/document.xml', {
291
- id: 'rId2',
292
- type: REL_THEME,
293
- target: 'theme/theme1.xml',
294
- });
295
- return pkg.toArrayBuffer();
296
- }
297
-
298
- export async function buildThemedXlsx(themeXml?: string): Promise<ArrayBuffer> {
299
- const pkg = createPackage();
300
- pkg.addPart(
301
- 'xl/workbook.xml',
302
- `${xmlDeclaration()}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">` +
303
- `<sheets/></workbook>`,
304
- CONTENT_TYPE_XLSX_WORKBOOK,
305
- );
306
- pkg.addPart('xl/theme/theme1.xml', themeXml ?? buildThemeXml(), CONTENT_TYPE_PPTX_THEME);
307
- pkg.addRelationship('', { id: 'rId1', type: REL_OFFICE_DOCUMENT, target: 'xl/workbook.xml' });
308
- pkg.addRelationship('xl/workbook.xml', {
309
- id: 'rId2',
310
- type: REL_THEME,
311
- target: 'theme/theme1.xml',
312
- });
313
- return pkg.toArrayBuffer();
314
- }