@jsamuel1/pptxgenjs 4.1.0 → 4.1.1
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/README.md +7 -7
- package/dist/pptxgen.bundle.js +7 -7
- package/dist/pptxgen.bundle.js.map +1 -1
- package/dist/pptxgen.cjs.js +208 -13
- package/dist/pptxgen.es.js +208 -13
- package/dist/pptxgen.min.js +8 -8
- package/dist/pptxgen.min.js.map +1 -1
- package/package.json +23 -21
package/dist/pptxgen.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* PptxGenJS 4.0
|
|
1
|
+
/* PptxGenJS 4.1.0 @ 2026-06-07T11:53:01.413Z */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var JSZip = require('jszip');
|
|
@@ -664,7 +664,8 @@ function getUuid(uuidFormat) {
|
|
|
664
664
|
return uuidFormat.replace(/[xy]/g, function (c) {
|
|
665
665
|
const r = (Math.random() * 16) | 0;
|
|
666
666
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
667
|
-
|
|
667
|
+
// OOXML ST_Guid requires uppercase hex: \{[0-9A-F]{8}-...\}
|
|
668
|
+
return v.toString(16).toUpperCase();
|
|
668
669
|
});
|
|
669
670
|
}
|
|
670
671
|
/**
|
|
@@ -925,6 +926,170 @@ function correctShadowOptions(ShadowProps) {
|
|
|
925
926
|
}
|
|
926
927
|
return ShadowProps;
|
|
927
928
|
}
|
|
929
|
+
/**
|
|
930
|
+
* Convert an SVG `<path d="…">` definition to an OOXML custom geometry (`<a:custGeom>`).
|
|
931
|
+
*
|
|
932
|
+
* Supports the following SVG path commands (both absolute upper-case and relative lower-case):
|
|
933
|
+
* - `M`/`m` moveTo → `<a:moveTo>` (extra coordinate pairs become implicit `lineTo`)
|
|
934
|
+
* - `L`/`l` lineTo → `<a:lnTo>`
|
|
935
|
+
* - `H`/`h` horizontal lineTo → `<a:lnTo>`
|
|
936
|
+
* - `V`/`v` vertical lineTo → `<a:lnTo>`
|
|
937
|
+
* - `C`/`c` cubic Bézier → `<a:cubicBezTo>`
|
|
938
|
+
* - `Q`/`q` quadratic Bézier → `<a:quadBezTo>`
|
|
939
|
+
* - `Z`/`z` close path → `<a:close>`
|
|
940
|
+
*
|
|
941
|
+
* Relative commands are tracked against the current pen position and converted to absolute
|
|
942
|
+
* coordinates. Coordinates are scaled from the SVG viewBox into EMU via `914400 / width`, so the
|
|
943
|
+
* viewBox width maps to exactly 1 inch (914400 EMU) of path coordinate space. The shape's on-slide
|
|
944
|
+
* dimensions still come from the `<a:xfrm><a:ext>` set by the caller — the path coordinate system is
|
|
945
|
+
* stretched to fit it — so the absolute scale here only needs to be internally consistent.
|
|
946
|
+
*
|
|
947
|
+
* @param {string} svgPathD - the SVG path `d` attribute (e.g. `"M 0 0 L 12 0 L 6 12 Z"`)
|
|
948
|
+
* @param {number} width - SVG viewBox width
|
|
949
|
+
* @param {number} height - SVG viewBox height
|
|
950
|
+
* @returns {string} OOXML `<a:custGeom>…</a:custGeom>` string (empty string on invalid input)
|
|
951
|
+
* @see ECMA-376 §20.1.9.8 (custGeom) / §20.1.9.16 (path2D)
|
|
952
|
+
*/
|
|
953
|
+
function svgPathToOoxml(svgPathD, width, height) {
|
|
954
|
+
var _a;
|
|
955
|
+
if (!svgPathD || typeof svgPathD !== 'string' || !(width > 0) || !(height > 0))
|
|
956
|
+
return '';
|
|
957
|
+
const scale = 914400 / width;
|
|
958
|
+
const pathW = Math.round(width * scale);
|
|
959
|
+
const pathH = Math.round(height * scale);
|
|
960
|
+
// Match each command letter followed by its (possibly empty) run of numeric arguments
|
|
961
|
+
const commandRegex = /([MmLlHhVvCcQqZz])([^MmLlHhVvCcQqZz]*)/g;
|
|
962
|
+
// Match numbers incl. decimals, leading sign, and scientific notation
|
|
963
|
+
const numberRegex = /-?\d*\.?\d+(?:[eE][-+]?\d+)?/g;
|
|
964
|
+
const sc = (v) => Math.round(v * scale);
|
|
965
|
+
let curX = 0;
|
|
966
|
+
let curY = 0;
|
|
967
|
+
let startX = 0;
|
|
968
|
+
let startY = 0;
|
|
969
|
+
let xml = '';
|
|
970
|
+
let match;
|
|
971
|
+
while ((match = commandRegex.exec(svgPathD)) !== null) {
|
|
972
|
+
const cmd = match[1];
|
|
973
|
+
const isRel = cmd >= 'a' && cmd <= 'z';
|
|
974
|
+
const upper = cmd.toUpperCase();
|
|
975
|
+
const args = ((_a = match[2].match(numberRegex)) !== null && _a !== void 0 ? _a : []).map(Number);
|
|
976
|
+
let i = 0;
|
|
977
|
+
switch (upper) {
|
|
978
|
+
case 'M': {
|
|
979
|
+
// First coordinate pair is a moveTo; any subsequent pairs are implicit lineTo (per SVG spec)
|
|
980
|
+
let first = true;
|
|
981
|
+
while (i + 1 < args.length) {
|
|
982
|
+
let x = args[i];
|
|
983
|
+
let y = args[i + 1];
|
|
984
|
+
if (isRel) {
|
|
985
|
+
x += curX;
|
|
986
|
+
y += curY;
|
|
987
|
+
}
|
|
988
|
+
curX = x;
|
|
989
|
+
curY = y;
|
|
990
|
+
if (first) {
|
|
991
|
+
startX = curX;
|
|
992
|
+
startY = curY;
|
|
993
|
+
xml += `<a:moveTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:moveTo>`;
|
|
994
|
+
first = false;
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
998
|
+
}
|
|
999
|
+
i += 2;
|
|
1000
|
+
}
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
case 'L': {
|
|
1004
|
+
while (i + 1 < args.length) {
|
|
1005
|
+
let x = args[i];
|
|
1006
|
+
let y = args[i + 1];
|
|
1007
|
+
if (isRel) {
|
|
1008
|
+
x += curX;
|
|
1009
|
+
y += curY;
|
|
1010
|
+
}
|
|
1011
|
+
curX = x;
|
|
1012
|
+
curY = y;
|
|
1013
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1014
|
+
i += 2;
|
|
1015
|
+
}
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
case 'H': {
|
|
1019
|
+
while (i < args.length) {
|
|
1020
|
+
let x = args[i];
|
|
1021
|
+
if (isRel)
|
|
1022
|
+
x += curX;
|
|
1023
|
+
curX = x;
|
|
1024
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1025
|
+
i += 1;
|
|
1026
|
+
}
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
case 'V': {
|
|
1030
|
+
while (i < args.length) {
|
|
1031
|
+
let y = args[i];
|
|
1032
|
+
if (isRel)
|
|
1033
|
+
y += curY;
|
|
1034
|
+
curY = y;
|
|
1035
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1036
|
+
i += 1;
|
|
1037
|
+
}
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
case 'C': {
|
|
1041
|
+
while (i + 5 < args.length) {
|
|
1042
|
+
let x1 = args[i];
|
|
1043
|
+
let y1 = args[i + 1];
|
|
1044
|
+
let x2 = args[i + 2];
|
|
1045
|
+
let y2 = args[i + 3];
|
|
1046
|
+
let x = args[i + 4];
|
|
1047
|
+
let y = args[i + 5];
|
|
1048
|
+
if (isRel) {
|
|
1049
|
+
x1 += curX;
|
|
1050
|
+
y1 += curY;
|
|
1051
|
+
x2 += curX;
|
|
1052
|
+
y2 += curY;
|
|
1053
|
+
x += curX;
|
|
1054
|
+
y += curY;
|
|
1055
|
+
}
|
|
1056
|
+
xml += `<a:cubicBezTo><a:pt x="${sc(x1)}" y="${sc(y1)}"/><a:pt x="${sc(x2)}" y="${sc(y2)}"/><a:pt x="${sc(x)}" y="${sc(y)}"/></a:cubicBezTo>`;
|
|
1057
|
+
curX = x;
|
|
1058
|
+
curY = y;
|
|
1059
|
+
i += 6;
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
case 'Q': {
|
|
1064
|
+
while (i + 3 < args.length) {
|
|
1065
|
+
let x1 = args[i];
|
|
1066
|
+
let y1 = args[i + 1];
|
|
1067
|
+
let x = args[i + 2];
|
|
1068
|
+
let y = args[i + 3];
|
|
1069
|
+
if (isRel) {
|
|
1070
|
+
x1 += curX;
|
|
1071
|
+
y1 += curY;
|
|
1072
|
+
x += curX;
|
|
1073
|
+
y += curY;
|
|
1074
|
+
}
|
|
1075
|
+
xml += `<a:quadBezTo><a:pt x="${sc(x1)}" y="${sc(y1)}"/><a:pt x="${sc(x)}" y="${sc(y)}"/></a:quadBezTo>`;
|
|
1076
|
+
curX = x;
|
|
1077
|
+
curY = y;
|
|
1078
|
+
i += 4;
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case 'Z': {
|
|
1083
|
+
xml += '<a:close/>';
|
|
1084
|
+
// Pen returns to the start of the current subpath
|
|
1085
|
+
curX = startX;
|
|
1086
|
+
curY = startY;
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return `<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path w="${pathW}" h="${pathH}">${xml}</a:path></a:pathLst></a:custGeom>`;
|
|
1092
|
+
}
|
|
928
1093
|
|
|
929
1094
|
/**
|
|
930
1095
|
* PptxGenJS: Table Generation
|
|
@@ -2685,7 +2850,11 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
|
|
|
2685
2850
|
itemOpts.lineSpacingMultiple = itemOpts.lineSpacingMultiple && !isNaN(itemOpts.lineSpacingMultiple) ? itemOpts.lineSpacingMultiple : null;
|
|
2686
2851
|
// D: Transform text options to bodyProperties as thats how we build XML
|
|
2687
2852
|
itemOpts._bodyProp = itemOpts._bodyProp || {};
|
|
2688
|
-
|
|
2853
|
+
// Back-compat: legacy `autoFit: true` ("resize shape to fit text") now maps to `fit: 'resize'`.
|
|
2854
|
+
// Routing through `fit` keeps a single code path (and avoids emitting `<a:spAutoFit/>` twice). @deprecated 3.3.0
|
|
2855
|
+
if (itemOpts.autoFit === true && !itemOpts.fit)
|
|
2856
|
+
itemOpts.fit = 'resize';
|
|
2857
|
+
itemOpts._bodyProp.autoFit = false; // DEPRECATED: (3.3.0) superseded by `fit` (see above)
|
|
2689
2858
|
itemOpts._bodyProp.anchor = !itemOpts.placeholder ? TEXT_VALIGN.ctr : null; // VALS: [t,ctr,b]
|
|
2690
2859
|
itemOpts._bodyProp.vert = itemOpts.vert || null; // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
|
|
2691
2860
|
itemOpts._bodyProp.wrap = typeof itemOpts.wrap === 'boolean' ? itemOpts.wrap : true;
|
|
@@ -5292,6 +5461,20 @@ function slideObjectToXml(slide) {
|
|
|
5292
5461
|
if (placeholderObj.options.h || placeholderObj.options.h === 0)
|
|
5293
5462
|
cy = getSmartParseNumber(placeholderObj.options.h, 'Y', slide._presLayout);
|
|
5294
5463
|
}
|
|
5464
|
+
// Normalize negative extents: PPTX requires cx/cy >= 0 (ST_PositiveCoordinate); encode direction via flip
|
|
5465
|
+
// A shape drawn "backwards" (e.g. a line with negative w/h) otherwise emits an invalid `<a:ext>` that triggers PowerPoint repair.
|
|
5466
|
+
if (cx < 0) {
|
|
5467
|
+
x += cx;
|
|
5468
|
+
cx = Math.abs(cx);
|
|
5469
|
+
imgWidth = cx;
|
|
5470
|
+
slideItemObj.options.flipH = !slideItemObj.options.flipH;
|
|
5471
|
+
}
|
|
5472
|
+
if (cy < 0) {
|
|
5473
|
+
y += cy;
|
|
5474
|
+
cy = Math.abs(cy);
|
|
5475
|
+
imgHeight = cy;
|
|
5476
|
+
slideItemObj.options.flipV = !slideItemObj.options.flipV;
|
|
5477
|
+
}
|
|
5295
5478
|
//
|
|
5296
5479
|
if (slideItemObj.options.flipH)
|
|
5297
5480
|
locationAttr += ' flipH="1"';
|
|
@@ -5562,7 +5745,11 @@ function slideObjectToXml(slide) {
|
|
|
5562
5745
|
strSlideXml += `<a:xfrm${locationAttr}>`;
|
|
5563
5746
|
strSlideXml += `<a:off x="${x}" y="${y}"/>`;
|
|
5564
5747
|
strSlideXml += `<a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
|
|
5565
|
-
if (slideItemObj.
|
|
5748
|
+
if (slideItemObj.options.svgPath) {
|
|
5749
|
+
// Feature 9: Convert an SVG path to OOXML custom geometry (<a:custGeom>) instead of a preset geometry.
|
|
5750
|
+
strSlideXml += svgPathToOoxml(slideItemObj.options.svgPath.d, slideItemObj.options.svgPath.viewBox.w, slideItemObj.options.svgPath.viewBox.h);
|
|
5751
|
+
}
|
|
5752
|
+
else if (slideItemObj.shape === 'custGeom') {
|
|
5566
5753
|
strSlideXml += '<a:custGeom><a:avLst />';
|
|
5567
5754
|
strSlideXml += '<a:gdLst>';
|
|
5568
5755
|
strSlideXml += '</a:gdLst>';
|
|
@@ -6202,6 +6389,13 @@ function genXmlBodyProperties(slideObject) {
|
|
|
6202
6389
|
bodyProperties += ' anchor="' + slideObject.options._bodyProp.anchor + '"'; // VALS: [t,ctr,b]
|
|
6203
6390
|
if (slideObject.options._bodyProp.vert)
|
|
6204
6391
|
bodyProperties += ' vert="' + slideObject.options._bodyProp.vert + '"'; // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
|
|
6392
|
+
// D2: Multi-column text (numCol/spcCol attributes on <a:bodyPr>)
|
|
6393
|
+
// NOTE: must be appended as attributes BEFORE the opening tag is closed below (section E)
|
|
6394
|
+
if (slideObject.options.columns && slideObject.options.columns > 1) {
|
|
6395
|
+
bodyProperties += ` numCol="${Math.round(slideObject.options.columns)}"`;
|
|
6396
|
+
const spcColIn = typeof slideObject.options.columnSpacing === 'number' ? slideObject.options.columnSpacing : 0.5;
|
|
6397
|
+
bodyProperties += ` spcCol="${Math.round(spcColIn * EMU)}"`;
|
|
6398
|
+
}
|
|
6205
6399
|
// E: Close <a:bodyPr element
|
|
6206
6400
|
bodyProperties += '>';
|
|
6207
6401
|
/**
|
|
@@ -6213,10 +6407,10 @@ function genXmlBodyProperties(slideObject) {
|
|
|
6213
6407
|
// NOTE: Use of '<a:noAutofit/>' instead of '' causes issues in PPT-2013!
|
|
6214
6408
|
if (slideObject.options.fit === 'none')
|
|
6215
6409
|
bodyProperties += '';
|
|
6216
|
-
//
|
|
6217
|
-
//
|
|
6410
|
+
// "Shrink text on overflow": emit a fixed `fontScale` (70%) so the text is scaled down to fit the shape.
|
|
6411
|
+
// NOTE: PowerPoint recalculates `fontScale` dynamically once the text/shape is edited; the emitted value is the initial scale.
|
|
6218
6412
|
else if (slideObject.options.fit === 'shrink')
|
|
6219
|
-
bodyProperties += '<a:normAutofit/>';
|
|
6413
|
+
bodyProperties += '<a:normAutofit fontScale="70000"/>';
|
|
6220
6414
|
else if (slideObject.options.fit === 'resize')
|
|
6221
6415
|
bodyProperties += '<a:spAutoFit/>';
|
|
6222
6416
|
}
|
|
@@ -6744,14 +6938,15 @@ function genXmlAnimPayload(anim, spid, nextId) {
|
|
|
6744
6938
|
}
|
|
6745
6939
|
// flyIn ADDS a <p:anim> that translates the shape from offscreen to its final position.
|
|
6746
6940
|
// direction (TransitionDirection: left|right|up|down) -> animated attr + tm="0" start formula:
|
|
6747
|
-
// left -> ppt_x, "
|
|
6748
|
-
// up -> ppt_y, "
|
|
6941
|
+
// left -> ppt_x, "#ppt_x-1slide" right -> ppt_x, "#ppt_x+1slide"
|
|
6942
|
+
// up -> ppt_y, "#ppt_y+1slide" down -> ppt_y, "#ppt_y-1slide"
|
|
6943
|
+
// The "1slide" offset starts the shape one full slide-width/height offscreen (PowerPoint-native Fly In).
|
|
6749
6944
|
if (anim.type === 'flyIn') {
|
|
6750
6945
|
const flyMap = {
|
|
6751
|
-
left: { attr: 'ppt_x', start: '
|
|
6752
|
-
right: { attr: 'ppt_x', start: '
|
|
6753
|
-
up: { attr: 'ppt_y', start: '
|
|
6754
|
-
down: { attr: 'ppt_y', start: '
|
|
6946
|
+
left: { attr: 'ppt_x', start: '#ppt_x-1slide' },
|
|
6947
|
+
right: { attr: 'ppt_x', start: '#ppt_x+1slide' },
|
|
6948
|
+
up: { attr: 'ppt_y', start: '#ppt_y+1slide' },
|
|
6949
|
+
down: { attr: 'ppt_y', start: '#ppt_y-1slide' },
|
|
6755
6950
|
};
|
|
6756
6951
|
const { attr, start } = (_b = flyMap[(_a = anim.direction) !== null && _a !== void 0 ? _a : 'left']) !== null && _b !== void 0 ? _b : flyMap.left;
|
|
6757
6952
|
payload +=
|
package/dist/pptxgen.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* PptxGenJS 4.0
|
|
1
|
+
/* PptxGenJS 4.1.0 @ 2026-06-07T11:53:01.419Z */
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
|
|
4
4
|
/******************************************************************************
|
|
@@ -662,7 +662,8 @@ function getUuid(uuidFormat) {
|
|
|
662
662
|
return uuidFormat.replace(/[xy]/g, function (c) {
|
|
663
663
|
const r = (Math.random() * 16) | 0;
|
|
664
664
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
665
|
-
|
|
665
|
+
// OOXML ST_Guid requires uppercase hex: \{[0-9A-F]{8}-...\}
|
|
666
|
+
return v.toString(16).toUpperCase();
|
|
666
667
|
});
|
|
667
668
|
}
|
|
668
669
|
/**
|
|
@@ -923,6 +924,170 @@ function correctShadowOptions(ShadowProps) {
|
|
|
923
924
|
}
|
|
924
925
|
return ShadowProps;
|
|
925
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Convert an SVG `<path d="…">` definition to an OOXML custom geometry (`<a:custGeom>`).
|
|
929
|
+
*
|
|
930
|
+
* Supports the following SVG path commands (both absolute upper-case and relative lower-case):
|
|
931
|
+
* - `M`/`m` moveTo → `<a:moveTo>` (extra coordinate pairs become implicit `lineTo`)
|
|
932
|
+
* - `L`/`l` lineTo → `<a:lnTo>`
|
|
933
|
+
* - `H`/`h` horizontal lineTo → `<a:lnTo>`
|
|
934
|
+
* - `V`/`v` vertical lineTo → `<a:lnTo>`
|
|
935
|
+
* - `C`/`c` cubic Bézier → `<a:cubicBezTo>`
|
|
936
|
+
* - `Q`/`q` quadratic Bézier → `<a:quadBezTo>`
|
|
937
|
+
* - `Z`/`z` close path → `<a:close>`
|
|
938
|
+
*
|
|
939
|
+
* Relative commands are tracked against the current pen position and converted to absolute
|
|
940
|
+
* coordinates. Coordinates are scaled from the SVG viewBox into EMU via `914400 / width`, so the
|
|
941
|
+
* viewBox width maps to exactly 1 inch (914400 EMU) of path coordinate space. The shape's on-slide
|
|
942
|
+
* dimensions still come from the `<a:xfrm><a:ext>` set by the caller — the path coordinate system is
|
|
943
|
+
* stretched to fit it — so the absolute scale here only needs to be internally consistent.
|
|
944
|
+
*
|
|
945
|
+
* @param {string} svgPathD - the SVG path `d` attribute (e.g. `"M 0 0 L 12 0 L 6 12 Z"`)
|
|
946
|
+
* @param {number} width - SVG viewBox width
|
|
947
|
+
* @param {number} height - SVG viewBox height
|
|
948
|
+
* @returns {string} OOXML `<a:custGeom>…</a:custGeom>` string (empty string on invalid input)
|
|
949
|
+
* @see ECMA-376 §20.1.9.8 (custGeom) / §20.1.9.16 (path2D)
|
|
950
|
+
*/
|
|
951
|
+
function svgPathToOoxml(svgPathD, width, height) {
|
|
952
|
+
var _a;
|
|
953
|
+
if (!svgPathD || typeof svgPathD !== 'string' || !(width > 0) || !(height > 0))
|
|
954
|
+
return '';
|
|
955
|
+
const scale = 914400 / width;
|
|
956
|
+
const pathW = Math.round(width * scale);
|
|
957
|
+
const pathH = Math.round(height * scale);
|
|
958
|
+
// Match each command letter followed by its (possibly empty) run of numeric arguments
|
|
959
|
+
const commandRegex = /([MmLlHhVvCcQqZz])([^MmLlHhVvCcQqZz]*)/g;
|
|
960
|
+
// Match numbers incl. decimals, leading sign, and scientific notation
|
|
961
|
+
const numberRegex = /-?\d*\.?\d+(?:[eE][-+]?\d+)?/g;
|
|
962
|
+
const sc = (v) => Math.round(v * scale);
|
|
963
|
+
let curX = 0;
|
|
964
|
+
let curY = 0;
|
|
965
|
+
let startX = 0;
|
|
966
|
+
let startY = 0;
|
|
967
|
+
let xml = '';
|
|
968
|
+
let match;
|
|
969
|
+
while ((match = commandRegex.exec(svgPathD)) !== null) {
|
|
970
|
+
const cmd = match[1];
|
|
971
|
+
const isRel = cmd >= 'a' && cmd <= 'z';
|
|
972
|
+
const upper = cmd.toUpperCase();
|
|
973
|
+
const args = ((_a = match[2].match(numberRegex)) !== null && _a !== void 0 ? _a : []).map(Number);
|
|
974
|
+
let i = 0;
|
|
975
|
+
switch (upper) {
|
|
976
|
+
case 'M': {
|
|
977
|
+
// First coordinate pair is a moveTo; any subsequent pairs are implicit lineTo (per SVG spec)
|
|
978
|
+
let first = true;
|
|
979
|
+
while (i + 1 < args.length) {
|
|
980
|
+
let x = args[i];
|
|
981
|
+
let y = args[i + 1];
|
|
982
|
+
if (isRel) {
|
|
983
|
+
x += curX;
|
|
984
|
+
y += curY;
|
|
985
|
+
}
|
|
986
|
+
curX = x;
|
|
987
|
+
curY = y;
|
|
988
|
+
if (first) {
|
|
989
|
+
startX = curX;
|
|
990
|
+
startY = curY;
|
|
991
|
+
xml += `<a:moveTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:moveTo>`;
|
|
992
|
+
first = false;
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
996
|
+
}
|
|
997
|
+
i += 2;
|
|
998
|
+
}
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
case 'L': {
|
|
1002
|
+
while (i + 1 < args.length) {
|
|
1003
|
+
let x = args[i];
|
|
1004
|
+
let y = args[i + 1];
|
|
1005
|
+
if (isRel) {
|
|
1006
|
+
x += curX;
|
|
1007
|
+
y += curY;
|
|
1008
|
+
}
|
|
1009
|
+
curX = x;
|
|
1010
|
+
curY = y;
|
|
1011
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1012
|
+
i += 2;
|
|
1013
|
+
}
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
case 'H': {
|
|
1017
|
+
while (i < args.length) {
|
|
1018
|
+
let x = args[i];
|
|
1019
|
+
if (isRel)
|
|
1020
|
+
x += curX;
|
|
1021
|
+
curX = x;
|
|
1022
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1023
|
+
i += 1;
|
|
1024
|
+
}
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
case 'V': {
|
|
1028
|
+
while (i < args.length) {
|
|
1029
|
+
let y = args[i];
|
|
1030
|
+
if (isRel)
|
|
1031
|
+
y += curY;
|
|
1032
|
+
curY = y;
|
|
1033
|
+
xml += `<a:lnTo><a:pt x="${sc(curX)}" y="${sc(curY)}"/></a:lnTo>`;
|
|
1034
|
+
i += 1;
|
|
1035
|
+
}
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
case 'C': {
|
|
1039
|
+
while (i + 5 < args.length) {
|
|
1040
|
+
let x1 = args[i];
|
|
1041
|
+
let y1 = args[i + 1];
|
|
1042
|
+
let x2 = args[i + 2];
|
|
1043
|
+
let y2 = args[i + 3];
|
|
1044
|
+
let x = args[i + 4];
|
|
1045
|
+
let y = args[i + 5];
|
|
1046
|
+
if (isRel) {
|
|
1047
|
+
x1 += curX;
|
|
1048
|
+
y1 += curY;
|
|
1049
|
+
x2 += curX;
|
|
1050
|
+
y2 += curY;
|
|
1051
|
+
x += curX;
|
|
1052
|
+
y += curY;
|
|
1053
|
+
}
|
|
1054
|
+
xml += `<a:cubicBezTo><a:pt x="${sc(x1)}" y="${sc(y1)}"/><a:pt x="${sc(x2)}" y="${sc(y2)}"/><a:pt x="${sc(x)}" y="${sc(y)}"/></a:cubicBezTo>`;
|
|
1055
|
+
curX = x;
|
|
1056
|
+
curY = y;
|
|
1057
|
+
i += 6;
|
|
1058
|
+
}
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
case 'Q': {
|
|
1062
|
+
while (i + 3 < args.length) {
|
|
1063
|
+
let x1 = args[i];
|
|
1064
|
+
let y1 = args[i + 1];
|
|
1065
|
+
let x = args[i + 2];
|
|
1066
|
+
let y = args[i + 3];
|
|
1067
|
+
if (isRel) {
|
|
1068
|
+
x1 += curX;
|
|
1069
|
+
y1 += curY;
|
|
1070
|
+
x += curX;
|
|
1071
|
+
y += curY;
|
|
1072
|
+
}
|
|
1073
|
+
xml += `<a:quadBezTo><a:pt x="${sc(x1)}" y="${sc(y1)}"/><a:pt x="${sc(x)}" y="${sc(y)}"/></a:quadBezTo>`;
|
|
1074
|
+
curX = x;
|
|
1075
|
+
curY = y;
|
|
1076
|
+
i += 4;
|
|
1077
|
+
}
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
case 'Z': {
|
|
1081
|
+
xml += '<a:close/>';
|
|
1082
|
+
// Pen returns to the start of the current subpath
|
|
1083
|
+
curX = startX;
|
|
1084
|
+
curY = startY;
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return `<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path w="${pathW}" h="${pathH}">${xml}</a:path></a:pathLst></a:custGeom>`;
|
|
1090
|
+
}
|
|
926
1091
|
|
|
927
1092
|
/**
|
|
928
1093
|
* PptxGenJS: Table Generation
|
|
@@ -2683,7 +2848,11 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
|
|
|
2683
2848
|
itemOpts.lineSpacingMultiple = itemOpts.lineSpacingMultiple && !isNaN(itemOpts.lineSpacingMultiple) ? itemOpts.lineSpacingMultiple : null;
|
|
2684
2849
|
// D: Transform text options to bodyProperties as thats how we build XML
|
|
2685
2850
|
itemOpts._bodyProp = itemOpts._bodyProp || {};
|
|
2686
|
-
|
|
2851
|
+
// Back-compat: legacy `autoFit: true` ("resize shape to fit text") now maps to `fit: 'resize'`.
|
|
2852
|
+
// Routing through `fit` keeps a single code path (and avoids emitting `<a:spAutoFit/>` twice). @deprecated 3.3.0
|
|
2853
|
+
if (itemOpts.autoFit === true && !itemOpts.fit)
|
|
2854
|
+
itemOpts.fit = 'resize';
|
|
2855
|
+
itemOpts._bodyProp.autoFit = false; // DEPRECATED: (3.3.0) superseded by `fit` (see above)
|
|
2687
2856
|
itemOpts._bodyProp.anchor = !itemOpts.placeholder ? TEXT_VALIGN.ctr : null; // VALS: [t,ctr,b]
|
|
2688
2857
|
itemOpts._bodyProp.vert = itemOpts.vert || null; // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
|
|
2689
2858
|
itemOpts._bodyProp.wrap = typeof itemOpts.wrap === 'boolean' ? itemOpts.wrap : true;
|
|
@@ -5290,6 +5459,20 @@ function slideObjectToXml(slide) {
|
|
|
5290
5459
|
if (placeholderObj.options.h || placeholderObj.options.h === 0)
|
|
5291
5460
|
cy = getSmartParseNumber(placeholderObj.options.h, 'Y', slide._presLayout);
|
|
5292
5461
|
}
|
|
5462
|
+
// Normalize negative extents: PPTX requires cx/cy >= 0 (ST_PositiveCoordinate); encode direction via flip
|
|
5463
|
+
// A shape drawn "backwards" (e.g. a line with negative w/h) otherwise emits an invalid `<a:ext>` that triggers PowerPoint repair.
|
|
5464
|
+
if (cx < 0) {
|
|
5465
|
+
x += cx;
|
|
5466
|
+
cx = Math.abs(cx);
|
|
5467
|
+
imgWidth = cx;
|
|
5468
|
+
slideItemObj.options.flipH = !slideItemObj.options.flipH;
|
|
5469
|
+
}
|
|
5470
|
+
if (cy < 0) {
|
|
5471
|
+
y += cy;
|
|
5472
|
+
cy = Math.abs(cy);
|
|
5473
|
+
imgHeight = cy;
|
|
5474
|
+
slideItemObj.options.flipV = !slideItemObj.options.flipV;
|
|
5475
|
+
}
|
|
5293
5476
|
//
|
|
5294
5477
|
if (slideItemObj.options.flipH)
|
|
5295
5478
|
locationAttr += ' flipH="1"';
|
|
@@ -5560,7 +5743,11 @@ function slideObjectToXml(slide) {
|
|
|
5560
5743
|
strSlideXml += `<a:xfrm${locationAttr}>`;
|
|
5561
5744
|
strSlideXml += `<a:off x="${x}" y="${y}"/>`;
|
|
5562
5745
|
strSlideXml += `<a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
|
|
5563
|
-
if (slideItemObj.
|
|
5746
|
+
if (slideItemObj.options.svgPath) {
|
|
5747
|
+
// Feature 9: Convert an SVG path to OOXML custom geometry (<a:custGeom>) instead of a preset geometry.
|
|
5748
|
+
strSlideXml += svgPathToOoxml(slideItemObj.options.svgPath.d, slideItemObj.options.svgPath.viewBox.w, slideItemObj.options.svgPath.viewBox.h);
|
|
5749
|
+
}
|
|
5750
|
+
else if (slideItemObj.shape === 'custGeom') {
|
|
5564
5751
|
strSlideXml += '<a:custGeom><a:avLst />';
|
|
5565
5752
|
strSlideXml += '<a:gdLst>';
|
|
5566
5753
|
strSlideXml += '</a:gdLst>';
|
|
@@ -6200,6 +6387,13 @@ function genXmlBodyProperties(slideObject) {
|
|
|
6200
6387
|
bodyProperties += ' anchor="' + slideObject.options._bodyProp.anchor + '"'; // VALS: [t,ctr,b]
|
|
6201
6388
|
if (slideObject.options._bodyProp.vert)
|
|
6202
6389
|
bodyProperties += ' vert="' + slideObject.options._bodyProp.vert + '"'; // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
|
|
6390
|
+
// D2: Multi-column text (numCol/spcCol attributes on <a:bodyPr>)
|
|
6391
|
+
// NOTE: must be appended as attributes BEFORE the opening tag is closed below (section E)
|
|
6392
|
+
if (slideObject.options.columns && slideObject.options.columns > 1) {
|
|
6393
|
+
bodyProperties += ` numCol="${Math.round(slideObject.options.columns)}"`;
|
|
6394
|
+
const spcColIn = typeof slideObject.options.columnSpacing === 'number' ? slideObject.options.columnSpacing : 0.5;
|
|
6395
|
+
bodyProperties += ` spcCol="${Math.round(spcColIn * EMU)}"`;
|
|
6396
|
+
}
|
|
6203
6397
|
// E: Close <a:bodyPr element
|
|
6204
6398
|
bodyProperties += '>';
|
|
6205
6399
|
/**
|
|
@@ -6211,10 +6405,10 @@ function genXmlBodyProperties(slideObject) {
|
|
|
6211
6405
|
// NOTE: Use of '<a:noAutofit/>' instead of '' causes issues in PPT-2013!
|
|
6212
6406
|
if (slideObject.options.fit === 'none')
|
|
6213
6407
|
bodyProperties += '';
|
|
6214
|
-
//
|
|
6215
|
-
//
|
|
6408
|
+
// "Shrink text on overflow": emit a fixed `fontScale` (70%) so the text is scaled down to fit the shape.
|
|
6409
|
+
// NOTE: PowerPoint recalculates `fontScale` dynamically once the text/shape is edited; the emitted value is the initial scale.
|
|
6216
6410
|
else if (slideObject.options.fit === 'shrink')
|
|
6217
|
-
bodyProperties += '<a:normAutofit/>';
|
|
6411
|
+
bodyProperties += '<a:normAutofit fontScale="70000"/>';
|
|
6218
6412
|
else if (slideObject.options.fit === 'resize')
|
|
6219
6413
|
bodyProperties += '<a:spAutoFit/>';
|
|
6220
6414
|
}
|
|
@@ -6742,14 +6936,15 @@ function genXmlAnimPayload(anim, spid, nextId) {
|
|
|
6742
6936
|
}
|
|
6743
6937
|
// flyIn ADDS a <p:anim> that translates the shape from offscreen to its final position.
|
|
6744
6938
|
// direction (TransitionDirection: left|right|up|down) -> animated attr + tm="0" start formula:
|
|
6745
|
-
// left -> ppt_x, "
|
|
6746
|
-
// up -> ppt_y, "
|
|
6939
|
+
// left -> ppt_x, "#ppt_x-1slide" right -> ppt_x, "#ppt_x+1slide"
|
|
6940
|
+
// up -> ppt_y, "#ppt_y+1slide" down -> ppt_y, "#ppt_y-1slide"
|
|
6941
|
+
// The "1slide" offset starts the shape one full slide-width/height offscreen (PowerPoint-native Fly In).
|
|
6747
6942
|
if (anim.type === 'flyIn') {
|
|
6748
6943
|
const flyMap = {
|
|
6749
|
-
left: { attr: 'ppt_x', start: '
|
|
6750
|
-
right: { attr: 'ppt_x', start: '
|
|
6751
|
-
up: { attr: 'ppt_y', start: '
|
|
6752
|
-
down: { attr: 'ppt_y', start: '
|
|
6944
|
+
left: { attr: 'ppt_x', start: '#ppt_x-1slide' },
|
|
6945
|
+
right: { attr: 'ppt_x', start: '#ppt_x+1slide' },
|
|
6946
|
+
up: { attr: 'ppt_y', start: '#ppt_y+1slide' },
|
|
6947
|
+
down: { attr: 'ppt_y', start: '#ppt_y-1slide' },
|
|
6753
6948
|
};
|
|
6754
6949
|
const { attr, start } = (_b = flyMap[(_a = anim.direction) !== null && _a !== void 0 ? _a : 'left']) !== null && _b !== void 0 ? _b : flyMap.left;
|
|
6755
6950
|
payload +=
|