json2pptx 0.5.3 → 0.5.4

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.mjs CHANGED
@@ -726,7 +726,10 @@ function clampOpacity(value) {
726
726
  return Math.min(1, Math.max(0, value));
727
727
  }
728
728
  function getOpacityRatio(value) {
729
- if (!value) return void 0;
729
+ if (value === void 0 || value === null) return void 0;
730
+ if (typeof value === "number") {
731
+ return value > 1 ? Math.min(1, Math.max(0, value / 100)) : Math.min(1, Math.max(0, value));
732
+ }
730
733
  const normalized = value.trim();
731
734
  if (!normalized) return void 0;
732
735
  if (normalized.endsWith("%")) {
@@ -1015,6 +1018,74 @@ function applySlideBackground(slide, slideJson, theme) {
1015
1018
  slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
1016
1019
  }
1017
1020
 
1021
+ // src/renderers/image-patch.ts
1022
+ function stripImageFilterTags(value) {
1023
+ return value.replace(/<a:alphaModFix\b[^>]*\/>/g, "").replace(/<a:grayscl\s*\/>/g, "");
1024
+ }
1025
+ function hasEnabledGrayscale(value) {
1026
+ if (value === void 0 || value === null) return false;
1027
+ if (typeof value === "number") return value > 0;
1028
+ const normalized = value.trim();
1029
+ if (!normalized) return false;
1030
+ if (normalized.endsWith("%")) {
1031
+ const percent = Number.parseFloat(normalized);
1032
+ return Number.isFinite(percent) && percent > 0;
1033
+ }
1034
+ const numeric = Number.parseFloat(normalized);
1035
+ return Number.isFinite(numeric) && numeric > 0;
1036
+ }
1037
+ function buildImageFilterXml(filters) {
1038
+ if (!filters) return "";
1039
+ const xml = [];
1040
+ const opacity = getFilterOpacity(filters.opacity);
1041
+ if (opacity !== void 0) {
1042
+ xml.push(`<a:alphaModFix amt="${Math.round(opacity * 1e5)}"/>`);
1043
+ }
1044
+ if (hasEnabledGrayscale(filters.grayscale)) {
1045
+ xml.push("<a:grayscl/>");
1046
+ }
1047
+ return xml.join("");
1048
+ }
1049
+ function imageFiltersRequireXmlPatch(filters) {
1050
+ if (!filters) return false;
1051
+ return getFilterOpacity(filters.opacity) !== void 0 || hasEnabledGrayscale(filters.grayscale);
1052
+ }
1053
+ function applyImageFilterPatch(slideXml, objectName, filters) {
1054
+ const filterXml = buildImageFilterXml(filters);
1055
+ if (!filterXml) return slideXml;
1056
+ const nameToken = `name="${objectName}"`;
1057
+ let cursor = 0;
1058
+ let result = slideXml;
1059
+ while (true) {
1060
+ const nameIndex = result.indexOf(nameToken, cursor);
1061
+ if (nameIndex === -1) break;
1062
+ const picStart = result.lastIndexOf("<p:pic", nameIndex);
1063
+ const picEnd = result.indexOf("</p:pic>", nameIndex);
1064
+ if (picStart === -1 || picEnd === -1) break;
1065
+ const picXml = result.slice(picStart, picEnd + "</p:pic>".length);
1066
+ const blipOpenClose = picXml.match(/<a:blip\b([^>]*)>([\s\S]*?)<\/a:blip>/);
1067
+ let updatedPicXml = picXml;
1068
+ if (blipOpenClose) {
1069
+ const attrs = blipOpenClose[1];
1070
+ const inner = stripImageFilterTags(blipOpenClose[2]);
1071
+ const nextBlip = `<a:blip${attrs}>${filterXml}${inner}</a:blip>`;
1072
+ updatedPicXml = picXml.replace(blipOpenClose[0], nextBlip);
1073
+ } else {
1074
+ const blipSelfClosing = picXml.match(/<a:blip\b([^>]*)\/>/);
1075
+ if (!blipSelfClosing) {
1076
+ cursor = picEnd + 1;
1077
+ continue;
1078
+ }
1079
+ const attrs = blipSelfClosing[1];
1080
+ const nextBlip = `<a:blip${attrs}>${filterXml}</a:blip>`;
1081
+ updatedPicXml = picXml.replace(blipSelfClosing[0], nextBlip);
1082
+ }
1083
+ result = result.slice(0, picStart) + updatedPicXml + result.slice(picEnd + "</p:pic>".length);
1084
+ cursor = picStart + updatedPicXml.length;
1085
+ }
1086
+ return result;
1087
+ }
1088
+
1018
1089
  // src/renderers/layout.ts
1019
1090
  function applyPptxLayout(pptx, width, height) {
1020
1091
  const viewportRatio = height / width;
@@ -1059,7 +1130,7 @@ var toPoints = (d) => {
1059
1130
  if (d.includes("NaN") || d.includes("undefined") || d.includes("null")) return [];
1060
1131
  let pathData;
1061
1132
  try {
1062
- pathData = new SVGPathData(d);
1133
+ pathData = new SVGPathData(d).toAbs().normalizeST().normalizeHVZ(false);
1063
1134
  } catch {
1064
1135
  return [];
1065
1136
  }
@@ -1252,33 +1323,33 @@ function addTextElement(slide, element, presentation, slideIndex, elementIndex,
1252
1323
  if (element.flipV) options.flipV = element.flipV;
1253
1324
  slide.addText(textProps, options);
1254
1325
  }
1255
- async function addImageElement(slide, element, ratioPx2Inch) {
1256
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1326
+ async function addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches) {
1327
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1257
1328
  if (element.type !== "image" || !element.src) return;
1329
+ const objectName = `image-${slideIndex}-${(_a = element.id) != null ? _a : elementIndex}`;
1258
1330
  const options = {
1259
- x: ((_a = element.left) != null ? _a : 0) / ratioPx2Inch,
1260
- y: ((_b = element.top) != null ? _b : 0) / ratioPx2Inch,
1261
- w: ((_c = element.width) != null ? _c : 0) / ratioPx2Inch,
1262
- h: ((_d = element.height) != null ? _d : 0) / ratioPx2Inch
1331
+ x: ((_b = element.left) != null ? _b : 0) / ratioPx2Inch,
1332
+ y: ((_c = element.top) != null ? _c : 0) / ratioPx2Inch,
1333
+ w: ((_d = element.width) != null ? _d : 0) / ratioPx2Inch,
1334
+ h: ((_e = element.height) != null ? _e : 0) / ratioPx2Inch,
1335
+ objectName
1263
1336
  };
1264
1337
  if (isBase64Image(element.src)) options.data = element.src;
1265
1338
  else options.data = await resolveImageData(element.src);
1266
1339
  if (element.flipH) options.flipH = element.flipH;
1267
1340
  if (element.flipV) options.flipV = element.flipV;
1268
1341
  if (element.rotate) options.rotate = element.rotate;
1269
- const filterOpacity = (_f = getFilterOpacity((_e = element.filters) == null ? void 0 : _e.opacity)) != null ? _f : 1;
1270
1342
  const elementOpacity = getElementOpacity(element.opacity);
1271
- const imageOpacity = filterOpacity * elementOpacity;
1272
- if (imageOpacity !== 1) {
1273
- options.transparency = (1 - imageOpacity) * 100;
1343
+ if (elementOpacity !== 1) {
1344
+ options.transparency = (1 - elementOpacity) * 100;
1274
1345
  }
1275
- if ((_g = element.clip) == null ? void 0 : _g.range) {
1346
+ if ((_f = element.clip) == null ? void 0 : _f.range) {
1276
1347
  if (element.clip.shape === "ellipse") options.rounding = true;
1277
1348
  const [start, end] = element.clip.range;
1278
1349
  const [startX, startY] = start;
1279
1350
  const [endX, endY] = end;
1280
- const originW = ((_h = element.width) != null ? _h : 0) / ((endX - startX) / ratioPx2Inch);
1281
- const originH = ((_i = element.height) != null ? _i : 0) / ((endY - startY) / ratioPx2Inch);
1351
+ const originW = ((_g = element.width) != null ? _g : 0) / ((endX - startX) / ratioPx2Inch);
1352
+ const originH = ((_h = element.height) != null ? _h : 0) / ((endY - startY) / ratioPx2Inch);
1282
1353
  options.w = originW / ratioPx2Inch;
1283
1354
  options.h = originH / ratioPx2Inch;
1284
1355
  options.sizing = {
@@ -1290,6 +1361,14 @@ async function addImageElement(slide, element, ratioPx2Inch) {
1290
1361
  };
1291
1362
  }
1292
1363
  slide.addImage(options);
1364
+ if (imageFiltersRequireXmlPatch(element.filters)) {
1365
+ imageFilterPatches.push({
1366
+ kind: "image",
1367
+ slideIndex,
1368
+ objectName,
1369
+ filters: element.filters
1370
+ });
1371
+ }
1293
1372
  }
1294
1373
  function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, elementIndex, fillPatches) {
1295
1374
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
@@ -1453,11 +1532,12 @@ function parseDataUrlImage(dataUrl) {
1453
1532
  return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
1454
1533
  }
1455
1534
  async function createPPTX(presentation) {
1456
- var _a, _b, _c, _d;
1535
+ var _a, _b, _c, _d, _e;
1457
1536
  const parsedPresentation = parseDocument(presentation);
1458
1537
  const normalizedPresentation = parsedPresentation;
1459
1538
  const pptx = new PptxGenJS();
1460
1539
  const fillPatches = [];
1540
+ const imageFilterPatches = [];
1461
1541
  const width = parsedPresentation.width;
1462
1542
  const height = parsedPresentation.height;
1463
1543
  const ratioPx2Inch = 96 * (width / 960);
@@ -1486,7 +1566,7 @@ async function createPPTX(presentation) {
1486
1566
  textPadding,
1487
1567
  fillPatches
1488
1568
  );
1489
- await addImageElement(slide, element, ratioPx2Inch);
1569
+ await addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches);
1490
1570
  addShapeElement(
1491
1571
  slide,
1492
1572
  element,
@@ -1505,7 +1585,7 @@ async function createPPTX(presentation) {
1505
1585
  outputType: "arraybuffer",
1506
1586
  compression: true
1507
1587
  });
1508
- if (!fillPatches.length) {
1588
+ if (!fillPatches.length && !imageFilterPatches.length) {
1509
1589
  return { blob: new Blob([pptxBuffer]), fileName };
1510
1590
  }
1511
1591
  const zip = await JSZip.loadAsync(pptxBuffer);
@@ -1550,6 +1630,18 @@ async function createPPTX(presentation) {
1550
1630
  const nextSlideXml = patch.kind === "background" ? applyBackgroundFillPatch(slideXml, patch.fill, relId) : applyShapeFillPatch(slideXml, patch.objectName, patch.fill, relId);
1551
1631
  slideCache.set(slideNumber, nextSlideXml);
1552
1632
  }
1633
+ for (const patch of imageFilterPatches) {
1634
+ const slideNumber = patch.slideIndex + 1;
1635
+ const slidePath = `ppt/slides/slide${slideNumber}.xml`;
1636
+ const slideXml = (_e = slideCache.get(slideNumber)) != null ? _e : zip.file(slidePath) ? await zip.file(slidePath).async("string") : "";
1637
+ if (!slideXml) {
1638
+ continue;
1639
+ }
1640
+ slideCache.set(
1641
+ slideNumber,
1642
+ applyImageFilterPatch(slideXml, patch.objectName, patch.filters)
1643
+ );
1644
+ }
1553
1645
  for (const [slideNumber, xml] of slideCache.entries()) {
1554
1646
  zip.file(`ppt/slides/slide${slideNumber}.xml`, xml);
1555
1647
  }