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.d.mts
CHANGED
|
@@ -33,7 +33,11 @@ type ElementOutline = {
|
|
|
33
33
|
style?: 'solid' | 'dashed' | 'dotted';
|
|
34
34
|
};
|
|
35
35
|
type ElementFilters = {
|
|
36
|
-
opacity?: string;
|
|
36
|
+
opacity?: string | number;
|
|
37
|
+
grayscale?: string | number;
|
|
38
|
+
blur?: string | number;
|
|
39
|
+
sepia?: string | number;
|
|
40
|
+
saturate?: string | number;
|
|
37
41
|
};
|
|
38
42
|
type ElementClip = {
|
|
39
43
|
shape?: 'ellipse' | string;
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,11 @@ type ElementOutline = {
|
|
|
33
33
|
style?: 'solid' | 'dashed' | 'dotted';
|
|
34
34
|
};
|
|
35
35
|
type ElementFilters = {
|
|
36
|
-
opacity?: string;
|
|
36
|
+
opacity?: string | number;
|
|
37
|
+
grayscale?: string | number;
|
|
38
|
+
blur?: string | number;
|
|
39
|
+
sepia?: string | number;
|
|
40
|
+
saturate?: string | number;
|
|
37
41
|
};
|
|
38
42
|
type ElementClip = {
|
|
39
43
|
shape?: 'ellipse' | string;
|
package/dist/index.js
CHANGED
|
@@ -764,7 +764,10 @@ function clampOpacity(value) {
|
|
|
764
764
|
return Math.min(1, Math.max(0, value));
|
|
765
765
|
}
|
|
766
766
|
function getOpacityRatio(value) {
|
|
767
|
-
if (
|
|
767
|
+
if (value === void 0 || value === null) return void 0;
|
|
768
|
+
if (typeof value === "number") {
|
|
769
|
+
return value > 1 ? Math.min(1, Math.max(0, value / 100)) : Math.min(1, Math.max(0, value));
|
|
770
|
+
}
|
|
768
771
|
const normalized = value.trim();
|
|
769
772
|
if (!normalized) return void 0;
|
|
770
773
|
if (normalized.endsWith("%")) {
|
|
@@ -1053,6 +1056,74 @@ function applySlideBackground(slide, slideJson, theme) {
|
|
|
1053
1056
|
slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
|
|
1054
1057
|
}
|
|
1055
1058
|
|
|
1059
|
+
// src/renderers/image-patch.ts
|
|
1060
|
+
function stripImageFilterTags(value) {
|
|
1061
|
+
return value.replace(/<a:alphaModFix\b[^>]*\/>/g, "").replace(/<a:grayscl\s*\/>/g, "");
|
|
1062
|
+
}
|
|
1063
|
+
function hasEnabledGrayscale(value) {
|
|
1064
|
+
if (value === void 0 || value === null) return false;
|
|
1065
|
+
if (typeof value === "number") return value > 0;
|
|
1066
|
+
const normalized = value.trim();
|
|
1067
|
+
if (!normalized) return false;
|
|
1068
|
+
if (normalized.endsWith("%")) {
|
|
1069
|
+
const percent = Number.parseFloat(normalized);
|
|
1070
|
+
return Number.isFinite(percent) && percent > 0;
|
|
1071
|
+
}
|
|
1072
|
+
const numeric = Number.parseFloat(normalized);
|
|
1073
|
+
return Number.isFinite(numeric) && numeric > 0;
|
|
1074
|
+
}
|
|
1075
|
+
function buildImageFilterXml(filters) {
|
|
1076
|
+
if (!filters) return "";
|
|
1077
|
+
const xml = [];
|
|
1078
|
+
const opacity = getFilterOpacity(filters.opacity);
|
|
1079
|
+
if (opacity !== void 0) {
|
|
1080
|
+
xml.push(`<a:alphaModFix amt="${Math.round(opacity * 1e5)}"/>`);
|
|
1081
|
+
}
|
|
1082
|
+
if (hasEnabledGrayscale(filters.grayscale)) {
|
|
1083
|
+
xml.push("<a:grayscl/>");
|
|
1084
|
+
}
|
|
1085
|
+
return xml.join("");
|
|
1086
|
+
}
|
|
1087
|
+
function imageFiltersRequireXmlPatch(filters) {
|
|
1088
|
+
if (!filters) return false;
|
|
1089
|
+
return getFilterOpacity(filters.opacity) !== void 0 || hasEnabledGrayscale(filters.grayscale);
|
|
1090
|
+
}
|
|
1091
|
+
function applyImageFilterPatch(slideXml, objectName, filters) {
|
|
1092
|
+
const filterXml = buildImageFilterXml(filters);
|
|
1093
|
+
if (!filterXml) return slideXml;
|
|
1094
|
+
const nameToken = `name="${objectName}"`;
|
|
1095
|
+
let cursor = 0;
|
|
1096
|
+
let result = slideXml;
|
|
1097
|
+
while (true) {
|
|
1098
|
+
const nameIndex = result.indexOf(nameToken, cursor);
|
|
1099
|
+
if (nameIndex === -1) break;
|
|
1100
|
+
const picStart = result.lastIndexOf("<p:pic", nameIndex);
|
|
1101
|
+
const picEnd = result.indexOf("</p:pic>", nameIndex);
|
|
1102
|
+
if (picStart === -1 || picEnd === -1) break;
|
|
1103
|
+
const picXml = result.slice(picStart, picEnd + "</p:pic>".length);
|
|
1104
|
+
const blipOpenClose = picXml.match(/<a:blip\b([^>]*)>([\s\S]*?)<\/a:blip>/);
|
|
1105
|
+
let updatedPicXml = picXml;
|
|
1106
|
+
if (blipOpenClose) {
|
|
1107
|
+
const attrs = blipOpenClose[1];
|
|
1108
|
+
const inner = stripImageFilterTags(blipOpenClose[2]);
|
|
1109
|
+
const nextBlip = `<a:blip${attrs}>${filterXml}${inner}</a:blip>`;
|
|
1110
|
+
updatedPicXml = picXml.replace(blipOpenClose[0], nextBlip);
|
|
1111
|
+
} else {
|
|
1112
|
+
const blipSelfClosing = picXml.match(/<a:blip\b([^>]*)\/>/);
|
|
1113
|
+
if (!blipSelfClosing) {
|
|
1114
|
+
cursor = picEnd + 1;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const attrs = blipSelfClosing[1];
|
|
1118
|
+
const nextBlip = `<a:blip${attrs}>${filterXml}</a:blip>`;
|
|
1119
|
+
updatedPicXml = picXml.replace(blipSelfClosing[0], nextBlip);
|
|
1120
|
+
}
|
|
1121
|
+
result = result.slice(0, picStart) + updatedPicXml + result.slice(picEnd + "</p:pic>".length);
|
|
1122
|
+
cursor = picStart + updatedPicXml.length;
|
|
1123
|
+
}
|
|
1124
|
+
return result;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1056
1127
|
// src/renderers/layout.ts
|
|
1057
1128
|
function applyPptxLayout(pptx, width, height) {
|
|
1058
1129
|
const viewportRatio = height / width;
|
|
@@ -1097,7 +1168,7 @@ var toPoints = (d) => {
|
|
|
1097
1168
|
if (d.includes("NaN") || d.includes("undefined") || d.includes("null")) return [];
|
|
1098
1169
|
let pathData;
|
|
1099
1170
|
try {
|
|
1100
|
-
pathData = new import_svg_pathdata.SVGPathData(d);
|
|
1171
|
+
pathData = new import_svg_pathdata.SVGPathData(d).toAbs().normalizeST().normalizeHVZ(false);
|
|
1101
1172
|
} catch {
|
|
1102
1173
|
return [];
|
|
1103
1174
|
}
|
|
@@ -1290,33 +1361,33 @@ function addTextElement(slide, element, presentation, slideIndex, elementIndex,
|
|
|
1290
1361
|
if (element.flipV) options.flipV = element.flipV;
|
|
1291
1362
|
slide.addText(textProps, options);
|
|
1292
1363
|
}
|
|
1293
|
-
async function addImageElement(slide, element, ratioPx2Inch) {
|
|
1294
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
1364
|
+
async function addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches) {
|
|
1365
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1295
1366
|
if (element.type !== "image" || !element.src) return;
|
|
1367
|
+
const objectName = `image-${slideIndex}-${(_a = element.id) != null ? _a : elementIndex}`;
|
|
1296
1368
|
const options = {
|
|
1297
|
-
x: ((
|
|
1298
|
-
y: ((
|
|
1299
|
-
w: ((
|
|
1300
|
-
h: ((
|
|
1369
|
+
x: ((_b = element.left) != null ? _b : 0) / ratioPx2Inch,
|
|
1370
|
+
y: ((_c = element.top) != null ? _c : 0) / ratioPx2Inch,
|
|
1371
|
+
w: ((_d = element.width) != null ? _d : 0) / ratioPx2Inch,
|
|
1372
|
+
h: ((_e = element.height) != null ? _e : 0) / ratioPx2Inch,
|
|
1373
|
+
objectName
|
|
1301
1374
|
};
|
|
1302
1375
|
if (isBase64Image(element.src)) options.data = element.src;
|
|
1303
1376
|
else options.data = await resolveImageData(element.src);
|
|
1304
1377
|
if (element.flipH) options.flipH = element.flipH;
|
|
1305
1378
|
if (element.flipV) options.flipV = element.flipV;
|
|
1306
1379
|
if (element.rotate) options.rotate = element.rotate;
|
|
1307
|
-
const filterOpacity = (_f = getFilterOpacity((_e = element.filters) == null ? void 0 : _e.opacity)) != null ? _f : 1;
|
|
1308
1380
|
const elementOpacity = getElementOpacity(element.opacity);
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
options.transparency = (1 - imageOpacity) * 100;
|
|
1381
|
+
if (elementOpacity !== 1) {
|
|
1382
|
+
options.transparency = (1 - elementOpacity) * 100;
|
|
1312
1383
|
}
|
|
1313
|
-
if ((
|
|
1384
|
+
if ((_f = element.clip) == null ? void 0 : _f.range) {
|
|
1314
1385
|
if (element.clip.shape === "ellipse") options.rounding = true;
|
|
1315
1386
|
const [start, end] = element.clip.range;
|
|
1316
1387
|
const [startX, startY] = start;
|
|
1317
1388
|
const [endX, endY] = end;
|
|
1318
|
-
const originW = ((
|
|
1319
|
-
const originH = ((
|
|
1389
|
+
const originW = ((_g = element.width) != null ? _g : 0) / ((endX - startX) / ratioPx2Inch);
|
|
1390
|
+
const originH = ((_h = element.height) != null ? _h : 0) / ((endY - startY) / ratioPx2Inch);
|
|
1320
1391
|
options.w = originW / ratioPx2Inch;
|
|
1321
1392
|
options.h = originH / ratioPx2Inch;
|
|
1322
1393
|
options.sizing = {
|
|
@@ -1328,6 +1399,14 @@ async function addImageElement(slide, element, ratioPx2Inch) {
|
|
|
1328
1399
|
};
|
|
1329
1400
|
}
|
|
1330
1401
|
slide.addImage(options);
|
|
1402
|
+
if (imageFiltersRequireXmlPatch(element.filters)) {
|
|
1403
|
+
imageFilterPatches.push({
|
|
1404
|
+
kind: "image",
|
|
1405
|
+
slideIndex,
|
|
1406
|
+
objectName,
|
|
1407
|
+
filters: element.filters
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1331
1410
|
}
|
|
1332
1411
|
function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, elementIndex, fillPatches) {
|
|
1333
1412
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
@@ -1491,11 +1570,12 @@ function parseDataUrlImage(dataUrl) {
|
|
|
1491
1570
|
return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
|
|
1492
1571
|
}
|
|
1493
1572
|
async function createPPTX(presentation) {
|
|
1494
|
-
var _a, _b, _c, _d;
|
|
1573
|
+
var _a, _b, _c, _d, _e;
|
|
1495
1574
|
const parsedPresentation = (0, import_json2pptx_schema.parseDocument)(presentation);
|
|
1496
1575
|
const normalizedPresentation = parsedPresentation;
|
|
1497
1576
|
const pptx = new import_pptxgenjs.default();
|
|
1498
1577
|
const fillPatches = [];
|
|
1578
|
+
const imageFilterPatches = [];
|
|
1499
1579
|
const width = parsedPresentation.width;
|
|
1500
1580
|
const height = parsedPresentation.height;
|
|
1501
1581
|
const ratioPx2Inch = 96 * (width / 960);
|
|
@@ -1524,7 +1604,7 @@ async function createPPTX(presentation) {
|
|
|
1524
1604
|
textPadding,
|
|
1525
1605
|
fillPatches
|
|
1526
1606
|
);
|
|
1527
|
-
await addImageElement(slide, element, ratioPx2Inch);
|
|
1607
|
+
await addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches);
|
|
1528
1608
|
addShapeElement(
|
|
1529
1609
|
slide,
|
|
1530
1610
|
element,
|
|
@@ -1543,7 +1623,7 @@ async function createPPTX(presentation) {
|
|
|
1543
1623
|
outputType: "arraybuffer",
|
|
1544
1624
|
compression: true
|
|
1545
1625
|
});
|
|
1546
|
-
if (!fillPatches.length) {
|
|
1626
|
+
if (!fillPatches.length && !imageFilterPatches.length) {
|
|
1547
1627
|
return { blob: new Blob([pptxBuffer]), fileName };
|
|
1548
1628
|
}
|
|
1549
1629
|
const zip = await import_jszip.default.loadAsync(pptxBuffer);
|
|
@@ -1588,6 +1668,18 @@ async function createPPTX(presentation) {
|
|
|
1588
1668
|
const nextSlideXml = patch.kind === "background" ? applyBackgroundFillPatch(slideXml, patch.fill, relId) : applyShapeFillPatch(slideXml, patch.objectName, patch.fill, relId);
|
|
1589
1669
|
slideCache.set(slideNumber, nextSlideXml);
|
|
1590
1670
|
}
|
|
1671
|
+
for (const patch of imageFilterPatches) {
|
|
1672
|
+
const slideNumber = patch.slideIndex + 1;
|
|
1673
|
+
const slidePath = `ppt/slides/slide${slideNumber}.xml`;
|
|
1674
|
+
const slideXml = (_e = slideCache.get(slideNumber)) != null ? _e : zip.file(slidePath) ? await zip.file(slidePath).async("string") : "";
|
|
1675
|
+
if (!slideXml) {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
slideCache.set(
|
|
1679
|
+
slideNumber,
|
|
1680
|
+
applyImageFilterPatch(slideXml, patch.objectName, patch.filters)
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1591
1683
|
for (const [slideNumber, xml] of slideCache.entries()) {
|
|
1592
1684
|
zip.file(`ppt/slides/slide${slideNumber}.xml`, xml);
|
|
1593
1685
|
}
|