@shbernal/pptxgenjs 5.2.0 → 5.4.0
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/{browser-DraPrTLD.js → browser-DGH8T04O.js} +3 -3
- package/dist/{browser-DraPrTLD.js.map → browser-DGH8T04O.js.map} +1 -1
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +4 -4
- package/dist/{core-interfaces-vUc0ElZs.js → core-interfaces-C091uvh_.js} +41 -9
- package/dist/core-interfaces-C091uvh_.js.map +1 -0
- package/dist/core.d.ts +2 -2
- package/dist/core.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/inspect.js +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -4
- package/dist/{pptxgen--5RWzhb4.js → pptxgen-S8dEuBnC.js} +922 -244
- package/dist/pptxgen-S8dEuBnC.js.map +1 -0
- package/dist/{pptxgen-DzBNFPxG.d.ts → pptxgen-ZuXXUvB-.d.ts} +2 -2
- package/dist/{pptxgen-DzBNFPxG.d.ts.map → pptxgen-ZuXXUvB-.d.ts.map} +1 -1
- package/dist/standalone.d.ts +443 -34
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +1002 -248
- package/dist/standalone.js.map +1 -1
- package/dist/units-BMrBTU0-.js +106 -0
- package/dist/units-BMrBTU0-.js.map +1 -0
- package/dist/{units-y594YyBo.d.ts → units-DlsWUw1U.d.ts} +444 -35
- package/dist/units-DlsWUw1U.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/core-interfaces-vUc0ElZs.js.map +0 -1
- package/dist/pptxgen--5RWzhb4.js.map +0 -1
- package/dist/units-DmzbVUNp.js +0 -62
- package/dist/units-DmzbVUNp.js.map +0 -1
- package/dist/units-y594YyBo.d.ts.map +0 -1
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { A as
|
|
1
|
+
import { a as coordToEmu, i as STANDARD_LAYOUTS, l as inchesToEmu, o as emuToInches } from "./units-BMrBTU0-.js";
|
|
2
|
+
import { A as DEF_SLIDE_MARGIN_IN, E as DEF_PRES_LAYOUT_NAME, G as REGEX_HEX_COLOR, H as OutputType, J as SLDNUMFLDID, K as SCHEME_COLOR_NAMES, L as LAYOUT_IDX_SERIES_BASE, M as DEF_TEXT_SHADOW, N as EMU, O as DEF_SHAPE_SHADOW, P as IMG_BROKEN, R as LETTERS, S as DEF_FONT_COLOR, T as DEF_PRES_LAYOUT, U as PIECHART_COLORS, V as ONEPT, W as PLACEHOLDER_TYPES, X as SchemeColor, Z as ShapeType, _ as DEF_CELL_BORDER, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_BORDER, c as AlignH, f as CHART_TYPE, h as ChartType, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_GLOW, l as AlignV, o as AXIS_ID_VALUE_PRIMARY, p as CONNECTOR_PRESETS, q as SHAPE_TYPE, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, tt as VALID_SHAPE_PRESETS, u as BARCHART_COLORS, v as DEF_CELL_MARGIN_IN, x as DEF_CHART_GRIDLINE, z as LINEH_MODIFIER } from "./core-interfaces-C091uvh_.js";
|
|
3
3
|
import JSZip from "jszip";
|
|
4
4
|
//#region src/gen-utils.ts
|
|
5
5
|
/**
|
|
6
6
|
* PptxGenJS: Utility Methods
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {'X' | 'Y'} xyDir -
|
|
16
|
-
* @param {PresLayout} layout - presentation layout
|
|
17
|
-
* @returns {
|
|
9
|
+
* Resolve a user `Coord` (x/y/w/h) to EMU — the single user-coordinate → EMU boundary.
|
|
10
|
+
* - bare `number` → **inches** (no magnitude guessing); `"<n>%"` → percent of the slide axis;
|
|
11
|
+
* `"<n>in"`/`"<n>pt"`/`"<n>emu"` → explicit units (see {@link Coord} / {@link coordToEmu})
|
|
12
|
+
* - `null`/`undefined` → 0 (callers may omit a coordinate)
|
|
13
|
+
* - throws on a non-finite number rather than silently collapsing the object to zero size
|
|
14
|
+
* @param {Coord|null|undefined} size - user coordinate
|
|
15
|
+
* @param {'X' | 'Y'} xyDir - axis (selects slide width vs height for percentages)
|
|
16
|
+
* @param {PresLayout} layout - presentation layout (EMU dimensions)
|
|
17
|
+
* @returns {Emu} resolved EMU value
|
|
18
18
|
*/
|
|
19
19
|
function getSmartParseNumber(size, xyDir, layout) {
|
|
20
|
-
if (
|
|
20
|
+
if (size === null || size === void 0) return 0;
|
|
21
21
|
if (typeof size === "number" && !isFinite(size)) throw new Error(`Invalid ${xyDir || "coordinate"} value: expected a finite number but received ${String(size)}. This usually means a layout dimension was read from a missing property (e.g. \`layout.width\` returning \`undefined\`). Use \`slide.width\`/\`slide.height\` or \`STANDARD_LAYOUTS.<NAME>.width\`/\`.height\` (inches).`);
|
|
22
|
-
|
|
23
|
-
if (typeof size === "number" && size >= 100) return size;
|
|
24
|
-
if (typeof size === "string" && size.includes("%")) {
|
|
25
|
-
if (xyDir && xyDir === "X") return Math.round(parseFloat(size) / 100 * layout.width);
|
|
26
|
-
if (xyDir && xyDir === "Y") return Math.round(parseFloat(size) / 100 * layout.height);
|
|
27
|
-
return Math.round(parseFloat(size) / 100 * layout.width);
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
22
|
+
return coordToEmu(size, xyDir === "Y" ? layout.height : layout.width);
|
|
30
23
|
}
|
|
31
24
|
/**
|
|
32
25
|
* Basic UUID Generator Adapted
|
|
@@ -99,14 +92,16 @@ function getDuplicateObjectNames(names) {
|
|
|
99
92
|
return Array.from(dupes);
|
|
100
93
|
}
|
|
101
94
|
/**
|
|
102
|
-
* Convert inches into EMU
|
|
103
|
-
*
|
|
104
|
-
* @
|
|
95
|
+
* Convert inches into EMU.
|
|
96
|
+
* - accepts a number (inches) or a numeric/`"<n>in"` string
|
|
97
|
+
* - no magnitude guessing: values are always treated as inches (use {@link coordToEmu} for
|
|
98
|
+
* user coordinates that may carry other units)
|
|
99
|
+
* @param {number|string} inches - inches as number or string
|
|
100
|
+
* @returns {Emu} EMU value
|
|
105
101
|
*/
|
|
106
102
|
function inch2Emu(inches) {
|
|
107
|
-
if (typeof inches === "number" && inches > 100) return inches;
|
|
108
103
|
if (typeof inches === "string") inches = Number(inches.replace(/in*/gi, ""));
|
|
109
|
-
return
|
|
104
|
+
return inchesToEmu(inches);
|
|
110
105
|
}
|
|
111
106
|
/**
|
|
112
107
|
* Convert `pt` into points (using `ONEPT`)
|
|
@@ -118,6 +113,33 @@ function valToPts(pt) {
|
|
|
118
113
|
return isNaN(points) ? 0 : Math.round(points * ONEPT);
|
|
119
114
|
}
|
|
120
115
|
/**
|
|
116
|
+
* Convert a transparency percentage (0-100) into a schema-valid `<a:alpha>` value
|
|
117
|
+
* (ST_PositiveFixedPercentage, 0-100000). Out-of-range transparency yields an
|
|
118
|
+
* alpha that PowerPoint rejects as needing repair, so clamp into range and warn.
|
|
119
|
+
*/
|
|
120
|
+
function transparencyToAlpha(transparency) {
|
|
121
|
+
const pct = Math.min(100, Math.max(0, transparency));
|
|
122
|
+
if (pct !== transparency) console.warn(`Warning: transparency ${transparency} is outside the valid range 0-100; using ${pct}.`);
|
|
123
|
+
return Math.round((100 - pct) * 1e3);
|
|
124
|
+
}
|
|
125
|
+
/** Convert an opacity (0-1) into a schema-valid `<a:alpha>` value (0-100000); clamps + warns on out-of-range input. */
|
|
126
|
+
function opacityToAlpha(opacity) {
|
|
127
|
+
const o = Math.min(1, Math.max(0, opacity));
|
|
128
|
+
if (o !== opacity) console.warn(`Warning: opacity ${opacity} is outside the valid range 0-1; using ${o}.`);
|
|
129
|
+
return Math.round(o * 1e5);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convert a line width (points) to EMU clamped into ST_LineWidth (0..20116800 EMU,
|
|
133
|
+
* i.e. 0-1584pt). Out-of-range widths make PowerPoint report the package as needing
|
|
134
|
+
* repair, so clamp into range and warn.
|
|
135
|
+
*/
|
|
136
|
+
function lineWidthToEmu(widthPts) {
|
|
137
|
+
const raw = valToPts(widthPts);
|
|
138
|
+
const clamped = Math.min(20116800, Math.max(0, raw));
|
|
139
|
+
if (clamped !== raw) console.warn(`Warning: line width ${widthPts} is outside the valid range 0-1584pt; using ${clamped / ONEPT}.`);
|
|
140
|
+
return clamped;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
121
143
|
* Convert degrees (0..360) to PowerPoint `rot` value
|
|
122
144
|
* @param {number} d degrees
|
|
123
145
|
* @returns {number} calculated `rot` value
|
|
@@ -165,8 +187,10 @@ function createColorElement(colorStr, innerElements) {
|
|
|
165
187
|
}
|
|
166
188
|
let colorVal = (colorStr || "").replace("#", "");
|
|
167
189
|
if (/^[0-9a-fA-F]{8}$/.test(colorVal)) {
|
|
168
|
-
|
|
169
|
-
|
|
190
|
+
if (!innerElements?.includes("<a:alpha")) {
|
|
191
|
+
const alphaHex = colorVal.slice(6, 8);
|
|
192
|
+
innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
|
|
193
|
+
}
|
|
170
194
|
colorVal = colorVal.slice(0, 6);
|
|
171
195
|
}
|
|
172
196
|
if (!REGEX_HEX_COLOR.test(colorVal) && colorVal !== "bg1" && colorVal !== "bg2" && colorVal !== "tx1" && colorVal !== "tx2" && colorVal !== "accent1" && colorVal !== "accent2" && colorVal !== "accent3" && colorVal !== "accent4" && colorVal !== "accent5" && colorVal !== "accent6") {
|
|
@@ -192,12 +216,38 @@ function createGlowElement(options, defaults) {
|
|
|
192
216
|
};
|
|
193
217
|
const size = Math.round(opts.size * ONEPT);
|
|
194
218
|
const color = opts.color || "000000";
|
|
195
|
-
const opacity =
|
|
219
|
+
const opacity = opacityToAlpha(opts.opacity ?? 0);
|
|
196
220
|
strXml += `<a:glow rad="${size}">`;
|
|
197
221
|
strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
|
|
198
222
|
strXml += "</a:glow>";
|
|
199
223
|
return strXml;
|
|
200
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Creates an `a:outerShdw`/`a:innerShdw` element for a text run or shape.
|
|
227
|
+
* Returns the shadow element only (no wrapping `a:effectLst`) so callers can
|
|
228
|
+
* combine it with other effects (e.g. glow) inside a single `a:effectLst`.
|
|
229
|
+
* @param {ShadowProps} options shadow properties
|
|
230
|
+
* @param {ShadowProps} defaults defaults for unspecified properties in `options`
|
|
231
|
+
* @see http://officeopenxml.com/drwSp-effects.php
|
|
232
|
+
* @returns {string} XML string, or '' when type is 'none'
|
|
233
|
+
*/
|
|
234
|
+
function createShadowElement$1(options, defaults) {
|
|
235
|
+
const opts = {
|
|
236
|
+
...defaults,
|
|
237
|
+
...options
|
|
238
|
+
};
|
|
239
|
+
if (opts.type === "none") return "";
|
|
240
|
+
const type = opts.type || "outer";
|
|
241
|
+
const blur = valToPts(opts.blur ?? 0);
|
|
242
|
+
const offset = valToPts(opts.offset ?? 0);
|
|
243
|
+
const angle = Math.round((opts.angle ?? 0) * 6e4);
|
|
244
|
+
const opacity = Math.round((opts.opacity ?? .75) * 1e5);
|
|
245
|
+
const color = opts.color || "000000";
|
|
246
|
+
let strXml = `<a:${type}Shdw ${type === "outer" ? "sx=\"100000\" sy=\"100000\" kx=\"0\" ky=\"0\" algn=\"bl\" rotWithShape=\"0\" " : ""}blurRad="${blur}" dist="${offset}" dir="${angle}">`;
|
|
247
|
+
strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
|
|
248
|
+
strXml += `</a:${type}Shdw>`;
|
|
249
|
+
return strXml;
|
|
250
|
+
}
|
|
201
251
|
function boolToXml(value) {
|
|
202
252
|
return value ? "1" : "0";
|
|
203
253
|
}
|
|
@@ -208,8 +258,8 @@ function normalizeGradientAngle(angle) {
|
|
|
208
258
|
}
|
|
209
259
|
function gradientStopColorAdjustments(stop) {
|
|
210
260
|
let internalElements = "";
|
|
211
|
-
if (stop.alpha) internalElements += `<a:alpha val="${
|
|
212
|
-
if (stop.transparency) internalElements += `<a:alpha val="${
|
|
261
|
+
if (stop.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(stop.alpha)}"/>`;
|
|
262
|
+
if (stop.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(stop.transparency)}"/>`;
|
|
213
263
|
return internalElements;
|
|
214
264
|
}
|
|
215
265
|
function normalizeGradientStops(stops) {
|
|
@@ -259,6 +309,17 @@ function genXmlPatternFill(pattern) {
|
|
|
259
309
|
* @param {Color | ShapeFillProps | ShapeLineProps} props fill props
|
|
260
310
|
* @returns XML string
|
|
261
311
|
*/
|
|
312
|
+
/**
|
|
313
|
+
* Map a friendly `LineCap` value to the OOXML `cap` attribute value (`flat`/`sq`/`rnd`).
|
|
314
|
+
* @param {LineCap} [lineCap] - line cap style (defaults to `flat`)
|
|
315
|
+
* @returns {string} value for the `cap` attribute on `<a:ln>`
|
|
316
|
+
*/
|
|
317
|
+
function createLineCap(lineCap) {
|
|
318
|
+
if (!lineCap || lineCap === "flat") return "flat";
|
|
319
|
+
else if (lineCap === "square") return "sq";
|
|
320
|
+
else if (lineCap === "round") return "rnd";
|
|
321
|
+
else throw new Error(`Invalid line cap: ${String(lineCap)}`);
|
|
322
|
+
}
|
|
262
323
|
function genXmlColorSelection(props) {
|
|
263
324
|
let fillType = "solid";
|
|
264
325
|
let colorVal = "";
|
|
@@ -269,8 +330,8 @@ function genXmlColorSelection(props) {
|
|
|
269
330
|
else {
|
|
270
331
|
if (props.type) fillType = props.type;
|
|
271
332
|
if (props.color) colorVal = props.color;
|
|
272
|
-
if (props.alpha) internalElements += `<a:alpha val="${
|
|
273
|
-
if (props.transparency) internalElements += `<a:alpha val="${
|
|
333
|
+
if (props.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(props.alpha)}"/>`;
|
|
334
|
+
if (props.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(props.transparency)}"/>`;
|
|
274
335
|
}
|
|
275
336
|
switch (fillType) {
|
|
276
337
|
case "solid":
|
|
@@ -368,15 +429,16 @@ function decodeBase64ToBytes(b64) {
|
|
|
368
429
|
}
|
|
369
430
|
}
|
|
370
431
|
/**
|
|
371
|
-
* Read the intrinsic
|
|
432
|
+
* Read the intrinsic dimensions of an image from its header bytes.
|
|
372
433
|
* - synchronous: parses only file-format headers, never decodes pixels
|
|
373
|
-
* -
|
|
374
|
-
* - vector
|
|
434
|
+
* - raster: PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X) — natural pixels
|
|
435
|
+
* - vector: SVG — intrinsic size from the root `<svg>` width/height or viewBox
|
|
436
|
+
* - unrecognized formats return `null` (no measurable intrinsic size)
|
|
375
437
|
*
|
|
376
438
|
* Used by image `sizing: 'cover' | 'contain'` to compute an aspect-correct
|
|
377
439
|
* `<a:srcRect>` crop from the *natural* image ratio rather than the displayed box.
|
|
378
440
|
* @param {string} dataB64 - base64 image payload or `data:` URI
|
|
379
|
-
* @returns {{ w: number, h: number } | null} natural
|
|
441
|
+
* @returns {{ w: number, h: number } | null} natural size, or `null` when unmeasurable
|
|
380
442
|
*/
|
|
381
443
|
function getImageSizeFromBase64(dataB64) {
|
|
382
444
|
const b = decodeBase64ToBytes(dataB64);
|
|
@@ -462,8 +524,46 @@ function getImageSizeFromBase64(dataB64) {
|
|
|
462
524
|
}
|
|
463
525
|
return null;
|
|
464
526
|
}
|
|
527
|
+
const text = utf8Decode(b);
|
|
528
|
+
if (/<svg[\s>]/i.test(text)) return getSvgSizeFromMarkup(text);
|
|
465
529
|
return null;
|
|
466
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Read the intrinsic size of an SVG document from its root `<svg>` element.
|
|
533
|
+
* Follows the SVG sizing model: an explicit absolute `width`/`height` pair wins;
|
|
534
|
+
* otherwise the `viewBox` width/height defines the size (and thus aspect ratio).
|
|
535
|
+
* Percentage or missing `width`/`height` fall through to `viewBox`.
|
|
536
|
+
* @param {string} svg - SVG markup
|
|
537
|
+
* @returns {{ w: number, h: number } | null} intrinsic size, or `null` when undeterminable
|
|
538
|
+
*/
|
|
539
|
+
function getSvgSizeFromMarkup(svg) {
|
|
540
|
+
const openTag = /<svg\b[^>]*>/i.exec(svg)?.[0];
|
|
541
|
+
if (!openTag) return null;
|
|
542
|
+
const attr = (name) => new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(openTag)?.[1] ?? null;
|
|
543
|
+
const absLength = (val) => {
|
|
544
|
+
if (val == null || /%\s*$/.test(val)) return NaN;
|
|
545
|
+
const m = /^\s*\+?(\d*\.?\d+)/.exec(val);
|
|
546
|
+
return m ? parseFloat(m[1]) : NaN;
|
|
547
|
+
};
|
|
548
|
+
let w = absLength(attr("width"));
|
|
549
|
+
let h = absLength(attr("height"));
|
|
550
|
+
if (!(w > 0 && h > 0)) {
|
|
551
|
+
const vb = attr("viewBox");
|
|
552
|
+
const p = vb ? vb.trim().split(/[\s,]+/).map(Number) : [];
|
|
553
|
+
if (p.length === 4 && p[2] > 0 && p[3] > 0) {
|
|
554
|
+
w = p[2];
|
|
555
|
+
h = p[3];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return w > 0 && h > 0 ? {
|
|
559
|
+
w,
|
|
560
|
+
h
|
|
561
|
+
} : null;
|
|
562
|
+
}
|
|
563
|
+
/** Decode UTF-8 bytes to a string, isomorphic across Node and browsers. */
|
|
564
|
+
function utf8Decode(bytes) {
|
|
565
|
+
return new TextDecoder().decode(bytes);
|
|
566
|
+
}
|
|
467
567
|
//#endregion
|
|
468
568
|
//#region src/gen-tables.ts
|
|
469
569
|
/**
|
|
@@ -614,6 +714,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
|
|
|
614
714
|
let emuSlideTabH = EMU * 1;
|
|
615
715
|
let emuTabCurrH = 0;
|
|
616
716
|
let numCols = 0;
|
|
717
|
+
let warnedNoTabH = false;
|
|
617
718
|
const tableRowSlides = [];
|
|
618
719
|
const tablePropX = getSmartParseNumber(tableProps.x, "X", presLayout);
|
|
619
720
|
const tablePropY = getSmartParseNumber(tableProps.y, "Y", presLayout);
|
|
@@ -633,6 +734,15 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
|
|
|
633
734
|
if (emuSlideTabH < tablePropH) emuSlideTabH = tablePropH;
|
|
634
735
|
}
|
|
635
736
|
}
|
|
737
|
+
if (emuSlideTabH <= 0) {
|
|
738
|
+
const emuStartY = tableRowSlides.length === 0 ? tablePropY || inch2Emu(arrInchMargins[0]) : inch2Emu(tableProps.autoPageSlideStartY || tableProps.newSlideStartY || arrInchMargins[0]);
|
|
739
|
+
const fallbackH = presLayout.height - emuStartY - inch2Emu(arrInchMargins[2]);
|
|
740
|
+
if (!warnedNoTabH) {
|
|
741
|
+
console.warn("addTable/autoPage: the table height (`h`) leaves no room to paginate; ignoring it and using the slide height. Increase `h` or decrease `y`.");
|
|
742
|
+
warnedNoTabH = true;
|
|
743
|
+
}
|
|
744
|
+
emuSlideTabH = fallbackH > 0 ? fallbackH : presLayout.height;
|
|
745
|
+
}
|
|
636
746
|
}
|
|
637
747
|
if (tableProps.verbose) {
|
|
638
748
|
console.log("[[VERBOSE MODE]]");
|
|
@@ -805,7 +915,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
|
|
|
805
915
|
console.log("|-----------------------------------------------------------------------|\n\n");
|
|
806
916
|
}
|
|
807
917
|
if (currTableRow.length > 0 && currTableRow.map((cell) => Array.isArray(cell.text) ? cell.text.length : 0).reduce((p, n) => p + n) > 0) newTableRowSlide.rows.push(currTableRow);
|
|
808
|
-
tableRowSlides.push(newTableRowSlide);
|
|
918
|
+
if (newTableRowSlide.rows.length > 0) tableRowSlides.push(newTableRowSlide);
|
|
809
919
|
newTableRowSlide = { rows: [] };
|
|
810
920
|
currTableRow = [];
|
|
811
921
|
row.forEach((cell) => currTableRow.push({
|
|
@@ -854,7 +964,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
|
|
|
854
964
|
for (let c = 0; c < numCols; c++) if (colSpanDepths[c] > 0) colSpanDepths[c]--;
|
|
855
965
|
if (tableProps.verbose) console.log(`- SLIDE [${tableRowSlides.length}]: ROW [${iRow}]: ...COMPLETE ...... emuTabCurrH = ${(emuTabCurrH / EMU).toFixed(2)} ( emuSlideTabH = ${(emuSlideTabH / EMU).toFixed(2)} )`);
|
|
856
966
|
});
|
|
857
|
-
tableRowSlides.push(newTableRowSlide);
|
|
967
|
+
if (newTableRowSlide.rows.length > 0 || tableRowSlides.length === 0) tableRowSlides.push(newTableRowSlide);
|
|
858
968
|
if (tableProps.verbose) {
|
|
859
969
|
console.log("\n|================================================|");
|
|
860
970
|
console.log(`| FINAL: tableRowSlides.length = ${tableRowSlides.length}`);
|
|
@@ -864,6 +974,47 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
|
|
|
864
974
|
return tableRowSlides;
|
|
865
975
|
}
|
|
866
976
|
/**
|
|
977
|
+
* Convert a computed CSS border (width string + color string) from `getComputedStyle` into a
|
|
978
|
+
* pptx `BorderProps`.
|
|
979
|
+
*
|
|
980
|
+
* Preserves *fractional* widths: a hairline CSS border such as `0.5px` must not be rounded to
|
|
981
|
+
* `0pt` and silently vanish — the table serializer (`valToPts`) emits fractional points just
|
|
982
|
+
* fine, so there is no reason to integer-round here (upstream gitbrent/PptxGenJS#1235). A
|
|
983
|
+
* computed width of `0` (or a non-finite value) yields `{ type: 'none' }` so we never emit a
|
|
984
|
+
* zero-width line.
|
|
985
|
+
* @param {string} widthStr - computed `border-<side>-width`, e.g. `"0.5px"`
|
|
986
|
+
* @param {string} colorStr - computed `border-<side>-color`, e.g. `"rgb(102, 102, 102)"`
|
|
987
|
+
* @returns {BorderProps} border props for the cell side
|
|
988
|
+
*/
|
|
989
|
+
function htmlBorderToProps(widthStr, colorStr) {
|
|
990
|
+
const pt = Number(String(widthStr).replace("px", ""));
|
|
991
|
+
if (!isFinite(pt) || pt <= 0) return { type: "none" };
|
|
992
|
+
const arrRGB = String(colorStr).replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
|
|
993
|
+
return {
|
|
994
|
+
pt,
|
|
995
|
+
color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Resolve a single HTML-table column width for `tableToSlides`.
|
|
1000
|
+
*
|
|
1001
|
+
* Precedence: an explicit `data-pptx-width` wins outright; otherwise the proportional width
|
|
1002
|
+
* derived from the live table is used, raised to `data-pptx-min-width` when that floor is larger.
|
|
1003
|
+
*
|
|
1004
|
+
* Hidden tables report `offsetWidth` 0 for every cell, which makes `calcWidth` non-finite (a 0/0
|
|
1005
|
+
* proportional calc). Fall back to `0` there so an explicit `data-pptx-width` / `data-pptx-min-width`
|
|
1006
|
+
* override still drives the column instead of emitting a `NaN` width (upstream gitbrent/PptxGenJS#1157).
|
|
1007
|
+
* @param {number} calcWidth - proportional width derived from `offsetWidth` (may be `NaN` for hidden tables)
|
|
1008
|
+
* @param {number} setWidth - `data-pptx-width` override (`0`/`NaN` when absent or invalid)
|
|
1009
|
+
* @param {number} minWidth - `data-pptx-min-width` floor (`0`/`NaN` when absent or invalid)
|
|
1010
|
+
* @returns {number} resolved column width
|
|
1011
|
+
*/
|
|
1012
|
+
function resolveHtmlColWidth(calcWidth, setWidth, minWidth) {
|
|
1013
|
+
const safeCalc = isFinite(calcWidth) ? calcWidth : 0;
|
|
1014
|
+
if (isFinite(setWidth) && setWidth > 0) return setWidth;
|
|
1015
|
+
return isFinite(minWidth) && minWidth > safeCalc ? minWidth : safeCalc;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
867
1018
|
* Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
|
|
868
1019
|
* @param {TableToSlidesHost} pptx - pptxgenjs instance
|
|
869
1020
|
* @param {string} tabEleId - HTMLElementID of the table
|
|
@@ -927,12 +1078,10 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
|
|
|
927
1078
|
});
|
|
928
1079
|
arrTabColW.forEach((colW, idxW) => {
|
|
929
1080
|
const intCalcWidth = Number((Number(emuSlideTabW) * (colW / intTabW * 100) / 100 / EMU).toFixed(2));
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (colSelectorSet) intMinWidth = Number(colSelectorSet.getAttribute("data-pptx-width"));
|
|
935
|
-
arrColW.push(intMinWidth > intCalcWidth ? intMinWidth : intCalcWidth);
|
|
1081
|
+
const headCell = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
|
|
1082
|
+
const intSetWidth = headCell ? Number(headCell.getAttribute("data-pptx-width")) : 0;
|
|
1083
|
+
const intMinWidth = headCell ? Number(headCell.getAttribute("data-pptx-min-width")) : 0;
|
|
1084
|
+
arrColW.push(resolveHtmlColWidth(intCalcWidth, intSetWidth, intMinWidth));
|
|
936
1085
|
});
|
|
937
1086
|
if (opts.verbose) console.log(`| arrColW ......................................... = [${arrColW.join(", ")}]`);
|
|
938
1087
|
[
|
|
@@ -1011,12 +1160,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
|
|
|
1011
1160
|
"bottom",
|
|
1012
1161
|
"left"
|
|
1013
1162
|
].forEach((val, idxb) => {
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
cellBorder[idxb] = {
|
|
1017
|
-
pt: intBorderW,
|
|
1018
|
-
color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
|
|
1019
|
-
};
|
|
1163
|
+
const style = window.getComputedStyle(cell);
|
|
1164
|
+
cellBorder[idxb] = htmlBorderToProps(style.getPropertyValue("border-" + val + "-width"), style.getPropertyValue("border-" + val + "-color"));
|
|
1020
1165
|
});
|
|
1021
1166
|
cellOpts.border = cellBorder;
|
|
1022
1167
|
}
|
|
@@ -1086,6 +1231,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
|
|
|
1086
1231
|
*/
|
|
1087
1232
|
/** counter for included charts (used for index in their filenames) */
|
|
1088
1233
|
let _chartCounter = 0;
|
|
1234
|
+
/** DPI PowerPoint assumes when sizing an inserted raster image (natural pixels / 96 == inches) */
|
|
1235
|
+
const IMAGE_NATURAL_DPI = 96;
|
|
1089
1236
|
function normalizeBorderTuple(border) {
|
|
1090
1237
|
return Array.isArray(border) ? border : [
|
|
1091
1238
|
border,
|
|
@@ -1108,7 +1255,7 @@ function createSlideMaster(props, target) {
|
|
|
1108
1255
|
else if ("line" in object) addShapeDefinition(tgt, "line", object.line);
|
|
1109
1256
|
else if ("rect" in object) addShapeDefinition(tgt, "rect", object.rect);
|
|
1110
1257
|
else if ("roundRect" in object) addShapeDefinition(tgt, "roundRect", object.roundRect);
|
|
1111
|
-
else if ("text" in object) addTextDefinition(tgt, [{ text: object.text.text }], object.text.options || {}, false);
|
|
1258
|
+
else if ("text" in object) addTextDefinition(tgt, Array.isArray(object.text.text) ? object.text.text : [{ text: object.text.text }], object.text.options || {}, false);
|
|
1112
1259
|
else if ("placeholder" in object) {
|
|
1113
1260
|
const placeholder = object.placeholder;
|
|
1114
1261
|
const { name, type, ...rawPlaceholderOptions } = placeholder.options;
|
|
@@ -1122,6 +1269,26 @@ function createSlideMaster(props, target) {
|
|
|
1122
1269
|
if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
|
|
1123
1270
|
}
|
|
1124
1271
|
/**
|
|
1272
|
+
* Round and clamp an integer chart percentage/angle option into a schema-valid range.
|
|
1273
|
+
*
|
|
1274
|
+
* Several chart attributes are bounded integer types whose out-of-range values make
|
|
1275
|
+
* PowerPoint report the package as needing repair: `<c:overlap>` (ST_Overlap, -100..100),
|
|
1276
|
+
* `<c:gapWidth>`/`<c:gapDepth>` (ST_GapAmount, 0..500), `<c:holeSize>` (ST_HoleSize, 10..90)
|
|
1277
|
+
* and `<c:firstSliceAng>` (ST_FirstSliceAng, 0..360). Missing/non-numeric input returns
|
|
1278
|
+
* `undefined` so the caller can apply its own default; an out-of-range value is clamped
|
|
1279
|
+
* and a warning is emitted (per the library's warn-rather-than-degrade policy).
|
|
1280
|
+
* @param value - caller-supplied option value
|
|
1281
|
+
* @param min - inclusive lower bound
|
|
1282
|
+
* @param max - inclusive upper bound
|
|
1283
|
+
* @param name - option name, for the warning message
|
|
1284
|
+
*/
|
|
1285
|
+
function clampChartPct(value, min, max, name) {
|
|
1286
|
+
if (typeof value !== "number" || isNaN(value)) return void 0;
|
|
1287
|
+
const clamped = Math.min(max, Math.max(min, Math.round(value)));
|
|
1288
|
+
if (clamped !== value) console.warn(`Warning: ${name} ${value} is outside the valid range ${min}-${max}; using ${clamped}.`);
|
|
1289
|
+
return clamped;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1125
1292
|
* Generate the chart based on input data.
|
|
1126
1293
|
* OOXML Chart Spec: ISO/IEC 29500-1:2016(E)
|
|
1127
1294
|
*
|
|
@@ -1293,7 +1460,13 @@ function addChartDefinition(target, type, data, opt) {
|
|
|
1293
1460
|
"marker",
|
|
1294
1461
|
"filled"
|
|
1295
1462
|
].includes(options.radarStyle || "")) options.radarStyle = "standard";
|
|
1296
|
-
|
|
1463
|
+
{
|
|
1464
|
+
const rawSymbolSize = options.lineDataSymbolSize;
|
|
1465
|
+
const hasSymbolSize = rawSymbolSize != null && !isNaN(rawSymbolSize);
|
|
1466
|
+
const symbolSize = Math.min(72, Math.max(2, Math.round(hasSymbolSize ? rawSymbolSize : 6)));
|
|
1467
|
+
if (hasSymbolSize && symbolSize !== rawSymbolSize) console.warn(`Warning: lineDataSymbolSize ${rawSymbolSize} is outside the valid marker size range (integer 2-72); using ${symbolSize}.`);
|
|
1468
|
+
options.lineDataSymbolSize = symbolSize;
|
|
1469
|
+
}
|
|
1297
1470
|
options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(.75);
|
|
1298
1471
|
const chartLayout = options.layout;
|
|
1299
1472
|
if (chartLayout) [
|
|
@@ -1343,8 +1516,11 @@ function addChartDefinition(target, type, data, opt) {
|
|
|
1343
1516
|
options.v3DRotY = typeof options.v3DRotY === "number" && !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30;
|
|
1344
1517
|
options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true;
|
|
1345
1518
|
options.v3DPerspective = typeof options.v3DPerspective === "number" && !isNaN(options.v3DPerspective) && options.v3DPerspective >= 0 && options.v3DPerspective <= 240 ? options.v3DPerspective : 30;
|
|
1346
|
-
options.barGapWidthPct =
|
|
1347
|
-
options.barGapDepthPct =
|
|
1519
|
+
options.barGapWidthPct = clampChartPct(options.barGapWidthPct, 0, 500, "barGapWidthPct") ?? 150;
|
|
1520
|
+
options.barGapDepthPct = clampChartPct(options.barGapDepthPct, 0, 500, "barGapDepthPct") ?? 150;
|
|
1521
|
+
options.barOverlapPct = clampChartPct(options.barOverlapPct, -100, 100, "barOverlapPct");
|
|
1522
|
+
options.holeSize = clampChartPct(options.holeSize, 10, 90, "holeSize");
|
|
1523
|
+
options.firstSliceAng = clampChartPct(options.firstSliceAng, 0, 360, "firstSliceAng");
|
|
1348
1524
|
options.chartColors = Array.isArray(options.chartColors) ? options.chartColors : options._type === "pie" || options._type === "doughnut" ? PIECHART_COLORS : BARCHART_COLORS;
|
|
1349
1525
|
options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : void 0;
|
|
1350
1526
|
options.border = options.border && typeof options.border === "object" ? options.border : void 0;
|
|
@@ -1433,16 +1609,29 @@ function addImageDefinition(target, opt) {
|
|
|
1433
1609
|
else if (strImageData?.toLowerCase().includes("image/svg+xml")) strImgExtn = "svg";
|
|
1434
1610
|
newObject._type = "image";
|
|
1435
1611
|
newObject.image = strImagePath || "preencoded.png";
|
|
1612
|
+
let defWidth = intWidth;
|
|
1613
|
+
let defHeight = intHeight;
|
|
1614
|
+
if ((!intWidth || !intHeight) && strImageData && strImgExtn !== "svg") {
|
|
1615
|
+
const natural = getImageSizeFromBase64(strImageData);
|
|
1616
|
+
if (natural) {
|
|
1617
|
+
if (!intWidth && !intHeight) {
|
|
1618
|
+
defWidth = natural.w / IMAGE_NATURAL_DPI;
|
|
1619
|
+
defHeight = natural.h / IMAGE_NATURAL_DPI;
|
|
1620
|
+
} else if (typeof intWidth === "number" && intWidth && !intHeight) defHeight = intWidth * (natural.h / natural.w);
|
|
1621
|
+
else if (typeof intHeight === "number" && intHeight && !intWidth) defWidth = intHeight * (natural.w / natural.h);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1436
1624
|
const objectOptions = {
|
|
1437
1625
|
x: intPosX || 0,
|
|
1438
1626
|
y: intPosY || 0,
|
|
1439
|
-
w:
|
|
1440
|
-
h:
|
|
1627
|
+
w: defWidth || 1,
|
|
1628
|
+
h: defHeight || 1,
|
|
1441
1629
|
altText: opt.altText || "",
|
|
1442
1630
|
rounding: typeof opt.rounding === "boolean" ? opt.rounding : false,
|
|
1443
1631
|
shape: opt.shape,
|
|
1444
1632
|
points: opt.points,
|
|
1445
1633
|
rectRadius: opt.rectRadius,
|
|
1634
|
+
shapeAdjust: opt.shapeAdjust,
|
|
1446
1635
|
sizing,
|
|
1447
1636
|
placeholder: opt.placeholder,
|
|
1448
1637
|
rotate: opt.rotate || 0,
|
|
@@ -1451,6 +1640,7 @@ function addImageDefinition(target, opt) {
|
|
|
1451
1640
|
transparency: opt.transparency || 0,
|
|
1452
1641
|
duotone: opt.duotone,
|
|
1453
1642
|
objectName,
|
|
1643
|
+
objectLock: opt.objectLock,
|
|
1454
1644
|
shadow: correctShadowOptions(opt.shadow)
|
|
1455
1645
|
};
|
|
1456
1646
|
newObject.options = objectOptions;
|
|
@@ -1480,7 +1670,10 @@ function addImageDefinition(target, opt) {
|
|
|
1480
1670
|
});
|
|
1481
1671
|
newObject.imageRid = imageRelId + 1;
|
|
1482
1672
|
} else {
|
|
1483
|
-
const dupeItem = target._relsMedia.find((item) =>
|
|
1673
|
+
const dupeItem = target._relsMedia.find((item) => {
|
|
1674
|
+
if (item.isDuplicate || !item.Target || item.type !== "image/" + strImgExtn) return false;
|
|
1675
|
+
return strImagePath ? item.path === strImagePath : !!strImageData && item.data === strImageData;
|
|
1676
|
+
});
|
|
1484
1677
|
target._relsMedia.push({
|
|
1485
1678
|
path: strImagePath || "preencoded." + strImgExtn,
|
|
1486
1679
|
type: "image/" + strImgExtn,
|
|
@@ -1538,6 +1731,7 @@ function addMediaDefinition(target, opt) {
|
|
|
1538
1731
|
slideData.options.h = intSizeY;
|
|
1539
1732
|
slideData.options.objectName = objectName;
|
|
1540
1733
|
if (opt.altText) slideData.options.altText = opt.altText;
|
|
1734
|
+
if (opt.objectLock) slideData.options.objectLock = opt.objectLock;
|
|
1541
1735
|
/**
|
|
1542
1736
|
* NOTE:
|
|
1543
1737
|
* - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1!
|
|
@@ -1567,7 +1761,10 @@ function addMediaDefinition(target, opt) {
|
|
|
1567
1761
|
Target: `../media/image-${target._slideNum}-${target._relsMedia.length + 1}.png`
|
|
1568
1762
|
});
|
|
1569
1763
|
} else {
|
|
1570
|
-
const dupeItem = target._relsMedia.find((item) =>
|
|
1764
|
+
const dupeItem = target._relsMedia.find((item) => {
|
|
1765
|
+
if (item.isDuplicate || !item.Target || item.type !== strType + "/" + strExtn) return false;
|
|
1766
|
+
return strPath ? item.path === strPath : !!strData && item.data === strData;
|
|
1767
|
+
});
|
|
1571
1768
|
const relId1 = getNewRelId(target);
|
|
1572
1769
|
target._relsMedia.push({
|
|
1573
1770
|
path: strPath || "preencoded" + strExtn,
|
|
@@ -1632,12 +1829,14 @@ function addShapeDefinition(target, shapeName, opts) {
|
|
|
1632
1829
|
const options = typeof opts === "object" ? opts : {};
|
|
1633
1830
|
options.line = options.line || { type: "none" };
|
|
1634
1831
|
options.shadow = correctShadowOptions(options.shadow);
|
|
1832
|
+
const resolvedShapeName = typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName;
|
|
1635
1833
|
const newObject = {
|
|
1636
1834
|
_type: "text",
|
|
1637
|
-
shape:
|
|
1835
|
+
shape: resolvedShapeName || "rect",
|
|
1638
1836
|
options
|
|
1639
1837
|
};
|
|
1640
1838
|
if (!shapeName) throw new Error("Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`");
|
|
1839
|
+
if (!VALID_SHAPE_PRESETS.has(resolvedShapeName)) throw new Error(`Invalid shape "${String(shapeName)}"! Use a value from \`pptxgen.shapes.*\` (e.g. \`pptxgen.shapes.RECTANGLE\`). PowerPoint can't render unknown preset geometries and will drop the shape during repair.`);
|
|
1641
1840
|
const newLineOpts = {
|
|
1642
1841
|
type: options.line.type || "solid",
|
|
1643
1842
|
color: options.line.color || "333333",
|
|
@@ -1666,6 +1865,51 @@ function addShapeDefinition(target, shapeName, opts) {
|
|
|
1666
1865
|
target._slideObjects.push(newObject);
|
|
1667
1866
|
}
|
|
1668
1867
|
/**
|
|
1868
|
+
* Adds a connector object to a slide definition.
|
|
1869
|
+
* A connector is a line between two points emitted as a PowerPoint connector (`<p:cxnSp>`).
|
|
1870
|
+
* Endpoints are converted to a bounding box (`x/y/w/h`) plus `flipH`/`flipV` so the box can be
|
|
1871
|
+
* oriented from any corner; the connector preset geometry is derived from `type`.
|
|
1872
|
+
* @param {PresSlideInternal} target - slide the connector is added to
|
|
1873
|
+
* @param {ConnectorProps} opts - connector options (endpoints + line styling)
|
|
1874
|
+
*/
|
|
1875
|
+
function addConnectorDefinition(target, opts) {
|
|
1876
|
+
if (!opts || [
|
|
1877
|
+
opts.x1,
|
|
1878
|
+
opts.y1,
|
|
1879
|
+
opts.x2,
|
|
1880
|
+
opts.y2
|
|
1881
|
+
].some((v) => typeof v === "undefined")) throw new Error("addConnector requires { x1, y1, x2, y2 }. Example: `slide.addConnector({ x1:1, y1:1, x2:4, y2:3 })`");
|
|
1882
|
+
const preset = CONNECTOR_PRESETS[opts.type || "straight"];
|
|
1883
|
+
if (!preset) throw new Error(`Invalid connector type "${String(opts.type)}". Use 'straight', 'elbow', or 'curved'.`);
|
|
1884
|
+
const x1 = getSmartParseNumber(opts.x1, "X", target._presLayout) / EMU;
|
|
1885
|
+
const y1 = getSmartParseNumber(opts.y1, "Y", target._presLayout) / EMU;
|
|
1886
|
+
const x2 = getSmartParseNumber(opts.x2, "X", target._presLayout) / EMU;
|
|
1887
|
+
const y2 = getSmartParseNumber(opts.y2, "Y", target._presLayout) / EMU;
|
|
1888
|
+
const newObject = {
|
|
1889
|
+
_type: "connector",
|
|
1890
|
+
shape: preset,
|
|
1891
|
+
options: {
|
|
1892
|
+
x: Math.min(x1, x2),
|
|
1893
|
+
y: Math.min(y1, y2),
|
|
1894
|
+
w: Math.abs(x2 - x1),
|
|
1895
|
+
h: Math.abs(y2 - y1),
|
|
1896
|
+
flipH: x2 < x1,
|
|
1897
|
+
flipV: y2 < y1,
|
|
1898
|
+
line: {
|
|
1899
|
+
type: "solid",
|
|
1900
|
+
color: opts.color || "333333",
|
|
1901
|
+
width: typeof opts.width === "number" ? opts.width : 1,
|
|
1902
|
+
dashType: opts.dashType || "solid",
|
|
1903
|
+
beginArrowType: opts.beginArrowType,
|
|
1904
|
+
endArrowType: opts.endArrowType
|
|
1905
|
+
},
|
|
1906
|
+
altText: opts.altText,
|
|
1907
|
+
objectName: opts.objectName ? encodeXmlEntities(validateObjectName(opts.objectName, "connector")) : `Connector ${target._slideObjects.filter((obj) => obj._type === "connector").length}`
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
target._slideObjects.push(newObject);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1669
1913
|
* Adds a table object to a slide definition.
|
|
1670
1914
|
* @param {PresSlideInternal} target - slide object that the table should be added to
|
|
1671
1915
|
* @param {TableRow[]} tableRows - table data
|
|
@@ -1734,9 +1978,8 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
|
|
|
1734
1978
|
}
|
|
1735
1979
|
arrRows.push(newRow);
|
|
1736
1980
|
});
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
if (opt.h) opt.h = getSmartParseNumber(opt.h, "Y", presLayout);
|
|
1981
|
+
if (opt.x === void 0 || opt.x === null) opt.x = .5;
|
|
1982
|
+
if (opt.y === void 0 || opt.y === null) opt.y = .5;
|
|
1740
1983
|
opt.fontSize = opt.fontSize || 12;
|
|
1741
1984
|
opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN;
|
|
1742
1985
|
if (typeof opt.margin === "number") opt.margin = [
|
|
@@ -1768,6 +2011,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
|
|
|
1768
2011
|
});
|
|
1769
2012
|
}
|
|
1770
2013
|
opt.autoPage = typeof opt.autoPage === "boolean" ? opt.autoPage : false;
|
|
2014
|
+
opt.autoPagePlaceholder = typeof opt.autoPagePlaceholder === "boolean" ? opt.autoPagePlaceholder : false;
|
|
1771
2015
|
opt.autoPageRepeatHeader = typeof opt.autoPageRepeatHeader === "boolean" ? opt.autoPageRepeatHeader : false;
|
|
1772
2016
|
opt.autoPageHeaderRows = typeof opt.autoPageHeaderRows !== "undefined" && !isNaN(Number(opt.autoPageHeaderRows)) ? Number(opt.autoPageHeaderRows) : 1;
|
|
1773
2017
|
opt.autoPageLineWeight = typeof opt.autoPageLineWeight !== "undefined" && !isNaN(Number(opt.autoPageLineWeight)) ? Number(opt.autoPageLineWeight) : 0;
|
|
@@ -1805,12 +2049,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
|
|
|
1805
2049
|
console.warn("addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.");
|
|
1806
2050
|
opt.colW = void 0;
|
|
1807
2051
|
}
|
|
1808
|
-
} else if (opt.w) opt.w =
|
|
1809
|
-
else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
|
|
1810
|
-
if (opt.x && opt.x < 20) opt.x = inch2Emu(opt.x);
|
|
1811
|
-
if (opt.y && opt.y < 20) opt.y = inch2Emu(opt.y);
|
|
1812
|
-
if (opt.w && typeof opt.w === "number" && opt.w < 20) opt.w = inch2Emu(opt.w);
|
|
1813
|
-
if (opt.h && typeof opt.h === "number" && opt.h < 20) opt.h = inch2Emu(opt.h);
|
|
2052
|
+
} else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
|
|
1814
2053
|
arrRows.forEach((row) => {
|
|
1815
2054
|
row.forEach((cell, idy) => {
|
|
1816
2055
|
if (typeof cell === "number" || typeof cell === "string") row[idy] = {
|
|
@@ -1836,12 +2075,14 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
|
|
|
1836
2075
|
});
|
|
1837
2076
|
} else {
|
|
1838
2077
|
if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
|
|
2078
|
+
const sourcePlaceholders = opt.autoPagePlaceholder && Array.isArray(target._slideObjects) ? target._slideObjects.filter((obj) => obj._type !== "table" && obj.options?.placeholder) : [];
|
|
1839
2079
|
getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
|
|
1840
2080
|
if (!getSlide(target._slideNum + idx)) slides.push(addSlide({ masterName: slideLayout?._name || void 0 }));
|
|
1841
|
-
if (idx > 0) opt.y =
|
|
2081
|
+
if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
|
|
1842
2082
|
{
|
|
1843
2083
|
const newSlide = getSlide(target._slideNum + idx);
|
|
1844
2084
|
opt.autoPage = false;
|
|
2085
|
+
if (idx > 0 && sourcePlaceholders.length > 0) sourcePlaceholders.forEach((ph) => newSlide._slideObjects.push(structuredClone(ph)));
|
|
1845
2086
|
createHyperlinkRels(newSlide, slide.rows);
|
|
1846
2087
|
newSlide.addTable(slide.rows, { ...opt });
|
|
1847
2088
|
if (idx > 0) newAutoPagedSlides.push(newSlide);
|
|
@@ -1910,6 +2151,10 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
|
|
|
1910
2151
|
itemOpts._bodyProp.anchor = !itemOpts.placeholder ? "ctr" : void 0;
|
|
1911
2152
|
itemOpts._bodyProp.vert = itemOpts.vert;
|
|
1912
2153
|
itemOpts._bodyProp.wrap = typeof itemOpts.wrap === "boolean" ? itemOpts.wrap : true;
|
|
2154
|
+
if (itemOpts.columns !== void 0) if (typeof itemOpts.columns !== "number" || isNaN(itemOpts.columns) || itemOpts.columns < 1 || itemOpts.columns > 16) console.warn("Warning: text `columns` must be a number 1-16 (ignoring value)");
|
|
2155
|
+
else itemOpts._bodyProp.numCol = Math.round(itemOpts.columns);
|
|
2156
|
+
if (itemOpts.columnSpacing !== void 0) if (typeof itemOpts.columnSpacing !== "number" || isNaN(itemOpts.columnSpacing) || itemOpts.columnSpacing < 0) console.warn("Warning: text `columnSpacing` must be a number >= 0 (ignoring value)");
|
|
2157
|
+
else itemOpts._bodyProp.spcCol = valToPts(itemOpts.columnSpacing);
|
|
1913
2158
|
if (itemOpts.inset && !isNaN(Number(itemOpts.inset)) || itemOpts.inset === 0) {
|
|
1914
2159
|
itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset);
|
|
1915
2160
|
itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset);
|
|
@@ -2190,6 +2435,16 @@ var Slide = class {
|
|
|
2190
2435
|
return this;
|
|
2191
2436
|
}
|
|
2192
2437
|
/**
|
|
2438
|
+
* Add a connector (a line drawn between two points, emitted as a PowerPoint `<p:cxnSp>`).
|
|
2439
|
+
* @param {ConnectorProps} options - connector endpoints (`x1,y1,x2,y2`) and line styling
|
|
2440
|
+
* @return {Slide} this Slide
|
|
2441
|
+
* @example slide.addConnector({ type: 'elbow', x1: 1, y1: 1, x2: 5, y2: 3, endArrowType: 'triangle' })
|
|
2442
|
+
*/
|
|
2443
|
+
addConnector(options) {
|
|
2444
|
+
addConnectorDefinition(this, options);
|
|
2445
|
+
return this;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2193
2448
|
* Add table to Slide
|
|
2194
2449
|
* @param {TableRow[]} tableRows - table rows
|
|
2195
2450
|
* @param {TableProps} options - table options
|
|
@@ -2419,6 +2674,22 @@ async function createExcelWorksheet(chartObject, zip) {
|
|
|
2419
2674
|
});
|
|
2420
2675
|
}
|
|
2421
2676
|
/**
|
|
2677
|
+
* Emit the `<a:latin>/<a:ea>/<a:cs>` font trio for a chart text run.
|
|
2678
|
+
*
|
|
2679
|
+
* In DrawingML run properties a typeface applies only to the script class of
|
|
2680
|
+
* its element: `<a:latin>` covers Latin/ASCII, `<a:ea>` covers East Asian, and
|
|
2681
|
+
* `<a:cs>` covers complex scripts. Emitting `<a:latin>` alone leaves East Asian
|
|
2682
|
+
* (e.g. Chinese) and complex-script glyphs falling back to the theme font, so a
|
|
2683
|
+
* user-specified font never takes effect for that text — most visibly on
|
|
2684
|
+
* PowerPoint for Mac. Stamping the same typeface onto all three classes is what
|
|
2685
|
+
* choosing a font in PowerPoint's UI does (upstream gitbrent/PptxGenJS#1420).
|
|
2686
|
+
* @param {string} typeface - font face name
|
|
2687
|
+
* @return {string} `<a:latin/><a:ea/><a:cs/>` XML
|
|
2688
|
+
*/
|
|
2689
|
+
function createChartTextFonts(typeface) {
|
|
2690
|
+
return `<a:latin typeface="${typeface}"/><a:ea typeface="${typeface}"/><a:cs typeface="${typeface}"/>`;
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2422
2693
|
* Main entry point method for create charts
|
|
2423
2694
|
* @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
|
|
2424
2695
|
* @param {ISlideRelChart} rel - chart object
|
|
@@ -2428,6 +2699,10 @@ function makeXmlCharts(rel) {
|
|
|
2428
2699
|
let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
|
|
2429
2700
|
let usesSecondaryValAxis = false;
|
|
2430
2701
|
let usesSecondaryCatAxis = false;
|
|
2702
|
+
let primaryCatAxisValType = null;
|
|
2703
|
+
let secondaryCatAxisValType = null;
|
|
2704
|
+
let primaryCatAxisHasCategoryChart = false;
|
|
2705
|
+
let secondaryCatAxisHasCategoryChart = false;
|
|
2431
2706
|
strXml += "<c:chartSpace xmlns:c=\"http://schemas.openxmlformats.org/drawingml/2006/chart\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">";
|
|
2432
2707
|
strXml += "<c:date1904 val=\"0\"/>";
|
|
2433
2708
|
strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? "1" : "0"}"/>`;
|
|
@@ -2440,6 +2715,8 @@ function makeXmlCharts(rel) {
|
|
|
2440
2715
|
fontSize: rel.opts.titleFontSize || 18,
|
|
2441
2716
|
titleAlign: rel.opts.titleAlign,
|
|
2442
2717
|
titleBold: rel.opts.titleBold,
|
|
2718
|
+
titleItalic: rel.opts.titleItalic,
|
|
2719
|
+
titleUnderline: rel.opts.titleUnderline,
|
|
2443
2720
|
titlePos: rel.opts.titlePos,
|
|
2444
2721
|
titleRotate: rel.opts.titleRotate
|
|
2445
2722
|
}, rel.opts.x, rel.opts.y);
|
|
@@ -2472,18 +2749,37 @@ function makeXmlCharts(rel) {
|
|
|
2472
2749
|
const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY;
|
|
2473
2750
|
usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis;
|
|
2474
2751
|
usesSecondaryCatAxis = usesSecondaryCatAxis || options.secondaryCatAxis;
|
|
2752
|
+
const usesValueXAxis = type.type === "scatter" || type.type === "bubble" || type.type === "bubble3D";
|
|
2753
|
+
if (options.secondaryCatAxis) if (usesValueXAxis) secondaryCatAxisValType = type.type;
|
|
2754
|
+
else secondaryCatAxisHasCategoryChart = true;
|
|
2755
|
+
else if (usesValueXAxis) primaryCatAxisValType = type.type;
|
|
2756
|
+
else primaryCatAxisHasCategoryChart = true;
|
|
2475
2757
|
strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId);
|
|
2476
2758
|
});
|
|
2477
2759
|
else strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY);
|
|
2478
2760
|
if (rel.opts._type !== "pie" && rel.opts._type !== "doughnut") {
|
|
2479
2761
|
if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) throw new Error("Secondary axis must be used by one of the multiple charts");
|
|
2762
|
+
const comboCatAxisType = (isSecondary) => {
|
|
2763
|
+
const valType = isSecondary ? secondaryCatAxisValType : primaryCatAxisValType;
|
|
2764
|
+
const hasCategoryChart = isSecondary ? secondaryCatAxisHasCategoryChart : primaryCatAxisHasCategoryChart;
|
|
2765
|
+
if (!valType) return {};
|
|
2766
|
+
if (hasCategoryChart) {
|
|
2767
|
+
console.warn(`A category-based chart and a scatter/bubble chart cannot share the same ${isSecondary ? "secondary" : "primary"} category axis; emitting a category axis. Put the scatter/bubble series on a separate axis.`);
|
|
2768
|
+
return {};
|
|
2769
|
+
}
|
|
2770
|
+
return { _type: valType };
|
|
2771
|
+
};
|
|
2480
2772
|
if (rel.opts.catAxes) {
|
|
2481
2773
|
if (!rel.opts.valAxes || rel.opts.valAxes.length !== rel.opts.catAxes.length) throw new Error("There must be the same number of value and category axes.");
|
|
2482
2774
|
strXml += makeCatAxis({
|
|
2483
2775
|
...rel.opts,
|
|
2484
|
-
...rel.opts.catAxes[0]
|
|
2776
|
+
...rel.opts.catAxes[0],
|
|
2777
|
+
...comboCatAxisType(false)
|
|
2485
2778
|
}, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
|
|
2486
|
-
} else strXml += makeCatAxis(
|
|
2779
|
+
} else strXml += makeCatAxis({
|
|
2780
|
+
...rel.opts,
|
|
2781
|
+
...comboCatAxisType(false)
|
|
2782
|
+
}, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
|
|
2487
2783
|
if (rel.opts.valAxes) {
|
|
2488
2784
|
strXml += makeValAxis({
|
|
2489
2785
|
...rel.opts,
|
|
@@ -2500,9 +2796,13 @@ function makeXmlCharts(rel) {
|
|
|
2500
2796
|
}
|
|
2501
2797
|
if (rel.opts?.catAxes && rel.opts?.catAxes[1]) strXml += makeCatAxis({
|
|
2502
2798
|
...rel.opts,
|
|
2503
|
-
...rel.opts.catAxes[1]
|
|
2799
|
+
...rel.opts.catAxes[1],
|
|
2800
|
+
...comboCatAxisType(true)
|
|
2801
|
+
}, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
|
|
2802
|
+
else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis({
|
|
2803
|
+
...rel.opts,
|
|
2804
|
+
...comboCatAxisType(true)
|
|
2504
2805
|
}, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
|
|
2505
|
-
else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
|
|
2506
2806
|
}
|
|
2507
2807
|
if (rel.opts.showDataTable) {
|
|
2508
2808
|
strXml += "<c:dTable>";
|
|
@@ -2557,8 +2857,7 @@ function makeXmlCharts(rel) {
|
|
|
2557
2857
|
strXml += " <a:pPr>";
|
|
2558
2858
|
strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : "<a:defRPr>";
|
|
2559
2859
|
if (rel.opts.legendColor) strXml += genXmlColorSelection(rel.opts.legendColor);
|
|
2560
|
-
if (rel.opts.legendFontFace) strXml +=
|
|
2561
|
-
if (rel.opts.legendFontFace) strXml += "<a:cs typeface=\"" + rel.opts.legendFontFace + "\"/>";
|
|
2860
|
+
if (rel.opts.legendFontFace) strXml += createChartTextFonts(rel.opts.legendFontFace);
|
|
2562
2861
|
strXml += " </a:defRPr>";
|
|
2563
2862
|
strXml += " </a:pPr>";
|
|
2564
2863
|
strXml += " <a:endParaRPr lang=\"en-US\"/>";
|
|
@@ -2596,6 +2895,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2596
2895
|
let idxColLtr = 1;
|
|
2597
2896
|
let optsChartData;
|
|
2598
2897
|
let strXml = "";
|
|
2898
|
+
const valFmtCode = encodeXmlEntities(opts.valLabelFormatCode || opts.dataTableFormatCode || opts.dataLabelFormatCode || "General");
|
|
2599
2899
|
switch (chartType) {
|
|
2600
2900
|
case "area":
|
|
2601
2901
|
case "bar":
|
|
@@ -2639,7 +2939,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2639
2939
|
} else if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
2640
2940
|
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
2641
2941
|
strXml += " </c:spPr>";
|
|
2642
|
-
if (chartType
|
|
2942
|
+
if (chartType === "bar" || chartType === "bar3D") strXml += " <c:invertIfNegative val=\"0\"/>";
|
|
2643
2943
|
if (chartType === "line" || chartType === "radar") {
|
|
2644
2944
|
strXml += "<c:marker>";
|
|
2645
2945
|
strXml += " <c:symbol val=\"" + opts.lineDataSymbol + "\"/>";
|
|
@@ -2654,6 +2954,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2654
2954
|
strXml += " </c:spPr>";
|
|
2655
2955
|
strXml += "</c:marker>";
|
|
2656
2956
|
}
|
|
2957
|
+
{
|
|
2958
|
+
const barVaryColors = (chartType === "bar" || chartType === "bar3D") && data.length === 1 && (opts.chartColors && opts.chartColors !== BARCHART_COLORS && opts.chartColors.length > 1 || opts.invertedColors?.length) ? opts.chartColors || BARCHART_COLORS : null;
|
|
2959
|
+
strXml += makeSeriesDataPointsXml(chartType, obj, opts, barVaryColors);
|
|
2960
|
+
}
|
|
2657
2961
|
if (chartType !== "radar") {
|
|
2658
2962
|
const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
|
|
2659
2963
|
const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
|
|
@@ -2661,12 +2965,15 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2661
2965
|
const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
|
|
2662
2966
|
const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
|
|
2663
2967
|
strXml += "<c:dLbls>";
|
|
2968
|
+
if (obj.customLabels?.length) obj.customLabels.forEach((lbl, idx) => {
|
|
2969
|
+
if (lbl) strXml += makeCustomDLblXml(idx, lbl, opts);
|
|
2970
|
+
});
|
|
2664
2971
|
strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
|
|
2665
2972
|
if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
|
|
2666
2973
|
strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
|
|
2667
2974
|
strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
|
|
2668
2975
|
strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
|
|
2669
|
-
strXml +=
|
|
2976
|
+
strXml += createChartTextFonts(lblFace);
|
|
2670
2977
|
strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
|
|
2671
2978
|
if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
|
|
2672
2979
|
strXml += "<c:showLegendKey val=\"0\"/>";
|
|
@@ -2675,29 +2982,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2675
2982
|
strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
|
|
2676
2983
|
strXml += "</c:dLbls>";
|
|
2677
2984
|
}
|
|
2678
|
-
if ((chartType === "bar" || chartType === "bar3D") && data.length === 1 && (opts.chartColors && opts.chartColors !== BARCHART_COLORS && opts.chartColors.length > 1 || opts.invertedColors?.length)) obj.values.forEach((value, index) => {
|
|
2679
|
-
const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
|
|
2680
|
-
strXml += " <c:dPt>";
|
|
2681
|
-
strXml += ` <c:idx val="${index}"/>`;
|
|
2682
|
-
strXml += " <c:invertIfNegative val=\"0\"/>";
|
|
2683
|
-
strXml += " <c:bubble3D val=\"0\"/>";
|
|
2684
|
-
strXml += " <c:spPr>";
|
|
2685
|
-
if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
|
|
2686
|
-
else if (chartType === "bar") {
|
|
2687
|
-
strXml += "<a:solidFill>";
|
|
2688
|
-
strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
|
|
2689
|
-
strXml += "</a:solidFill>";
|
|
2690
|
-
} else {
|
|
2691
|
-
strXml += "<a:ln>";
|
|
2692
|
-
strXml += " <a:solidFill>";
|
|
2693
|
-
strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
|
|
2694
|
-
strXml += " </a:solidFill>";
|
|
2695
|
-
strXml += "</a:ln>";
|
|
2696
|
-
}
|
|
2697
|
-
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
2698
|
-
strXml += " </c:spPr>";
|
|
2699
|
-
strXml += " </c:dPt>";
|
|
2700
|
-
});
|
|
2701
2985
|
strXml += "<c:cat>";
|
|
2702
2986
|
if (opts.catLabelFormatCode) {
|
|
2703
2987
|
strXml += " <c:numRef>";
|
|
@@ -2734,10 +3018,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2734
3018
|
strXml += " <c:numRef>";
|
|
2735
3019
|
strXml += `<c:f>Sheet1!$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$2:$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$${obj.labels[0].length + 1}</c:f>`;
|
|
2736
3020
|
strXml += " <c:numCache>";
|
|
2737
|
-
strXml += " <c:formatCode>" +
|
|
3021
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
2738
3022
|
strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`;
|
|
2739
3023
|
obj.values.forEach((value, idx) => {
|
|
2740
|
-
|
|
3024
|
+
strXml += numCachePt(idx, value);
|
|
2741
3025
|
});
|
|
2742
3026
|
strXml += " </c:numCache>";
|
|
2743
3027
|
strXml += " </c:numRef>";
|
|
@@ -2753,7 +3037,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2753
3037
|
strXml += " <a:p><a:pPr>";
|
|
2754
3038
|
strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
|
|
2755
3039
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
2756
|
-
strXml += "
|
|
3040
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
2757
3041
|
strXml += " </a:defRPr>";
|
|
2758
3042
|
strXml += " </a:pPr></a:p>";
|
|
2759
3043
|
strXml += " </c:txPr>";
|
|
@@ -2769,6 +3053,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2769
3053
|
if (chartType === "bar") {
|
|
2770
3054
|
strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
|
|
2771
3055
|
strXml += ` <c:overlap val="${opts.barOverlapPct != null ? opts.barOverlapPct : (opts.barGrouping || "").includes("tacked") ? 100 : 0}"/>`;
|
|
3056
|
+
strXml += createSerLinesElement(opts.barSeriesLine);
|
|
2772
3057
|
} else if (chartType === "bar3D") {
|
|
2773
3058
|
strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
|
|
2774
3059
|
strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`;
|
|
@@ -2820,6 +3105,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2820
3105
|
strXml += "<a:effectLst/>";
|
|
2821
3106
|
strXml += "</c:spPr>";
|
|
2822
3107
|
strXml += "</c:marker>";
|
|
3108
|
+
{
|
|
3109
|
+
const scatterVaryColors = data.length === 1 && opts.chartColors !== BARCHART_COLORS ? opts.chartColors || BARCHART_COLORS : null;
|
|
3110
|
+
strXml += makeSeriesDataPointsXml(chartType, obj, opts, scatterVaryColors);
|
|
3111
|
+
}
|
|
2823
3112
|
if (opts.showLabel) {
|
|
2824
3113
|
const chartUuid = getUuid("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
|
|
2825
3114
|
if (obj.labels[0] && (opts.dataLabelFormatScatter === "custom" || opts.dataLabelFormatScatter === "customXY")) {
|
|
@@ -2838,13 +3127,13 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2838
3127
|
strXml += " <a:pPr>";
|
|
2839
3128
|
strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
|
|
2840
3129
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
2841
|
-
strXml +=
|
|
3130
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
2842
3131
|
strXml += " </a:defRPr>";
|
|
2843
3132
|
strXml += " </a:pPr>";
|
|
2844
3133
|
strXml += " <a:r>";
|
|
2845
3134
|
strXml += ` <a:rPr lang="${opts.lang || "en-US"}" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike" dirty="0">`;
|
|
2846
3135
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
2847
|
-
strXml +=
|
|
3136
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
2848
3137
|
strXml += " </a:rPr>";
|
|
2849
3138
|
strXml += " <a:t>" + encodeXmlEntities(label) + "</a:t>";
|
|
2850
3139
|
strXml += " </a:r>";
|
|
@@ -2924,7 +3213,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2924
3213
|
strXml += " <a:pPr>";
|
|
2925
3214
|
strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
|
|
2926
3215
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
2927
|
-
strXml +=
|
|
3216
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
2928
3217
|
strXml += " </a:defRPr>";
|
|
2929
3218
|
strXml += " </a:pPr>";
|
|
2930
3219
|
strXml += ` <a:endParaRPr lang="${opts.lang || "en-US"}"/>`;
|
|
@@ -2945,31 +3234,14 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2945
3234
|
strXml += "</c:dLbls>";
|
|
2946
3235
|
}
|
|
2947
3236
|
}
|
|
2948
|
-
if (data.length === 1 && opts.chartColors !== BARCHART_COLORS) obj.values.forEach((value, index) => {
|
|
2949
|
-
const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
|
|
2950
|
-
strXml += " <c:dPt>";
|
|
2951
|
-
strXml += ` <c:idx val="${index}"/>`;
|
|
2952
|
-
strXml += " <c:invertIfNegative val=\"0\"/>";
|
|
2953
|
-
strXml += " <c:bubble3D val=\"0\"/>";
|
|
2954
|
-
strXml += " <c:spPr>";
|
|
2955
|
-
if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
|
|
2956
|
-
else {
|
|
2957
|
-
strXml += "<a:solidFill>";
|
|
2958
|
-
strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
|
|
2959
|
-
strXml += "</a:solidFill>";
|
|
2960
|
-
}
|
|
2961
|
-
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
2962
|
-
strXml += " </c:spPr>";
|
|
2963
|
-
strXml += " </c:dPt>";
|
|
2964
|
-
});
|
|
2965
3237
|
strXml += "<c:xVal>";
|
|
2966
3238
|
strXml += " <c:numRef>";
|
|
2967
3239
|
strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
|
|
2968
3240
|
strXml += " <c:numCache>";
|
|
2969
|
-
strXml += " <c:formatCode>
|
|
3241
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
2970
3242
|
strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
|
|
2971
3243
|
data[0].values.forEach((value, idx) => {
|
|
2972
|
-
|
|
3244
|
+
strXml += numCachePt(idx, value);
|
|
2973
3245
|
});
|
|
2974
3246
|
strXml += " </c:numCache>";
|
|
2975
3247
|
strXml += " </c:numRef>";
|
|
@@ -2978,10 +3250,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2978
3250
|
strXml += " <c:numRef>";
|
|
2979
3251
|
strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`;
|
|
2980
3252
|
strXml += " <c:numCache>";
|
|
2981
|
-
strXml += " <c:formatCode>
|
|
3253
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
2982
3254
|
strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
|
|
2983
3255
|
data[0].values.forEach((_value, idx) => {
|
|
2984
|
-
|
|
3256
|
+
strXml += numCachePt(idx, obj.values[idx]);
|
|
2985
3257
|
});
|
|
2986
3258
|
strXml += " </c:numCache>";
|
|
2987
3259
|
strXml += " </c:numRef>";
|
|
@@ -2997,7 +3269,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
2997
3269
|
strXml += " <a:p><a:pPr>";
|
|
2998
3270
|
strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
|
|
2999
3271
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
3000
|
-
strXml += "
|
|
3272
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
3001
3273
|
strXml += " </a:defRPr>";
|
|
3002
3274
|
strXml += " </a:pPr></a:p>";
|
|
3003
3275
|
strXml += " </c:txPr>";
|
|
@@ -3047,10 +3319,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3047
3319
|
strXml += " <c:numRef>";
|
|
3048
3320
|
strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
|
|
3049
3321
|
strXml += " <c:numCache>";
|
|
3050
|
-
strXml += " <c:formatCode>
|
|
3322
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
3051
3323
|
strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
|
|
3052
3324
|
data[0].values.forEach((value, idx) => {
|
|
3053
|
-
strXml +=
|
|
3325
|
+
strXml += numCachePt(idx, value);
|
|
3054
3326
|
});
|
|
3055
3327
|
strXml += " </c:numCache>";
|
|
3056
3328
|
strXml += " </c:numRef>";
|
|
@@ -3060,10 +3332,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3060
3332
|
strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`;
|
|
3061
3333
|
idxColLtr++;
|
|
3062
3334
|
strXml += " <c:numCache>";
|
|
3063
|
-
strXml += " <c:formatCode>
|
|
3335
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
3064
3336
|
strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
|
|
3065
3337
|
data[0].values.forEach((_value, idx) => {
|
|
3066
|
-
strXml +=
|
|
3338
|
+
strXml += numCachePt(idx, obj.values[idx]);
|
|
3067
3339
|
});
|
|
3068
3340
|
strXml += " </c:numCache>";
|
|
3069
3341
|
strXml += " </c:numRef>";
|
|
@@ -3076,7 +3348,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3076
3348
|
strXml += " <c:formatCode>General</c:formatCode>";
|
|
3077
3349
|
strXml += ` <c:ptCount val="${obj.sizes.length}"/>`;
|
|
3078
3350
|
obj.sizes.forEach((value, idx) => {
|
|
3079
|
-
strXml +=
|
|
3351
|
+
strXml += numCachePt(idx, value);
|
|
3080
3352
|
});
|
|
3081
3353
|
strXml += " </c:numCache>";
|
|
3082
3354
|
strXml += " </c:numRef>";
|
|
@@ -3089,12 +3361,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3089
3361
|
strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
|
|
3090
3362
|
strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(Math.round(opts.dataLabelFontSize || 12) * 100)}" u="none">`;
|
|
3091
3363
|
strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
|
|
3092
|
-
strXml +=
|
|
3364
|
+
strXml += createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
3093
3365
|
strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
|
|
3094
3366
|
if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
|
|
3095
3367
|
strXml += "<c:showLegendKey val=\"0\"/>";
|
|
3096
3368
|
strXml += `<c:showVal val="${opts.showValue ? "1" : "0"}"/>`;
|
|
3097
|
-
strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="0"/>`;
|
|
3369
|
+
strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="${opts.showBubbleSize ? "1" : "0"}"/>`;
|
|
3098
3370
|
strXml += "<c:extLst>";
|
|
3099
3371
|
strXml += " <c:ext uri=\"{CE6537A1-D6FC-4f65-9D91-7224C49458BB}\" xmlns:c15=\"http://schemas.microsoft.com/office/drawing/2012/chart\">";
|
|
3100
3372
|
strXml += " <c15:showLeaderLines val=\"" + (opts.showLeaderLines ? "1" : "0") + "\"/>";
|
|
@@ -3128,33 +3400,37 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3128
3400
|
else strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
3129
3401
|
strXml += " </c:spPr>";
|
|
3130
3402
|
optsChartData.labels[0].forEach((_label, idx) => {
|
|
3403
|
+
const ptStyle = optsChartData.pointStyles?.[idx];
|
|
3131
3404
|
strXml += "<c:dPt>";
|
|
3132
3405
|
strXml += ` <c:idx val="${idx}"/>`;
|
|
3133
3406
|
strXml += " <c:bubble3D val=\"0\"/>";
|
|
3134
3407
|
strXml += " <c:spPr>";
|
|
3135
|
-
strXml += `<a:solidFill>${createColorElement(opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
|
|
3136
|
-
if (
|
|
3408
|
+
strXml += `<a:solidFill>${createColorElement(ptStyle?.fill || opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
|
|
3409
|
+
if (ptStyle?.border) strXml += createChartBorderLine(ptStyle.border);
|
|
3410
|
+
else if (opts.dataBorder) strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="flat"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
3137
3411
|
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
3138
3412
|
strXml += " </c:spPr>";
|
|
3139
3413
|
strXml += "</c:dPt>";
|
|
3140
3414
|
});
|
|
3141
3415
|
strXml += "<c:dLbls>";
|
|
3142
3416
|
optsChartData.labels[0].forEach((_label, idx) => {
|
|
3417
|
+
const customLbl = optsChartData.customLabels?.[idx];
|
|
3143
3418
|
strXml += "<c:dLbl>";
|
|
3144
3419
|
strXml += ` <c:idx val="${idx}"/>`;
|
|
3420
|
+
if (customLbl) strXml += `<c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="${opts.lang || "en-US"}" dirty="0"/><a:t>${encodeXmlEntities(customLbl)}</a:t></a:r></a:p></c:rich></c:tx>`;
|
|
3145
3421
|
strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
|
|
3146
3422
|
strXml += " <c:spPr/><c:txPr>";
|
|
3147
3423
|
strXml += " <a:bodyPr/><a:lstStyle/>";
|
|
3148
3424
|
strXml += " <a:p><a:pPr>";
|
|
3149
3425
|
strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
|
|
3150
3426
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
3151
|
-
strXml +=
|
|
3427
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
3152
3428
|
strXml += " </a:defRPr>";
|
|
3153
3429
|
strXml += " </a:pPr></a:p>";
|
|
3154
3430
|
strXml += " </c:txPr>";
|
|
3155
3431
|
if (chartType === "pie" && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
|
|
3156
3432
|
strXml += " <c:showLegendKey val=\"0\"/>";
|
|
3157
|
-
strXml += " <c:showVal val=\"" + (opts.showValue ? "1" : "0") + "\"/>";
|
|
3433
|
+
strXml += " <c:showVal val=\"" + (customLbl ? "0" : opts.showValue ? "1" : "0") + "\"/>";
|
|
3158
3434
|
strXml += " <c:showCatName val=\"" + (opts.showLabel ? "1" : "0") + "\"/>";
|
|
3159
3435
|
strXml += " <c:showSerName val=\"" + (opts.showSerName ? "1" : "0") + "\"/>";
|
|
3160
3436
|
strXml += " <c:showPercent val=\"" + (opts.showPercent ? "1" : "0") + "\"/>";
|
|
@@ -3169,7 +3445,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3169
3445
|
strXml += " <a:pPr>";
|
|
3170
3446
|
strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
|
|
3171
3447
|
strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
|
|
3172
|
-
strXml +=
|
|
3448
|
+
strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
|
|
3173
3449
|
strXml += " </a:defRPr>";
|
|
3174
3450
|
strXml += " </a:pPr>";
|
|
3175
3451
|
strXml += " </a:p>";
|
|
@@ -3182,6 +3458,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3182
3458
|
strXml += " <c:showPercent val=\"1\"/>";
|
|
3183
3459
|
strXml += " <c:showBubbleSize val=\"0\"/>";
|
|
3184
3460
|
strXml += ` <c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
|
|
3461
|
+
strXml += createLeaderLinesElement(opts);
|
|
3185
3462
|
strXml += "</c:dLbls>";
|
|
3186
3463
|
strXml += "<c:cat>";
|
|
3187
3464
|
strXml += " <c:strRef>";
|
|
@@ -3198,6 +3475,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
|
|
|
3198
3475
|
strXml += " <c:numRef>";
|
|
3199
3476
|
strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`;
|
|
3200
3477
|
strXml += " <c:numCache>";
|
|
3478
|
+
strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
|
|
3201
3479
|
strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`;
|
|
3202
3480
|
optsChartData.values.forEach((value, idx) => {
|
|
3203
3481
|
strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
|
|
@@ -3273,7 +3551,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
|
|
|
3273
3551
|
strXml += " <a:pPr>";
|
|
3274
3552
|
strXml += ` <a:defRPr sz="${Math.round((opts.catAxisLabelFontSize || 12) * 100)}" b="${opts.catAxisLabelFontBold ? 1 : 0}" i="${opts.catAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
|
|
3275
3553
|
strXml += " <a:solidFill>" + createColorElement(opts.catAxisLabelColor || "000000") + "</a:solidFill>";
|
|
3276
|
-
strXml += "
|
|
3554
|
+
strXml += " " + createChartTextFonts(opts.catAxisLabelFontFace || "Arial");
|
|
3277
3555
|
strXml += " </a:defRPr>";
|
|
3278
3556
|
strXml += " </a:pPr>";
|
|
3279
3557
|
strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
|
|
@@ -3366,7 +3644,7 @@ function makeValAxis(opts, valAxisId) {
|
|
|
3366
3644
|
strXml += " <a:pPr>";
|
|
3367
3645
|
strXml += ` <a:defRPr sz="${Math.round((opts.valAxisLabelFontSize || 12) * 100)}" b="${opts.valAxisLabelFontBold ? 1 : 0}" i="${opts.valAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
|
|
3368
3646
|
strXml += " <a:solidFill>" + createColorElement(opts.valAxisLabelColor || "000000") + "</a:solidFill>";
|
|
3369
|
-
strXml += "
|
|
3647
|
+
strXml += " " + createChartTextFonts(opts.valAxisLabelFontFace || "Arial");
|
|
3370
3648
|
strXml += " </a:defRPr>";
|
|
3371
3649
|
strXml += " </a:pPr>";
|
|
3372
3650
|
strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
|
|
@@ -3422,7 +3700,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
|
|
|
3422
3700
|
strXml += " <a:pPr>";
|
|
3423
3701
|
strXml += ` <a:defRPr sz="${Math.round((opts.serAxisLabelFontSize || 12) * 100)}" b="${opts.serAxisLabelFontBold ? "1" : "0"}" i="${opts.serAxisLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
|
|
3424
3702
|
strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || "000000")}</a:solidFill>`;
|
|
3425
|
-
strXml +=
|
|
3703
|
+
strXml += " " + createChartTextFonts(opts.serAxisLabelFontFace || "Arial");
|
|
3426
3704
|
strXml += " </a:defRPr>";
|
|
3427
3705
|
strXml += " </a:pPr>";
|
|
3428
3706
|
strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
|
|
@@ -3462,17 +3740,31 @@ function genXmlTitle(opts, chartX, chartY) {
|
|
|
3462
3740
|
const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : "<a:bodyPr/>";
|
|
3463
3741
|
const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : "";
|
|
3464
3742
|
const titleBold = opts.titleBold ? 1 : 0;
|
|
3743
|
+
const titleItalic = opts.titleItalic ? 1 : 0;
|
|
3744
|
+
const titleUnderline = opts.titleUnderline ? "sng" : "none";
|
|
3465
3745
|
let layout = "<c:layout/>";
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
let
|
|
3470
|
-
|
|
3471
|
-
if (
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3746
|
+
const hasX = opts.titlePos && typeof opts.titlePos.x === "number";
|
|
3747
|
+
const hasY = opts.titlePos && typeof opts.titlePos.y === "number";
|
|
3748
|
+
if (hasX || hasY) {
|
|
3749
|
+
let modes = "";
|
|
3750
|
+
let vals = "";
|
|
3751
|
+
if (hasX) {
|
|
3752
|
+
const totalX = opts.titlePos.x + chartX;
|
|
3753
|
+
let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
|
|
3754
|
+
if (valX >= 1) valX = valX / 10;
|
|
3755
|
+
if (valX >= .1) valX = valX / 10;
|
|
3756
|
+
modes += "<c:xMode val=\"edge\"/>";
|
|
3757
|
+
vals += `<c:x val="${valX}"/>`;
|
|
3758
|
+
}
|
|
3759
|
+
if (hasY) {
|
|
3760
|
+
const totalY = opts.titlePos.y + chartY;
|
|
3761
|
+
let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
|
|
3762
|
+
if (valY >= 1) valY = valY / 10;
|
|
3763
|
+
if (valY >= .1) valY = valY / 10;
|
|
3764
|
+
modes += "<c:yMode val=\"edge\"/>";
|
|
3765
|
+
vals += `<c:y val="${valY}"/>`;
|
|
3766
|
+
}
|
|
3767
|
+
layout = `<c:layout><c:manualLayout>${modes}${vals}</c:manualLayout></c:layout>`;
|
|
3476
3768
|
}
|
|
3477
3769
|
return `<c:title>
|
|
3478
3770
|
<c:tx>
|
|
@@ -3481,15 +3773,15 @@ function genXmlTitle(opts, chartX, chartY) {
|
|
|
3481
3773
|
<a:lstStyle/>
|
|
3482
3774
|
<a:p>
|
|
3483
3775
|
${align}
|
|
3484
|
-
<a:defRPr ${sizeAttr} b="${titleBold}" i="
|
|
3776
|
+
<a:defRPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
|
|
3485
3777
|
<a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
|
|
3486
|
-
|
|
3778
|
+
${createChartTextFonts(opts.fontFace || "Arial")}
|
|
3487
3779
|
</a:defRPr>
|
|
3488
3780
|
</a:pPr>
|
|
3489
3781
|
<a:r>
|
|
3490
|
-
<a:rPr ${sizeAttr} b="${titleBold}" i="
|
|
3782
|
+
<a:rPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
|
|
3491
3783
|
<a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
|
|
3492
|
-
|
|
3784
|
+
${createChartTextFonts(opts.fontFace || "Arial")}
|
|
3493
3785
|
</a:rPr>
|
|
3494
3786
|
<a:t>${encodeXmlEntities(opts.title) || ""}</a:t>
|
|
3495
3787
|
</a:r>
|
|
@@ -3563,11 +3855,123 @@ function createGridLineElement(glOpts) {
|
|
|
3563
3855
|
strXml += "</c:majorGridlines>";
|
|
3564
3856
|
return strXml;
|
|
3565
3857
|
}
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3858
|
+
/**
|
|
3859
|
+
* Build a `<c:pt>` numeric-cache data point, or '' to leave a gap.
|
|
3860
|
+
*
|
|
3861
|
+
* `<c:v>` inside a `<c:numCache>` is an `xsd:double`; emitting `NaN`, `Infinity`
|
|
3862
|
+
* or an empty string yields an invalid value that makes PowerPoint report the
|
|
3863
|
+
* package as needing repair. Null/undefined are intentional gaps and are skipped
|
|
3864
|
+
* silently (a sparse, idx-keyed cache is valid); other non-finite numbers are
|
|
3865
|
+
* skipped with a warning, per the library's "warn rather than emit a degenerate
|
|
3866
|
+
* result" policy.
|
|
3867
|
+
* @param idx - zero-based data-point index (emitted as `idx`)
|
|
3868
|
+
* @param value - numeric value (or null/undefined gap)
|
|
3869
|
+
*/
|
|
3870
|
+
function numCachePt(idx, value) {
|
|
3871
|
+
if (value == null) return "";
|
|
3872
|
+
if (!Number.isFinite(value)) {
|
|
3873
|
+
console.warn(`Warning: chart value "${value}" at index ${idx} is not a finite number; data point omitted.`);
|
|
3874
|
+
return "";
|
|
3875
|
+
}
|
|
3876
|
+
return `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
|
|
3877
|
+
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Build a `<c:serLines>` ("Series Lines") element for a bar chart.
|
|
3880
|
+
* @param opt - `true` for PowerPoint automatic styling, an {@link OptsChartGridLine}
|
|
3881
|
+
* to customize the line, or falsy / `{ style: 'none' }` to omit the element.
|
|
3882
|
+
*/
|
|
3883
|
+
function createSerLinesElement(opt) {
|
|
3884
|
+
if (!opt) return "";
|
|
3885
|
+
if (opt === true) return "<c:serLines/>";
|
|
3886
|
+
if (opt.style === "none") return "";
|
|
3887
|
+
let strXml = "<c:serLines><c:spPr>";
|
|
3888
|
+
strXml += `<a:ln w="${valToPts(opt.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(opt.cap || DEF_CHART_GRIDLINE.cap)}">`;
|
|
3889
|
+
strXml += `<a:solidFill><a:srgbClr val="${opt.color || DEF_CHART_GRIDLINE.color}"/></a:solidFill>`;
|
|
3890
|
+
strXml += `<a:prstDash val="${opt.style || DEF_CHART_GRIDLINE.style}"/><a:round/>`;
|
|
3891
|
+
strXml += "</a:ln></c:spPr></c:serLines>";
|
|
3892
|
+
return strXml;
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Build the `<c:leaderLines>` element for pie/doughnut data labels.
|
|
3896
|
+
*
|
|
3897
|
+
* Schema position: inside `<c:dLbls>`, immediately after `<c:showLeaderLines>`
|
|
3898
|
+
* (CT_DLbls / Group_DLbls order: showLeaderLines → leaderLines).
|
|
3899
|
+
*
|
|
3900
|
+
* Returns `''` unless the caller both enabled leader lines (`showLeaderLines`)
|
|
3901
|
+
* and configured their appearance (`leaderLineColor` / `leaderLineSize`). When
|
|
3902
|
+
* appearance is unset we leave the element off so PowerPoint applies its
|
|
3903
|
+
* automatic leader-line color, matching prior behavior.
|
|
3904
|
+
*
|
|
3905
|
+
* @param opts - chart options (reads `showLeaderLines`, `leaderLineColor`, `leaderLineSize`)
|
|
3906
|
+
*/
|
|
3907
|
+
function createLeaderLinesElement(opts) {
|
|
3908
|
+
if (!opts.showLeaderLines) return "";
|
|
3909
|
+
if (!opts.leaderLineColor && opts.leaderLineSize == null) return "";
|
|
3910
|
+
return `<c:leaderLines><c:spPr><a:ln w="${valToPts(opts.leaderLineSize ?? .75)}" cap="flat"><a:solidFill>${createColorElement(opts.leaderLineColor || "808080")}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln><a:effectLst/></c:spPr></c:leaderLines>`;
|
|
3911
|
+
}
|
|
3912
|
+
function makeCustomDLblXml(idx, text, opts) {
|
|
3913
|
+
const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
|
|
3914
|
+
const bold = opts.dataLabelFontBold ? "1" : "0";
|
|
3915
|
+
const italic = opts.dataLabelFontItalic ? "1" : "0";
|
|
3916
|
+
const color = createColorElement(opts.dataLabelColor || "000000");
|
|
3917
|
+
const face = opts.dataLabelFontFace || "Arial";
|
|
3918
|
+
const lang = opts.lang || "en-US";
|
|
3919
|
+
return `<c:dLbl><c:idx val="${idx}"/><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="${sz}" b="${bold}" i="${italic}" u="none" strike="noStrike"><a:solidFill>${color}</a:solidFill>${createChartTextFonts(face)}</a:defRPr></a:pPr><a:r><a:rPr lang="${lang}" sz="${sz}" b="${bold}" i="${italic}" u="none" strike="noStrike" dirty="0"><a:solidFill>${color}</a:solidFill>${createChartTextFonts(face)}</a:rPr><a:t>${encodeXmlEntities(text)}</a:t></a:r></a:p></c:rich></c:tx><c:showLegendKey val="0"/><c:showVal val="0"/><c:showCatName val="0"/><c:showSerName val="0"/><c:showPercent val="0"/><c:showBubbleSize val="0"/></c:dLbl>`;
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Build an `<a:ln>` border element from a per-data-point `BorderProps`.
|
|
3923
|
+
* @param border - point border style (`type`, `color`, `pt`)
|
|
3924
|
+
*/
|
|
3925
|
+
function createChartBorderLine(border) {
|
|
3926
|
+
if (border.type === "none") return "<a:ln><a:noFill/></a:ln>";
|
|
3927
|
+
const dash = border.type === "dash" ? "dash" : "solid";
|
|
3928
|
+
return `<a:ln w="${valToPts(border.pt ?? 1)}" cap="flat"><a:solidFill>${createColorElement(border.color || "666666")}</a:solidFill><a:prstDash val="${dash}"/><a:round/></a:ln>`;
|
|
3929
|
+
}
|
|
3930
|
+
/**
|
|
3931
|
+
* Build `<c:dPt>` entries for a series in the bar/line/area/scatter loops.
|
|
3932
|
+
*
|
|
3933
|
+
* Merges two sources into a single `c:dPt` per index so we never emit a
|
|
3934
|
+
* duplicate `<c:idx>` (which corrupts the chart):
|
|
3935
|
+
* - legacy single-series color-vary fills (bar/scatter), supplied via `varyColors`
|
|
3936
|
+
* - per-point `pointStyles` border/fill overrides
|
|
3937
|
+
*
|
|
3938
|
+
* Must be emitted in schema position *before* `c:dLbls` (CT_*Ser order).
|
|
3939
|
+
* RADAR is skipped: extra per-point markup historically corrupts the chart.
|
|
3940
|
+
*
|
|
3941
|
+
* @param chartType - series chart type
|
|
3942
|
+
* @param obj - series data (reads `values`, `pointStyles`)
|
|
3943
|
+
* @param opts - chart options (fill/shadow/lineSize context)
|
|
3944
|
+
* @param varyColors - color array when single-series color-vary applies, else `null`
|
|
3945
|
+
*/
|
|
3946
|
+
function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
|
|
3947
|
+
if (chartType === "radar") return "";
|
|
3948
|
+
const pointStyles = obj.pointStyles;
|
|
3949
|
+
if (!varyColors && !pointStyles?.length) return "";
|
|
3950
|
+
const isBar = chartType === "bar" || chartType === "bar3D";
|
|
3951
|
+
const isScatter = chartType === "scatter";
|
|
3952
|
+
let xml = "";
|
|
3953
|
+
obj.values.forEach((value, index) => {
|
|
3954
|
+
const ptStyle = pointStyles?.[index];
|
|
3955
|
+
const arrColors = varyColors ? value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : varyColors : null;
|
|
3956
|
+
const fillColor = ptStyle?.fill || (arrColors ? arrColors[index % arrColors.length] : null);
|
|
3957
|
+
const border = ptStyle?.border;
|
|
3958
|
+
if (!fillColor && !border) return;
|
|
3959
|
+
xml += "<c:dPt>";
|
|
3960
|
+
xml += `<c:idx val="${index}"/>`;
|
|
3961
|
+
if (isBar) xml += "<c:invertIfNegative val=\"0\"/>";
|
|
3962
|
+
xml += "<c:bubble3D val=\"0\"/>";
|
|
3963
|
+
xml += "<c:spPr>";
|
|
3964
|
+
if ((isBar || isScatter) && opts.lineSize === 0 && !border && !ptStyle?.fill) xml += "<a:ln><a:noFill/></a:ln>";
|
|
3965
|
+
else {
|
|
3966
|
+
if (fillColor) if (chartType === "bar3D") xml += `<a:ln><a:solidFill>${createColorElement(fillColor)}</a:solidFill></a:ln>`;
|
|
3967
|
+
else xml += `<a:solidFill>${createColorElement(fillColor)}</a:solidFill>`;
|
|
3968
|
+
if (border) xml += createChartBorderLine(border);
|
|
3969
|
+
}
|
|
3970
|
+
xml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
3971
|
+
xml += "</c:spPr>";
|
|
3972
|
+
xml += "</c:dPt>";
|
|
3973
|
+
});
|
|
3974
|
+
return xml;
|
|
3571
3975
|
}
|
|
3572
3976
|
//#endregion
|
|
3573
3977
|
//#region src/gen-media.ts
|
|
@@ -3580,9 +3984,11 @@ function hasEncodingPath(rel) {
|
|
|
3580
3984
|
/**
|
|
3581
3985
|
* Encode Image/Audio/Video into base64
|
|
3582
3986
|
* @param {PresSlideInternal | SlideLayoutInternal} layout - slide layout
|
|
3987
|
+
* @param {RuntimeAdapter} runtime - runtime adapter (Node/browser media loader)
|
|
3988
|
+
* @param {'throw' | 'placeholder'} onMediaError - failure policy: reject the export (default) or substitute a placeholder and warn
|
|
3583
3989
|
* @return {Promise} promise
|
|
3584
3990
|
*/
|
|
3585
|
-
function encodeSlideMediaRels(layout, runtime) {
|
|
3991
|
+
function encodeSlideMediaRels(layout, runtime, onMediaError = "throw") {
|
|
3586
3992
|
const imageProms = [];
|
|
3587
3993
|
const candidateRels = layout._relsMedia.filter((rel) => rel.type !== "online" && !rel.data && hasEncodingPath(rel));
|
|
3588
3994
|
const unqPaths = [];
|
|
@@ -3600,9 +4006,13 @@ function encodeSlideMediaRels(layout, runtime) {
|
|
|
3600
4006
|
if (rel.isSvgPng) await runtime.createSvgPngPreview(rel);
|
|
3601
4007
|
return "done";
|
|
3602
4008
|
} catch (ex) {
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
4009
|
+
if (onMediaError === "placeholder") {
|
|
4010
|
+
console.warn(`[WARNING] Failed to load media "${rel.path}"; embedding a broken-image placeholder. (${String(ex)})`);
|
|
4011
|
+
rel.data = IMG_BROKEN;
|
|
4012
|
+
candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path).forEach((dupe) => dupe.data = rel.data);
|
|
4013
|
+
return "done";
|
|
4014
|
+
}
|
|
4015
|
+
throw new Error(`Failed to load media "${rel.path}" during export.`, { cause: ex });
|
|
3606
4016
|
}
|
|
3607
4017
|
})());
|
|
3608
4018
|
});
|
|
@@ -3616,6 +4026,37 @@ function encodeSlideMediaRels(layout, runtime) {
|
|
|
3616
4026
|
/**
|
|
3617
4027
|
* PptxGenJS: XML Generation
|
|
3618
4028
|
*/
|
|
4029
|
+
const _warnedTextRangeMsgs = /* @__PURE__ */ new Set();
|
|
4030
|
+
function warnTextRangeOnce(msg) {
|
|
4031
|
+
if (_warnedTextRangeMsgs.has(msg)) return;
|
|
4032
|
+
_warnedTextRangeMsgs.add(msg);
|
|
4033
|
+
console.warn(msg);
|
|
4034
|
+
}
|
|
4035
|
+
/**
|
|
4036
|
+
* Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
|
|
4037
|
+
* hundredths of a point for the `sz` attribute. Out-of-range sizes make
|
|
4038
|
+
* PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
|
|
4039
|
+
*/
|
|
4040
|
+
function clampFontSizeSz(fontSizePts) {
|
|
4041
|
+
const raw = Math.round(fontSizePts * 100);
|
|
4042
|
+
const clamped = Math.min(4e5, Math.max(100, raw));
|
|
4043
|
+
if (clamped !== raw) warnTextRangeOnce(`Warning: fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
|
|
4044
|
+
return clamped;
|
|
4045
|
+
}
|
|
4046
|
+
/** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
|
|
4047
|
+
function clampCharSpacingSpc(charSpacingPts) {
|
|
4048
|
+
const raw = Math.round(charSpacingPts * 100);
|
|
4049
|
+
const clamped = Math.min(4e5, Math.max(-4e5, raw));
|
|
4050
|
+
if (clamped !== raw) warnTextRangeOnce(`Warning: charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
|
|
4051
|
+
return clamped;
|
|
4052
|
+
}
|
|
4053
|
+
/** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
|
|
4054
|
+
function clampLineSpacingPts(lineSpacingPts) {
|
|
4055
|
+
const raw = Math.round(lineSpacingPts * 100);
|
|
4056
|
+
const clamped = Math.min(158400, Math.max(0, raw));
|
|
4057
|
+
if (clamped !== raw) warnTextRangeOnce(`Warning: lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
|
|
4058
|
+
return clamped;
|
|
4059
|
+
}
|
|
3619
4060
|
const ImageSizingXml = {
|
|
3620
4061
|
cover: function(imgSize, boxDim) {
|
|
3621
4062
|
const imgRatio = imgSize.h / imgSize.w;
|
|
@@ -3662,23 +4103,90 @@ const ImageSizingXml = {
|
|
|
3662
4103
|
* @return {string} `<a:prstGeom>` XML
|
|
3663
4104
|
*/
|
|
3664
4105
|
const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
|
|
4106
|
+
const SHAPE_LOCK_ATTRS = [
|
|
4107
|
+
"noGrp",
|
|
4108
|
+
"noSelect",
|
|
4109
|
+
"noRot",
|
|
4110
|
+
"noChangeAspect",
|
|
4111
|
+
"noMove",
|
|
4112
|
+
"noResize",
|
|
4113
|
+
"noEditPoints",
|
|
4114
|
+
"noAdjustHandles",
|
|
4115
|
+
"noChangeArrowheads",
|
|
4116
|
+
"noChangeShapeType",
|
|
4117
|
+
"noTextEdit"
|
|
4118
|
+
];
|
|
4119
|
+
const PICTURE_LOCK_ATTRS = [
|
|
4120
|
+
"noGrp",
|
|
4121
|
+
"noSelect",
|
|
4122
|
+
"noRot",
|
|
4123
|
+
"noChangeAspect",
|
|
4124
|
+
"noMove",
|
|
4125
|
+
"noResize",
|
|
4126
|
+
"noEditPoints",
|
|
4127
|
+
"noAdjustHandles",
|
|
4128
|
+
"noChangeArrowheads",
|
|
4129
|
+
"noChangeShapeType",
|
|
4130
|
+
"noCrop"
|
|
4131
|
+
];
|
|
4132
|
+
const GRAPHIC_FRAME_LOCK_ATTRS = [
|
|
4133
|
+
"noGrp",
|
|
4134
|
+
"noDrilldown",
|
|
4135
|
+
"noSelect",
|
|
4136
|
+
"noChangeAspect",
|
|
4137
|
+
"noMove",
|
|
4138
|
+
"noResize"
|
|
4139
|
+
];
|
|
4140
|
+
/**
|
|
4141
|
+
* Serialize an object-lock element (`a:spLocks` / `a:picLocks` / `a:graphicFrameLocks`).
|
|
4142
|
+
* Only flags set to `true` AND valid for this element type are emitted; a flag set on an
|
|
4143
|
+
* unsupported element type is dropped with a warning (silent coercion is a footgun).
|
|
4144
|
+
* @param tag - locking element tag, e.g. `'a:spLocks'`
|
|
4145
|
+
* @param allowed - attribute names this element type supports, in desired emit order
|
|
4146
|
+
* @param locks - merged lock flags (callers fold any hard-coded default in first)
|
|
4147
|
+
* @param objectName - for the warning message
|
|
4148
|
+
* @returns the locking element string, or `''` when no applicable flag is set
|
|
4149
|
+
*/
|
|
4150
|
+
function genXmlObjectLock(tag, allowed, locks, objectName) {
|
|
4151
|
+
if (!locks) return "";
|
|
4152
|
+
const lockMap = locks;
|
|
4153
|
+
for (const key of Object.keys(lockMap)) if (lockMap[key] && !allowed.includes(key)) console.warn(`Warning: objectLock.${key} is not supported on <${tag}> (object "${objectName ?? ""}") and was ignored.`);
|
|
4154
|
+
const attrs = allowed.filter((name) => lockMap[name] === true).map((name) => `${name}="1"`);
|
|
4155
|
+
return attrs.length > 0 ? `<${tag} ${attrs.join(" ")}/>` : "";
|
|
4156
|
+
}
|
|
3665
4157
|
function genXmlPresetGeom(shapeName, options, cx, cy) {
|
|
3666
|
-
|
|
4158
|
+
if (!VALID_SHAPE_PRESETS.has(shapeName)) throw new Error(`Invalid shape "${String(shapeName)}"! Use a value from \`pptxgen.shapes.*\` (e.g. \`pptxgen.shapes.RECTANGLE\`). PowerPoint can't render unknown preset geometries and will drop the shape during repair.`);
|
|
4159
|
+
let avLst = "";
|
|
4160
|
+
const emittedAdjNames = /* @__PURE__ */ new Set();
|
|
4161
|
+
const emitGuide = (name, fmlaVal) => {
|
|
4162
|
+
avLst += `<a:gd name="${name}" fmla="val ${fmlaVal}"/>`;
|
|
4163
|
+
emittedAdjNames.add(name);
|
|
4164
|
+
};
|
|
3667
4165
|
if (options.rectRadius) {
|
|
3668
4166
|
const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
|
|
3669
4167
|
if (RECT_RADIUS_ADJ1_SHAPES.has(shapeName)) {
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
} else
|
|
4168
|
+
emitGuide("adj1", adjVal);
|
|
4169
|
+
emitGuide("adj2", 0);
|
|
4170
|
+
} else emitGuide("adj", adjVal);
|
|
3673
4171
|
} else if (options.angleRange) {
|
|
3674
4172
|
for (let i = 0; i < 2; i++) {
|
|
3675
4173
|
const angle = options.angleRange[i];
|
|
3676
|
-
|
|
4174
|
+
emitGuide(`adj${i + 1}`, convertRotationDegrees(angle));
|
|
3677
4175
|
}
|
|
3678
|
-
if (options.arcThicknessRatio)
|
|
4176
|
+
if (options.arcThicknessRatio) emitGuide("adj3", Math.round(options.arcThicknessRatio * 5e4));
|
|
3679
4177
|
}
|
|
3680
|
-
|
|
3681
|
-
|
|
4178
|
+
if (options.shapeAdjust) (Array.isArray(options.shapeAdjust) ? options.shapeAdjust : [options.shapeAdjust]).forEach((adj) => {
|
|
4179
|
+
if (!adj || typeof adj.name !== "string" || adj.name.length === 0 || typeof adj.value !== "number" || !isFinite(adj.value)) {
|
|
4180
|
+
console.warn(`Warning: shapeAdjust entry ${JSON.stringify(adj)} is invalid (needs { name:string, value:number }) and was ignored.`);
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
if (emittedAdjNames.has(adj.name)) {
|
|
4184
|
+
console.warn(`Warning: shapeAdjust "${adj.name}" was ignored because rectRadius/angleRange already set that handle.`);
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
emitGuide(adj.name, Math.round(adj.value * 1e5));
|
|
4188
|
+
});
|
|
4189
|
+
return `<a:prstGeom prst="${shapeName}"><a:avLst>${avLst}</a:avLst></a:prstGeom>`;
|
|
3682
4190
|
}
|
|
3683
4191
|
/**
|
|
3684
4192
|
* Emit an `<a:custGeom>` for a freeform path built from `points`.
|
|
@@ -3731,6 +4239,45 @@ function genXmlCustGeom(points, cx, cy, layout) {
|
|
|
3731
4239
|
}
|
|
3732
4240
|
const PLACEHOLDER_TYPE_MAP = PLACEHOLDER_TYPES;
|
|
3733
4241
|
/**
|
|
4242
|
+
* Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
|
|
4243
|
+
* Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
|
|
4244
|
+
* outer edges render with the same border as its origin cell (Issue #680).
|
|
4245
|
+
* @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
|
|
4246
|
+
* @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
|
|
4247
|
+
*/
|
|
4248
|
+
function genTableCellBorderXml(cellBorder) {
|
|
4249
|
+
let strXml = "";
|
|
4250
|
+
[
|
|
4251
|
+
{
|
|
4252
|
+
idx: 3,
|
|
4253
|
+
name: "lnL"
|
|
4254
|
+
},
|
|
4255
|
+
{
|
|
4256
|
+
idx: 1,
|
|
4257
|
+
name: "lnR"
|
|
4258
|
+
},
|
|
4259
|
+
{
|
|
4260
|
+
idx: 0,
|
|
4261
|
+
name: "lnT"
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
idx: 2,
|
|
4265
|
+
name: "lnB"
|
|
4266
|
+
}
|
|
4267
|
+
].forEach((obj) => {
|
|
4268
|
+
const border = cellBorder[obj.idx];
|
|
4269
|
+
if (!border) return;
|
|
4270
|
+
const cap = createLineCap(border.cap);
|
|
4271
|
+
if (border.type !== "none") {
|
|
4272
|
+
strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="${cap}" cmpd="sng" algn="ctr">`;
|
|
4273
|
+
strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
|
|
4274
|
+
strXml += `<a:prstDash val="${border.type === "dash" ? "sysDash" : "solid"}"/><a:round/><a:headEnd type="none" w="med" len="med"/><a:tailEnd type="none" w="med" len="med"/>`;
|
|
4275
|
+
strXml += `</a:${obj.name}>`;
|
|
4276
|
+
} else strXml += `<a:${obj.name} w="0" cap="${cap}" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
|
|
4277
|
+
});
|
|
4278
|
+
return strXml;
|
|
4279
|
+
}
|
|
4280
|
+
/**
|
|
3734
4281
|
* Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
|
|
3735
4282
|
* @param {PresSlideInternal|SlideLayoutInternal} slideObject - slide object created within createSlideObject
|
|
3736
4283
|
* @return {string} XML string with <p:cSld> as the root
|
|
@@ -3789,7 +4336,10 @@ function slideObjectToXml(slide) {
|
|
|
3789
4336
|
intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1;
|
|
3790
4337
|
});
|
|
3791
4338
|
strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
|
|
3792
|
-
strXml +=
|
|
4339
|
+
strXml += `<p:cNvGraphicFramePr>${genXmlObjectLock("a:graphicFrameLocks", GRAPHIC_FRAME_LOCK_ATTRS, {
|
|
4340
|
+
noGrp: true,
|
|
4341
|
+
...slideItemObj.options.objectLock
|
|
4342
|
+
}, slideItemObj.options.objectName)}</p:cNvGraphicFramePr> <p:nvPr><p:extLst><p:ext uri="{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"><p14:modId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1579011935"/></p:ext></p:extLst></p:nvPr></p:nvGraphicFramePr>`;
|
|
3793
4343
|
strXml += `<p:xfrm><a:off x="${x || (x === 0 ? 0 : EMU)}" y="${y || (y === 0 ? 0 : EMU)}"/><a:ext cx="${cx || (cx === 0 ? 0 : EMU)}" cy="${cy || EMU}"/></p:xfrm>`;
|
|
3794
4344
|
{
|
|
3795
4345
|
const tblPrAttrs = (objTabOpts.hasHeader ? " firstRow=\"1\"" : "") + (objTabOpts.hasFooter ? " lastRow=\"1\"" : "") + (objTabOpts.hasBandedRows ? " bandRow=\"1\"" : "") + (objTabOpts.hasBandedColumns ? " bandCol=\"1\"" : "") + (objTabOpts.hasFirstColumn ? " firstCol=\"1\"" : "") + (objTabOpts.hasLastColumn ? " lastCol=\"1\"" : "");
|
|
@@ -3821,7 +4371,8 @@ function slideObjectToXml(slide) {
|
|
|
3821
4371
|
return {
|
|
3822
4372
|
_type: "tablecell",
|
|
3823
4373
|
options: { rowspan },
|
|
3824
|
-
_hmerge: true
|
|
4374
|
+
_hmerge: true,
|
|
4375
|
+
_spanOrigin: cell
|
|
3825
4376
|
};
|
|
3826
4377
|
});
|
|
3827
4378
|
cells.splice(cIdx + 1, 0, ...vMergeCells);
|
|
@@ -3837,12 +4388,14 @@ function slideObjectToXml(slide) {
|
|
|
3837
4388
|
const colspan = cell.options?.colspan;
|
|
3838
4389
|
const _hmerge = cell._hmerge;
|
|
3839
4390
|
if (rowspan && rowspan > 1) {
|
|
4391
|
+
const _spanOrigin = cell._spanOrigin || cell;
|
|
3840
4392
|
const hMergeCell = {
|
|
3841
4393
|
_type: "tablecell",
|
|
3842
4394
|
options: { colspan },
|
|
3843
4395
|
_rowContinue: rowspan - 1,
|
|
3844
4396
|
_vmerge: true,
|
|
3845
|
-
_hmerge
|
|
4397
|
+
_hmerge,
|
|
4398
|
+
_spanOrigin
|
|
3846
4399
|
};
|
|
3847
4400
|
nextRow.splice(cIdx, 0, hMergeCell);
|
|
3848
4401
|
}
|
|
@@ -3852,7 +4405,7 @@ function slideObjectToXml(slide) {
|
|
|
3852
4405
|
let intRowH = 0;
|
|
3853
4406
|
if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]));
|
|
3854
4407
|
else if (objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH))) intRowH = inch2Emu(Number(objTabOpts.rowH));
|
|
3855
|
-
else if (slideItemObj.options.cy || slideItemObj.options.h) intRowH = Math.round((slideItemObj.options.h ?
|
|
4408
|
+
else if (slideItemObj.options.cy || slideItemObj.options.h) intRowH = Math.round((slideItemObj.options.h ? cy : typeof slideItemObj.options.cy === "number" ? slideItemObj.options.cy : 1) / arrTabRows.length);
|
|
3856
4409
|
strXml += `<a:tr h="${intRowH}">`;
|
|
3857
4410
|
cells.forEach((cellObj) => {
|
|
3858
4411
|
const cell = cellObj;
|
|
@@ -3865,7 +4418,17 @@ function slideObjectToXml(slide) {
|
|
|
3865
4418
|
let cellSpanAttrStr = Object.entries(cellSpanAttrs).filter(([, v]) => !!v).map(([k, v]) => `${String(k)}="${String(v)}"`).join(" ");
|
|
3866
4419
|
if (cellSpanAttrStr) cellSpanAttrStr = " " + cellSpanAttrStr;
|
|
3867
4420
|
if (cell._hmerge || cell._vmerge) {
|
|
3868
|
-
|
|
4421
|
+
const origin = cell._spanOrigin;
|
|
4422
|
+
let spanPrXml = "";
|
|
4423
|
+
if (origin) {
|
|
4424
|
+
const originOpts = origin.options || {};
|
|
4425
|
+
const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
|
|
4426
|
+
if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
|
|
4427
|
+
let spanFill = origin._optImp?.fill?.color ? origin._optImp.fill.color : origin._optImp?.fill && typeof origin._optImp.fill === "string" ? origin._optImp.fill : "";
|
|
4428
|
+
spanFill = spanFill || originOpts.fill ? originOpts.fill : "";
|
|
4429
|
+
if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
|
|
4430
|
+
}
|
|
4431
|
+
strXml += `<a:tc${cellSpanAttrStr}><a:tcPr>${spanPrXml}</a:tcPr></a:tc>`;
|
|
3869
4432
|
return;
|
|
3870
4433
|
}
|
|
3871
4434
|
const cellOpts = cell.options || {};
|
|
@@ -3909,32 +4472,7 @@ function slideObjectToXml(slide) {
|
|
|
3909
4472
|
else cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(cellMargin[2])}"`;
|
|
3910
4473
|
strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`;
|
|
3911
4474
|
const cellBorder = Array.isArray(cellOpts.border) ? cellOpts.border : null;
|
|
3912
|
-
if (cellBorder)
|
|
3913
|
-
{
|
|
3914
|
-
idx: 3,
|
|
3915
|
-
name: "lnL"
|
|
3916
|
-
},
|
|
3917
|
-
{
|
|
3918
|
-
idx: 1,
|
|
3919
|
-
name: "lnR"
|
|
3920
|
-
},
|
|
3921
|
-
{
|
|
3922
|
-
idx: 0,
|
|
3923
|
-
name: "lnT"
|
|
3924
|
-
},
|
|
3925
|
-
{
|
|
3926
|
-
idx: 2,
|
|
3927
|
-
name: "lnB"
|
|
3928
|
-
}
|
|
3929
|
-
].forEach((obj) => {
|
|
3930
|
-
const border = cellBorder[obj.idx];
|
|
3931
|
-
if (border.type !== "none") {
|
|
3932
|
-
strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="flat" cmpd="sng" algn="ctr">`;
|
|
3933
|
-
strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
|
|
3934
|
-
strXml += `<a:prstDash val="${border.type === "dash" ? "sysDash" : "solid"}"/><a:round/><a:headEnd type="none" w="med" len="med"/><a:tailEnd type="none" w="med" len="med"/>`;
|
|
3935
|
-
strXml += `</a:${obj.name}>`;
|
|
3936
|
-
} else strXml += `<a:${obj.name} w="0" cap="flat" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
|
|
3937
|
-
});
|
|
4475
|
+
if (cellBorder) strXml += genTableCellBorderXml(cellBorder);
|
|
3938
4476
|
strXml += cellFill;
|
|
3939
4477
|
strXml += " </a:tcPr>";
|
|
3940
4478
|
strXml += " </a:tc>";
|
|
@@ -3953,10 +4491,10 @@ function slideObjectToXml(slide) {
|
|
|
3953
4491
|
if (!slideItemObj.options.line && cy === 0) cy = EMU * .3;
|
|
3954
4492
|
if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {};
|
|
3955
4493
|
if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
|
|
3956
|
-
slideItemObj.options._bodyProp.
|
|
4494
|
+
slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[0] || 0);
|
|
3957
4495
|
slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0);
|
|
3958
4496
|
slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0);
|
|
3959
|
-
slideItemObj.options._bodyProp.
|
|
4497
|
+
slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[3] || 0);
|
|
3960
4498
|
} else if (typeof slideItemObj.options.margin === "number") {
|
|
3961
4499
|
slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin);
|
|
3962
4500
|
slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin);
|
|
@@ -3968,7 +4506,11 @@ function slideObjectToXml(slide) {
|
|
|
3968
4506
|
if (slideItemObj.options.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.options.hyperlink._rId}" tooltip="${slideItemObj.options.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.options.hyperlink.tooltip) : ""}"/>`;
|
|
3969
4507
|
if (slideItemObj.options.hyperlink?.slide) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.options.hyperlink._rId}" tooltip="${slideItemObj.options.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.options.hyperlink.tooltip) : ""}" action="ppaction://hlinksldjump"/>`;
|
|
3970
4508
|
strSlideXml += "</p:cNvPr>";
|
|
3971
|
-
|
|
4509
|
+
{
|
|
4510
|
+
const spLockXml = genXmlObjectLock("a:spLocks", SHAPE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName);
|
|
4511
|
+
strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"" : "");
|
|
4512
|
+
strSlideXml += spLockXml ? `>${spLockXml}</p:cNvSpPr>` : "/>";
|
|
4513
|
+
}
|
|
3972
4514
|
strSlideXml += `<p:nvPr>${slideItemObj._type === "placeholder" ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`;
|
|
3973
4515
|
strSlideXml += "</p:nvSpPr><p:spPr>";
|
|
3974
4516
|
strSlideXml += `<a:xfrm${locationAttr}>`;
|
|
@@ -3978,7 +4520,8 @@ function slideObjectToXml(slide) {
|
|
|
3978
4520
|
else strSlideXml += genXmlPresetGeom(slideItemObj.shape, slideItemObj.options, cx, cy);
|
|
3979
4521
|
strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : "<a:noFill/>";
|
|
3980
4522
|
if (slideItemObj.options.line) {
|
|
3981
|
-
|
|
4523
|
+
const lnAttrs = (slideItemObj.options.line.width ? ` w="${lineWidthToEmu(slideItemObj.options.line.width)}"` : "") + (slideItemObj.options.line.cap ? ` cap="${createLineCap(slideItemObj.options.line.cap)}"` : "");
|
|
4524
|
+
strSlideXml += `<a:ln${lnAttrs}>`;
|
|
3982
4525
|
if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line);
|
|
3983
4526
|
if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`;
|
|
3984
4527
|
if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`;
|
|
@@ -4004,6 +4547,24 @@ function slideObjectToXml(slide) {
|
|
|
4004
4547
|
strSlideXml += genXmlTextBody(slideItemObj);
|
|
4005
4548
|
strSlideXml += "</p:sp>";
|
|
4006
4549
|
break;
|
|
4550
|
+
case "connector":
|
|
4551
|
+
strSlideXml += "<p:cxnSp><p:nvCxnSpPr>";
|
|
4552
|
+
strSlideXml += `<p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
|
|
4553
|
+
strSlideXml += "<p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr><p:spPr>";
|
|
4554
|
+
strSlideXml += `<a:xfrm${locationAttr}><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
|
|
4555
|
+
strSlideXml += `<a:prstGeom prst="${slideItemObj.shape}"><a:avLst/></a:prstGeom>`;
|
|
4556
|
+
{
|
|
4557
|
+
const ln = slideItemObj.options.line || {};
|
|
4558
|
+
const lnAttrs = (ln.width ? ` w="${lineWidthToEmu(ln.width)}"` : "") + (ln.cap ? ` cap="${createLineCap(ln.cap)}"` : "");
|
|
4559
|
+
strSlideXml += `<a:ln${lnAttrs}>`;
|
|
4560
|
+
if (ln.color) strSlideXml += genXmlColorSelection(ln);
|
|
4561
|
+
if (ln.dashType) strSlideXml += `<a:prstDash val="${ln.dashType}"/>`;
|
|
4562
|
+
if (ln.beginArrowType) strSlideXml += `<a:headEnd type="${ln.beginArrowType}"/>`;
|
|
4563
|
+
if (ln.endArrowType) strSlideXml += `<a:tailEnd type="${ln.endArrowType}"/>`;
|
|
4564
|
+
strSlideXml += "</a:ln>";
|
|
4565
|
+
}
|
|
4566
|
+
strSlideXml += "</p:spPr></p:cxnSp>";
|
|
4567
|
+
break;
|
|
4007
4568
|
case "image":
|
|
4008
4569
|
strSlideXml += "<p:pic>";
|
|
4009
4570
|
strSlideXml += " <p:nvPicPr>";
|
|
@@ -4011,7 +4572,10 @@ function slideObjectToXml(slide) {
|
|
|
4011
4572
|
if (slideItemObj.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}"/>`;
|
|
4012
4573
|
if (slideItemObj.hyperlink?.slide) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}" action="ppaction://hlinksldjump"/>`;
|
|
4013
4574
|
strSlideXml += " </p:cNvPr>";
|
|
4014
|
-
strSlideXml +=
|
|
4575
|
+
strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
|
|
4576
|
+
noChangeAspect: true,
|
|
4577
|
+
...slideItemObj.options.objectLock
|
|
4578
|
+
}, slideItemObj.options.objectName)}</p:cNvPicPr>`;
|
|
4015
4579
|
strSlideXml += " <p:nvPr>" + genXmlPlaceholder(placeholderObj) + "</p:nvPr>";
|
|
4016
4580
|
strSlideXml += " </p:nvPicPr>";
|
|
4017
4581
|
strSlideXml += "<p:blipFill>";
|
|
@@ -4044,7 +4608,7 @@ function slideObjectToXml(slide) {
|
|
|
4044
4608
|
const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
|
|
4045
4609
|
const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
|
|
4046
4610
|
if (natural) cropSize = natural;
|
|
4047
|
-
else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) to enable an aspect-correct crop.`);
|
|
4611
|
+
else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) or an SVG with width/height or a viewBox to enable an aspect-correct crop.`);
|
|
4048
4612
|
}
|
|
4049
4613
|
strSlideXml += ImageSizingXml[sizing.type](cropSize, {
|
|
4050
4614
|
w: boxW,
|
|
@@ -4063,6 +4627,16 @@ function slideObjectToXml(slide) {
|
|
|
4063
4627
|
strSlideXml += " </a:xfrm>";
|
|
4064
4628
|
if (slideItemObj.options.points) strSlideXml += " " + genXmlCustGeom(slideItemObj.options.points, imgWidth, imgHeight, slide._presLayout);
|
|
4065
4629
|
else strSlideXml += " " + genXmlPresetGeom(slideItemObj.options.shape ?? (rounding ? "ellipse" : "rect"), slideItemObj.options, imgWidth, imgHeight);
|
|
4630
|
+
if (slideItemObj.options.line) {
|
|
4631
|
+
const imgLine = slideItemObj.options.line;
|
|
4632
|
+
const lnAttrs = (imgLine.width ? ` w="${lineWidthToEmu(imgLine.width)}"` : "") + (imgLine.cap ? ` cap="${createLineCap(imgLine.cap)}"` : "");
|
|
4633
|
+
strSlideXml += `<a:ln${lnAttrs}>`;
|
|
4634
|
+
if (imgLine.color) strSlideXml += genXmlColorSelection(imgLine);
|
|
4635
|
+
if (imgLine.dashType) strSlideXml += `<a:prstDash val="${imgLine.dashType}"/>`;
|
|
4636
|
+
if (imgLine.beginArrowType) strSlideXml += `<a:headEnd type="${imgLine.beginArrowType}"/>`;
|
|
4637
|
+
if (imgLine.endArrowType) strSlideXml += `<a:tailEnd type="${imgLine.endArrowType}"/>`;
|
|
4638
|
+
strSlideXml += "</a:ln>";
|
|
4639
|
+
}
|
|
4066
4640
|
if (slideItemObj.options.shadow && slideItemObj.options.shadow.type !== "none") {
|
|
4067
4641
|
const sh = slideItemObj.options.shadow;
|
|
4068
4642
|
const shadowType = sh.type || "outer";
|
|
@@ -4086,7 +4660,7 @@ function slideObjectToXml(slide) {
|
|
|
4086
4660
|
strSlideXml += "<p:pic>";
|
|
4087
4661
|
strSlideXml += " <p:nvPicPr>";
|
|
4088
4662
|
strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
|
|
4089
|
-
strSlideXml +=
|
|
4663
|
+
strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName)}</p:cNvPicPr>`;
|
|
4090
4664
|
strSlideXml += " <p:nvPr>";
|
|
4091
4665
|
strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
|
|
4092
4666
|
strSlideXml += " </p:nvPr>";
|
|
@@ -4101,7 +4675,10 @@ function slideObjectToXml(slide) {
|
|
|
4101
4675
|
strSlideXml += "<p:pic>";
|
|
4102
4676
|
strSlideXml += " <p:nvPicPr>";
|
|
4103
4677
|
strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"><a:hlinkClick r:id="" action="ppaction://media"/></p:cNvPr>`;
|
|
4104
|
-
strSlideXml +=
|
|
4678
|
+
strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
|
|
4679
|
+
noChangeAspect: true,
|
|
4680
|
+
...slideItemObj.options.objectLock
|
|
4681
|
+
}, slideItemObj.options.objectName)}</p:cNvPicPr>`;
|
|
4105
4682
|
strSlideXml += " <p:nvPr>";
|
|
4106
4683
|
strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
|
|
4107
4684
|
strSlideXml += " <p:extLst>";
|
|
@@ -4165,7 +4742,7 @@ function slideObjectToXml(slide) {
|
|
|
4165
4742
|
strSlideXml += "/>";
|
|
4166
4743
|
strSlideXml += " <a:lstStyle><a:lvl1pPr>";
|
|
4167
4744
|
if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
|
|
4168
|
-
strSlideXml += `<a:defRPr sz="${
|
|
4745
|
+
strSlideXml += `<a:defRPr sz="${clampFontSizeSz(slide._slideNumberProps.fontSize || 12)}">`;
|
|
4169
4746
|
if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color);
|
|
4170
4747
|
if (slide._slideNumberProps.fontFace) strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>`;
|
|
4171
4748
|
strSlideXml += "</a:defRPr>";
|
|
@@ -4254,7 +4831,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
|
|
|
4254
4831
|
paragraphPropXml += "";
|
|
4255
4832
|
break;
|
|
4256
4833
|
}
|
|
4257
|
-
if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${
|
|
4834
|
+
if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${clampLineSpacingPts(textObj.options.lineSpacing)}"/></a:lnSpc>`;
|
|
4258
4835
|
else if (textObj.options.lineSpacingMultiple) strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 1e5)}"/></a:lnSpc>`;
|
|
4259
4836
|
if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`;
|
|
4260
4837
|
if (textObj.options.paraSpaceBefore && !isNaN(Number(textObj.options.paraSpaceBefore)) && textObj.options.paraSpaceBefore > 0) strXmlParaSpc += `<a:spcBef><a:spcPts val="${Math.round(textObj.options.paraSpaceBefore * 100)}"/></a:spcBef>`;
|
|
@@ -4262,9 +4839,17 @@ function genXmlParagraphProperties(textObj, isDefault) {
|
|
|
4262
4839
|
if (typeof textObj.options.bullet === "object") {
|
|
4263
4840
|
if (textObj?.options?.bullet?.indent) bulletMarL = valToPts(textObj.options.bullet.indent);
|
|
4264
4841
|
if (textObj.options.bullet.color) strXmlBulletColor = `<a:buClr>${createColorElement(textObj.options.bullet.color)}</a:buClr>`;
|
|
4842
|
+
let bulletSizePct = 1e5;
|
|
4843
|
+
if (textObj.options.bullet.size !== void 0) {
|
|
4844
|
+
const bulletSize = Number(textObj.options.bullet.size);
|
|
4845
|
+
if (isNaN(bulletSize) || bulletSize < 25 || bulletSize > 400) console.warn("Warning: `bullet.size` must be a percentage between 25 and 400!");
|
|
4846
|
+
else bulletSizePct = Math.round(bulletSize * 1e3);
|
|
4847
|
+
}
|
|
4848
|
+
const strXmlBulletSize = `<a:buSzPct val="${bulletSizePct}"/>`;
|
|
4849
|
+
const strXmlBulletFont = textObj.options.bullet.fontFace ? `<a:buFont typeface="${encodeXmlEntities(textObj.options.bullet.fontFace)}"/>` : "";
|
|
4265
4850
|
if (textObj.options.bullet.type && textObj.options.bullet.type.toString().toLowerCase() === "number") {
|
|
4266
4851
|
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
|
|
4267
|
-
strXmlBullet =
|
|
4852
|
+
strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont || "<a:buFont typeface=\"+mj-lt\"/>"}<a:buAutoNum type="${textObj.options.bullet.style || "arabicPeriod"}" startAt="${textObj.options.bullet.numberStartAt || textObj.options.bullet.startAt || "1"}"/>`;
|
|
4268
4853
|
} else if (textObj.options.bullet.characterCode) {
|
|
4269
4854
|
let bulletCode = `&#x${textObj.options.bullet.characterCode};`;
|
|
4270
4855
|
if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.characterCode)) {
|
|
@@ -4272,7 +4857,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
|
|
|
4272
4857
|
bulletCode = "•";
|
|
4273
4858
|
}
|
|
4274
4859
|
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
|
|
4275
|
-
strXmlBullet = "<a:
|
|
4860
|
+
strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
|
|
4276
4861
|
} else if (textObj.options.bullet.code) {
|
|
4277
4862
|
let bulletCode = `&#x${textObj.options.bullet.code};`;
|
|
4278
4863
|
if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.code)) {
|
|
@@ -4280,10 +4865,10 @@ function genXmlParagraphProperties(textObj, isDefault) {
|
|
|
4280
4865
|
bulletCode = "•";
|
|
4281
4866
|
}
|
|
4282
4867
|
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
|
|
4283
|
-
strXmlBullet = "<a:
|
|
4868
|
+
strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
|
|
4284
4869
|
} else {
|
|
4285
4870
|
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
|
|
4286
|
-
strXmlBullet =
|
|
4871
|
+
strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont}<a:buChar char="•"/>`;
|
|
4287
4872
|
}
|
|
4288
4873
|
} else if (textObj.options.bullet) {
|
|
4289
4874
|
paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
|
|
@@ -4308,7 +4893,7 @@ function genXmlTextRunProperties(opts, isDefault) {
|
|
|
4308
4893
|
let runProps = "";
|
|
4309
4894
|
const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
|
|
4310
4895
|
runProps += "<" + runPropsTag + " lang=\"" + (opts.lang ? opts.lang : "en-US") + "\"" + (opts.lang ? " altLang=\"en-US\"" : "");
|
|
4311
|
-
runProps += opts.fontSize ? ` sz="${
|
|
4896
|
+
runProps += opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "";
|
|
4312
4897
|
runProps += opts?.bold ? ` b="${opts.bold ? "1" : "0"}"` : "";
|
|
4313
4898
|
runProps += opts?.italic ? ` i="${opts.italic ? "1" : "0"}"` : "";
|
|
4314
4899
|
runProps += opts?.strike ? ` strike="${typeof opts.strike === "string" ? opts.strike : "sngStrike"}"` : "";
|
|
@@ -4319,17 +4904,23 @@ function genXmlTextRunProperties(opts, isDefault) {
|
|
|
4319
4904
|
if (opts.baseline) runProps += ` baseline="${Math.round(opts.baseline * 50)}"`;
|
|
4320
4905
|
else if (opts.subscript) runProps += " baseline=\"-40000\"";
|
|
4321
4906
|
else if (opts.superscript) runProps += " baseline=\"30000\"";
|
|
4322
|
-
runProps += opts.charSpacing ? ` spc="${
|
|
4907
|
+
runProps += opts.charSpacing ? ` spc="${clampCharSpacingSpc(opts.charSpacing)}" kern="0"` : "";
|
|
4323
4908
|
runProps += " dirty=\"0\">";
|
|
4324
|
-
|
|
4325
|
-
|
|
4909
|
+
const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
|
|
4910
|
+
if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
|
|
4911
|
+
if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${lineWidthToEmu(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
|
|
4326
4912
|
if (opts.color) runProps += genXmlColorSelection({
|
|
4327
4913
|
color: opts.color,
|
|
4328
4914
|
transparency: opts.transparency
|
|
4329
4915
|
});
|
|
4916
|
+
if (opts.glow || hasShadow) {
|
|
4917
|
+
runProps += "<a:effectLst>";
|
|
4918
|
+
if (opts.glow) runProps += createGlowElement(opts.glow, DEF_TEXT_GLOW);
|
|
4919
|
+
if (hasShadow) runProps += createShadowElement$1(opts.shadow, DEF_TEXT_SHADOW);
|
|
4920
|
+
runProps += "</a:effectLst>";
|
|
4921
|
+
}
|
|
4330
4922
|
if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`;
|
|
4331
4923
|
if (typeof opts.underline === "object" && opts.underline.color) runProps += `<a:uFill>${genXmlColorSelection(opts.underline.color)}</a:uFill>`;
|
|
4332
|
-
if (opts.glow) runProps += `<a:effectLst>${createGlowElement(opts.glow, DEF_TEXT_GLOW)}</a:effectLst>`;
|
|
4333
4924
|
if (opts.fontFace) runProps += `<a:latin typeface="${opts.fontFace}" pitchFamily="34" charset="0"/><a:ea typeface="${opts.fontFace}" pitchFamily="34" charset="-122"/><a:cs typeface="${opts.fontFace}" pitchFamily="34" charset="-120"/>`;
|
|
4334
4925
|
}
|
|
4335
4926
|
if (opts.hyperlink) {
|
|
@@ -4359,6 +4950,28 @@ function genXmlTextRun(textObj) {
|
|
|
4359
4950
|
return `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(String(textObj.text))}</a:t></a:r>`;
|
|
4360
4951
|
}
|
|
4361
4952
|
/**
|
|
4953
|
+
* Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
|
|
4954
|
+
* @param {TextFitShrinkProps} fit - shrink fit options
|
|
4955
|
+
* @return {string} XML string (`<a:normAutofit .../>`)
|
|
4956
|
+
* @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
|
|
4957
|
+
*/
|
|
4958
|
+
function genXmlNormAutofit(fit) {
|
|
4959
|
+
let attrs = "";
|
|
4960
|
+
const pct = (val, name) => {
|
|
4961
|
+
if (val === void 0 || val === null) return null;
|
|
4962
|
+
if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
|
|
4963
|
+
console.warn(`Warning: fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
|
|
4964
|
+
return null;
|
|
4965
|
+
}
|
|
4966
|
+
return Math.round(val * 1e3);
|
|
4967
|
+
};
|
|
4968
|
+
const fontScale = pct(fit.fontScale, "fontScale");
|
|
4969
|
+
if (fontScale !== null) attrs += ` fontScale="${fontScale}"`;
|
|
4970
|
+
const lnSpcReduction = pct(fit.lnSpcReduction, "lnSpcReduction");
|
|
4971
|
+
if (lnSpcReduction !== null) attrs += ` lnSpcReduction="${lnSpcReduction}"`;
|
|
4972
|
+
return `<a:normAutofit${attrs}/>`;
|
|
4973
|
+
}
|
|
4974
|
+
/**
|
|
4362
4975
|
* Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
|
|
4363
4976
|
* @param {ISlideObject | TableCell} slideObject - various options
|
|
4364
4977
|
* @return {string} XML string
|
|
@@ -4371,6 +4984,8 @@ function genXmlBodyProperties(slideObject) {
|
|
|
4371
4984
|
if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`;
|
|
4372
4985
|
if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`;
|
|
4373
4986
|
if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`;
|
|
4987
|
+
if (slideObject.options._bodyProp.numCol) bodyProperties += ` numCol="${slideObject.options._bodyProp.numCol}"`;
|
|
4988
|
+
if (slideObject.options._bodyProp.spcCol) bodyProperties += ` spcCol="${slideObject.options._bodyProp.spcCol}"`;
|
|
4374
4989
|
bodyProperties += " rtlCol=\"0\"";
|
|
4375
4990
|
if (slideObject.options._bodyProp.anchor) bodyProperties += " anchor=\"" + slideObject.options._bodyProp.anchor + "\"";
|
|
4376
4991
|
if (slideObject.options._bodyProp.vert) bodyProperties += " vert=\"" + slideObject.options._bodyProp.vert + "\"";
|
|
@@ -4381,9 +4996,11 @@ function genXmlBodyProperties(slideObject) {
|
|
|
4381
4996
|
* @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
|
|
4382
4997
|
*/
|
|
4383
4998
|
if (slideObject.options.fit) {
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
else if (
|
|
4999
|
+
const fit = slideObject.options.fit;
|
|
5000
|
+
if (fit === "none") bodyProperties += "";
|
|
5001
|
+
else if (fit === "shrink") bodyProperties += "<a:normAutofit/>";
|
|
5002
|
+
else if (fit === "resize") bodyProperties += "<a:spAutoFit/>";
|
|
5003
|
+
else if (typeof fit === "object" && fit.type === "shrink") bodyProperties += genXmlNormAutofit(fit);
|
|
4387
5004
|
}
|
|
4388
5005
|
if (slideObject.options.shrinkText) bodyProperties += "<a:normAutofit/>";
|
|
4389
5006
|
bodyProperties += slideObject.options._bodyProp.autoFit ? "<a:spAutoFit/>" : "";
|
|
@@ -4519,13 +5136,13 @@ function genXmlTextBody(slideObj) {
|
|
|
4519
5136
|
}
|
|
4520
5137
|
});
|
|
4521
5138
|
if (slideObj._type === "tablecell" && (opts.fontSize || opts.fontFace)) if (opts.fontFace) {
|
|
4522
|
-
strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${
|
|
5139
|
+
strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\">";
|
|
4523
5140
|
strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`;
|
|
4524
5141
|
strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`;
|
|
4525
5142
|
strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`;
|
|
4526
5143
|
strSlideXml += "</a:endParaRPr>";
|
|
4527
|
-
} else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${
|
|
4528
|
-
else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${
|
|
5144
|
+
} else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
|
|
5145
|
+
else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
|
|
4529
5146
|
else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}" dirty="0"/>`;
|
|
4530
5147
|
strSlideXml += "</a:p>";
|
|
4531
5148
|
});
|
|
@@ -4866,11 +5483,51 @@ function getLayoutIdxForSlide(slides, slideLayouts, slideNumber) {
|
|
|
4866
5483
|
return 1;
|
|
4867
5484
|
}
|
|
4868
5485
|
/**
|
|
5486
|
+
* Theme `<a:clrScheme>` slots in OOXML document order, with their default Office color child.
|
|
5487
|
+
* `dk1`/`lt1` default to `sysClr` (windowText/window); the rest are `srgbClr`. A user override
|
|
5488
|
+
* for any slot is emitted as `<a:srgbClr>` (see `buildThemeClrScheme`).
|
|
5489
|
+
*/
|
|
5490
|
+
const THEME_CLR_SCHEME_DEFAULTS = [
|
|
5491
|
+
["dk1", "<a:sysClr val=\"windowText\" lastClr=\"000000\"/>"],
|
|
5492
|
+
["lt1", "<a:sysClr val=\"window\" lastClr=\"FFFFFF\"/>"],
|
|
5493
|
+
["dk2", "<a:srgbClr val=\"44546A\"/>"],
|
|
5494
|
+
["lt2", "<a:srgbClr val=\"E7E6E6\"/>"],
|
|
5495
|
+
["accent1", "<a:srgbClr val=\"4472C4\"/>"],
|
|
5496
|
+
["accent2", "<a:srgbClr val=\"ED7D31\"/>"],
|
|
5497
|
+
["accent3", "<a:srgbClr val=\"A5A5A5\"/>"],
|
|
5498
|
+
["accent4", "<a:srgbClr val=\"FFC000\"/>"],
|
|
5499
|
+
["accent5", "<a:srgbClr val=\"5B9BD5\"/>"],
|
|
5500
|
+
["accent6", "<a:srgbClr val=\"70AD47\"/>"],
|
|
5501
|
+
["hlink", "<a:srgbClr val=\"0563C1\"/>"],
|
|
5502
|
+
["folHlink", "<a:srgbClr val=\"954F72\"/>"]
|
|
5503
|
+
];
|
|
5504
|
+
/**
|
|
5505
|
+
* Build the theme `<a:clrScheme>` block, applying any caller-supplied color overrides over the
|
|
5506
|
+
* default Office scheme. Invalid (non 6-digit-hex) overrides warn and keep the default rather
|
|
5507
|
+
* than emitting a degenerate color.
|
|
5508
|
+
* @param {ThemeColorScheme} [scheme] - per-slot hex overrides
|
|
5509
|
+
* @return {string} the `<a:clrScheme>...</a:clrScheme>` XML
|
|
5510
|
+
*/
|
|
5511
|
+
function buildThemeClrScheme(scheme) {
|
|
5512
|
+
return `<a:clrScheme name="Office">${THEME_CLR_SCHEME_DEFAULTS.map(([slot, defaultChild]) => {
|
|
5513
|
+
const override = scheme?.[slot];
|
|
5514
|
+
let child = defaultChild;
|
|
5515
|
+
if (typeof override === "string" && override.length > 0) {
|
|
5516
|
+
const hex = override.replace("#", "");
|
|
5517
|
+
if (REGEX_HEX_COLOR.test(hex)) child = `<a:srgbClr val="${hex.toUpperCase()}"/>`;
|
|
5518
|
+
else console.warn(`makeXmlTheme: colorScheme.${slot} "${override}" is not a 6-digit hex color; keeping the Office default.`);
|
|
5519
|
+
}
|
|
5520
|
+
return `<a:${slot}>${child}</a:${slot}>`;
|
|
5521
|
+
}).join("")}</a:clrScheme>`;
|
|
5522
|
+
}
|
|
5523
|
+
/**
|
|
4869
5524
|
* Creates `ppt/theme/theme1.xml`
|
|
4870
5525
|
* @return {string} XML
|
|
4871
5526
|
*/
|
|
4872
5527
|
function makeXmlTheme(pres) {
|
|
4873
|
-
|
|
5528
|
+
const majorFont = pres.theme?.headFontFace ? `<a:latin typeface="${pres.theme?.headFontFace}"/>` : "<a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/>";
|
|
5529
|
+
const minorFont = pres.theme?.bodyFontFace ? `<a:latin typeface="${pres.theme?.bodyFontFace}"/>` : "<a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/>";
|
|
5530
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements>${buildThemeClrScheme(pres.theme?.colorScheme)}<a:fontScheme name="Office"><a:majorFont>${majorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont>${minorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>`;
|
|
4874
5531
|
}
|
|
4875
5532
|
/**
|
|
4876
5533
|
* Create presentation file (`ppt/presentation.xml`)
|
|
@@ -5001,7 +5658,7 @@ function genXmlTableStyleBorders(border) {
|
|
|
5001
5658
|
xml += `<a:${side}>`;
|
|
5002
5659
|
if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
|
|
5003
5660
|
else {
|
|
5004
|
-
xml += `<a:ln w="${
|
|
5661
|
+
xml += `<a:ln w="${lineWidthToEmu(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
|
|
5005
5662
|
xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
|
|
5006
5663
|
xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
|
|
5007
5664
|
xml += "</a:ln>";
|
|
@@ -5079,7 +5736,7 @@ function makeXmlViewProps() {
|
|
|
5079
5736
|
* @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
|
|
5080
5737
|
* @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
|
|
5081
5738
|
*/
|
|
5082
|
-
const VERSION = "5.
|
|
5739
|
+
const VERSION = "5.4.0";
|
|
5083
5740
|
function standardLayoutToPresLayout(layout) {
|
|
5084
5741
|
return {
|
|
5085
5742
|
name: layout.name,
|
|
@@ -5331,6 +5988,7 @@ var PptxGenJS = class {
|
|
|
5331
5988
|
this._tableStyles = [];
|
|
5332
5989
|
this._masterSlide = {
|
|
5333
5990
|
addChart: null,
|
|
5991
|
+
addConnector: null,
|
|
5334
5992
|
addImage: null,
|
|
5335
5993
|
addMedia: null,
|
|
5336
5994
|
addNotes: null,
|
|
@@ -5403,14 +6061,27 @@ var PptxGenJS = class {
|
|
|
5403
6061
|
const arrChartPromises = [];
|
|
5404
6062
|
let arrMediaPromises = [];
|
|
5405
6063
|
const zip = new JSZip();
|
|
6064
|
+
const onMediaError = props.onMediaError ?? "throw";
|
|
5406
6065
|
this._slides.forEach((slide) => {
|
|
5407
|
-
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime));
|
|
6066
|
+
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime, onMediaError));
|
|
5408
6067
|
});
|
|
5409
6068
|
this._slideLayouts.forEach((layout) => {
|
|
5410
|
-
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime));
|
|
6069
|
+
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime, onMediaError));
|
|
5411
6070
|
});
|
|
5412
|
-
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
|
|
6071
|
+
arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime, onMediaError));
|
|
5413
6072
|
return await Promise.all(arrMediaPromises).then(async () => {
|
|
6073
|
+
const canonicalMediaTargets = /* @__PURE__ */ new Map();
|
|
6074
|
+
for (const target of [
|
|
6075
|
+
...this._slides,
|
|
6076
|
+
...this._slideLayouts,
|
|
6077
|
+
this._masterSlide
|
|
6078
|
+
]) for (const rel of target._relsMedia || []) {
|
|
6079
|
+
if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
|
|
6080
|
+
const key = (rel.extn || "") + "\0" + rel.data;
|
|
6081
|
+
const canonical = canonicalMediaTargets.get(key);
|
|
6082
|
+
if (canonical) rel.Target = canonical;
|
|
6083
|
+
else canonicalMediaTargets.set(key, rel.Target);
|
|
6084
|
+
}
|
|
5414
6085
|
this._slides.forEach((slide) => {
|
|
5415
6086
|
if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
|
|
5416
6087
|
});
|
|
@@ -5463,14 +6134,18 @@ var PptxGenJS = class {
|
|
|
5463
6134
|
});
|
|
5464
6135
|
this.createChartMediaRels(this._masterSlide, zip, arrChartPromises);
|
|
5465
6136
|
return await Promise.all(arrChartPromises).then(async () => {
|
|
6137
|
+
const compression = props.compression === false ? "STORE" : "DEFLATE";
|
|
5466
6138
|
if (props.outputType === "STREAM") return await zip.generateAsync({
|
|
5467
6139
|
type: "nodebuffer",
|
|
5468
|
-
compression
|
|
6140
|
+
compression
|
|
6141
|
+
});
|
|
6142
|
+
else if (props.outputType) return await zip.generateAsync({
|
|
6143
|
+
type: props.outputType,
|
|
6144
|
+
compression
|
|
5469
6145
|
});
|
|
5470
|
-
else if (props.outputType) return await zip.generateAsync({ type: props.outputType });
|
|
5471
6146
|
else return await zip.generateAsync({
|
|
5472
6147
|
type: "blob",
|
|
5473
|
-
compression
|
|
6148
|
+
compression
|
|
5474
6149
|
});
|
|
5475
6150
|
});
|
|
5476
6151
|
});
|
|
@@ -5493,10 +6168,12 @@ var PptxGenJS = class {
|
|
|
5493
6168
|
*/
|
|
5494
6169
|
async write(props) {
|
|
5495
6170
|
const propsOutpType = typeof props === "object" && props?.outputType ? props.outputType : props ? props : null;
|
|
5496
|
-
const propsCompress = typeof props === "object"
|
|
6171
|
+
const propsCompress = typeof props === "object" ? props?.compression : void 0;
|
|
6172
|
+
const propsMediaError = typeof props === "object" ? props?.onMediaError : void 0;
|
|
5497
6173
|
return await this.exportPresentation({
|
|
5498
6174
|
compression: propsCompress,
|
|
5499
|
-
outputType: propsOutpType
|
|
6175
|
+
outputType: propsOutpType,
|
|
6176
|
+
onMediaError: propsMediaError
|
|
5500
6177
|
});
|
|
5501
6178
|
}
|
|
5502
6179
|
/**
|
|
@@ -5510,11 +6187,12 @@ var PptxGenJS = class {
|
|
|
5510
6187
|
console.warn("[WARNING] writeFile(string) is deprecated - pass { fileName } instead.");
|
|
5511
6188
|
props = { fileName: props };
|
|
5512
6189
|
}
|
|
5513
|
-
const { fileName: rawName = "Presentation.pptx", compression
|
|
6190
|
+
const { fileName: rawName = "Presentation.pptx", compression, onMediaError } = props;
|
|
5514
6191
|
const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
|
|
5515
6192
|
const data = await this.exportPresentation({
|
|
5516
6193
|
compression,
|
|
5517
|
-
outputType: this._runtime.writeFileOutputType
|
|
6194
|
+
outputType: this._runtime.writeFileOutputType,
|
|
6195
|
+
onMediaError
|
|
5518
6196
|
});
|
|
5519
6197
|
return await this._runtime.writeFile(fileName, data);
|
|
5520
6198
|
}
|
|
@@ -5676,4 +6354,4 @@ var PptxGenJS = class {
|
|
|
5676
6354
|
//#endregion
|
|
5677
6355
|
export { PptxGenJS as t };
|
|
5678
6356
|
|
|
5679
|
-
//# sourceMappingURL=pptxgen
|
|
6357
|
+
//# sourceMappingURL=pptxgen-S8dEuBnC.js.map
|