json2pptx 0.2.0 → 0.2.2

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 CHANGED
@@ -41,7 +41,59 @@ __export(index_exports, {
41
41
  module.exports = __toCommonJS(index_exports);
42
42
  var import_pptxgenjs = __toESM(require("pptxgenjs"));
43
43
  var import_jszip = __toESM(require("jszip"));
44
- var import_tinycolor2 = __toESM(require("tinycolor2"));
44
+
45
+ // src/element.ts
46
+ var getElementRange = (element) => {
47
+ var _a, _b, _c, _d, _e, _f;
48
+ let minX = 0;
49
+ let maxX = 0;
50
+ let minY = 0;
51
+ let maxY = 0;
52
+ if (element.type === "line" && element.start && element.end) {
53
+ minX = (_a = element.left) != null ? _a : 0;
54
+ maxX = ((_b = element.left) != null ? _b : 0) + Math.max(element.start[0], element.end[0]);
55
+ minY = (_c = element.top) != null ? _c : 0;
56
+ maxY = ((_d = element.top) != null ? _d : 0) + Math.max(element.start[1], element.end[1]);
57
+ } else if (element.left !== void 0 && element.top !== void 0) {
58
+ minX = element.left;
59
+ minY = element.top;
60
+ maxX = element.left + ((_e = element.width) != null ? _e : 0);
61
+ maxY = element.top + ((_f = element.height) != null ? _f : 0);
62
+ }
63
+ return { minX, maxX, minY, maxY };
64
+ };
65
+ var isLineElement = (element) => element.type === "line";
66
+ var getLineElementPath = (element) => {
67
+ if (!isLineElement(element) || !element.start || !element.end) return "";
68
+ const start = element.start.join(",");
69
+ const end = element.end.join(",");
70
+ const broken = element.broken;
71
+ const broken2 = element.broken2;
72
+ const curve = element.curve;
73
+ const cubic = element.cubic;
74
+ if (broken) {
75
+ const mid = broken.join(",");
76
+ return `M${start} L${mid} L${end}`;
77
+ }
78
+ if (broken2) {
79
+ const { minX, maxX, minY, maxY } = getElementRange(element);
80
+ if (maxX - minX >= maxY - minY) {
81
+ return `M${start} L${broken2[0]},${element.start[1]} L${broken2[0]},${element.end[1]} ${end}`;
82
+ }
83
+ return `M${start} L${element.start[0]},${broken2[1]} L${element.end[0]},${broken2[1]} ${end}`;
84
+ }
85
+ if (curve) {
86
+ const mid = curve.join(",");
87
+ return `M${start} Q${mid} ${end}`;
88
+ }
89
+ if (cubic) {
90
+ const [c1, c2] = cubic;
91
+ const p1 = c1.join(",");
92
+ const p2 = c2.join(",");
93
+ return `M${start} C${p1} ${p2} ${end}`;
94
+ }
95
+ return `M${start} L${end}`;
96
+ };
45
97
 
46
98
  // src/resolveImageData.ts
47
99
  var dataUrlRegex = /^data:image\/[^;]+;base64,/;
@@ -97,6 +149,15 @@ var resolveImageData = async (src) => {
97
149
  return `data:${contentType};base64,${base64}`;
98
150
  };
99
151
 
152
+ // src/renderers/constants.ts
153
+ var DEFAULT_WIDTH = 1e3;
154
+ var DEFAULT_HEIGHT = 562.5;
155
+ var DEFAULT_FONT_SIZE = 16;
156
+ var DEFAULT_FONT_FACE = "\u5FAE\u8F6F\u96C5\u9ED1";
157
+
158
+ // src/renderers/shared.ts
159
+ var import_tinycolor2 = __toESM(require("tinycolor2"));
160
+
100
161
  // src/htmlParser/tags.ts
101
162
  var childlessTags = ["style", "script", "template"];
102
163
  var closingTags = [
@@ -534,160 +595,12 @@ var toAST = (str) => {
534
595
  return format(nodes);
535
596
  };
536
597
 
537
- // src/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
- // src/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}`;
598
+ // src/renderers/shared.ts
599
+ var dashTypeMap = {
600
+ solid: "solid",
601
+ dashed: "dash",
602
+ dotted: "sysDot"
678
603
  };
679
-
680
- // src/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
604
  function formatColor(input) {
692
605
  if (!input) {
693
606
  return {
@@ -827,114 +740,32 @@ function formatHTML(html, ratioPx2Pt) {
827
740
  parse2(ast);
828
741
  return slices;
829
742
  }
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 stripFillTags = (value) => value.replace(/<a:solidFill>[\s\S]*?<\/a:solidFill>/g, "").replace(/<a:gradFill>[\s\S]*?<\/a:gradFill>/g, "").replace(/<a:blipFill>[\s\S]*?<\/a:blipFill>/g, "").replace(/<a:noFill\s*\/>/g, "").replace(/<a:noFill><\/a:noFill>/g, "");
880
- var applyPatternFill = (slideXml, objectName, relId) => {
881
- const nameToken = `name="${objectName}"`;
882
- let cursor = 0;
883
- let result = slideXml;
884
- while (true) {
885
- const nameIndex = result.indexOf(nameToken, cursor);
886
- if (nameIndex === -1) break;
887
- const spStart = result.lastIndexOf("<p:sp", nameIndex);
888
- const spEnd = result.indexOf("</p:sp>", nameIndex);
889
- if (spStart === -1 || spEnd === -1) break;
890
- const spXml = result.slice(spStart, spEnd + "</p:sp>".length);
891
- const spPrStart = spXml.indexOf("<p:spPr>");
892
- const spPrEnd = spXml.indexOf("</p:spPr>");
893
- if (spPrStart === -1 || spPrEnd === -1) {
894
- cursor = spEnd + 1;
895
- continue;
896
- }
897
- const spPrOpenEnd = spXml.indexOf(">", spPrStart);
898
- const spPrInner = spXml.slice(spPrOpenEnd + 1, spPrEnd);
899
- const cleanedInner = stripFillTags(spPrInner);
900
- const blipFill = `<a:blipFill><a:blip r:embed="${relId}"/><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill>`;
901
- let nextInner = cleanedInner;
902
- if (cleanedInner.includes("</a:custGeom>")) {
903
- nextInner = cleanedInner.replace("</a:custGeom>", `</a:custGeom>${blipFill}`);
904
- } else {
905
- const lnIndex = cleanedInner.indexOf("<a:ln");
906
- nextInner = lnIndex === -1 ? `${cleanedInner}${blipFill}` : `${cleanedInner.slice(0, lnIndex)}${blipFill}${cleanedInner.slice(lnIndex)}`;
907
- }
908
- const updatedSpXml = spXml.slice(0, spPrOpenEnd + 1) + nextInner + spXml.slice(spPrEnd);
909
- result = result.slice(0, spStart) + updatedSpXml + result.slice(spEnd + "</p:sp>".length);
910
- cursor = spStart + updatedSpXml.length;
743
+ function normalizeFontName(value) {
744
+ return value ? value.replace(/^"+|"+$/g, "") : void 0;
745
+ }
746
+ function clampOpacity(value) {
747
+ if (value === void 0) return 1;
748
+ if (!Number.isFinite(value)) return 1;
749
+ return Math.min(1, Math.max(0, value));
750
+ }
751
+ function getOpacityRatio(value) {
752
+ if (!value) return void 0;
753
+ const normalized = value.trim();
754
+ if (!normalized) return void 0;
755
+ if (normalized.endsWith("%")) {
756
+ const percent = Number.parseFloat(normalized);
757
+ return Number.isFinite(percent) ? Math.min(1, Math.max(0, percent / 100)) : void 0;
911
758
  }
912
- return result;
913
- };
914
- var parseDataUrlImage = (dataUrl) => {
915
- var _a;
916
- const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
917
- if (!match) return null;
918
- const mime = match[1];
919
- const data = match[2];
920
- const extMap = {
921
- "image/jpeg": "jpeg",
922
- "image/jpg": "jpg",
923
- "image/png": "png",
924
- "image/gif": "gif",
925
- "image/svg+xml": "svg",
926
- "image/webp": "webp",
927
- "image/bmp": "bmp"
928
- };
929
- return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
930
- };
931
- var normalizeFontName = (value) => value ? value.replace(/^"+|"+$/g, "") : void 0;
932
- var parseFontSize = (value) => {
759
+ const numeric = Number.parseFloat(normalized);
760
+ if (!Number.isFinite(numeric)) return void 0;
761
+ return numeric > 1 ? Math.min(1, Math.max(0, numeric / 100)) : Math.min(1, Math.max(0, numeric));
762
+ }
763
+ function parseFontSize(value) {
933
764
  if (!value) return void 0;
934
765
  const size = Number.parseFloat(value);
935
766
  return Number.isFinite(size) ? size : void 0;
936
- };
937
- var parseTableColor = (value) => {
767
+ }
768
+ function parseTableColor(value) {
938
769
  if (!value) return void 0;
939
770
  const normalized = value.trim();
940
771
  if (!normalized) return void 0;
@@ -943,8 +774,8 @@ var parseTableColor = (value) => {
943
774
  color: c.color.replace("#", ""),
944
775
  transparency: (1 - c.alpha) * 100
945
776
  };
946
- };
947
- var isPlaceholderCell = (cell) => {
777
+ }
778
+ function isPlaceholderCell(cell) {
948
779
  var _a, _b, _c;
949
780
  if (!cell) return false;
950
781
  const colspan = (_a = cell.colspan) != null ? _a : 1;
@@ -954,15 +785,15 @@ var isPlaceholderCell = (cell) => {
954
785
  const style = cell.style;
955
786
  const hasStyle = Boolean(style == null ? void 0 : style.fontname) || Boolean(style == null ? void 0 : style.fontsize) || Boolean(style == null ? void 0 : style.color) || Boolean(style == null ? void 0 : style.backcolor);
956
787
  return text.trim() === "" && !hasStyle;
957
- };
958
- var buildTableRows = (element, ratioPx2Pt) => {
788
+ }
789
+ function buildTableRows(element, ratioPx2Pt, ratioPx2Inch) {
959
790
  var _a, _b;
960
791
  const data = element.data;
961
792
  if (!data || !data.length) return [];
962
793
  const colCount = (_b = (_a = element.colWidths) == null ? void 0 : _a.length) != null ? _b : Math.max(...data.map((row) => row.length));
963
794
  const rows = [];
964
795
  const skip = new Array(colCount).fill(0);
965
- data.forEach((row, rowIndex) => {
796
+ data.forEach((row) => {
966
797
  var _a2, _b2, _c, _d;
967
798
  const cells = [];
968
799
  let colIndex = 0;
@@ -997,6 +828,12 @@ var buildTableRows = (element, ratioPx2Pt) => {
997
828
  const fontSize = parseFontSize(style.fontsize);
998
829
  const fill = parseTableColor(style.backcolor);
999
830
  const color = parseTableColor(style.color);
831
+ const cellMargin = [
832
+ 6 / ratioPx2Inch,
833
+ 8 / ratioPx2Inch,
834
+ 6 / ratioPx2Inch,
835
+ 8 / ratioPx2Inch
836
+ ];
1000
837
  const options = {
1001
838
  colspan: colSpan > 1 ? colSpan : void 0,
1002
839
  rowspan: rowSpan > 1 ? rowSpan : void 0,
@@ -1006,7 +843,7 @@ var buildTableRows = (element, ratioPx2Pt) => {
1006
843
  fontSize: fontSize ? fontSize / ratioPx2Pt : void 0,
1007
844
  color: color == null ? void 0 : color.color,
1008
845
  fill: fill ? { color: fill.color, transparency: fill.transparency } : void 0,
1009
- margin: 0
846
+ margin: cellMargin
1010
847
  };
1011
848
  cells.push({
1012
849
  text: (_d = cell.text) != null ? _d : "",
@@ -1017,8 +854,8 @@ var buildTableRows = (element, ratioPx2Pt) => {
1017
854
  rows.push(cells);
1018
855
  });
1019
856
  return rows;
1020
- };
1021
- var getShadowOption = (shadow, ratioPx2Pt) => {
857
+ }
858
+ function getShadowOption(shadow, ratioPx2Pt) {
1022
859
  var _a, _b;
1023
860
  const c = formatColor((_a = shadow.color) != null ? _a : "#000000");
1024
861
  const { h = 0, v = 0 } = shadow;
@@ -1057,241 +894,531 @@ var getShadowOption = (shadow, ratioPx2Pt) => {
1057
894
  angle = 225;
1058
895
  }
1059
896
  return {
1060
- type: "outer",
1061
- color: c.color.replace("#", ""),
1062
- opacity: c.alpha,
897
+ color: c.color,
898
+ transparency: (1 - c.alpha) * 100,
1063
899
  blur: ((_b = shadow.blur) != null ? _b : 0) / ratioPx2Pt,
1064
900
  offset,
1065
901
  angle
1066
902
  };
1067
- };
1068
- var getOutlineOption = (outline, ratioPx2Pt) => {
903
+ }
904
+ function getOutlineOption(outline, ratioPx2Pt, opacity = 1) {
1069
905
  const c = formatColor(outline.color || "#000000");
906
+ const alpha = c.alpha * clampOpacity(opacity);
1070
907
  return {
1071
908
  color: c.color,
1072
- transparency: (1 - c.alpha) * 100,
909
+ transparency: (1 - alpha) * 100,
1073
910
  width: (outline.width || 1) / ratioPx2Pt,
1074
911
  dashType: outline.style ? dashTypeMap[outline.style] : "solid"
1075
912
  };
1076
- };
1077
- var isBase64Image = (url) => {
1078
- const regex = /^data:image\/[^;]+;base64,/;
1079
- return regex.test(url);
1080
- };
1081
- async function buildPptxBlob(template) {
1082
- 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, _L, _M, _N, _O, _P, _Q, _R, _S, _T;
1083
- const pptx = new import_pptxgenjs.default();
1084
- const patternShapes = [];
1085
- const width = (_a = template.width) != null ? _a : DEFAULT_WIDTH;
1086
- const height = (_b = template.height) != null ? _b : DEFAULT_HEIGHT;
913
+ }
914
+ function getElementOpacity(value) {
915
+ return clampOpacity(value);
916
+ }
917
+ function getFilterOpacity(value) {
918
+ return getOpacityRatio(value);
919
+ }
920
+ function getDashTypeMap() {
921
+ return dashTypeMap;
922
+ }
923
+
924
+ // src/renderers/background.ts
925
+ function applySlideBackground(slide, slideJson, theme) {
926
+ var _a, _b;
927
+ const backgroundColor = (_b = (_a = slideJson.background) == null ? void 0 : _a.color) != null ? _b : theme == null ? void 0 : theme.backgroundColor;
928
+ if (!backgroundColor) return;
929
+ const c = formatColor(backgroundColor);
930
+ slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
931
+ }
932
+
933
+ // src/renderers/layout.ts
934
+ function applyPptxLayout(pptx, width, height) {
1087
935
  const viewportRatio = height / width;
1088
- const ratioPx2Inch = 96 * (width / 960);
1089
- const ratioPx2Pt = 96 / 72 * (width / 960);
1090
- if (Math.abs(viewportRatio - 0.625) < 1e-3) pptx.layout = "LAYOUT_16x10";
1091
- else if (Math.abs(viewportRatio - 0.75) < 1e-3) pptx.layout = "LAYOUT_4x3";
1092
- else if (Math.abs(viewportRatio - 0.70710678) < 1e-4) {
936
+ if (Math.abs(viewportRatio - 0.625) < 1e-3) {
937
+ pptx.layout = "LAYOUT_16x10";
938
+ return;
939
+ }
940
+ if (Math.abs(viewportRatio - 0.75) < 1e-3) {
941
+ pptx.layout = "LAYOUT_4x3";
942
+ return;
943
+ }
944
+ if (Math.abs(viewportRatio - 0.70710678) < 1e-4) {
1093
945
  pptx.defineLayout({ name: "A3", width: 10, height: 7.0710678 });
1094
946
  pptx.layout = "A3";
1095
- } else if (Math.abs(viewportRatio - 1.41421356) < 1e-4) {
947
+ return;
948
+ }
949
+ if (Math.abs(viewportRatio - 1.41421356) < 1e-4) {
1096
950
  pptx.defineLayout({ name: "A3_V", width: 10, height: 14.1421356 });
1097
951
  pptx.layout = "A3_V";
1098
- } else pptx.layout = "LAYOUT_16x9";
1099
- for (const [slideIndex, slideJson] of ((_c = template.slides) != null ? _c : []).entries()) {
1100
- const slide = pptx.addSlide();
1101
- const backgroundColor = (_d = slideJson.background) == null ? void 0 : _d.color;
1102
- if (backgroundColor) {
1103
- const c = formatColor(backgroundColor);
1104
- slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
952
+ return;
953
+ }
954
+ pptx.layout = "LAYOUT_16x9";
955
+ }
956
+
957
+ // src/svgPathParser.ts
958
+ var import_svg_pathdata = require("svg-pathdata");
959
+ var import_svg_arc_to_cubic_bezier = __toESM(require("svg-arc-to-cubic-bezier"));
960
+ var typeMap = {
961
+ 1: "Z",
962
+ 2: "M",
963
+ 4: "H",
964
+ 8: "V",
965
+ 16: "L",
966
+ 32: "C",
967
+ 64: "S",
968
+ 128: "Q",
969
+ 256: "T",
970
+ 512: "A"
971
+ };
972
+ var toPoints = (d) => {
973
+ if (!d) return [];
974
+ if (d.includes("NaN") || d.includes("undefined") || d.includes("null")) return [];
975
+ let pathData;
976
+ try {
977
+ pathData = new import_svg_pathdata.SVGPathData(d);
978
+ } catch {
979
+ return [];
980
+ }
981
+ const points = [];
982
+ for (const item of pathData.commands) {
983
+ const type = typeMap[item.type];
984
+ if (item.type === 2 || item.type === 16) {
985
+ points.push({
986
+ x: item.x,
987
+ y: item.y,
988
+ relative: item.relative,
989
+ type
990
+ });
1105
991
  }
1106
- for (const [elementIndex, element] of ((_e = slideJson.elements) != null ? _e : []).entries()) {
1107
- if (element.type === "text" && element.content) {
1108
- const textProps = formatHTML(element.content, ratioPx2Pt);
1109
- const options = {
1110
- x: ((_f = element.left) != null ? _f : 0) / ratioPx2Inch,
1111
- y: ((_g = element.top) != null ? _g : 0) / ratioPx2Inch,
1112
- w: ((_h = element.width) != null ? _h : 0) / ratioPx2Inch,
1113
- h: ((_i = element.height) != null ? _i : 0) / ratioPx2Inch,
1114
- fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1115
- fontFace: element.defaultFontName || ((_j = template.theme) == null ? void 0 : _j.fontName) || DEFAULT_FONT_FACE,
1116
- color: "#000000",
1117
- valign: "top",
1118
- margin: 0,
1119
- paraSpaceBefore: 5 / ratioPx2Pt,
1120
- lineSpacingMultiple: 1.5 / 1.25,
1121
- autoFit: true
1122
- };
1123
- if (element.rotate) options.rotate = element.rotate;
1124
- if (element.wordSpace) options.charSpacing = element.wordSpace / ratioPx2Pt;
1125
- if (element.lineHeight) options.lineSpacingMultiple = element.lineHeight / 1.25;
1126
- if (element.fill) {
1127
- const c = formatColor(element.fill);
1128
- const opacity = element.opacity === void 0 ? 1 : element.opacity;
1129
- options.fill = {
1130
- color: c.color,
1131
- transparency: (1 - c.alpha * opacity) * 100
1132
- };
1133
- } else {
1134
- options.fill = { color: "FFFFFF", transparency: 100 };
1135
- }
1136
- if (element.defaultColor) options.color = formatColor(element.defaultColor).color;
1137
- if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1138
- if ((_k = element.outline) == null ? void 0 : _k.width) options.line = getOutlineOption(element.outline, ratioPx2Pt);
1139
- if (element.opacity !== void 0) options.transparency = (1 - element.opacity) * 100;
1140
- if (element.paragraphSpace !== void 0) {
1141
- options.paraSpaceBefore = element.paragraphSpace / ratioPx2Pt;
1142
- }
1143
- if (element.vertical) options.vert = "eaVert";
1144
- slide.addText(textProps, options);
1145
- continue;
992
+ if (item.type === 32) {
993
+ points.push({
994
+ x: item.x,
995
+ y: item.y,
996
+ curve: {
997
+ type: "cubic",
998
+ x1: item.x1,
999
+ y1: item.y1,
1000
+ x2: item.x2,
1001
+ y2: item.y2
1002
+ },
1003
+ relative: item.relative,
1004
+ type
1005
+ });
1006
+ } else if (item.type === 128) {
1007
+ points.push({
1008
+ x: item.x,
1009
+ y: item.y,
1010
+ curve: {
1011
+ type: "quadratic",
1012
+ x1: item.x1,
1013
+ y1: item.y1
1014
+ },
1015
+ relative: item.relative,
1016
+ type
1017
+ });
1018
+ } else if (item.type === 512) {
1019
+ const lastPoint = points[points.length - 1];
1020
+ if (!lastPoint || !["M", "L", "Q", "C"].includes(lastPoint.type)) continue;
1021
+ const cubicBezierPoints = (0, import_svg_arc_to_cubic_bezier.default)({
1022
+ px: lastPoint.x,
1023
+ py: lastPoint.y,
1024
+ cx: item.x,
1025
+ cy: item.y,
1026
+ rx: item.rX,
1027
+ ry: item.rY,
1028
+ xAxisRotation: item.xRot,
1029
+ largeArcFlag: item.lArcFlag,
1030
+ sweepFlag: item.sweepFlag
1031
+ });
1032
+ for (const cbPoint of cubicBezierPoints) {
1033
+ points.push({
1034
+ x: cbPoint.x,
1035
+ y: cbPoint.y,
1036
+ curve: {
1037
+ type: "cubic",
1038
+ x1: cbPoint.x1,
1039
+ y1: cbPoint.y1,
1040
+ x2: cbPoint.x2,
1041
+ y2: cbPoint.y2
1042
+ },
1043
+ relative: false,
1044
+ type: "C"
1045
+ });
1146
1046
  }
1147
- if (element.type === "image" && element.src) {
1148
- const options = {
1149
- x: ((_l = element.left) != null ? _l : 0) / ratioPx2Inch,
1150
- y: ((_m = element.top) != null ? _m : 0) / ratioPx2Inch,
1151
- w: ((_n = element.width) != null ? _n : 0) / ratioPx2Inch,
1152
- h: ((_o = element.height) != null ? _o : 0) / ratioPx2Inch
1047
+ } else if (item.type === 1) {
1048
+ points.push({ close: true, type });
1049
+ } else continue;
1050
+ }
1051
+ return points;
1052
+ };
1053
+
1054
+ // src/renderers/points.ts
1055
+ function formatPoints(points, ratioPx2Inch, scale = { x: 1, y: 1 }) {
1056
+ return points.map((point) => {
1057
+ if ("close" in point) {
1058
+ return { close: true };
1059
+ }
1060
+ if (point.type === "M") {
1061
+ return {
1062
+ x: point.x / ratioPx2Inch * scale.x,
1063
+ y: point.y / ratioPx2Inch * scale.y,
1064
+ moveTo: true
1065
+ };
1066
+ }
1067
+ if (point.curve) {
1068
+ if (point.curve.type === "cubic") {
1069
+ return {
1070
+ x: point.x / ratioPx2Inch * scale.x,
1071
+ y: point.y / ratioPx2Inch * scale.y,
1072
+ curve: {
1073
+ type: "cubic",
1074
+ x1: point.curve.x1 / ratioPx2Inch * scale.x,
1075
+ y1: point.curve.y1 / ratioPx2Inch * scale.y,
1076
+ x2: point.curve.x2 / ratioPx2Inch * scale.x,
1077
+ y2: point.curve.y2 / ratioPx2Inch * scale.y
1078
+ }
1153
1079
  };
1154
- if (isBase64Image(element.src)) options.data = element.src;
1155
- else options.data = await resolveImageData(element.src);
1156
- if (element.flipH) options.flipH = element.flipH;
1157
- if (element.flipV) options.flipV = element.flipV;
1158
- if (element.rotate) options.rotate = element.rotate;
1159
- if ((_p = element.filters) == null ? void 0 : _p.opacity) {
1160
- options.transparency = 100 - parseInt(element.filters.opacity, 10);
1161
- }
1162
- if ((_q = element.clip) == null ? void 0 : _q.range) {
1163
- if (element.clip.shape === "ellipse") options.rounding = true;
1164
- const [start, end] = element.clip.range;
1165
- const [startX, startY] = start;
1166
- const [endX, endY] = end;
1167
- const originW = ((_r = element.width) != null ? _r : 0) / ((endX - startX) / ratioPx2Inch);
1168
- const originH = ((_s = element.height) != null ? _s : 0) / ((endY - startY) / ratioPx2Inch);
1169
- options.w = originW / ratioPx2Inch;
1170
- options.h = originH / ratioPx2Inch;
1171
- options.sizing = {
1172
- type: "crop",
1173
- x: startX / ratioPx2Inch * (originW / ratioPx2Inch),
1174
- y: startY / ratioPx2Inch * (originH / ratioPx2Inch),
1175
- w: (endX - startX) / ratioPx2Inch * (originW / ratioPx2Inch),
1176
- h: (endY - startY) / ratioPx2Inch * (originH / ratioPx2Inch)
1177
- };
1178
- }
1179
- slide.addImage(options);
1180
- continue;
1181
1080
  }
1182
- if (element.type === "shape" && element.path && element.viewBox) {
1183
- const scale = {
1184
- x: ((_t = element.width) != null ? _t : 0) / element.viewBox[0],
1185
- y: ((_u = element.height) != null ? _u : 0) / element.viewBox[1]
1186
- };
1187
- const points = formatPoints(toPoints(element.path), ratioPx2Inch, scale);
1188
- const pattern = element.pattern;
1189
- const hasFill = typeof element.fill === "string" ? element.fill.trim().length > 0 : false;
1190
- const opacity = element.opacity === void 0 ? 1 : element.opacity;
1191
- const options = {
1192
- x: ((_v = element.left) != null ? _v : 0) / ratioPx2Inch,
1193
- y: ((_w = element.top) != null ? _w : 0) / ratioPx2Inch,
1194
- w: ((_x = element.width) != null ? _x : 0) / ratioPx2Inch,
1195
- h: ((_y = element.height) != null ? _y : 0) / ratioPx2Inch,
1196
- points
1197
- };
1198
- if (pattern) {
1199
- const objectName = `pattern-${slideIndex}-${(_z = element.id) != null ? _z : elementIndex}`;
1200
- options.objectName = objectName;
1201
- options.fill = { color: "FFFFFF", transparency: 100 };
1202
- patternShapes.push({ slideIndex, objectName, dataUrl: pattern });
1203
- } else if (hasFill) {
1204
- const fillColor = formatColor(element.fill || "#000000");
1205
- options.fill = {
1206
- color: fillColor.color,
1207
- transparency: (1 - fillColor.alpha * opacity) * 100
1208
- };
1209
- } else {
1210
- options.fill = { color: "FFFFFF", transparency: 100 };
1211
- }
1212
- if (element.flipH) options.flipH = element.flipH;
1213
- if (element.flipV) options.flipV = element.flipV;
1214
- if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1215
- if ((_A = element.outline) == null ? void 0 : _A.width) options.line = getOutlineOption(element.outline, ratioPx2Pt);
1216
- if (element.rotate) options.rotate = element.rotate;
1217
- slide.addShape("custGeom", options);
1218
- if ((_B = element.text) == null ? void 0 : _B.content) {
1219
- const textProps = formatHTML(element.text.content, ratioPx2Pt);
1220
- const textOptions = {
1221
- x: ((_C = element.left) != null ? _C : 0) / ratioPx2Inch,
1222
- y: ((_D = element.top) != null ? _D : 0) / ratioPx2Inch,
1223
- w: ((_E = element.width) != null ? _E : 0) / ratioPx2Inch,
1224
- h: ((_F = element.height) != null ? _F : 0) / ratioPx2Inch,
1225
- fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1226
- fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
1227
- color: "#000000",
1228
- paraSpaceBefore: 5 / ratioPx2Pt,
1229
- valign: element.text.align,
1230
- fill: { color: "FFFFFF", transparency: 100 }
1231
- };
1232
- textOptions.margin = 0;
1233
- if (element.rotate) textOptions.rotate = element.rotate;
1234
- if (element.text.defaultColor) {
1235
- textOptions.color = formatColor(element.text.defaultColor).color;
1081
+ if (point.curve.type === "quadratic") {
1082
+ return {
1083
+ x: point.x / ratioPx2Inch * scale.x,
1084
+ y: point.y / ratioPx2Inch * scale.y,
1085
+ curve: {
1086
+ type: "quadratic",
1087
+ x1: point.curve.x1 / ratioPx2Inch * scale.x,
1088
+ y1: point.curve.y1 / ratioPx2Inch * scale.y
1236
1089
  }
1237
- slide.addText(textProps, textOptions);
1238
- }
1239
- continue;
1240
- }
1241
- if (element.type === "line" && element.start && element.end) {
1242
- const path = getLineElementPath(element);
1243
- const points = formatPoints(toPoints(path), ratioPx2Inch);
1244
- const { minX, maxX, minY, maxY } = getElementRange(element);
1245
- const c = formatColor(element.color || "#000000");
1246
- const options = {
1247
- x: ((_G = element.left) != null ? _G : 0) / ratioPx2Inch,
1248
- y: ((_H = element.top) != null ? _H : 0) / ratioPx2Inch,
1249
- w: (maxX - minX) / ratioPx2Inch,
1250
- h: (maxY - minY) / ratioPx2Inch,
1251
- line: {
1252
- color: c.color,
1253
- transparency: (1 - c.alpha) * 100,
1254
- width: ((_I = element.width) != null ? _I : 1) / ratioPx2Pt,
1255
- dashType: element.style ? dashTypeMap[element.style] : "solid",
1256
- beginArrowType: ((_J = element.points) == null ? void 0 : _J[0]) ? "arrow" : "none",
1257
- endArrowType: ((_K = element.points) == null ? void 0 : _K[1]) ? "arrow" : "none"
1258
- },
1259
- points
1260
1090
  };
1261
- if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1262
- slide.addShape("custGeom", options);
1263
- }
1264
- if (element.type === "table") {
1265
- const rows = buildTableRows(element, ratioPx2Pt);
1266
- if (!rows.length) continue;
1267
- const colWidths = element.colWidths;
1268
- const colW = colWidths ? colWidths.map((ratio) => {
1269
- var _a2;
1270
- return ((_a2 = element.width) != null ? _a2 : 0) * ratio / ratioPx2Inch;
1271
- }) : void 0;
1272
- const rowCount = rows.length || 1;
1273
- const baseRowHeight = ((_L = element.height) != null ? _L : 0) / rowCount / ratioPx2Inch;
1274
- const minRowHeight = element.cellMinHeight ? element.cellMinHeight / ratioPx2Inch : void 0;
1275
- const rowH = minRowHeight ? new Array(rowCount).fill(Math.max(minRowHeight, baseRowHeight)) : void 0;
1276
- const outline = element.outline;
1277
- const border = (outline == null ? void 0 : outline.width) || (outline == null ? void 0 : outline.color) ? {
1278
- pt: ((_M = outline.width) != null ? _M : 1) / ratioPx2Pt,
1279
- color: formatColor(outline.color || "#000000").color.replace("#", "")
1280
- } : void 0;
1281
- slide.addTable(rows, {
1282
- x: ((_N = element.left) != null ? _N : 0) / ratioPx2Inch,
1283
- y: ((_O = element.top) != null ? _O : 0) / ratioPx2Inch,
1284
- w: ((_P = element.width) != null ? _P : 0) / ratioPx2Inch,
1285
- h: ((_Q = element.height) != null ? _Q : 0) / ratioPx2Inch,
1286
- colW,
1287
- rowH,
1288
- border,
1289
- margin: 0
1290
- });
1291
1091
  }
1292
1092
  }
1093
+ return {
1094
+ x: point.x / ratioPx2Inch * scale.x,
1095
+ y: point.y / ratioPx2Inch * scale.y
1096
+ };
1097
+ });
1098
+ }
1099
+
1100
+ // src/renderers/utils.ts
1101
+ function isBase64Image(url) {
1102
+ const regex = /^data:image\/[^;]+;base64,/;
1103
+ return regex.test(url);
1104
+ }
1105
+ function getLineArrowType(value) {
1106
+ if (value === "arrow") return "arrow";
1107
+ if (value === "dot") return "oval";
1108
+ return "none";
1109
+ }
1110
+
1111
+ // src/renderers/elements.ts
1112
+ function addTextElement(slide, element, template, ratioPx2Pt, ratioPx2Inch, textPadding) {
1113
+ var _a, _b, _c, _d, _e, _f;
1114
+ if (element.type !== "text" || !element.content) return;
1115
+ const textProps = formatHTML(element.content, ratioPx2Pt);
1116
+ const opacity = getElementOpacity(element.opacity);
1117
+ const options = {
1118
+ x: ((_a = element.left) != null ? _a : 0) / ratioPx2Inch,
1119
+ y: ((_b = element.top) != null ? _b : 0) / ratioPx2Inch,
1120
+ w: ((_c = element.width) != null ? _c : 0) / ratioPx2Inch,
1121
+ h: ((_d = element.height) != null ? _d : 0) / ratioPx2Inch,
1122
+ fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1123
+ fontFace: element.defaultFontName || ((_e = template.theme) == null ? void 0 : _e.fontName) || DEFAULT_FONT_FACE,
1124
+ color: "#000000",
1125
+ valign: "top",
1126
+ margin: textPadding,
1127
+ paraSpaceBefore: 0,
1128
+ lineSpacingMultiple: 1.5,
1129
+ fit: "none"
1130
+ };
1131
+ if (element.rotate) options.rotate = element.rotate;
1132
+ if (element.wordSpace) options.charSpacing = element.wordSpace / ratioPx2Pt;
1133
+ if (element.lineHeight) options.lineSpacingMultiple = element.lineHeight;
1134
+ if (element.fill) {
1135
+ const c = formatColor(element.fill);
1136
+ options.fill = {
1137
+ color: c.color,
1138
+ transparency: (1 - c.alpha * opacity) * 100
1139
+ };
1140
+ } else {
1141
+ options.fill = { color: "FFFFFF", transparency: 100 };
1142
+ }
1143
+ if (element.defaultColor) options.color = formatColor(element.defaultColor).color;
1144
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1145
+ if ((_f = element.outline) == null ? void 0 : _f.width) {
1146
+ options.line = getOutlineOption(element.outline, ratioPx2Pt, opacity);
1147
+ }
1148
+ if (element.opacity !== void 0) options.transparency = (1 - opacity) * 100;
1149
+ if (element.paragraphSpace !== void 0) {
1150
+ options.paraSpaceBefore = element.paragraphSpace / ratioPx2Pt;
1151
+ }
1152
+ if (element.vertical) options.vert = "eaVert";
1153
+ if (element.flipH) options.flipH = element.flipH;
1154
+ if (element.flipV) options.flipV = element.flipV;
1155
+ slide.addText(textProps, options);
1156
+ }
1157
+ async function addImageElement(slide, element, ratioPx2Inch) {
1158
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1159
+ if (element.type !== "image" || !element.src) return;
1160
+ const options = {
1161
+ x: ((_a = element.left) != null ? _a : 0) / ratioPx2Inch,
1162
+ y: ((_b = element.top) != null ? _b : 0) / ratioPx2Inch,
1163
+ w: ((_c = element.width) != null ? _c : 0) / ratioPx2Inch,
1164
+ h: ((_d = element.height) != null ? _d : 0) / ratioPx2Inch
1165
+ };
1166
+ if (isBase64Image(element.src)) options.data = element.src;
1167
+ else options.data = await resolveImageData(element.src);
1168
+ if (element.flipH) options.flipH = element.flipH;
1169
+ if (element.flipV) options.flipV = element.flipV;
1170
+ if (element.rotate) options.rotate = element.rotate;
1171
+ const filterOpacity = (_f = getFilterOpacity((_e = element.filters) == null ? void 0 : _e.opacity)) != null ? _f : 1;
1172
+ const elementOpacity = getElementOpacity(element.opacity);
1173
+ const imageOpacity = filterOpacity * elementOpacity;
1174
+ if (imageOpacity !== 1) {
1175
+ options.transparency = (1 - imageOpacity) * 100;
1176
+ }
1177
+ if ((_g = element.clip) == null ? void 0 : _g.range) {
1178
+ if (element.clip.shape === "ellipse") options.rounding = true;
1179
+ const [start, end] = element.clip.range;
1180
+ const [startX, startY] = start;
1181
+ const [endX, endY] = end;
1182
+ const originW = ((_h = element.width) != null ? _h : 0) / ((endX - startX) / ratioPx2Inch);
1183
+ const originH = ((_i = element.height) != null ? _i : 0) / ((endY - startY) / ratioPx2Inch);
1184
+ options.w = originW / ratioPx2Inch;
1185
+ options.h = originH / ratioPx2Inch;
1186
+ options.sizing = {
1187
+ type: "crop",
1188
+ x: startX / ratioPx2Inch * (originW / ratioPx2Inch),
1189
+ y: startY / ratioPx2Inch * (originH / ratioPx2Inch),
1190
+ w: (endX - startX) / ratioPx2Inch * (originW / ratioPx2Inch),
1191
+ h: (endY - startY) / ratioPx2Inch * (originH / ratioPx2Inch)
1192
+ };
1193
+ }
1194
+ slide.addImage(options);
1195
+ }
1196
+ function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, elementIndex, patternShapes) {
1197
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1198
+ if (element.type !== "shape" || !element.path || !element.viewBox) return;
1199
+ const scale = {
1200
+ x: ((_a = element.width) != null ? _a : 0) / element.viewBox[0],
1201
+ y: ((_b = element.height) != null ? _b : 0) / element.viewBox[1]
1202
+ };
1203
+ const rawPoints = toPoints(element.path);
1204
+ if (!rawPoints.length) return;
1205
+ const points = formatPoints(rawPoints, ratioPx2Inch, scale);
1206
+ const pattern = element.pattern;
1207
+ const hasFill = typeof element.fill === "string" ? element.fill.trim().length > 0 : false;
1208
+ const opacity = getElementOpacity(element.opacity);
1209
+ const options = {
1210
+ x: ((_c = element.left) != null ? _c : 0) / ratioPx2Inch,
1211
+ y: ((_d = element.top) != null ? _d : 0) / ratioPx2Inch,
1212
+ w: ((_e = element.width) != null ? _e : 0) / ratioPx2Inch,
1213
+ h: ((_f = element.height) != null ? _f : 0) / ratioPx2Inch,
1214
+ points
1215
+ };
1216
+ if (pattern) {
1217
+ const objectName = `pattern-${slideIndex}-${(_g = element.id) != null ? _g : elementIndex}`;
1218
+ options.objectName = objectName;
1219
+ options.fill = { color: "FFFFFF", transparency: 100 };
1220
+ patternShapes.push({ slideIndex, objectName, dataUrl: pattern });
1221
+ } else if (hasFill) {
1222
+ const fillColor = formatColor(element.fill || "#000000");
1223
+ options.fill = {
1224
+ color: fillColor.color,
1225
+ transparency: (1 - fillColor.alpha * opacity) * 100
1226
+ };
1227
+ } else {
1228
+ options.fill = { color: "FFFFFF", transparency: 100 };
1229
+ }
1230
+ if (element.flipH) options.flipH = element.flipH;
1231
+ if (element.flipV) options.flipV = element.flipV;
1232
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1233
+ if ((_h = element.outline) == null ? void 0 : _h.width) {
1234
+ options.line = getOutlineOption(element.outline, ratioPx2Pt, opacity);
1235
+ }
1236
+ if (element.rotate) options.rotate = element.rotate;
1237
+ slide.addShape("custGeom", options);
1238
+ if (!((_i = element.text) == null ? void 0 : _i.content)) return;
1239
+ const textProps = formatHTML(element.text.content, ratioPx2Pt);
1240
+ const textOptions = {
1241
+ x: ((_j = element.left) != null ? _j : 0) / ratioPx2Inch,
1242
+ y: ((_k = element.top) != null ? _k : 0) / ratioPx2Inch,
1243
+ w: ((_l = element.width) != null ? _l : 0) / ratioPx2Inch,
1244
+ h: ((_m = element.height) != null ? _m : 0) / ratioPx2Inch,
1245
+ fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1246
+ fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
1247
+ color: "#000000",
1248
+ paraSpaceBefore: 0,
1249
+ valign: element.text.align,
1250
+ fill: { color: "FFFFFF", transparency: 100 },
1251
+ fit: "none"
1252
+ };
1253
+ textOptions.margin = 0;
1254
+ if (element.rotate) textOptions.rotate = element.rotate;
1255
+ if (element.text.defaultColor) {
1256
+ textOptions.color = formatColor(element.text.defaultColor).color;
1257
+ }
1258
+ if (element.opacity !== void 0) {
1259
+ textOptions.transparency = (1 - opacity) * 100;
1260
+ }
1261
+ if (element.flipH) textOptions.flipH = element.flipH;
1262
+ if (element.flipV) textOptions.flipV = element.flipV;
1263
+ slide.addText(textProps, textOptions);
1264
+ }
1265
+ function addLineElement(slide, element, ratioPx2Pt, ratioPx2Inch) {
1266
+ var _a, _b, _c, _d;
1267
+ if (element.type !== "line" || !element.start || !element.end) return;
1268
+ const path = getLineElementPath(element);
1269
+ const rawPoints = toPoints(path);
1270
+ if (!rawPoints.length) return;
1271
+ const points = formatPoints(rawPoints, ratioPx2Inch);
1272
+ const { minX, maxX, minY, maxY } = getElementRange(element);
1273
+ const c = formatColor(element.color || "#000000");
1274
+ const opacity = getElementOpacity(element.opacity);
1275
+ const pointsMeta = (_a = element.points) != null ? _a : [];
1276
+ const dashTypeMap2 = getDashTypeMap();
1277
+ const options = {
1278
+ x: ((_b = element.left) != null ? _b : 0) / ratioPx2Inch,
1279
+ y: ((_c = element.top) != null ? _c : 0) / ratioPx2Inch,
1280
+ w: (maxX - minX) / ratioPx2Inch,
1281
+ h: (maxY - minY) / ratioPx2Inch,
1282
+ line: {
1283
+ color: c.color,
1284
+ transparency: (1 - c.alpha * opacity) * 100,
1285
+ width: ((_d = element.width) != null ? _d : 1) / ratioPx2Pt,
1286
+ dashType: element.style ? dashTypeMap2[element.style] : "solid",
1287
+ beginArrowType: getLineArrowType(pointsMeta[0]),
1288
+ endArrowType: getLineArrowType(pointsMeta[1])
1289
+ },
1290
+ points
1291
+ };
1292
+ if (element.flipH) options.flipH = element.flipH;
1293
+ if (element.flipV) options.flipV = element.flipV;
1294
+ if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1295
+ slide.addShape("custGeom", options);
1296
+ }
1297
+ function addTableElement(slide, element, ratioPx2Pt, ratioPx2Inch) {
1298
+ var _a, _b, _c, _d, _e, _f;
1299
+ if (element.type !== "table") return;
1300
+ const rows = buildTableRows(element, ratioPx2Pt, ratioPx2Inch);
1301
+ if (!rows.length) return;
1302
+ const colWidths = element.colWidths;
1303
+ const colW = colWidths ? colWidths.map((ratio) => {
1304
+ var _a2;
1305
+ return ((_a2 = element.width) != null ? _a2 : 0) * ratio / ratioPx2Inch;
1306
+ }) : void 0;
1307
+ const rowCount = rows.length || 1;
1308
+ const baseRowHeight = ((_a = element.height) != null ? _a : 0) / rowCount / ratioPx2Inch;
1309
+ const minRowHeight = element.cellMinHeight ? element.cellMinHeight / ratioPx2Inch : void 0;
1310
+ const rowH = minRowHeight ? new Array(rowCount).fill(Math.max(minRowHeight, baseRowHeight)) : void 0;
1311
+ const outline = element.outline;
1312
+ const border = (outline == null ? void 0 : outline.width) || (outline == null ? void 0 : outline.color) ? {
1313
+ pt: ((_b = outline.width) != null ? _b : 1) / ratioPx2Pt,
1314
+ color: formatColor(outline.color || "#000000").color.replace("#", "")
1315
+ } : {
1316
+ pt: 1 / ratioPx2Pt,
1317
+ color: "DDDDDD"
1318
+ };
1319
+ slide.addTable(rows, {
1320
+ x: ((_c = element.left) != null ? _c : 0) / ratioPx2Inch,
1321
+ y: ((_d = element.top) != null ? _d : 0) / ratioPx2Inch,
1322
+ w: ((_e = element.width) != null ? _e : 0) / ratioPx2Inch,
1323
+ h: ((_f = element.height) != null ? _f : 0) / ratioPx2Inch,
1324
+ colW,
1325
+ rowH,
1326
+ border,
1327
+ margin: 0
1328
+ });
1329
+ }
1330
+
1331
+ // src/index.ts
1332
+ var ENABLE_DECK_JSON = true;
1333
+ var PPTX_JSON_PAYLOAD_PATH = "json2ppt-editor.json";
1334
+ var PPTX_JSON_PAYLOAD_VERSION = 1;
1335
+ function sanitizeFileName(name) {
1336
+ return name.replace(/[\\/:*?"<>|]/g, "").trim() || "presentation";
1337
+ }
1338
+ function stripFillTags(value) {
1339
+ return value.replace(/<a:solidFill>[\s\S]*?<\/a:solidFill>/g, "").replace(/<a:gradFill>[\s\S]*?<\/a:gradFill>/g, "").replace(/<a:blipFill>[\s\S]*?<\/a:blipFill>/g, "").replace(/<a:noFill\s*\/>/g, "").replace(/<a:noFill><\/a:noFill>/g, "");
1340
+ }
1341
+ function applyPatternFill(slideXml, objectName, relId) {
1342
+ const nameToken = `name="${objectName}"`;
1343
+ let cursor = 0;
1344
+ let result = slideXml;
1345
+ while (true) {
1346
+ const nameIndex = result.indexOf(nameToken, cursor);
1347
+ if (nameIndex === -1) break;
1348
+ const spStart = result.lastIndexOf("<p:sp", nameIndex);
1349
+ const spEnd = result.indexOf("</p:sp>", nameIndex);
1350
+ if (spStart === -1 || spEnd === -1) break;
1351
+ const spXml = result.slice(spStart, spEnd + "</p:sp>".length);
1352
+ const spPrStart = spXml.indexOf("<p:spPr>");
1353
+ const spPrEnd = spXml.indexOf("</p:spPr>");
1354
+ if (spPrStart === -1 || spPrEnd === -1) {
1355
+ cursor = spEnd + 1;
1356
+ continue;
1357
+ }
1358
+ const spPrOpenEnd = spXml.indexOf(">", spPrStart);
1359
+ const spPrInner = spXml.slice(spPrOpenEnd + 1, spPrEnd);
1360
+ const cleanedInner = stripFillTags(spPrInner);
1361
+ const blipFill = `<a:blipFill><a:blip r:embed="${relId}"/><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill>`;
1362
+ let nextInner = cleanedInner;
1363
+ if (cleanedInner.includes("</a:custGeom>")) {
1364
+ nextInner = cleanedInner.replace("</a:custGeom>", `</a:custGeom>${blipFill}`);
1365
+ } else {
1366
+ const lnIndex = cleanedInner.indexOf("<a:ln");
1367
+ nextInner = lnIndex === -1 ? `${cleanedInner}${blipFill}` : `${cleanedInner.slice(0, lnIndex)}${blipFill}${cleanedInner.slice(lnIndex)}`;
1368
+ }
1369
+ const updatedSpXml = spXml.slice(0, spPrOpenEnd + 1) + nextInner + spXml.slice(spPrEnd);
1370
+ result = result.slice(0, spStart) + updatedSpXml + result.slice(spEnd + "</p:sp>".length);
1371
+ cursor = spStart + updatedSpXml.length;
1372
+ }
1373
+ return result;
1374
+ }
1375
+ function parseDataUrlImage(dataUrl) {
1376
+ var _a;
1377
+ const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
1378
+ if (!match) return null;
1379
+ const mime = match[1];
1380
+ const data = match[2];
1381
+ const extMap = {
1382
+ "image/jpeg": "jpeg",
1383
+ "image/jpg": "jpg",
1384
+ "image/png": "png",
1385
+ "image/gif": "gif",
1386
+ "image/svg+xml": "svg",
1387
+ "image/webp": "webp",
1388
+ "image/bmp": "bmp"
1389
+ };
1390
+ return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
1391
+ }
1392
+ async function buildPptxBlob(template) {
1393
+ var _a, _b, _c, _d, _e, _f, _g;
1394
+ const pptx = new import_pptxgenjs.default();
1395
+ const patternShapes = [];
1396
+ const width = (_a = template.width) != null ? _a : DEFAULT_WIDTH;
1397
+ const height = (_b = template.height) != null ? _b : DEFAULT_HEIGHT;
1398
+ const ratioPx2Inch = 96 * (width / 960);
1399
+ const ratioPx2Pt = 96 / 72 * (width / 960);
1400
+ const textPadding = 10 / ratioPx2Pt;
1401
+ applyPptxLayout(pptx, width, height);
1402
+ for (const [slideIndex, slideJson] of ((_c = template.slides) != null ? _c : []).entries()) {
1403
+ const slide = pptx.addSlide();
1404
+ applySlideBackground(slide, slideJson, template.theme);
1405
+ for (const [elementIndex, element] of ((_d = slideJson.elements) != null ? _d : []).entries()) {
1406
+ addTextElement(slide, element, template, ratioPx2Pt, ratioPx2Inch, textPadding);
1407
+ await addImageElement(slide, element, ratioPx2Inch);
1408
+ addShapeElement(
1409
+ slide,
1410
+ element,
1411
+ ratioPx2Pt,
1412
+ ratioPx2Inch,
1413
+ slideIndex,
1414
+ elementIndex,
1415
+ patternShapes
1416
+ );
1417
+ addLineElement(slide, element, ratioPx2Pt, ratioPx2Inch);
1418
+ addTableElement(slide, element, ratioPx2Pt, ratioPx2Inch);
1419
+ }
1293
1420
  }
1294
- const fileName = `${sanitizeFileName((_R = template.title) != null ? _R : "presentation")}.pptx`;
1421
+ const fileName = `${sanitizeFileName((_e = template.title) != null ? _e : "presentation")}.pptx`;
1295
1422
  const pptxBuffer = await pptx.write({
1296
1423
  outputType: "arraybuffer",
1297
1424
  compression: true
@@ -1322,7 +1449,7 @@ async function buildPptxBlob(template) {
1322
1449
  const slideNumber = pattern.slideIndex + 1;
1323
1450
  const slidePath = `ppt/slides/slide${slideNumber}.xml`;
1324
1451
  const relsPath = `ppt/slides/_rels/slide${slideNumber}.xml.rels`;
1325
- const relsXml = (_S = relsCache.get(slideNumber)) != null ? _S : zip.file(relsPath) ? await zip.file(relsPath).async("string") : `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`;
1452
+ const relsXml = (_f = relsCache.get(slideNumber)) != null ? _f : zip.file(relsPath) ? await zip.file(relsPath).async("string") : `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`;
1326
1453
  let maxRelId = 0;
1327
1454
  relsXml.replace(/Id="rId(\d+)"/g, (_, id) => {
1328
1455
  const value = Number.parseInt(id, 10);
@@ -1336,7 +1463,7 @@ async function buildPptxBlob(template) {
1336
1463
  `${relEntry}</Relationships>`
1337
1464
  );
1338
1465
  relsCache.set(slideNumber, nextRelsXml);
1339
- const slideXml = (_T = slideCache.get(slideNumber)) != null ? _T : zip.file(slidePath) ? await zip.file(slidePath).async("string") : "";
1466
+ const slideXml = (_g = slideCache.get(slideNumber)) != null ? _g : zip.file(slidePath) ? await zip.file(slidePath).async("string") : "";
1340
1467
  const nextSlideXml = slideXml ? applyPatternFill(slideXml, pattern.objectName, relId) : slideXml;
1341
1468
  slideCache.set(slideNumber, nextSlideXml);
1342
1469
  }