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.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("%")) {
|
|
@@ -909,8 +912,9 @@ function getShadowOption(shadow, ratioPx2Pt) {
|
|
|
909
912
|
angle = 225;
|
|
910
913
|
}
|
|
911
914
|
return {
|
|
915
|
+
type: "outer",
|
|
912
916
|
color: c.color,
|
|
913
|
-
|
|
917
|
+
opacity: c.alpha,
|
|
914
918
|
blur: ((_b = shadow.blur) != null ? _b : 0) / ratioPx2Pt,
|
|
915
919
|
offset,
|
|
916
920
|
angle
|
|
@@ -1052,6 +1056,74 @@ function applySlideBackground(slide, slideJson, theme) {
|
|
|
1052
1056
|
slide.background = { color: c.color, transparency: (1 - c.alpha) * 100 };
|
|
1053
1057
|
}
|
|
1054
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
|
+
|
|
1055
1127
|
// src/renderers/layout.ts
|
|
1056
1128
|
function applyPptxLayout(pptx, width, height) {
|
|
1057
1129
|
const viewportRatio = height / width;
|
|
@@ -1096,7 +1168,7 @@ var toPoints = (d) => {
|
|
|
1096
1168
|
if (d.includes("NaN") || d.includes("undefined") || d.includes("null")) return [];
|
|
1097
1169
|
let pathData;
|
|
1098
1170
|
try {
|
|
1099
|
-
pathData = new import_svg_pathdata.SVGPathData(d);
|
|
1171
|
+
pathData = new import_svg_pathdata.SVGPathData(d).toAbs().normalizeST().normalizeHVZ(false);
|
|
1100
1172
|
} catch {
|
|
1101
1173
|
return [];
|
|
1102
1174
|
}
|
|
@@ -1289,33 +1361,33 @@ function addTextElement(slide, element, presentation, slideIndex, elementIndex,
|
|
|
1289
1361
|
if (element.flipV) options.flipV = element.flipV;
|
|
1290
1362
|
slide.addText(textProps, options);
|
|
1291
1363
|
}
|
|
1292
|
-
async function addImageElement(slide, element, ratioPx2Inch) {
|
|
1293
|
-
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;
|
|
1294
1366
|
if (element.type !== "image" || !element.src) return;
|
|
1367
|
+
const objectName = `image-${slideIndex}-${(_a = element.id) != null ? _a : elementIndex}`;
|
|
1295
1368
|
const options = {
|
|
1296
|
-
x: ((
|
|
1297
|
-
y: ((
|
|
1298
|
-
w: ((
|
|
1299
|
-
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
|
|
1300
1374
|
};
|
|
1301
1375
|
if (isBase64Image(element.src)) options.data = element.src;
|
|
1302
1376
|
else options.data = await resolveImageData(element.src);
|
|
1303
1377
|
if (element.flipH) options.flipH = element.flipH;
|
|
1304
1378
|
if (element.flipV) options.flipV = element.flipV;
|
|
1305
1379
|
if (element.rotate) options.rotate = element.rotate;
|
|
1306
|
-
const filterOpacity = (_f = getFilterOpacity((_e = element.filters) == null ? void 0 : _e.opacity)) != null ? _f : 1;
|
|
1307
1380
|
const elementOpacity = getElementOpacity(element.opacity);
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
options.transparency = (1 - imageOpacity) * 100;
|
|
1381
|
+
if (elementOpacity !== 1) {
|
|
1382
|
+
options.transparency = (1 - elementOpacity) * 100;
|
|
1311
1383
|
}
|
|
1312
|
-
if ((
|
|
1384
|
+
if ((_f = element.clip) == null ? void 0 : _f.range) {
|
|
1313
1385
|
if (element.clip.shape === "ellipse") options.rounding = true;
|
|
1314
1386
|
const [start, end] = element.clip.range;
|
|
1315
1387
|
const [startX, startY] = start;
|
|
1316
1388
|
const [endX, endY] = end;
|
|
1317
|
-
const originW = ((
|
|
1318
|
-
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);
|
|
1319
1391
|
options.w = originW / ratioPx2Inch;
|
|
1320
1392
|
options.h = originH / ratioPx2Inch;
|
|
1321
1393
|
options.sizing = {
|
|
@@ -1327,6 +1399,14 @@ async function addImageElement(slide, element, ratioPx2Inch) {
|
|
|
1327
1399
|
};
|
|
1328
1400
|
}
|
|
1329
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
|
+
}
|
|
1330
1410
|
}
|
|
1331
1411
|
function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, elementIndex, fillPatches) {
|
|
1332
1412
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
@@ -1385,7 +1465,7 @@ function addShapeElement(slide, element, ratioPx2Pt, ratioPx2Inch, slideIndex, e
|
|
|
1385
1465
|
fontFace: element.text.defaultFontName || DEFAULT_FONT_FACE,
|
|
1386
1466
|
color: "#000000",
|
|
1387
1467
|
paraSpaceBefore: 0,
|
|
1388
|
-
valign: element.text.align === "middle" ? "
|
|
1468
|
+
valign: element.text.align === "middle" ? "middle" : element.text.align,
|
|
1389
1469
|
fill: { color: "FFFFFF", transparency: 100 },
|
|
1390
1470
|
fit: "none",
|
|
1391
1471
|
objectName: `shape-text-${slideIndex}-${(_n = element.id) != null ? _n : elementIndex}`
|
|
@@ -1490,11 +1570,12 @@ function parseDataUrlImage(dataUrl) {
|
|
|
1490
1570
|
return { mime, data, ext: (_a = extMap[mime]) != null ? _a : "png" };
|
|
1491
1571
|
}
|
|
1492
1572
|
async function createPPTX(presentation) {
|
|
1493
|
-
var _a, _b, _c, _d;
|
|
1573
|
+
var _a, _b, _c, _d, _e;
|
|
1494
1574
|
const parsedPresentation = (0, import_json2pptx_schema.parseDocument)(presentation);
|
|
1495
1575
|
const normalizedPresentation = parsedPresentation;
|
|
1496
1576
|
const pptx = new import_pptxgenjs.default();
|
|
1497
1577
|
const fillPatches = [];
|
|
1578
|
+
const imageFilterPatches = [];
|
|
1498
1579
|
const width = parsedPresentation.width;
|
|
1499
1580
|
const height = parsedPresentation.height;
|
|
1500
1581
|
const ratioPx2Inch = 96 * (width / 960);
|
|
@@ -1523,7 +1604,7 @@ async function createPPTX(presentation) {
|
|
|
1523
1604
|
textPadding,
|
|
1524
1605
|
fillPatches
|
|
1525
1606
|
);
|
|
1526
|
-
await addImageElement(slide, element, ratioPx2Inch);
|
|
1607
|
+
await addImageElement(slide, element, ratioPx2Inch, slideIndex, elementIndex, imageFilterPatches);
|
|
1527
1608
|
addShapeElement(
|
|
1528
1609
|
slide,
|
|
1529
1610
|
element,
|
|
@@ -1542,7 +1623,7 @@ async function createPPTX(presentation) {
|
|
|
1542
1623
|
outputType: "arraybuffer",
|
|
1543
1624
|
compression: true
|
|
1544
1625
|
});
|
|
1545
|
-
if (!fillPatches.length) {
|
|
1626
|
+
if (!fillPatches.length && !imageFilterPatches.length) {
|
|
1546
1627
|
return { blob: new Blob([pptxBuffer]), fileName };
|
|
1547
1628
|
}
|
|
1548
1629
|
const zip = await import_jszip.default.loadAsync(pptxBuffer);
|
|
@@ -1587,6 +1668,18 @@ async function createPPTX(presentation) {
|
|
|
1587
1668
|
const nextSlideXml = patch.kind === "background" ? applyBackgroundFillPatch(slideXml, patch.fill, relId) : applyShapeFillPatch(slideXml, patch.objectName, patch.fill, relId);
|
|
1588
1669
|
slideCache.set(slideNumber, nextSlideXml);
|
|
1589
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
|
+
}
|
|
1590
1683
|
for (const [slideNumber, xml] of slideCache.entries()) {
|
|
1591
1684
|
zip.file(`ppt/slides/slide${slideNumber}.xml`, xml);
|
|
1592
1685
|
}
|