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.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +110 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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 (
|
|
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
|
|
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: ((
|
|
1260
|
-
y: ((
|
|
1261
|
-
w: ((
|
|
1262
|
-
h: ((
|
|
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
|
-
|
|
1272
|
-
|
|
1273
|
-
options.transparency = (1 - imageOpacity) * 100;
|
|
1343
|
+
if (elementOpacity !== 1) {
|
|
1344
|
+
options.transparency = (1 - elementOpacity) * 100;
|
|
1274
1345
|
}
|
|
1275
|
-
if ((
|
|
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 = ((
|
|
1281
|
-
const originH = ((
|
|
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
|
}
|