json2pptx 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -58,6 +58,7 @@ type ShapeElement = BaseElement<'shape'> & {
58
58
  path?: string;
59
59
  viewBox?: [number, number];
60
60
  fill?: string;
61
+ pattern?: string;
61
62
  text?: TextContent;
62
63
  };
63
64
  type LineElement = BaseElement<'line'> & {
@@ -72,7 +73,27 @@ type LineElement = BaseElement<'line'> & {
72
73
  style?: 'solid' | 'dashed' | 'dotted';
73
74
  points?: [unknown, unknown];
74
75
  };
75
- type SlideElement = TextElement | ImageElement | ShapeElement | LineElement;
76
+ type TableCellStyle = {
77
+ fontname?: string;
78
+ color?: string;
79
+ align?: string;
80
+ fontsize?: string;
81
+ backcolor?: string;
82
+ };
83
+ type TableCellData = {
84
+ id?: string;
85
+ colspan?: number;
86
+ rowspan?: number;
87
+ text?: string;
88
+ style?: TableCellStyle;
89
+ };
90
+ type TableElement = BaseElement<'table'> & {
91
+ type: 'table';
92
+ colWidths?: number[];
93
+ data?: TableCellData[][];
94
+ cellMinHeight?: number;
95
+ };
96
+ type SlideElement = TextElement | ImageElement | ShapeElement | LineElement | TableElement;
76
97
  type SlideBackground = {
77
98
  color?: string;
78
99
  };
package/dist/index.d.ts CHANGED
@@ -58,6 +58,7 @@ type ShapeElement = BaseElement<'shape'> & {
58
58
  path?: string;
59
59
  viewBox?: [number, number];
60
60
  fill?: string;
61
+ pattern?: string;
61
62
  text?: TextContent;
62
63
  };
63
64
  type LineElement = BaseElement<'line'> & {
@@ -72,7 +73,27 @@ type LineElement = BaseElement<'line'> & {
72
73
  style?: 'solid' | 'dashed' | 'dotted';
73
74
  points?: [unknown, unknown];
74
75
  };
75
- type SlideElement = TextElement | ImageElement | ShapeElement | LineElement;
76
+ type TableCellStyle = {
77
+ fontname?: string;
78
+ color?: string;
79
+ align?: string;
80
+ fontsize?: string;
81
+ backcolor?: string;
82
+ };
83
+ type TableCellData = {
84
+ id?: string;
85
+ colspan?: number;
86
+ rowspan?: number;
87
+ text?: string;
88
+ style?: TableCellStyle;
89
+ };
90
+ type TableElement = BaseElement<'table'> & {
91
+ type: 'table';
92
+ colWidths?: number[];
93
+ data?: TableCellData[][];
94
+ cellMinHeight?: number;
95
+ };
96
+ type SlideElement = TextElement | ImageElement | ShapeElement | LineElement | TableElement;
76
97
  type SlideBackground = {
77
98
  color?: string;
78
99
  };
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // lib/index.ts
30
+ // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ENABLE_DECK_JSON: () => ENABLE_DECK_JSON,
@@ -43,7 +43,7 @@ var import_pptxgenjs = __toESM(require("pptxgenjs"));
43
43
  var import_jszip = __toESM(require("jszip"));
44
44
  var import_tinycolor2 = __toESM(require("tinycolor2"));
45
45
 
46
- // lib/resolveImageData.ts
46
+ // src/resolveImageData.ts
47
47
  var dataUrlRegex = /^data:image\/[^;]+;base64,/;
48
48
  var mimeFromPath = (path) => {
49
49
  const lower = path.toLowerCase();
@@ -97,7 +97,7 @@ var resolveImageData = async (src) => {
97
97
  return `data:${contentType};base64,${base64}`;
98
98
  };
99
99
 
100
- // lib/htmlParser/tags.ts
100
+ // src/htmlParser/tags.ts
101
101
  var childlessTags = ["style", "script", "template"];
102
102
  var closingTags = [
103
103
  "html",
@@ -146,7 +146,7 @@ var voidTags = [
146
146
  "wbr"
147
147
  ];
148
148
 
149
- // lib/htmlParser/lexer.ts
149
+ // src/htmlParser/lexer.ts
150
150
  var jumpPosition = (state, end) => {
151
151
  const len = end - state.position;
152
152
  movePosition(state, len);
@@ -379,7 +379,7 @@ var lexer = (str) => {
379
379
  return state.tokens;
380
380
  };
381
381
 
382
- // lib/htmlParser/parser.ts
382
+ // src/htmlParser/parser.ts
383
383
  var parser = (tokens) => {
384
384
  const root = { tagName: null, children: [] };
385
385
  const state = { tokens, cursor: 0, stack: [root] };
@@ -484,7 +484,7 @@ var parse = (state) => {
484
484
  state.cursor = cursor;
485
485
  };
486
486
 
487
- // lib/htmlParser/format.ts
487
+ // src/htmlParser/format.ts
488
488
  var splitHead = (str, sep) => {
489
489
  const idx = str.indexOf(sep);
490
490
  if (idx === -1) return [str];
@@ -527,14 +527,14 @@ var format = (nodes) => {
527
527
  });
528
528
  };
529
529
 
530
- // lib/htmlParser/index.ts
530
+ // src/htmlParser/index.ts
531
531
  var toAST = (str) => {
532
532
  const tokens = lexer(str);
533
533
  const nodes = parser(tokens);
534
534
  return format(nodes);
535
535
  };
536
536
 
537
- // lib/svgPathParser.ts
537
+ // src/svgPathParser.ts
538
538
  var import_svg_pathdata = require("svg-pathdata");
539
539
  var import_svg_arc_to_cubic_bezier = __toESM(require("svg-arc-to-cubic-bezier"));
540
540
  var typeMap = {
@@ -624,7 +624,7 @@ var toPoints = (d) => {
624
624
  return points;
625
625
  };
626
626
 
627
- // lib/element.ts
627
+ // src/element.ts
628
628
  var getElementRange = (element) => {
629
629
  var _a, _b, _c, _d, _e, _f;
630
630
  let minX = 0;
@@ -677,7 +677,7 @@ var getLineElementPath = (element) => {
677
677
  return `M${start} L${end}`;
678
678
  };
679
679
 
680
- // lib/index.ts
680
+ // src/index.ts
681
681
  var DEFAULT_WIDTH = 1e3;
682
682
  var DEFAULT_HEIGHT = 562.5;
683
683
  var DEFAULT_FONT_SIZE = 16;
@@ -876,6 +876,148 @@ var dashTypeMap = {
876
876
  dashed: "dash",
877
877
  dotted: "sysDot"
878
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;
911
+ }
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) => {
933
+ if (!value) return void 0;
934
+ const size = Number.parseFloat(value);
935
+ return Number.isFinite(size) ? size : void 0;
936
+ };
937
+ var parseTableColor = (value) => {
938
+ if (!value) return void 0;
939
+ const normalized = value.trim();
940
+ if (!normalized) return void 0;
941
+ const c = formatColor(normalized);
942
+ return {
943
+ color: c.color.replace("#", ""),
944
+ transparency: (1 - c.alpha) * 100
945
+ };
946
+ };
947
+ var isPlaceholderCell = (cell) => {
948
+ var _a, _b, _c;
949
+ if (!cell) return false;
950
+ const colspan = (_a = cell.colspan) != null ? _a : 1;
951
+ const rowspan = (_b = cell.rowspan) != null ? _b : 1;
952
+ if (colspan !== 1 || rowspan !== 1) return false;
953
+ const text = (_c = cell.text) != null ? _c : "";
954
+ const style = cell.style;
955
+ 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
+ return text.trim() === "" && !hasStyle;
957
+ };
958
+ var buildTableRows = (element, ratioPx2Pt) => {
959
+ var _a, _b;
960
+ const data = element.data;
961
+ if (!data || !data.length) return [];
962
+ const colCount = (_b = (_a = element.colWidths) == null ? void 0 : _a.length) != null ? _b : Math.max(...data.map((row) => row.length));
963
+ const rows = [];
964
+ const skip = new Array(colCount).fill(0);
965
+ data.forEach((row, rowIndex) => {
966
+ var _a2, _b2, _c, _d;
967
+ const cells = [];
968
+ let colIndex = 0;
969
+ let cellIndex = 0;
970
+ while (colIndex < colCount) {
971
+ if (skip[colIndex] > 0) {
972
+ skip[colIndex] -= 1;
973
+ if (isPlaceholderCell(row[cellIndex])) {
974
+ cellIndex += 1;
975
+ }
976
+ colIndex += 1;
977
+ continue;
978
+ }
979
+ const cell = row[cellIndex];
980
+ if (!cell) break;
981
+ cellIndex += 1;
982
+ const colSpan = (_a2 = cell.colspan) != null ? _a2 : 1;
983
+ const rowSpan = (_b2 = cell.rowspan) != null ? _b2 : 1;
984
+ if (rowSpan > 1) {
985
+ for (let i = 0; i < colSpan; i += 1) {
986
+ skip[colIndex + i] = rowSpan - 1;
987
+ }
988
+ }
989
+ if (colSpan > 1) {
990
+ for (let i = 0; i < colSpan - 1; i += 1) {
991
+ if (isPlaceholderCell(row[cellIndex])) {
992
+ cellIndex += 1;
993
+ }
994
+ }
995
+ }
996
+ const style = (_c = cell.style) != null ? _c : {};
997
+ const fontSize = parseFontSize(style.fontsize);
998
+ const fill = parseTableColor(style.backcolor);
999
+ const color = parseTableColor(style.color);
1000
+ const options = {
1001
+ colspan: colSpan > 1 ? colSpan : void 0,
1002
+ rowspan: rowSpan > 1 ? rowSpan : void 0,
1003
+ align: style.align,
1004
+ valign: "middle",
1005
+ fontFace: normalizeFontName(style.fontname),
1006
+ fontSize: fontSize ? fontSize / ratioPx2Pt : void 0,
1007
+ color: color == null ? void 0 : color.color,
1008
+ fill: fill ? { color: fill.color, transparency: fill.transparency } : void 0,
1009
+ margin: 0
1010
+ };
1011
+ cells.push({
1012
+ text: (_d = cell.text) != null ? _d : "",
1013
+ options
1014
+ });
1015
+ colIndex += colSpan;
1016
+ }
1017
+ rows.push(cells);
1018
+ });
1019
+ return rows;
1020
+ };
879
1021
  var getShadowOption = (shadow, ratioPx2Pt) => {
880
1022
  var _a, _b;
881
1023
  const c = formatColor((_a = shadow.color) != null ? _a : "#000000");
@@ -937,8 +1079,9 @@ var isBase64Image = (url) => {
937
1079
  return regex.test(url);
938
1080
  };
939
1081
  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;
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;
941
1083
  const pptx = new import_pptxgenjs.default();
1084
+ const patternShapes = [];
942
1085
  const width = (_a = template.width) != null ? _a : DEFAULT_WIDTH;
943
1086
  const height = (_b = template.height) != null ? _b : DEFAULT_HEIGHT;
944
1087
  const viewportRatio = height / width;
@@ -953,14 +1096,14 @@ async function buildPptxBlob(template) {
953
1096
  pptx.defineLayout({ name: "A3_V", width: 10, height: 14.1421356 });
954
1097
  pptx.layout = "A3_V";
955
1098
  } else pptx.layout = "LAYOUT_16x9";
956
- for (const slideJson of (_c = template.slides) != null ? _c : []) {
1099
+ for (const [slideIndex, slideJson] of ((_c = template.slides) != null ? _c : []).entries()) {
957
1100
  const slide = pptx.addSlide();
958
1101
  const backgroundColor = (_d = slideJson.background) == null ? void 0 : _d.color;
959
1102
  if (backgroundColor) {
960
1103
  const c = formatColor(backgroundColor);
961
1104
  slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
962
1105
  }
963
- for (const element of (_e = slideJson.elements) != null ? _e : []) {
1106
+ for (const [elementIndex, element] of ((_e = slideJson.elements) != null ? _e : []).entries()) {
964
1107
  if (element.type === "text" && element.content) {
965
1108
  const textProps = formatHTML(element.content, ratioPx2Pt);
966
1109
  const options = {
@@ -972,7 +1115,7 @@ async function buildPptxBlob(template) {
972
1115
  fontFace: element.defaultFontName || ((_j = template.theme) == null ? void 0 : _j.fontName) || DEFAULT_FONT_FACE,
973
1116
  color: "#000000",
974
1117
  valign: "top",
975
- margin: 10 / ratioPx2Pt,
1118
+ margin: 0,
976
1119
  paraSpaceBefore: 5 / ratioPx2Pt,
977
1120
  lineSpacingMultiple: 1.5 / 1.25,
978
1121
  autoFit: true
@@ -987,6 +1130,8 @@ async function buildPptxBlob(template) {
987
1130
  color: c.color,
988
1131
  transparency: (1 - c.alpha * opacity) * 100
989
1132
  };
1133
+ } else {
1134
+ options.fill = { color: "FFFFFF", transparency: 100 };
990
1135
  }
991
1136
  if (element.defaultColor) options.color = formatColor(element.defaultColor).color;
992
1137
  if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
@@ -1040,38 +1185,51 @@ async function buildPptxBlob(template) {
1040
1185
  y: ((_u = element.height) != null ? _u : 0) / element.viewBox[1]
1041
1186
  };
1042
1187
  const points = formatPoints(toPoints(element.path), ratioPx2Inch, scale);
1043
- let fillColor = formatColor(element.fill || "#000000");
1188
+ const pattern = element.pattern;
1189
+ const hasFill = typeof element.fill === "string" ? element.fill.trim().length > 0 : false;
1044
1190
  const opacity = element.opacity === void 0 ? 1 : element.opacity;
1045
1191
  const options = {
1046
1192
  x: ((_v = element.left) != null ? _v : 0) / ratioPx2Inch,
1047
1193
  y: ((_w = element.top) != null ? _w : 0) / ratioPx2Inch,
1048
1194
  w: ((_x = element.width) != null ? _x : 0) / ratioPx2Inch,
1049
1195
  h: ((_y = element.height) != null ? _y : 0) / ratioPx2Inch,
1050
- fill: {
1051
- color: fillColor.color,
1052
- transparency: (1 - fillColor.alpha * opacity) * 100
1053
- },
1054
1196
  points
1055
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
+ }
1056
1212
  if (element.flipH) options.flipH = element.flipH;
1057
1213
  if (element.flipV) options.flipV = element.flipV;
1058
1214
  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);
1215
+ if ((_A = element.outline) == null ? void 0 : _A.width) options.line = getOutlineOption(element.outline, ratioPx2Pt);
1060
1216
  if (element.rotate) options.rotate = element.rotate;
1061
1217
  slide.addShape("custGeom", options);
1062
- if ((_A = element.text) == null ? void 0 : _A.content) {
1218
+ if ((_B = element.text) == null ? void 0 : _B.content) {
1063
1219
  const textProps = formatHTML(element.text.content, ratioPx2Pt);
1064
1220
  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,
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,
1069
1225
  fontSize: DEFAULT_FONT_SIZE / ratioPx2Pt,
1070
1226
  fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
1071
1227
  color: "#000000",
1072
1228
  paraSpaceBefore: 5 / ratioPx2Pt,
1073
- valign: element.text.align
1229
+ valign: element.text.align,
1230
+ fill: { color: "FFFFFF", transparency: 100 }
1074
1231
  };
1232
+ textOptions.margin = 0;
1075
1233
  if (element.rotate) textOptions.rotate = element.rotate;
1076
1234
  if (element.text.defaultColor) {
1077
1235
  textOptions.color = formatColor(element.text.defaultColor).color;
@@ -1086,45 +1244,122 @@ async function buildPptxBlob(template) {
1086
1244
  const { minX, maxX, minY, maxY } = getElementRange(element);
1087
1245
  const c = formatColor(element.color || "#000000");
1088
1246
  const options = {
1089
- x: ((_F = element.left) != null ? _F : 0) / ratioPx2Inch,
1090
- y: ((_G = element.top) != null ? _G : 0) / ratioPx2Inch,
1247
+ x: ((_G = element.left) != null ? _G : 0) / ratioPx2Inch,
1248
+ y: ((_H = element.top) != null ? _H : 0) / ratioPx2Inch,
1091
1249
  w: (maxX - minX) / ratioPx2Inch,
1092
1250
  h: (maxY - minY) / ratioPx2Inch,
1093
1251
  line: {
1094
1252
  color: c.color,
1095
1253
  transparency: (1 - c.alpha) * 100,
1096
- width: ((_H = element.width) != null ? _H : 1) / ratioPx2Pt,
1254
+ width: ((_I = element.width) != null ? _I : 1) / ratioPx2Pt,
1097
1255
  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"
1256
+ beginArrowType: ((_J = element.points) == null ? void 0 : _J[0]) ? "arrow" : "none",
1257
+ endArrowType: ((_K = element.points) == null ? void 0 : _K[1]) ? "arrow" : "none"
1100
1258
  },
1101
1259
  points
1102
1260
  };
1103
1261
  if (element.shadow) options.shadow = getShadowOption(element.shadow, ratioPx2Pt);
1104
1262
  slide.addShape("custGeom", options);
1105
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
+ }
1106
1292
  }
1107
1293
  }
1108
- const fileName = `${sanitizeFileName((_K = template.title) != null ? _K : "presentation")}.pptx`;
1294
+ const fileName = `${sanitizeFileName((_R = template.title) != null ? _R : "presentation")}.pptx`;
1109
1295
  const pptxBuffer = await pptx.write({
1110
1296
  outputType: "arraybuffer",
1111
1297
  compression: true
1112
1298
  });
1113
- if (!ENABLE_DECK_JSON) {
1299
+ const needsZip = ENABLE_DECK_JSON || patternShapes.length > 0;
1300
+ if (!needsZip) {
1114
1301
  return { blob: new Blob([pptxBuffer]), fileName };
1115
1302
  }
1116
1303
  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
- );
1304
+ if (patternShapes.length) {
1305
+ const mediaFiles = Object.keys(zip.files).filter((name) => name.startsWith("ppt/media/"));
1306
+ let maxImageIndex = 0;
1307
+ for (const name of mediaFiles) {
1308
+ const match = name.match(/ppt\/media\/image(\d+)/);
1309
+ if (match) {
1310
+ const index = Number.parseInt(match[1], 10);
1311
+ if (Number.isFinite(index)) maxImageIndex = Math.max(maxImageIndex, index);
1312
+ }
1313
+ }
1314
+ const slideCache = /* @__PURE__ */ new Map();
1315
+ const relsCache = /* @__PURE__ */ new Map();
1316
+ for (const pattern of patternShapes) {
1317
+ const parsed = parseDataUrlImage(pattern.dataUrl);
1318
+ if (!parsed) continue;
1319
+ maxImageIndex += 1;
1320
+ const imageName = `image${maxImageIndex}.${parsed.ext}`;
1321
+ zip.file(`ppt/media/${imageName}`, parsed.data, { base64: true });
1322
+ const slideNumber = pattern.slideIndex + 1;
1323
+ const slidePath = `ppt/slides/slide${slideNumber}.xml`;
1324
+ 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>`;
1326
+ let maxRelId = 0;
1327
+ relsXml.replace(/Id="rId(\d+)"/g, (_, id) => {
1328
+ const value = Number.parseInt(id, 10);
1329
+ if (Number.isFinite(value)) maxRelId = Math.max(maxRelId, value);
1330
+ return "";
1331
+ });
1332
+ const relId = `rId${maxRelId + 1}`;
1333
+ const relEntry = `<Relationship Id="${relId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/${imageName}"/>`;
1334
+ const nextRelsXml = relsXml.replace(
1335
+ "</Relationships>",
1336
+ `${relEntry}</Relationships>`
1337
+ );
1338
+ relsCache.set(slideNumber, nextRelsXml);
1339
+ const slideXml = (_T = slideCache.get(slideNumber)) != null ? _T : zip.file(slidePath) ? await zip.file(slidePath).async("string") : "";
1340
+ const nextSlideXml = slideXml ? applyPatternFill(slideXml, pattern.objectName, relId) : slideXml;
1341
+ slideCache.set(slideNumber, nextSlideXml);
1342
+ }
1343
+ for (const [slideNumber, xml] of slideCache.entries()) {
1344
+ zip.file(`ppt/slides/slide${slideNumber}.xml`, xml);
1345
+ }
1346
+ for (const [slideNumber, xml] of relsCache.entries()) {
1347
+ zip.file(`ppt/slides/_rels/slide${slideNumber}.xml.rels`, xml);
1348
+ }
1349
+ }
1350
+ if (ENABLE_DECK_JSON) {
1351
+ zip.file(
1352
+ PPTX_JSON_PAYLOAD_PATH,
1353
+ JSON.stringify(
1354
+ {
1355
+ version: PPTX_JSON_PAYLOAD_VERSION,
1356
+ deck: template
1357
+ },
1358
+ null,
1359
+ 2
1360
+ )
1361
+ );
1362
+ }
1128
1363
  const blob = await zip.generateAsync({ type: "blob" });
1129
1364
  return { blob, fileName };
1130
1365
  }