@shbernal/pptxgenjs 5.2.0 → 5.3.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.
@@ -1,32 +1,25 @@
1
- import { a as emuToInches, c as inchesToEmu, i as STANDARD_LAYOUTS } from "./units-DmzbVUNp.js";
2
- import { A as DEF_TEXT_GLOW, B as ONEPT, D as DEF_SHAPE_SHADOW, G as SCHEME_COLOR_NAMES, H as PIECHART_COLORS, I as LAYOUT_IDX_SERIES_BASE, K as SHAPE_TYPE, L as LETTERS, M as EMU, N as IMG_BROKEN, R as LINEH_MODIFIER, T as DEF_PRES_LAYOUT_NAME, U as PLACEHOLDER_TYPES, V as OutputType, W as REGEX_HEX_COLOR, X as ShapeType, Y as SchemeColor, _ as DEF_CELL_MARGIN_IN, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_GRIDLINE, c as AlignH, f as CHART_TYPE, g as DEF_CELL_BORDER, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_SHADOW, k as DEF_SLIDE_MARGIN_IN, l as AlignV, m as ChartType, o as AXIS_ID_VALUE_PRIMARY, q as SLDNUMFLDID, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, u as BARCHART_COLORS, w as DEF_PRES_LAYOUT, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./core-interfaces-vUc0ElZs.js";
1
+ import { a as coordToEmu, i as STANDARD_LAYOUTS, l as inchesToEmu, o as emuToInches } from "./units-BMrBTU0-.js";
2
+ import { A as DEF_TEXT_GLOW, B as ONEPT, D as DEF_SHAPE_SHADOW, G as SCHEME_COLOR_NAMES, H as PIECHART_COLORS, I as LAYOUT_IDX_SERIES_BASE, K as SHAPE_TYPE, L as LETTERS, M as EMU, N as IMG_BROKEN, R as LINEH_MODIFIER, T as DEF_PRES_LAYOUT_NAME, U as PLACEHOLDER_TYPES, V as OutputType, W as REGEX_HEX_COLOR, X as ShapeType, Y as SchemeColor, _ as DEF_CELL_MARGIN_IN, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_GRIDLINE, c as AlignH, et as VALID_SHAPE_PRESETS, f as CHART_TYPE, g as DEF_CELL_BORDER, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_SHADOW, k as DEF_SLIDE_MARGIN_IN, l as AlignV, m as ChartType, o as AXIS_ID_VALUE_PRIMARY, q as SLDNUMFLDID, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, u as BARCHART_COLORS, w as DEF_PRES_LAYOUT, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./core-interfaces-BFXQk67T.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
- * Translates any type of `x`/`y`/`w`/`h` prop to EMU
10
- * - guaranteed to return a result regardless of undefined, null, etc. (0)
11
- * - {number} - 12800 (EMU)
12
- * - {number} - 0.5 (inches)
13
- * - {string} - "75%"
14
- * @param {number|string} size - numeric ("5.5") or percentage ("90%")
15
- * @param {'X' | 'Y'} xyDir - direction
16
- * @param {PresLayout} layout - presentation layout
17
- * @returns {number} calculated size
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 (typeof size === "string" && !isNaN(Number(size))) size = Number(size);
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
- if (typeof size === "number" && size < 100) return inch2Emu(size);
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
- * @param {number|string} inches - as string or number
104
- * @returns {number} EMU value
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 Math.round(EMU * inches);
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
- const alphaHex = colorVal.slice(6, 8);
169
- innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
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 = Math.round((opts.opacity ?? 0) * 1e5);
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="${Math.round((100 - stop.alpha) * 1e3)}"/>`;
212
- if (stop.transparency) internalElements += `<a:alpha val="${Math.round((100 - stop.transparency) * 1e3)}"/>`;
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="${Math.round((100 - props.alpha) * 1e3)}"/>`;
273
- if (props.transparency) internalElements += `<a:alpha val="${Math.round((100 - props.transparency) * 1e3)}"/>`;
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":
@@ -864,6 +925,28 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
864
925
  return tableRowSlides;
865
926
  }
866
927
  /**
928
+ * Convert a computed CSS border (width string + color string) from `getComputedStyle` into a
929
+ * pptx `BorderProps`.
930
+ *
931
+ * Preserves *fractional* widths: a hairline CSS border such as `0.5px` must not be rounded to
932
+ * `0pt` and silently vanish — the table serializer (`valToPts`) emits fractional points just
933
+ * fine, so there is no reason to integer-round here (upstream gitbrent/PptxGenJS#1235). A
934
+ * computed width of `0` (or a non-finite value) yields `{ type: 'none' }` so we never emit a
935
+ * zero-width line.
936
+ * @param {string} widthStr - computed `border-<side>-width`, e.g. `"0.5px"`
937
+ * @param {string} colorStr - computed `border-<side>-color`, e.g. `"rgb(102, 102, 102)"`
938
+ * @returns {BorderProps} border props for the cell side
939
+ */
940
+ function htmlBorderToProps(widthStr, colorStr) {
941
+ const pt = Number(String(widthStr).replace("px", ""));
942
+ if (!isFinite(pt) || pt <= 0) return { type: "none" };
943
+ const arrRGB = String(colorStr).replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
944
+ return {
945
+ pt,
946
+ color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
947
+ };
948
+ }
949
+ /**
867
950
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
868
951
  * @param {TableToSlidesHost} pptx - pptxgenjs instance
869
952
  * @param {string} tabEleId - HTMLElementID of the table
@@ -1011,12 +1094,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
1011
1094
  "bottom",
1012
1095
  "left"
1013
1096
  ].forEach((val, idxb) => {
1014
- const intBorderW = Math.round(Number(window.getComputedStyle(cell).getPropertyValue("border-" + val + "-width").replace("px", "")));
1015
- const arrRGB = window.getComputedStyle(cell).getPropertyValue("border-" + val + "-color").replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
1016
- cellBorder[idxb] = {
1017
- pt: intBorderW,
1018
- color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
1019
- };
1097
+ const style = window.getComputedStyle(cell);
1098
+ cellBorder[idxb] = htmlBorderToProps(style.getPropertyValue("border-" + val + "-width"), style.getPropertyValue("border-" + val + "-color"));
1020
1099
  });
1021
1100
  cellOpts.border = cellBorder;
1022
1101
  }
@@ -1086,6 +1165,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
1086
1165
  */
1087
1166
  /** counter for included charts (used for index in their filenames) */
1088
1167
  let _chartCounter = 0;
1168
+ /** DPI PowerPoint assumes when sizing an inserted raster image (natural pixels / 96 == inches) */
1169
+ const IMAGE_NATURAL_DPI = 96;
1089
1170
  function normalizeBorderTuple(border) {
1090
1171
  return Array.isArray(border) ? border : [
1091
1172
  border,
@@ -1122,6 +1203,26 @@ function createSlideMaster(props, target) {
1122
1203
  if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
1123
1204
  }
1124
1205
  /**
1206
+ * Round and clamp an integer chart percentage/angle option into a schema-valid range.
1207
+ *
1208
+ * Several chart attributes are bounded integer types whose out-of-range values make
1209
+ * PowerPoint report the package as needing repair: `<c:overlap>` (ST_Overlap, -100..100),
1210
+ * `<c:gapWidth>`/`<c:gapDepth>` (ST_GapAmount, 0..500), `<c:holeSize>` (ST_HoleSize, 10..90)
1211
+ * and `<c:firstSliceAng>` (ST_FirstSliceAng, 0..360). Missing/non-numeric input returns
1212
+ * `undefined` so the caller can apply its own default; an out-of-range value is clamped
1213
+ * and a warning is emitted (per the library's warn-rather-than-degrade policy).
1214
+ * @param value - caller-supplied option value
1215
+ * @param min - inclusive lower bound
1216
+ * @param max - inclusive upper bound
1217
+ * @param name - option name, for the warning message
1218
+ */
1219
+ function clampChartPct(value, min, max, name) {
1220
+ if (typeof value !== "number" || isNaN(value)) return void 0;
1221
+ const clamped = Math.min(max, Math.max(min, Math.round(value)));
1222
+ if (clamped !== value) console.warn(`Warning: ${name} ${value} is outside the valid range ${min}-${max}; using ${clamped}.`);
1223
+ return clamped;
1224
+ }
1225
+ /**
1125
1226
  * Generate the chart based on input data.
1126
1227
  * OOXML Chart Spec: ISO/IEC 29500-1:2016(E)
1127
1228
  *
@@ -1293,7 +1394,13 @@ function addChartDefinition(target, type, data, opt) {
1293
1394
  "marker",
1294
1395
  "filled"
1295
1396
  ].includes(options.radarStyle || "")) options.radarStyle = "standard";
1296
- options.lineDataSymbolSize = options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize) ? options.lineDataSymbolSize : 6;
1397
+ {
1398
+ const rawSymbolSize = options.lineDataSymbolSize;
1399
+ const hasSymbolSize = rawSymbolSize != null && !isNaN(rawSymbolSize);
1400
+ const symbolSize = Math.min(72, Math.max(2, Math.round(hasSymbolSize ? rawSymbolSize : 6)));
1401
+ if (hasSymbolSize && symbolSize !== rawSymbolSize) console.warn(`Warning: lineDataSymbolSize ${rawSymbolSize} is outside the valid marker size range (integer 2-72); using ${symbolSize}.`);
1402
+ options.lineDataSymbolSize = symbolSize;
1403
+ }
1297
1404
  options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(.75);
1298
1405
  const chartLayout = options.layout;
1299
1406
  if (chartLayout) [
@@ -1343,8 +1450,11 @@ function addChartDefinition(target, type, data, opt) {
1343
1450
  options.v3DRotY = typeof options.v3DRotY === "number" && !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30;
1344
1451
  options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true;
1345
1452
  options.v3DPerspective = typeof options.v3DPerspective === "number" && !isNaN(options.v3DPerspective) && options.v3DPerspective >= 0 && options.v3DPerspective <= 240 ? options.v3DPerspective : 30;
1346
- options.barGapWidthPct = typeof options.barGapWidthPct === "number" && !isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1e3 ? options.barGapWidthPct : 150;
1347
- options.barGapDepthPct = typeof options.barGapDepthPct === "number" && !isNaN(options.barGapDepthPct) && options.barGapDepthPct >= 0 && options.barGapDepthPct <= 1e3 ? options.barGapDepthPct : 150;
1453
+ options.barGapWidthPct = clampChartPct(options.barGapWidthPct, 0, 500, "barGapWidthPct") ?? 150;
1454
+ options.barGapDepthPct = clampChartPct(options.barGapDepthPct, 0, 500, "barGapDepthPct") ?? 150;
1455
+ options.barOverlapPct = clampChartPct(options.barOverlapPct, -100, 100, "barOverlapPct");
1456
+ options.holeSize = clampChartPct(options.holeSize, 10, 90, "holeSize");
1457
+ options.firstSliceAng = clampChartPct(options.firstSliceAng, 0, 360, "firstSliceAng");
1348
1458
  options.chartColors = Array.isArray(options.chartColors) ? options.chartColors : options._type === "pie" || options._type === "doughnut" ? PIECHART_COLORS : BARCHART_COLORS;
1349
1459
  options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : void 0;
1350
1460
  options.border = options.border && typeof options.border === "object" ? options.border : void 0;
@@ -1433,16 +1543,29 @@ function addImageDefinition(target, opt) {
1433
1543
  else if (strImageData?.toLowerCase().includes("image/svg+xml")) strImgExtn = "svg";
1434
1544
  newObject._type = "image";
1435
1545
  newObject.image = strImagePath || "preencoded.png";
1546
+ let defWidth = intWidth;
1547
+ let defHeight = intHeight;
1548
+ if ((!intWidth || !intHeight) && strImageData && strImgExtn !== "svg") {
1549
+ const natural = getImageSizeFromBase64(strImageData);
1550
+ if (natural) {
1551
+ if (!intWidth && !intHeight) {
1552
+ defWidth = natural.w / IMAGE_NATURAL_DPI;
1553
+ defHeight = natural.h / IMAGE_NATURAL_DPI;
1554
+ } else if (typeof intWidth === "number" && intWidth && !intHeight) defHeight = intWidth * (natural.h / natural.w);
1555
+ else if (typeof intHeight === "number" && intHeight && !intWidth) defWidth = intHeight * (natural.w / natural.h);
1556
+ }
1557
+ }
1436
1558
  const objectOptions = {
1437
1559
  x: intPosX || 0,
1438
1560
  y: intPosY || 0,
1439
- w: intWidth || 1,
1440
- h: intHeight || 1,
1561
+ w: defWidth || 1,
1562
+ h: defHeight || 1,
1441
1563
  altText: opt.altText || "",
1442
1564
  rounding: typeof opt.rounding === "boolean" ? opt.rounding : false,
1443
1565
  shape: opt.shape,
1444
1566
  points: opt.points,
1445
1567
  rectRadius: opt.rectRadius,
1568
+ shapeAdjust: opt.shapeAdjust,
1446
1569
  sizing,
1447
1570
  placeholder: opt.placeholder,
1448
1571
  rotate: opt.rotate || 0,
@@ -1451,6 +1574,7 @@ function addImageDefinition(target, opt) {
1451
1574
  transparency: opt.transparency || 0,
1452
1575
  duotone: opt.duotone,
1453
1576
  objectName,
1577
+ objectLock: opt.objectLock,
1454
1578
  shadow: correctShadowOptions(opt.shadow)
1455
1579
  };
1456
1580
  newObject.options = objectOptions;
@@ -1480,7 +1604,10 @@ function addImageDefinition(target, opt) {
1480
1604
  });
1481
1605
  newObject.imageRid = imageRelId + 1;
1482
1606
  } else {
1483
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strImagePath && item.type === "image/" + strImgExtn && !item.isDuplicate);
1607
+ const dupeItem = target._relsMedia.find((item) => {
1608
+ if (item.isDuplicate || !item.Target || item.type !== "image/" + strImgExtn) return false;
1609
+ return strImagePath ? item.path === strImagePath : !!strImageData && item.data === strImageData;
1610
+ });
1484
1611
  target._relsMedia.push({
1485
1612
  path: strImagePath || "preencoded." + strImgExtn,
1486
1613
  type: "image/" + strImgExtn,
@@ -1538,6 +1665,7 @@ function addMediaDefinition(target, opt) {
1538
1665
  slideData.options.h = intSizeY;
1539
1666
  slideData.options.objectName = objectName;
1540
1667
  if (opt.altText) slideData.options.altText = opt.altText;
1668
+ if (opt.objectLock) slideData.options.objectLock = opt.objectLock;
1541
1669
  /**
1542
1670
  * NOTE:
1543
1671
  * - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1!
@@ -1567,7 +1695,10 @@ function addMediaDefinition(target, opt) {
1567
1695
  Target: `../media/image-${target._slideNum}-${target._relsMedia.length + 1}.png`
1568
1696
  });
1569
1697
  } else {
1570
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strPath && item.type === strType + "/" + strExtn && !item.isDuplicate);
1698
+ const dupeItem = target._relsMedia.find((item) => {
1699
+ if (item.isDuplicate || !item.Target || item.type !== strType + "/" + strExtn) return false;
1700
+ return strPath ? item.path === strPath : !!strData && item.data === strData;
1701
+ });
1571
1702
  const relId1 = getNewRelId(target);
1572
1703
  target._relsMedia.push({
1573
1704
  path: strPath || "preencoded" + strExtn,
@@ -1632,12 +1763,14 @@ function addShapeDefinition(target, shapeName, opts) {
1632
1763
  const options = typeof opts === "object" ? opts : {};
1633
1764
  options.line = options.line || { type: "none" };
1634
1765
  options.shadow = correctShadowOptions(options.shadow);
1766
+ const resolvedShapeName = typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName;
1635
1767
  const newObject = {
1636
1768
  _type: "text",
1637
- shape: (typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName) || "rect",
1769
+ shape: resolvedShapeName || "rect",
1638
1770
  options
1639
1771
  };
1640
1772
  if (!shapeName) throw new Error("Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`");
1773
+ 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
1774
  const newLineOpts = {
1642
1775
  type: options.line.type || "solid",
1643
1776
  color: options.line.color || "333333",
@@ -1734,9 +1867,8 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
1734
1867
  }
1735
1868
  arrRows.push(newRow);
1736
1869
  });
1737
- opt.x = getSmartParseNumber(opt.x || (opt.x === 0 ? 0 : EMU / 2), "X", presLayout);
1738
- opt.y = getSmartParseNumber(opt.y || (opt.y === 0 ? 0 : EMU / 2), "Y", presLayout);
1739
- if (opt.h) opt.h = getSmartParseNumber(opt.h, "Y", presLayout);
1870
+ if (opt.x === void 0 || opt.x === null) opt.x = .5;
1871
+ if (opt.y === void 0 || opt.y === null) opt.y = .5;
1740
1872
  opt.fontSize = opt.fontSize || 12;
1741
1873
  opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN;
1742
1874
  if (typeof opt.margin === "number") opt.margin = [
@@ -1805,12 +1937,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
1805
1937
  console.warn("addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.");
1806
1938
  opt.colW = void 0;
1807
1939
  }
1808
- } else if (opt.w) opt.w = getSmartParseNumber(opt.w, "X", presLayout);
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);
1940
+ } else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
1814
1941
  arrRows.forEach((row) => {
1815
1942
  row.forEach((cell, idy) => {
1816
1943
  if (typeof cell === "number" || typeof cell === "string") row[idy] = {
@@ -1838,7 +1965,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
1838
1965
  if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
1839
1966
  getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
1840
1967
  if (!getSlide(target._slideNum + idx)) slides.push(addSlide({ masterName: slideLayout?._name || void 0 }));
1841
- if (idx > 0) opt.y = inch2Emu(opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0]);
1968
+ if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
1842
1969
  {
1843
1970
  const newSlide = getSlide(target._slideNum + idx);
1844
1971
  opt.autoPage = false;
@@ -1910,6 +2037,10 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
1910
2037
  itemOpts._bodyProp.anchor = !itemOpts.placeholder ? "ctr" : void 0;
1911
2038
  itemOpts._bodyProp.vert = itemOpts.vert;
1912
2039
  itemOpts._bodyProp.wrap = typeof itemOpts.wrap === "boolean" ? itemOpts.wrap : true;
2040
+ 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)");
2041
+ else itemOpts._bodyProp.numCol = Math.round(itemOpts.columns);
2042
+ 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)");
2043
+ else itemOpts._bodyProp.spcCol = valToPts(itemOpts.columnSpacing);
1913
2044
  if (itemOpts.inset && !isNaN(Number(itemOpts.inset)) || itemOpts.inset === 0) {
1914
2045
  itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset);
1915
2046
  itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset);
@@ -2419,6 +2550,22 @@ async function createExcelWorksheet(chartObject, zip) {
2419
2550
  });
2420
2551
  }
2421
2552
  /**
2553
+ * Emit the `<a:latin>/<a:ea>/<a:cs>` font trio for a chart text run.
2554
+ *
2555
+ * In DrawingML run properties a typeface applies only to the script class of
2556
+ * its element: `<a:latin>` covers Latin/ASCII, `<a:ea>` covers East Asian, and
2557
+ * `<a:cs>` covers complex scripts. Emitting `<a:latin>` alone leaves East Asian
2558
+ * (e.g. Chinese) and complex-script glyphs falling back to the theme font, so a
2559
+ * user-specified font never takes effect for that text — most visibly on
2560
+ * PowerPoint for Mac. Stamping the same typeface onto all three classes is what
2561
+ * choosing a font in PowerPoint's UI does (upstream gitbrent/PptxGenJS#1420).
2562
+ * @param {string} typeface - font face name
2563
+ * @return {string} `<a:latin/><a:ea/><a:cs/>` XML
2564
+ */
2565
+ function createChartTextFonts(typeface) {
2566
+ return `<a:latin typeface="${typeface}"/><a:ea typeface="${typeface}"/><a:cs typeface="${typeface}"/>`;
2567
+ }
2568
+ /**
2422
2569
  * Main entry point method for create charts
2423
2570
  * @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
2424
2571
  * @param {ISlideRelChart} rel - chart object
@@ -2428,6 +2575,10 @@ function makeXmlCharts(rel) {
2428
2575
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
2429
2576
  let usesSecondaryValAxis = false;
2430
2577
  let usesSecondaryCatAxis = false;
2578
+ let primaryCatAxisValType = null;
2579
+ let secondaryCatAxisValType = null;
2580
+ let primaryCatAxisHasCategoryChart = false;
2581
+ let secondaryCatAxisHasCategoryChart = false;
2431
2582
  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
2583
  strXml += "<c:date1904 val=\"0\"/>";
2433
2584
  strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? "1" : "0"}"/>`;
@@ -2440,6 +2591,8 @@ function makeXmlCharts(rel) {
2440
2591
  fontSize: rel.opts.titleFontSize || 18,
2441
2592
  titleAlign: rel.opts.titleAlign,
2442
2593
  titleBold: rel.opts.titleBold,
2594
+ titleItalic: rel.opts.titleItalic,
2595
+ titleUnderline: rel.opts.titleUnderline,
2443
2596
  titlePos: rel.opts.titlePos,
2444
2597
  titleRotate: rel.opts.titleRotate
2445
2598
  }, rel.opts.x, rel.opts.y);
@@ -2472,18 +2625,37 @@ function makeXmlCharts(rel) {
2472
2625
  const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY;
2473
2626
  usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis;
2474
2627
  usesSecondaryCatAxis = usesSecondaryCatAxis || options.secondaryCatAxis;
2628
+ const usesValueXAxis = type.type === "scatter" || type.type === "bubble" || type.type === "bubble3D";
2629
+ if (options.secondaryCatAxis) if (usesValueXAxis) secondaryCatAxisValType = type.type;
2630
+ else secondaryCatAxisHasCategoryChart = true;
2631
+ else if (usesValueXAxis) primaryCatAxisValType = type.type;
2632
+ else primaryCatAxisHasCategoryChart = true;
2475
2633
  strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId);
2476
2634
  });
2477
2635
  else strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY);
2478
2636
  if (rel.opts._type !== "pie" && rel.opts._type !== "doughnut") {
2479
2637
  if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) throw new Error("Secondary axis must be used by one of the multiple charts");
2638
+ const comboCatAxisType = (isSecondary) => {
2639
+ const valType = isSecondary ? secondaryCatAxisValType : primaryCatAxisValType;
2640
+ const hasCategoryChart = isSecondary ? secondaryCatAxisHasCategoryChart : primaryCatAxisHasCategoryChart;
2641
+ if (!valType) return {};
2642
+ if (hasCategoryChart) {
2643
+ 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.`);
2644
+ return {};
2645
+ }
2646
+ return { _type: valType };
2647
+ };
2480
2648
  if (rel.opts.catAxes) {
2481
2649
  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
2650
  strXml += makeCatAxis({
2483
2651
  ...rel.opts,
2484
- ...rel.opts.catAxes[0]
2652
+ ...rel.opts.catAxes[0],
2653
+ ...comboCatAxisType(false)
2485
2654
  }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
2486
- } else strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
2655
+ } else strXml += makeCatAxis({
2656
+ ...rel.opts,
2657
+ ...comboCatAxisType(false)
2658
+ }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
2487
2659
  if (rel.opts.valAxes) {
2488
2660
  strXml += makeValAxis({
2489
2661
  ...rel.opts,
@@ -2500,9 +2672,13 @@ function makeXmlCharts(rel) {
2500
2672
  }
2501
2673
  if (rel.opts?.catAxes && rel.opts?.catAxes[1]) strXml += makeCatAxis({
2502
2674
  ...rel.opts,
2503
- ...rel.opts.catAxes[1]
2675
+ ...rel.opts.catAxes[1],
2676
+ ...comboCatAxisType(true)
2677
+ }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
2678
+ else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis({
2679
+ ...rel.opts,
2680
+ ...comboCatAxisType(true)
2504
2681
  }, 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
2682
  }
2507
2683
  if (rel.opts.showDataTable) {
2508
2684
  strXml += "<c:dTable>";
@@ -2557,8 +2733,7 @@ function makeXmlCharts(rel) {
2557
2733
  strXml += " <a:pPr>";
2558
2734
  strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : "<a:defRPr>";
2559
2735
  if (rel.opts.legendColor) strXml += genXmlColorSelection(rel.opts.legendColor);
2560
- if (rel.opts.legendFontFace) strXml += "<a:latin typeface=\"" + rel.opts.legendFontFace + "\"/>";
2561
- if (rel.opts.legendFontFace) strXml += "<a:cs typeface=\"" + rel.opts.legendFontFace + "\"/>";
2736
+ if (rel.opts.legendFontFace) strXml += createChartTextFonts(rel.opts.legendFontFace);
2562
2737
  strXml += " </a:defRPr>";
2563
2738
  strXml += " </a:pPr>";
2564
2739
  strXml += " <a:endParaRPr lang=\"en-US\"/>";
@@ -2596,6 +2771,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2596
2771
  let idxColLtr = 1;
2597
2772
  let optsChartData;
2598
2773
  let strXml = "";
2774
+ const valFmtCode = encodeXmlEntities(opts.valLabelFormatCode || opts.dataTableFormatCode || opts.dataLabelFormatCode || "General");
2599
2775
  switch (chartType) {
2600
2776
  case "area":
2601
2777
  case "bar":
@@ -2639,7 +2815,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2639
2815
  } 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
2816
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
2641
2817
  strXml += " </c:spPr>";
2642
- if (chartType !== "line" && chartType !== "radar") strXml += " <c:invertIfNegative val=\"0\"/>";
2818
+ if (chartType === "bar" || chartType === "bar3D") strXml += " <c:invertIfNegative val=\"0\"/>";
2643
2819
  if (chartType === "line" || chartType === "radar") {
2644
2820
  strXml += "<c:marker>";
2645
2821
  strXml += " <c:symbol val=\"" + opts.lineDataSymbol + "\"/>";
@@ -2654,6 +2830,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2654
2830
  strXml += " </c:spPr>";
2655
2831
  strXml += "</c:marker>";
2656
2832
  }
2833
+ {
2834
+ 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;
2835
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, barVaryColors);
2836
+ }
2657
2837
  if (chartType !== "radar") {
2658
2838
  const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
2659
2839
  const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
@@ -2661,12 +2841,15 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2661
2841
  const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
2662
2842
  const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
2663
2843
  strXml += "<c:dLbls>";
2844
+ if (obj.customLabels?.length) obj.customLabels.forEach((lbl, idx) => {
2845
+ if (lbl) strXml += makeCustomDLblXml(idx, lbl, opts);
2846
+ });
2664
2847
  strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
2665
2848
  if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
2666
2849
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
2667
2850
  strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
2668
2851
  strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
2669
- strXml += `<a:latin typeface="${lblFace}"/>`;
2852
+ strXml += createChartTextFonts(lblFace);
2670
2853
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
2671
2854
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
2672
2855
  strXml += "<c:showLegendKey val=\"0\"/>";
@@ -2675,29 +2858,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2675
2858
  strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
2676
2859
  strXml += "</c:dLbls>";
2677
2860
  }
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
2861
  strXml += "<c:cat>";
2702
2862
  if (opts.catLabelFormatCode) {
2703
2863
  strXml += " <c:numRef>";
@@ -2734,10 +2894,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2734
2894
  strXml += " <c:numRef>";
2735
2895
  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
2896
  strXml += " <c:numCache>";
2737
- strXml += " <c:formatCode>" + (opts.valLabelFormatCode || opts.dataTableFormatCode || "General") + "</c:formatCode>";
2897
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
2738
2898
  strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`;
2739
2899
  obj.values.forEach((value, idx) => {
2740
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
2900
+ strXml += numCachePt(idx, value);
2741
2901
  });
2742
2902
  strXml += " </c:numCache>";
2743
2903
  strXml += " </c:numRef>";
@@ -2753,7 +2913,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2753
2913
  strXml += " <a:p><a:pPr>";
2754
2914
  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
2915
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
2756
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
2916
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
2757
2917
  strXml += " </a:defRPr>";
2758
2918
  strXml += " </a:pPr></a:p>";
2759
2919
  strXml += " </c:txPr>";
@@ -2769,6 +2929,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2769
2929
  if (chartType === "bar") {
2770
2930
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
2771
2931
  strXml += ` <c:overlap val="${opts.barOverlapPct != null ? opts.barOverlapPct : (opts.barGrouping || "").includes("tacked") ? 100 : 0}"/>`;
2932
+ strXml += createSerLinesElement(opts.barSeriesLine);
2772
2933
  } else if (chartType === "bar3D") {
2773
2934
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
2774
2935
  strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`;
@@ -2820,6 +2981,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2820
2981
  strXml += "<a:effectLst/>";
2821
2982
  strXml += "</c:spPr>";
2822
2983
  strXml += "</c:marker>";
2984
+ {
2985
+ const scatterVaryColors = data.length === 1 && opts.chartColors !== BARCHART_COLORS ? opts.chartColors || BARCHART_COLORS : null;
2986
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, scatterVaryColors);
2987
+ }
2823
2988
  if (opts.showLabel) {
2824
2989
  const chartUuid = getUuid("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
2825
2990
  if (obj.labels[0] && (opts.dataLabelFormatScatter === "custom" || opts.dataLabelFormatScatter === "customXY")) {
@@ -2838,13 +3003,13 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2838
3003
  strXml += " <a:pPr>";
2839
3004
  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
3005
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
2841
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3006
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
2842
3007
  strXml += " </a:defRPr>";
2843
3008
  strXml += " </a:pPr>";
2844
3009
  strXml += " <a:r>";
2845
3010
  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
3011
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
2847
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3012
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
2848
3013
  strXml += " </a:rPr>";
2849
3014
  strXml += " <a:t>" + encodeXmlEntities(label) + "</a:t>";
2850
3015
  strXml += " </a:r>";
@@ -2924,7 +3089,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2924
3089
  strXml += " <a:pPr>";
2925
3090
  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
3091
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
2927
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3092
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
2928
3093
  strXml += " </a:defRPr>";
2929
3094
  strXml += " </a:pPr>";
2930
3095
  strXml += ` <a:endParaRPr lang="${opts.lang || "en-US"}"/>`;
@@ -2945,31 +3110,14 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2945
3110
  strXml += "</c:dLbls>";
2946
3111
  }
2947
3112
  }
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
3113
  strXml += "<c:xVal>";
2966
3114
  strXml += " <c:numRef>";
2967
3115
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
2968
3116
  strXml += " <c:numCache>";
2969
- strXml += " <c:formatCode>General</c:formatCode>";
3117
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
2970
3118
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
2971
3119
  data[0].values.forEach((value, idx) => {
2972
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
3120
+ strXml += numCachePt(idx, value);
2973
3121
  });
2974
3122
  strXml += " </c:numCache>";
2975
3123
  strXml += " </c:numRef>";
@@ -2978,10 +3126,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2978
3126
  strXml += " <c:numRef>";
2979
3127
  strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`;
2980
3128
  strXml += " <c:numCache>";
2981
- strXml += " <c:formatCode>General</c:formatCode>";
3129
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
2982
3130
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
2983
3131
  data[0].values.forEach((_value, idx) => {
2984
- if (obj.values[idx] != null) strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx]}</c:v></c:pt>`;
3132
+ strXml += numCachePt(idx, obj.values[idx]);
2985
3133
  });
2986
3134
  strXml += " </c:numCache>";
2987
3135
  strXml += " </c:numRef>";
@@ -2997,7 +3145,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2997
3145
  strXml += " <a:p><a:pPr>";
2998
3146
  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
3147
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
3000
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
3148
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
3001
3149
  strXml += " </a:defRPr>";
3002
3150
  strXml += " </a:pPr></a:p>";
3003
3151
  strXml += " </c:txPr>";
@@ -3047,10 +3195,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3047
3195
  strXml += " <c:numRef>";
3048
3196
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
3049
3197
  strXml += " <c:numCache>";
3050
- strXml += " <c:formatCode>General</c:formatCode>";
3198
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
3051
3199
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
3052
3200
  data[0].values.forEach((value, idx) => {
3053
- strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
3201
+ strXml += numCachePt(idx, value);
3054
3202
  });
3055
3203
  strXml += " </c:numCache>";
3056
3204
  strXml += " </c:numRef>";
@@ -3060,10 +3208,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3060
3208
  strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`;
3061
3209
  idxColLtr++;
3062
3210
  strXml += " <c:numCache>";
3063
- strXml += " <c:formatCode>General</c:formatCode>";
3211
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
3064
3212
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
3065
3213
  data[0].values.forEach((_value, idx) => {
3066
- strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx] || obj.values[idx] === 0 ? obj.values[idx] : ""}</c:v></c:pt>`;
3214
+ strXml += numCachePt(idx, obj.values[idx]);
3067
3215
  });
3068
3216
  strXml += " </c:numCache>";
3069
3217
  strXml += " </c:numRef>";
@@ -3076,7 +3224,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3076
3224
  strXml += " <c:formatCode>General</c:formatCode>";
3077
3225
  strXml += ` <c:ptCount val="${obj.sizes.length}"/>`;
3078
3226
  obj.sizes.forEach((value, idx) => {
3079
- strXml += `<c:pt idx="${idx}"><c:v>${value ?? ""}</c:v></c:pt>`;
3227
+ strXml += numCachePt(idx, value);
3080
3228
  });
3081
3229
  strXml += " </c:numCache>";
3082
3230
  strXml += " </c:numRef>";
@@ -3089,12 +3237,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3089
3237
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
3090
3238
  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
3239
  strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
3092
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3240
+ strXml += createChartTextFonts(opts.dataLabelFontFace || "Arial");
3093
3241
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
3094
3242
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
3095
3243
  strXml += "<c:showLegendKey val=\"0\"/>";
3096
3244
  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"/>`;
3245
+ strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="${opts.showBubbleSize ? "1" : "0"}"/>`;
3098
3246
  strXml += "<c:extLst>";
3099
3247
  strXml += " <c:ext uri=\"{CE6537A1-D6FC-4f65-9D91-7224C49458BB}\" xmlns:c15=\"http://schemas.microsoft.com/office/drawing/2012/chart\">";
3100
3248
  strXml += " <c15:showLeaderLines val=\"" + (opts.showLeaderLines ? "1" : "0") + "\"/>";
@@ -3128,33 +3276,37 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3128
3276
  else strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
3129
3277
  strXml += " </c:spPr>";
3130
3278
  optsChartData.labels[0].forEach((_label, idx) => {
3279
+ const ptStyle = optsChartData.pointStyles?.[idx];
3131
3280
  strXml += "<c:dPt>";
3132
3281
  strXml += ` <c:idx val="${idx}"/>`;
3133
3282
  strXml += " <c:bubble3D val=\"0\"/>";
3134
3283
  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 (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>`;
3284
+ strXml += `<a:solidFill>${createColorElement(ptStyle?.fill || opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
3285
+ if (ptStyle?.border) strXml += createChartBorderLine(ptStyle.border);
3286
+ 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
3287
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
3138
3288
  strXml += " </c:spPr>";
3139
3289
  strXml += "</c:dPt>";
3140
3290
  });
3141
3291
  strXml += "<c:dLbls>";
3142
3292
  optsChartData.labels[0].forEach((_label, idx) => {
3293
+ const customLbl = optsChartData.customLabels?.[idx];
3143
3294
  strXml += "<c:dLbl>";
3144
3295
  strXml += ` <c:idx val="${idx}"/>`;
3296
+ 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
3297
  strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
3146
3298
  strXml += " <c:spPr/><c:txPr>";
3147
3299
  strXml += " <a:bodyPr/><a:lstStyle/>";
3148
3300
  strXml += " <a:p><a:pPr>";
3149
3301
  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
3302
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
3151
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3303
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
3152
3304
  strXml += " </a:defRPr>";
3153
3305
  strXml += " </a:pPr></a:p>";
3154
3306
  strXml += " </c:txPr>";
3155
3307
  if (chartType === "pie" && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
3156
3308
  strXml += " <c:showLegendKey val=\"0\"/>";
3157
- strXml += " <c:showVal val=\"" + (opts.showValue ? "1" : "0") + "\"/>";
3309
+ strXml += " <c:showVal val=\"" + (customLbl ? "0" : opts.showValue ? "1" : "0") + "\"/>";
3158
3310
  strXml += " <c:showCatName val=\"" + (opts.showLabel ? "1" : "0") + "\"/>";
3159
3311
  strXml += " <c:showSerName val=\"" + (opts.showSerName ? "1" : "0") + "\"/>";
3160
3312
  strXml += " <c:showPercent val=\"" + (opts.showPercent ? "1" : "0") + "\"/>";
@@ -3169,7 +3321,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3169
3321
  strXml += " <a:pPr>";
3170
3322
  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
3323
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
3172
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
3324
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
3173
3325
  strXml += " </a:defRPr>";
3174
3326
  strXml += " </a:pPr>";
3175
3327
  strXml += " </a:p>";
@@ -3198,6 +3350,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3198
3350
  strXml += " <c:numRef>";
3199
3351
  strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`;
3200
3352
  strXml += " <c:numCache>";
3353
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
3201
3354
  strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`;
3202
3355
  optsChartData.values.forEach((value, idx) => {
3203
3356
  strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
@@ -3273,7 +3426,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
3273
3426
  strXml += " <a:pPr>";
3274
3427
  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
3428
  strXml += " <a:solidFill>" + createColorElement(opts.catAxisLabelColor || "000000") + "</a:solidFill>";
3276
- strXml += " <a:latin typeface=\"" + (opts.catAxisLabelFontFace || "Arial") + "\"/>";
3429
+ strXml += " " + createChartTextFonts(opts.catAxisLabelFontFace || "Arial");
3277
3430
  strXml += " </a:defRPr>";
3278
3431
  strXml += " </a:pPr>";
3279
3432
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -3366,7 +3519,7 @@ function makeValAxis(opts, valAxisId) {
3366
3519
  strXml += " <a:pPr>";
3367
3520
  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
3521
  strXml += " <a:solidFill>" + createColorElement(opts.valAxisLabelColor || "000000") + "</a:solidFill>";
3369
- strXml += " <a:latin typeface=\"" + (opts.valAxisLabelFontFace || "Arial") + "\"/>";
3522
+ strXml += " " + createChartTextFonts(opts.valAxisLabelFontFace || "Arial");
3370
3523
  strXml += " </a:defRPr>";
3371
3524
  strXml += " </a:pPr>";
3372
3525
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -3422,7 +3575,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
3422
3575
  strXml += " <a:pPr>";
3423
3576
  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
3577
  strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || "000000")}</a:solidFill>`;
3425
- strXml += ` <a:latin typeface="${opts.serAxisLabelFontFace || "Arial"}"/>`;
3578
+ strXml += " " + createChartTextFonts(opts.serAxisLabelFontFace || "Arial");
3426
3579
  strXml += " </a:defRPr>";
3427
3580
  strXml += " </a:pPr>";
3428
3581
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -3462,17 +3615,31 @@ function genXmlTitle(opts, chartX, chartY) {
3462
3615
  const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : "<a:bodyPr/>";
3463
3616
  const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : "";
3464
3617
  const titleBold = opts.titleBold ? 1 : 0;
3618
+ const titleItalic = opts.titleItalic ? 1 : 0;
3619
+ const titleUnderline = opts.titleUnderline ? "sng" : "none";
3465
3620
  let layout = "<c:layout/>";
3466
- if (opts.titlePos && typeof opts.titlePos.x === "number" && typeof opts.titlePos.y === "number") {
3467
- const totalX = opts.titlePos.x + chartX;
3468
- const totalY = opts.titlePos.y + chartY;
3469
- let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
3470
- if (valX >= 1) valX = valX / 10;
3471
- if (valX >= .1) valX = valX / 10;
3472
- let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
3473
- if (valY >= 1) valY = valY / 10;
3474
- if (valY >= .1) valY = valY / 10;
3475
- layout = `<c:layout><c:manualLayout><c:xMode val="edge"/><c:yMode val="edge"/><c:x val="${valX}"/><c:y val="${valY}"/></c:manualLayout></c:layout>`;
3621
+ const hasX = opts.titlePos && typeof opts.titlePos.x === "number";
3622
+ const hasY = opts.titlePos && typeof opts.titlePos.y === "number";
3623
+ if (hasX || hasY) {
3624
+ let modes = "";
3625
+ let vals = "";
3626
+ if (hasX) {
3627
+ const totalX = opts.titlePos.x + chartX;
3628
+ let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
3629
+ if (valX >= 1) valX = valX / 10;
3630
+ if (valX >= .1) valX = valX / 10;
3631
+ modes += "<c:xMode val=\"edge\"/>";
3632
+ vals += `<c:x val="${valX}"/>`;
3633
+ }
3634
+ if (hasY) {
3635
+ const totalY = opts.titlePos.y + chartY;
3636
+ let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
3637
+ if (valY >= 1) valY = valY / 10;
3638
+ if (valY >= .1) valY = valY / 10;
3639
+ modes += "<c:yMode val=\"edge\"/>";
3640
+ vals += `<c:y val="${valY}"/>`;
3641
+ }
3642
+ layout = `<c:layout><c:manualLayout>${modes}${vals}</c:manualLayout></c:layout>`;
3476
3643
  }
3477
3644
  return `<c:title>
3478
3645
  <c:tx>
@@ -3481,15 +3648,15 @@ function genXmlTitle(opts, chartX, chartY) {
3481
3648
  <a:lstStyle/>
3482
3649
  <a:p>
3483
3650
  ${align}
3484
- <a:defRPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
3651
+ <a:defRPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
3485
3652
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
3486
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
3653
+ ${createChartTextFonts(opts.fontFace || "Arial")}
3487
3654
  </a:defRPr>
3488
3655
  </a:pPr>
3489
3656
  <a:r>
3490
- <a:rPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
3657
+ <a:rPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
3491
3658
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
3492
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
3659
+ ${createChartTextFonts(opts.fontFace || "Arial")}
3493
3660
  </a:rPr>
3494
3661
  <a:t>${encodeXmlEntities(opts.title) || ""}</a:t>
3495
3662
  </a:r>
@@ -3563,11 +3730,105 @@ function createGridLineElement(glOpts) {
3563
3730
  strXml += "</c:majorGridlines>";
3564
3731
  return strXml;
3565
3732
  }
3566
- function createLineCap(lineCap) {
3567
- if (!lineCap || lineCap === "flat") return "flat";
3568
- else if (lineCap === "square") return "sq";
3569
- else if (lineCap === "round") return "rnd";
3570
- else throw new Error(`Invalid chart line cap: ${lineCap}`);
3733
+ /**
3734
+ * Build a `<c:pt>` numeric-cache data point, or '' to leave a gap.
3735
+ *
3736
+ * `<c:v>` inside a `<c:numCache>` is an `xsd:double`; emitting `NaN`, `Infinity`
3737
+ * or an empty string yields an invalid value that makes PowerPoint report the
3738
+ * package as needing repair. Null/undefined are intentional gaps and are skipped
3739
+ * silently (a sparse, idx-keyed cache is valid); other non-finite numbers are
3740
+ * skipped with a warning, per the library's "warn rather than emit a degenerate
3741
+ * result" policy.
3742
+ * @param idx - zero-based data-point index (emitted as `idx`)
3743
+ * @param value - numeric value (or null/undefined gap)
3744
+ */
3745
+ function numCachePt(idx, value) {
3746
+ if (value == null) return "";
3747
+ if (!Number.isFinite(value)) {
3748
+ console.warn(`Warning: chart value "${value}" at index ${idx} is not a finite number; data point omitted.`);
3749
+ return "";
3750
+ }
3751
+ return `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
3752
+ }
3753
+ /**
3754
+ * Build a `<c:serLines>` ("Series Lines") element for a bar chart.
3755
+ * @param opt - `true` for PowerPoint automatic styling, an {@link OptsChartGridLine}
3756
+ * to customize the line, or falsy / `{ style: 'none' }` to omit the element.
3757
+ */
3758
+ function createSerLinesElement(opt) {
3759
+ if (!opt) return "";
3760
+ if (opt === true) return "<c:serLines/>";
3761
+ if (opt.style === "none") return "";
3762
+ let strXml = "<c:serLines><c:spPr>";
3763
+ strXml += `<a:ln w="${valToPts(opt.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(opt.cap || DEF_CHART_GRIDLINE.cap)}">`;
3764
+ strXml += `<a:solidFill><a:srgbClr val="${opt.color || DEF_CHART_GRIDLINE.color}"/></a:solidFill>`;
3765
+ strXml += `<a:prstDash val="${opt.style || DEF_CHART_GRIDLINE.style}"/><a:round/>`;
3766
+ strXml += "</a:ln></c:spPr></c:serLines>";
3767
+ return strXml;
3768
+ }
3769
+ function makeCustomDLblXml(idx, text, opts) {
3770
+ const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
3771
+ const bold = opts.dataLabelFontBold ? "1" : "0";
3772
+ const italic = opts.dataLabelFontItalic ? "1" : "0";
3773
+ const color = createColorElement(opts.dataLabelColor || "000000");
3774
+ const face = opts.dataLabelFontFace || "Arial";
3775
+ const lang = opts.lang || "en-US";
3776
+ 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>`;
3777
+ }
3778
+ /**
3779
+ * Build an `<a:ln>` border element from a per-data-point `BorderProps`.
3780
+ * @param border - point border style (`type`, `color`, `pt`)
3781
+ */
3782
+ function createChartBorderLine(border) {
3783
+ if (border.type === "none") return "<a:ln><a:noFill/></a:ln>";
3784
+ const dash = border.type === "dash" ? "dash" : "solid";
3785
+ 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>`;
3786
+ }
3787
+ /**
3788
+ * Build `<c:dPt>` entries for a series in the bar/line/area/scatter loops.
3789
+ *
3790
+ * Merges two sources into a single `c:dPt` per index so we never emit a
3791
+ * duplicate `<c:idx>` (which corrupts the chart):
3792
+ * - legacy single-series color-vary fills (bar/scatter), supplied via `varyColors`
3793
+ * - per-point `pointStyles` border/fill overrides
3794
+ *
3795
+ * Must be emitted in schema position *before* `c:dLbls` (CT_*Ser order).
3796
+ * RADAR is skipped: extra per-point markup historically corrupts the chart.
3797
+ *
3798
+ * @param chartType - series chart type
3799
+ * @param obj - series data (reads `values`, `pointStyles`)
3800
+ * @param opts - chart options (fill/shadow/lineSize context)
3801
+ * @param varyColors - color array when single-series color-vary applies, else `null`
3802
+ */
3803
+ function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
3804
+ if (chartType === "radar") return "";
3805
+ const pointStyles = obj.pointStyles;
3806
+ if (!varyColors && !pointStyles?.length) return "";
3807
+ const isBar = chartType === "bar" || chartType === "bar3D";
3808
+ const isScatter = chartType === "scatter";
3809
+ let xml = "";
3810
+ obj.values.forEach((value, index) => {
3811
+ const ptStyle = pointStyles?.[index];
3812
+ const arrColors = varyColors ? value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : varyColors : null;
3813
+ const fillColor = ptStyle?.fill || (arrColors ? arrColors[index % arrColors.length] : null);
3814
+ const border = ptStyle?.border;
3815
+ if (!fillColor && !border) return;
3816
+ xml += "<c:dPt>";
3817
+ xml += `<c:idx val="${index}"/>`;
3818
+ if (isBar) xml += "<c:invertIfNegative val=\"0\"/>";
3819
+ xml += "<c:bubble3D val=\"0\"/>";
3820
+ xml += "<c:spPr>";
3821
+ if ((isBar || isScatter) && opts.lineSize === 0 && !border && !ptStyle?.fill) xml += "<a:ln><a:noFill/></a:ln>";
3822
+ else {
3823
+ if (fillColor) if (chartType === "bar3D") xml += `<a:ln><a:solidFill>${createColorElement(fillColor)}</a:solidFill></a:ln>`;
3824
+ else xml += `<a:solidFill>${createColorElement(fillColor)}</a:solidFill>`;
3825
+ if (border) xml += createChartBorderLine(border);
3826
+ }
3827
+ xml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
3828
+ xml += "</c:spPr>";
3829
+ xml += "</c:dPt>";
3830
+ });
3831
+ return xml;
3571
3832
  }
3572
3833
  //#endregion
3573
3834
  //#region src/gen-media.ts
@@ -3616,6 +3877,37 @@ function encodeSlideMediaRels(layout, runtime) {
3616
3877
  /**
3617
3878
  * PptxGenJS: XML Generation
3618
3879
  */
3880
+ const _warnedTextRangeMsgs = /* @__PURE__ */ new Set();
3881
+ function warnTextRangeOnce(msg) {
3882
+ if (_warnedTextRangeMsgs.has(msg)) return;
3883
+ _warnedTextRangeMsgs.add(msg);
3884
+ console.warn(msg);
3885
+ }
3886
+ /**
3887
+ * Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
3888
+ * hundredths of a point for the `sz` attribute. Out-of-range sizes make
3889
+ * PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
3890
+ */
3891
+ function clampFontSizeSz(fontSizePts) {
3892
+ const raw = Math.round(fontSizePts * 100);
3893
+ const clamped = Math.min(4e5, Math.max(100, raw));
3894
+ if (clamped !== raw) warnTextRangeOnce(`Warning: fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
3895
+ return clamped;
3896
+ }
3897
+ /** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
3898
+ function clampCharSpacingSpc(charSpacingPts) {
3899
+ const raw = Math.round(charSpacingPts * 100);
3900
+ const clamped = Math.min(4e5, Math.max(-4e5, raw));
3901
+ if (clamped !== raw) warnTextRangeOnce(`Warning: charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
3902
+ return clamped;
3903
+ }
3904
+ /** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
3905
+ function clampLineSpacingPts(lineSpacingPts) {
3906
+ const raw = Math.round(lineSpacingPts * 100);
3907
+ const clamped = Math.min(158400, Math.max(0, raw));
3908
+ if (clamped !== raw) warnTextRangeOnce(`Warning: lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
3909
+ return clamped;
3910
+ }
3619
3911
  const ImageSizingXml = {
3620
3912
  cover: function(imgSize, boxDim) {
3621
3913
  const imgRatio = imgSize.h / imgSize.w;
@@ -3662,23 +3954,90 @@ const ImageSizingXml = {
3662
3954
  * @return {string} `<a:prstGeom>` XML
3663
3955
  */
3664
3956
  const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
3957
+ const SHAPE_LOCK_ATTRS = [
3958
+ "noGrp",
3959
+ "noSelect",
3960
+ "noRot",
3961
+ "noChangeAspect",
3962
+ "noMove",
3963
+ "noResize",
3964
+ "noEditPoints",
3965
+ "noAdjustHandles",
3966
+ "noChangeArrowheads",
3967
+ "noChangeShapeType",
3968
+ "noTextEdit"
3969
+ ];
3970
+ const PICTURE_LOCK_ATTRS = [
3971
+ "noGrp",
3972
+ "noSelect",
3973
+ "noRot",
3974
+ "noChangeAspect",
3975
+ "noMove",
3976
+ "noResize",
3977
+ "noEditPoints",
3978
+ "noAdjustHandles",
3979
+ "noChangeArrowheads",
3980
+ "noChangeShapeType",
3981
+ "noCrop"
3982
+ ];
3983
+ const GRAPHIC_FRAME_LOCK_ATTRS = [
3984
+ "noGrp",
3985
+ "noDrilldown",
3986
+ "noSelect",
3987
+ "noChangeAspect",
3988
+ "noMove",
3989
+ "noResize"
3990
+ ];
3991
+ /**
3992
+ * Serialize an object-lock element (`a:spLocks` / `a:picLocks` / `a:graphicFrameLocks`).
3993
+ * Only flags set to `true` AND valid for this element type are emitted; a flag set on an
3994
+ * unsupported element type is dropped with a warning (silent coercion is a footgun).
3995
+ * @param tag - locking element tag, e.g. `'a:spLocks'`
3996
+ * @param allowed - attribute names this element type supports, in desired emit order
3997
+ * @param locks - merged lock flags (callers fold any hard-coded default in first)
3998
+ * @param objectName - for the warning message
3999
+ * @returns the locking element string, or `''` when no applicable flag is set
4000
+ */
4001
+ function genXmlObjectLock(tag, allowed, locks, objectName) {
4002
+ if (!locks) return "";
4003
+ const lockMap = locks;
4004
+ 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.`);
4005
+ const attrs = allowed.filter((name) => lockMap[name] === true).map((name) => `${name}="1"`);
4006
+ return attrs.length > 0 ? `<${tag} ${attrs.join(" ")}/>` : "";
4007
+ }
3665
4008
  function genXmlPresetGeom(shapeName, options, cx, cy) {
3666
- let strXml = `<a:prstGeom prst="${shapeName}"><a:avLst>`;
4009
+ 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.`);
4010
+ let avLst = "";
4011
+ const emittedAdjNames = /* @__PURE__ */ new Set();
4012
+ const emitGuide = (name, fmlaVal) => {
4013
+ avLst += `<a:gd name="${name}" fmla="val ${fmlaVal}"/>`;
4014
+ emittedAdjNames.add(name);
4015
+ };
3667
4016
  if (options.rectRadius) {
3668
4017
  const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
3669
4018
  if (RECT_RADIUS_ADJ1_SHAPES.has(shapeName)) {
3670
- strXml += `<a:gd name="adj1" fmla="val ${adjVal}"/>`;
3671
- strXml += "<a:gd name=\"adj2\" fmla=\"val 0\"/>";
3672
- } else strXml += `<a:gd name="adj" fmla="val ${adjVal}"/>`;
4019
+ emitGuide("adj1", adjVal);
4020
+ emitGuide("adj2", 0);
4021
+ } else emitGuide("adj", adjVal);
3673
4022
  } else if (options.angleRange) {
3674
4023
  for (let i = 0; i < 2; i++) {
3675
4024
  const angle = options.angleRange[i];
3676
- strXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`;
4025
+ emitGuide(`adj${i + 1}`, convertRotationDegrees(angle));
3677
4026
  }
3678
- if (options.arcThicknessRatio) strXml += `<a:gd name="adj3" fmla="val ${Math.round(options.arcThicknessRatio * 5e4)}" />`;
4027
+ if (options.arcThicknessRatio) emitGuide("adj3", Math.round(options.arcThicknessRatio * 5e4));
3679
4028
  }
3680
- strXml += "</a:avLst></a:prstGeom>";
3681
- return strXml;
4029
+ if (options.shapeAdjust) (Array.isArray(options.shapeAdjust) ? options.shapeAdjust : [options.shapeAdjust]).forEach((adj) => {
4030
+ if (!adj || typeof adj.name !== "string" || adj.name.length === 0 || typeof adj.value !== "number" || !isFinite(adj.value)) {
4031
+ console.warn(`Warning: shapeAdjust entry ${JSON.stringify(adj)} is invalid (needs { name:string, value:number }) and was ignored.`);
4032
+ return;
4033
+ }
4034
+ if (emittedAdjNames.has(adj.name)) {
4035
+ console.warn(`Warning: shapeAdjust "${adj.name}" was ignored because rectRadius/angleRange already set that handle.`);
4036
+ return;
4037
+ }
4038
+ emitGuide(adj.name, Math.round(adj.value * 1e5));
4039
+ });
4040
+ return `<a:prstGeom prst="${shapeName}"><a:avLst>${avLst}</a:avLst></a:prstGeom>`;
3682
4041
  }
3683
4042
  /**
3684
4043
  * Emit an `<a:custGeom>` for a freeform path built from `points`.
@@ -3731,6 +4090,45 @@ function genXmlCustGeom(points, cx, cy, layout) {
3731
4090
  }
3732
4091
  const PLACEHOLDER_TYPE_MAP = PLACEHOLDER_TYPES;
3733
4092
  /**
4093
+ * Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
4094
+ * Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
4095
+ * outer edges render with the same border as its origin cell (Issue #680).
4096
+ * @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
4097
+ * @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
4098
+ */
4099
+ function genTableCellBorderXml(cellBorder) {
4100
+ let strXml = "";
4101
+ [
4102
+ {
4103
+ idx: 3,
4104
+ name: "lnL"
4105
+ },
4106
+ {
4107
+ idx: 1,
4108
+ name: "lnR"
4109
+ },
4110
+ {
4111
+ idx: 0,
4112
+ name: "lnT"
4113
+ },
4114
+ {
4115
+ idx: 2,
4116
+ name: "lnB"
4117
+ }
4118
+ ].forEach((obj) => {
4119
+ const border = cellBorder[obj.idx];
4120
+ if (!border) return;
4121
+ const cap = createLineCap(border.cap);
4122
+ if (border.type !== "none") {
4123
+ strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="${cap}" cmpd="sng" algn="ctr">`;
4124
+ strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
4125
+ 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"/>`;
4126
+ strXml += `</a:${obj.name}>`;
4127
+ } else strXml += `<a:${obj.name} w="0" cap="${cap}" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
4128
+ });
4129
+ return strXml;
4130
+ }
4131
+ /**
3734
4132
  * Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
3735
4133
  * @param {PresSlideInternal|SlideLayoutInternal} slideObject - slide object created within createSlideObject
3736
4134
  * @return {string} XML string with <p:cSld> as the root
@@ -3789,7 +4187,10 @@ function slideObjectToXml(slide) {
3789
4187
  intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1;
3790
4188
  });
3791
4189
  strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
3792
- strXml += "<p:cNvGraphicFramePr><a:graphicFrameLocks noGrp=\"1\"/></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>";
4190
+ strXml += `<p:cNvGraphicFramePr>${genXmlObjectLock("a:graphicFrameLocks", GRAPHIC_FRAME_LOCK_ATTRS, {
4191
+ noGrp: true,
4192
+ ...slideItemObj.options.objectLock
4193
+ }, 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
4194
  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
4195
  {
3795
4196
  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 +4222,8 @@ function slideObjectToXml(slide) {
3821
4222
  return {
3822
4223
  _type: "tablecell",
3823
4224
  options: { rowspan },
3824
- _hmerge: true
4225
+ _hmerge: true,
4226
+ _spanOrigin: cell
3825
4227
  };
3826
4228
  });
3827
4229
  cells.splice(cIdx + 1, 0, ...vMergeCells);
@@ -3837,12 +4239,14 @@ function slideObjectToXml(slide) {
3837
4239
  const colspan = cell.options?.colspan;
3838
4240
  const _hmerge = cell._hmerge;
3839
4241
  if (rowspan && rowspan > 1) {
4242
+ const _spanOrigin = cell._spanOrigin || cell;
3840
4243
  const hMergeCell = {
3841
4244
  _type: "tablecell",
3842
4245
  options: { colspan },
3843
4246
  _rowContinue: rowspan - 1,
3844
4247
  _vmerge: true,
3845
- _hmerge
4248
+ _hmerge,
4249
+ _spanOrigin
3846
4250
  };
3847
4251
  nextRow.splice(cIdx, 0, hMergeCell);
3848
4252
  }
@@ -3852,7 +4256,7 @@ function slideObjectToXml(slide) {
3852
4256
  let intRowH = 0;
3853
4257
  if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]));
3854
4258
  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 ? inch2Emu(slideItemObj.options.h) : typeof slideItemObj.options.cy === "number" ? slideItemObj.options.cy : 1) / arrTabRows.length);
4259
+ 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
4260
  strXml += `<a:tr h="${intRowH}">`;
3857
4261
  cells.forEach((cellObj) => {
3858
4262
  const cell = cellObj;
@@ -3865,7 +4269,17 @@ function slideObjectToXml(slide) {
3865
4269
  let cellSpanAttrStr = Object.entries(cellSpanAttrs).filter(([, v]) => !!v).map(([k, v]) => `${String(k)}="${String(v)}"`).join(" ");
3866
4270
  if (cellSpanAttrStr) cellSpanAttrStr = " " + cellSpanAttrStr;
3867
4271
  if (cell._hmerge || cell._vmerge) {
3868
- strXml += `<a:tc${cellSpanAttrStr}><a:tcPr/></a:tc>`;
4272
+ const origin = cell._spanOrigin;
4273
+ let spanPrXml = "";
4274
+ if (origin) {
4275
+ const originOpts = origin.options || {};
4276
+ const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
4277
+ if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
4278
+ let spanFill = origin._optImp?.fill?.color ? origin._optImp.fill.color : origin._optImp?.fill && typeof origin._optImp.fill === "string" ? origin._optImp.fill : "";
4279
+ spanFill = spanFill || originOpts.fill ? originOpts.fill : "";
4280
+ if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
4281
+ }
4282
+ strXml += `<a:tc${cellSpanAttrStr}><a:tcPr>${spanPrXml}</a:tcPr></a:tc>`;
3869
4283
  return;
3870
4284
  }
3871
4285
  const cellOpts = cell.options || {};
@@ -3909,32 +4323,7 @@ function slideObjectToXml(slide) {
3909
4323
  else cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(cellMargin[2])}"`;
3910
4324
  strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`;
3911
4325
  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
- });
4326
+ if (cellBorder) strXml += genTableCellBorderXml(cellBorder);
3938
4327
  strXml += cellFill;
3939
4328
  strXml += " </a:tcPr>";
3940
4329
  strXml += " </a:tc>";
@@ -3953,10 +4342,10 @@ function slideObjectToXml(slide) {
3953
4342
  if (!slideItemObj.options.line && cy === 0) cy = EMU * .3;
3954
4343
  if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {};
3955
4344
  if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
3956
- slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[0] || 0);
4345
+ slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[0] || 0);
3957
4346
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0);
3958
4347
  slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0);
3959
- slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[3] || 0);
4348
+ slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[3] || 0);
3960
4349
  } else if (typeof slideItemObj.options.margin === "number") {
3961
4350
  slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin);
3962
4351
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin);
@@ -3968,7 +4357,11 @@ function slideObjectToXml(slide) {
3968
4357
  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
4358
  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
4359
  strSlideXml += "</p:cNvPr>";
3971
- strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"/>" : "/>");
4360
+ {
4361
+ const spLockXml = genXmlObjectLock("a:spLocks", SHAPE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName);
4362
+ strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"" : "");
4363
+ strSlideXml += spLockXml ? `>${spLockXml}</p:cNvSpPr>` : "/>";
4364
+ }
3972
4365
  strSlideXml += `<p:nvPr>${slideItemObj._type === "placeholder" ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`;
3973
4366
  strSlideXml += "</p:nvSpPr><p:spPr>";
3974
4367
  strSlideXml += `<a:xfrm${locationAttr}>`;
@@ -3978,7 +4371,8 @@ function slideObjectToXml(slide) {
3978
4371
  else strSlideXml += genXmlPresetGeom(slideItemObj.shape, slideItemObj.options, cx, cy);
3979
4372
  strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : "<a:noFill/>";
3980
4373
  if (slideItemObj.options.line) {
3981
- strSlideXml += slideItemObj.options.line.width ? `<a:ln w="${valToPts(slideItemObj.options.line.width)}">` : "<a:ln>";
4374
+ const lnAttrs = (slideItemObj.options.line.width ? ` w="${lineWidthToEmu(slideItemObj.options.line.width)}"` : "") + (slideItemObj.options.line.cap ? ` cap="${createLineCap(slideItemObj.options.line.cap)}"` : "");
4375
+ strSlideXml += `<a:ln${lnAttrs}>`;
3982
4376
  if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line);
3983
4377
  if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`;
3984
4378
  if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`;
@@ -4011,7 +4405,10 @@ function slideObjectToXml(slide) {
4011
4405
  if (slideItemObj.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}"/>`;
4012
4406
  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
4407
  strSlideXml += " </p:cNvPr>";
4014
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
4408
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
4409
+ noChangeAspect: true,
4410
+ ...slideItemObj.options.objectLock
4411
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
4015
4412
  strSlideXml += " <p:nvPr>" + genXmlPlaceholder(placeholderObj) + "</p:nvPr>";
4016
4413
  strSlideXml += " </p:nvPicPr>";
4017
4414
  strSlideXml += "<p:blipFill>";
@@ -4086,7 +4483,7 @@ function slideObjectToXml(slide) {
4086
4483
  strSlideXml += "<p:pic>";
4087
4484
  strSlideXml += " <p:nvPicPr>";
4088
4485
  strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
4089
- strSlideXml += " <p:cNvPicPr/>";
4486
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName)}</p:cNvPicPr>`;
4090
4487
  strSlideXml += " <p:nvPr>";
4091
4488
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
4092
4489
  strSlideXml += " </p:nvPr>";
@@ -4101,7 +4498,10 @@ function slideObjectToXml(slide) {
4101
4498
  strSlideXml += "<p:pic>";
4102
4499
  strSlideXml += " <p:nvPicPr>";
4103
4500
  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 += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
4501
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
4502
+ noChangeAspect: true,
4503
+ ...slideItemObj.options.objectLock
4504
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
4105
4505
  strSlideXml += " <p:nvPr>";
4106
4506
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
4107
4507
  strSlideXml += " <p:extLst>";
@@ -4165,7 +4565,7 @@ function slideObjectToXml(slide) {
4165
4565
  strSlideXml += "/>";
4166
4566
  strSlideXml += " <a:lstStyle><a:lvl1pPr>";
4167
4567
  if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
4168
- strSlideXml += `<a:defRPr sz="${Math.round((slide._slideNumberProps.fontSize || 12) * 100)}">`;
4568
+ strSlideXml += `<a:defRPr sz="${clampFontSizeSz(slide._slideNumberProps.fontSize || 12)}">`;
4169
4569
  if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color);
4170
4570
  if (slide._slideNumberProps.fontFace) strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>`;
4171
4571
  strSlideXml += "</a:defRPr>";
@@ -4254,7 +4654,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
4254
4654
  paragraphPropXml += "";
4255
4655
  break;
4256
4656
  }
4257
- if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${Math.round(textObj.options.lineSpacing * 100)}"/></a:lnSpc>`;
4657
+ if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${clampLineSpacingPts(textObj.options.lineSpacing)}"/></a:lnSpc>`;
4258
4658
  else if (textObj.options.lineSpacingMultiple) strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 1e5)}"/></a:lnSpc>`;
4259
4659
  if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`;
4260
4660
  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>`;
@@ -4308,7 +4708,7 @@ function genXmlTextRunProperties(opts, isDefault) {
4308
4708
  let runProps = "";
4309
4709
  const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
4310
4710
  runProps += "<" + runPropsTag + " lang=\"" + (opts.lang ? opts.lang : "en-US") + "\"" + (opts.lang ? " altLang=\"en-US\"" : "");
4311
- runProps += opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "";
4711
+ runProps += opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "";
4312
4712
  runProps += opts?.bold ? ` b="${opts.bold ? "1" : "0"}"` : "";
4313
4713
  runProps += opts?.italic ? ` i="${opts.italic ? "1" : "0"}"` : "";
4314
4714
  runProps += opts?.strike ? ` strike="${typeof opts.strike === "string" ? opts.strike : "sngStrike"}"` : "";
@@ -4319,17 +4719,23 @@ function genXmlTextRunProperties(opts, isDefault) {
4319
4719
  if (opts.baseline) runProps += ` baseline="${Math.round(opts.baseline * 50)}"`;
4320
4720
  else if (opts.subscript) runProps += " baseline=\"-40000\"";
4321
4721
  else if (opts.superscript) runProps += " baseline=\"30000\"";
4322
- runProps += opts.charSpacing ? ` spc="${Math.round(opts.charSpacing * 100)}" kern="0"` : "";
4722
+ runProps += opts.charSpacing ? ` spc="${clampCharSpacingSpc(opts.charSpacing)}" kern="0"` : "";
4323
4723
  runProps += " dirty=\"0\">";
4324
- if (opts.color || opts.fontFace || opts.outline || typeof opts.underline === "object" && opts.underline.color) {
4325
- if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${valToPts(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
4724
+ const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
4725
+ if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
4726
+ if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${lineWidthToEmu(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
4326
4727
  if (opts.color) runProps += genXmlColorSelection({
4327
4728
  color: opts.color,
4328
4729
  transparency: opts.transparency
4329
4730
  });
4731
+ if (opts.glow || hasShadow) {
4732
+ runProps += "<a:effectLst>";
4733
+ if (opts.glow) runProps += createGlowElement(opts.glow, DEF_TEXT_GLOW);
4734
+ if (hasShadow) runProps += createShadowElement$1(opts.shadow, DEF_TEXT_SHADOW);
4735
+ runProps += "</a:effectLst>";
4736
+ }
4330
4737
  if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`;
4331
4738
  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
4739
  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
4740
  }
4335
4741
  if (opts.hyperlink) {
@@ -4359,6 +4765,28 @@ function genXmlTextRun(textObj) {
4359
4765
  return `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(String(textObj.text))}</a:t></a:r>`;
4360
4766
  }
4361
4767
  /**
4768
+ * Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
4769
+ * @param {TextFitShrinkProps} fit - shrink fit options
4770
+ * @return {string} XML string (`<a:normAutofit .../>`)
4771
+ * @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
4772
+ */
4773
+ function genXmlNormAutofit(fit) {
4774
+ let attrs = "";
4775
+ const pct = (val, name) => {
4776
+ if (val === void 0 || val === null) return null;
4777
+ if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
4778
+ console.warn(`Warning: fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
4779
+ return null;
4780
+ }
4781
+ return Math.round(val * 1e3);
4782
+ };
4783
+ const fontScale = pct(fit.fontScale, "fontScale");
4784
+ if (fontScale !== null) attrs += ` fontScale="${fontScale}"`;
4785
+ const lnSpcReduction = pct(fit.lnSpcReduction, "lnSpcReduction");
4786
+ if (lnSpcReduction !== null) attrs += ` lnSpcReduction="${lnSpcReduction}"`;
4787
+ return `<a:normAutofit${attrs}/>`;
4788
+ }
4789
+ /**
4362
4790
  * Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
4363
4791
  * @param {ISlideObject | TableCell} slideObject - various options
4364
4792
  * @return {string} XML string
@@ -4371,6 +4799,8 @@ function genXmlBodyProperties(slideObject) {
4371
4799
  if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`;
4372
4800
  if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`;
4373
4801
  if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`;
4802
+ if (slideObject.options._bodyProp.numCol) bodyProperties += ` numCol="${slideObject.options._bodyProp.numCol}"`;
4803
+ if (slideObject.options._bodyProp.spcCol) bodyProperties += ` spcCol="${slideObject.options._bodyProp.spcCol}"`;
4374
4804
  bodyProperties += " rtlCol=\"0\"";
4375
4805
  if (slideObject.options._bodyProp.anchor) bodyProperties += " anchor=\"" + slideObject.options._bodyProp.anchor + "\"";
4376
4806
  if (slideObject.options._bodyProp.vert) bodyProperties += " vert=\"" + slideObject.options._bodyProp.vert + "\"";
@@ -4381,9 +4811,11 @@ function genXmlBodyProperties(slideObject) {
4381
4811
  * @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
4382
4812
  */
4383
4813
  if (slideObject.options.fit) {
4384
- if (slideObject.options.fit === "none") bodyProperties += "";
4385
- else if (slideObject.options.fit === "shrink") bodyProperties += "<a:normAutofit/>";
4386
- else if (slideObject.options.fit === "resize") bodyProperties += "<a:spAutoFit/>";
4814
+ const fit = slideObject.options.fit;
4815
+ if (fit === "none") bodyProperties += "";
4816
+ else if (fit === "shrink") bodyProperties += "<a:normAutofit/>";
4817
+ else if (fit === "resize") bodyProperties += "<a:spAutoFit/>";
4818
+ else if (typeof fit === "object" && fit.type === "shrink") bodyProperties += genXmlNormAutofit(fit);
4387
4819
  }
4388
4820
  if (slideObject.options.shrinkText) bodyProperties += "<a:normAutofit/>";
4389
4821
  bodyProperties += slideObject.options._bodyProp.autoFit ? "<a:spAutoFit/>" : "";
@@ -4519,13 +4951,13 @@ function genXmlTextBody(slideObj) {
4519
4951
  }
4520
4952
  });
4521
4953
  if (slideObj._type === "tablecell" && (opts.fontSize || opts.fontFace)) if (opts.fontFace) {
4522
- strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\">";
4954
+ strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\">";
4523
4955
  strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`;
4524
4956
  strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`;
4525
4957
  strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`;
4526
4958
  strSlideXml += "</a:endParaRPr>";
4527
- } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
4528
- else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
4959
+ } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
4960
+ else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
4529
4961
  else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}" dirty="0"/>`;
4530
4962
  strSlideXml += "</a:p>";
4531
4963
  });
@@ -5001,7 +5433,7 @@ function genXmlTableStyleBorders(border) {
5001
5433
  xml += `<a:${side}>`;
5002
5434
  if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
5003
5435
  else {
5004
- xml += `<a:ln w="${valToPts(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
5436
+ xml += `<a:ln w="${lineWidthToEmu(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
5005
5437
  xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
5006
5438
  xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
5007
5439
  xml += "</a:ln>";
@@ -5079,7 +5511,7 @@ function makeXmlViewProps() {
5079
5511
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
5080
5512
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
5081
5513
  */
5082
- const VERSION = "5.2.0";
5514
+ const VERSION = "5.3.0";
5083
5515
  function standardLayoutToPresLayout(layout) {
5084
5516
  return {
5085
5517
  name: layout.name,
@@ -5411,6 +5843,18 @@ var PptxGenJS = class {
5411
5843
  });
5412
5844
  arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
5413
5845
  return await Promise.all(arrMediaPromises).then(async () => {
5846
+ const canonicalMediaTargets = /* @__PURE__ */ new Map();
5847
+ for (const target of [
5848
+ ...this._slides,
5849
+ ...this._slideLayouts,
5850
+ this._masterSlide
5851
+ ]) for (const rel of target._relsMedia || []) {
5852
+ if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
5853
+ const key = (rel.extn || "") + "\0" + rel.data;
5854
+ const canonical = canonicalMediaTargets.get(key);
5855
+ if (canonical) rel.Target = canonical;
5856
+ else canonicalMediaTargets.set(key, rel.Target);
5857
+ }
5414
5858
  this._slides.forEach((slide) => {
5415
5859
  if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
5416
5860
  });
@@ -5676,4 +6120,4 @@ var PptxGenJS = class {
5676
6120
  //#endregion
5677
6121
  export { PptxGenJS as t };
5678
6122
 
5679
- //# sourceMappingURL=pptxgen--5RWzhb4.js.map
6123
+ //# sourceMappingURL=pptxgen-B-mAxCRC.js.map