@8btc/ppt-generator-mcp 0.0.1

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 (39) hide show
  1. package/README.md +129 -0
  2. package/dist/export-pptx.d.ts +6 -0
  3. package/dist/export-pptx.js +876 -0
  4. package/dist/generate-ppt-slides.d.ts +10 -0
  5. package/dist/generate-ppt-slides.js +581 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +161 -0
  8. package/dist/ppt-generator.d.ts +39 -0
  9. package/dist/ppt-generator.js +110 -0
  10. package/dist/tpls/imgs.json +482 -0
  11. package/dist/tpls/tpl-1.json +7649 -0
  12. package/dist/tpls/tpl-2.json +7455 -0
  13. package/dist/tpls/tpl-3.json +8184 -0
  14. package/dist/tpls/tpl-4.json +8352 -0
  15. package/dist/types/outline.d.ts +36 -0
  16. package/dist/types/outline.js +2 -0
  17. package/dist/types/slide.d.ts +696 -0
  18. package/dist/types/slide.js +2 -0
  19. package/dist/utils/element.d.ts +94 -0
  20. package/dist/utils/element.js +239 -0
  21. package/dist/utils/htmlParser/format.d.ts +3 -0
  22. package/dist/utils/htmlParser/format.js +47 -0
  23. package/dist/utils/htmlParser/index.d.ts +4 -0
  24. package/dist/utils/htmlParser/index.js +15 -0
  25. package/dist/utils/htmlParser/lexer.d.ts +2 -0
  26. package/dist/utils/htmlParser/lexer.js +245 -0
  27. package/dist/utils/htmlParser/parser.d.ts +15 -0
  28. package/dist/utils/htmlParser/parser.js +117 -0
  29. package/dist/utils/htmlParser/stringify.d.ts +3 -0
  30. package/dist/utils/htmlParser/stringify.js +32 -0
  31. package/dist/utils/htmlParser/tags.d.ts +8 -0
  32. package/dist/utils/htmlParser/tags.js +50 -0
  33. package/dist/utils/htmlParser/types.d.ts +55 -0
  34. package/dist/utils/htmlParser/types.js +2 -0
  35. package/dist/utils/svg2Base64.d.ts +1 -0
  36. package/dist/utils/svg2Base64.js +58 -0
  37. package/dist/utils/svgPathParser.d.ts +120 -0
  38. package/dist/utils/svgPathParser.js +145 -0
  39. package/package.json +59 -0
@@ -0,0 +1,876 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.exportPPTX = void 0;
7
+ const lodash_1 = require("lodash");
8
+ const pptxgenjs_1 = __importDefault(require("pptxgenjs"));
9
+ const tinycolor2_1 = __importDefault(require("tinycolor2"));
10
+ const element_1 = require("./utils/element");
11
+ const htmlParser_1 = require("./utils/htmlParser");
12
+ const svgPathParser_1 = require("./utils/svgPathParser");
13
+ const svg2Base64_1 = require("./utils/svg2Base64");
14
+ const defaultFontSize = 16;
15
+ // 导出PPTX文件
16
+ const exportPPTX = (_slides, ignoreMedia, options) => {
17
+ const { viewportRatio, viewportSize } = options;
18
+ const pptx = new pptxgenjs_1.default();
19
+ if (viewportRatio === 0.625)
20
+ pptx.layout = "LAYOUT_16x10";
21
+ else if (viewportRatio === 0.75)
22
+ pptx.layout = "LAYOUT_4x3";
23
+ else if (viewportRatio === 0.70710678) {
24
+ pptx.defineLayout({ name: "A3", width: 10, height: 7.0710678 });
25
+ pptx.layout = "A3";
26
+ }
27
+ else if (viewportRatio === 1.41421356) {
28
+ pptx.defineLayout({ name: "A3_V", width: 10, height: 14.1421356 });
29
+ pptx.layout = "A3_V";
30
+ }
31
+ else
32
+ pptx.layout = "LAYOUT_16x9";
33
+ for (const slide of _slides) {
34
+ const pptxSlide = pptx.addSlide();
35
+ if (slide.background) {
36
+ const background = slide.background;
37
+ if (background.type === "image" && background.image) {
38
+ if (isSVGImage(background.image.src)) {
39
+ pptxSlide.addImage({
40
+ data: background.image.src,
41
+ x: 0,
42
+ y: 0,
43
+ w: viewportSize / ratioPx2Inch(viewportSize),
44
+ h: (viewportSize * viewportRatio) / ratioPx2Inch(viewportSize),
45
+ });
46
+ }
47
+ else if (isBase64Image(background.image.src)) {
48
+ pptxSlide.background = { data: background.image.src };
49
+ }
50
+ else {
51
+ pptxSlide.background = { path: background.image.src };
52
+ }
53
+ }
54
+ else if (background.type === "solid" && background.color) {
55
+ const c = formatColor(background.color);
56
+ pptxSlide.background = {
57
+ color: c.color,
58
+ transparency: (1 - c.alpha) * 100,
59
+ };
60
+ }
61
+ else if (background.type === "gradient" && background.gradient) {
62
+ const colors = background.gradient.colors;
63
+ const color1 = colors[0].color;
64
+ const color2 = colors[colors.length - 1].color;
65
+ const color = tinycolor2_1.default.mix(color1, color2).toHexString();
66
+ const c = formatColor(color);
67
+ pptxSlide.background = {
68
+ color: c.color,
69
+ transparency: (1 - c.alpha) * 100,
70
+ };
71
+ }
72
+ }
73
+ if (slide.remark) {
74
+ const doc = new DOMParser().parseFromString(slide.remark, "text/html");
75
+ const pList = doc.body.querySelectorAll("p");
76
+ const text = [];
77
+ for (const p of pList) {
78
+ const textContent = p.textContent;
79
+ text.push(textContent || "");
80
+ }
81
+ pptxSlide.addNotes(text.join("\n"));
82
+ }
83
+ if (!slide.elements)
84
+ continue;
85
+ for (const el of slide.elements) {
86
+ if (el.type === "text") {
87
+ const textProps = formatHTML(el.content, viewportSize);
88
+ const options = {
89
+ x: el.left / ratioPx2Inch(viewportSize),
90
+ y: el.top / ratioPx2Inch(viewportSize),
91
+ w: el.width / ratioPx2Inch(viewportSize),
92
+ h: el.height / ratioPx2Inch(viewportSize),
93
+ fontSize: defaultFontSize / ratioPx2Pt(viewportSize),
94
+ fontFace: "PingFang SC",
95
+ color: "#000000",
96
+ valign: "top",
97
+ margin: 10 / ratioPx2Pt(viewportSize),
98
+ paraSpaceBefore: 5 / ratioPx2Pt(viewportSize),
99
+ lineSpacingMultiple: 1.5 / 1.25,
100
+ autoFit: true,
101
+ };
102
+ if (el.rotate)
103
+ options.rotate = el.rotate;
104
+ if (el.wordSpace)
105
+ options.charSpacing = el.wordSpace / ratioPx2Pt(viewportSize);
106
+ if (el.lineHeight)
107
+ options.lineSpacingMultiple = el.lineHeight / 1.25;
108
+ if (el.fill) {
109
+ const c = formatColor(el.fill);
110
+ const opacity = el.opacity === undefined ? 1 : el.opacity;
111
+ options.fill = {
112
+ color: c.color,
113
+ transparency: (1 - c.alpha * opacity) * 100,
114
+ };
115
+ }
116
+ if (el.defaultColor)
117
+ options.color = formatColor(el.defaultColor).color;
118
+ if (el.defaultFontName)
119
+ options.fontFace = el.defaultFontName;
120
+ if (el.shadow)
121
+ options.shadow = getShadowOption(el.shadow, viewportSize);
122
+ if (el.outline?.width)
123
+ options.line = getOutlineOption(el.outline, viewportSize);
124
+ if (el.opacity !== undefined)
125
+ options.transparency = (1 - el.opacity) * 100;
126
+ if (el.paragraphSpace !== undefined)
127
+ options.paraSpaceBefore =
128
+ el.paragraphSpace / ratioPx2Pt(viewportSize);
129
+ if (el.vertical)
130
+ options.vert = "eaVert";
131
+ pptxSlide.addText(textProps, options);
132
+ }
133
+ else if (el.type === "image") {
134
+ const options = {
135
+ x: el.left / ratioPx2Inch(viewportSize),
136
+ y: el.top / ratioPx2Inch(viewportSize),
137
+ w: el.width / ratioPx2Inch(viewportSize),
138
+ h: el.height / ratioPx2Inch(viewportSize),
139
+ };
140
+ if (isBase64Image(el.src))
141
+ options.data = el.src;
142
+ else
143
+ options.path = el.src;
144
+ if (el.flipH)
145
+ options.flipH = el.flipH;
146
+ if (el.flipV)
147
+ options.flipV = el.flipV;
148
+ if (el.rotate)
149
+ options.rotate = el.rotate;
150
+ if (el.link) {
151
+ const linkOption = getLinkOption(el.link, _slides);
152
+ if (linkOption)
153
+ options.hyperlink = linkOption;
154
+ }
155
+ if (el.filters?.opacity)
156
+ options.transparency = 100 - parseInt(el.filters?.opacity);
157
+ if (el.clip) {
158
+ if (el.clip.shape === "ellipse")
159
+ options.rounding = true;
160
+ const [start, end] = el.clip.range;
161
+ const [startX, startY] = start;
162
+ const [endX, endY] = end;
163
+ const originW = el.width / ((endX - startX) / ratioPx2Inch(viewportSize));
164
+ const originH = el.height / ((endY - startY) / ratioPx2Inch(viewportSize));
165
+ options.w = originW / ratioPx2Inch(viewportSize);
166
+ options.h = originH / ratioPx2Inch(viewportSize);
167
+ options.sizing = {
168
+ type: "crop",
169
+ x: ((startX / ratioPx2Inch(viewportSize)) * originW) /
170
+ ratioPx2Inch(viewportSize),
171
+ y: ((startY / ratioPx2Inch(viewportSize)) * originH) /
172
+ ratioPx2Inch(viewportSize),
173
+ w: (((endX - startX) / ratioPx2Inch(viewportSize)) * originW) /
174
+ ratioPx2Inch(viewportSize),
175
+ h: (((endY - startY) / ratioPx2Inch(viewportSize)) * originH) /
176
+ ratioPx2Inch(viewportSize),
177
+ };
178
+ }
179
+ pptxSlide.addImage(options);
180
+ }
181
+ else if (el.type === "shape") {
182
+ // if (el.special) {
183
+ // const svgRef = document.querySelector(
184
+ // `.thumbnail-list .base-element-${el.id} svg`
185
+ // ) as HTMLElement;
186
+ // if (svgRef.clientWidth < 1 || svgRef.clientHeight < 1) continue; // 临时处理(导入PPTX文件带来的异常数据)
187
+ // const base64SVG = svg2Base64(svgRef);
188
+ // const options: pptxgen.ImageProps = {
189
+ // data: base64SVG,
190
+ // x: el.left / ratioPx2Inch(viewportSize),
191
+ // y: el.top / ratioPx2Inch(viewportSize),
192
+ // w: el.width / ratioPx2Inch(viewportSize),
193
+ // h: el.height / ratioPx2Inch(viewportSize),
194
+ // };
195
+ // if (el.rotate) options.rotate = el.rotate;
196
+ // if (el.flipH) options.flipH = el.flipH;
197
+ // if (el.flipV) options.flipV = el.flipV;
198
+ // if (el.link) {
199
+ // const linkOption = getLinkOption(el.link, _slides);
200
+ // if (linkOption) options.hyperlink = linkOption;
201
+ // }
202
+ // pptxSlide.addImage(options);
203
+ // } else
204
+ {
205
+ const scale = {
206
+ x: el.width / el.viewBox[0],
207
+ y: el.height / el.viewBox[1],
208
+ };
209
+ const points = formatPoints((0, svgPathParser_1.toPoints)(el.path), scale, viewportSize);
210
+ let fillColor = formatColor(el.fill);
211
+ if (el.gradient) {
212
+ const colors = el.gradient.colors;
213
+ const color1 = colors[0].color;
214
+ const color2 = colors[colors.length - 1].color;
215
+ const color = tinycolor2_1.default.mix(color1, color2).toHexString();
216
+ fillColor = formatColor(color);
217
+ }
218
+ if (el.pattern)
219
+ fillColor = formatColor("#00000000");
220
+ const opacity = el.opacity === undefined ? 1 : el.opacity;
221
+ const options = {
222
+ x: el.left / ratioPx2Inch(viewportSize),
223
+ y: el.top / ratioPx2Inch(viewportSize),
224
+ w: el.width / ratioPx2Inch(viewportSize),
225
+ h: el.height / ratioPx2Inch(viewportSize),
226
+ fill: {
227
+ color: fillColor.color,
228
+ transparency: (1 - fillColor.alpha * opacity) * 100,
229
+ },
230
+ points,
231
+ };
232
+ if (el.flipH)
233
+ options.flipH = el.flipH;
234
+ if (el.flipV)
235
+ options.flipV = el.flipV;
236
+ if (el.shadow)
237
+ options.shadow = getShadowOption(el.shadow, viewportSize);
238
+ if (el.outline?.width)
239
+ options.line = getOutlineOption(el.outline, viewportSize);
240
+ if (el.rotate)
241
+ options.rotate = el.rotate;
242
+ if (el.link) {
243
+ const linkOption = getLinkOption(el.link, _slides);
244
+ if (linkOption)
245
+ options.hyperlink = linkOption;
246
+ }
247
+ pptxSlide.addShape("custGeom", options);
248
+ }
249
+ if (el.text) {
250
+ const textProps = formatHTML(el.text.content, viewportSize);
251
+ const options = {
252
+ x: el.left / ratioPx2Inch(viewportSize),
253
+ y: el.top / ratioPx2Inch(viewportSize),
254
+ w: el.width / ratioPx2Inch(viewportSize),
255
+ h: el.height / ratioPx2Inch(viewportSize),
256
+ fontSize: defaultFontSize / ratioPx2Pt(viewportSize),
257
+ fontFace: "PingFang SC",
258
+ color: "#000000",
259
+ paraSpaceBefore: 5 / ratioPx2Pt(viewportSize),
260
+ valign: el.text.align,
261
+ };
262
+ if (el.rotate)
263
+ options.rotate = el.rotate;
264
+ if (el.text.defaultColor)
265
+ options.color = formatColor(el.text.defaultColor).color;
266
+ if (el.text.defaultFontName)
267
+ options.fontFace = el.text.defaultFontName;
268
+ pptxSlide.addText(textProps, options);
269
+ }
270
+ if (el.pattern) {
271
+ const options = {
272
+ x: el.left / ratioPx2Inch(viewportSize),
273
+ y: el.top / ratioPx2Inch(viewportSize),
274
+ w: el.width / ratioPx2Inch(viewportSize),
275
+ h: el.height / ratioPx2Inch(viewportSize),
276
+ };
277
+ if (isBase64Image(el.pattern))
278
+ options.data = el.pattern;
279
+ else
280
+ options.path = el.pattern;
281
+ if (el.flipH)
282
+ options.flipH = el.flipH;
283
+ if (el.flipV)
284
+ options.flipV = el.flipV;
285
+ if (el.rotate)
286
+ options.rotate = el.rotate;
287
+ if (el.link) {
288
+ const linkOption = getLinkOption(el.link, _slides);
289
+ if (linkOption)
290
+ options.hyperlink = linkOption;
291
+ }
292
+ pptxSlide.addImage(options);
293
+ }
294
+ }
295
+ else if (el.type === "line") {
296
+ const path = (0, element_1.getLineElementPath)(el);
297
+ const points = formatPoints((0, svgPathParser_1.toPoints)(path), { x: 1, y: 1 }, viewportSize);
298
+ const { minX, maxX, minY, maxY } = (0, element_1.getElementRange)(el);
299
+ const c = formatColor(el.color);
300
+ const options = {
301
+ x: el.left / ratioPx2Inch(viewportSize),
302
+ y: el.top / ratioPx2Inch(viewportSize),
303
+ w: (maxX - minX) / ratioPx2Inch(viewportSize),
304
+ h: (maxY - minY) / ratioPx2Inch(viewportSize),
305
+ line: {
306
+ color: c.color,
307
+ transparency: (1 - c.alpha) * 100,
308
+ width: el.width / ratioPx2Pt(viewportSize),
309
+ dashType: dashTypeMap[el.style],
310
+ beginArrowType: el.points[0] ? "arrow" : "none",
311
+ endArrowType: el.points[1] ? "arrow" : "none",
312
+ },
313
+ points,
314
+ };
315
+ if (el.shadow)
316
+ options.shadow = getShadowOption(el.shadow, viewportSize);
317
+ pptxSlide.addShape("custGeom", options);
318
+ }
319
+ else if (el.type === "chart") {
320
+ const chartData = [];
321
+ for (let i = 0; i < el.data.series.length; i++) {
322
+ const item = el.data.series[i];
323
+ chartData.push({
324
+ name: `系列${i + 1}`,
325
+ labels: el.data.labels,
326
+ values: item,
327
+ });
328
+ }
329
+ let chartColors = [];
330
+ if (el.themeColors.length === 10)
331
+ chartColors = el.themeColors.map((color) => formatColor(color).color);
332
+ else if (el.themeColors.length === 1)
333
+ chartColors = (0, tinycolor2_1.default)(el.themeColors[0])
334
+ .analogous(10)
335
+ .map((color) => formatColor(color.toHexString()).color);
336
+ else {
337
+ const len = el.themeColors.length;
338
+ const supplement = (0, tinycolor2_1.default)(el.themeColors[len - 1])
339
+ .analogous(10 + 1 - len)
340
+ .map((color) => color.toHexString());
341
+ chartColors = [
342
+ ...el.themeColors.slice(0, len - 1),
343
+ ...supplement,
344
+ ].map((color) => formatColor(color).color);
345
+ }
346
+ const options = {
347
+ x: el.left / ratioPx2Inch(viewportSize),
348
+ y: el.top / ratioPx2Inch(viewportSize),
349
+ w: el.width / ratioPx2Inch(viewportSize),
350
+ h: el.height / ratioPx2Inch(viewportSize),
351
+ chartColors: el.chartType === "pie" || el.chartType === "ring"
352
+ ? chartColors
353
+ : chartColors.slice(0, el.data.series.length),
354
+ };
355
+ const textColor = formatColor(el.textColor || "#000000").color;
356
+ options.catAxisLabelColor = textColor;
357
+ options.valAxisLabelColor = textColor;
358
+ const fontSize = 14 / ratioPx2Pt(viewportSize);
359
+ options.catAxisLabelFontSize = fontSize;
360
+ options.valAxisLabelFontSize = fontSize;
361
+ if (el.fill || el.outline) {
362
+ const plotArea = {};
363
+ if (el.fill) {
364
+ plotArea.fill = { color: formatColor(el.fill).color };
365
+ }
366
+ if (el.outline) {
367
+ plotArea.border = {
368
+ pt: el.outline.width / ratioPx2Pt(viewportSize),
369
+ color: formatColor(el.outline.color).color,
370
+ };
371
+ }
372
+ options.plotArea = plotArea;
373
+ }
374
+ if ((el.data.series.length > 1 && el.chartType !== "scatter") ||
375
+ el.chartType === "pie" ||
376
+ el.chartType === "ring") {
377
+ options.showLegend = true;
378
+ options.legendPos = "b";
379
+ options.legendColor = textColor;
380
+ options.legendFontSize = fontSize;
381
+ }
382
+ let type = pptx.ChartType.bar;
383
+ if (el.chartType === "bar") {
384
+ type = pptx.ChartType.bar;
385
+ options.barDir = "col";
386
+ if (el.options?.stack)
387
+ options.barGrouping = "stacked";
388
+ }
389
+ else if (el.chartType === "column") {
390
+ type = pptx.ChartType.bar;
391
+ options.barDir = "bar";
392
+ if (el.options?.stack)
393
+ options.barGrouping = "stacked";
394
+ }
395
+ else if (el.chartType === "line") {
396
+ type = pptx.ChartType.line;
397
+ if (el.options?.lineSmooth)
398
+ options.lineSmooth = true;
399
+ }
400
+ else if (el.chartType === "area") {
401
+ type = pptx.ChartType.area;
402
+ }
403
+ else if (el.chartType === "radar") {
404
+ type = pptx.ChartType.radar;
405
+ }
406
+ else if (el.chartType === "scatter") {
407
+ type = pptx.ChartType.scatter;
408
+ options.lineSize = 0;
409
+ }
410
+ else if (el.chartType === "pie") {
411
+ type = pptx.ChartType.pie;
412
+ }
413
+ else if (el.chartType === "ring") {
414
+ type = pptx.ChartType.doughnut;
415
+ options.holeSize = 60;
416
+ }
417
+ pptxSlide.addChart(type, chartData, options);
418
+ }
419
+ else if (el.type === "table") {
420
+ const hiddenCells = [];
421
+ for (let i = 0; i < el.data.length; i++) {
422
+ const rowData = el.data[i];
423
+ for (let j = 0; j < rowData.length; j++) {
424
+ const cell = rowData[j];
425
+ if (cell.colspan > 1 || cell.rowspan > 1) {
426
+ for (let row = i; row < i + cell.rowspan; row++) {
427
+ for (let col = row === i ? j + 1 : j; col < j + cell.colspan; col++)
428
+ hiddenCells.push(`${row}_${col}`);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ const tableData = [];
434
+ const theme = el.theme;
435
+ let themeColor = null;
436
+ let subThemeColors = [];
437
+ if (theme) {
438
+ themeColor = formatColor(theme.color);
439
+ subThemeColors = (0, element_1.getTableSubThemeColor)(theme.color).map((item) => formatColor(item));
440
+ }
441
+ for (let i = 0; i < el.data.length; i++) {
442
+ const row = el.data[i];
443
+ const _row = [];
444
+ for (let j = 0; j < row.length; j++) {
445
+ const cell = row[j];
446
+ const cellOptions = {
447
+ colspan: cell.colspan,
448
+ rowspan: cell.rowspan,
449
+ bold: cell.style?.bold || false,
450
+ italic: cell.style?.em || false,
451
+ underline: { style: cell.style?.underline ? "sng" : "none" },
452
+ align: cell.style?.align || "left",
453
+ valign: "middle",
454
+ fontFace: cell.style?.fontname || "PingFang SC",
455
+ fontSize: (cell.style?.fontsize ? parseInt(cell.style?.fontsize) : 14) /
456
+ ratioPx2Pt(viewportSize),
457
+ };
458
+ if (theme && themeColor) {
459
+ let c;
460
+ if (i % 2 === 0)
461
+ c = subThemeColors[1];
462
+ else
463
+ c = subThemeColors[0];
464
+ if (theme.rowHeader && i === 0)
465
+ c = themeColor;
466
+ else if (theme.rowFooter && i === el.data.length - 1)
467
+ c = themeColor;
468
+ else if (theme.colHeader && j === 0)
469
+ c = themeColor;
470
+ else if (theme.colFooter && j === row.length - 1)
471
+ c = themeColor;
472
+ cellOptions.fill = {
473
+ color: c.color,
474
+ transparency: (1 - c.alpha) * 100,
475
+ };
476
+ }
477
+ if (cell.style?.backcolor) {
478
+ const c = formatColor(cell.style.backcolor);
479
+ cellOptions.fill = {
480
+ color: c.color,
481
+ transparency: (1 - c.alpha) * 100,
482
+ };
483
+ }
484
+ if (cell.style?.color)
485
+ cellOptions.color = formatColor(cell.style.color).color;
486
+ if (!hiddenCells.includes(`${i}_${j}`)) {
487
+ _row.push({
488
+ text: cell.text,
489
+ options: cellOptions,
490
+ });
491
+ }
492
+ }
493
+ if (_row.length)
494
+ tableData.push(_row);
495
+ }
496
+ const options = {
497
+ x: el.left / ratioPx2Inch(viewportSize),
498
+ y: el.top / ratioPx2Inch(viewportSize),
499
+ w: el.width / ratioPx2Inch(viewportSize),
500
+ h: el.height / ratioPx2Inch(viewportSize),
501
+ colW: el.colWidths.map((item) => (el.width * item) / ratioPx2Inch(viewportSize)),
502
+ };
503
+ if (el.theme)
504
+ options.fill = { color: "#ffffff" };
505
+ if (el.outline.width && el.outline.color) {
506
+ options.border = {
507
+ type: el.outline.style === "solid" ? "solid" : "dash",
508
+ pt: el.outline.width / ratioPx2Pt(viewportSize),
509
+ color: formatColor(el.outline.color).color,
510
+ };
511
+ }
512
+ pptxSlide.addTable(tableData, options);
513
+ }
514
+ else if (el.type === "latex") {
515
+ const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`);
516
+ const base64SVG = (0, svg2Base64_1.svg2Base64)(svgRef);
517
+ const options = {
518
+ data: base64SVG,
519
+ x: el.left / ratioPx2Inch(viewportSize),
520
+ y: el.top / ratioPx2Inch(viewportSize),
521
+ w: el.width / ratioPx2Inch(viewportSize),
522
+ h: el.height / ratioPx2Inch(viewportSize),
523
+ };
524
+ if (el.link) {
525
+ const linkOption = getLinkOption(el.link, _slides);
526
+ if (linkOption)
527
+ options.hyperlink = linkOption;
528
+ }
529
+ pptxSlide.addImage(options);
530
+ }
531
+ else if (!ignoreMedia && (el.type === "video" || el.type === "audio")) {
532
+ const options = {
533
+ x: el.left / ratioPx2Inch(viewportSize),
534
+ y: el.top / ratioPx2Inch(viewportSize),
535
+ w: el.width / ratioPx2Inch(viewportSize),
536
+ h: el.height / ratioPx2Inch(viewportSize),
537
+ path: el.src,
538
+ type: el.type,
539
+ };
540
+ if (el.type === "video" && el.poster)
541
+ options.cover = el.poster;
542
+ const extMatch = el.src.match(/\.([a-zA-Z0-9]+)(?:[\?#]|$)/);
543
+ if (extMatch && extMatch[1])
544
+ options.extn = extMatch[1];
545
+ else if (el.ext)
546
+ options.extn = el.ext;
547
+ const videoExts = ["avi", "mp4", "m4v", "mov", "wmv"];
548
+ const audioExts = ["mp3", "m4a", "mp4", "wav", "wma"];
549
+ if (options.extn &&
550
+ [...videoExts, ...audioExts].includes(options.extn)) {
551
+ pptxSlide.addMedia(options);
552
+ }
553
+ }
554
+ }
555
+ }
556
+ return pptx;
557
+ };
558
+ exports.exportPPTX = exportPPTX;
559
+ const ratioPx2Inch = (viewportSize) => {
560
+ return 96 * (viewportSize / 960);
561
+ };
562
+ const ratioPx2Pt = (viewportSize) => {
563
+ return (96 / 72) * (viewportSize / 960);
564
+ };
565
+ // 格式化颜色值为 透明度 + HexString,供pptxgenjs使用
566
+ const formatColor = (_color) => {
567
+ if (!_color) {
568
+ return {
569
+ alpha: 0,
570
+ color: "#000000",
571
+ };
572
+ }
573
+ const c = (0, tinycolor2_1.default)(_color);
574
+ const alpha = c.getAlpha();
575
+ const color = alpha === 0 ? "#ffffff" : c.setAlpha(1).toHexString();
576
+ return {
577
+ alpha,
578
+ color,
579
+ };
580
+ };
581
+ // 将HTML字符串格式化为pptxgenjs所需的格式
582
+ // 核心思路:将HTML字符串按样式分片平铺,每个片段需要继承祖先元素的样式信息,遇到块级元素需要换行
583
+ const formatHTML = (html, viewportSize) => {
584
+ const ast = (0, htmlParser_1.toAST)(html);
585
+ let bulletFlag = false;
586
+ let indent = 0;
587
+ const slices = [];
588
+ const parse = (obj, baseStyleObj = {}) => {
589
+ for (const item of obj) {
590
+ const isBlockTag = "tagName" in item && ["div", "li", "p"].includes(item.tagName);
591
+ if (isBlockTag && slices.length) {
592
+ const lastSlice = slices[slices.length - 1];
593
+ if (!lastSlice.options)
594
+ lastSlice.options = {};
595
+ lastSlice.options.breakLine = true;
596
+ }
597
+ const styleObj = { ...baseStyleObj };
598
+ const styleAttr = "attributes" in item
599
+ ? item.attributes.find((attr) => attr.key === "style")
600
+ : null;
601
+ if (styleAttr && styleAttr.value) {
602
+ const styleArr = styleAttr.value.split(";");
603
+ for (const styleItem of styleArr) {
604
+ const [_key, _value] = styleItem.split(": ");
605
+ const [key, value] = [(0, lodash_1.trim)(_key), (0, lodash_1.trim)(_value)];
606
+ if (key && value)
607
+ styleObj[key] = value;
608
+ }
609
+ }
610
+ if ("tagName" in item) {
611
+ if (item.tagName === "em") {
612
+ styleObj["font-style"] = "italic";
613
+ }
614
+ if (item.tagName === "strong") {
615
+ styleObj["font-weight"] = "bold";
616
+ }
617
+ if (item.tagName === "sup") {
618
+ styleObj["vertical-align"] = "super";
619
+ }
620
+ if (item.tagName === "sub") {
621
+ styleObj["vertical-align"] = "sub";
622
+ }
623
+ if (item.tagName === "a") {
624
+ const attr = item.attributes.find((attr) => attr.key === "href");
625
+ styleObj["href"] = attr?.value || "";
626
+ }
627
+ if (item.tagName === "ul") {
628
+ styleObj["list-type"] = "ul";
629
+ }
630
+ if (item.tagName === "ol") {
631
+ styleObj["list-type"] = "ol";
632
+ }
633
+ if (item.tagName === "li") {
634
+ bulletFlag = true;
635
+ }
636
+ if (item.tagName === "p") {
637
+ if ("attributes" in item) {
638
+ const dataIndentAttr = item.attributes.find((attr) => attr.key === "data-indent");
639
+ if (dataIndentAttr && dataIndentAttr.value)
640
+ indent = +dataIndentAttr.value;
641
+ }
642
+ }
643
+ }
644
+ if ("tagName" in item && item.tagName === "br") {
645
+ slices.push({ text: "", options: { breakLine: true } });
646
+ }
647
+ else if ("content" in item) {
648
+ const text = item.content
649
+ .replace(/&nbsp;/g, " ")
650
+ .replace(/&gt;/g, ">")
651
+ .replace(/&lt;/g, "<")
652
+ .replace(/&amp;/g, "&")
653
+ .replace(/\n/g, "");
654
+ const options = {};
655
+ if (styleObj["font-size"]) {
656
+ options.fontSize =
657
+ parseInt(styleObj["font-size"]) / ratioPx2Pt(viewportSize);
658
+ }
659
+ if (styleObj["color"]) {
660
+ options.color = formatColor(styleObj["color"]).color;
661
+ }
662
+ if (styleObj["background-color"]) {
663
+ options.highlight = formatColor(styleObj["background-color"]).color;
664
+ }
665
+ if (styleObj["text-decoration-line"]) {
666
+ if (styleObj["text-decoration-line"].indexOf("underline") !== -1) {
667
+ options.underline = {
668
+ color: options.color || "#000000",
669
+ style: "sng",
670
+ };
671
+ }
672
+ if (styleObj["text-decoration-line"].indexOf("line-through") !== -1) {
673
+ options.strike = "sngStrike";
674
+ }
675
+ }
676
+ if (styleObj["text-decoration"]) {
677
+ if (styleObj["text-decoration"].indexOf("underline") !== -1) {
678
+ options.underline = {
679
+ color: options.color || "#000000",
680
+ style: "sng",
681
+ };
682
+ }
683
+ if (styleObj["text-decoration"].indexOf("line-through") !== -1) {
684
+ options.strike = "sngStrike";
685
+ }
686
+ }
687
+ if (styleObj["vertical-align"]) {
688
+ if (styleObj["vertical-align"] === "super")
689
+ options.superscript = true;
690
+ if (styleObj["vertical-align"] === "sub")
691
+ options.subscript = true;
692
+ }
693
+ if (styleObj["text-align"])
694
+ options.align = styleObj["text-align"];
695
+ if (styleObj["font-weight"])
696
+ options.bold = styleObj["font-weight"] === "bold";
697
+ if (styleObj["font-style"])
698
+ options.italic = styleObj["font-style"] === "italic";
699
+ if (styleObj["font-family"])
700
+ options.fontFace = styleObj["font-family"];
701
+ if (styleObj["href"])
702
+ options.hyperlink = { url: styleObj["href"] };
703
+ if (bulletFlag && styleObj["list-type"] === "ol") {
704
+ options.bullet = {
705
+ type: "number",
706
+ indent: (options.fontSize || defaultFontSize) * 1.25,
707
+ };
708
+ options.paraSpaceBefore = 0.1;
709
+ bulletFlag = false;
710
+ }
711
+ if (bulletFlag && styleObj["list-type"] === "ul") {
712
+ options.bullet = {
713
+ indent: (options.fontSize || defaultFontSize) * 1.25,
714
+ };
715
+ options.paraSpaceBefore = 0.1;
716
+ bulletFlag = false;
717
+ }
718
+ if (indent) {
719
+ options.indentLevel = indent;
720
+ indent = 0;
721
+ }
722
+ slices.push({ text, options });
723
+ }
724
+ else if ("children" in item)
725
+ parse(item.children, styleObj);
726
+ }
727
+ };
728
+ parse(ast);
729
+ return slices;
730
+ };
731
+ // 将SVG路径信息格式化为pptxgenjs所需要的格式
732
+ const formatPoints = (points, scale = { x: 1, y: 1 }, viewportSize) => {
733
+ return points.map((point) => {
734
+ if (point.close !== undefined) {
735
+ return { close: true };
736
+ }
737
+ else if (point.type === "M") {
738
+ return {
739
+ x: (point.x / ratioPx2Inch(viewportSize)) * scale.x,
740
+ y: (point.y / ratioPx2Inch(viewportSize)) * scale.y,
741
+ moveTo: true,
742
+ };
743
+ }
744
+ else if (point.curve) {
745
+ if (point.curve.type === "cubic") {
746
+ return {
747
+ x: (point.x / ratioPx2Inch(viewportSize)) * scale.x,
748
+ y: (point.y / ratioPx2Inch(viewportSize)) * scale.y,
749
+ curve: {
750
+ type: "cubic",
751
+ x1: (point.curve.x1 / ratioPx2Inch(viewportSize)) *
752
+ scale.x,
753
+ y1: (point.curve.y1 / ratioPx2Inch(viewportSize)) *
754
+ scale.y,
755
+ x2: (point.curve.x2 / ratioPx2Inch(viewportSize)) *
756
+ scale.x,
757
+ y2: (point.curve.y2 / ratioPx2Inch(viewportSize)) *
758
+ scale.y,
759
+ },
760
+ };
761
+ }
762
+ else if (point.curve.type === "quadratic") {
763
+ return {
764
+ x: (point.x / ratioPx2Inch(viewportSize)) * scale.x,
765
+ y: (point.y / ratioPx2Inch(viewportSize)) * scale.y,
766
+ curve: {
767
+ type: "quadratic",
768
+ x1: (point.curve.x1 / ratioPx2Inch(viewportSize)) *
769
+ scale.x,
770
+ y1: (point.curve.y1 / ratioPx2Inch(viewportSize)) *
771
+ scale.y,
772
+ },
773
+ };
774
+ }
775
+ }
776
+ return {
777
+ x: (point.x / ratioPx2Inch(viewportSize)) * scale.x,
778
+ y: (point.y / ratioPx2Inch(viewportSize)) * scale.y,
779
+ };
780
+ });
781
+ };
782
+ // 获取阴影配置
783
+ const getShadowOption = (shadow, viewportSize) => {
784
+ const c = formatColor(shadow.color);
785
+ const { h, v } = shadow;
786
+ let offset = 4;
787
+ let angle = 45;
788
+ if (h === 0 && v === 0) {
789
+ offset = 4;
790
+ angle = 45;
791
+ }
792
+ else if (h === 0) {
793
+ if (v > 0) {
794
+ offset = v;
795
+ angle = 90;
796
+ }
797
+ else {
798
+ offset = -v;
799
+ angle = 270;
800
+ }
801
+ }
802
+ else if (v === 0) {
803
+ if (h > 0) {
804
+ offset = h;
805
+ angle = 1;
806
+ }
807
+ else {
808
+ offset = -h;
809
+ angle = 180;
810
+ }
811
+ }
812
+ else if (h > 0 && v > 0) {
813
+ offset = Math.max(h, v);
814
+ angle = 45;
815
+ }
816
+ else if (h > 0 && v < 0) {
817
+ offset = Math.max(h, -v);
818
+ angle = 315;
819
+ }
820
+ else if (h < 0 && v > 0) {
821
+ offset = Math.max(-h, v);
822
+ angle = 135;
823
+ }
824
+ else if (h < 0 && v < 0) {
825
+ offset = Math.max(-h, -v);
826
+ angle = 225;
827
+ }
828
+ return {
829
+ type: "outer",
830
+ color: c.color.replace("#", ""),
831
+ opacity: c.alpha,
832
+ blur: shadow.blur / ratioPx2Pt(viewportSize),
833
+ offset,
834
+ angle,
835
+ };
836
+ };
837
+ const dashTypeMap = {
838
+ solid: "solid",
839
+ dashed: "dash",
840
+ dotted: "sysDot",
841
+ };
842
+ // 获取边框配置
843
+ const getOutlineOption = (outline, viewportSize) => {
844
+ const c = formatColor(outline?.color || "#000000");
845
+ return {
846
+ color: c.color,
847
+ transparency: (1 - c.alpha) * 100,
848
+ width: (outline.width || 1) / ratioPx2Pt(viewportSize),
849
+ dashType: outline.style
850
+ ? dashTypeMap[outline.style]
851
+ : "solid",
852
+ };
853
+ };
854
+ // 获取超链接配置
855
+ const getLinkOption = (link, slides) => {
856
+ const { type, target } = link;
857
+ if (type === "web")
858
+ return { url: target };
859
+ if (type === "slide") {
860
+ const index = slides.findIndex((slide) => slide.id === target);
861
+ if (index !== -1)
862
+ return { slide: index + 1 };
863
+ }
864
+ return null;
865
+ };
866
+ // 判断是否为Base64图片地址
867
+ const isBase64Image = (url) => {
868
+ const regex = /^data:image\/[^;]+;base64,/;
869
+ return url.match(regex) !== null;
870
+ };
871
+ // 判断是否为SVG图片地址
872
+ const isSVGImage = (url) => {
873
+ const isSVGBase64 = /^data:image\/svg\+xml;base64,/.test(url);
874
+ const isSVGUrl = /\.svg$/.test(url);
875
+ return isSVGBase64 || isSVGUrl;
876
+ };