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.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +113 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +113 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
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("%")) {
|
|
@@ -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
|
-
|
|
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
|
|
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: ((
|
|
1259
|
-
y: ((
|
|
1260
|
-
w: ((
|
|
1261
|
-
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
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
options.transparency = (1 - imageOpacity) * 100;
|
|
1343
|
+
if (elementOpacity !== 1) {
|
|
1344
|
+
options.transparency = (1 - elementOpacity) * 100;
|
|
1273
1345
|
}
|
|
1274
|
-
if ((
|
|
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 = ((
|
|
1280
|
-
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);
|
|
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" ? "
|
|
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
|
}
|