@json-to-office/core-pptx 0.1.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 (44) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +9 -0
  3. package/dist/components/chart.d.ts +63 -0
  4. package/dist/components/chart.d.ts.map +1 -0
  5. package/dist/components/highcharts.d.ts +8 -0
  6. package/dist/components/highcharts.d.ts.map +1 -0
  7. package/dist/components/image.d.ts +37 -0
  8. package/dist/components/image.d.ts.map +1 -0
  9. package/dist/components/index.d.ts +13 -0
  10. package/dist/components/index.d.ts.map +1 -0
  11. package/dist/components/shape.d.ts +45 -0
  12. package/dist/components/shape.d.ts.map +1 -0
  13. package/dist/components/table.d.ts +46 -0
  14. package/dist/components/table.d.ts.map +1 -0
  15. package/dist/components/text.d.ts +57 -0
  16. package/dist/components/text.d.ts.map +1 -0
  17. package/dist/core/generator.d.ts +61 -0
  18. package/dist/core/generator.d.ts.map +1 -0
  19. package/dist/core/grid.d.ts +35 -0
  20. package/dist/core/grid.d.ts.map +1 -0
  21. package/dist/core/render.d.ts +8 -0
  22. package/dist/core/render.d.ts.map +1 -0
  23. package/dist/core/structure.d.ts +8 -0
  24. package/dist/core/structure.d.ts.map +1 -0
  25. package/dist/core/template.d.ts +10 -0
  26. package/dist/core/template.d.ts.map +1 -0
  27. package/dist/index.d.ts +10 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +1205 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/themes/defaults.d.ts +8 -0
  32. package/dist/themes/defaults.d.ts.map +1 -0
  33. package/dist/themes/index.d.ts +2 -0
  34. package/dist/themes/index.d.ts.map +1 -0
  35. package/dist/tsconfig.tsbuildinfo +1 -0
  36. package/dist/types.d.ts +184 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/utils/color.d.ts +12 -0
  39. package/dist/utils/color.d.ts.map +1 -0
  40. package/dist/utils/environment.d.ts +8 -0
  41. package/dist/utils/environment.d.ts.map +1 -0
  42. package/dist/utils/warn.d.ts +20 -0
  43. package/dist/utils/warn.d.ts.map +1 -0
  44. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,1205 @@
1
+ // src/core/generator.ts
2
+ import JSZip from "jszip";
3
+ import { writeFileSync } from "fs";
4
+
5
+ // src/types.ts
6
+ function isPresentationComponent(component) {
7
+ return typeof component === "object" && component !== null && component.name === "pptx";
8
+ }
9
+ function isSlideComponent(component) {
10
+ return typeof component === "object" && component !== null && component.name === "slide";
11
+ }
12
+
13
+ // src/utils/warn.ts
14
+ var W = {
15
+ UNKNOWN_COMPONENT: "UNKNOWN_COMPONENT",
16
+ UNKNOWN_CHART_TYPE: "UNKNOWN_CHART_TYPE",
17
+ UNKNOWN_SHAPE: "UNKNOWN_SHAPE",
18
+ CHART_NO_DATA: "CHART_NO_DATA",
19
+ CHART_INVALID_SERIES: "CHART_INVALID_SERIES",
20
+ CHART_MULTI_SERIES: "CHART_MULTI_SERIES",
21
+ IMAGE_NO_SOURCE: "IMAGE_NO_SOURCE",
22
+ IMAGE_PROBE_FAILED: "IMAGE_PROBE_FAILED",
23
+ MISSING_TEMPLATE: "MISSING_TEMPLATE",
24
+ UNKNOWN_PLACEHOLDER: "UNKNOWN_PLACEHOLDER",
25
+ PLACEHOLDER_NO_POSITION: "PLACEHOLDER_NO_POSITION",
26
+ THEME_COLOR_FALLBACK: "THEME_COLOR_FALLBACK",
27
+ UNKNOWN_COLOR: "UNKNOWN_COLOR",
28
+ GRID_POSITION_CLAMPED: "GRID_POSITION_CLAMPED"
29
+ };
30
+ function warn(warnings, code, message, extra) {
31
+ if (warnings) {
32
+ warnings.push({ code, message, ...extra });
33
+ } else {
34
+ console.warn(message);
35
+ }
36
+ }
37
+
38
+ // src/core/grid.ts
39
+ var DEFAULT_GRID_CONFIG = {
40
+ columns: 12,
41
+ rows: 6,
42
+ margin: { top: 0.5, right: 0.5, bottom: 0.5, left: 0.5 },
43
+ gutter: { column: 0.2, row: 0.2 }
44
+ };
45
+ function resolveMargin(margin) {
46
+ if (margin == null) return DEFAULT_GRID_CONFIG.margin;
47
+ if (typeof margin === "number") return { top: margin, right: margin, bottom: margin, left: margin };
48
+ return margin;
49
+ }
50
+ function resolveGutter(gutter) {
51
+ if (gutter == null) return DEFAULT_GRID_CONFIG.gutter;
52
+ if (typeof gutter === "number") return { column: gutter, row: gutter };
53
+ return gutter;
54
+ }
55
+ function mergeGridConfigs(base, override) {
56
+ if (!override) return base;
57
+ if (!base) return override;
58
+ const merged = {
59
+ columns: override.columns ?? base.columns,
60
+ rows: override.rows ?? base.rows
61
+ };
62
+ if (override.margin !== void 0) {
63
+ if (typeof override.margin === "number") {
64
+ merged.margin = override.margin;
65
+ } else {
66
+ merged.margin = { ...resolveMargin(base.margin), ...override.margin };
67
+ }
68
+ } else {
69
+ merged.margin = base.margin;
70
+ }
71
+ if (override.gutter !== void 0) {
72
+ if (typeof override.gutter === "number") {
73
+ merged.gutter = override.gutter;
74
+ } else {
75
+ merged.gutter = { ...resolveGutter(base.gutter), ...override.gutter };
76
+ }
77
+ } else {
78
+ merged.gutter = base.gutter;
79
+ }
80
+ return merged;
81
+ }
82
+ function resolveGridPosition(gridPos, gridConfig, slideWidth, slideHeight, warnings) {
83
+ const cols = Math.max(1, gridConfig?.columns ?? DEFAULT_GRID_CONFIG.columns);
84
+ const rows = Math.max(1, gridConfig?.rows ?? DEFAULT_GRID_CONFIG.rows);
85
+ const margin = resolveMargin(gridConfig?.margin);
86
+ const gutter = resolveGutter(gridConfig?.gutter);
87
+ const col = Math.max(0, Math.min(gridPos.column, cols - 1));
88
+ const row = Math.max(0, Math.min(gridPos.row, rows - 1));
89
+ const colSpan = Math.max(1, Math.min(gridPos.columnSpan ?? 1, cols - col));
90
+ const rowSpan = Math.max(1, Math.min(gridPos.rowSpan ?? 1, rows - row));
91
+ if (gridPos.column !== col || gridPos.row !== row) {
92
+ warn(
93
+ warnings,
94
+ W.GRID_POSITION_CLAMPED,
95
+ `Grid position clamped: column ${gridPos.column}\u2192${col}, row ${gridPos.row}\u2192${row} (grid: ${cols}\xD7${rows})`
96
+ );
97
+ }
98
+ const availableW = slideWidth - margin.left - margin.right;
99
+ const availableH = slideHeight - margin.top - margin.bottom;
100
+ const trackW = (availableW - (cols - 1) * gutter.column) / cols;
101
+ const trackH = (availableH - (rows - 1) * gutter.row) / rows;
102
+ const x = margin.left + col * (trackW + gutter.column);
103
+ const y = margin.top + row * (trackH + gutter.row);
104
+ const w = colSpan * trackW + (colSpan - 1) * gutter.column;
105
+ const h = rowSpan * trackH + (rowSpan - 1) * gutter.row;
106
+ return { x, y, w, h };
107
+ }
108
+ function resolveComponentGridPosition(component, gridConfig, slideWidth, slideHeight, warnings) {
109
+ const gridPos = component.props.grid;
110
+ if (!gridPos) return component;
111
+ const resolved = resolveGridPosition(gridPos, gridConfig, slideWidth, slideHeight, warnings);
112
+ const { grid: _grid, ...restProps } = component.props;
113
+ const newProps = { ...restProps };
114
+ const hasPercentX = typeof newProps.x === "string" || typeof newProps.w === "string";
115
+ const hasPercentY = typeof newProps.y === "string" || typeof newProps.h === "string";
116
+ const toPercX = (v) => `${+(v / slideWidth * 100).toFixed(2)}%`;
117
+ const toPercY = (v) => `${+(v / slideHeight * 100).toFixed(2)}%`;
118
+ if (newProps.x == null) newProps.x = hasPercentX ? toPercX(resolved.x) : resolved.x;
119
+ if (newProps.y == null) newProps.y = hasPercentY ? toPercY(resolved.y) : resolved.y;
120
+ if (newProps.w == null) newProps.w = hasPercentX ? toPercX(resolved.w) : resolved.w;
121
+ if (newProps.h == null) newProps.h = hasPercentY ? toPercY(resolved.h) : resolved.h;
122
+ return { ...component, props: newProps };
123
+ }
124
+
125
+ // src/themes/defaults.ts
126
+ var DEFAULT_STYLES = {
127
+ title: { fontSize: 36, bold: true, fontColor: "text", align: "center" },
128
+ subtitle: { fontSize: 20, italic: true, fontColor: "text2", align: "center" },
129
+ heading1: { fontSize: 28, bold: true, fontColor: "primary" },
130
+ heading2: { fontSize: 22, bold: true, fontColor: "primary" },
131
+ heading3: { fontSize: 18, bold: true, fontColor: "text" },
132
+ body: { fontSize: 14 },
133
+ caption: { fontSize: 10, italic: true, fontColor: "text2" }
134
+ };
135
+ var DEFAULT_PPTX_THEME = {
136
+ name: "default",
137
+ colors: {
138
+ primary: "#4472C4",
139
+ secondary: "#ED7D31",
140
+ accent: "#70AD47",
141
+ background: "#FFFFFF",
142
+ text: "#333333",
143
+ text2: "#44546A",
144
+ background2: "#E7E6E6",
145
+ accent4: "#FFC000",
146
+ accent5: "#5B9BD5",
147
+ accent6: "#70AD47"
148
+ },
149
+ fonts: {
150
+ heading: "Arial",
151
+ body: "Arial"
152
+ },
153
+ defaults: {
154
+ fontSize: 18,
155
+ fontColor: "#333333"
156
+ },
157
+ styles: DEFAULT_STYLES
158
+ };
159
+ var PPTX_THEMES = {
160
+ default: DEFAULT_PPTX_THEME,
161
+ dark: {
162
+ name: "dark",
163
+ colors: {
164
+ primary: "#5B9BD5",
165
+ secondary: "#FF6F61",
166
+ accent: "#6BCB77",
167
+ background: "#2D2D2D",
168
+ text: "#FFFFFF",
169
+ text2: "#CCCCCC",
170
+ background2: "#3D3D3D",
171
+ accent4: "#FFB347",
172
+ accent5: "#77DD77",
173
+ accent6: "#AEC6CF"
174
+ },
175
+ fonts: {
176
+ heading: "Arial",
177
+ body: "Arial"
178
+ },
179
+ defaults: {
180
+ fontSize: 18,
181
+ fontColor: "#FFFFFF"
182
+ },
183
+ styles: DEFAULT_STYLES
184
+ },
185
+ minimal: {
186
+ name: "minimal",
187
+ colors: {
188
+ primary: "#000000",
189
+ secondary: "#666666",
190
+ accent: "#999999",
191
+ background: "#FFFFFF",
192
+ text: "#000000",
193
+ text2: "#444444",
194
+ background2: "#F5F5F5",
195
+ accent4: "#BBBBBB",
196
+ accent5: "#DDDDDD",
197
+ accent6: "#888888"
198
+ },
199
+ fonts: {
200
+ heading: "Helvetica",
201
+ body: "Helvetica"
202
+ },
203
+ defaults: {
204
+ fontSize: 18,
205
+ fontColor: "#000000"
206
+ },
207
+ styles: DEFAULT_STYLES
208
+ }
209
+ };
210
+ function getPptxTheme(name) {
211
+ return PPTX_THEMES[name] || DEFAULT_PPTX_THEME;
212
+ }
213
+ var pptxThemes = PPTX_THEMES;
214
+
215
+ // src/core/structure.ts
216
+ function processPresentation(document, options) {
217
+ const { props, children = [] } = document;
218
+ const themeName = props.theme ?? "default";
219
+ const theme = options?.customThemes?.[themeName] ?? getPptxTheme(themeName);
220
+ const slideWidth = props.slideWidth ?? 10;
221
+ const slideHeight = props.slideHeight ?? 7.5;
222
+ let templates;
223
+ if (props.templates && props.templates.length > 0) {
224
+ templates = props.templates.map((m) => {
225
+ const effectiveGrid = mergeGridConfigs(props.grid, m.grid);
226
+ const resolvedPhs = m.placeholders?.map((ph) => {
227
+ if (!ph.grid) return ph;
228
+ const abs = resolveGridPosition(ph.grid, effectiveGrid, slideWidth, slideHeight);
229
+ return {
230
+ ...ph,
231
+ x: ph.x ?? abs.x,
232
+ y: ph.y ?? abs.y,
233
+ w: ph.w ?? abs.w,
234
+ h: ph.h ?? abs.h,
235
+ grid: void 0
236
+ };
237
+ });
238
+ const resolvedObjects = m.objects?.map(
239
+ (obj) => resolveComponentGridPosition(obj, effectiveGrid, slideWidth, slideHeight)
240
+ );
241
+ return { ...m, placeholders: resolvedPhs, objects: resolvedObjects };
242
+ });
243
+ }
244
+ const slides = [];
245
+ for (const child of children) {
246
+ if (isSlideComponent(child)) {
247
+ const slideComponents = [];
248
+ if (child.children) {
249
+ for (const slideChild of child.children) {
250
+ slideComponents.push(slideChild);
251
+ }
252
+ }
253
+ slides.push({
254
+ components: slideComponents,
255
+ background: child.props.background,
256
+ notes: child.props.notes,
257
+ layout: child.props.layout,
258
+ hidden: child.props.hidden,
259
+ template: child.props.template,
260
+ placeholders: child.props.placeholders
261
+ });
262
+ }
263
+ }
264
+ return {
265
+ metadata: {
266
+ title: props.title,
267
+ author: props.author,
268
+ subject: props.subject,
269
+ company: props.company
270
+ },
271
+ theme,
272
+ grid: props.grid,
273
+ slideWidth,
274
+ slideHeight,
275
+ rtlMode: props.rtlMode ?? false,
276
+ pageNumberFormat: props.pageNumberFormat ?? "9",
277
+ slides,
278
+ templates
279
+ };
280
+ }
281
+
282
+ // src/core/render.ts
283
+ import PptxGenJS from "pptxgenjs";
284
+
285
+ // src/utils/color.ts
286
+ import { SEMANTIC_COLOR_NAMES } from "@json-to-office/shared-pptx";
287
+ var SEMANTIC_TO_THEME_KEY = {
288
+ ...Object.fromEntries(SEMANTIC_COLOR_NAMES.map((n) => [n, n])),
289
+ // Aliases (PowerPoint XML compat)
290
+ accent1: "primary",
291
+ accent2: "secondary",
292
+ accent3: "accent",
293
+ tx1: "text",
294
+ tx2: "text2",
295
+ bg1: "background",
296
+ bg2: "background2"
297
+ };
298
+ function resolveColor(color, theme, warnings) {
299
+ const themeKey = SEMANTIC_TO_THEME_KEY[color];
300
+ if (themeKey) {
301
+ const resolved = theme.colors[themeKey];
302
+ if (resolved) return resolved.startsWith("#") ? resolved.slice(1) : resolved;
303
+ warn(warnings, W.THEME_COLOR_FALLBACK, `Theme color "${themeKey}" not defined, falling back to primary`);
304
+ return theme.colors.primary.startsWith("#") ? theme.colors.primary.slice(1) : theme.colors.primary;
305
+ }
306
+ const bare = color.startsWith("#") ? color.slice(1) : color;
307
+ if (/^[0-9A-Fa-f]{3}$/.test(bare)) {
308
+ return bare[0] + bare[0] + bare[1] + bare[1] + bare[2] + bare[2];
309
+ }
310
+ if (!/^[0-9A-Fa-f]{6}$/.test(bare)) {
311
+ warn(warnings, W.UNKNOWN_COLOR, `Unknown color value: "${color}", treating as literal`);
312
+ }
313
+ return bare;
314
+ }
315
+
316
+ // src/components/text.ts
317
+ function resolvePagePlaceholders(text, ctx) {
318
+ const { slideNumber, totalSlides, pageNumberFormat } = ctx;
319
+ const fmt = (n) => pageNumberFormat === "09" ? String(n).padStart(String(totalSlides).length, "0") : String(n);
320
+ return text.replace(/\{PAGE_NUMBER\}/g, fmt(slideNumber)).replace(/\{PAGE_COUNT\}/g, fmt(totalSlides));
321
+ }
322
+ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
323
+ const style = props.style ? theme.styles?.[props.style] : void 0;
324
+ const isHeadingStyle = props.style && /^(title|heading)/.test(props.style);
325
+ const opts = {};
326
+ if (props.x !== void 0) opts.x = props.x;
327
+ if (props.y !== void 0) opts.y = props.y;
328
+ if (props.w !== void 0) opts.w = props.w;
329
+ if (props.h !== void 0) opts.h = props.h;
330
+ if (props.h === void 0) {
331
+ const fontSize = props.fontSize ?? theme.defaults.fontSize ?? 18;
332
+ const lines = (props.text.match(/\n/g)?.length ?? 0) + 1;
333
+ opts.h = Math.max(0.5, fontSize / 72 * 1.6 * lines);
334
+ opts.isTextBox = true;
335
+ }
336
+ opts.fontSize = props.fontSize ?? style?.fontSize ?? theme.defaults.fontSize;
337
+ opts.fontFace = props.fontFace ?? style?.fontFace ?? (isHeadingStyle ? theme.fonts.heading : theme.fonts.body);
338
+ opts.color = resolveColor(props.color ?? style?.fontColor ?? theme.defaults.fontColor, theme, warnings);
339
+ const bold = props.bold ?? style?.bold;
340
+ const italic = props.italic ?? style?.italic;
341
+ if (bold != null) opts.bold = bold;
342
+ if (italic != null) opts.italic = italic;
343
+ if (props.strike) opts.strike = true;
344
+ if (props.underline !== void 0) {
345
+ if (typeof props.underline === "boolean") {
346
+ opts.underline = { style: "sng" };
347
+ } else {
348
+ opts.underline = props.underline;
349
+ }
350
+ }
351
+ const align = props.align ?? style?.align;
352
+ if (align) opts.align = align;
353
+ opts.valign = props.valign ?? "top";
354
+ if (props.bullet !== void 0) opts.bullet = props.bullet;
355
+ opts.margin = props.margin ?? 0;
356
+ if (props.rotate !== void 0) opts.rotate = props.rotate;
357
+ if (props.shadow) {
358
+ opts.shadow = {
359
+ type: props.shadow.type ?? "outer",
360
+ color: resolveColor(props.shadow.color ?? "000000", theme, warnings),
361
+ blur: props.shadow.blur ?? 3,
362
+ offset: props.shadow.offset ?? 3,
363
+ angle: props.shadow.angle ?? 45,
364
+ opacity: props.shadow.opacity ?? 0.5
365
+ };
366
+ }
367
+ if (props.fill) {
368
+ opts.fill = { color: resolveColor(props.fill.color, theme, warnings) };
369
+ if (props.fill.transparency !== void 0) {
370
+ opts.fill.transparency = props.fill.transparency;
371
+ }
372
+ }
373
+ if (props.hyperlink) {
374
+ if (props.hyperlink.url) {
375
+ opts.hyperlink = {
376
+ url: props.hyperlink.url,
377
+ tooltip: props.hyperlink.tooltip
378
+ };
379
+ } else if (props.hyperlink.slide) {
380
+ opts.hyperlink = {
381
+ slide: props.hyperlink.slide,
382
+ tooltip: props.hyperlink.tooltip
383
+ };
384
+ }
385
+ }
386
+ const lineSpacing = props.lineSpacing ?? style?.lineSpacing;
387
+ if (lineSpacing !== void 0) opts.lineSpacing = lineSpacing;
388
+ const charSpacing = props.charSpacing ?? style?.charSpacing;
389
+ if (charSpacing !== void 0) opts.charSpacing = charSpacing;
390
+ if (props.paraSpaceBefore !== void 0) opts.paraSpaceBefore = props.paraSpaceBefore;
391
+ const paraSpaceAfter = props.paraSpaceAfter ?? style?.paraSpaceAfter;
392
+ if (paraSpaceAfter !== void 0) opts.paraSpaceAfter = paraSpaceAfter;
393
+ if (props.breakLine) opts.breakLine = true;
394
+ const text = slideCtx ? resolvePagePlaceholders(props.text, slideCtx) : props.text;
395
+ slide.addText(text, opts);
396
+ }
397
+
398
+ // src/components/image.ts
399
+ import path from "path";
400
+ import probe from "probe-image-size";
401
+ function isPrivateUrl(urlStr) {
402
+ try {
403
+ const { hostname } = new URL(urlStr);
404
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("10.") || hostname.startsWith("192.168.") || hostname.startsWith("169.254.") || hostname.endsWith(".local") || hostname.endsWith(".internal")) return true;
405
+ if (hostname.startsWith("172.")) {
406
+ const second = parseInt(hostname.split(".")[1], 10);
407
+ if (second >= 16 && second <= 31) return true;
408
+ }
409
+ return false;
410
+ } catch {
411
+ return true;
412
+ }
413
+ }
414
+ async function probeImageSize(imagePath, warnings) {
415
+ try {
416
+ if (/^data:image\//.test(imagePath)) {
417
+ const base64Data = imagePath.split(",")[1];
418
+ if (!base64Data) return void 0;
419
+ const buf = Buffer.from(base64Data, "base64");
420
+ const result2 = probe.sync(buf);
421
+ return result2 ? { width: result2.width, height: result2.height } : void 0;
422
+ }
423
+ if (/^https?:\/\//.test(imagePath)) {
424
+ if (isPrivateUrl(imagePath)) return void 0;
425
+ const result2 = await probe(imagePath, { timeout: 5e3 });
426
+ return { width: result2.width, height: result2.height };
427
+ }
428
+ const resolved = path.resolve(imagePath);
429
+ if (!resolved.startsWith(process.cwd())) return void 0;
430
+ const { createReadStream } = await import("fs");
431
+ const result = await probe(createReadStream(resolved));
432
+ return result ? { width: result.width, height: result.height } : void 0;
433
+ } catch (err) {
434
+ warn(warnings, W.IMAGE_PROBE_FAILED, `Image probe failed: ${err instanceof Error ? err.message : String(err)}`, { component: "image" });
435
+ return void 0;
436
+ }
437
+ }
438
+ async function renderImageComponent(slide, props, theme, warnings) {
439
+ const opts = {};
440
+ if (props.path) {
441
+ opts.path = props.path;
442
+ } else if (props.base64) {
443
+ opts.data = props.base64;
444
+ } else {
445
+ warn(warnings, W.IMAGE_NO_SOURCE, "Image component missing both path and base64", { component: "image" });
446
+ return;
447
+ }
448
+ if (props.x !== void 0) opts.x = props.x;
449
+ if (props.y !== void 0) opts.y = props.y;
450
+ if (props.w !== void 0) opts.w = props.w;
451
+ if (props.h !== void 0) opts.h = props.h;
452
+ if (props.sizing && (props.sizing.type === "contain" || props.sizing.type === "cover")) {
453
+ const source = props.path || props.base64;
454
+ const intrinsic = source ? await probeImageSize(source, warnings) : void 0;
455
+ const boxW = Number(props.sizing.w ?? props.w);
456
+ const boxH = Number(props.sizing.h ?? props.h);
457
+ if (intrinsic && !isNaN(boxW) && !isNaN(boxH)) {
458
+ const imgAspect = intrinsic.width / intrinsic.height;
459
+ if (props.sizing.type === "contain") {
460
+ const boxAspect = boxW / boxH;
461
+ let fitW, fitH;
462
+ if (imgAspect > boxAspect) {
463
+ fitW = boxW;
464
+ fitH = boxW / imgAspect;
465
+ } else {
466
+ fitH = boxH;
467
+ fitW = boxH * imgAspect;
468
+ }
469
+ const baseX = Number(props.x ?? 0);
470
+ const baseY = Number(props.y ?? 0);
471
+ opts.x = baseX + (boxW - fitW) / 2;
472
+ opts.y = baseY + (boxH - fitH) / 2;
473
+ opts.w = fitW;
474
+ opts.h = fitH;
475
+ } else {
476
+ opts.w = intrinsic.width;
477
+ opts.h = intrinsic.height;
478
+ opts.sizing = { type: "cover", w: boxW, h: boxH };
479
+ }
480
+ } else {
481
+ opts.sizing = { ...props.sizing, w: boxW, h: boxH };
482
+ }
483
+ } else if (props.sizing) {
484
+ opts.sizing = {
485
+ ...props.sizing,
486
+ w: props.sizing.w ?? props.w,
487
+ h: props.sizing.h ?? props.h
488
+ };
489
+ }
490
+ if (props.rotate !== void 0) opts.rotate = props.rotate;
491
+ if (props.rounding) opts.rounding = true;
492
+ if (props.shadow) {
493
+ opts.shadow = {
494
+ type: props.shadow.type ?? "outer",
495
+ color: resolveColor(props.shadow.color ?? "000000", theme, warnings),
496
+ blur: props.shadow.blur ?? 3,
497
+ offset: props.shadow.offset ?? 3,
498
+ angle: props.shadow.angle ?? 45,
499
+ opacity: props.shadow.opacity ?? 0.5
500
+ };
501
+ }
502
+ if (props.hyperlink) {
503
+ if (props.hyperlink.url) {
504
+ opts.hyperlink = {
505
+ url: props.hyperlink.url,
506
+ tooltip: props.hyperlink.tooltip
507
+ };
508
+ } else if (props.hyperlink.slide) {
509
+ opts.hyperlink = {
510
+ slide: props.hyperlink.slide,
511
+ tooltip: props.hyperlink.tooltip
512
+ };
513
+ }
514
+ }
515
+ if (props.alt) opts.altText = props.alt;
516
+ slide.addImage(opts);
517
+ }
518
+
519
+ // src/components/shape.ts
520
+ var SHAPE_TYPE_MAP = {
521
+ rect: "rect",
522
+ roundRect: "roundRect",
523
+ ellipse: "ellipse",
524
+ triangle: "triangle",
525
+ diamond: "diamond",
526
+ pentagon: "pentagon",
527
+ hexagon: "hexagon",
528
+ star5: "star5",
529
+ star6: "star6",
530
+ line: "line",
531
+ arrow: "rightArrow",
532
+ chevron: "chevron",
533
+ cloud: "cloud",
534
+ heart: "heart",
535
+ lightning: "lightningBolt"
536
+ };
537
+ function buildShapeOpts(props, theme, warnings) {
538
+ const opts = {};
539
+ if (props.x !== void 0) opts.x = props.x;
540
+ if (props.y !== void 0) opts.y = props.y;
541
+ if (props.w !== void 0) opts.w = props.w;
542
+ if (props.h !== void 0) opts.h = props.h;
543
+ if (props.fill) {
544
+ opts.fill = { color: resolveColor(props.fill.color, theme, warnings) };
545
+ if (props.fill.transparency !== void 0) {
546
+ opts.fill.transparency = props.fill.transparency;
547
+ }
548
+ }
549
+ if (props.line) {
550
+ opts.line = {};
551
+ if (props.line.color) opts.line.color = resolveColor(props.line.color, theme, warnings);
552
+ if (props.line.width) opts.line.width = props.line.width;
553
+ if (props.line.dashType) opts.line.dashType = props.line.dashType;
554
+ }
555
+ if (props.rotate !== void 0) opts.rotate = props.rotate;
556
+ if (props.rectRadius !== void 0) opts.rectRadius = props.rectRadius;
557
+ if (props.shadow) {
558
+ opts.shadow = {
559
+ type: props.shadow.type ?? "outer",
560
+ color: resolveColor(props.shadow.color ?? "000000", theme, warnings),
561
+ blur: props.shadow.blur ?? 3,
562
+ offset: props.shadow.offset ?? 3,
563
+ angle: props.shadow.angle ?? 45,
564
+ opacity: props.shadow.opacity ?? 0.5
565
+ };
566
+ }
567
+ return opts;
568
+ }
569
+ function renderShapeComponent(slide, props, theme, pptx, warnings) {
570
+ const shapeTypeName = SHAPE_TYPE_MAP[props.type] || props.type;
571
+ const shapeType = pptx.ShapeType[shapeTypeName];
572
+ if (!shapeType) {
573
+ warn(warnings, W.UNKNOWN_SHAPE, `Unknown shape type: ${props.type}`, { component: "shape" });
574
+ return;
575
+ }
576
+ const style = props.style ? theme.styles?.[props.style] : void 0;
577
+ const isHeadingStyle = props.style && /^(title|heading)/.test(props.style);
578
+ const opts = buildShapeOpts(props, theme, warnings);
579
+ if (props.text && (!Array.isArray(props.text) || props.text.length > 0)) {
580
+ opts.shape = shapeType;
581
+ opts.fontSize = props.fontSize ?? style?.fontSize ?? theme.defaults.fontSize;
582
+ opts.fontFace = props.fontFace ?? style?.fontFace ?? (isHeadingStyle ? theme.fonts.heading : theme.fonts.body);
583
+ opts.color = resolveColor(props.fontColor ?? style?.fontColor ?? theme.defaults.fontColor, theme, warnings);
584
+ const bold = props.bold ?? style?.bold;
585
+ const italic = props.italic ?? style?.italic;
586
+ if (bold != null) opts.bold = bold;
587
+ if (italic != null) opts.italic = italic;
588
+ const charSpacing = props.charSpacing ?? style?.charSpacing;
589
+ if (charSpacing !== void 0) opts.charSpacing = charSpacing;
590
+ const align = props.align ?? style?.align;
591
+ if (align) opts.align = align;
592
+ opts.valign = props.valign ?? "top";
593
+ if (Array.isArray(props.text)) {
594
+ const textSegments = props.text.map((seg) => {
595
+ const segOpts = {};
596
+ if (seg.fontSize != null) segOpts.fontSize = seg.fontSize;
597
+ if (seg.fontFace != null) segOpts.fontFace = seg.fontFace;
598
+ if (seg.color != null) segOpts.color = resolveColor(seg.color, theme, warnings);
599
+ if (seg.bold != null) segOpts.bold = seg.bold;
600
+ if (seg.italic != null) segOpts.italic = seg.italic;
601
+ if (seg.breakLine != null) segOpts.breakLine = seg.breakLine;
602
+ if (seg.charSpacing != null) segOpts.charSpacing = seg.charSpacing;
603
+ if (seg.spaceBefore != null) segOpts.paraSpaceBefore = seg.spaceBefore;
604
+ if (seg.spaceAfter != null) segOpts.paraSpaceAfter = seg.spaceAfter;
605
+ return { text: seg.text, options: segOpts };
606
+ });
607
+ slide.addText(textSegments, opts);
608
+ } else {
609
+ slide.addText(props.text, opts);
610
+ }
611
+ } else {
612
+ slide.addShape(shapeType, opts);
613
+ }
614
+ }
615
+
616
+ // src/components/table.ts
617
+ var EMOJI_PRONE_CHARS = /[✓✔✗✘☐☑☒★☆●○■□▶◀▲▼⚡⚠❌❓❗]/gu;
618
+ function applyTextVariationSelector(text) {
619
+ return text.replace(EMOJI_PRONE_CHARS, (ch) => ch + "\uFE0E");
620
+ }
621
+ function renderTableComponent(slide, props, theme, pptx, warnings) {
622
+ let bgFill;
623
+ let headerFill;
624
+ let borderRadiusTableW;
625
+ if (props.borderRadius && pptx && props.rows.length >= 2) {
626
+ const lastRow = props.rows[props.rows.length - 1];
627
+ const lastCell = lastRow?.[0];
628
+ bgFill = props.fill ? resolveColor(props.fill, theme, warnings) : typeof lastCell === "object" && lastCell.fill ? resolveColor(lastCell.fill, theme, warnings) : "FFFFFF";
629
+ const firstCell = props.rows[0]?.[0];
630
+ headerFill = typeof firstCell === "object" && firstCell.fill ? resolveColor(firstCell.fill, theme, warnings) : bgFill;
631
+ borderRadiusTableW = Array.isArray(props.colW) ? props.colW.reduce((sum, w) => sum + w, 0) : typeof props.colW === "number" ? props.colW * (props.rows[0]?.length ?? 1) : typeof props.w === "number" ? props.w : 5;
632
+ }
633
+ const innerBorder = props.border ? {
634
+ type: props.border.type ?? "solid",
635
+ pt: props.border.pt ?? 1,
636
+ color: resolveColor(props.border.color ?? "000000", theme, warnings)
637
+ } : void 0;
638
+ const buildBorderRadiusBorders = (rowIndex, colIndex, colCount) => {
639
+ const isTop = rowIndex === 0;
640
+ const isBottom = rowIndex === props.rows.length - 1;
641
+ const isLeft = colIndex === 0;
642
+ const isRight = colIndex === colCount - 1;
643
+ const zeroBorder = { type: "none", pt: 0 };
644
+ const hInner = innerBorder ?? zeroBorder;
645
+ return [
646
+ isTop || rowIndex === 1 ? zeroBorder : hInner,
647
+ // top: outer + header-body seam
648
+ isRight ? zeroBorder : hInner,
649
+ // right
650
+ isBottom || rowIndex === 0 ? zeroBorder : hInner,
651
+ // bottom: outer + header-body seam
652
+ isLeft ? zeroBorder : hInner
653
+ // left
654
+ ];
655
+ };
656
+ const lastRowIdx = props.rows.length - 1;
657
+ const tableRows = props.rows.map(
658
+ (row, rowIndex) => row.map((cell, colIndex) => {
659
+ const lastColIdx = row.length - 1;
660
+ const isCorner = bgFill && (rowIndex === 0 || rowIndex === lastRowIdx) && (colIndex === 0 || colIndex === lastColIdx);
661
+ if (typeof cell === "string") {
662
+ if (!bgFill) return { text: applyTextVariationSelector(cell) };
663
+ const isHeader = rowIndex === 0;
664
+ const opts2 = {
665
+ border: buildBorderRadiusBorders(rowIndex, colIndex, row.length)
666
+ };
667
+ if (!isCorner) opts2.fill = { color: isHeader ? headerFill : bgFill };
668
+ return { text: applyTextVariationSelector(cell), options: opts2 };
669
+ }
670
+ const cellOpts = {};
671
+ if (cell.color) cellOpts.color = resolveColor(cell.color, theme, warnings);
672
+ if (bgFill) {
673
+ const isHeader = rowIndex === 0;
674
+ if (!isCorner) {
675
+ const resolvedFill = cell.fill ? resolveColor(cell.fill, theme, warnings) : isHeader ? headerFill : bgFill;
676
+ cellOpts.fill = { color: resolvedFill };
677
+ }
678
+ cellOpts.border = buildBorderRadiusBorders(rowIndex, colIndex, row.length);
679
+ } else if (cell.fill) {
680
+ cellOpts.fill = { color: resolveColor(cell.fill, theme, warnings) };
681
+ }
682
+ if (cell.fontSize) cellOpts.fontSize = cell.fontSize;
683
+ if (cell.fontFace) cellOpts.fontFace = cell.fontFace;
684
+ if (cell.bold) cellOpts.bold = true;
685
+ if (cell.italic) cellOpts.italic = true;
686
+ if (cell.align) cellOpts.align = cell.align;
687
+ if (cell.valign) cellOpts.valign = cell.valign;
688
+ if (cell.colspan) cellOpts.colspan = cell.colspan;
689
+ if (cell.rowspan) cellOpts.rowspan = cell.rowspan;
690
+ if (cell.margin !== void 0) cellOpts.margin = cell.margin;
691
+ return { text: applyTextVariationSelector(cell.text), options: cellOpts };
692
+ })
693
+ );
694
+ const opts = {};
695
+ if (props.x !== void 0) opts.x = props.x;
696
+ if (props.y !== void 0) opts.y = props.y;
697
+ if (props.w !== void 0) opts.w = props.w;
698
+ if (props.h !== void 0) opts.h = props.h;
699
+ if (props.colW !== void 0) opts.colW = props.colW;
700
+ if (props.rowH !== void 0) opts.rowH = props.rowH;
701
+ if (props.border && !bgFill) {
702
+ opts.border = {
703
+ type: props.border.type ?? "solid",
704
+ pt: props.border.pt ?? 1,
705
+ color: resolveColor(props.border.color ?? "000000", theme, warnings)
706
+ };
707
+ }
708
+ if (props.fill) opts.fill = { color: resolveColor(props.fill, theme, warnings) };
709
+ opts.fontSize = props.fontSize ?? theme.defaults.fontSize;
710
+ opts.fontFace = props.fontFace ?? theme.fonts.body;
711
+ if (props.color) opts.color = resolveColor(props.color, theme, warnings);
712
+ if (props.align) opts.align = props.align;
713
+ opts.valign = props.valign ?? "middle";
714
+ if (props.autoPage) opts.autoPage = true;
715
+ if (props.autoPageRepeatHeader) {
716
+ opts.autoPageRepeatHeader = true;
717
+ opts.autoPageHeaderRows = 1;
718
+ }
719
+ if (props.margin !== void 0) opts.margin = props.margin;
720
+ if (props.borderRadius && pptx && typeof props.x === "number" && typeof props.y === "number") {
721
+ let tableH = props.h ?? 2;
722
+ if (typeof props.rowH === "number") {
723
+ tableH = props.rowH * props.rows.length;
724
+ } else if (Array.isArray(props.rowH)) {
725
+ tableH = props.rowH.reduce((sum, h) => sum + h, 0);
726
+ }
727
+ const headerH = typeof props.rowH === "number" ? props.rowH : Array.isArray(props.rowH) ? props.rowH[0] : 0.45;
728
+ const tableW = borderRadiusTableW;
729
+ const noLine = { type: "none" };
730
+ slide.addShape(pptx.ShapeType.roundRect, {
731
+ x: props.x,
732
+ y: props.y,
733
+ w: tableW,
734
+ h: headerH,
735
+ fill: { color: headerFill },
736
+ rectRadius: props.borderRadius,
737
+ line: noLine
738
+ });
739
+ slide.addShape(pptx.ShapeType.rect, {
740
+ x: props.x,
741
+ y: props.y + headerH - props.borderRadius,
742
+ w: tableW,
743
+ h: props.borderRadius,
744
+ fill: { color: headerFill },
745
+ line: noLine
746
+ });
747
+ const bodyY = props.y + headerH;
748
+ const bodyH = tableH - headerH;
749
+ slide.addShape(pptx.ShapeType.roundRect, {
750
+ x: props.x,
751
+ y: bodyY,
752
+ w: tableW,
753
+ h: bodyH,
754
+ fill: { color: bgFill },
755
+ rectRadius: props.borderRadius,
756
+ line: noLine
757
+ });
758
+ slide.addShape(pptx.ShapeType.rect, {
759
+ x: props.x,
760
+ y: bodyY,
761
+ w: tableW,
762
+ h: props.borderRadius,
763
+ fill: { color: bgFill },
764
+ line: noLine
765
+ });
766
+ }
767
+ if (bgFill && borderRadiusTableW !== void 0) {
768
+ opts.w = borderRadiusTableW;
769
+ opts.border = [{ type: "none" }, { type: "none" }, { type: "none" }, { type: "none" }];
770
+ }
771
+ slide.addTable(tableRows, opts);
772
+ }
773
+
774
+ // src/utils/environment.ts
775
+ function isNodeEnvironment() {
776
+ return typeof process !== "undefined" && process.versions != null && process.versions.node != null && typeof process.versions.node === "string";
777
+ }
778
+
779
+ // src/components/highcharts.ts
780
+ var PX_PER_INCH = 96;
781
+ var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
782
+ function getExportServerUrl(propsUrl) {
783
+ return propsUrl || process.env.HIGHCHARTS_SERVER_URL || DEFAULT_EXPORT_SERVER_URL;
784
+ }
785
+ async function generateChart(config) {
786
+ if (!isNodeEnvironment()) {
787
+ throw new Error(
788
+ "Highcharts export server requires a Node.js environment. Chart generation is not available in browser environments."
789
+ );
790
+ }
791
+ const serverUrl = getExportServerUrl(config.serverUrl);
792
+ const response = await fetch(`${serverUrl}/export`, {
793
+ method: "POST",
794
+ headers: { "Content-Type": "application/json" },
795
+ body: JSON.stringify({
796
+ infile: config.options,
797
+ type: "png",
798
+ b64: true,
799
+ scale: config.scale
800
+ })
801
+ }).catch((error) => {
802
+ throw new Error(
803
+ `Highcharts Export Server is not running at ${serverUrl}. Start it with: npx highcharts-export-server --enableServer true
804
+ Cause: ${error instanceof Error ? error.message : String(error)}`
805
+ );
806
+ });
807
+ if (!response.ok) {
808
+ throw new Error(
809
+ `Highcharts export server returned ${response.status}: ${response.statusText}`
810
+ );
811
+ }
812
+ const base64Data = await response.text();
813
+ return {
814
+ base64DataUri: `data:image/png;base64,${base64Data}`,
815
+ width: config.options.chart?.width ?? 960,
816
+ height: config.options.chart?.height ?? 720
817
+ };
818
+ }
819
+ async function renderHighchartsComponent(slide, props, _theme, _warnings) {
820
+ const chart = await generateChart(props);
821
+ const w = props.w ?? chart.width / PX_PER_INCH;
822
+ const h = props.h ?? chart.height / PX_PER_INCH;
823
+ slide.addImage({
824
+ data: chart.base64DataUri,
825
+ x: props.x ?? 0,
826
+ y: props.y ?? 0,
827
+ w,
828
+ h
829
+ });
830
+ }
831
+
832
+ // src/components/chart.ts
833
+ var CHART_TYPE_MAP = {
834
+ area: "area",
835
+ bar: "bar",
836
+ bar3D: "bar3D",
837
+ bubble: "bubble",
838
+ doughnut: "doughnut",
839
+ line: "line",
840
+ pie: "pie",
841
+ radar: "radar",
842
+ scatter: "scatter"
843
+ };
844
+ var DEFAULT_THEME_COLORS = ["primary", "secondary", "accent", "accent4", "accent5", "accent6"];
845
+ function renderChartComponent(slide, props, theme, _pptx, warnings) {
846
+ const chartType = CHART_TYPE_MAP[props.type];
847
+ if (!chartType) {
848
+ warn(warnings, W.UNKNOWN_CHART_TYPE, `Unknown chart type: ${props.type}`, { component: "chart" });
849
+ return;
850
+ }
851
+ if (!props.data || props.data.length === 0) {
852
+ warn(warnings, W.CHART_NO_DATA, "Chart component has no data series", { component: "chart" });
853
+ return;
854
+ }
855
+ for (const series of props.data) {
856
+ if (!series.labels || !series.values) {
857
+ warn(warnings, W.CHART_INVALID_SERIES, `Chart series "${series.name ?? "(unnamed)"}" missing labels or values`, { component: "chart" });
858
+ return;
859
+ }
860
+ }
861
+ if ((chartType === "pie" || chartType === "doughnut") && props.data.length > 1) {
862
+ warn(warnings, W.CHART_MULTI_SERIES, `${props.type} chart has ${props.data.length} series \u2014 only the first will render`, { component: "chart" });
863
+ }
864
+ const data = props.data.map((series) => {
865
+ const d = {};
866
+ if (series.name !== void 0) d.name = series.name;
867
+ if (series.labels) d.labels = series.labels;
868
+ if (series.values) d.values = series.values;
869
+ if (series.sizes) d.sizes = series.sizes;
870
+ return d;
871
+ });
872
+ const opts = {};
873
+ if (props.x !== void 0) opts.x = props.x;
874
+ if (props.y !== void 0) opts.y = props.y;
875
+ if (props.w !== void 0) opts.w = props.w;
876
+ if (props.h !== void 0) opts.h = props.h;
877
+ const colorSources = props.chartColors ?? DEFAULT_THEME_COLORS;
878
+ opts.chartColors = colorSources.map((c) => resolveColor(c, theme, warnings));
879
+ const themeTextColor = resolveColor("text", theme, warnings);
880
+ opts.titleColor = props.titleColor ? resolveColor(props.titleColor, theme, warnings) : themeTextColor;
881
+ opts.legendColor = props.legendColor ? resolveColor(props.legendColor, theme, warnings) : themeTextColor;
882
+ opts.catAxisLabelColor = props.catAxisLabelColor ? resolveColor(props.catAxisLabelColor, theme, warnings) : themeTextColor;
883
+ opts.valAxisLabelColor = props.valAxisLabelColor ? resolveColor(props.valAxisLabelColor, theme, warnings) : themeTextColor;
884
+ if (props.showLegend !== void 0) opts.showLegend = props.showLegend;
885
+ if (props.showTitle !== void 0) opts.showTitle = props.showTitle;
886
+ if (props.showValue !== void 0) opts.showValue = props.showValue;
887
+ if (props.showPercent !== void 0) opts.showPercent = props.showPercent;
888
+ if (props.showLabel !== void 0) opts.showLabel = props.showLabel;
889
+ if (props.showSerName !== void 0) opts.showSerName = props.showSerName;
890
+ if (props.title !== void 0) opts.title = props.title;
891
+ if (props.titleFontSize !== void 0) opts.titleFontSize = props.titleFontSize;
892
+ if (props.titleFontFace !== void 0) opts.titleFontFace = props.titleFontFace;
893
+ if (props.legendPos !== void 0) opts.legendPos = props.legendPos;
894
+ if (props.legendFontSize !== void 0) opts.legendFontSize = props.legendFontSize;
895
+ if (props.legendFontFace !== void 0) opts.legendFontFace = props.legendFontFace;
896
+ if (props.catAxisTitle !== void 0) {
897
+ opts.catAxisTitle = props.catAxisTitle;
898
+ opts.showCatAxisTitle = true;
899
+ }
900
+ if (props.catAxisHidden !== void 0) opts.catAxisHidden = props.catAxisHidden;
901
+ if (props.catAxisLabelRotate !== void 0) opts.catAxisLabelRotate = props.catAxisLabelRotate;
902
+ if (props.catAxisLabelFontSize !== void 0) opts.catAxisLabelFontSize = props.catAxisLabelFontSize;
903
+ if (props.valAxisTitle !== void 0) {
904
+ opts.valAxisTitle = props.valAxisTitle;
905
+ opts.showValAxisTitle = true;
906
+ }
907
+ if (props.valAxisHidden !== void 0) opts.valAxisHidden = props.valAxisHidden;
908
+ if (props.valAxisMinVal !== void 0) opts.valAxisMinVal = props.valAxisMinVal;
909
+ if (props.valAxisMaxVal !== void 0) opts.valAxisMaxVal = props.valAxisMaxVal;
910
+ if (props.valAxisLabelFormatCode !== void 0) opts.valAxisLabelFormatCode = props.valAxisLabelFormatCode;
911
+ if (props.valAxisMajorUnit !== void 0) opts.valAxisMajorUnit = props.valAxisMajorUnit;
912
+ if (props.barDir !== void 0) opts.barDir = props.barDir;
913
+ if (props.barGrouping !== void 0) opts.barGrouping = props.barGrouping;
914
+ if (props.barGapWidthPct !== void 0) opts.barGapWidthPct = props.barGapWidthPct;
915
+ if (props.lineSmooth !== void 0) opts.lineSmooth = props.lineSmooth;
916
+ if (props.lineDataSymbol !== void 0) opts.lineDataSymbol = props.lineDataSymbol;
917
+ if (props.lineSize !== void 0) opts.lineSize = props.lineSize;
918
+ if (props.firstSliceAng !== void 0) opts.firstSliceAng = props.firstSliceAng;
919
+ if (props.holeSize !== void 0) opts.holeSize = props.holeSize;
920
+ if (props.radarStyle !== void 0) opts.radarStyle = props.radarStyle;
921
+ opts.dataLabelColor = props.dataLabelColor ? resolveColor(props.dataLabelColor, theme, warnings) : themeTextColor;
922
+ if (props.dataLabelFontSize !== void 0) opts.dataLabelFontSize = props.dataLabelFontSize;
923
+ if (props.dataLabelFontFace !== void 0) opts.dataLabelFontFace = props.dataLabelFontFace;
924
+ if (props.dataLabelFontBold !== void 0) opts.dataLabelFontBold = props.dataLabelFontBold;
925
+ if (props.dataLabelPosition !== void 0) opts.dataLabelPosition = props.dataLabelPosition;
926
+ slide.addChart(chartType, data, opts);
927
+ }
928
+
929
+ // src/components/index.ts
930
+ async function renderComponent(slide, component, theme, pptx, warnings, slideCtx) {
931
+ if (component.enabled === false) return;
932
+ const { name, props } = component;
933
+ const p = props;
934
+ switch (name) {
935
+ case "text":
936
+ renderTextComponent(slide, p, theme, warnings, slideCtx);
937
+ break;
938
+ case "image":
939
+ await renderImageComponent(slide, p, theme, warnings);
940
+ break;
941
+ case "shape":
942
+ renderShapeComponent(slide, p, theme, pptx, warnings);
943
+ break;
944
+ case "table":
945
+ renderTableComponent(slide, p, theme, pptx, warnings);
946
+ break;
947
+ case "highcharts":
948
+ await renderHighchartsComponent(slide, p, theme, warnings);
949
+ break;
950
+ case "chart":
951
+ renderChartComponent(slide, p, theme, pptx, warnings);
952
+ break;
953
+ default:
954
+ warn(warnings, W.UNKNOWN_COMPONENT, `Unknown PPTX component type: ${name}`, { component: name });
955
+ }
956
+ }
957
+
958
+ // src/core/template.ts
959
+ function buildSlideTemplateProps(def, theme, warnings) {
960
+ const result = { title: def.name };
961
+ if (def.background) {
962
+ if (def.background.color) {
963
+ result.background = { color: resolveColor(def.background.color, theme, warnings) };
964
+ } else if (def.background.image) {
965
+ if (def.background.image.path) {
966
+ result.background = { path: def.background.image.path };
967
+ } else if (def.background.image.base64) {
968
+ result.background = { data: def.background.image.base64 };
969
+ }
970
+ }
971
+ }
972
+ if (def.margin !== void 0) result.margin = def.margin;
973
+ if (def.slideNumber) {
974
+ result.slideNumber = {
975
+ x: def.slideNumber.x,
976
+ y: def.slideNumber.y
977
+ };
978
+ if (def.slideNumber.w !== void 0) result.slideNumber.w = def.slideNumber.w;
979
+ if (def.slideNumber.h !== void 0) result.slideNumber.h = def.slideNumber.h;
980
+ if (def.slideNumber.color) result.slideNumber.color = resolveColor(def.slideNumber.color, theme, warnings);
981
+ if (def.slideNumber.fontSize) result.slideNumber.fontSize = def.slideNumber.fontSize;
982
+ }
983
+ return result;
984
+ }
985
+
986
+ // src/core/render.ts
987
+ async function renderPresentation(processed, warnings) {
988
+ const pptx = new PptxGenJS();
989
+ if (processed.metadata.title) pptx.title = processed.metadata.title;
990
+ if (processed.metadata.author) pptx.author = processed.metadata.author;
991
+ if (processed.metadata.subject) pptx.subject = processed.metadata.subject;
992
+ if (processed.metadata.company) pptx.company = processed.metadata.company;
993
+ pptx.defineLayout({
994
+ name: "CUSTOM",
995
+ width: processed.slideWidth,
996
+ height: processed.slideHeight
997
+ });
998
+ pptx.layout = "CUSTOM";
999
+ if (processed.rtlMode) {
1000
+ pptx.rtlMode = true;
1001
+ }
1002
+ pptx.theme = {
1003
+ headFontFace: processed.theme.fonts.heading,
1004
+ bodyFontFace: processed.theme.fonts.body
1005
+ };
1006
+ const templateMap = new Map(processed.templates?.map((m) => [m.name, m]) ?? []);
1007
+ if (processed.templates) {
1008
+ for (const templateDef of processed.templates) {
1009
+ const templateProps = buildSlideTemplateProps(templateDef, processed.theme, warnings);
1010
+ pptx.defineSlideMaster(templateProps);
1011
+ }
1012
+ }
1013
+ const totalSlides = processed.slides.length;
1014
+ for (let slideIdx = 0; slideIdx < totalSlides; slideIdx++) {
1015
+ const slideData = processed.slides[slideIdx];
1016
+ const slideCtx = {
1017
+ slideNumber: slideIdx + 1,
1018
+ totalSlides,
1019
+ pageNumberFormat: processed.pageNumberFormat
1020
+ };
1021
+ const slide = slideData.template ? pptx.addSlide({ masterName: slideData.template }) : pptx.addSlide();
1022
+ if (slideData.background) {
1023
+ if (slideData.background.color) {
1024
+ slide.background = { color: resolveColor(slideData.background.color, processed.theme, warnings) };
1025
+ } else if (slideData.background.image) {
1026
+ if (slideData.background.image.path) {
1027
+ slide.background = { path: slideData.background.image.path };
1028
+ } else if (slideData.background.image.base64) {
1029
+ slide.background = { data: slideData.background.image.base64 };
1030
+ }
1031
+ }
1032
+ }
1033
+ if (slideData.hidden) {
1034
+ slide.hidden = true;
1035
+ }
1036
+ const templateDef = slideData.template ? templateMap.get(slideData.template) : void 0;
1037
+ if (slideData.template && !templateDef) {
1038
+ warn(warnings, W.MISSING_TEMPLATE, `Unknown template "${slideData.template}". Available: ${[...templateMap.keys()].join(", ")}`, { slide: slideIdx });
1039
+ }
1040
+ const effectiveGrid = mergeGridConfigs(processed.grid, templateDef?.grid);
1041
+ if (templateDef?.objects) {
1042
+ for (const obj of templateDef.objects) {
1043
+ await renderComponent(slide, obj, processed.theme, pptx, warnings, slideCtx);
1044
+ }
1045
+ }
1046
+ for (const component of slideData.components) {
1047
+ const resolved = resolveComponentGridPosition(
1048
+ component,
1049
+ effectiveGrid,
1050
+ processed.slideWidth,
1051
+ processed.slideHeight,
1052
+ warnings
1053
+ );
1054
+ await renderComponent(slide, resolved, processed.theme, pptx, warnings, slideCtx);
1055
+ }
1056
+ if (slideData.placeholders) {
1057
+ if (templateDef) {
1058
+ const phMap = new Map(templateDef.placeholders?.map((p) => [p.name, p]) ?? []);
1059
+ for (const [phName, component] of Object.entries(slideData.placeholders)) {
1060
+ const phDef = phMap.get(phName);
1061
+ if (!phDef) {
1062
+ warn(warnings, W.UNKNOWN_PLACEHOLDER, `Unknown placeholder "${phName}" in template "${slideData.template}". Available: ${[...phMap.keys()].join(", ")}`, { slide: slideIdx });
1063
+ continue;
1064
+ }
1065
+ const gridResolved = resolveComponentGridPosition(
1066
+ component,
1067
+ effectiveGrid,
1068
+ processed.slideWidth,
1069
+ processed.slideHeight,
1070
+ warnings
1071
+ );
1072
+ const posDefaults = {};
1073
+ if (phDef.x != null) posDefaults.x = phDef.x;
1074
+ if (phDef.y != null) posDefaults.y = phDef.y;
1075
+ if (phDef.w != null) posDefaults.w = phDef.w;
1076
+ if (phDef.h != null) posDefaults.h = phDef.h;
1077
+ const props = { ...posDefaults, ...phDef.defaults?.props ?? {}, ...gridResolved.props };
1078
+ await renderComponent(slide, { ...gridResolved, props }, processed.theme, pptx, warnings, slideCtx);
1079
+ }
1080
+ } else {
1081
+ for (const [phName, component] of Object.entries(slideData.placeholders)) {
1082
+ const hasPosition = component.props.x != null || component.props.y != null || component.props.grid;
1083
+ if (hasPosition) {
1084
+ const resolved = resolveComponentGridPosition(
1085
+ component,
1086
+ effectiveGrid,
1087
+ processed.slideWidth,
1088
+ processed.slideHeight,
1089
+ warnings
1090
+ );
1091
+ await renderComponent(slide, resolved, processed.theme, pptx, warnings, slideCtx);
1092
+ } else {
1093
+ warn(warnings, W.PLACEHOLDER_NO_POSITION, `Placeholder "${phName}" has no template and no explicit position \u2014 skipped`, { slide: slideIdx });
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ if (slideData.notes) {
1099
+ slide.addNotes(slideData.notes);
1100
+ }
1101
+ }
1102
+ return pptx;
1103
+ }
1104
+
1105
+ // src/core/generator.ts
1106
+ function isPresentationComponentDefinition(definition) {
1107
+ if (typeof definition !== "object" || definition === null) return false;
1108
+ const def = definition;
1109
+ return def.name === "pptx" && "props" in def;
1110
+ }
1111
+ async function generatePresentation(document, options, warnings) {
1112
+ if (!document || document.name !== "pptx") {
1113
+ throw new Error("Top-level component must be a pptx component");
1114
+ }
1115
+ const processed = processPresentation(document, options);
1116
+ return await renderPresentation(processed, warnings);
1117
+ }
1118
+ async function generateBufferFromJson(jsonConfig, options) {
1119
+ const result = await generateBufferWithWarnings(jsonConfig, options);
1120
+ return result.buffer;
1121
+ }
1122
+ async function generateBufferWithWarnings(jsonConfig, options) {
1123
+ let component;
1124
+ if (typeof jsonConfig === "string") {
1125
+ const parsed = JSON.parse(jsonConfig);
1126
+ if (!isPresentationComponent(parsed)) {
1127
+ throw new Error("Parsed JSON must be a presentation component");
1128
+ }
1129
+ component = parsed;
1130
+ } else {
1131
+ component = jsonConfig;
1132
+ }
1133
+ const warnings = [];
1134
+ const pptx = await generatePresentation(component, options, warnings);
1135
+ const data = await pptx.write({ outputType: "nodebuffer" });
1136
+ const buffer = await neutralizeTableStyle(data);
1137
+ return { buffer, warnings };
1138
+ }
1139
+ async function generateAndSaveFromJson(jsonConfig, outputPath, options) {
1140
+ const buffer = await generateBufferFromJson(jsonConfig, options);
1141
+ writeFileSync(outputPath, buffer);
1142
+ }
1143
+ async function generateFromFile(filePath, outputPath) {
1144
+ const { readFileSync } = await import("fs");
1145
+ const json = readFileSync(filePath, "utf-8");
1146
+ await generateAndSaveFromJson(json, outputPath);
1147
+ }
1148
+ var MEDIUM_STYLE_2_ACCENT_1 = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
1149
+ var NO_STYLE_NO_GRID = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}";
1150
+ async function neutralizeTableStyle(buffer) {
1151
+ const zip = await JSZip.loadAsync(buffer);
1152
+ let changed = false;
1153
+ for (const [path2, entry] of Object.entries(zip.files)) {
1154
+ if (!path2.match(/^ppt\/slides\/slide\d+\.xml$/)) continue;
1155
+ const xml = await entry.async("string");
1156
+ if (xml.includes(MEDIUM_STYLE_2_ACCENT_1)) {
1157
+ zip.file(path2, xml.replaceAll(MEDIUM_STYLE_2_ACCENT_1, NO_STYLE_NO_GRID));
1158
+ changed = true;
1159
+ }
1160
+ }
1161
+ return changed ? await zip.generateAsync({ type: "nodebuffer" }) : buffer;
1162
+ }
1163
+ async function savePresentation(pptx, outputPath) {
1164
+ const data = await pptx.write({ outputType: "nodebuffer" });
1165
+ const buffer = await neutralizeTableStyle(data);
1166
+ writeFileSync(outputPath, buffer);
1167
+ }
1168
+ var PresentationGenerator = {
1169
+ generate: generatePresentation,
1170
+ generateBufferFromJson,
1171
+ generateBufferWithWarnings,
1172
+ generateAndSaveFromJson,
1173
+ generateFromFile,
1174
+ save: savePresentation,
1175
+ isPresentationComponentDefinition
1176
+ };
1177
+
1178
+ // src/index.ts
1179
+ function getPptxCoreVersion() {
1180
+ return "PptxCore v1.0.0";
1181
+ }
1182
+ export {
1183
+ DEFAULT_PPTX_THEME,
1184
+ PresentationGenerator,
1185
+ W as WarningCodes,
1186
+ generateAndSaveFromJson,
1187
+ generateBufferFromJson,
1188
+ generateBufferWithWarnings,
1189
+ generateFromFile,
1190
+ generatePresentation,
1191
+ getPptxCoreVersion,
1192
+ getPptxTheme,
1193
+ isPresentationComponent,
1194
+ isPresentationComponentDefinition,
1195
+ isSlideComponent,
1196
+ pptxThemes,
1197
+ renderComponent,
1198
+ renderHighchartsComponent,
1199
+ renderImageComponent,
1200
+ renderShapeComponent,
1201
+ renderTableComponent,
1202
+ renderTextComponent,
1203
+ savePresentation
1204
+ };
1205
+ //# sourceMappingURL=index.js.map