@bendyline/squisq-formats 1.3.0 → 1.4.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 (224) hide show
  1. package/dist/chunk-6M7Z25LA.js +46 -0
  2. package/dist/chunk-6M7Z25LA.js.map +1 -0
  3. package/dist/{chunk-S3Y7H2BK.js → chunk-APSIAEXL.js} +9 -8
  4. package/dist/chunk-APSIAEXL.js.map +1 -0
  5. package/dist/{chunk-UDS45KUJ.js → chunk-BJQIJ4S4.js} +36 -52
  6. package/dist/chunk-BJQIJ4S4.js.map +1 -0
  7. package/dist/{chunk-33YRFXZZ.js → chunk-CRTC6DEX.js} +259 -13
  8. package/dist/chunk-CRTC6DEX.js.map +1 -0
  9. package/dist/chunk-FMOIGA5T.js +201 -0
  10. package/dist/chunk-FMOIGA5T.js.map +1 -0
  11. package/dist/{chunk-VN2KEOYB.js → chunk-I2GFTEOV.js} +321 -91
  12. package/dist/chunk-I2GFTEOV.js.map +1 -0
  13. package/dist/chunk-JBNVE2GV.js +116 -0
  14. package/dist/chunk-JBNVE2GV.js.map +1 -0
  15. package/dist/chunk-L53YIGYS.js +141 -0
  16. package/dist/chunk-L53YIGYS.js.map +1 -0
  17. package/dist/{chunk-ERZ627GR.js → chunk-ON232HJR.js} +11 -26
  18. package/dist/chunk-ON232HJR.js.map +1 -0
  19. package/dist/chunk-PN52A5AA.js +32 -0
  20. package/dist/chunk-PN52A5AA.js.map +1 -0
  21. package/dist/{chunk-7DEUGIOO.js → chunk-QSE7EWE7.js} +12 -87
  22. package/dist/chunk-QSE7EWE7.js.map +1 -0
  23. package/dist/container/index.d.ts +7 -5
  24. package/dist/container/index.js +39 -70
  25. package/dist/container/index.js.map +1 -1
  26. package/dist/csv/index.d.ts +37 -0
  27. package/dist/csv/index.js +13 -0
  28. package/dist/csv/index.js.map +1 -0
  29. package/dist/docx/index.d.ts +128 -15
  30. package/dist/docx/index.js +19 -22
  31. package/dist/docx/index.js.map +1 -1
  32. package/dist/epub/index.d.ts +66 -11
  33. package/dist/epub/index.js +11 -18
  34. package/dist/epub/index.js.map +1 -1
  35. package/dist/html/index.d.ts +315 -15
  36. package/dist/html/index.js +32 -165
  37. package/dist/html/index.js.map +1 -1
  38. package/dist/index.d.ts +10 -38
  39. package/dist/index.js +80 -37
  40. package/dist/index.js.map +1 -1
  41. package/dist/ooxml/index.d.ts +353 -14
  42. package/dist/ooxml/index.js +116 -22
  43. package/dist/ooxml/index.js.map +1 -1
  44. package/dist/pdf/index.d.ts +143 -17
  45. package/dist/pdf/index.js +16 -25
  46. package/dist/pdf/index.js.map +1 -1
  47. package/dist/pptx/index.d.ts +98 -21
  48. package/dist/pptx/index.js +18 -34
  49. package/dist/pptx/index.js.map +1 -1
  50. package/dist/xlsx/index.d.ts +29 -25
  51. package/dist/xlsx/index.js +13 -44
  52. package/dist/xlsx/index.js.map +1 -1
  53. package/package.json +7 -2
  54. package/src/__tests__/csvImport.test.ts +51 -0
  55. package/src/__tests__/exportThemeReconciliation.test.ts +87 -0
  56. package/src/__tests__/htmlImport.test.ts +57 -0
  57. package/src/__tests__/plainHtml.test.ts +31 -0
  58. package/src/__tests__/pptxExport.test.ts +138 -0
  59. package/src/__tests__/pptxImport.test.ts +70 -0
  60. package/src/__tests__/roundTripMatrix.fixtures.ts +86 -0
  61. package/src/__tests__/roundTripMatrix.helpers.ts +154 -0
  62. package/src/__tests__/roundTripMatrix.test.ts +136 -0
  63. package/src/__tests__/xlsxImport.test.ts +80 -0
  64. package/src/csv/index.ts +165 -0
  65. package/src/docx/export.ts +27 -44
  66. package/src/docx/import.ts +1 -2
  67. package/src/epub/export.ts +10 -27
  68. package/src/html/import.ts +297 -0
  69. package/src/html/index.ts +4 -0
  70. package/src/html/plainHtml.ts +66 -16
  71. package/src/index.ts +10 -3
  72. package/src/ooxml/namespaces.ts +3 -0
  73. package/src/pdf/export.ts +10 -5
  74. package/src/pdf/import.ts +1 -1
  75. package/src/pptx/export.ts +225 -85
  76. package/src/pptx/import.ts +156 -0
  77. package/src/pptx/index.ts +10 -28
  78. package/src/shared/inlineRuns.ts +99 -0
  79. package/src/shared/text.ts +41 -0
  80. package/src/xlsx/import.ts +159 -0
  81. package/src/xlsx/index.ts +11 -28
  82. package/dist/__tests__/container.test.d.ts +0 -8
  83. package/dist/__tests__/container.test.d.ts.map +0 -1
  84. package/dist/__tests__/container.test.js +0 -147
  85. package/dist/__tests__/container.test.js.map +0 -1
  86. package/dist/__tests__/docxExport.test.d.ts +0 -8
  87. package/dist/__tests__/docxExport.test.d.ts.map +0 -1
  88. package/dist/__tests__/docxExport.test.js +0 -383
  89. package/dist/__tests__/docxExport.test.js.map +0 -1
  90. package/dist/__tests__/docxImport.test.d.ts +0 -8
  91. package/dist/__tests__/docxImport.test.d.ts.map +0 -1
  92. package/dist/__tests__/docxImport.test.js +0 -309
  93. package/dist/__tests__/docxImport.test.js.map +0 -1
  94. package/dist/__tests__/epub.test.d.ts +0 -8
  95. package/dist/__tests__/epub.test.d.ts.map +0 -1
  96. package/dist/__tests__/epub.test.js +0 -472
  97. package/dist/__tests__/epub.test.js.map +0 -1
  98. package/dist/__tests__/html.test.d.ts +0 -8
  99. package/dist/__tests__/html.test.d.ts.map +0 -1
  100. package/dist/__tests__/html.test.js +0 -317
  101. package/dist/__tests__/html.test.js.map +0 -1
  102. package/dist/__tests__/ooxml.test.d.ts +0 -5
  103. package/dist/__tests__/ooxml.test.d.ts.map +0 -1
  104. package/dist/__tests__/ooxml.test.js +0 -152
  105. package/dist/__tests__/ooxml.test.js.map +0 -1
  106. package/dist/__tests__/pdfExport.test.d.ts +0 -8
  107. package/dist/__tests__/pdfExport.test.d.ts.map +0 -1
  108. package/dist/__tests__/pdfExport.test.js +0 -275
  109. package/dist/__tests__/pdfExport.test.js.map +0 -1
  110. package/dist/__tests__/pdfImport.test.d.ts +0 -11
  111. package/dist/__tests__/pdfImport.test.d.ts.map +0 -1
  112. package/dist/__tests__/pdfImport.test.js +0 -254
  113. package/dist/__tests__/pdfImport.test.js.map +0 -1
  114. package/dist/__tests__/plainHtml.test.d.ts +0 -5
  115. package/dist/__tests__/plainHtml.test.d.ts.map +0 -1
  116. package/dist/__tests__/plainHtml.test.js +0 -327
  117. package/dist/__tests__/plainHtml.test.js.map +0 -1
  118. package/dist/__tests__/plainHtmlBundle.test.d.ts +0 -11
  119. package/dist/__tests__/plainHtmlBundle.test.d.ts.map +0 -1
  120. package/dist/__tests__/plainHtmlBundle.test.js +0 -210
  121. package/dist/__tests__/plainHtmlBundle.test.js.map +0 -1
  122. package/dist/__tests__/roundTrip.test.d.ts +0 -7
  123. package/dist/__tests__/roundTrip.test.d.ts.map +0 -1
  124. package/dist/__tests__/roundTrip.test.js +0 -173
  125. package/dist/__tests__/roundTrip.test.js.map +0 -1
  126. package/dist/chunk-33YRFXZZ.js.map +0 -1
  127. package/dist/chunk-67KIJHV2.js +0 -21
  128. package/dist/chunk-67KIJHV2.js.map +0 -1
  129. package/dist/chunk-7DEUGIOO.js.map +0 -1
  130. package/dist/chunk-ERZ627GR.js.map +0 -1
  131. package/dist/chunk-S3Y7H2BK.js.map +0 -1
  132. package/dist/chunk-UDS45KUJ.js.map +0 -1
  133. package/dist/chunk-VN2KEOYB.js.map +0 -1
  134. package/dist/chunk-YN5HFCEW.js +0 -120
  135. package/dist/chunk-YN5HFCEW.js.map +0 -1
  136. package/dist/container/index.d.ts.map +0 -1
  137. package/dist/docx/export.d.ts +0 -69
  138. package/dist/docx/export.d.ts.map +0 -1
  139. package/dist/docx/export.js +0 -985
  140. package/dist/docx/export.js.map +0 -1
  141. package/dist/docx/import.d.ts +0 -65
  142. package/dist/docx/import.d.ts.map +0 -1
  143. package/dist/docx/import.js +0 -779
  144. package/dist/docx/import.js.map +0 -1
  145. package/dist/docx/index.d.ts.map +0 -1
  146. package/dist/docx/styles.d.ts +0 -68
  147. package/dist/docx/styles.d.ts.map +0 -1
  148. package/dist/docx/styles.js +0 -125
  149. package/dist/docx/styles.js.map +0 -1
  150. package/dist/epub/export.d.ts +0 -72
  151. package/dist/epub/export.d.ts.map +0 -1
  152. package/dist/epub/export.js +0 -698
  153. package/dist/epub/export.js.map +0 -1
  154. package/dist/epub/index.d.ts.map +0 -1
  155. package/dist/html/docsHtmlBundle.d.ts +0 -53
  156. package/dist/html/docsHtmlBundle.d.ts.map +0 -1
  157. package/dist/html/docsHtmlBundle.js +0 -299
  158. package/dist/html/docsHtmlBundle.js.map +0 -1
  159. package/dist/html/htmlTemplate.d.ts +0 -70
  160. package/dist/html/htmlTemplate.d.ts.map +0 -1
  161. package/dist/html/htmlTemplate.js +0 -282
  162. package/dist/html/htmlTemplate.js.map +0 -1
  163. package/dist/html/imageUtils.d.ts +0 -29
  164. package/dist/html/imageUtils.d.ts.map +0 -1
  165. package/dist/html/imageUtils.js +0 -63
  166. package/dist/html/imageUtils.js.map +0 -1
  167. package/dist/html/index.d.ts.map +0 -1
  168. package/dist/html/plainHtml.d.ts +0 -79
  169. package/dist/html/plainHtml.d.ts.map +0 -1
  170. package/dist/html/plainHtml.js +0 -614
  171. package/dist/html/plainHtml.js.map +0 -1
  172. package/dist/html/plainHtmlBundle.d.ts +0 -93
  173. package/dist/html/plainHtmlBundle.d.ts.map +0 -1
  174. package/dist/html/plainHtmlBundle.js +0 -357
  175. package/dist/html/plainHtmlBundle.js.map +0 -1
  176. package/dist/index.d.ts.map +0 -1
  177. package/dist/ooxml/index.d.ts.map +0 -1
  178. package/dist/ooxml/namespaces.d.ts +0 -77
  179. package/dist/ooxml/namespaces.d.ts.map +0 -1
  180. package/dist/ooxml/namespaces.js +0 -104
  181. package/dist/ooxml/namespaces.js.map +0 -1
  182. package/dist/ooxml/reader.d.ts +0 -52
  183. package/dist/ooxml/reader.d.ts.map +0 -1
  184. package/dist/ooxml/reader.js +0 -190
  185. package/dist/ooxml/reader.js.map +0 -1
  186. package/dist/ooxml/types.d.ts +0 -82
  187. package/dist/ooxml/types.d.ts.map +0 -1
  188. package/dist/ooxml/types.js +0 -9
  189. package/dist/ooxml/types.js.map +0 -1
  190. package/dist/ooxml/writer.d.ts +0 -71
  191. package/dist/ooxml/writer.d.ts.map +0 -1
  192. package/dist/ooxml/writer.js +0 -200
  193. package/dist/ooxml/writer.js.map +0 -1
  194. package/dist/ooxml/xmlUtils.d.ts +0 -76
  195. package/dist/ooxml/xmlUtils.d.ts.map +0 -1
  196. package/dist/ooxml/xmlUtils.js +0 -108
  197. package/dist/ooxml/xmlUtils.js.map +0 -1
  198. package/dist/pdf/export.d.ts +0 -56
  199. package/dist/pdf/export.d.ts.map +0 -1
  200. package/dist/pdf/export.js +0 -768
  201. package/dist/pdf/export.js.map +0 -1
  202. package/dist/pdf/import.d.ts +0 -94
  203. package/dist/pdf/import.d.ts.map +0 -1
  204. package/dist/pdf/import.js +0 -796
  205. package/dist/pdf/import.js.map +0 -1
  206. package/dist/pdf/index.d.ts.map +0 -1
  207. package/dist/pdf/styles.d.ts +0 -142
  208. package/dist/pdf/styles.d.ts.map +0 -1
  209. package/dist/pdf/styles.js +0 -145
  210. package/dist/pdf/styles.js.map +0 -1
  211. package/dist/pptx/export.d.ts +0 -75
  212. package/dist/pptx/export.d.ts.map +0 -1
  213. package/dist/pptx/export.js +0 -724
  214. package/dist/pptx/export.js.map +0 -1
  215. package/dist/pptx/index.d.ts.map +0 -1
  216. package/dist/pptx/styles.d.ts +0 -54
  217. package/dist/pptx/styles.d.ts.map +0 -1
  218. package/dist/pptx/styles.js +0 -73
  219. package/dist/pptx/styles.js.map +0 -1
  220. package/dist/pptx/templates.d.ts +0 -24
  221. package/dist/pptx/templates.d.ts.map +0 -1
  222. package/dist/pptx/templates.js +0 -158
  223. package/dist/pptx/templates.js.map +0 -1
  224. package/dist/xlsx/index.d.ts.map +0 -1
@@ -27,10 +27,10 @@
27
27
  * ```
28
28
  */
29
29
 
30
- import type { Doc } from '@bendyline/squisq/schemas';
31
- import { resolveTheme, resolveFontFamily } from '@bendyline/squisq/schemas';
30
+ import type { Doc, Transition, TransitionDirection } from '@bendyline/squisq/schemas';
31
+ import { resolveFontFamily } from '@bendyline/squisq/schemas';
32
32
  import type { Theme } from '@bendyline/squisq/schemas';
33
- import { docToMarkdown } from '@bendyline/squisq/doc';
33
+ import { docToMarkdown, resolveThemeForDoc } from '@bendyline/squisq/doc';
34
34
  import type {
35
35
  MarkdownDocument,
36
36
  MarkdownBlockNode,
@@ -52,9 +52,18 @@ import { readFrontmatterThemeId } from '@bendyline/squisq/markdown';
52
52
 
53
53
  import { createPackage } from '../ooxml/writer.js';
54
54
  import { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';
55
+ import { inferMimeType } from '../html/imageUtils.js';
56
+ import { stripHtmlTags, extractPlainText } from '../shared/text.js';
57
+ import {
58
+ inlineNodesToRuns,
59
+ inlineNodeToRuns,
60
+ type InlineRunHandlers,
61
+ } from '../shared/inlineRuns.js';
55
62
  import {
56
63
  NS_PML,
64
+ NS_PML_2010,
57
65
  NS_DRAWINGML,
66
+ NS_MC,
58
67
  NS_R,
59
68
  REL_OFFICE_DOCUMENT,
60
69
  REL_SLIDE,
@@ -143,7 +152,7 @@ export async function markdownDocToPptx(
143
152
  // shorter legacy `themeId` / `theme` aliases — see
144
153
  // `readFrontmatterThemeId` for the precedence.
145
154
  const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);
146
- const style = resolveSlideStyle(themeId, options);
155
+ const style = resolveSlideStyle(themeId, options, doc);
147
156
 
148
157
  const slides = segmentIntoSlides(doc.children, options.slideBreak ?? 'h2');
149
158
 
@@ -196,7 +205,11 @@ interface SlideStyle {
196
205
  hasTheme: boolean;
197
206
  }
198
207
 
199
- function resolveSlideStyle(themeId: string | undefined, options: PptxExportOptions): SlideStyle {
208
+ function resolveSlideStyle(
209
+ themeId: string | undefined,
210
+ options: PptxExportOptions,
211
+ doc: MarkdownDocument,
212
+ ): SlideStyle {
200
213
  if (!themeId) {
201
214
  return {
202
215
  background: 'FFFFFF',
@@ -211,7 +224,7 @@ function resolveSlideStyle(themeId: string | undefined, options: PptxExportOptio
211
224
  };
212
225
  }
213
226
 
214
- const theme: Theme = resolveTheme(themeId);
227
+ const theme: Theme = resolveThemeForDoc(doc, themeId);
215
228
  const c = theme.colors;
216
229
 
217
230
  return {
@@ -238,6 +251,7 @@ function stripHash(color: string): string {
238
251
  interface SlideData {
239
252
  title?: string;
240
253
  titleDepth?: number;
254
+ transition?: Transition;
241
255
  bodyNodes: MarkdownBlockNode[];
242
256
  }
243
257
 
@@ -255,6 +269,7 @@ function segmentIntoSlides(
255
269
  current = {
256
270
  title: extractPlainText(node.children),
257
271
  titleDepth: node.depth,
272
+ transition: node.attributes?.blockMeta?.transition,
258
273
  bodyNodes: [],
259
274
  };
260
275
  } else {
@@ -269,37 +284,6 @@ function segmentIntoSlides(
269
284
  return slides;
270
285
  }
271
286
 
272
- function extractPlainText(nodes: MarkdownInlineNode[]): string {
273
- const parts: string[] = [];
274
- for (const node of nodes) {
275
- switch (node.type) {
276
- case 'text':
277
- parts.push(node.value);
278
- break;
279
- case 'inlineCode':
280
- parts.push(node.value);
281
- break;
282
- case 'strong':
283
- case 'emphasis':
284
- case 'delete':
285
- parts.push(extractPlainText(node.children));
286
- break;
287
- case 'link':
288
- parts.push(extractPlainText(node.children));
289
- break;
290
- case 'image':
291
- parts.push(node.alt ?? '');
292
- break;
293
- case 'inlineMath':
294
- parts.push(node.value);
295
- break;
296
- default:
297
- break;
298
- }
299
- }
300
- return parts.join('');
301
- }
302
-
303
287
  // ============================================
304
288
  // Slide Context
305
289
  // ============================================
@@ -450,20 +434,208 @@ function buildSlideXml(slide: SlideData, ctx: SlideContext): string {
450
434
  const bgXml = ctx.style.hasTheme
451
435
  ? `<p:bg><p:bgPr><a:solidFill><a:srgbClr val="${ctx.style.background}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>`
452
436
  : '';
437
+ const transitionXml = buildTransitionXml(slide.transition);
438
+ const extensionAttrs = transitionXml.includes('p14:')
439
+ ? ` xmlns:mc="${NS_MC}" xmlns:p14="${NS_PML_2010}" mc:Ignorable="p14"`
440
+ : '';
453
441
 
454
442
  return (
455
443
  xmlDeclaration() +
456
- `<p:sld xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}">` +
444
+ `<p:sld xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"${extensionAttrs}>` +
457
445
  `<p:cSld>` +
458
446
  bgXml +
459
447
  `<p:spTree>` +
460
448
  shapes.join('') +
461
449
  `</p:spTree>` +
462
450
  `</p:cSld>` +
451
+ transitionXml +
463
452
  `</p:sld>`
464
453
  );
465
454
  }
466
455
 
456
+ interface TransitionXml {
457
+ xml: string;
458
+ }
459
+
460
+ function buildTransitionXml(transition: Transition | undefined): string {
461
+ if (!transition || transition.type === 'cut') return '';
462
+
463
+ const child = buildTransitionChildXml(transition);
464
+ const spd = pptxSpeedForDuration(transition.duration);
465
+ return `<p:transition spd="${spd}">${child.xml}</p:transition>`;
466
+ }
467
+
468
+ function buildTransitionChildXml(transition: Transition): TransitionXml {
469
+ const type = transition.type;
470
+ switch (type) {
471
+ case 'fade':
472
+ case 'morph':
473
+ return p('fade');
474
+ case 'dissolve':
475
+ return p('dissolve');
476
+ case 'push':
477
+ return p('push', ` dir="${sideDir(transition.direction, 'left')}"`);
478
+ case 'slideLeft':
479
+ return p('push', ` dir="l"`);
480
+ case 'slideRight':
481
+ return p('push', ` dir="r"`);
482
+ case 'slideUp':
483
+ return p('push', ` dir="u"`);
484
+ case 'slideDown':
485
+ return p('push', ` dir="d"`);
486
+ case 'wipe':
487
+ return p('wipe', ` dir="${sideDir(transition.direction, 'left')}"`);
488
+ case 'split':
489
+ return p('split', ` orient="${orientation(transition.direction)}" dir="out"`);
490
+ case 'randomBars':
491
+ case 'randomBar':
492
+ return p('randomBar', ` dir="${orientation(transition.direction)}"`);
493
+ case 'shape':
494
+ case 'circle':
495
+ return p('circle');
496
+ case 'diamond':
497
+ return p('diamond');
498
+ case 'plus':
499
+ return p('plus');
500
+ case 'uncover':
501
+ case 'pull':
502
+ return p('pull', ` dir="${sideDir(transition.direction, 'left')}"`);
503
+ case 'cover':
504
+ return p('cover', ` dir="${sideDir(transition.direction, 'left')}"`);
505
+ case 'checkerboard':
506
+ case 'checker':
507
+ return p('checker', ` dir="${orientation(transition.direction)}"`);
508
+ case 'blinds':
509
+ return p('blinds', ` dir="${orientation(transition.direction)}"`);
510
+ case 'comb':
511
+ return p('comb', ` dir="${orientation(transition.direction)}"`);
512
+ case 'clock':
513
+ case 'wheel':
514
+ return p('wheel', ` spokes="4"`);
515
+ case 'wheelReverse':
516
+ return p14('wheelReverse');
517
+ case 'newsflash':
518
+ return p('newsflash');
519
+ case 'random':
520
+ return p('random');
521
+ case 'strips':
522
+ return p('strips', ` dir="${cornerDir(transition.direction)}"`);
523
+ case 'wedge':
524
+ return p('wedge');
525
+ case 'zoom':
526
+ case 'box':
527
+ return p('zoom', ` dir="${inOutDir(transition.direction, 'in')}"`);
528
+ case 'flash':
529
+ return p14('flash');
530
+ case 'vortex':
531
+ case 'orbit':
532
+ case 'rotate':
533
+ return p14('vortex');
534
+ case 'switch':
535
+ case 'cube':
536
+ return p14('switch');
537
+ case 'flip':
538
+ case 'fallOver':
539
+ return p14('flip');
540
+ case 'ripple':
541
+ return p14('ripple');
542
+ case 'glitter':
543
+ case 'prestige':
544
+ return p14('glitter');
545
+ case 'honeycomb':
546
+ return p14('honeycomb');
547
+ case 'prism':
548
+ return p14('prism');
549
+ case 'doors':
550
+ case 'drape':
551
+ case 'curtains':
552
+ return p14('doors');
553
+ case 'window':
554
+ return p14('window');
555
+ case 'shred':
556
+ case 'fracture':
557
+ case 'crush':
558
+ return p14('shred');
559
+ case 'ferris':
560
+ case 'ferrisWheel':
561
+ return p14('ferris');
562
+ case 'flythrough':
563
+ case 'flyThrough':
564
+ case 'airplane':
565
+ case 'origami':
566
+ return p14('flythrough');
567
+ case 'warp':
568
+ case 'pageCurl':
569
+ case 'pageCurlDouble':
570
+ case 'pageCurlSingle':
571
+ case 'peelOff':
572
+ return p14('warp');
573
+ case 'gallery':
574
+ return p14('gallery');
575
+ case 'conveyor':
576
+ return p14('conveyor');
577
+ case 'pan':
578
+ case 'wind':
579
+ return p14('pan');
580
+ case 'reveal':
581
+ return p14('reveal');
582
+ default:
583
+ return p('fade');
584
+ }
585
+ }
586
+
587
+ function p(tag: string, attrs: string = ''): TransitionXml {
588
+ return { xml: `<p:${tag}${attrs}/>` };
589
+ }
590
+
591
+ function p14(tag: string, attrs: string = ''): TransitionXml {
592
+ return { xml: `<p14:${tag}${attrs}/>` };
593
+ }
594
+
595
+ function pptxSpeedForDuration(duration: number | undefined): 'fast' | 'med' | 'slow' {
596
+ if (duration != null && duration <= 0.5) return 'fast';
597
+ if (duration != null && duration >= 1.2) return 'slow';
598
+ return 'med';
599
+ }
600
+
601
+ function sideDir(
602
+ direction: TransitionDirection | undefined,
603
+ fallback: TransitionDirection,
604
+ ): string {
605
+ switch (direction ?? fallback) {
606
+ case 'right':
607
+ return 'r';
608
+ case 'up':
609
+ return 'u';
610
+ case 'down':
611
+ return 'd';
612
+ case 'left':
613
+ default:
614
+ return 'l';
615
+ }
616
+ }
617
+
618
+ function cornerDir(direction: TransitionDirection | undefined): string {
619
+ switch (direction) {
620
+ case 'right':
621
+ case 'down':
622
+ return 'rd';
623
+ case 'up':
624
+ return 'lu';
625
+ case 'left':
626
+ default:
627
+ return 'ld';
628
+ }
629
+ }
630
+
631
+ function orientation(direction: TransitionDirection | undefined): string {
632
+ return direction === 'vertical' || direction === 'up' || direction === 'down' ? 'vert' : 'horz';
633
+ }
634
+
635
+ function inOutDir(direction: TransitionDirection | undefined, fallback: 'in' | 'out'): string {
636
+ return direction === 'out' ? 'out' : direction === 'in' ? 'in' : fallback;
637
+ }
638
+
467
639
  function buildTitleShape(title: string, style: SlideStyle): string {
468
640
  return (
469
641
  `<p:sp>` +
@@ -768,43 +940,29 @@ interface InlineFormat {
768
940
  code?: boolean;
769
941
  }
770
942
 
943
+ /**
944
+ * PPTX leaf handlers for the shared run-based inline walker. The traversal
945
+ * (format threading) lives in `shared/inlineRuns.ts`; these emit DrawingML.
946
+ */
947
+ function pptxRunHandlers(ctx: SlideContext): InlineRunHandlers {
948
+ return {
949
+ run: (text, format) => makeRun(text, format, ctx.style),
950
+ link: (node, format) => convertLink(node, ctx, format),
951
+ image: (node, format) => convertImage(node, format, ctx),
952
+ lineBreak: () => `<a:br/>`,
953
+ };
954
+ }
955
+
771
956
  function convertInlines(
772
957
  nodes: MarkdownInlineNode[],
773
958
  ctx: SlideContext,
774
959
  format: InlineFormat = {},
775
960
  ): string {
776
- const parts: string[] = [];
777
- for (const node of nodes) {
778
- parts.push(convertInline(node, ctx, format));
779
- }
780
- return parts.join('');
961
+ return inlineNodesToRuns(nodes, pptxRunHandlers(ctx), format);
781
962
  }
782
963
 
783
964
  function convertInline(node: MarkdownInlineNode, ctx: SlideContext, format: InlineFormat): string {
784
- switch (node.type) {
785
- case 'text':
786
- return makeRun(node.value, format, ctx.style);
787
- case 'strong':
788
- return convertInlines(node.children, ctx, { ...format, bold: true });
789
- case 'emphasis':
790
- return convertInlines(node.children, ctx, { ...format, italic: true });
791
- case 'delete':
792
- return convertInlines(node.children, ctx, { ...format, strike: true });
793
- case 'inlineCode':
794
- return makeRun(node.value, { ...format, code: true }, ctx.style);
795
- case 'link':
796
- return convertLink(node, ctx, format);
797
- case 'image':
798
- return convertImage(node, format, ctx);
799
- case 'break':
800
- return `<a:br/>`;
801
- case 'htmlInline':
802
- return makeRun(stripHtmlTags(node.rawHtml), format, ctx.style);
803
- case 'inlineMath':
804
- return makeRun(node.value, { ...format, code: true }, ctx.style);
805
- default:
806
- return '';
807
- }
965
+ return inlineNodeToRuns(node, pptxRunHandlers(ctx), format);
808
966
  }
809
967
 
810
968
  function makeRun(text: string, format: InlineFormat, style: SlideStyle): string {
@@ -1013,10 +1171,6 @@ async function buildPptxPackage(
1013
1171
  // Helpers
1014
1172
  // ============================================
1015
1173
 
1016
- function stripHtmlTags(html: string): string {
1017
- return html.replace(/<[^>]*>/g, '');
1018
- }
1019
-
1020
1174
  function inferExtension(path: string): string {
1021
1175
  const dot = path.lastIndexOf('.');
1022
1176
  if (dot === -1) return 'png';
@@ -1026,17 +1180,3 @@ function inferExtension(path: string): string {
1026
1180
  .split('?')[0];
1027
1181
  return ext || 'png';
1028
1182
  }
1029
-
1030
- function inferMimeType(ext: string): string {
1031
- const mimeTypes: Record<string, string> = {
1032
- jpg: 'image/jpeg',
1033
- jpeg: 'image/jpeg',
1034
- png: 'image/png',
1035
- gif: 'image/gif',
1036
- webp: 'image/webp',
1037
- svg: 'image/svg+xml',
1038
- bmp: 'image/bmp',
1039
- avif: 'image/avif',
1040
- };
1041
- return mimeTypes[ext] || 'image/png';
1042
- }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * PPTX import — PresentationML (.pptx) → MarkdownDocument.
3
+ *
4
+ * Reuses the shared ooxml/ reader. Reads slide order from
5
+ * `ppt/presentation.xml` (`<p:sldIdLst>`), resolves each slide part via
6
+ * relationships, and converts each slide to: an H2 of the title placeholder
7
+ * (or "Slide N"), the remaining text as a bullet list, and any slide tables
8
+ * (`<a:tbl>`) as markdown tables. Text lives in the DrawingML namespace
9
+ * (`a:p` / `a:r` / `a:t`) inside PresentationML shapes (`p:sp`).
10
+ */
11
+
12
+ import type {
13
+ MarkdownBlockNode,
14
+ MarkdownDocument,
15
+ MarkdownListItem,
16
+ MarkdownTable,
17
+ MarkdownTableCell,
18
+ MarkdownTableRow,
19
+ } from '@bendyline/squisq/markdown';
20
+ import { getPartRelationships, getPartXml, openPackage } from '../ooxml/reader.js';
21
+ import type { OoxmlPackage } from '../ooxml/types.js';
22
+ import { NS_DRAWINGML, NS_PML, NS_R } from '../ooxml/namespaces.js';
23
+
24
+ export interface PptxImportOptions {
25
+ /** Whether to extract embedded images as data URIs (not yet used). */
26
+ extractImages?: boolean;
27
+ }
28
+
29
+ function attrNS(el: Element, ns: string, local: string, fallback: string): string | null {
30
+ return el.getAttributeNS(ns, local) ?? el.getAttribute(fallback);
31
+ }
32
+
33
+ function resolveTarget(baseDir: string, target: string): string {
34
+ if (target.startsWith('/')) return target.replace(/^\//, '');
35
+ const stack = baseDir ? baseDir.split('/') : [];
36
+ for (const seg of target.split('/')) {
37
+ if (seg === '..') stack.pop();
38
+ else if (seg !== '.') stack.push(seg);
39
+ }
40
+ return stack.join('/');
41
+ }
42
+
43
+ async function orderedSlidePaths(pkg: OoxmlPackage): Promise<string[]> {
44
+ const pres = await getPartXml(pkg, 'ppt/presentation.xml');
45
+ if (!pres) return [];
46
+ const rels = await getPartRelationships(pkg, 'ppt/presentation.xml');
47
+ const relById = new Map(rels.map((r) => [r.id, r.target]));
48
+ const out: string[] = [];
49
+ const ids = pres.getElementsByTagNameNS(NS_PML, 'sldId');
50
+ for (let i = 0; i < ids.length; i++) {
51
+ const rid = attrNS(ids[i]!, NS_R, 'id', 'r:id');
52
+ const target = rid ? relById.get(rid) : undefined;
53
+ if (target) out.push(resolveTarget('ppt', target));
54
+ }
55
+ return out;
56
+ }
57
+
58
+ /** Concatenate the DrawingML text runs (`a:t`) inside a paragraph element. */
59
+ function paragraphText(para: Element): string {
60
+ const ts = para.getElementsByTagNameNS(NS_DRAWINGML, 't');
61
+ let s = '';
62
+ for (let i = 0; i < ts.length; i++) s += ts[i]!.textContent ?? '';
63
+ return s.trim();
64
+ }
65
+
66
+ function isTitleShape(sp: Element): boolean {
67
+ const ph = sp.getElementsByTagNameNS(NS_PML, 'ph');
68
+ if (!ph.length) return false;
69
+ const type = ph[0]!.getAttribute('type');
70
+ return type === 'title' || type === 'ctrTitle';
71
+ }
72
+
73
+ function tableToMarkdown(tbl: Element): MarkdownTable {
74
+ const rows: MarkdownTableRow[] = [];
75
+ const trs = tbl.getElementsByTagNameNS(NS_DRAWINGML, 'tr');
76
+ for (let r = 0; r < trs.length; r++) {
77
+ const tcs = trs[r]!.getElementsByTagNameNS(NS_DRAWINGML, 'tc');
78
+ const cells: MarkdownTableCell[] = [];
79
+ for (let c = 0; c < tcs.length; c++) {
80
+ const paras = tcs[c]!.getElementsByTagNameNS(NS_DRAWINGML, 'p');
81
+ const text = Array.from({ length: paras.length }, (_, i) => paragraphText(paras[i]!))
82
+ .filter(Boolean)
83
+ .join(' ');
84
+ cells.push({
85
+ type: 'tableCell',
86
+ ...(r === 0 ? { isHeader: true } : {}),
87
+ children: text ? [{ type: 'text', value: text }] : [],
88
+ });
89
+ }
90
+ rows.push({ type: 'tableRow', children: cells });
91
+ }
92
+ return { type: 'table', children: rows };
93
+ }
94
+
95
+ async function convertSlide(
96
+ pkg: OoxmlPackage,
97
+ path: string,
98
+ index: number,
99
+ ): Promise<MarkdownBlockNode[]> {
100
+ const doc = await getPartXml(pkg, path);
101
+ if (!doc) return [];
102
+ const out: MarkdownBlockNode[] = [];
103
+
104
+ let title = '';
105
+ const bullets: string[] = [];
106
+ const shapes = doc.getElementsByTagNameNS(NS_PML, 'sp');
107
+ for (let s = 0; s < shapes.length; s++) {
108
+ const sp = shapes[s]!;
109
+ const txBody = sp.getElementsByTagNameNS(NS_PML, 'txBody');
110
+ if (!txBody.length) continue;
111
+ const paras = txBody[0]!.getElementsByTagNameNS(NS_DRAWINGML, 'p');
112
+ const texts: string[] = [];
113
+ for (let p = 0; p < paras.length; p++) {
114
+ const t = paragraphText(paras[p]!);
115
+ if (t) texts.push(t);
116
+ }
117
+ if (texts.length === 0) continue;
118
+ if (isTitleShape(sp) && !title) {
119
+ title = texts.join(' ');
120
+ } else {
121
+ bullets.push(...texts);
122
+ }
123
+ }
124
+
125
+ out.push({
126
+ type: 'heading',
127
+ depth: 2,
128
+ children: [{ type: 'text', value: title || `Slide ${index + 1}` }],
129
+ });
130
+
131
+ if (bullets.length > 0) {
132
+ const items: MarkdownListItem[] = bullets.map((text) => ({
133
+ type: 'listItem',
134
+ children: [{ type: 'paragraph', children: [{ type: 'text', value: text }] }],
135
+ }));
136
+ out.push({ type: 'list', ordered: false, children: items });
137
+ }
138
+
139
+ const tbls = doc.getElementsByTagNameNS(NS_DRAWINGML, 'tbl');
140
+ for (let t = 0; t < tbls.length; t++) out.push(tableToMarkdown(tbls[t]!));
141
+
142
+ return out;
143
+ }
144
+
145
+ export async function pptxToMarkdownDoc(
146
+ data: ArrayBuffer | Blob,
147
+ _options: PptxImportOptions = {},
148
+ ): Promise<MarkdownDocument> {
149
+ const pkg = await openPackage(data);
150
+ const paths = await orderedSlidePaths(pkg);
151
+ const children: MarkdownBlockNode[] = [];
152
+ for (let i = 0; i < paths.length; i++) {
153
+ children.push(...(await convertSlide(pkg, paths[i]!, i)));
154
+ }
155
+ return { type: 'document', children };
156
+ }
package/src/pptx/index.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * Slide segmentation: each H1/H2 heading starts a new slide by default.
8
8
  * Inline formatting (bold, italic, code, links) is preserved as DrawingML runs.
9
9
  *
10
- * Import is not yet implemented.
10
+ * Includes both export and import paths.
11
11
  *
12
12
  * @example
13
13
  * ```ts
@@ -19,38 +19,20 @@
19
19
  export { markdownDocToPptx, docToPptx } from './export.js';
20
20
  export type { PptxExportOptions } from './export.js';
21
21
 
22
- // Import (stubs — not yet implemented)
23
- import type { MarkdownDocument } from '@bendyline/squisq/markdown';
22
+ // Import
23
+ import { markdownToDoc } from '@bendyline/squisq/doc';
24
24
  import type { Doc } from '@bendyline/squisq/schemas';
25
+ import { type PptxImportOptions, pptxToMarkdownDoc } from './import.js';
25
26
 
26
- /**
27
- * Options for PPTX import (placeholder).
28
- */
29
- export interface PptxImportOptions {
30
- /** Whether to extract embedded images as data URIs */
31
- extractImages?: boolean;
32
- }
27
+ export type { PptxImportOptions } from './import.js';
28
+ export { pptxToMarkdownDoc } from './import.js';
33
29
 
34
30
  /**
35
- * Convert a .pptx file to a MarkdownDocument.
36
- *
37
- * @throws Error — PPTX import is not yet implemented
38
- */
39
- export async function pptxToMarkdownDoc(
40
- _data: ArrayBuffer | Blob,
41
- _options?: PptxImportOptions,
42
- ): Promise<MarkdownDocument> {
43
- throw new Error('PPTX import is not yet implemented');
44
- }
45
-
46
- /**
47
- * Convert a .pptx file to a squisq Doc.
48
- *
49
- * @throws Error — PPTX import is not yet implemented
31
+ * Convert a .pptx file to a squisq Doc (via the markdown model).
50
32
  */
51
33
  export async function pptxToDoc(
52
- _data: ArrayBuffer | Blob,
53
- _options?: PptxImportOptions,
34
+ data: ArrayBuffer | Blob,
35
+ options?: PptxImportOptions,
54
36
  ): Promise<Doc> {
55
- throw new Error('PPTX import is not yet implemented');
37
+ return markdownToDoc(await pptxToMarkdownDoc(data, options));
56
38
  }