@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.
- package/LICENSE +18 -0
- package/README.md +9 -0
- package/dist/components/chart.d.ts +63 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/highcharts.d.ts +8 -0
- package/dist/components/highcharts.d.ts.map +1 -0
- package/dist/components/image.d.ts +37 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/shape.d.ts +45 -0
- package/dist/components/shape.d.ts.map +1 -0
- package/dist/components/table.d.ts +46 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/text.d.ts +57 -0
- package/dist/components/text.d.ts.map +1 -0
- package/dist/core/generator.d.ts +61 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/grid.d.ts +35 -0
- package/dist/core/grid.d.ts.map +1 -0
- package/dist/core/render.d.ts +8 -0
- package/dist/core/render.d.ts.map +1 -0
- package/dist/core/structure.d.ts +8 -0
- package/dist/core/structure.d.ts.map +1 -0
- package/dist/core/template.d.ts +10 -0
- package/dist/core/template.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1205 -0
- package/dist/index.js.map +1 -0
- package/dist/themes/defaults.d.ts +8 -0
- package/dist/themes/defaults.d.ts.map +1 -0
- package/dist/themes/index.d.ts +2 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/color.d.ts +12 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/environment.d.ts +8 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/warn.d.ts +20 -0
- package/dist/utils/warn.d.ts.map +1 -0
- 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
|