json2pptx 0.5.2 → 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("%")) {
@@ -871,8 +874,9 @@ function getShadowOption(shadow, ratioPx2Pt) {
871
874
  angle = 225;
872
875
  }
873
876
  return {
877
+ type: "outer",
874
878
  color: c.color,
875
- transparency: (1 - c.alpha) * 100,
879
+ opacity: c.alpha,
876
880
  blur: ((_b = shadow.blur) != null ? _b : 0) / ratioPx2Pt,
877
881
  offset,
878
882
  angle
@@ -1014,6 +1018,74 @@ function applySlideBackground(slide, slideJson, theme) {
1014
1018
  slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
1015
1019
  }
1016
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
+
1017
1089
  // src/renderers/layout.ts
1018
1090
  function applyPptxLayout(pptx, width, height) {
1019
1091
  const viewportRatio = height / width;
@@ -1058,7 +1130,7 @@ var toPoints = (d) => {
1058
1130
  if (d.includes("NaN") || d.includes("undefined") || d.includes("null")) return [];
1059
1131
  let pathData;
1060
1132
  try {
1061
- pathData = new SVGPathData(d);
1133
+ pathData = new SVGPathData(d).toAbs().normalizeST().normalizeHVZ(false);
1062
1134
  } catch {
1063
1135
  return [];
1064
1136
  }
@@ -1251,33 +1323,33 @@ function addTextElement(slide, element, presentation, slideIndex, elementIndex,
1251
1323
  if (element.flipV) options.flipV = element.flipV;
1252
1324
  slide.addText(textProps, options);
1253
1325
  }
1254
- async function addImageElement(slide, element, ratioPx2Inch) {
1255
- 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;
1256
1328
  if (element.type !== "image" || !element.src) return;
1329
+ const objectName = `image-${slideIndex}-${(_a = element.id) != null ? _a : elementIndex}`;
1257
1330
  const options = {
1258
- x: ((_a = element.left) != null ? _a : 0) / ratioPx2Inch,
1259
- y: ((_b = element.top) != null ? _b : 0) / ratioPx2Inch,
1260
- w: ((_c = element.width) != null ? _c : 0) / ratioPx2Inch,
1261
- 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
1262
1336
  };
1263
1337
  if (isBase64Image(element.src)) options.data = element.src;
1264
1338
  else options.data = await resolveImageData(element.src);
1265
1339
  if (element.flipH) options.flipH = element.flipH;
1266
1340
  if (element.flipV) options.flipV = element.flipV;
1267
1341
  if (element.rotate) options.rotate = element.rotate;
1268
- const filterOpacity = (_f = getFilterOpacity((_e = element.filters) == null ? void 0 : _e.opacity)) != null ? _f : 1;
1269
1342
  const elementOpacity = getElementOpacity(element.opacity);
1270
- const imageOpacity = filterOpacity * elementOpacity;
1271
- if (imageOpacity !== 1) {
1272
- options.transparency = (1 - imageOpacity) * 100;
1343
+ if (elementOpacity !== 1) {
1344
+ options.transparency = (1 - elementOpacity) * 100;
1273
1345
  }
1274
- if ((_g = element.clip) == null ? void 0 : _g.range) {
1346
+ if ((_f = element.clip) == null ? void 0 : _f.range) {
1275
1347
  if (element.clip.shape === "ellipse") options.rounding = true;
1276
1348
  const [start, end] = element.clip.range;
1277
1349
  const [startX, startY] = start;
1278
1350
  const [endX, endY] = end;
1279
- const originW = ((_h = element.width) != null ? _h : 0) / ((endX - startX) / ratioPx2Inch);
1280
- 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);
1281
1353
  options.w = originW / ratioPx2Inch;
1282
1354
  options.h = originH / ratioPx2Inch;
1283
1355
  options.sizing = {
@@ -1289,6 +1361,14 @@ async function addImageElement(slide, element, ratioPx2Inch) {
1289
1361
  };
1290
1362
  }
1291
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
+ }
1292
1372
  }
1293
1373
  function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, elementIndex, fillPatches) {
1294
1374
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
@@ -1347,7 +1427,7 @@ function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, e
1347
1427
  fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
1348
1428
  color: "#000000",
1349
1429
  paraSpaceBefore: 0,
1350
- valign: element.text.align === "middle" ? "mid" : element.text.align,
1430
+ valign: element.text.align === "middle" ? "middle" : element.text.align,
1351
1431
  fill: { color: "FFFFFF", transparency: 100 },
1352
1432
  fit: "none",
1353
1433
  objectName: `shape-text-${slideIndex}-${(_n = element.id) != null ? _n : elementIndex}`
@@ -1452,11 +1532,12 @@ function parseDataUrlImage(dataUrl) {
1452
1532
  return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
1453
1533
  }
1454
1534
  async function createPPTX(presentation) {
1455
- var _a, _b, _c, _d;
1535
+ var _a, _b, _c, _d, _e;
1456
1536
  const parsedPresentation = parseDocument(presentation);
1457
1537
  const normalizedPresentation = parsedPresentation;
1458
1538
  const pptx = new PptxGenJS();
1459
1539
  const fillPatches = [];
1540
+ const imageFilterPatches = [];
1460
1541
  const width = parsedPresentation.width;
1461
1542
  const height = parsedPresentation.height;
1462
1543
  const ratioPx2Inch = 96 * (width / 960);
@@ -1485,7 +1566,7 @@ async function createPPTX(presentation) {
1485
1566
  textPadding,
1486
1567
  fillPatches
1487
1568
  );
1488
- await addImageElement(slide, element, ratioPx2Inch);
1569
+ await addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches);
1489
1570
  addShapeElement(
1490
1571
  slide,
1491
1572
  element,
@@ -1504,7 +1585,7 @@ async function createPPTX(presentation) {
1504
1585
  outputType: "arraybuffer",
1505
1586
  compression: true
1506
1587
  });
1507
- if (!fillPatches.length) {
1588
+ if (!fillPatches.length && !imageFilterPatches.length) {
1508
1589
  return { blob: new Blob([pptxBuffer]), fileName };
1509
1590
  }
1510
1591
  const zip = await JSZip.loadAsync(pptxBuffer);
@@ -1549,6 +1630,18 @@ async function createPPTX(presentation) {
1549
1630
  const nextSlideXml = patch.kind === "background" ? applyBackgroundFillPatch(slideXml, patch.fill, relId) : applyShapeFillPatch(slideXml, patch.objectName, patch.fill, relId);
1550
1631
  slideCache.set(slideNumber, nextSlideXml);
1551
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
+ }
1552
1645
  for (const [slideNumber, xml] of slideCache.entries()) {
1553
1646
  zip.file(`ppt/slides/slide${slideNumber}.xml`, xml);
1554
1647
  }