@aivorynet/slaide 1.0.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 (145) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +201 -0
  3. package/README.md +87 -0
  4. package/bin/slaide.js +25 -0
  5. package/dist/assets.d.ts +10 -0
  6. package/dist/assets.js +54 -0
  7. package/dist/assets.js.map +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +433 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/compare/index.d.ts +24 -0
  12. package/dist/compare/index.js +246 -0
  13. package/dist/compare/index.js.map +1 -0
  14. package/dist/compare/ssim.d.ts +2 -0
  15. package/dist/compare/ssim.js +46 -0
  16. package/dist/compare/ssim.js.map +1 -0
  17. package/dist/compiler/chrome.d.ts +7 -0
  18. package/dist/compiler/chrome.js +94 -0
  19. package/dist/compiler/chrome.js.map +1 -0
  20. package/dist/compiler/compile.d.ts +2 -0
  21. package/dist/compiler/compile.js +432 -0
  22. package/dist/compiler/compile.js.map +1 -0
  23. package/dist/compiler/markdown.d.ts +26 -0
  24. package/dist/compiler/markdown.js +439 -0
  25. package/dist/compiler/markdown.js.map +1 -0
  26. package/dist/compiler/tokens.d.ts +13 -0
  27. package/dist/compiler/tokens.js +127 -0
  28. package/dist/compiler/tokens.js.map +1 -0
  29. package/dist/container.d.ts +47 -0
  30. package/dist/container.js +309 -0
  31. package/dist/container.js.map +1 -0
  32. package/dist/engine-entry.d.ts +1 -0
  33. package/dist/engine-entry.js +71 -0
  34. package/dist/engine-entry.js.map +1 -0
  35. package/dist/export-keynote/keynote.d.ts +10 -0
  36. package/dist/export-keynote/keynote.js +58 -0
  37. package/dist/export-keynote/keynote.js.map +1 -0
  38. package/dist/export-pptx/embed-fonts.d.ts +5 -0
  39. package/dist/export-pptx/embed-fonts.js +159 -0
  40. package/dist/export-pptx/embed-fonts.js.map +1 -0
  41. package/dist/export-pptx/inject-anim.d.ts +28 -0
  42. package/dist/export-pptx/inject-anim.js +150 -0
  43. package/dist/export-pptx/inject-anim.js.map +1 -0
  44. package/dist/export-pptx/pptx.d.ts +10 -0
  45. package/dist/export-pptx/pptx.js +425 -0
  46. package/dist/export-pptx/pptx.js.map +1 -0
  47. package/dist/export-pptx/transitions.d.ts +16 -0
  48. package/dist/export-pptx/transitions.js +82 -0
  49. package/dist/export-pptx/transitions.js.map +1 -0
  50. package/dist/import/emit.d.ts +5 -0
  51. package/dist/import/emit.js +274 -0
  52. package/dist/import/emit.js.map +1 -0
  53. package/dist/import/index.d.ts +21 -0
  54. package/dist/import/index.js +192 -0
  55. package/dist/import/index.js.map +1 -0
  56. package/dist/import/pptx.d.ts +117 -0
  57. package/dist/import/pptx.js +1043 -0
  58. package/dist/import/pptx.js.map +1 -0
  59. package/dist/import/raster-com.d.ts +27 -0
  60. package/dist/import/raster-com.js +113 -0
  61. package/dist/import/raster-com.js.map +1 -0
  62. package/dist/import/raster-extension.d.ts +35 -0
  63. package/dist/import/raster-extension.js +27 -0
  64. package/dist/import/raster-extension.js.map +1 -0
  65. package/dist/index.d.ts +63 -0
  66. package/dist/index.js +174 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/install/detect.d.ts +17 -0
  69. package/dist/install/detect.js +82 -0
  70. package/dist/install/detect.js.map +1 -0
  71. package/dist/install/index.d.ts +20 -0
  72. package/dist/install/index.js +126 -0
  73. package/dist/install/index.js.map +1 -0
  74. package/dist/install/registry.d.ts +33 -0
  75. package/dist/install/registry.js +169 -0
  76. package/dist/install/registry.js.map +1 -0
  77. package/dist/install/skills.d.ts +17 -0
  78. package/dist/install/skills.js +72 -0
  79. package/dist/install/skills.js.map +1 -0
  80. package/dist/master-io.d.ts +7 -0
  81. package/dist/master-io.js +55 -0
  82. package/dist/master-io.js.map +1 -0
  83. package/dist/mcp/server.d.ts +3 -0
  84. package/dist/mcp/server.js +114 -0
  85. package/dist/mcp/server.js.map +1 -0
  86. package/dist/optimize/export-optimize.d.ts +21 -0
  87. package/dist/optimize/export-optimize.js +167 -0
  88. package/dist/optimize/export-optimize.js.map +1 -0
  89. package/dist/parser/frontmatter.d.ts +8 -0
  90. package/dist/parser/frontmatter.js +45 -0
  91. package/dist/parser/frontmatter.js.map +1 -0
  92. package/dist/parser/parse.d.ts +2 -0
  93. package/dist/parser/parse.js +184 -0
  94. package/dist/parser/parse.js.map +1 -0
  95. package/dist/render/anim.d.ts +50 -0
  96. package/dist/render/anim.js +213 -0
  97. package/dist/render/anim.js.map +1 -0
  98. package/dist/render/charts.d.ts +3 -0
  99. package/dist/render/charts.js +171 -0
  100. package/dist/render/charts.js.map +1 -0
  101. package/dist/render/css.d.ts +3 -0
  102. package/dist/render/css.js +183 -0
  103. package/dist/render/css.js.map +1 -0
  104. package/dist/render/echarts-lib.generated.d.ts +1 -0
  105. package/dist/render/echarts-lib.generated.js +7 -0
  106. package/dist/render/echarts-lib.generated.js.map +1 -0
  107. package/dist/render/html.d.ts +5 -0
  108. package/dist/render/html.js +186 -0
  109. package/dist/render/html.js.map +1 -0
  110. package/dist/render/inject.d.ts +12 -0
  111. package/dist/render/inject.js +19 -0
  112. package/dist/render/inject.js.map +1 -0
  113. package/dist/render/mermaid-lib.generated.d.ts +1 -0
  114. package/dist/render/mermaid-lib.generated.js +7 -0
  115. package/dist/render/mermaid-lib.generated.js.map +1 -0
  116. package/dist/render/runtime.d.ts +1 -0
  117. package/dist/render/runtime.js +393 -0
  118. package/dist/render/runtime.js.map +1 -0
  119. package/dist/render-pdf/pdf.d.ts +9 -0
  120. package/dist/render-pdf/pdf.js +53 -0
  121. package/dist/render-pdf/pdf.js.map +1 -0
  122. package/dist/render-png/montage.d.ts +14 -0
  123. package/dist/render-png/montage.js +75 -0
  124. package/dist/render-png/montage.js.map +1 -0
  125. package/dist/render-png/shoot.d.ts +25 -0
  126. package/dist/render-png/shoot.js +79 -0
  127. package/dist/render-png/shoot.js.map +1 -0
  128. package/dist/scaffold.d.ts +9 -0
  129. package/dist/scaffold.js +36 -0
  130. package/dist/scaffold.js.map +1 -0
  131. package/dist/types.d.ts +212 -0
  132. package/dist/types.js +7 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/util.d.ts +6 -0
  135. package/dist/util.js +28 -0
  136. package/dist/util.js.map +1 -0
  137. package/package.json +66 -0
  138. package/skills/slaide/SKILL.md +64 -0
  139. package/skills/slaide/agents/openai.yaml +12 -0
  140. package/skills/slaide/examples/branded-deck.slaide +198 -0
  141. package/skills/slaide/examples/branded-theme.slaide.yaml +187 -0
  142. package/skills/slaide/examples/sample.slaide +98 -0
  143. package/skills/slaide/reference.md +177 -0
  144. package/skills/slaide/themes.md +171 -0
  145. package/themes/aurora.slaide.yaml +136 -0
@@ -0,0 +1,425 @@
1
+ // Copyright 2026 AIVory, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // @slaide/export-pptx — slaide deck -> editable PowerPoint (.pptx).
4
+ //
5
+ // Strategy: render the deck's web HTML in headless Chromium, then MEASURE the laid-out
6
+ // result (each region's real box + per-run computed styles, images, and backgrounds) and
7
+ // rebuild it as native PPTX shapes via pptxgenjs. Measuring the rendered DOM means this
8
+ // works for ANY deck — grid layouts and absolute `anchor:` slots alike — and round-trips
9
+ // an imported deck back to PowerPoint with its geometry intact. The result is fully
10
+ // editable in PowerPoint (real text boxes, runs, pictures), not a flat raster.
11
+ import { renderDeckHtml } from '../index.js';
12
+ import { readFileSync, writeFileSync } from 'node:fs';
13
+ import { dirname, resolve } from 'node:path';
14
+ import { injectAnim } from './inject-anim.js';
15
+ // Runs in the page context (returned by page.evaluate). Kept as a stringified function so it
16
+ // ships to the browser; documents the DOM->structure extraction in one place.
17
+ //
18
+ // Text is measured at BLOCK granularity: one text box per leaf block (paragraph / heading /
19
+ // list / inline-only container), each at its own getBoundingClientRect. Copying the rendered
20
+ // position of every block reproduces card padding, per-paragraph gaps, vertical distribution
21
+ // and per-block alignment — a single per-region text box loses all of these (text flush to the
22
+ // card edges, paragraphs crammed under one uniform line spacing).
23
+ const EXTRACT = String.raw `
24
+ (function(){
25
+ function rgbToHex(s){
26
+ var m = (s||'').match(/[\d.]+/g);
27
+ if(!m) return null;
28
+ if(m.length>=4 && parseFloat(m[3])===0) return null; // fully transparent
29
+ var r=+m[0],g=+m[1],b=+m[2];
30
+ return ((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1).toUpperCase();
31
+ }
32
+ function firstGradientColor(bgImg){
33
+ if(!bgImg) return null;
34
+ var hex = bgImg.match(/#([0-9a-fA-F]{6})/);
35
+ if(hex) return hex[1].toUpperCase();
36
+ var rgb = bgImg.match(/rgba?\([^)]+\)/);
37
+ return rgb ? rgbToHex(rgb[0]) : null;
38
+ }
39
+ var curRegionFill = null; // a gradient-text region's stop colour (set while walking its runs)
40
+ function runColor(cs){
41
+ // gradient text fill renders the glyph colour transparent; recover the gradient's first
42
+ // stop so the run isn't exported as invisible/black. A slot-level gradient (fill: <grad>
43
+ // -> --region-fill, clipped to the text) lives on the REGION, not the run, so fall back to
44
+ // the region's gradient stop before defaulting to black.
45
+ var fill = cs.webkitTextFillColor || cs.color;
46
+ var hex = rgbToHex(fill);
47
+ if(hex) return hex;
48
+ return firstGradientColor(cs.backgroundImage) || curRegionFill || '000000';
49
+ }
50
+ var BLOCK = {p:1,li:1,div:1,ul:1,ol:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,blockquote:1,tr:1,table:1,section:1,figure:1,pre:1};
51
+ var TERMINAL = {p:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,blockquote:1,pre:1}; // always one text unit
52
+ var WHOLE = {ul:1,ol:1,table:1}; // kept whole so bullets / rows stay in one box
53
+ function skip(el){
54
+ var t = el.tagName.toLowerCase();
55
+ if(t==='img') return true;
56
+ return !!(el.classList && (el.classList.contains('sl-chart')||el.classList.contains('sl-svg')));
57
+ }
58
+ function alphaOf(s){ var m=(s||'').match(/[\d.]+/g); return (m && m.length>=4) ? parseFloat(m[3]) : 1; }
59
+ function extract(slideEl){
60
+ var sRect = slideEl.getBoundingClientRect();
61
+ function box(el){ var r = el.getBoundingClientRect(); return { x: r.left - sRect.left, y: r.top - sRect.top, w: r.width, h: r.height }; }
62
+ var capId = 0; // per-slide marker id; selectors are scoped to .sl-active so reuse is safe
63
+
64
+ // background: prefer the bg layer, else the slide's own background colour
65
+ var bg = null;
66
+ var bgl = slideEl.querySelector('.sl-layer-bg');
67
+ var probes = bgl ? [bgl, slideEl] : [slideEl];
68
+ for(var i=0;i<probes.length && !bg;i++){
69
+ var cs = getComputedStyle(probes[i]);
70
+ if(cs.backgroundImage && cs.backgroundImage.indexOf('url(')===0){
71
+ var m = cs.backgroundImage.match(/url\(["']?(.+?)["']?\)/);
72
+ if(m) bg = { type:'image', src: m[1] };
73
+ } else if(cs.backgroundImage && cs.backgroundImage.indexOf('gradient')>=0){
74
+ var gc = firstGradientColor(cs.backgroundImage);
75
+ if(gc) bg = { type:'color', color: gc };
76
+ } else {
77
+ var c = rgbToHex(cs.backgroundColor);
78
+ if(c) bg = { type:'color', color: c };
79
+ }
80
+ }
81
+
82
+ // walk a subtree into styled runs. curBuild = the data-build group inherited from the
83
+ // nearest ancestor (null = appears with the slide); it rides on each run so paragraph
84
+ // builds can be reconstructed.
85
+ function walk(node, curBuild, runs){
86
+ node.childNodes.forEach(function(ch){
87
+ if(ch.nodeType===3){
88
+ var t = ch.textContent;
89
+ if(!t) return;
90
+ if(!t.trim()){
91
+ // collapse inter-run whitespace to a single joining space, so two adjacent inline
92
+ // spans ("88 %" + "Beteiligung") don't concatenate into "88 %Beteiligung".
93
+ var prev = runs[runs.length-1];
94
+ if(prev && !prev.breakLine && !/\s$/.test(prev.text)) prev.text += ' ';
95
+ return;
96
+ }
97
+ var cs = getComputedStyle(ch.parentElement);
98
+ runs.push({
99
+ text: t.replace(/\s+/g,' '),
100
+ bold: parseInt(cs.fontWeight,10) >= 600 || undefined,
101
+ italic: cs.fontStyle==='italic' || undefined,
102
+ underline: (cs.textDecorationLine||'').indexOf('underline')>=0 || undefined,
103
+ color: runColor(cs),
104
+ sizePt: Math.round(parseFloat(cs.fontSize)*0.75*10)/10,
105
+ font: (cs.fontFamily||'').split(',')[0].replace(/["']/g,'').trim(),
106
+ b: curBuild
107
+ });
108
+ } else if(ch.nodeType===1){
109
+ var tag = ch.tagName.toLowerCase();
110
+ if(tag==='img') return;
111
+ // charts / inline svg are captured as images — never walk their internal <text>
112
+ if(ch.classList && (ch.classList.contains('sl-chart')||ch.classList.contains('sl-svg'))) return;
113
+ if(tag==='br'){ if(runs.length) runs[runs.length-1].breakLine=true; return; }
114
+ var bAttr = ch.getAttribute ? ch.getAttribute('data-build') : null;
115
+ var childBuild = (bAttr!=null) ? bAttr : curBuild;
116
+ var block = BLOCK[tag]===1;
117
+ if(block && runs.length && !runs[runs.length-1].breakLine) runs[runs.length-1].breakLine=true;
118
+ var first = runs.length;
119
+ walk(ch, childBuild, runs);
120
+ if(tag==='li' && runs[first]) runs[first].bullet=true;
121
+ if(block && runs.length && !runs[runs.length-1].breakLine) runs[runs.length-1].breakLine=true;
122
+ }
123
+ });
124
+ }
125
+ // measure one element as a text box (null if it holds no text)
126
+ function unitFromEl(el, curBuild){
127
+ var runs = [];
128
+ walk(el, curBuild, runs);
129
+ if(runs.length) runs[runs.length-1].breakLine = false; // no trailing empty paragraph
130
+ if(!runs.length) return null;
131
+ var cs = getComputedStyle(el);
132
+ var b = box(el);
133
+ var lh = parseFloat(cs.lineHeight), fs = parseFloat(cs.fontSize);
134
+ var ls = (isFinite(lh) && isFinite(fs) && fs>0) ? Math.round(lh/fs*100)/100 : undefined;
135
+ return { x:b.x, y:b.y, w:b.w, h:b.h, align: cs.textAlign, lineSpacing: ls, runs: runs };
136
+ }
137
+ function hasBlockChild(el){
138
+ for(var i=0;i<el.children.length;i++){
139
+ var c = el.children[i];
140
+ if(skip(c)) continue;
141
+ if(BLOCK[c.tagName.toLowerCase()]===1) return true;
142
+ }
143
+ return false;
144
+ }
145
+ // Descend through wrapper / flex / build containers to the leaf text blocks; lists and
146
+ // tables are kept whole. Each leaf is measured at its own box, so padding and per-paragraph
147
+ // gaps come for free from the real rendered layout.
148
+ function collectUnits(el, curBuild, out){
149
+ if(!hasBlockChild(el)){
150
+ var u = unitFromEl(el, curBuild);
151
+ if(u) out.push(u);
152
+ return;
153
+ }
154
+ for(var i=0;i<el.children.length;i++){
155
+ var ch = el.children[i];
156
+ if(skip(ch)) continue;
157
+ var tag = ch.tagName.toLowerCase();
158
+ var bAttr = ch.getAttribute ? ch.getAttribute('data-build') : null;
159
+ var childBuild = (bAttr!=null) ? bAttr : curBuild;
160
+ if(WHOLE[tag]===1 || TERMINAL[tag]===1){
161
+ var u = unitFromEl(ch, childBuild);
162
+ if(u) out.push(u);
163
+ } else if(hasBlockChild(ch)){
164
+ collectUnits(ch, childBuild, out);
165
+ } else {
166
+ var u2 = unitFromEl(ch, childBuild);
167
+ if(u2) out.push(u2);
168
+ }
169
+ }
170
+ }
171
+
172
+ var regions = [];
173
+ slideEl.querySelectorAll('.sl-region').forEach(function(region){
174
+ var rcs = getComputedStyle(region);
175
+ var rbox = box(region);
176
+ var shapes = [];
177
+ // a region with background-clip:text paints a gradient onto its TEXT, not a box.
178
+ var clipText = (rcs.webkitBackgroundClip==='text' || rcs.backgroundClip==='text');
179
+ var gradImg = (rcs.backgroundImage && rcs.backgroundImage.indexOf('gradient')>=0) ? firstGradientColor(rcs.backgroundImage) : null;
180
+ curRegionFill = clipText ? gradImg : null;
181
+ // region box fill / border / radius (cards, pills, colour panels, dark backgrounds)
182
+ var fill = rgbToHex(rcs.backgroundColor);
183
+ var fillAlpha = fill ? alphaOf(rcs.backgroundColor) : 1;
184
+ if(!fill && gradImg && !clipText){ fill = gradImg; fillAlpha = 1; }
185
+ var bw = parseFloat(rcs.borderTopWidth) || 0;
186
+ var borderColor = bw>0 ? rgbToHex(rcs.borderTopColor) : null;
187
+ var borderAlpha = borderColor ? alphaOf(rcs.borderTopColor) : 1;
188
+ var radius = parseFloat(rcs.borderTopLeftRadius) || 0;
189
+ if(fill || borderColor){
190
+ shapes.push({ x:rbox.x, y:rbox.y, w:rbox.w, h:rbox.h,
191
+ fill: fill || undefined, fillAlpha: fillAlpha,
192
+ borderColor: borderColor || undefined, borderAlpha: borderAlpha,
193
+ borderWidth: bw || undefined, radius: radius || undefined });
194
+ }
195
+ // free-layer placed boxes (.sl-shape) carry their own fill / border / radius
196
+ region.querySelectorAll('.sl-shape').forEach(function(sh){
197
+ var scs = getComputedStyle(sh);
198
+ var sclip = (scs.webkitBackgroundClip==='text' || scs.backgroundClip==='text');
199
+ var sgrad = (scs.backgroundImage && scs.backgroundImage.indexOf('gradient')>=0) ? firstGradientColor(scs.backgroundImage) : null;
200
+ var sfill = rgbToHex(scs.backgroundColor);
201
+ if(!sfill && sgrad && !sclip) sfill = sgrad;
202
+ var sbw = parseFloat(scs.borderTopWidth) || 0;
203
+ var sbc = sbw>0 ? rgbToHex(scs.borderTopColor) : null;
204
+ if(!sfill && !sbc) return;
205
+ var sb = box(sh);
206
+ shapes.push({ x:sb.x, y:sb.y, w:sb.w, h:sb.h,
207
+ fill: sfill || undefined, fillAlpha: sfill ? alphaOf(scs.backgroundColor) : 1,
208
+ borderColor: sbc || undefined, borderAlpha: sbc ? alphaOf(scs.borderTopColor) : 1,
209
+ borderWidth: sbw || undefined, radius: (parseFloat(scs.borderTopLeftRadius)||0) || undefined });
210
+ });
211
+ // images (image slots + any placed images)
212
+ var imgs = [];
213
+ region.querySelectorAll('img').forEach(function(im){
214
+ if(im.closest('.sl-chart, .sl-svg')) return;
215
+ var ib = box(im);
216
+ if(ib.w>1 && ib.h>1 && im.src) imgs.push({ x:ib.x, y:ib.y, w:ib.w, h:ib.h, src: im.src });
217
+ });
218
+ // charts (mermaid/echarts) + inline svg → tag each for an element screenshot in Node
219
+ var shots = [];
220
+ region.querySelectorAll('.sl-chart, .sl-svg').forEach(function(cel){
221
+ if(cel.parentElement && cel.parentElement.closest('.sl-chart, .sl-svg')) return; // skip nested
222
+ var cb = box(cel);
223
+ if(cb.w<2 || cb.h<2) return;
224
+ var cap = String(capId++);
225
+ cel.setAttribute('data-sl-cap', cap);
226
+ shots.push({ x:cb.x, y:cb.y, w:cb.w, h:cb.h, cap:cap });
227
+ });
228
+ // text, measured at block granularity
229
+ var textBoxes = [];
230
+ collectUnits(region, null, textBoxes);
231
+ curRegionFill = null;
232
+ if(shapes.length || textBoxes.length || imgs.length || shots.length){
233
+ regions.push({
234
+ shapes: shapes, textBoxes: textBoxes,
235
+ imgs: imgs.length ? imgs : undefined,
236
+ shots: shots.length ? shots : undefined
237
+ });
238
+ }
239
+ });
240
+ return { bg: bg, regions: regions };
241
+ }
242
+ return extract;
243
+ })()
244
+ `;
245
+ async function loadPlaywright() {
246
+ try {
247
+ return await import('playwright');
248
+ }
249
+ catch {
250
+ throw new Error('PPTX export needs Playwright. Install it:\n npm install playwright\n npx playwright install chromium');
251
+ }
252
+ }
253
+ function mapAlign(a) {
254
+ if (a === 'center')
255
+ return 'center';
256
+ if (a === 'right' || a === 'end')
257
+ return 'right';
258
+ if (a === 'justify')
259
+ return 'justify';
260
+ return 'left';
261
+ }
262
+ const PX_PER_IN = 96;
263
+ export async function exportPptx(deckPath, opts, injected) {
264
+ const source = readFileSync(deckPath, 'utf8');
265
+ const { html, ir } = renderDeckHtml(source, dirname(resolve(deckPath)), { mode: 'web', inline: true });
266
+ const W = ir.canvas.width;
267
+ const H = ir.canvas.height;
268
+ const { chromium } = await loadPlaywright();
269
+ const browser = injected ?? (await chromium.launch());
270
+ let slidesData;
271
+ try {
272
+ const page = await browser.newPage({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
273
+ // Pin the stage at 1:1 (no fit-scaling) so getBoundingClientRect is in canvas px.
274
+ const doc = html.replace('</head>', '<style>.sl-stage{transform:none !important;}.sl-counter,.sl-progress,.sl-notes,.sl-help{display:none !important;}</style></head>');
275
+ await page.setContent(doc, { waitUntil: 'networkidle' });
276
+ await page.evaluate(() => document.fonts?.ready);
277
+ // Render charts (lazy) and wait until they settle before measuring the DOM.
278
+ await page.evaluate(() => window.__slaideCharts?.renderAll());
279
+ await page
280
+ .waitForFunction(() => window.__slaideChartsReady === true, { timeout: 8000 })
281
+ .catch(() => { });
282
+ await page.waitForTimeout(300);
283
+ slidesData = [];
284
+ for (let i = 0; i < ir.slides.length; i++) {
285
+ await page.evaluate((n) => window.slaide.show(n), i);
286
+ await page.waitForTimeout(80);
287
+ const data = (await page.evaluate(([extractSrc]) => {
288
+ const extract = eval(extractSrc);
289
+ const slide = document.querySelector('.sl-slide.sl-active') || document.querySelectorAll('.sl-slide')[0];
290
+ return extract(slide);
291
+ }, [EXTRACT]));
292
+ // Screenshot each tagged chart/svg element (transparent PNG) on the active slide.
293
+ for (const region of data.regions) {
294
+ if (!region.shots)
295
+ continue;
296
+ for (const shot of region.shots) {
297
+ const handle = await page.$(`.sl-slide.sl-active [data-sl-cap="${shot.cap}"]`);
298
+ if (!handle)
299
+ continue;
300
+ const png = await handle.screenshot({ omitBackground: true });
301
+ shot.data = 'data:image/png;base64,' + Buffer.from(png).toString('base64');
302
+ }
303
+ }
304
+ data.transition = ir.slides[i].transition;
305
+ const tms = ir.slides[i].vars['--transition-ms'];
306
+ data.transitionMs = tms ? parseInt(tms, 10) : undefined;
307
+ slidesData.push(data);
308
+ }
309
+ }
310
+ finally {
311
+ if (!injected)
312
+ await browser.close();
313
+ }
314
+ let mod;
315
+ try {
316
+ mod = await import('pptxgenjs');
317
+ }
318
+ catch {
319
+ throw new Error('PPTX export needs pptxgenjs (an optional dependency). Install it:\n npm install pptxgenjs');
320
+ }
321
+ const PptxGen = mod.default ?? mod;
322
+ const pptx = new PptxGen();
323
+ pptx.defineLayout({ name: 'slaide', width: W / PX_PER_IN, height: H / PX_PER_IN });
324
+ pptx.layout = 'slaide';
325
+ if (ir.meta.title)
326
+ pptx.title = ir.meta.title;
327
+ if (ir.meta.author)
328
+ pptx.author = ir.meta.author;
329
+ const stripData = (s) => s.replace(/^data:/, '');
330
+ const inch = (px) => px / PX_PER_IN;
331
+ for (const data of slidesData) {
332
+ const slide = pptx.addSlide();
333
+ if (data.bg?.type === 'color')
334
+ slide.background = { color: data.bg.color };
335
+ else if (data.bg?.type === 'image' && data.bg.src.startsWith('data:'))
336
+ slide.background = { data: stripData(data.bg.src) };
337
+ // Regions are walked in DOM order = paint order, so shapes/images/text stack correctly.
338
+ const transp = (a) => (a !== undefined && a < 1 ? Math.round((1 - a) * 100) : undefined);
339
+ for (const r of data.regions) {
340
+ // 1. box fills / borders (region cards, pills, colour panels, free-layer boxes)
341
+ for (const s of r.shapes) {
342
+ const shapeOpts = {
343
+ x: inch(s.x), y: inch(s.y), w: inch(s.w), h: inch(s.h),
344
+ fill: s.fill ? { color: s.fill, transparency: transp(s.fillAlpha) } : { type: 'none' },
345
+ };
346
+ if (s.borderColor)
347
+ shapeOpts.line = { color: s.borderColor, transparency: transp(s.borderAlpha), width: Math.max(0.5, (s.borderWidth ?? 1) * 0.75) };
348
+ if (s.radius)
349
+ shapeOpts.rectRadius = inch(Math.min(s.radius, s.w / 2, s.h / 2));
350
+ slide.addShape(s.radius ? 'roundRect' : 'rect', shapeOpts);
351
+ }
352
+ // 2. images (image slots + placed images)
353
+ for (const im of r.imgs ?? []) {
354
+ if (im.src.startsWith('data:'))
355
+ slide.addImage({ data: stripData(im.src), x: inch(im.x), y: inch(im.y), w: inch(im.w), h: inch(im.h) });
356
+ }
357
+ // 2b. charts / inline svg captured as transparent PNGs
358
+ for (const s of r.shots ?? []) {
359
+ if (s.data)
360
+ slide.addImage({ data: stripData(s.data), x: inch(s.x), y: inch(s.y), w: inch(s.w), h: inch(s.h) });
361
+ }
362
+ // 3. text — one box per measured block, at its real position (preserves padding, gaps,
363
+ // per-block alignment). valign:'middle' anchors each block on its stable centre so a
364
+ // PowerPoint line box slightly taller than the HTML one grows symmetrically, not down.
365
+ for (const tb of r.textBoxes) {
366
+ const runs = tb.runs.map((run) => ({
367
+ text: run.text,
368
+ options: {
369
+ bold: run.bold,
370
+ italic: run.italic,
371
+ underline: run.underline ? { style: 'sng' } : undefined,
372
+ color: run.color,
373
+ fontSize: run.sizePt,
374
+ fontFace: run.font,
375
+ breakLine: run.breakLine,
376
+ bullet: run.bullet ? true : undefined,
377
+ },
378
+ }));
379
+ slide.addText(runs, {
380
+ x: inch(tb.x), y: inch(tb.y), w: inch(tb.w), h: inch(tb.h),
381
+ align: mapAlign(tb.align),
382
+ valign: 'middle',
383
+ margin: 0,
384
+ autoFit: false,
385
+ wrap: true,
386
+ lineSpacingMultiple: tb.lineSpacing && tb.lineSpacing > 0.5 && tb.lineSpacing < 3 ? tb.lineSpacing : undefined,
387
+ });
388
+ }
389
+ }
390
+ }
391
+ // pptxgenjs cannot emit transitions or build animations, so write to a buffer and splice the
392
+ // missing OOXML in with one zip pass (see inject-anim.ts). Each kind is opt-out via PptxOptions.
393
+ let buf = (await pptx.write({ outputType: 'nodebuffer' }));
394
+ const anims = slidesData.map((d) => ({
395
+ transition: opts.transitions === false ? undefined : { name: d.transition, durationMs: d.transitionMs ?? ir.transitions.duration },
396
+ // One build spec per text box, in add order (= document order of text-bearing <p:sp>),
397
+ // so inject-anim's textSpids() line up 1:1.
398
+ builds: opts.builds === false ? undefined : d.regions.flatMap((r) => r.textBoxes.map((tb) => ({ pBuilds: paragraphBuilds(tb.runs) }))),
399
+ }));
400
+ buf = await injectAnim(buf, anims, { safe: false });
401
+ // Embed the deck's web fonts (best-effort) so the file renders on machines that lack them.
402
+ if (opts.embedFonts !== false && ir.fontImports.length) {
403
+ const { embedFonts } = await import('./embed-fonts.js');
404
+ buf = await embedFonts(buf, ir.fontImports);
405
+ }
406
+ writeFileSync(opts.out, buf);
407
+ return opts.out;
408
+ }
409
+ // Split a region's runs into paragraphs (pptxgenjs starts a new paragraph after each run whose
410
+ // breakLine is set) and flag the ones that carry a build, so they can reveal on click.
411
+ function paragraphBuilds(runs) {
412
+ const flags = [];
413
+ let cur = false;
414
+ for (const r of runs) {
415
+ if (r.b != null)
416
+ cur = true;
417
+ if (r.breakLine) {
418
+ flags.push(cur);
419
+ cur = false;
420
+ }
421
+ }
422
+ flags.push(cur); // trailing paragraph (EXTRACT clears the last run's breakLine)
423
+ return flags;
424
+ }
425
+ //# sourceMappingURL=pptx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pptx.js","sourceRoot":"","sources":["../../src/export-pptx/pptx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface TransitionXmlOpts {
2
+ /** per-slide --transition-ms (else the deck default). Mapped to the spd bucket. */
3
+ durationMs?: number;
4
+ /** collapse approximated effects (zoom/flip/morph) to a plain fade for maximum portability. */
5
+ safe?: boolean;
6
+ }
7
+ /** Map a duration in ms onto PowerPoint's legacy transition-speed bucket. */
8
+ export declare function speedBucket(ms: number | undefined): 'slow' | 'med' | 'fast';
9
+ /** The inner transition element for a name (e.g. `<p:fade/>`); `null` for `none`. */
10
+ export declare function transitionChildXml(name: string, safe?: boolean): string | null;
11
+ /**
12
+ * Full slide-transition markup to inject after <p:clrMapOvr>. Returns '' for `none`
13
+ * (no transition element), the mc:AlternateContent block for `morph`, otherwise a plain
14
+ * `<p:transition spd="...">{child}</p:transition>`.
15
+ */
16
+ export declare function transitionXml(name: string, opts?: TransitionXmlOpts): string;
@@ -0,0 +1,82 @@
1
+ // Copyright 2026 AIVory, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Slaide slide-transition name -> OOXML <p:transition> XML.
4
+ //
5
+ // pptxgenjs has no transition API, so the .pptx is post-processed (see inject-anim.ts):
6
+ // the generated slide XML gets a <p:transition> child injected after <p:clrMapOvr>. This
7
+ // module is the pure, browserless mapping that turns a Slaide transition name (the same
8
+ // names authors use in `transition:` and that live in render/anim.ts) into the matching
9
+ // PowerPoint transition element.
10
+ //
11
+ // PowerPoint transition speed is the legacy three-bucket `spd` (slow|med|fast); we map the
12
+ // deck's transition duration onto it. We deliberately avoid the p14:dur millisecond
13
+ // extension to keep every slide schema-clean and free of repair prompts in PowerPoint.
14
+ /** Map a duration in ms onto PowerPoint's legacy transition-speed bucket. */
15
+ export function speedBucket(ms) {
16
+ if (!ms || ms <= 0)
17
+ return 'med';
18
+ if (ms <= 300)
19
+ return 'fast';
20
+ if (ms <= 600)
21
+ return 'med';
22
+ return 'slow';
23
+ }
24
+ // Slaide name -> the single CT_SlideTransition child element. `null` means "no transition"
25
+ // (the `none` keyword); unknown names fall back to a plain fade.
26
+ const CHILD = {
27
+ fade: '<p:fade/>',
28
+ dissolve: '<p:dissolve/>',
29
+ 'fade-through-black': '<p:fade thruBlk="1"/>',
30
+ 'fade-black': '<p:fade thruBlk="1"/>',
31
+ 'slide-left': '<p:push dir="l"/>',
32
+ slide: '<p:push dir="l"/>',
33
+ 'slide-right': '<p:push dir="r"/>',
34
+ 'slide-up': '<p:push dir="u"/>',
35
+ 'slide-down': '<p:push dir="d"/>',
36
+ push: '<p:push dir="l"/>',
37
+ cover: '<p:cover dir="l"/>',
38
+ reveal: '<p:pull dir="l"/>',
39
+ zoom: '<p:zoom dir="in"/>',
40
+ 'zoom-out': '<p:zoom dir="out"/>',
41
+ flip: '<p:fade/>', // OOXML has no legacy "flip" transition; fade is the closest portable match
42
+ };
43
+ // Effects with no faithful legacy equivalent; collapsed to fade when `safe`.
44
+ const APPROX = new Set(['zoom', 'zoom-out', 'flip', 'morph']);
45
+ /** The inner transition element for a name (e.g. `<p:fade/>`); `null` for `none`. */
46
+ export function transitionChildXml(name, safe = false) {
47
+ const n = (name || '').trim();
48
+ if (!n || n === 'none')
49
+ return null;
50
+ if (n === 'morph')
51
+ return safe ? '<p:fade/>' : null; // morph handled by transitionXml (needs mc:AlternateContent)
52
+ if (safe && APPROX.has(n))
53
+ return '<p:fade/>';
54
+ return CHILD[n] ?? '<p:fade/>';
55
+ }
56
+ const MC_NS = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
57
+ const P159_NS = 'http://schemas.microsoft.com/office/powerpoint/2015/09/main';
58
+ /** Real "morph" needs the p159 namespace inside mc:AlternateContent, with a fade fallback. */
59
+ function morphXml(spd) {
60
+ return (`<mc:AlternateContent xmlns:mc="${MC_NS}">` +
61
+ `<mc:Choice xmlns:p159="${P159_NS}" Requires="p159">` +
62
+ `<p:transition spd="${spd}"><p159:morph option="byObject"/></p:transition>` +
63
+ `</mc:Choice>` +
64
+ `<mc:Fallback><p:transition spd="${spd}"><p:fade/></p:transition></mc:Fallback>` +
65
+ `</mc:AlternateContent>`);
66
+ }
67
+ /**
68
+ * Full slide-transition markup to inject after <p:clrMapOvr>. Returns '' for `none`
69
+ * (no transition element), the mc:AlternateContent block for `morph`, otherwise a plain
70
+ * `<p:transition spd="...">{child}</p:transition>`.
71
+ */
72
+ export function transitionXml(name, opts = {}) {
73
+ const n = (name || '').trim();
74
+ if (!n || n === 'none')
75
+ return '';
76
+ const spd = speedBucket(opts.durationMs);
77
+ if (n === 'morph' && !opts.safe)
78
+ return morphXml(spd);
79
+ const child = transitionChildXml(n, opts.safe) ?? '<p:fade/>';
80
+ return `<p:transition spd="${spd}">${child}</p:transition>`;
81
+ }
82
+ //# sourceMappingURL=transitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transitions.js","sourceRoot":"","sources":["../../src/export-pptx/transitions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { ImportIR } from './pptx.js';
2
+ export declare function emit(ir: ImportIR): {
3
+ master: string;
4
+ deck: string;
5
+ };