@jsamuel1/pptxgenjs 4.1.3 → 4.1.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/pptxgen.bundle.js +2 -95
- package/dist/pptxgen.bundle.js.map +1 -1
- package/dist/pptxgen.cjs.js +295 -31
- package/dist/pptxgen.es.js +295 -31
- package/dist/pptxgen.min.js +2 -95
- package/dist/pptxgen.min.js.map +1 -1
- package/dist/utils.cjs.js +148 -0
- package/dist/utils.es.js +146 -0
- package/dist/utils.js +148 -0
- package/package.json +25 -26
- package/types/index.d.ts +41 -0
- package/types/utils.d.ts +40 -0
package/dist/pptxgen.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* PptxGenJS 4.1.
|
|
1
|
+
/* PptxGenJS 4.1.4 @ 2026-06-08T05:10:40.789Z */
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
|
|
4
4
|
/******************************************************************************
|
|
@@ -842,11 +842,14 @@ function genXmlGradientFill(props) {
|
|
|
842
842
|
const stops = [...props.stops].sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
843
843
|
const gsList = stops
|
|
844
844
|
.map(stop => {
|
|
845
|
-
// position 0–100 → `pos` in thousandths of a percent (× 1000)
|
|
846
|
-
|
|
845
|
+
// position 0–100 → `pos` in thousandths of a percent (× 1000).
|
|
846
|
+
// `pos` is ST_PositiveFixedPercentage [0,100000]; clamp the 0–100 input
|
|
847
|
+
// before scaling so out-of-range stops stay schema-valid (clamp-don't-crash).
|
|
848
|
+
const pos = Math.round(Math.max(0, Math.min(100, stop.position || 0)) * 1000);
|
|
847
849
|
// Per-stop transparency uses PROMPT.md direct mapping (100 = opaque → 100000; 40 → 40000).
|
|
848
850
|
// NOTE: this differs from the solid-fill path which inverts via `(100 - transparency) * 1000`.
|
|
849
|
-
|
|
851
|
+
// `a:alpha@val` is also ST_PositiveFixedPercentage [0,100000]; clamp into [0,100] first.
|
|
852
|
+
const inner = typeof stop.transparency === 'number' ? `<a:alpha val="${Math.round(Math.max(0, Math.min(100, stop.transparency)) * 1000)}"/>` : '';
|
|
850
853
|
return `<a:gs pos="${pos}">${createColorElement(stop.color, inner)}</a:gs>`;
|
|
851
854
|
})
|
|
852
855
|
.join('');
|
|
@@ -1089,6 +1092,75 @@ function svgPathToOoxml(svgPathD, width, height) {
|
|
|
1089
1092
|
}
|
|
1090
1093
|
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>`;
|
|
1091
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Compute evenly-spaced grid cell positions within a bounding area.
|
|
1097
|
+
* - Pure math utility (no OOXML emission); returns one `{ x, y, w, h }` (inches) per item, in item order.
|
|
1098
|
+
* - Eliminates repetitive grid-math when positioning capability cards, icon grids, comparison layouts, etc.
|
|
1099
|
+
*
|
|
1100
|
+
* Calculation (per item `i`):
|
|
1101
|
+
* cellW = (area.w - (columns - 1) * gapX) / columns
|
|
1102
|
+
* rows = ceil(items / columns)
|
|
1103
|
+
* cellH = (area.h - (rows - 1) * gapY) / rows
|
|
1104
|
+
* col = i % columns; row = floor(i / columns)
|
|
1105
|
+
* x = area.x + col * (cellW + gapX); y = area.y + row * (cellH + gapY)
|
|
1106
|
+
*
|
|
1107
|
+
* @param {LayoutGridProps} props - grid options
|
|
1108
|
+
* @returns {LayoutGridResult} array of `{ x, y, w, h }` cells (inches), one per item
|
|
1109
|
+
* @throws {Error} when `area` has zero/negative width or height
|
|
1110
|
+
* @example pptx.layoutGrid({ items: 6, columns: 3, area: { x: 0.5, y: 2, w: 12, h: 4 }, gap: 0.2 })
|
|
1111
|
+
*/
|
|
1112
|
+
function layoutGrid(props) {
|
|
1113
|
+
var _a, _b, _c, _d, _e;
|
|
1114
|
+
const { items, columns, area } = props;
|
|
1115
|
+
const gap = (_a = props.gap) !== null && _a !== void 0 ? _a : 0.2;
|
|
1116
|
+
const gapX = (_b = props.gapX) !== null && _b !== void 0 ? _b : gap;
|
|
1117
|
+
const gapY = (_c = props.gapY) !== null && _c !== void 0 ? _c : gap;
|
|
1118
|
+
const padding = (_d = props.padding) !== null && _d !== void 0 ? _d : 0;
|
|
1119
|
+
const align = (_e = props.align) !== null && _e !== void 0 ? _e : 'start';
|
|
1120
|
+
// Edge case: no items -> empty result
|
|
1121
|
+
if (!items || items <= 0)
|
|
1122
|
+
return [];
|
|
1123
|
+
// Guard: a zero/negative area can't be subdivided
|
|
1124
|
+
if (!area || !(area.w > 0) || !(area.h > 0))
|
|
1125
|
+
throw new Error('layoutGrid: `area` requires positive `w` and `h`');
|
|
1126
|
+
if (!(columns > 0))
|
|
1127
|
+
throw new Error('layoutGrid: `columns` must be a positive number');
|
|
1128
|
+
const rows = Math.ceil(items / columns);
|
|
1129
|
+
const cellW = (area.w - (columns - 1) * gapX) / columns;
|
|
1130
|
+
const cellH = (area.h - (rows - 1) * gapY) / rows;
|
|
1131
|
+
const result = [];
|
|
1132
|
+
for (let i = 0; i < items; i++) {
|
|
1133
|
+
const col = i % columns;
|
|
1134
|
+
const row = Math.floor(i / columns);
|
|
1135
|
+
// Items on the final (possibly partial) row can be re-aligned within the area
|
|
1136
|
+
let rowCellW = cellW;
|
|
1137
|
+
let rowOffsetX = 0;
|
|
1138
|
+
const isLastRow = row === rows - 1;
|
|
1139
|
+
const lastRowCount = items - (rows - 1) * columns;
|
|
1140
|
+
if (isLastRow && lastRowCount < columns && lastRowCount > 0) {
|
|
1141
|
+
const rowCols = lastRowCount;
|
|
1142
|
+
if (align === 'stretch') {
|
|
1143
|
+
// Widen the partial row's cells to fill the full area width
|
|
1144
|
+
rowCellW = (area.w - (rowCols - 1) * gapX) / rowCols;
|
|
1145
|
+
}
|
|
1146
|
+
else if (align === 'center') {
|
|
1147
|
+
// Centre the partial row (cells keep their size)
|
|
1148
|
+
const rowWidth = rowCols * cellW + (rowCols - 1) * gapX;
|
|
1149
|
+
rowOffsetX = (area.w - rowWidth) / 2;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const x = area.x + rowOffsetX + col * (rowCellW + gapX);
|
|
1153
|
+
const y = area.y + row * (cellH + gapY);
|
|
1154
|
+
// `padding` insets each cell box on all sides
|
|
1155
|
+
result.push({
|
|
1156
|
+
x: x + padding,
|
|
1157
|
+
y: y + padding,
|
|
1158
|
+
w: rowCellW - 2 * padding,
|
|
1159
|
+
h: cellH - 2 * padding,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1092
1164
|
|
|
1093
1165
|
/**
|
|
1094
1166
|
* PptxGenJS: Table Generation
|
|
@@ -2513,6 +2585,122 @@ function addCalloutDefinition(target, opts) {
|
|
|
2513
2585
|
textOpts.objectName = options.objectName;
|
|
2514
2586
|
addTextDefinition(target, [{ text: options.text || '', options: null }], textOpts, false);
|
|
2515
2587
|
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Adds a structured "card" to a slide definition (docs/feature-card-helper.md).
|
|
2590
|
+
* Builds a single shape group (`<p:grpSp>`) containing a rounded-rect background and,
|
|
2591
|
+
* as applicable, an icon container + icon (SVG path or emoji/text), a title, a description,
|
|
2592
|
+
* and a top-right badge — all positioned with group-relative coordinates. When `animation`
|
|
2593
|
+
* is supplied it is attached to the group object so the whole card animates as one.
|
|
2594
|
+
* @param {PresSlide} target slide the card should be added to
|
|
2595
|
+
* @param {CardProps} opts card options
|
|
2596
|
+
*/
|
|
2597
|
+
function addCardDefinition(target, opts) {
|
|
2598
|
+
const options = typeof opts === 'object' ? opts : {};
|
|
2599
|
+
const x = options.x !== undefined ? Number(options.x) : 1;
|
|
2600
|
+
const y = options.y !== undefined ? Number(options.y) : 1;
|
|
2601
|
+
const w = options.w !== undefined ? Number(options.w) : 3;
|
|
2602
|
+
const h = options.h !== undefined ? Number(options.h) : 2;
|
|
2603
|
+
const padding = 0.2;
|
|
2604
|
+
const cornerRadius = options.cornerRadius !== undefined ? options.cornerRadius : 0.12;
|
|
2605
|
+
const fill = options.fill !== undefined ? options.fill : '1a1a24';
|
|
2606
|
+
const align = options.align === 'left' ? 'left' : 'center';
|
|
2607
|
+
const iconPosition = options.iconPosition === 'left' ? 'left' : 'top';
|
|
2608
|
+
const iconSize = options.iconSize !== undefined ? Number(options.iconSize) : 0.4;
|
|
2609
|
+
const hasIcon = options.icon !== undefined && options.icon !== null;
|
|
2610
|
+
const titleFont = options.titleFont || {};
|
|
2611
|
+
const descFont = options.descFont || {};
|
|
2612
|
+
const textAlign = align === 'center' ? 'center' : 'left';
|
|
2613
|
+
// Create the group container; children below use coords relative to the group origin (0,0..w,h)
|
|
2614
|
+
const group = addGroupDefinition(target, { x, y, w, h, objectName: options.objectName });
|
|
2615
|
+
// The group is the just-pushed top-level slide object — grab it so card-level animation can attach
|
|
2616
|
+
const groupObj = target._slideObjects[target._slideObjects.length - 1];
|
|
2617
|
+
if (options.animation && groupObj && groupObj.options)
|
|
2618
|
+
groupObj.options.animation = options.animation;
|
|
2619
|
+
// 1) Background roundRect (fill + optional border/shadow/glow). `rectRadius` maps inches -> adj.
|
|
2620
|
+
const bgOpts = {
|
|
2621
|
+
x: 0, y: 0, w, h,
|
|
2622
|
+
fill: typeof fill === 'string' ? { color: fill } : fill,
|
|
2623
|
+
rectRadius: cornerRadius,
|
|
2624
|
+
};
|
|
2625
|
+
if (options.border)
|
|
2626
|
+
bgOpts.line = { color: options.border.color || '2A2438', width: options.border.width || 1 };
|
|
2627
|
+
if (options.shadow)
|
|
2628
|
+
bgOpts.shadow = Object.assign({ type: 'outer' }, options.shadow);
|
|
2629
|
+
if (options.glow)
|
|
2630
|
+
bgOpts.glow = options.glow;
|
|
2631
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, bgOpts);
|
|
2632
|
+
// 2) Layout anchors for icon / title / description (group-relative inches)
|
|
2633
|
+
let iconX = (w - iconSize) / 2;
|
|
2634
|
+
let iconY = padding;
|
|
2635
|
+
let titleX = padding;
|
|
2636
|
+
let titleY = hasIcon ? padding + iconSize + 0.15 : padding;
|
|
2637
|
+
let titleW = w - 2 * padding;
|
|
2638
|
+
const titleH = 0.4;
|
|
2639
|
+
if (iconPosition === 'left' && hasIcon) {
|
|
2640
|
+
iconX = padding;
|
|
2641
|
+
iconY = padding;
|
|
2642
|
+
titleX = padding + iconSize + 0.2;
|
|
2643
|
+
titleY = padding;
|
|
2644
|
+
titleW = w - titleX - padding;
|
|
2645
|
+
}
|
|
2646
|
+
const descX = titleX;
|
|
2647
|
+
const descY = titleY + titleH + 0.05;
|
|
2648
|
+
const descW = titleW;
|
|
2649
|
+
const descH = Math.max(0.2, h - descY - padding);
|
|
2650
|
+
// 3) Icon container + glyph
|
|
2651
|
+
if (hasIcon) {
|
|
2652
|
+
const iconFill = options.iconFill !== undefined ? options.iconFill : '7C3AED';
|
|
2653
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, {
|
|
2654
|
+
x: iconX, y: iconY, w: iconSize, h: iconSize,
|
|
2655
|
+
fill: { color: iconFill }, rectRadius: cornerRadius / 2, line: { type: 'none' },
|
|
2656
|
+
});
|
|
2657
|
+
const glyphColor = titleFont.color || 'E4E4ED';
|
|
2658
|
+
if (typeof options.icon === 'string') {
|
|
2659
|
+
// Emoji / text glyph centred in the container
|
|
2660
|
+
group.addText(options.icon, {
|
|
2661
|
+
x: iconX, y: iconY, w: iconSize, h: iconSize,
|
|
2662
|
+
align: 'center', valign: 'middle', fontSize: Math.round(iconSize * 36), color: glyphColor,
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
else if (options.icon && typeof options.icon === 'object' && options.icon.svgPath) {
|
|
2666
|
+
// SVG path glyph, inset within the container; emits <a:custGeom>
|
|
2667
|
+
const inset = iconSize * 0.22;
|
|
2668
|
+
group.addShape('rect', {
|
|
2669
|
+
x: iconX + inset, y: iconY + inset, w: iconSize - 2 * inset, h: iconSize - 2 * inset,
|
|
2670
|
+
svgPath: options.icon.svgPath, fill: { color: glyphColor }, line: { type: 'none' },
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
// 4) Title
|
|
2675
|
+
group.addText(options.title || '', {
|
|
2676
|
+
x: titleX, y: titleY, w: titleW, h: titleH,
|
|
2677
|
+
fontFace: titleFont.face, fontSize: titleFont.size !== undefined ? titleFont.size : 13,
|
|
2678
|
+
bold: titleFont.bold !== undefined ? titleFont.bold : true,
|
|
2679
|
+
color: titleFont.color || 'E4E4ED', align: textAlign, valign: 'top',
|
|
2680
|
+
});
|
|
2681
|
+
// 5) Description (shrink-to-fit so overflow stays inside the card)
|
|
2682
|
+
if (options.description) {
|
|
2683
|
+
group.addText(options.description, {
|
|
2684
|
+
x: descX, y: descY, w: descW, h: descH,
|
|
2685
|
+
fontFace: descFont.face, fontSize: descFont.size !== undefined ? descFont.size : 10,
|
|
2686
|
+
bold: descFont.bold !== undefined ? descFont.bold : false,
|
|
2687
|
+
color: descFont.color || '8A8A9A', align: textAlign, valign: 'top', fit: 'shrink',
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
// 6) Badge (top-right)
|
|
2691
|
+
if (options.badge && options.badge.text) {
|
|
2692
|
+
const badgeH = 0.28;
|
|
2693
|
+
const badgeW = Math.max(0.5, options.badge.text.length * 0.11 + 0.2);
|
|
2694
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, {
|
|
2695
|
+
x: w - badgeW - padding, y: padding, w: badgeW, h: badgeH,
|
|
2696
|
+
fill: { color: options.badge.fill || '10B981' }, rectRadius: badgeH / 2, line: { type: 'none' },
|
|
2697
|
+
});
|
|
2698
|
+
group.addText(options.badge.text, {
|
|
2699
|
+
x: w - badgeW - padding, y: padding, w: badgeW, h: badgeH,
|
|
2700
|
+
align: 'center', valign: 'middle', fontSize: 8, bold: true, color: options.badge.color || 'FFFFFF',
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2516
2704
|
/**
|
|
2517
2705
|
* Feature 6: Adds a shape group to a slide definition and returns a group handle.
|
|
2518
2706
|
* The group emits a `<p:grpSp>` whose `<a:xfrm>` carries the absolute position/size plus
|
|
@@ -2856,9 +3044,14 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
|
|
|
2856
3044
|
const value = from + i;
|
|
2857
3045
|
const frameOpts = Object.assign({}, opts);
|
|
2858
3046
|
delete frameOpts.counter;
|
|
2859
|
-
//
|
|
2860
|
-
|
|
2861
|
-
//
|
|
3047
|
+
// Odometer entrance: ALL frames live in ONE parallel build step (`withPrevious`),
|
|
3048
|
+
// each appearing at a CUMULATIVE delay from the container start (frame 0 -> 0,
|
|
3049
|
+
// frame 1 -> stepMs, frame 2 -> 2*stepMs, ...). `afterPrevious` would split each
|
|
3050
|
+
// frame into a separate build step in genXmlTiming, breaking the count-up; using
|
|
3051
|
+
// `withPrevious` keeps them in one group so the staggered delays drive the odometer.
|
|
3052
|
+
frameOpts.animation = { type: 'appear', trigger: 'withPrevious', delay: i * stepMs };
|
|
3053
|
+
// Every frame except the last hides itself one step after IT appears (the exit delay
|
|
3054
|
+
// is relative to each frame's own appearance), so frame N+1 masks frame N -> count-up.
|
|
2862
3055
|
if (i < frameCount - 1)
|
|
2863
3056
|
frameOpts._counterExit = stepMs;
|
|
2864
3057
|
else
|
|
@@ -3267,6 +3460,16 @@ class Slide {
|
|
|
3267
3460
|
addCalloutDefinition(this, options);
|
|
3268
3461
|
return this;
|
|
3269
3462
|
}
|
|
3463
|
+
/**
|
|
3464
|
+
* Add a structured card (rounded-rect background + optional icon/title/description/badge)
|
|
3465
|
+
* to Slide as a single shape group.
|
|
3466
|
+
* @param {CardProps} options - card options
|
|
3467
|
+
* @return {Slide} this Slide
|
|
3468
|
+
*/
|
|
3469
|
+
addCard(options) {
|
|
3470
|
+
addCardDefinition(this, options);
|
|
3471
|
+
return this;
|
|
3472
|
+
}
|
|
3270
3473
|
/**
|
|
3271
3474
|
* Add table to Slide
|
|
3272
3475
|
* @param {TableRow[]} tableRows - table rows
|
|
@@ -3988,7 +4191,7 @@ function makeXmlCharts(rel) {
|
|
|
3988
4191
|
* @example '<c:lineChart>'
|
|
3989
4192
|
* @return {string} XML chart
|
|
3990
4193
|
*/
|
|
3991
|
-
function makeChartType(chartType, data, opts, valAxisId, catAxisId,
|
|
4194
|
+
function makeChartType(chartType, data, opts, valAxisId, catAxisId, _isMultiTypeChart) {
|
|
3992
4195
|
// NOTE: "Chart Range" (as shown in "select Chart Area dialog") is calculated.
|
|
3993
4196
|
// ....: Ensure each X/Y Axis/Col has same row height (esp. applicable to XY Scatter where X can often be larger than Y's)
|
|
3994
4197
|
let colorIndex = -1; // Maintain the color index by region
|
|
@@ -4013,6 +4216,11 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4013
4216
|
if (chartType === CHART_TYPE.RADAR) {
|
|
4014
4217
|
strXml += '<c:radarStyle val="' + opts.radarStyle + '"/>';
|
|
4015
4218
|
}
|
|
4219
|
+
// `c:grouping` is a required first child of CT_LineChart (EG_LineChartShared, minOccurs=1)
|
|
4220
|
+
// and must precede `c:varyColors`. Without it, line/combo charts fail schema validation.
|
|
4221
|
+
if (chartType === CHART_TYPE.LINE) {
|
|
4222
|
+
strXml += '<c:grouping val="standard"/>';
|
|
4223
|
+
}
|
|
4016
4224
|
strXml += '<c:varyColors val="0"/>';
|
|
4017
4225
|
// 2: "Series" block for every data row
|
|
4018
4226
|
/* EX1:
|
|
@@ -4088,7 +4296,26 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4088
4296
|
}
|
|
4089
4297
|
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
4090
4298
|
strXml += ' </c:spPr>';
|
|
4091
|
-
|
|
4299
|
+
// `c:invertIfNegative` is only valid on bar/bar3D series (CT_BarSer). Emitting it for
|
|
4300
|
+
// area/line/radar series violates the OOXML schema (invalid child of CT_AreaSer/CT_LineSer/CT_RadarSer).
|
|
4301
|
+
if (chartType === CHART_TYPE.BAR || chartType === CHART_TYPE.BAR3D) {
|
|
4302
|
+
strXml += ' <c:invertIfNegative val="0"/>';
|
|
4303
|
+
}
|
|
4304
|
+
// 'c:marker' tag: `lineDataSymbol`
|
|
4305
|
+
// NOTE: CT_LineSer requires `marker` to precede `dLbls` (sequence: …spPr, marker?, dPt*, dLbls?…),
|
|
4306
|
+
// so this block must be emitted before the `c:dLbls` block below or line/combo charts fail schema validation.
|
|
4307
|
+
if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
|
|
4308
|
+
strXml += '<c:marker>';
|
|
4309
|
+
strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>';
|
|
4310
|
+
if (opts.lineDataSymbolSize)
|
|
4311
|
+
strXml += `<c:size val="${opts.lineDataSymbolSize}"/>`; // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
|
|
4312
|
+
strXml += ' <c:spPr>';
|
|
4313
|
+
strXml += ` <a:solidFill>${createColorElement(opts.chartColors[obj._dataIndex + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : obj._dataIndex])}</a:solidFill>`;
|
|
4314
|
+
strXml += ` <a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || seriesColor)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
4315
|
+
strXml += ' <a:effectLst/>';
|
|
4316
|
+
strXml += ' </c:spPr>';
|
|
4317
|
+
strXml += '</c:marker>';
|
|
4318
|
+
}
|
|
4092
4319
|
// Data Labels per series
|
|
4093
4320
|
// NOTE: [20190117] Adding these to RADAR chart causes unrecoverable corruption!
|
|
4094
4321
|
if (chartType !== CHART_TYPE.RADAR) {
|
|
@@ -4109,19 +4336,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4109
4336
|
strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? '1' : '0'}"/>`;
|
|
4110
4337
|
strXml += '</c:dLbls>';
|
|
4111
4338
|
}
|
|
4112
|
-
// 'c:marker' tag: `lineDataSymbol`
|
|
4113
|
-
if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
|
|
4114
|
-
strXml += '<c:marker>';
|
|
4115
|
-
strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>';
|
|
4116
|
-
if (opts.lineDataSymbolSize)
|
|
4117
|
-
strXml += `<c:size val="${opts.lineDataSymbolSize}"/>`; // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
|
|
4118
|
-
strXml += ' <c:spPr>';
|
|
4119
|
-
strXml += ` <a:solidFill>${createColorElement(opts.chartColors[obj._dataIndex + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : obj._dataIndex])}</a:solidFill>`;
|
|
4120
|
-
strXml += ` <a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || seriesColor)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
4121
|
-
strXml += ' <a:effectLst/>';
|
|
4122
|
-
strXml += ' </c:spPr>';
|
|
4123
|
-
strXml += '</c:marker>';
|
|
4124
|
-
}
|
|
4125
4339
|
// Allow users with a single data set to pass their own array of colors (check for this using != ours)
|
|
4126
4340
|
// Color chart bars various colors when >1 color
|
|
4127
4341
|
// NOTE: `<c:dPt>` created with various colors will change PPT legend by design so each dataPt/color is an legend item!
|
|
@@ -4270,6 +4484,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4270
4484
|
// 2: Series: (One for each Y-Axis)
|
|
4271
4485
|
colorIndex = -1;
|
|
4272
4486
|
data.filter((_obj, idx) => idx > 0).forEach((obj, idx) => {
|
|
4487
|
+
var _a;
|
|
4273
4488
|
colorIndex++;
|
|
4274
4489
|
strXml += '<c:ser>';
|
|
4275
4490
|
strXml += ` <c:idx val="${idx}"/>`;
|
|
@@ -4322,7 +4537,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4322
4537
|
// Option: scatter data point labels
|
|
4323
4538
|
if (opts.showLabel) {
|
|
4324
4539
|
const chartUuid = getUuid('-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
|
|
4325
|
-
if (obj.labels[0] && (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY')) {
|
|
4540
|
+
if (((_a = obj.labels) === null || _a === void 0 ? void 0 : _a[0]) && (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY')) {
|
|
4326
4541
|
strXml += '<c:dLbls>';
|
|
4327
4542
|
obj.labels[0].forEach((label, idx) => {
|
|
4328
4543
|
if (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY') {
|
|
@@ -4791,8 +5006,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4791
5006
|
// 4: Close "SERIES"
|
|
4792
5007
|
strXml += ' </c:ser>';
|
|
4793
5008
|
strXml += ` <c:firstSliceAng val="${opts.firstSliceAng ? Math.round(opts.firstSliceAng) : 0}"/>`;
|
|
4794
|
-
if (chartType === CHART_TYPE.DOUGHNUT)
|
|
4795
|
-
|
|
5009
|
+
if (chartType === CHART_TYPE.DOUGHNUT) {
|
|
5010
|
+
// ST_HoleSize restricts to [10,90]; clamp out-of-range input (clamp-don't-crash)
|
|
5011
|
+
const rawHoleSize = typeof opts.holeSize === 'number' ? opts.holeSize : 50;
|
|
5012
|
+
const holeSizeVal = Math.max(10, Math.min(90, Math.round(rawHoleSize)));
|
|
5013
|
+
strXml += `<c:holeSize val="${holeSizeVal}"/>`;
|
|
5014
|
+
}
|
|
4796
5015
|
strXml += '</c:' + chartType + 'Chart>';
|
|
4797
5016
|
// Done with Doughnut/Pie
|
|
4798
5017
|
break;
|
|
@@ -4885,11 +5104,15 @@ function makeCatAxis(opts, axisId, valAxisId) {
|
|
|
4885
5104
|
strXml += ' </c:txPr>';
|
|
4886
5105
|
strXml += ' <c:crossAx val="' + valAxisId + '"/>';
|
|
4887
5106
|
strXml += ` <c:${typeof opts.valAxisCrossesAt === 'number' ? 'crossesAt' : 'crosses'} val="${opts.valAxisCrossesAt || 'autoZero'}"/>`;
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
strXml += ' <c:
|
|
5107
|
+
// NOTE: `auto`/`lblAlgn`/`noMultiLvlLbl`/`tickLblSkip` are CT_CatAx-only children and are invalid on the
|
|
5108
|
+
// CT_ValAx that scatter/bubble/bubble3D charts emit for their (numeric) x-axis. Gate them to the catAx/dateAx case.
|
|
5109
|
+
if (!(opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D)) {
|
|
5110
|
+
strXml += ' <c:auto val="1"/>';
|
|
5111
|
+
strXml += ' <c:lblAlgn val="ctr"/>';
|
|
5112
|
+
strXml += ` <c:noMultiLvlLbl val="${opts.catAxisMultiLevelLabels ? 0 : 1}"/>`;
|
|
5113
|
+
if (opts.catAxisLabelFrequency)
|
|
5114
|
+
strXml += ' <c:tickLblSkip val="' + opts.catAxisLabelFrequency + '"/>';
|
|
5115
|
+
}
|
|
4893
5116
|
// Issue#149: PPT will auto-adjust these as needed after calcing the date bounds, so we only include them when specified by user
|
|
4894
5117
|
// Allow major and minor units to be set for double value axis charts
|
|
4895
5118
|
if (opts.catLabelFormatCode || opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
|
|
@@ -7177,6 +7400,36 @@ function genXmlTiming(slide) {
|
|
|
7177
7400
|
const presetMap = { appear: 1, fadeIn: 10, flyIn: 2, zoomIn: 23 };
|
|
7178
7401
|
// trigger -> build-step wrapper nodeType
|
|
7179
7402
|
const wrapNodeTypeMap = { afterPrevious: 'afterEffect', withPrevious: 'withEffect', onClick: 'clickEffect' };
|
|
7403
|
+
// --- Pre-process group/stagger sugar into trigger/delay -------------------
|
|
7404
|
+
// `group` is syntactic sugar over `trigger` (spec: docs/feature-animation-stagger.md).
|
|
7405
|
+
// Walking the animated objects in order: the first object of each consecutive `group`
|
|
7406
|
+
// run becomes the group leader (-> afterPrevious, starting a new build step); the rest
|
|
7407
|
+
// of the run join it (-> withPrevious). When `stagger` is set, the Nth item within the
|
|
7408
|
+
// run (0-indexed) gets `delay = N * stagger`. Objects without a `group` are left untouched
|
|
7409
|
+
// so the explicit `trigger` behaviour stays byte-for-byte identical (backwards-compat).
|
|
7410
|
+
// Entries are shallow-copied before mutation so the caller's `options.animation` is never
|
|
7411
|
+
// modified (keeps repeated `stream()`/`write()` calls deterministic).
|
|
7412
|
+
let prevGroup;
|
|
7413
|
+
let groupIndex = 0;
|
|
7414
|
+
animated.forEach(entry => {
|
|
7415
|
+
const a = entry.anim;
|
|
7416
|
+
if (typeof a.group === 'number') {
|
|
7417
|
+
const isNewGroup = a.group !== prevGroup;
|
|
7418
|
+
if (isNewGroup)
|
|
7419
|
+
groupIndex = 0;
|
|
7420
|
+
else
|
|
7421
|
+
groupIndex++;
|
|
7422
|
+
const resolved = Object.assign(Object.assign({}, a), { trigger: isNewGroup ? 'afterPrevious' : 'withPrevious' });
|
|
7423
|
+
if (typeof a.stagger === 'number')
|
|
7424
|
+
resolved.delay = groupIndex * a.stagger;
|
|
7425
|
+
entry.anim = resolved;
|
|
7426
|
+
prevGroup = a.group;
|
|
7427
|
+
}
|
|
7428
|
+
else {
|
|
7429
|
+
// Ungrouped object forms its own step; reset so a later same-numbered group is a fresh run
|
|
7430
|
+
prevGroup = undefined;
|
|
7431
|
+
}
|
|
7432
|
+
});
|
|
7180
7433
|
const steps = [];
|
|
7181
7434
|
animated.forEach(entry => {
|
|
7182
7435
|
var _a;
|
|
@@ -7569,7 +7822,7 @@ function makeXmlViewProps() {
|
|
|
7569
7822
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7570
7823
|
* SOFTWARE.
|
|
7571
7824
|
*/
|
|
7572
|
-
const VERSION = '4.1.
|
|
7825
|
+
const VERSION = '4.1.4';
|
|
7573
7826
|
class PptxGenJS {
|
|
7574
7827
|
set layout(value) {
|
|
7575
7828
|
const newLayout = this.LAYOUTS[value];
|
|
@@ -7922,6 +8175,7 @@ class PptxGenJS {
|
|
|
7922
8175
|
addImage: null,
|
|
7923
8176
|
addMedia: null,
|
|
7924
8177
|
addNotes: null,
|
|
8178
|
+
addCard: null,
|
|
7925
8179
|
addShape: null,
|
|
7926
8180
|
addTable: null,
|
|
7927
8181
|
addText: null,
|
|
@@ -8084,6 +8338,16 @@ class PptxGenJS {
|
|
|
8084
8338
|
}
|
|
8085
8339
|
return newSlide;
|
|
8086
8340
|
}
|
|
8341
|
+
/**
|
|
8342
|
+
* Compute evenly-spaced grid cell positions within a bounding area.
|
|
8343
|
+
* - Pure layout helper: returns one `{ x, y, w, h }` (inches) per item; emits no slide content.
|
|
8344
|
+
* @param {LayoutGridProps} props - grid options
|
|
8345
|
+
* @returns {LayoutGridResult} array of `{ x, y, w, h }` cells (inches), one per item
|
|
8346
|
+
* @example const grid = pptx.layoutGrid({ items: 6, columns: 3, area: { x: 0.5, y: 2, w: 12, h: 4 }, gap: 0.2 })
|
|
8347
|
+
*/
|
|
8348
|
+
layoutGrid(props) {
|
|
8349
|
+
return layoutGrid(props);
|
|
8350
|
+
}
|
|
8087
8351
|
/**
|
|
8088
8352
|
* Create a custom Slide Layout in any size
|
|
8089
8353
|
* @param {PresLayout} layout - layout properties
|