json2pptx 0.1.0 → 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1141 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // lib/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ENABLE_DECK_JSON: () => ENABLE_DECK_JSON,
34
+ PPTX_JSON_PAYLOAD_PATH: () => PPTX_JSON_PAYLOAD_PATH,
35
+ PPTX_JSON_PAYLOAD_VERSION: () => PPTX_JSON_PAYLOAD_VERSION,
36
+ buildPptxBlob: () => buildPptxBlob,
37
+ getElementRange: () => getElementRange,
38
+ getLineElementPath: () => getLineElementPath,
39
+ resolveImageData: () => resolveImageData
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+ var import_pptxgenjs = __toESM(require("pptxgenjs"));
43
+ var import_jszip = __toESM(require("jszip"));
44
+ var import_tinycolor2 = __toESM(require("tinycolor2"));
45
+
46
+ // lib/resolveImageData.ts
47
+ var dataUrlRegex = /^data:image\/[^;]+;base64,/;
48
+ var mimeFromPath = (path) => {
49
+ const lower = path.toLowerCase();
50
+ if (lower.endsWith(".png")) return "image/png";
51
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
52
+ if (lower.endsWith(".gif")) return "image/gif";
53
+ if (lower.endsWith(".svg")) return "image/svg+xml";
54
+ if (lower.endsWith(".webp")) return "image/webp";
55
+ if (lower.endsWith(".bmp")) return "image/bmp";
56
+ return "application/octet-stream";
57
+ };
58
+ var toBase64 = (buffer) => {
59
+ if (typeof Buffer !== "undefined") {
60
+ return Buffer.from(buffer).toString("base64");
61
+ }
62
+ let binary = "";
63
+ const bytes = new Uint8Array(buffer);
64
+ const len = bytes.byteLength;
65
+ for (let i = 0; i < len; i += 1) {
66
+ binary += String.fromCharCode(bytes[i]);
67
+ }
68
+ return btoa(binary);
69
+ };
70
+ var readLocalFile = async (path) => {
71
+ const fs = await import("fs/promises");
72
+ const buffer = await fs.readFile(path);
73
+ return buffer.toString("base64");
74
+ };
75
+ var resolveImageData = async (src) => {
76
+ var _a;
77
+ if (dataUrlRegex.test(src)) return src;
78
+ const isFileUrl = src.startsWith("file://");
79
+ const isLocalPath = !src.startsWith("http") && !src.startsWith("data:");
80
+ const isNode = typeof process !== "undefined" && Boolean((_a = process.versions) == null ? void 0 : _a.node);
81
+ if ((isFileUrl || isLocalPath) && isNode) {
82
+ const filePath = isFileUrl ? src.replace("file://", "") : src;
83
+ const base642 = await readLocalFile(filePath);
84
+ const mime = mimeFromPath(filePath);
85
+ return `data:${mime};base64,${base642}`;
86
+ }
87
+ if (typeof fetch !== "function") {
88
+ throw new Error("fetch is not available to resolve image data");
89
+ }
90
+ const response = await fetch(src);
91
+ if (!response.ok) {
92
+ throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
93
+ }
94
+ const arrayBuffer = await response.arrayBuffer();
95
+ const base64 = toBase64(arrayBuffer);
96
+ const contentType = response.headers.get("content-type") || mimeFromPath(src);
97
+ return `data:${contentType};base64,${base64}`;
98
+ };
99
+
100
+ // lib/htmlParser/tags.ts
101
+ var childlessTags = ["style", "script", "template"];
102
+ var closingTags = [
103
+ "html",
104
+ "head",
105
+ "body",
106
+ "p",
107
+ "dt",
108
+ "dd",
109
+ "li",
110
+ "option",
111
+ "thead",
112
+ "th",
113
+ "tbody",
114
+ "tr",
115
+ "td",
116
+ "tfoot",
117
+ "colgroup"
118
+ ];
119
+ var closingTagAncestorBreakers = {
120
+ li: ["ul", "ol", "menu"],
121
+ dt: ["dl"],
122
+ dd: ["dl"],
123
+ tbody: ["table"],
124
+ thead: ["table"],
125
+ tfoot: ["table"],
126
+ tr: ["table"],
127
+ td: ["table"]
128
+ };
129
+ var voidTags = [
130
+ "!doctype",
131
+ "area",
132
+ "base",
133
+ "br",
134
+ "col",
135
+ "command",
136
+ "embed",
137
+ "hr",
138
+ "img",
139
+ "input",
140
+ "keygen",
141
+ "link",
142
+ "meta",
143
+ "param",
144
+ "source",
145
+ "track",
146
+ "wbr"
147
+ ];
148
+
149
+ // lib/htmlParser/lexer.ts
150
+ var jumpPosition = (state, end) => {
151
+ const len = end - state.position;
152
+ movePosition(state, len);
153
+ };
154
+ var movePosition = (state, len) => {
155
+ state.position = state.position + len;
156
+ };
157
+ var findTextEnd = (str, index) => {
158
+ const isEnd = false;
159
+ while (!isEnd) {
160
+ const textEnd = str.indexOf("<", index);
161
+ if (textEnd === -1) {
162
+ return textEnd;
163
+ }
164
+ const char = str.charAt(textEnd + 1);
165
+ if (char === "/" || char === "!" || /[A-Za-z0-9]/.test(char)) {
166
+ return textEnd;
167
+ }
168
+ index = textEnd + 1;
169
+ }
170
+ return -1;
171
+ };
172
+ var lexText = (state) => {
173
+ const { str } = state;
174
+ let textEnd = findTextEnd(str, state.position);
175
+ if (textEnd === state.position) return;
176
+ if (textEnd === -1) {
177
+ textEnd = str.length;
178
+ }
179
+ const content = str.slice(state.position, textEnd);
180
+ jumpPosition(state, textEnd);
181
+ state.tokens.push({
182
+ type: "text",
183
+ content
184
+ });
185
+ };
186
+ var lexComment = (state) => {
187
+ const { str } = state;
188
+ movePosition(state, 4);
189
+ let contentEnd = str.indexOf("-->", state.position);
190
+ let commentEnd = contentEnd + 3;
191
+ if (contentEnd === -1) {
192
+ contentEnd = commentEnd = str.length;
193
+ }
194
+ const content = str.slice(state.position, contentEnd);
195
+ jumpPosition(state, commentEnd);
196
+ state.tokens.push({
197
+ type: "comment",
198
+ content
199
+ });
200
+ };
201
+ var lexTagName = (state) => {
202
+ const { str } = state;
203
+ const len = str.length;
204
+ let start = state.position;
205
+ while (start < len) {
206
+ const char = str.charAt(start);
207
+ const isTagChar = !(/\s/.test(char) || char === "/" || char === ">");
208
+ if (isTagChar) break;
209
+ start++;
210
+ }
211
+ let end = start + 1;
212
+ while (end < len) {
213
+ const char = str.charAt(end);
214
+ const isTagChar = !(/\s/.test(char) || char === "/" || char === ">");
215
+ if (!isTagChar) break;
216
+ end++;
217
+ }
218
+ jumpPosition(state, end);
219
+ const tagName = str.slice(start, end);
220
+ state.tokens.push({
221
+ type: "tag",
222
+ content: tagName
223
+ });
224
+ return tagName;
225
+ };
226
+ var lexTagAttributes = (state) => {
227
+ const { str, tokens } = state;
228
+ let cursor = state.position;
229
+ let quote = null;
230
+ let wordBegin = cursor;
231
+ const words = [];
232
+ const len = str.length;
233
+ while (cursor < len) {
234
+ const char = str.charAt(cursor);
235
+ if (quote) {
236
+ const isQuoteEnd = char === quote;
237
+ if (isQuoteEnd) quote = null;
238
+ cursor++;
239
+ continue;
240
+ }
241
+ const isTagEnd = char === "/" || char === ">";
242
+ if (isTagEnd) {
243
+ if (cursor !== wordBegin) words.push(str.slice(wordBegin, cursor));
244
+ break;
245
+ }
246
+ const isWordEnd = /\s/.test(char);
247
+ if (isWordEnd) {
248
+ if (cursor !== wordBegin) words.push(str.slice(wordBegin, cursor));
249
+ wordBegin = cursor + 1;
250
+ cursor++;
251
+ continue;
252
+ }
253
+ const isQuoteStart = char === "'" || char === '"';
254
+ if (isQuoteStart) {
255
+ quote = char;
256
+ cursor++;
257
+ continue;
258
+ }
259
+ cursor++;
260
+ }
261
+ jumpPosition(state, cursor);
262
+ const type = "attribute";
263
+ for (let i = 0; i < words.length; i++) {
264
+ const word = words[i];
265
+ const isNotPair = word.indexOf("=") === -1;
266
+ if (isNotPair) {
267
+ const secondWord = words[i + 1];
268
+ if (secondWord && secondWord.startsWith("=")) {
269
+ if (secondWord.length > 1) {
270
+ const newWord = word + secondWord;
271
+ tokens.push({ type, content: newWord });
272
+ i += 1;
273
+ continue;
274
+ }
275
+ const thirdWord = words[i + 2];
276
+ i += 1;
277
+ if (thirdWord) {
278
+ const newWord = word + "=" + thirdWord;
279
+ tokens.push({ type, content: newWord });
280
+ i += 1;
281
+ continue;
282
+ }
283
+ }
284
+ }
285
+ if (word.endsWith("=")) {
286
+ const secondWord = words[i + 1];
287
+ if (secondWord && secondWord.indexOf("=") === -1) {
288
+ const newWord2 = word + secondWord;
289
+ tokens.push({ type, content: newWord2 });
290
+ i += 1;
291
+ continue;
292
+ }
293
+ const newWord = word.slice(0, -1);
294
+ tokens.push({ type, content: newWord });
295
+ continue;
296
+ }
297
+ tokens.push({ type, content: word });
298
+ }
299
+ };
300
+ var lexSkipTag = (tagName, state) => {
301
+ const { str, tokens } = state;
302
+ const safeTagName = tagName.toLowerCase();
303
+ const len = str.length;
304
+ let index = state.position;
305
+ while (index < len) {
306
+ const nextTag = str.indexOf("</", index);
307
+ if (nextTag === -1) {
308
+ lexText(state);
309
+ break;
310
+ }
311
+ const tagState = {
312
+ str,
313
+ position: state.position,
314
+ tokens: []
315
+ };
316
+ jumpPosition(tagState, nextTag);
317
+ const name = lexTag(tagState);
318
+ if (safeTagName !== name.toLowerCase()) {
319
+ index = tagState.position;
320
+ continue;
321
+ }
322
+ if (nextTag !== state.position) {
323
+ const textStart = state.position;
324
+ jumpPosition(state, nextTag);
325
+ tokens.push({
326
+ type: "text",
327
+ content: str.slice(textStart, nextTag)
328
+ });
329
+ }
330
+ tokens.push(...tagState.tokens);
331
+ jumpPosition(state, tagState.position);
332
+ break;
333
+ }
334
+ };
335
+ var lexTag = (state) => {
336
+ const { str } = state;
337
+ const secondChar = str.charAt(state.position + 1);
338
+ const tagStartClose = secondChar === "/";
339
+ movePosition(state, tagStartClose ? 2 : 1);
340
+ state.tokens.push({
341
+ type: "tag-start",
342
+ close: tagStartClose
343
+ });
344
+ const tagName = lexTagName(state);
345
+ lexTagAttributes(state);
346
+ const firstChar = str.charAt(state.position);
347
+ const tagEndClose = firstChar === "/";
348
+ movePosition(state, tagEndClose ? 2 : 1);
349
+ state.tokens.push({
350
+ type: "tag-end",
351
+ close: tagEndClose
352
+ });
353
+ return tagName;
354
+ };
355
+ var lex = (state) => {
356
+ const str = state.str;
357
+ const len = str.length;
358
+ while (state.position < len) {
359
+ const start = state.position;
360
+ lexText(state);
361
+ if (state.position === start) {
362
+ const isComment = str.startsWith("!--", start + 1);
363
+ if (isComment) lexComment(state);
364
+ else {
365
+ const tagName = lexTag(state);
366
+ const safeTag = tagName.toLowerCase();
367
+ if (childlessTags.includes(safeTag)) lexSkipTag(tagName, state);
368
+ }
369
+ }
370
+ }
371
+ };
372
+ var lexer = (str) => {
373
+ const state = {
374
+ str,
375
+ position: 0,
376
+ tokens: []
377
+ };
378
+ lex(state);
379
+ return state.tokens;
380
+ };
381
+
382
+ // lib/htmlParser/parser.ts
383
+ var parser = (tokens) => {
384
+ const root = { tagName: null, children: [] };
385
+ const state = { tokens, cursor: 0, stack: [root] };
386
+ parse(state);
387
+ return root.children;
388
+ };
389
+ var hasTerminalParent = (tagName, stack) => {
390
+ const tagParents = closingTagAncestorBreakers[tagName];
391
+ if (tagParents) {
392
+ let currentIndex = stack.length - 1;
393
+ while (currentIndex >= 0) {
394
+ const parentTagName = stack[currentIndex].tagName;
395
+ if (parentTagName === tagName) break;
396
+ if (parentTagName && tagParents.includes(parentTagName)) return true;
397
+ currentIndex--;
398
+ }
399
+ }
400
+ return false;
401
+ };
402
+ var rewindStack = (stack, newLength) => {
403
+ stack.splice(newLength);
404
+ };
405
+ var parse = (state) => {
406
+ const { stack, tokens } = state;
407
+ let { cursor } = state;
408
+ let nodes = stack[stack.length - 1].children;
409
+ const len = tokens.length;
410
+ while (cursor < len) {
411
+ const token = tokens[cursor];
412
+ if (token.type !== "tag-start") {
413
+ nodes.push(token);
414
+ cursor++;
415
+ continue;
416
+ }
417
+ const tagToken = tokens[++cursor];
418
+ cursor++;
419
+ const tagName = tagToken.content.toLowerCase();
420
+ if (token.close) {
421
+ let index = stack.length;
422
+ let shouldRewind = false;
423
+ while (--index > -1) {
424
+ if (stack[index].tagName === tagName) {
425
+ shouldRewind = true;
426
+ break;
427
+ }
428
+ }
429
+ while (cursor < len) {
430
+ if (tokens[cursor].type !== "tag-end") break;
431
+ cursor++;
432
+ }
433
+ if (shouldRewind) {
434
+ rewindStack(stack, index);
435
+ break;
436
+ } else continue;
437
+ }
438
+ const isClosingTag = closingTags.includes(tagName);
439
+ let shouldRewindToAutoClose = isClosingTag;
440
+ if (shouldRewindToAutoClose) {
441
+ shouldRewindToAutoClose = !hasTerminalParent(tagName, stack);
442
+ }
443
+ if (shouldRewindToAutoClose) {
444
+ let currentIndex = stack.length - 1;
445
+ while (currentIndex > 0) {
446
+ if (tagName === stack[currentIndex].tagName) {
447
+ rewindStack(stack, currentIndex);
448
+ const previousIndex = currentIndex - 1;
449
+ nodes = stack[previousIndex].children;
450
+ break;
451
+ }
452
+ currentIndex = currentIndex - 1;
453
+ }
454
+ }
455
+ const attributes = [];
456
+ let tagEndToken;
457
+ while (cursor < len) {
458
+ const _token = tokens[cursor];
459
+ if (_token.type === "tag-end") {
460
+ tagEndToken = _token;
461
+ break;
462
+ }
463
+ attributes.push(_token.content);
464
+ cursor++;
465
+ }
466
+ if (!tagEndToken) break;
467
+ cursor++;
468
+ const children = [];
469
+ const elementNode = {
470
+ type: "element",
471
+ tagName: tagToken.content,
472
+ attributes,
473
+ children
474
+ };
475
+ nodes.push(elementNode);
476
+ const hasChildren = !(tagEndToken.close || voidTags.includes(tagName));
477
+ if (hasChildren) {
478
+ stack.push({ tagName, children });
479
+ const innerState = { tokens, cursor, stack };
480
+ parse(innerState);
481
+ cursor = innerState.cursor;
482
+ }
483
+ }
484
+ state.cursor = cursor;
485
+ };
486
+
487
+ // lib/htmlParser/format.ts
488
+ var splitHead = (str, sep) => {
489
+ const idx = str.indexOf(sep);
490
+ if (idx === -1) return [str];
491
+ return [str.slice(0, idx), str.slice(idx + sep.length)];
492
+ };
493
+ var unquote = (str) => {
494
+ const car = str.charAt(0);
495
+ const end = str.length - 1;
496
+ const isQuoteStart = car === '"' || car === "'";
497
+ if (isQuoteStart && car === str.charAt(end)) {
498
+ return str.slice(1, end);
499
+ }
500
+ return str;
501
+ };
502
+ var formatAttributes = (attributes) => {
503
+ return attributes.map((attribute) => {
504
+ const parts = splitHead(attribute.trim(), "=");
505
+ const key = parts[0];
506
+ const value = typeof parts[1] === "string" ? unquote(parts[1]) : null;
507
+ return { key, value };
508
+ });
509
+ };
510
+ var format = (nodes) => {
511
+ return nodes.map((node) => {
512
+ if (node.type === "element") {
513
+ const children = format(node.children);
514
+ const item2 = {
515
+ type: "element",
516
+ tagName: node.tagName.toLowerCase(),
517
+ attributes: formatAttributes(node.attributes),
518
+ children
519
+ };
520
+ return item2;
521
+ }
522
+ const item = {
523
+ type: node.type,
524
+ content: node.content
525
+ };
526
+ return item;
527
+ });
528
+ };
529
+
530
+ // lib/htmlParser/index.ts
531
+ var toAST = (str) => {
532
+ const tokens = lexer(str);
533
+ const nodes = parser(tokens);
534
+ return format(nodes);
535
+ };
536
+
537
+ // lib/svgPathParser.ts
538
+ var import_svg_pathdata = require("svg-pathdata");
539
+ var import_svg_arc_to_cubic_bezier = __toESM(require("svg-arc-to-cubic-bezier"));
540
+ var typeMap = {
541
+ 1: "Z",
542
+ 2: "M",
543
+ 4: "H",
544
+ 8: "V",
545
+ 16: "L",
546
+ 32: "C",
547
+ 64: "S",
548
+ 128: "Q",
549
+ 256: "T",
550
+ 512: "A"
551
+ };
552
+ var toPoints = (d) => {
553
+ const pathData = new import_svg_pathdata.SVGPathData(d);
554
+ const points = [];
555
+ for (const item of pathData.commands) {
556
+ const type = typeMap[item.type];
557
+ if (item.type === 2 || item.type === 16) {
558
+ points.push({
559
+ x: item.x,
560
+ y: item.y,
561
+ relative: item.relative,
562
+ type
563
+ });
564
+ }
565
+ if (item.type === 32) {
566
+ points.push({
567
+ x: item.x,
568
+ y: item.y,
569
+ curve: {
570
+ type: "cubic",
571
+ x1: item.x1,
572
+ y1: item.y1,
573
+ x2: item.x2,
574
+ y2: item.y2
575
+ },
576
+ relative: item.relative,
577
+ type
578
+ });
579
+ } else if (item.type === 128) {
580
+ points.push({
581
+ x: item.x,
582
+ y: item.y,
583
+ curve: {
584
+ type: "quadratic",
585
+ x1: item.x1,
586
+ y1: item.y1
587
+ },
588
+ relative: item.relative,
589
+ type
590
+ });
591
+ } else if (item.type === 512) {
592
+ const lastPoint = points[points.length - 1];
593
+ if (!lastPoint || !["M", "L", "Q", "C"].includes(lastPoint.type)) continue;
594
+ const cubicBezierPoints = (0, import_svg_arc_to_cubic_bezier.default)({
595
+ px: lastPoint.x,
596
+ py: lastPoint.y,
597
+ cx: item.x,
598
+ cy: item.y,
599
+ rx: item.rX,
600
+ ry: item.rY,
601
+ xAxisRotation: item.xRot,
602
+ largeArcFlag: item.lArcFlag,
603
+ sweepFlag: item.sweepFlag
604
+ });
605
+ for (const cbPoint of cubicBezierPoints) {
606
+ points.push({
607
+ x: cbPoint.x,
608
+ y: cbPoint.y,
609
+ curve: {
610
+ type: "cubic",
611
+ x1: cbPoint.x1,
612
+ y1: cbPoint.y1,
613
+ x2: cbPoint.x2,
614
+ y2: cbPoint.y2
615
+ },
616
+ relative: false,
617
+ type: "C"
618
+ });
619
+ }
620
+ } else if (item.type === 1) {
621
+ points.push({ close: true, type });
622
+ } else continue;
623
+ }
624
+ return points;
625
+ };
626
+
627
+ // lib/element.ts
628
+ var getElementRange = (element) => {
629
+ var _a, _b, _c, _d, _e, _f;
630
+ let minX = 0;
631
+ let maxX = 0;
632
+ let minY = 0;
633
+ let maxY = 0;
634
+ if (element.type === "line" && element.start && element.end) {
635
+ minX = (_a = element.left) != null ? _a : 0;
636
+ maxX = ((_b = element.left) != null ? _b : 0) + Math.max(element.start[0], element.end[0]);
637
+ minY = (_c = element.top) != null ? _c : 0;
638
+ maxY = ((_d = element.top) != null ? _d : 0) + Math.max(element.start[1], element.end[1]);
639
+ } else if (element.left !== void 0 && element.top !== void 0) {
640
+ minX = element.left;
641
+ minY = element.top;
642
+ maxX = element.left + ((_e = element.width) != null ? _e : 0);
643
+ maxY = element.top + ((_f = element.height) != null ? _f : 0);
644
+ }
645
+ return { minX, maxX, minY, maxY };
646
+ };
647
+ var isLineElement = (element) => element.type === "line";
648
+ var getLineElementPath = (element) => {
649
+ if (!isLineElement(element) || !element.start || !element.end) return "";
650
+ const start = element.start.join(",");
651
+ const end = element.end.join(",");
652
+ const broken = element.broken;
653
+ const broken2 = element.broken2;
654
+ const curve = element.curve;
655
+ const cubic = element.cubic;
656
+ if (broken) {
657
+ const mid = broken.join(",");
658
+ return `M${start} L${mid} L${end}`;
659
+ }
660
+ if (broken2) {
661
+ const { minX, maxX, minY, maxY } = getElementRange(element);
662
+ if (maxX - minX >= maxY - minY) {
663
+ return `M${start} L${broken2[0]},${element.start[1]} L${broken2[0]},${element.end[1]} ${end}`;
664
+ }
665
+ return `M${start} L${element.start[0]},${broken2[1]} L${element.end[0]},${broken2[1]} ${end}`;
666
+ }
667
+ if (curve) {
668
+ const mid = curve.join(",");
669
+ return `M${start} Q${mid} ${end}`;
670
+ }
671
+ if (cubic) {
672
+ const [c1, c2] = cubic;
673
+ const p1 = c1.join(",");
674
+ const p2 = c2.join(",");
675
+ return `M${start} C${p1} ${p2} ${end}`;
676
+ }
677
+ return `M${start} L${end}`;
678
+ };
679
+
680
+ // lib/index.ts
681
+ var DEFAULT_WIDTH = 1e3;
682
+ var DEFAULT_HEIGHT = 562.5;
683
+ var DEFAULT_FONT_SIZE = 16;
684
+ var DEFAULT_FONT_FACE = "\u5FAE\u8F6F\u96C5\u9ED1";
685
+ var ENABLE_DECK_JSON = true;
686
+ var PPTX_JSON_PAYLOAD_PATH = "json2ppt-editor.json";
687
+ var PPTX_JSON_PAYLOAD_VERSION = 1;
688
+ function sanitizeFileName(name) {
689
+ return name.replace(/[\\/:*?"<>|]/g, "").trim() || "presentation";
690
+ }
691
+ function formatColor(input) {
692
+ if (!input) {
693
+ return {
694
+ alpha: 0,
695
+ color: "#000000"
696
+ };
697
+ }
698
+ const color = (0, import_tinycolor2.default)(input);
699
+ const alpha = color.getAlpha();
700
+ const hex = alpha === 0 ? "#ffffff" : color.setAlpha(1).toHexString();
701
+ return { alpha, color: hex };
702
+ }
703
+ function formatHTML(html, ratioPx2Pt) {
704
+ const ast = toAST(html);
705
+ let bulletFlag = false;
706
+ let indent = 0;
707
+ const slices = [];
708
+ const parse2 = (obj, baseStyleObj = {}) => {
709
+ for (const item of obj) {
710
+ const isBlockTag = "tagName" in item && ["div", "li", "p"].includes(item.tagName);
711
+ if (isBlockTag && slices.length) {
712
+ const lastSlice = slices[slices.length - 1];
713
+ if (!lastSlice.options) lastSlice.options = {};
714
+ lastSlice.options.breakLine = true;
715
+ }
716
+ const styleObj = { ...baseStyleObj };
717
+ const styleAttr = "attributes" in item ? item.attributes.find((attr) => attr.key === "style") : null;
718
+ if (styleAttr && styleAttr.value) {
719
+ const styleArr = styleAttr.value.split(";");
720
+ for (const styleItem of styleArr) {
721
+ const match = styleItem.match(/([^:]+):\s*(.+)/);
722
+ if (match) {
723
+ const [key, value] = [match[1].trim(), match[2].trim()];
724
+ if (key && value) styleObj[key] = value;
725
+ }
726
+ }
727
+ }
728
+ if ("tagName" in item) {
729
+ if (item.tagName === "em") {
730
+ styleObj["font-style"] = "italic";
731
+ }
732
+ if (item.tagName === "strong") {
733
+ styleObj["font-weight"] = "bold";
734
+ }
735
+ if (item.tagName === "sup") {
736
+ styleObj["vertical-align"] = "super";
737
+ }
738
+ if (item.tagName === "sub") {
739
+ styleObj["vertical-align"] = "sub";
740
+ }
741
+ if (item.tagName === "a") {
742
+ const attr = item.attributes.find((attr2) => attr2.key === "href");
743
+ styleObj.href = (attr == null ? void 0 : attr.value) || "";
744
+ }
745
+ if (item.tagName === "ul") {
746
+ styleObj["list-type"] = "ul";
747
+ }
748
+ if (item.tagName === "ol") {
749
+ styleObj["list-type"] = "ol";
750
+ }
751
+ if (item.tagName === "li") {
752
+ bulletFlag = true;
753
+ }
754
+ if (item.tagName === "p") {
755
+ if ("attributes" in item) {
756
+ const dataIndentAttr = item.attributes.find((attr) => attr.key === "data-indent");
757
+ if (dataIndentAttr && dataIndentAttr.value) indent = +dataIndentAttr.value;
758
+ }
759
+ }
760
+ }
761
+ if ("tagName" in item && item.tagName === "br") {
762
+ slices.push({ text: "", options: { breakLine: true } });
763
+ } else if ("content" in item) {
764
+ const text = item.content.replace(/&nbsp;/g, " ").replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&").replace(/\n/g, "");
765
+ const options = {};
766
+ if (styleObj["font-size"]) {
767
+ options.fontSize = parseInt(styleObj["font-size"], 10) / ratioPx2Pt;
768
+ }
769
+ if (styleObj.color) {
770
+ options.color = formatColor(styleObj.color).color;
771
+ }
772
+ if (styleObj["background-color"]) {
773
+ options.highlight = formatColor(styleObj["background-color"]).color;
774
+ }
775
+ if (styleObj["text-decoration-line"]) {
776
+ if (styleObj["text-decoration-line"].indexOf("underline") !== -1) {
777
+ options.underline = {
778
+ color: options.color || "#000000",
779
+ style: "sng"
780
+ };
781
+ }
782
+ if (styleObj["text-decoration-line"].indexOf("line-through") !== -1) {
783
+ options.strike = "sngStrike";
784
+ }
785
+ }
786
+ if (styleObj["text-decoration"]) {
787
+ if (styleObj["text-decoration"].indexOf("underline") !== -1) {
788
+ options.underline = {
789
+ color: options.color || "#000000",
790
+ style: "sng"
791
+ };
792
+ }
793
+ if (styleObj["text-decoration"].indexOf("line-through") !== -1) {
794
+ options.strike = "sngStrike";
795
+ }
796
+ }
797
+ if (styleObj["vertical-align"]) {
798
+ if (styleObj["vertical-align"] === "super") options.superscript = true;
799
+ if (styleObj["vertical-align"] === "sub") options.subscript = true;
800
+ }
801
+ if (styleObj["text-align"]) options.align = styleObj["text-align"];
802
+ if (styleObj["font-weight"]) options.bold = styleObj["font-weight"] === "bold";
803
+ if (styleObj["font-style"]) options.italic = styleObj["font-style"] === "italic";
804
+ if (styleObj["font-family"]) options.fontFace = styleObj["font-family"];
805
+ if (styleObj.href) options.hyperlink = { url: styleObj.href };
806
+ if (bulletFlag && styleObj["list-type"] === "ol") {
807
+ options.bullet = {
808
+ type: "number",
809
+ indent: (options.fontSize || DEFAULT_FONT_SIZE) * 1.25
810
+ };
811
+ options.paraSpaceBefore = 0.1;
812
+ bulletFlag = false;
813
+ }
814
+ if (bulletFlag && styleObj["list-type"] === "ul") {
815
+ options.bullet = { indent: (options.fontSize || DEFAULT_FONT_SIZE) * 1.25 };
816
+ options.paraSpaceBefore = 0.1;
817
+ bulletFlag = false;
818
+ }
819
+ if (indent) {
820
+ options.indentLevel = indent;
821
+ indent = 0;
822
+ }
823
+ slices.push({ text, options });
824
+ } else if ("children" in item) parse2(item.children, styleObj);
825
+ }
826
+ };
827
+ parse2(ast);
828
+ return slices;
829
+ }
830
+ var formatPoints = (points, ratioPx2Inch, scale = { x: 1, y: 1 }) => {
831
+ return points.map((point) => {
832
+ if ("close" in point) {
833
+ return { close: true };
834
+ }
835
+ if (point.type === "M") {
836
+ return {
837
+ x: point.x / ratioPx2Inch * scale.x,
838
+ y: point.y / ratioPx2Inch * scale.y,
839
+ moveTo: true
840
+ };
841
+ }
842
+ if (point.curve) {
843
+ if (point.curve.type === "cubic") {
844
+ return {
845
+ x: point.x / ratioPx2Inch * scale.x,
846
+ y: point.y / ratioPx2Inch * scale.y,
847
+ curve: {
848
+ type: "cubic",
849
+ x1: point.curve.x1 / ratioPx2Inch * scale.x,
850
+ y1: point.curve.y1 / ratioPx2Inch * scale.y,
851
+ x2: point.curve.x2 / ratioPx2Inch * scale.x,
852
+ y2: point.curve.y2 / ratioPx2Inch * scale.y
853
+ }
854
+ };
855
+ }
856
+ if (point.curve.type === "quadratic") {
857
+ return {
858
+ x: point.x / ratioPx2Inch * scale.x,
859
+ y: point.y / ratioPx2Inch * scale.y,
860
+ curve: {
861
+ type: "quadratic",
862
+ x1: point.curve.x1 / ratioPx2Inch * scale.x,
863
+ y1: point.curve.y1 / ratioPx2Inch * scale.y
864
+ }
865
+ };
866
+ }
867
+ }
868
+ return {
869
+ x: point.x / ratioPx2Inch * scale.x,
870
+ y: point.y / ratioPx2Inch * scale.y
871
+ };
872
+ });
873
+ };
874
+ var dashTypeMap = {
875
+ solid: "solid",
876
+ dashed: "dash",
877
+ dotted: "sysDot"
878
+ };
879
+ var getShadowOption = (shadow, ratioPx2Pt) => {
880
+ var _a, _b;
881
+ const c = formatColor((_a = shadow.color) != null ? _a : "#000000");
882
+ const { h = 0, v = 0 } = shadow;
883
+ let offset = 4;
884
+ let angle = 45;
885
+ if (h === 0 && v === 0) {
886
+ offset = 4;
887
+ angle = 45;
888
+ } else if (h === 0) {
889
+ if (v > 0) {
890
+ offset = v;
891
+ angle = 90;
892
+ } else {
893
+ offset = -v;
894
+ angle = 270;
895
+ }
896
+ } else if (v === 0) {
897
+ if (h > 0) {
898
+ offset = h;
899
+ angle = 1;
900
+ } else {
901
+ offset = -h;
902
+ angle = 180;
903
+ }
904
+ } else if (h > 0 && v > 0) {
905
+ offset = Math.max(h, v);
906
+ angle = 45;
907
+ } else if (h > 0 && v < 0) {
908
+ offset = Math.max(h, -v);
909
+ angle = 315;
910
+ } else if (h < 0 && v > 0) {
911
+ offset = Math.max(-h, v);
912
+ angle = 135;
913
+ } else if (h < 0 && v < 0) {
914
+ offset = Math.max(-h, -v);
915
+ angle = 225;
916
+ }
917
+ return {
918
+ type: "outer",
919
+ color: c.color.replace("#", ""),
920
+ opacity: c.alpha,
921
+ blur: ((_b = shadow.blur) != null ? _b : 0) / ratioPx2Pt,
922
+ offset,
923
+ angle
924
+ };
925
+ };
926
+ var getOutlineOption = (outline, ratioPx2Pt) => {
927
+ const c = formatColor(outline.color || "#000000");
928
+ return {
929
+ color: c.color,
930
+ transparency: (1 - c.alpha) * 100,
931
+ width: (outline.width || 1) / ratioPx2Pt,
932
+ dashType: outline.style ? dashTypeMap[outline.style] : "solid"
933
+ };
934
+ };
935
+ var isBase64Image = (url) => {
936
+ const regex = /^data:image\/[^;]+;base64,/;
937
+ return regex.test(url);
938
+ };
939
+ async function buildPptxBlob(template) {
940
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K;
941
+ const pptx = new import_pptxgenjs.default();
942
+ const width = (_a = template.width) != null ? _a : DEFAULT_WIDTH;
943
+ const height = (_b = template.height) != null ? _b : DEFAULT_HEIGHT;
944
+ const viewportRatio = height / width;
945
+ const ratioPx2Inch = 96 * (width / 960);
946
+ const ratioPx2Pt = 96 / 72 * (width / 960);
947
+ if (Math.abs(viewportRatio - 0.625) < 1e-3) pptx.layout = "LAYOUT_16x10";
948
+ else if (Math.abs(viewportRatio - 0.75) < 1e-3) pptx.layout = "LAYOUT_4x3";
949
+ else if (Math.abs(viewportRatio - 0.70710678) < 1e-4) {
950
+ pptx.defineLayout({ name: "A3", width: 10, height: 7.0710678 });
951
+ pptx.layout = "A3";
952
+ } else if (Math.abs(viewportRatio - 1.41421356) < 1e-4) {
953
+ pptx.defineLayout({ name: "A3_V", width: 10, height: 14.1421356 });
954
+ pptx.layout = "A3_V";
955
+ } else pptx.layout = "LAYOUT_16x9";
956
+ for (const slideJson of (_c = template.slides) != null ? _c : []) {
957
+ const slide = pptx.addSlide();
958
+ const backgroundColor = (_d = slideJson.background) == null ? void 0 : _d.color;
959
+ if (backgroundColor) {
960
+ const c = formatColor(backgroundColor);
961
+ slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
962
+ }
963
+ for (const element of (_e = slideJson.elements) != null ? _e : []) {
964
+ if (element.type === "text" && element.content) {
965
+ const textProps = formatHTML(element.content, ratioPx2Pt);
966
+ const options = {
967
+ x: ((_f = element.left) != null ? _f : 0) / ratioPx2Inch,
968
+ y: ((_g = element.top) != null ? _g : 0) / ratioPx2Inch,
969
+ w: ((_h = element.width) != null ? _h : 0) / ratioPx2Inch,
970
+ h: ((_i = element.height) != null ? _i : 0) / ratioPx2Inch,
971
+ fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
972
+ fontFace: element.defaultFontName || ((_j = template.theme) == null ? void 0 : _j.fontName) || DEFAULT_FONT_FACE,
973
+ color: "#000000",
974
+ valign: "top",
975
+ margin: 10 / ratioPx2Pt,
976
+ paraSpaceBefore: 5 / ratioPx2Pt,
977
+ lineSpacingMultiple: 1.5 / 1.25,
978
+ autoFit: true
979
+ };
980
+ if (element.rotate) options.rotate = element.rotate;
981
+ if (element.wordSpace) options.charSpacing = element.wordSpace / ratioPx2Pt;
982
+ if (element.lineHeight) options.lineSpacingMultiple = element.lineHeight / 1.25;
983
+ if (element.fill) {
984
+ const c = formatColor(element.fill);
985
+ const opacity = element.opacity === void 0 ? 1 : element.opacity;
986
+ options.fill = {
987
+ color: c.color,
988
+ transparency: (1 - c.alpha * opacity) * 100
989
+ };
990
+ }
991
+ if (element.defaultColor) options.color = formatColor(element.defaultColor).color;
992
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
993
+ if ((_k = element.outline) == null ? void 0 : _k.width) options.line = getOutlineOption(element.outline, ratioPx2Pt);
994
+ if (element.opacity !== void 0) options.transparency = (1 - element.opacity) * 100;
995
+ if (element.paragraphSpace !== void 0) {
996
+ options.paraSpaceBefore = element.paragraphSpace / ratioPx2Pt;
997
+ }
998
+ if (element.vertical) options.vert = "eaVert";
999
+ slide.addText(textProps, options);
1000
+ continue;
1001
+ }
1002
+ if (element.type === "image" && element.src) {
1003
+ const options = {
1004
+ x: ((_l = element.left) != null ? _l : 0) / ratioPx2Inch,
1005
+ y: ((_m = element.top) != null ? _m : 0) / ratioPx2Inch,
1006
+ w: ((_n = element.width) != null ? _n : 0) / ratioPx2Inch,
1007
+ h: ((_o = element.height) != null ? _o : 0) / ratioPx2Inch
1008
+ };
1009
+ if (isBase64Image(element.src)) options.data = element.src;
1010
+ else options.data = await resolveImageData(element.src);
1011
+ if (element.flipH) options.flipH = element.flipH;
1012
+ if (element.flipV) options.flipV = element.flipV;
1013
+ if (element.rotate) options.rotate = element.rotate;
1014
+ if ((_p = element.filters) == null ? void 0 : _p.opacity) {
1015
+ options.transparency = 100 - parseInt(element.filters.opacity, 10);
1016
+ }
1017
+ if ((_q = element.clip) == null ? void 0 : _q.range) {
1018
+ if (element.clip.shape === "ellipse") options.rounding = true;
1019
+ const [start, end] = element.clip.range;
1020
+ const [startX, startY] = start;
1021
+ const [endX, endY] = end;
1022
+ const originW = ((_r = element.width) != null ? _r : 0) / ((endX - startX) / ratioPx2Inch);
1023
+ const originH = ((_s = element.height) != null ? _s : 0) / ((endY - startY) / ratioPx2Inch);
1024
+ options.w = originW / ratioPx2Inch;
1025
+ options.h = originH / ratioPx2Inch;
1026
+ options.sizing = {
1027
+ type: "crop",
1028
+ x: startX / ratioPx2Inch * (originW / ratioPx2Inch),
1029
+ y: startY / ratioPx2Inch * (originH / ratioPx2Inch),
1030
+ w: (endX - startX) / ratioPx2Inch * (originW / ratioPx2Inch),
1031
+ h: (endY - startY) / ratioPx2Inch * (originH / ratioPx2Inch)
1032
+ };
1033
+ }
1034
+ slide.addImage(options);
1035
+ continue;
1036
+ }
1037
+ if (element.type === "shape" && element.path && element.viewBox) {
1038
+ const scale = {
1039
+ x: ((_t = element.width) != null ? _t : 0) / element.viewBox[0],
1040
+ y: ((_u = element.height) != null ? _u : 0) / element.viewBox[1]
1041
+ };
1042
+ const points = formatPoints(toPoints(element.path), ratioPx2Inch, scale);
1043
+ let fillColor = formatColor(element.fill || "#000000");
1044
+ const opacity = element.opacity === void 0 ? 1 : element.opacity;
1045
+ const options = {
1046
+ x: ((_v = element.left) != null ? _v : 0) / ratioPx2Inch,
1047
+ y: ((_w = element.top) != null ? _w : 0) / ratioPx2Inch,
1048
+ w: ((_x = element.width) != null ? _x : 0) / ratioPx2Inch,
1049
+ h: ((_y = element.height) != null ? _y : 0) / ratioPx2Inch,
1050
+ fill: {
1051
+ color: fillColor.color,
1052
+ transparency: (1 - fillColor.alpha * opacity) * 100
1053
+ },
1054
+ points
1055
+ };
1056
+ if (element.flipH) options.flipH = element.flipH;
1057
+ if (element.flipV) options.flipV = element.flipV;
1058
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1059
+ if ((_z = element.outline) == null ? void 0 : _z.width) options.line = getOutlineOption(element.outline, ratioPx2Pt);
1060
+ if (element.rotate) options.rotate = element.rotate;
1061
+ slide.addShape("custGeom", options);
1062
+ if ((_A = element.text) == null ? void 0 : _A.content) {
1063
+ const textProps = formatHTML(element.text.content, ratioPx2Pt);
1064
+ const textOptions = {
1065
+ x: ((_B = element.left) != null ? _B : 0) / ratioPx2Inch,
1066
+ y: ((_C = element.top) != null ? _C : 0) / ratioPx2Inch,
1067
+ w: ((_D = element.width) != null ? _D : 0) / ratioPx2Inch,
1068
+ h: ((_E = element.height) != null ? _E : 0) / ratioPx2Inch,
1069
+ fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1070
+ fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
1071
+ color: "#000000",
1072
+ paraSpaceBefore: 5 / ratioPx2Pt,
1073
+ valign: element.text.align
1074
+ };
1075
+ if (element.rotate) textOptions.rotate = element.rotate;
1076
+ if (element.text.defaultColor) {
1077
+ textOptions.color = formatColor(element.text.defaultColor).color;
1078
+ }
1079
+ slide.addText(textProps, textOptions);
1080
+ }
1081
+ continue;
1082
+ }
1083
+ if (element.type === "line" && element.start && element.end) {
1084
+ const path = getLineElementPath(element);
1085
+ const points = formatPoints(toPoints(path), ratioPx2Inch);
1086
+ const { minX, maxX, minY, maxY } = getElementRange(element);
1087
+ const c = formatColor(element.color || "#000000");
1088
+ const options = {
1089
+ x: ((_F = element.left) != null ? _F : 0) / ratioPx2Inch,
1090
+ y: ((_G = element.top) != null ? _G : 0) / ratioPx2Inch,
1091
+ w: (maxX - minX) / ratioPx2Inch,
1092
+ h: (maxY - minY) / ratioPx2Inch,
1093
+ line: {
1094
+ color: c.color,
1095
+ transparency: (1 - c.alpha) * 100,
1096
+ width: ((_H = element.width) != null ? _H : 1) / ratioPx2Pt,
1097
+ dashType: element.style ? dashTypeMap[element.style] : "solid",
1098
+ beginArrowType: ((_I = element.points) == null ? void 0 : _I[0]) ? "arrow" : "none",
1099
+ endArrowType: ((_J = element.points) == null ? void 0 : _J[1]) ? "arrow" : "none"
1100
+ },
1101
+ points
1102
+ };
1103
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1104
+ slide.addShape("custGeom", options);
1105
+ }
1106
+ }
1107
+ }
1108
+ const fileName = `${sanitizeFileName((_K = template.title) != null ? _K : "presentation")}.pptx`;
1109
+ const pptxBuffer = await pptx.write({
1110
+ outputType: "arraybuffer",
1111
+ compression: true
1112
+ });
1113
+ if (!ENABLE_DECK_JSON) {
1114
+ return { blob: new Blob([pptxBuffer]), fileName };
1115
+ }
1116
+ const zip = await import_jszip.default.loadAsync(pptxBuffer);
1117
+ zip.file(
1118
+ PPTX_JSON_PAYLOAD_PATH,
1119
+ JSON.stringify(
1120
+ {
1121
+ version: PPTX_JSON_PAYLOAD_VERSION,
1122
+ deck: template
1123
+ },
1124
+ null,
1125
+ 2
1126
+ )
1127
+ );
1128
+ const blob = await zip.generateAsync({ type: "blob" });
1129
+ return { blob, fileName };
1130
+ }
1131
+ // Annotate the CommonJS export names for ESM import in node:
1132
+ 0 && (module.exports = {
1133
+ ENABLE_DECK_JSON,
1134
+ PPTX_JSON_PAYLOAD_PATH,
1135
+ PPTX_JSON_PAYLOAD_VERSION,
1136
+ buildPptxBlob,
1137
+ getElementRange,
1138
+ getLineElementPath,
1139
+ resolveImageData
1140
+ });
1141
+ //# sourceMappingURL=index.js.map