@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.
@@ -3137,6 +3137,10 @@ while (n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[+
3137
3137
  const EMU_PER_INCH = 914400;
3138
3138
  const EMU_PER_POINT = 12700;
3139
3139
  const POINTS_PER_INCH = 72;
3140
+ /** A bare number larger than this (in inches) is almost certainly a mistake — likely a raw EMU
3141
+ * value passed where inches are expected. We interpret it as inches (the documented contract) but
3142
+ * warn, pointing at the explicit `"<n>emu"` form. ~1000in is far beyond any real slide. */
3143
+ const IMPLAUSIBLE_INCHES = 1e3;
3140
3144
  function inchesToEmu(inches) {
3141
3145
  assertFiniteNumber(inches, "inches");
3142
3146
  return Math.round(inches * EMU_PER_INCH);
@@ -3150,6 +3154,46 @@ function pixelsToEmu(pixels, dpi) {
3150
3154
  assertPositiveFiniteNumber(dpi, "dpi");
3151
3155
  return inchesToEmu(pixels / dpi);
3152
3156
  }
3157
+ /**
3158
+ * Resolve a percentage of an axis length to EMU.
3159
+ * @param percent - percentage value (e.g. `50` for 50%)
3160
+ * @param axisEmu - the axis length in EMU (slide width for x/w, height for y/h)
3161
+ */
3162
+ function percentToEmu(percent, axisEmu) {
3163
+ assertFiniteNumber(percent, "percent");
3164
+ assertFiniteNumber(axisEmu, "axisEmu");
3165
+ return Math.round(percent / 100 * axisEmu);
3166
+ }
3167
+ /**
3168
+ * The single user-coordinate → EMU boundary. Convert each user-supplied coordinate exactly once.
3169
+ *
3170
+ * Accepts (see {@link Coord}):
3171
+ * - a bare `number` → **always inches** (the documented unit); no magnitude guessing
3172
+ * - `"<n>%"` → percentage of `axisEmu`
3173
+ * - `"<n>in"` / `"<n>pt"` / `"<n>emu"` → explicit units (the escape hatch for non-inch values)
3174
+ *
3175
+ * Throws on non-finite or unparseable input rather than silently emitting a degenerate 0-size.
3176
+ * @param value - user coordinate
3177
+ * @param axisEmu - axis length in EMU, used only to resolve percentages
3178
+ */
3179
+ function coordToEmu(value, axisEmu) {
3180
+ if (typeof value === "number") {
3181
+ assertFiniteNumber(value, "coordinate");
3182
+ if (Math.abs(value) > IMPLAUSIBLE_INCHES) console.warn(`PptxGenJS: coordinate ${value} interpreted as ${value} inches. A bare number is always inches; if you meant EMU, pass it as a string like "${Math.round(value)}emu".`);
3183
+ return inchesToEmu(value);
3184
+ }
3185
+ const match = /^\s*(-?\d*\.?\d+)\s*(%|in|pt|emu)\s*$/.exec(value);
3186
+ if (!match) throw new Error(`PptxGenJS: invalid coordinate "${value}". Expected a number (inches) or a string like "50%", "5in", "72pt", or "914400emu".`);
3187
+ const n = Number(match[1]);
3188
+ switch (match[2]) {
3189
+ case "%": return percentToEmu(n, axisEmu);
3190
+ case "in": return inchesToEmu(n);
3191
+ case "pt": return pointsToEmu(n);
3192
+ default:
3193
+ assertFiniteNumber(n, "coordinate");
3194
+ return Math.round(n);
3195
+ }
3196
+ }
3153
3197
  function emuToInches(emu) {
3154
3198
  assertFiniteNumber(emu, "emu");
3155
3199
  return emu / EMU_PER_INCH;
@@ -3433,7 +3477,7 @@ let ShapeType = /* @__PURE__ */ function(ShapeType) {
3433
3477
  ShapeType["flowChartSort"] = "flowChartSort";
3434
3478
  ShapeType["flowChartSummingJunction"] = "flowChartSummingJunction";
3435
3479
  ShapeType["flowChartTerminator"] = "flowChartTerminator";
3436
- ShapeType["folderCorner"] = "folderCorner";
3480
+ ShapeType["foldedCorner"] = "foldedCorner";
3437
3481
  ShapeType["frame"] = "frame";
3438
3482
  ShapeType["funnel"] = "funnel";
3439
3483
  ShapeType["gear6"] = "gear6";
@@ -3636,7 +3680,7 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3636
3680
  SHAPE_TYPE["FLOWCHART_STORED_DATA"] = "flowChartOnlineStorage";
3637
3681
  SHAPE_TYPE["FLOWCHART_SUMMING_JUNCTION"] = "flowChartSummingJunction";
3638
3682
  SHAPE_TYPE["FLOWCHART_TERMINATOR"] = "flowChartTerminator";
3639
- SHAPE_TYPE["FOLDED_CORNER"] = "folderCorner";
3683
+ SHAPE_TYPE["FOLDED_CORNER"] = "foldedCorner";
3640
3684
  SHAPE_TYPE["FRAME"] = "frame";
3641
3685
  SHAPE_TYPE["FUNNEL"] = "funnel";
3642
3686
  SHAPE_TYPE["GEAR_6"] = "gear6";
@@ -3671,10 +3715,6 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3671
3715
  SHAPE_TYPE["LINE_CALLOUT_3_ACCENT_BAR"] = "accentCallout3";
3672
3716
  SHAPE_TYPE["LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout3";
3673
3717
  SHAPE_TYPE["LINE_CALLOUT_3_NO_BORDER"] = "callout3";
3674
- SHAPE_TYPE["LINE_CALLOUT_4"] = "borderCallout4";
3675
- SHAPE_TYPE["LINE_CALLOUT_4_ACCENT_BAR"] = "accentCallout3=4";
3676
- SHAPE_TYPE["LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout4";
3677
- SHAPE_TYPE["LINE_CALLOUT_4_NO_BORDER"] = "callout4";
3678
3718
  SHAPE_TYPE["LINE"] = "line";
3679
3719
  SHAPE_TYPE["LINE_INVERSE"] = "lineInv";
3680
3720
  SHAPE_TYPE["MATH_DIVIDE"] = "mathDivide";
@@ -3742,6 +3782,31 @@ let SHAPE_TYPE = /* @__PURE__ */ function(SHAPE_TYPE) {
3742
3782
  SHAPE_TYPE["WAVE"] = "wave";
3743
3783
  return SHAPE_TYPE;
3744
3784
  }({});
3785
+ /**
3786
+ * Valid ECMA-376 `ST_ShapeType` presets that are not surfaced with a friendly
3787
+ * `SHAPE_TYPE` name. They are still legal geometries PowerPoint renders, so the
3788
+ * preset-validation set must accept them.
3789
+ */
3790
+ const EXTRA_SHAPE_PRESETS = [
3791
+ "straightConnector1",
3792
+ "bentConnector2",
3793
+ "bentConnector3",
3794
+ "bentConnector4",
3795
+ "bentConnector5",
3796
+ "curvedConnector2",
3797
+ "curvedConnector3",
3798
+ "curvedConnector4",
3799
+ "curvedConnector5"
3800
+ ];
3801
+ /**
3802
+ * Every shape geometry name PptxGenJS can serialize without corrupting the
3803
+ * package: the OOXML preset geometries (`ST_ShapeType` — `SHAPE_TYPE` values
3804
+ * plus the unexposed connectors above) and `custGeom` (freeform paths, emitted
3805
+ * as `<a:custGeom>` rather than `<a:prstGeom>`). Used to reject bogus presets
3806
+ * before they become an invalid `<a:prstGeom prst="...">` that triggers
3807
+ * PowerPoint's "needs repair" dialog and drops the shape.
3808
+ */
3809
+ const VALID_SHAPE_PRESETS = new Set([...Object.values(SHAPE_TYPE), ...EXTRA_SHAPE_PRESETS]);
3745
3810
  let CHART_TYPE = /* @__PURE__ */ function(CHART_TYPE) {
3746
3811
  CHART_TYPE["AREA"] = "area";
3747
3812
  CHART_TYPE["BAR"] = "bar";
@@ -3910,27 +3975,20 @@ const IMG_PLAYBTN = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAVnCAYAA
3910
3975
  * PptxGenJS: Utility Methods
3911
3976
  */
3912
3977
  /**
3913
- * Translates any type of `x`/`y`/`w`/`h` prop to EMU
3914
- * - guaranteed to return a result regardless of undefined, null, etc. (0)
3915
- * - {number} - 12800 (EMU)
3916
- * - {number} - 0.5 (inches)
3917
- * - {string} - "75%"
3918
- * @param {number|string} size - numeric ("5.5") or percentage ("90%")
3919
- * @param {'X' | 'Y'} xyDir - direction
3920
- * @param {PresLayout} layout - presentation layout
3921
- * @returns {number} calculated size
3978
+ * Resolve a user `Coord` (x/y/w/h) to EMU — the single user-coordinate → EMU boundary.
3979
+ * - bare `number` **inches** (no magnitude guessing); `"<n>%"` → percent of the slide axis;
3980
+ * `"<n>in"`/`"<n>pt"`/`"<n>emu"` explicit units (see {@link Coord} / {@link coordToEmu})
3981
+ * - `null`/`undefined` 0 (callers may omit a coordinate)
3982
+ * - throws on a non-finite number rather than silently collapsing the object to zero size
3983
+ * @param {Coord|null|undefined} size - user coordinate
3984
+ * @param {'X' | 'Y'} xyDir - axis (selects slide width vs height for percentages)
3985
+ * @param {PresLayout} layout - presentation layout (EMU dimensions)
3986
+ * @returns {Emu} resolved EMU value
3922
3987
  */
3923
3988
  function getSmartParseNumber(size, xyDir, layout) {
3924
- if (typeof size === "string" && !isNaN(Number(size))) size = Number(size);
3989
+ if (size === null || size === void 0) return 0;
3925
3990
  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).`);
3926
- if (typeof size === "number" && size < 100) return inch2Emu(size);
3927
- if (typeof size === "number" && size >= 100) return size;
3928
- if (typeof size === "string" && size.includes("%")) {
3929
- if (xyDir && xyDir === "X") return Math.round(parseFloat(size) / 100 * layout.width);
3930
- if (xyDir && xyDir === "Y") return Math.round(parseFloat(size) / 100 * layout.height);
3931
- return Math.round(parseFloat(size) / 100 * layout.width);
3932
- }
3933
- return 0;
3991
+ return coordToEmu(size, xyDir === "Y" ? layout.height : layout.width);
3934
3992
  }
3935
3993
  /**
3936
3994
  * Basic UUID Generator Adapted
@@ -4003,14 +4061,16 @@ function getDuplicateObjectNames(names) {
4003
4061
  return Array.from(dupes);
4004
4062
  }
4005
4063
  /**
4006
- * Convert inches into EMU
4007
- * @param {number|string} inches - as string or number
4008
- * @returns {number} EMU value
4064
+ * Convert inches into EMU.
4065
+ * - accepts a number (inches) or a numeric/`"<n>in"` string
4066
+ * - no magnitude guessing: values are always treated as inches (use {@link coordToEmu} for
4067
+ * user coordinates that may carry other units)
4068
+ * @param {number|string} inches - inches as number or string
4069
+ * @returns {Emu} EMU value
4009
4070
  */
4010
4071
  function inch2Emu(inches) {
4011
- if (typeof inches === "number" && inches > 100) return inches;
4012
4072
  if (typeof inches === "string") inches = Number(inches.replace(/in*/gi, ""));
4013
- return Math.round(EMU * inches);
4073
+ return inchesToEmu(inches);
4014
4074
  }
4015
4075
  /**
4016
4076
  * Convert `pt` into points (using `ONEPT`)
@@ -4022,6 +4082,33 @@ function valToPts(pt) {
4022
4082
  return isNaN(points) ? 0 : Math.round(points * ONEPT);
4023
4083
  }
4024
4084
  /**
4085
+ * Convert a transparency percentage (0-100) into a schema-valid `<a:alpha>` value
4086
+ * (ST_PositiveFixedPercentage, 0-100000). Out-of-range transparency yields an
4087
+ * alpha that PowerPoint rejects as needing repair, so clamp into range and warn.
4088
+ */
4089
+ function transparencyToAlpha(transparency) {
4090
+ const pct = Math.min(100, Math.max(0, transparency));
4091
+ if (pct !== transparency) console.warn(`Warning: transparency ${transparency} is outside the valid range 0-100; using ${pct}.`);
4092
+ return Math.round((100 - pct) * 1e3);
4093
+ }
4094
+ /** Convert an opacity (0-1) into a schema-valid `<a:alpha>` value (0-100000); clamps + warns on out-of-range input. */
4095
+ function opacityToAlpha(opacity) {
4096
+ const o = Math.min(1, Math.max(0, opacity));
4097
+ if (o !== opacity) console.warn(`Warning: opacity ${opacity} is outside the valid range 0-1; using ${o}.`);
4098
+ return Math.round(o * 1e5);
4099
+ }
4100
+ /**
4101
+ * Convert a line width (points) to EMU clamped into ST_LineWidth (0..20116800 EMU,
4102
+ * i.e. 0-1584pt). Out-of-range widths make PowerPoint report the package as needing
4103
+ * repair, so clamp into range and warn.
4104
+ */
4105
+ function lineWidthToEmu(widthPts) {
4106
+ const raw = valToPts(widthPts);
4107
+ const clamped = Math.min(20116800, Math.max(0, raw));
4108
+ if (clamped !== raw) console.warn(`Warning: line width ${widthPts} is outside the valid range 0-1584pt; using ${clamped / ONEPT}.`);
4109
+ return clamped;
4110
+ }
4111
+ /**
4025
4112
  * Convert degrees (0..360) to PowerPoint `rot` value
4026
4113
  * @param {number} d degrees
4027
4114
  * @returns {number} calculated `rot` value
@@ -4069,8 +4156,10 @@ function createColorElement(colorStr, innerElements) {
4069
4156
  }
4070
4157
  let colorVal = (colorStr || "").replace("#", "");
4071
4158
  if (/^[0-9a-fA-F]{8}$/.test(colorVal)) {
4072
- const alphaHex = colorVal.slice(6, 8);
4073
- innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
4159
+ if (!innerElements?.includes("<a:alpha")) {
4160
+ const alphaHex = colorVal.slice(6, 8);
4161
+ innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
4162
+ }
4074
4163
  colorVal = colorVal.slice(0, 6);
4075
4164
  }
4076
4165
  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") {
@@ -4096,12 +4185,38 @@ function createGlowElement(options, defaults) {
4096
4185
  };
4097
4186
  const size = Math.round(opts.size * ONEPT);
4098
4187
  const color = opts.color || "000000";
4099
- const opacity = Math.round((opts.opacity ?? 0) * 1e5);
4188
+ const opacity = opacityToAlpha(opts.opacity ?? 0);
4100
4189
  strXml += `<a:glow rad="${size}">`;
4101
4190
  strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4102
4191
  strXml += "</a:glow>";
4103
4192
  return strXml;
4104
4193
  }
4194
+ /**
4195
+ * Creates an `a:outerShdw`/`a:innerShdw` element for a text run or shape.
4196
+ * Returns the shadow element only (no wrapping `a:effectLst`) so callers can
4197
+ * combine it with other effects (e.g. glow) inside a single `a:effectLst`.
4198
+ * @param {ShadowProps} options shadow properties
4199
+ * @param {ShadowProps} defaults defaults for unspecified properties in `options`
4200
+ * @see http://officeopenxml.com/drwSp-effects.php
4201
+ * @returns {string} XML string, or '' when type is 'none'
4202
+ */
4203
+ function createShadowElement$1(options, defaults) {
4204
+ const opts = {
4205
+ ...defaults,
4206
+ ...options
4207
+ };
4208
+ if (opts.type === "none") return "";
4209
+ const type = opts.type || "outer";
4210
+ const blur = valToPts(opts.blur ?? 0);
4211
+ const offset = valToPts(opts.offset ?? 0);
4212
+ const angle = Math.round((opts.angle ?? 0) * 6e4);
4213
+ const opacity = Math.round((opts.opacity ?? .75) * 1e5);
4214
+ const color = opts.color || "000000";
4215
+ 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}">`;
4216
+ strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4217
+ strXml += `</a:${type}Shdw>`;
4218
+ return strXml;
4219
+ }
4105
4220
  function boolToXml(value) {
4106
4221
  return value ? "1" : "0";
4107
4222
  }
@@ -4112,8 +4227,8 @@ function normalizeGradientAngle(angle) {
4112
4227
  }
4113
4228
  function gradientStopColorAdjustments(stop) {
4114
4229
  let internalElements = "";
4115
- if (stop.alpha) internalElements += `<a:alpha val="${Math.round((100 - stop.alpha) * 1e3)}"/>`;
4116
- if (stop.transparency) internalElements += `<a:alpha val="${Math.round((100 - stop.transparency) * 1e3)}"/>`;
4230
+ if (stop.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(stop.alpha)}"/>`;
4231
+ if (stop.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(stop.transparency)}"/>`;
4117
4232
  return internalElements;
4118
4233
  }
4119
4234
  function normalizeGradientStops(stops) {
@@ -4163,6 +4278,17 @@ function genXmlPatternFill(pattern) {
4163
4278
  * @param {Color | ShapeFillProps | ShapeLineProps} props fill props
4164
4279
  * @returns XML string
4165
4280
  */
4281
+ /**
4282
+ * Map a friendly `LineCap` value to the OOXML `cap` attribute value (`flat`/`sq`/`rnd`).
4283
+ * @param {LineCap} [lineCap] - line cap style (defaults to `flat`)
4284
+ * @returns {string} value for the `cap` attribute on `<a:ln>`
4285
+ */
4286
+ function createLineCap(lineCap) {
4287
+ if (!lineCap || lineCap === "flat") return "flat";
4288
+ else if (lineCap === "square") return "sq";
4289
+ else if (lineCap === "round") return "rnd";
4290
+ else throw new Error(`Invalid line cap: ${String(lineCap)}`);
4291
+ }
4166
4292
  function genXmlColorSelection(props) {
4167
4293
  let fillType = "solid";
4168
4294
  let colorVal = "";
@@ -4173,8 +4299,8 @@ function genXmlColorSelection(props) {
4173
4299
  else {
4174
4300
  if (props.type) fillType = props.type;
4175
4301
  if (props.color) colorVal = props.color;
4176
- if (props.alpha) internalElements += `<a:alpha val="${Math.round((100 - props.alpha) * 1e3)}"/>`;
4177
- if (props.transparency) internalElements += `<a:alpha val="${Math.round((100 - props.transparency) * 1e3)}"/>`;
4302
+ if (props.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(props.alpha)}"/>`;
4303
+ if (props.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(props.transparency)}"/>`;
4178
4304
  }
4179
4305
  switch (fillType) {
4180
4306
  case "solid":
@@ -4768,6 +4894,28 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4768
4894
  return tableRowSlides;
4769
4895
  }
4770
4896
  /**
4897
+ * Convert a computed CSS border (width string + color string) from `getComputedStyle` into a
4898
+ * pptx `BorderProps`.
4899
+ *
4900
+ * Preserves *fractional* widths: a hairline CSS border such as `0.5px` must not be rounded to
4901
+ * `0pt` and silently vanish — the table serializer (`valToPts`) emits fractional points just
4902
+ * fine, so there is no reason to integer-round here (upstream gitbrent/PptxGenJS#1235). A
4903
+ * computed width of `0` (or a non-finite value) yields `{ type: 'none' }` so we never emit a
4904
+ * zero-width line.
4905
+ * @param {string} widthStr - computed `border-<side>-width`, e.g. `"0.5px"`
4906
+ * @param {string} colorStr - computed `border-<side>-color`, e.g. `"rgb(102, 102, 102)"`
4907
+ * @returns {BorderProps} border props for the cell side
4908
+ */
4909
+ function htmlBorderToProps(widthStr, colorStr) {
4910
+ const pt = Number(String(widthStr).replace("px", ""));
4911
+ if (!isFinite(pt) || pt <= 0) return { type: "none" };
4912
+ const arrRGB = String(colorStr).replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
4913
+ return {
4914
+ pt,
4915
+ color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
4916
+ };
4917
+ }
4918
+ /**
4771
4919
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
4772
4920
  * @param {TableToSlidesHost} pptx - pptxgenjs instance
4773
4921
  * @param {string} tabEleId - HTMLElementID of the table
@@ -4915,12 +5063,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4915
5063
  "bottom",
4916
5064
  "left"
4917
5065
  ].forEach((val, idxb) => {
4918
- const intBorderW = Math.round(Number(window.getComputedStyle(cell).getPropertyValue("border-" + val + "-width").replace("px", "")));
4919
- const arrRGB = window.getComputedStyle(cell).getPropertyValue("border-" + val + "-color").replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
4920
- cellBorder[idxb] = {
4921
- pt: intBorderW,
4922
- color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
4923
- };
5066
+ const style = window.getComputedStyle(cell);
5067
+ cellBorder[idxb] = htmlBorderToProps(style.getPropertyValue("border-" + val + "-width"), style.getPropertyValue("border-" + val + "-color"));
4924
5068
  });
4925
5069
  cellOpts.border = cellBorder;
4926
5070
  }
@@ -4990,6 +5134,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4990
5134
  */
4991
5135
  /** counter for included charts (used for index in their filenames) */
4992
5136
  let _chartCounter = 0;
5137
+ /** DPI PowerPoint assumes when sizing an inserted raster image (natural pixels / 96 == inches) */
5138
+ const IMAGE_NATURAL_DPI = 96;
4993
5139
  function normalizeBorderTuple(border) {
4994
5140
  return Array.isArray(border) ? border : [
4995
5141
  border,
@@ -5026,6 +5172,26 @@ function createSlideMaster(props, target) {
5026
5172
  if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
5027
5173
  }
5028
5174
  /**
5175
+ * Round and clamp an integer chart percentage/angle option into a schema-valid range.
5176
+ *
5177
+ * Several chart attributes are bounded integer types whose out-of-range values make
5178
+ * PowerPoint report the package as needing repair: `<c:overlap>` (ST_Overlap, -100..100),
5179
+ * `<c:gapWidth>`/`<c:gapDepth>` (ST_GapAmount, 0..500), `<c:holeSize>` (ST_HoleSize, 10..90)
5180
+ * and `<c:firstSliceAng>` (ST_FirstSliceAng, 0..360). Missing/non-numeric input returns
5181
+ * `undefined` so the caller can apply its own default; an out-of-range value is clamped
5182
+ * and a warning is emitted (per the library's warn-rather-than-degrade policy).
5183
+ * @param value - caller-supplied option value
5184
+ * @param min - inclusive lower bound
5185
+ * @param max - inclusive upper bound
5186
+ * @param name - option name, for the warning message
5187
+ */
5188
+ function clampChartPct(value, min, max, name) {
5189
+ if (typeof value !== "number" || isNaN(value)) return void 0;
5190
+ const clamped = Math.min(max, Math.max(min, Math.round(value)));
5191
+ if (clamped !== value) console.warn(`Warning: ${name} ${value} is outside the valid range ${min}-${max}; using ${clamped}.`);
5192
+ return clamped;
5193
+ }
5194
+ /**
5029
5195
  * Generate the chart based on input data.
5030
5196
  * OOXML Chart Spec: ISO/IEC 29500-1:2016(E)
5031
5197
  *
@@ -5197,7 +5363,13 @@ function addChartDefinition(target, type, data, opt) {
5197
5363
  "marker",
5198
5364
  "filled"
5199
5365
  ].includes(options.radarStyle || "")) options.radarStyle = "standard";
5200
- options.lineDataSymbolSize = options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize) ? options.lineDataSymbolSize : 6;
5366
+ {
5367
+ const rawSymbolSize = options.lineDataSymbolSize;
5368
+ const hasSymbolSize = rawSymbolSize != null && !isNaN(rawSymbolSize);
5369
+ const symbolSize = Math.min(72, Math.max(2, Math.round(hasSymbolSize ? rawSymbolSize : 6)));
5370
+ if (hasSymbolSize && symbolSize !== rawSymbolSize) console.warn(`Warning: lineDataSymbolSize ${rawSymbolSize} is outside the valid marker size range (integer 2-72); using ${symbolSize}.`);
5371
+ options.lineDataSymbolSize = symbolSize;
5372
+ }
5201
5373
  options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(.75);
5202
5374
  const chartLayout = options.layout;
5203
5375
  if (chartLayout) [
@@ -5247,8 +5419,11 @@ function addChartDefinition(target, type, data, opt) {
5247
5419
  options.v3DRotY = typeof options.v3DRotY === "number" && !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30;
5248
5420
  options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true;
5249
5421
  options.v3DPerspective = typeof options.v3DPerspective === "number" && !isNaN(options.v3DPerspective) && options.v3DPerspective >= 0 && options.v3DPerspective <= 240 ? options.v3DPerspective : 30;
5250
- options.barGapWidthPct = typeof options.barGapWidthPct === "number" && !isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1e3 ? options.barGapWidthPct : 150;
5251
- options.barGapDepthPct = typeof options.barGapDepthPct === "number" && !isNaN(options.barGapDepthPct) && options.barGapDepthPct >= 0 && options.barGapDepthPct <= 1e3 ? options.barGapDepthPct : 150;
5422
+ options.barGapWidthPct = clampChartPct(options.barGapWidthPct, 0, 500, "barGapWidthPct") ?? 150;
5423
+ options.barGapDepthPct = clampChartPct(options.barGapDepthPct, 0, 500, "barGapDepthPct") ?? 150;
5424
+ options.barOverlapPct = clampChartPct(options.barOverlapPct, -100, 100, "barOverlapPct");
5425
+ options.holeSize = clampChartPct(options.holeSize, 10, 90, "holeSize");
5426
+ options.firstSliceAng = clampChartPct(options.firstSliceAng, 0, 360, "firstSliceAng");
5252
5427
  options.chartColors = Array.isArray(options.chartColors) ? options.chartColors : options._type === "pie" || options._type === "doughnut" ? PIECHART_COLORS : BARCHART_COLORS;
5253
5428
  options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : void 0;
5254
5429
  options.border = options.border && typeof options.border === "object" ? options.border : void 0;
@@ -5337,16 +5512,29 @@ function addImageDefinition(target, opt) {
5337
5512
  else if (strImageData?.toLowerCase().includes("image/svg+xml")) strImgExtn = "svg";
5338
5513
  newObject._type = "image";
5339
5514
  newObject.image = strImagePath || "preencoded.png";
5515
+ let defWidth = intWidth;
5516
+ let defHeight = intHeight;
5517
+ if ((!intWidth || !intHeight) && strImageData && strImgExtn !== "svg") {
5518
+ const natural = getImageSizeFromBase64(strImageData);
5519
+ if (natural) {
5520
+ if (!intWidth && !intHeight) {
5521
+ defWidth = natural.w / IMAGE_NATURAL_DPI;
5522
+ defHeight = natural.h / IMAGE_NATURAL_DPI;
5523
+ } else if (typeof intWidth === "number" && intWidth && !intHeight) defHeight = intWidth * (natural.h / natural.w);
5524
+ else if (typeof intHeight === "number" && intHeight && !intWidth) defWidth = intHeight * (natural.w / natural.h);
5525
+ }
5526
+ }
5340
5527
  const objectOptions = {
5341
5528
  x: intPosX || 0,
5342
5529
  y: intPosY || 0,
5343
- w: intWidth || 1,
5344
- h: intHeight || 1,
5530
+ w: defWidth || 1,
5531
+ h: defHeight || 1,
5345
5532
  altText: opt.altText || "",
5346
5533
  rounding: typeof opt.rounding === "boolean" ? opt.rounding : false,
5347
5534
  shape: opt.shape,
5348
5535
  points: opt.points,
5349
5536
  rectRadius: opt.rectRadius,
5537
+ shapeAdjust: opt.shapeAdjust,
5350
5538
  sizing,
5351
5539
  placeholder: opt.placeholder,
5352
5540
  rotate: opt.rotate || 0,
@@ -5355,6 +5543,7 @@ function addImageDefinition(target, opt) {
5355
5543
  transparency: opt.transparency || 0,
5356
5544
  duotone: opt.duotone,
5357
5545
  objectName,
5546
+ objectLock: opt.objectLock,
5358
5547
  shadow: correctShadowOptions(opt.shadow)
5359
5548
  };
5360
5549
  newObject.options = objectOptions;
@@ -5384,7 +5573,10 @@ function addImageDefinition(target, opt) {
5384
5573
  });
5385
5574
  newObject.imageRid = imageRelId + 1;
5386
5575
  } else {
5387
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strImagePath && item.type === "image/" + strImgExtn && !item.isDuplicate);
5576
+ const dupeItem = target._relsMedia.find((item) => {
5577
+ if (item.isDuplicate || !item.Target || item.type !== "image/" + strImgExtn) return false;
5578
+ return strImagePath ? item.path === strImagePath : !!strImageData && item.data === strImageData;
5579
+ });
5388
5580
  target._relsMedia.push({
5389
5581
  path: strImagePath || "preencoded." + strImgExtn,
5390
5582
  type: "image/" + strImgExtn,
@@ -5442,6 +5634,7 @@ function addMediaDefinition(target, opt) {
5442
5634
  slideData.options.h = intSizeY;
5443
5635
  slideData.options.objectName = objectName;
5444
5636
  if (opt.altText) slideData.options.altText = opt.altText;
5637
+ if (opt.objectLock) slideData.options.objectLock = opt.objectLock;
5445
5638
  /**
5446
5639
  * NOTE:
5447
5640
  * - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1!
@@ -5471,7 +5664,10 @@ function addMediaDefinition(target, opt) {
5471
5664
  Target: `../media/image-${target._slideNum}-${target._relsMedia.length + 1}.png`
5472
5665
  });
5473
5666
  } else {
5474
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strPath && item.type === strType + "/" + strExtn && !item.isDuplicate);
5667
+ const dupeItem = target._relsMedia.find((item) => {
5668
+ if (item.isDuplicate || !item.Target || item.type !== strType + "/" + strExtn) return false;
5669
+ return strPath ? item.path === strPath : !!strData && item.data === strData;
5670
+ });
5475
5671
  const relId1 = getNewRelId(target);
5476
5672
  target._relsMedia.push({
5477
5673
  path: strPath || "preencoded" + strExtn,
@@ -5536,12 +5732,14 @@ function addShapeDefinition(target, shapeName, opts) {
5536
5732
  const options = typeof opts === "object" ? opts : {};
5537
5733
  options.line = options.line || { type: "none" };
5538
5734
  options.shadow = correctShadowOptions(options.shadow);
5735
+ const resolvedShapeName = typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName;
5539
5736
  const newObject = {
5540
5737
  _type: "text",
5541
- shape: (typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName) || "rect",
5738
+ shape: resolvedShapeName || "rect",
5542
5739
  options
5543
5740
  };
5544
5741
  if (!shapeName) throw new Error("Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`");
5742
+ 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.`);
5545
5743
  const newLineOpts = {
5546
5744
  type: options.line.type || "solid",
5547
5745
  color: options.line.color || "333333",
@@ -5638,9 +5836,8 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5638
5836
  }
5639
5837
  arrRows.push(newRow);
5640
5838
  });
5641
- opt.x = getSmartParseNumber(opt.x || (opt.x === 0 ? 0 : EMU / 2), "X", presLayout);
5642
- opt.y = getSmartParseNumber(opt.y || (opt.y === 0 ? 0 : EMU / 2), "Y", presLayout);
5643
- if (opt.h) opt.h = getSmartParseNumber(opt.h, "Y", presLayout);
5839
+ if (opt.x === void 0 || opt.x === null) opt.x = .5;
5840
+ if (opt.y === void 0 || opt.y === null) opt.y = .5;
5644
5841
  opt.fontSize = opt.fontSize || 12;
5645
5842
  opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN;
5646
5843
  if (typeof opt.margin === "number") opt.margin = [
@@ -5709,12 +5906,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5709
5906
  console.warn("addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.");
5710
5907
  opt.colW = void 0;
5711
5908
  }
5712
- } else if (opt.w) opt.w = getSmartParseNumber(opt.w, "X", presLayout);
5713
- else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
5714
- if (opt.x && opt.x < 20) opt.x = inch2Emu(opt.x);
5715
- if (opt.y && opt.y < 20) opt.y = inch2Emu(opt.y);
5716
- if (opt.w && typeof opt.w === "number" && opt.w < 20) opt.w = inch2Emu(opt.w);
5717
- if (opt.h && typeof opt.h === "number" && opt.h < 20) opt.h = inch2Emu(opt.h);
5909
+ } else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
5718
5910
  arrRows.forEach((row) => {
5719
5911
  row.forEach((cell, idy) => {
5720
5912
  if (typeof cell === "number" || typeof cell === "string") row[idy] = {
@@ -5742,7 +5934,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5742
5934
  if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
5743
5935
  getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
5744
5936
  if (!getSlide(target._slideNum + idx)) slides.push(addSlide({ masterName: slideLayout?._name || void 0 }));
5745
- if (idx > 0) opt.y = inch2Emu(opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0]);
5937
+ if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
5746
5938
  {
5747
5939
  const newSlide = getSlide(target._slideNum + idx);
5748
5940
  opt.autoPage = false;
@@ -5814,6 +6006,10 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
5814
6006
  itemOpts._bodyProp.anchor = !itemOpts.placeholder ? "ctr" : void 0;
5815
6007
  itemOpts._bodyProp.vert = itemOpts.vert;
5816
6008
  itemOpts._bodyProp.wrap = typeof itemOpts.wrap === "boolean" ? itemOpts.wrap : true;
6009
+ 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)");
6010
+ else itemOpts._bodyProp.numCol = Math.round(itemOpts.columns);
6011
+ 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)");
6012
+ else itemOpts._bodyProp.spcCol = valToPts(itemOpts.columnSpacing);
5817
6013
  if (itemOpts.inset && !isNaN(Number(itemOpts.inset)) || itemOpts.inset === 0) {
5818
6014
  itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset);
5819
6015
  itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset);
@@ -6323,6 +6519,22 @@ async function createExcelWorksheet(chartObject, zip) {
6323
6519
  });
6324
6520
  }
6325
6521
  /**
6522
+ * Emit the `<a:latin>/<a:ea>/<a:cs>` font trio for a chart text run.
6523
+ *
6524
+ * In DrawingML run properties a typeface applies only to the script class of
6525
+ * its element: `<a:latin>` covers Latin/ASCII, `<a:ea>` covers East Asian, and
6526
+ * `<a:cs>` covers complex scripts. Emitting `<a:latin>` alone leaves East Asian
6527
+ * (e.g. Chinese) and complex-script glyphs falling back to the theme font, so a
6528
+ * user-specified font never takes effect for that text — most visibly on
6529
+ * PowerPoint for Mac. Stamping the same typeface onto all three classes is what
6530
+ * choosing a font in PowerPoint's UI does (upstream gitbrent/PptxGenJS#1420).
6531
+ * @param {string} typeface - font face name
6532
+ * @return {string} `<a:latin/><a:ea/><a:cs/>` XML
6533
+ */
6534
+ function createChartTextFonts(typeface) {
6535
+ return `<a:latin typeface="${typeface}"/><a:ea typeface="${typeface}"/><a:cs typeface="${typeface}"/>`;
6536
+ }
6537
+ /**
6326
6538
  * Main entry point method for create charts
6327
6539
  * @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
6328
6540
  * @param {ISlideRelChart} rel - chart object
@@ -6332,6 +6544,10 @@ function makeXmlCharts(rel) {
6332
6544
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
6333
6545
  let usesSecondaryValAxis = false;
6334
6546
  let usesSecondaryCatAxis = false;
6547
+ let primaryCatAxisValType = null;
6548
+ let secondaryCatAxisValType = null;
6549
+ let primaryCatAxisHasCategoryChart = false;
6550
+ let secondaryCatAxisHasCategoryChart = false;
6335
6551
  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\">";
6336
6552
  strXml += "<c:date1904 val=\"0\"/>";
6337
6553
  strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? "1" : "0"}"/>`;
@@ -6344,6 +6560,8 @@ function makeXmlCharts(rel) {
6344
6560
  fontSize: rel.opts.titleFontSize || 18,
6345
6561
  titleAlign: rel.opts.titleAlign,
6346
6562
  titleBold: rel.opts.titleBold,
6563
+ titleItalic: rel.opts.titleItalic,
6564
+ titleUnderline: rel.opts.titleUnderline,
6347
6565
  titlePos: rel.opts.titlePos,
6348
6566
  titleRotate: rel.opts.titleRotate
6349
6567
  }, rel.opts.x, rel.opts.y);
@@ -6376,18 +6594,37 @@ function makeXmlCharts(rel) {
6376
6594
  const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY;
6377
6595
  usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis;
6378
6596
  usesSecondaryCatAxis = usesSecondaryCatAxis || options.secondaryCatAxis;
6597
+ const usesValueXAxis = type.type === "scatter" || type.type === "bubble" || type.type === "bubble3D";
6598
+ if (options.secondaryCatAxis) if (usesValueXAxis) secondaryCatAxisValType = type.type;
6599
+ else secondaryCatAxisHasCategoryChart = true;
6600
+ else if (usesValueXAxis) primaryCatAxisValType = type.type;
6601
+ else primaryCatAxisHasCategoryChart = true;
6379
6602
  strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId);
6380
6603
  });
6381
6604
  else strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY);
6382
6605
  if (rel.opts._type !== "pie" && rel.opts._type !== "doughnut") {
6383
6606
  if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) throw new Error("Secondary axis must be used by one of the multiple charts");
6607
+ const comboCatAxisType = (isSecondary) => {
6608
+ const valType = isSecondary ? secondaryCatAxisValType : primaryCatAxisValType;
6609
+ const hasCategoryChart = isSecondary ? secondaryCatAxisHasCategoryChart : primaryCatAxisHasCategoryChart;
6610
+ if (!valType) return {};
6611
+ if (hasCategoryChart) {
6612
+ 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.`);
6613
+ return {};
6614
+ }
6615
+ return { _type: valType };
6616
+ };
6384
6617
  if (rel.opts.catAxes) {
6385
6618
  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.");
6386
6619
  strXml += makeCatAxis({
6387
6620
  ...rel.opts,
6388
- ...rel.opts.catAxes[0]
6621
+ ...rel.opts.catAxes[0],
6622
+ ...comboCatAxisType(false)
6389
6623
  }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6390
- } else strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6624
+ } else strXml += makeCatAxis({
6625
+ ...rel.opts,
6626
+ ...comboCatAxisType(false)
6627
+ }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6391
6628
  if (rel.opts.valAxes) {
6392
6629
  strXml += makeValAxis({
6393
6630
  ...rel.opts,
@@ -6404,9 +6641,13 @@ function makeXmlCharts(rel) {
6404
6641
  }
6405
6642
  if (rel.opts?.catAxes && rel.opts?.catAxes[1]) strXml += makeCatAxis({
6406
6643
  ...rel.opts,
6407
- ...rel.opts.catAxes[1]
6644
+ ...rel.opts.catAxes[1],
6645
+ ...comboCatAxisType(true)
6646
+ }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6647
+ else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis({
6648
+ ...rel.opts,
6649
+ ...comboCatAxisType(true)
6408
6650
  }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6409
- else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6410
6651
  }
6411
6652
  if (rel.opts.showDataTable) {
6412
6653
  strXml += "<c:dTable>";
@@ -6461,8 +6702,7 @@ function makeXmlCharts(rel) {
6461
6702
  strXml += " <a:pPr>";
6462
6703
  strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : "<a:defRPr>";
6463
6704
  if (rel.opts.legendColor) strXml += genXmlColorSelection(rel.opts.legendColor);
6464
- if (rel.opts.legendFontFace) strXml += "<a:latin typeface=\"" + rel.opts.legendFontFace + "\"/>";
6465
- if (rel.opts.legendFontFace) strXml += "<a:cs typeface=\"" + rel.opts.legendFontFace + "\"/>";
6705
+ if (rel.opts.legendFontFace) strXml += createChartTextFonts(rel.opts.legendFontFace);
6466
6706
  strXml += " </a:defRPr>";
6467
6707
  strXml += " </a:pPr>";
6468
6708
  strXml += " <a:endParaRPr lang=\"en-US\"/>";
@@ -6500,6 +6740,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6500
6740
  let idxColLtr = 1;
6501
6741
  let optsChartData;
6502
6742
  let strXml = "";
6743
+ const valFmtCode = encodeXmlEntities(opts.valLabelFormatCode || opts.dataTableFormatCode || opts.dataLabelFormatCode || "General");
6503
6744
  switch (chartType) {
6504
6745
  case "area":
6505
6746
  case "bar":
@@ -6543,7 +6784,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6543
6784
  } 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>`;
6544
6785
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6545
6786
  strXml += " </c:spPr>";
6546
- if (chartType !== "line" && chartType !== "radar") strXml += " <c:invertIfNegative val=\"0\"/>";
6787
+ if (chartType === "bar" || chartType === "bar3D") strXml += " <c:invertIfNegative val=\"0\"/>";
6547
6788
  if (chartType === "line" || chartType === "radar") {
6548
6789
  strXml += "<c:marker>";
6549
6790
  strXml += " <c:symbol val=\"" + opts.lineDataSymbol + "\"/>";
@@ -6558,6 +6799,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6558
6799
  strXml += " </c:spPr>";
6559
6800
  strXml += "</c:marker>";
6560
6801
  }
6802
+ {
6803
+ 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;
6804
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, barVaryColors);
6805
+ }
6561
6806
  if (chartType !== "radar") {
6562
6807
  const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
6563
6808
  const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
@@ -6565,12 +6810,15 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6565
6810
  const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
6566
6811
  const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
6567
6812
  strXml += "<c:dLbls>";
6813
+ if (obj.customLabels?.length) obj.customLabels.forEach((lbl, idx) => {
6814
+ if (lbl) strXml += makeCustomDLblXml(idx, lbl, opts);
6815
+ });
6568
6816
  strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
6569
6817
  if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
6570
6818
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6571
6819
  strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
6572
6820
  strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
6573
- strXml += `<a:latin typeface="${lblFace}"/>`;
6821
+ strXml += createChartTextFonts(lblFace);
6574
6822
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6575
6823
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6576
6824
  strXml += "<c:showLegendKey val=\"0\"/>";
@@ -6579,29 +6827,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6579
6827
  strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
6580
6828
  strXml += "</c:dLbls>";
6581
6829
  }
6582
- 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) => {
6583
- const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
6584
- strXml += " <c:dPt>";
6585
- strXml += ` <c:idx val="${index}"/>`;
6586
- strXml += " <c:invertIfNegative val=\"0\"/>";
6587
- strXml += " <c:bubble3D val=\"0\"/>";
6588
- strXml += " <c:spPr>";
6589
- if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6590
- else if (chartType === "bar") {
6591
- strXml += "<a:solidFill>";
6592
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6593
- strXml += "</a:solidFill>";
6594
- } else {
6595
- strXml += "<a:ln>";
6596
- strXml += " <a:solidFill>";
6597
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6598
- strXml += " </a:solidFill>";
6599
- strXml += "</a:ln>";
6600
- }
6601
- strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6602
- strXml += " </c:spPr>";
6603
- strXml += " </c:dPt>";
6604
- });
6605
6830
  strXml += "<c:cat>";
6606
6831
  if (opts.catLabelFormatCode) {
6607
6832
  strXml += " <c:numRef>";
@@ -6638,10 +6863,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6638
6863
  strXml += " <c:numRef>";
6639
6864
  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>`;
6640
6865
  strXml += " <c:numCache>";
6641
- strXml += " <c:formatCode>" + (opts.valLabelFormatCode || opts.dataTableFormatCode || "General") + "</c:formatCode>";
6866
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6642
6867
  strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`;
6643
6868
  obj.values.forEach((value, idx) => {
6644
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
6869
+ strXml += numCachePt(idx, value);
6645
6870
  });
6646
6871
  strXml += " </c:numCache>";
6647
6872
  strXml += " </c:numRef>";
@@ -6657,7 +6882,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6657
6882
  strXml += " <a:p><a:pPr>";
6658
6883
  strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6659
6884
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6660
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
6885
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6661
6886
  strXml += " </a:defRPr>";
6662
6887
  strXml += " </a:pPr></a:p>";
6663
6888
  strXml += " </c:txPr>";
@@ -6673,6 +6898,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6673
6898
  if (chartType === "bar") {
6674
6899
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6675
6900
  strXml += ` <c:overlap val="${opts.barOverlapPct != null ? opts.barOverlapPct : (opts.barGrouping || "").includes("tacked") ? 100 : 0}"/>`;
6901
+ strXml += createSerLinesElement(opts.barSeriesLine);
6676
6902
  } else if (chartType === "bar3D") {
6677
6903
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6678
6904
  strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`;
@@ -6724,6 +6950,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6724
6950
  strXml += "<a:effectLst/>";
6725
6951
  strXml += "</c:spPr>";
6726
6952
  strXml += "</c:marker>";
6953
+ {
6954
+ const scatterVaryColors = data.length === 1 && opts.chartColors !== BARCHART_COLORS ? opts.chartColors || BARCHART_COLORS : null;
6955
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, scatterVaryColors);
6956
+ }
6727
6957
  if (opts.showLabel) {
6728
6958
  const chartUuid = getUuid("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
6729
6959
  if (obj.labels[0] && (opts.dataLabelFormatScatter === "custom" || opts.dataLabelFormatScatter === "customXY")) {
@@ -6742,13 +6972,13 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6742
6972
  strXml += " <a:pPr>";
6743
6973
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
6744
6974
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6745
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
6975
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6746
6976
  strXml += " </a:defRPr>";
6747
6977
  strXml += " </a:pPr>";
6748
6978
  strXml += " <a:r>";
6749
6979
  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">`;
6750
6980
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6751
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
6981
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6752
6982
  strXml += " </a:rPr>";
6753
6983
  strXml += " <a:t>" + encodeXmlEntities(label) + "</a:t>";
6754
6984
  strXml += " </a:r>";
@@ -6828,7 +7058,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6828
7058
  strXml += " <a:pPr>";
6829
7059
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
6830
7060
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6831
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7061
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6832
7062
  strXml += " </a:defRPr>";
6833
7063
  strXml += " </a:pPr>";
6834
7064
  strXml += ` <a:endParaRPr lang="${opts.lang || "en-US"}"/>`;
@@ -6849,31 +7079,14 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6849
7079
  strXml += "</c:dLbls>";
6850
7080
  }
6851
7081
  }
6852
- if (data.length === 1 && opts.chartColors !== BARCHART_COLORS) obj.values.forEach((value, index) => {
6853
- const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || [];
6854
- strXml += " <c:dPt>";
6855
- strXml += ` <c:idx val="${index}"/>`;
6856
- strXml += " <c:invertIfNegative val=\"0\"/>";
6857
- strXml += " <c:bubble3D val=\"0\"/>";
6858
- strXml += " <c:spPr>";
6859
- if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
6860
- else {
6861
- strXml += "<a:solidFill>";
6862
- strXml += " <a:srgbClr val=\"" + arrColors[index % arrColors.length] + "\"/>";
6863
- strXml += "</a:solidFill>";
6864
- }
6865
- strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6866
- strXml += " </c:spPr>";
6867
- strXml += " </c:dPt>";
6868
- });
6869
7082
  strXml += "<c:xVal>";
6870
7083
  strXml += " <c:numRef>";
6871
7084
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6872
7085
  strXml += " <c:numCache>";
6873
- strXml += " <c:formatCode>General</c:formatCode>";
7086
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6874
7087
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6875
7088
  data[0].values.forEach((value, idx) => {
6876
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7089
+ strXml += numCachePt(idx, value);
6877
7090
  });
6878
7091
  strXml += " </c:numCache>";
6879
7092
  strXml += " </c:numRef>";
@@ -6882,10 +7095,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6882
7095
  strXml += " <c:numRef>";
6883
7096
  strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`;
6884
7097
  strXml += " <c:numCache>";
6885
- strXml += " <c:formatCode>General</c:formatCode>";
7098
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6886
7099
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6887
7100
  data[0].values.forEach((_value, idx) => {
6888
- if (obj.values[idx] != null) strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx]}</c:v></c:pt>`;
7101
+ strXml += numCachePt(idx, obj.values[idx]);
6889
7102
  });
6890
7103
  strXml += " </c:numCache>";
6891
7104
  strXml += " </c:numRef>";
@@ -6901,7 +7114,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6901
7114
  strXml += " <a:p><a:pPr>";
6902
7115
  strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
6903
7116
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6904
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
7117
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6905
7118
  strXml += " </a:defRPr>";
6906
7119
  strXml += " </a:pPr></a:p>";
6907
7120
  strXml += " </c:txPr>";
@@ -6951,10 +7164,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6951
7164
  strXml += " <c:numRef>";
6952
7165
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6953
7166
  strXml += " <c:numCache>";
6954
- strXml += " <c:formatCode>General</c:formatCode>";
7167
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6955
7168
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6956
7169
  data[0].values.forEach((value, idx) => {
6957
- strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
7170
+ strXml += numCachePt(idx, value);
6958
7171
  });
6959
7172
  strXml += " </c:numCache>";
6960
7173
  strXml += " </c:numRef>";
@@ -6964,10 +7177,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6964
7177
  strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`;
6965
7178
  idxColLtr++;
6966
7179
  strXml += " <c:numCache>";
6967
- strXml += " <c:formatCode>General</c:formatCode>";
7180
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6968
7181
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6969
7182
  data[0].values.forEach((_value, idx) => {
6970
- strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx] || obj.values[idx] === 0 ? obj.values[idx] : ""}</c:v></c:pt>`;
7183
+ strXml += numCachePt(idx, obj.values[idx]);
6971
7184
  });
6972
7185
  strXml += " </c:numCache>";
6973
7186
  strXml += " </c:numRef>";
@@ -6980,7 +7193,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6980
7193
  strXml += " <c:formatCode>General</c:formatCode>";
6981
7194
  strXml += ` <c:ptCount val="${obj.sizes.length}"/>`;
6982
7195
  obj.sizes.forEach((value, idx) => {
6983
- strXml += `<c:pt idx="${idx}"><c:v>${value ?? ""}</c:v></c:pt>`;
7196
+ strXml += numCachePt(idx, value);
6984
7197
  });
6985
7198
  strXml += " </c:numCache>";
6986
7199
  strXml += " </c:numRef>";
@@ -6993,12 +7206,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6993
7206
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6994
7207
  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">`;
6995
7208
  strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
6996
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7209
+ strXml += createChartTextFonts(opts.dataLabelFontFace || "Arial");
6997
7210
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6998
7211
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6999
7212
  strXml += "<c:showLegendKey val=\"0\"/>";
7000
7213
  strXml += `<c:showVal val="${opts.showValue ? "1" : "0"}"/>`;
7001
- strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="0"/>`;
7214
+ strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="${opts.showBubbleSize ? "1" : "0"}"/>`;
7002
7215
  strXml += "<c:extLst>";
7003
7216
  strXml += " <c:ext uri=\"{CE6537A1-D6FC-4f65-9D91-7224C49458BB}\" xmlns:c15=\"http://schemas.microsoft.com/office/drawing/2012/chart\">";
7004
7217
  strXml += " <c15:showLeaderLines val=\"" + (opts.showLeaderLines ? "1" : "0") + "\"/>";
@@ -7032,33 +7245,37 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7032
7245
  else strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7033
7246
  strXml += " </c:spPr>";
7034
7247
  optsChartData.labels[0].forEach((_label, idx) => {
7248
+ const ptStyle = optsChartData.pointStyles?.[idx];
7035
7249
  strXml += "<c:dPt>";
7036
7250
  strXml += ` <c:idx val="${idx}"/>`;
7037
7251
  strXml += " <c:bubble3D val=\"0\"/>";
7038
7252
  strXml += " <c:spPr>";
7039
- strXml += `<a:solidFill>${createColorElement(opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
7040
- 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>`;
7253
+ strXml += `<a:solidFill>${createColorElement(ptStyle?.fill || opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
7254
+ if (ptStyle?.border) strXml += createChartBorderLine(ptStyle.border);
7255
+ 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>`;
7041
7256
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7042
7257
  strXml += " </c:spPr>";
7043
7258
  strXml += "</c:dPt>";
7044
7259
  });
7045
7260
  strXml += "<c:dLbls>";
7046
7261
  optsChartData.labels[0].forEach((_label, idx) => {
7262
+ const customLbl = optsChartData.customLabels?.[idx];
7047
7263
  strXml += "<c:dLbl>";
7048
7264
  strXml += ` <c:idx val="${idx}"/>`;
7265
+ 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>`;
7049
7266
  strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
7050
7267
  strXml += " <c:spPr/><c:txPr>";
7051
7268
  strXml += " <a:bodyPr/><a:lstStyle/>";
7052
7269
  strXml += " <a:p><a:pPr>";
7053
7270
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
7054
7271
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
7055
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7272
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
7056
7273
  strXml += " </a:defRPr>";
7057
7274
  strXml += " </a:pPr></a:p>";
7058
7275
  strXml += " </c:txPr>";
7059
7276
  if (chartType === "pie" && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
7060
7277
  strXml += " <c:showLegendKey val=\"0\"/>";
7061
- strXml += " <c:showVal val=\"" + (opts.showValue ? "1" : "0") + "\"/>";
7278
+ strXml += " <c:showVal val=\"" + (customLbl ? "0" : opts.showValue ? "1" : "0") + "\"/>";
7062
7279
  strXml += " <c:showCatName val=\"" + (opts.showLabel ? "1" : "0") + "\"/>";
7063
7280
  strXml += " <c:showSerName val=\"" + (opts.showSerName ? "1" : "0") + "\"/>";
7064
7281
  strXml += " <c:showPercent val=\"" + (opts.showPercent ? "1" : "0") + "\"/>";
@@ -7073,7 +7290,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7073
7290
  strXml += " <a:pPr>";
7074
7291
  strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" b="${opts.dataLabelFontBold ? "1" : "0"}" i="${opts.dataLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
7075
7292
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
7076
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7293
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
7077
7294
  strXml += " </a:defRPr>";
7078
7295
  strXml += " </a:pPr>";
7079
7296
  strXml += " </a:p>";
@@ -7102,6 +7319,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7102
7319
  strXml += " <c:numRef>";
7103
7320
  strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`;
7104
7321
  strXml += " <c:numCache>";
7322
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
7105
7323
  strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`;
7106
7324
  optsChartData.values.forEach((value, idx) => {
7107
7325
  strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
@@ -7177,7 +7395,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
7177
7395
  strXml += " <a:pPr>";
7178
7396
  strXml += ` <a:defRPr sz="${Math.round((opts.catAxisLabelFontSize || 12) * 100)}" b="${opts.catAxisLabelFontBold ? 1 : 0}" i="${opts.catAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
7179
7397
  strXml += " <a:solidFill>" + createColorElement(opts.catAxisLabelColor || "000000") + "</a:solidFill>";
7180
- strXml += " <a:latin typeface=\"" + (opts.catAxisLabelFontFace || "Arial") + "\"/>";
7398
+ strXml += " " + createChartTextFonts(opts.catAxisLabelFontFace || "Arial");
7181
7399
  strXml += " </a:defRPr>";
7182
7400
  strXml += " </a:pPr>";
7183
7401
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7270,7 +7488,7 @@ function makeValAxis(opts, valAxisId) {
7270
7488
  strXml += " <a:pPr>";
7271
7489
  strXml += ` <a:defRPr sz="${Math.round((opts.valAxisLabelFontSize || 12) * 100)}" b="${opts.valAxisLabelFontBold ? 1 : 0}" i="${opts.valAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`;
7272
7490
  strXml += " <a:solidFill>" + createColorElement(opts.valAxisLabelColor || "000000") + "</a:solidFill>";
7273
- strXml += " <a:latin typeface=\"" + (opts.valAxisLabelFontFace || "Arial") + "\"/>";
7491
+ strXml += " " + createChartTextFonts(opts.valAxisLabelFontFace || "Arial");
7274
7492
  strXml += " </a:defRPr>";
7275
7493
  strXml += " </a:pPr>";
7276
7494
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7326,7 +7544,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
7326
7544
  strXml += " <a:pPr>";
7327
7545
  strXml += ` <a:defRPr sz="${Math.round((opts.serAxisLabelFontSize || 12) * 100)}" b="${opts.serAxisLabelFontBold ? "1" : "0"}" i="${opts.serAxisLabelFontItalic ? "1" : "0"}" u="none" strike="noStrike">`;
7328
7546
  strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || "000000")}</a:solidFill>`;
7329
- strXml += ` <a:latin typeface="${opts.serAxisLabelFontFace || "Arial"}"/>`;
7547
+ strXml += " " + createChartTextFonts(opts.serAxisLabelFontFace || "Arial");
7330
7548
  strXml += " </a:defRPr>";
7331
7549
  strXml += " </a:pPr>";
7332
7550
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7366,17 +7584,31 @@ function genXmlTitle(opts, chartX, chartY) {
7366
7584
  const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : "<a:bodyPr/>";
7367
7585
  const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : "";
7368
7586
  const titleBold = opts.titleBold ? 1 : 0;
7587
+ const titleItalic = opts.titleItalic ? 1 : 0;
7588
+ const titleUnderline = opts.titleUnderline ? "sng" : "none";
7369
7589
  let layout = "<c:layout/>";
7370
- if (opts.titlePos && typeof opts.titlePos.x === "number" && typeof opts.titlePos.y === "number") {
7371
- const totalX = opts.titlePos.x + chartX;
7372
- const totalY = opts.titlePos.y + chartY;
7373
- let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
7374
- if (valX >= 1) valX = valX / 10;
7375
- if (valX >= .1) valX = valX / 10;
7376
- let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
7377
- if (valY >= 1) valY = valY / 10;
7378
- if (valY >= .1) valY = valY / 10;
7379
- 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>`;
7590
+ const hasX = opts.titlePos && typeof opts.titlePos.x === "number";
7591
+ const hasY = opts.titlePos && typeof opts.titlePos.y === "number";
7592
+ if (hasX || hasY) {
7593
+ let modes = "";
7594
+ let vals = "";
7595
+ if (hasX) {
7596
+ const totalX = opts.titlePos.x + chartX;
7597
+ let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
7598
+ if (valX >= 1) valX = valX / 10;
7599
+ if (valX >= .1) valX = valX / 10;
7600
+ modes += "<c:xMode val=\"edge\"/>";
7601
+ vals += `<c:x val="${valX}"/>`;
7602
+ }
7603
+ if (hasY) {
7604
+ const totalY = opts.titlePos.y + chartY;
7605
+ let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
7606
+ if (valY >= 1) valY = valY / 10;
7607
+ if (valY >= .1) valY = valY / 10;
7608
+ modes += "<c:yMode val=\"edge\"/>";
7609
+ vals += `<c:y val="${valY}"/>`;
7610
+ }
7611
+ layout = `<c:layout><c:manualLayout>${modes}${vals}</c:manualLayout></c:layout>`;
7380
7612
  }
7381
7613
  return `<c:title>
7382
7614
  <c:tx>
@@ -7385,15 +7617,15 @@ function genXmlTitle(opts, chartX, chartY) {
7385
7617
  <a:lstStyle/>
7386
7618
  <a:p>
7387
7619
  ${align}
7388
- <a:defRPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7620
+ <a:defRPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7389
7621
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7390
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7622
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7391
7623
  </a:defRPr>
7392
7624
  </a:pPr>
7393
7625
  <a:r>
7394
- <a:rPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7626
+ <a:rPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7395
7627
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7396
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7628
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7397
7629
  </a:rPr>
7398
7630
  <a:t>${encodeXmlEntities(opts.title) || ""}</a:t>
7399
7631
  </a:r>
@@ -7467,11 +7699,105 @@ function createGridLineElement(glOpts) {
7467
7699
  strXml += "</c:majorGridlines>";
7468
7700
  return strXml;
7469
7701
  }
7470
- function createLineCap(lineCap) {
7471
- if (!lineCap || lineCap === "flat") return "flat";
7472
- else if (lineCap === "square") return "sq";
7473
- else if (lineCap === "round") return "rnd";
7474
- else throw new Error(`Invalid chart line cap: ${lineCap}`);
7702
+ /**
7703
+ * Build a `<c:pt>` numeric-cache data point, or '' to leave a gap.
7704
+ *
7705
+ * `<c:v>` inside a `<c:numCache>` is an `xsd:double`; emitting `NaN`, `Infinity`
7706
+ * or an empty string yields an invalid value that makes PowerPoint report the
7707
+ * package as needing repair. Null/undefined are intentional gaps and are skipped
7708
+ * silently (a sparse, idx-keyed cache is valid); other non-finite numbers are
7709
+ * skipped with a warning, per the library's "warn rather than emit a degenerate
7710
+ * result" policy.
7711
+ * @param idx - zero-based data-point index (emitted as `idx`)
7712
+ * @param value - numeric value (or null/undefined gap)
7713
+ */
7714
+ function numCachePt(idx, value) {
7715
+ if (value == null) return "";
7716
+ if (!Number.isFinite(value)) {
7717
+ console.warn(`Warning: chart value "${value}" at index ${idx} is not a finite number; data point omitted.`);
7718
+ return "";
7719
+ }
7720
+ return `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7721
+ }
7722
+ /**
7723
+ * Build a `<c:serLines>` ("Series Lines") element for a bar chart.
7724
+ * @param opt - `true` for PowerPoint automatic styling, an {@link OptsChartGridLine}
7725
+ * to customize the line, or falsy / `{ style: 'none' }` to omit the element.
7726
+ */
7727
+ function createSerLinesElement(opt) {
7728
+ if (!opt) return "";
7729
+ if (opt === true) return "<c:serLines/>";
7730
+ if (opt.style === "none") return "";
7731
+ let strXml = "<c:serLines><c:spPr>";
7732
+ strXml += `<a:ln w="${valToPts(opt.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(opt.cap || DEF_CHART_GRIDLINE.cap)}">`;
7733
+ strXml += `<a:solidFill><a:srgbClr val="${opt.color || DEF_CHART_GRIDLINE.color}"/></a:solidFill>`;
7734
+ strXml += `<a:prstDash val="${opt.style || DEF_CHART_GRIDLINE.style}"/><a:round/>`;
7735
+ strXml += "</a:ln></c:spPr></c:serLines>";
7736
+ return strXml;
7737
+ }
7738
+ function makeCustomDLblXml(idx, text, opts) {
7739
+ const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
7740
+ const bold = opts.dataLabelFontBold ? "1" : "0";
7741
+ const italic = opts.dataLabelFontItalic ? "1" : "0";
7742
+ const color = createColorElement(opts.dataLabelColor || "000000");
7743
+ const face = opts.dataLabelFontFace || "Arial";
7744
+ const lang = opts.lang || "en-US";
7745
+ 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>`;
7746
+ }
7747
+ /**
7748
+ * Build an `<a:ln>` border element from a per-data-point `BorderProps`.
7749
+ * @param border - point border style (`type`, `color`, `pt`)
7750
+ */
7751
+ function createChartBorderLine(border) {
7752
+ if (border.type === "none") return "<a:ln><a:noFill/></a:ln>";
7753
+ const dash = border.type === "dash" ? "dash" : "solid";
7754
+ 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>`;
7755
+ }
7756
+ /**
7757
+ * Build `<c:dPt>` entries for a series in the bar/line/area/scatter loops.
7758
+ *
7759
+ * Merges two sources into a single `c:dPt` per index so we never emit a
7760
+ * duplicate `<c:idx>` (which corrupts the chart):
7761
+ * - legacy single-series color-vary fills (bar/scatter), supplied via `varyColors`
7762
+ * - per-point `pointStyles` border/fill overrides
7763
+ *
7764
+ * Must be emitted in schema position *before* `c:dLbls` (CT_*Ser order).
7765
+ * RADAR is skipped: extra per-point markup historically corrupts the chart.
7766
+ *
7767
+ * @param chartType - series chart type
7768
+ * @param obj - series data (reads `values`, `pointStyles`)
7769
+ * @param opts - chart options (fill/shadow/lineSize context)
7770
+ * @param varyColors - color array when single-series color-vary applies, else `null`
7771
+ */
7772
+ function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
7773
+ if (chartType === "radar") return "";
7774
+ const pointStyles = obj.pointStyles;
7775
+ if (!varyColors && !pointStyles?.length) return "";
7776
+ const isBar = chartType === "bar" || chartType === "bar3D";
7777
+ const isScatter = chartType === "scatter";
7778
+ let xml = "";
7779
+ obj.values.forEach((value, index) => {
7780
+ const ptStyle = pointStyles?.[index];
7781
+ const arrColors = varyColors ? value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : varyColors : null;
7782
+ const fillColor = ptStyle?.fill || (arrColors ? arrColors[index % arrColors.length] : null);
7783
+ const border = ptStyle?.border;
7784
+ if (!fillColor && !border) return;
7785
+ xml += "<c:dPt>";
7786
+ xml += `<c:idx val="${index}"/>`;
7787
+ if (isBar) xml += "<c:invertIfNegative val=\"0\"/>";
7788
+ xml += "<c:bubble3D val=\"0\"/>";
7789
+ xml += "<c:spPr>";
7790
+ if ((isBar || isScatter) && opts.lineSize === 0 && !border && !ptStyle?.fill) xml += "<a:ln><a:noFill/></a:ln>";
7791
+ else {
7792
+ if (fillColor) if (chartType === "bar3D") xml += `<a:ln><a:solidFill>${createColorElement(fillColor)}</a:solidFill></a:ln>`;
7793
+ else xml += `<a:solidFill>${createColorElement(fillColor)}</a:solidFill>`;
7794
+ if (border) xml += createChartBorderLine(border);
7795
+ }
7796
+ xml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7797
+ xml += "</c:spPr>";
7798
+ xml += "</c:dPt>";
7799
+ });
7800
+ return xml;
7475
7801
  }
7476
7802
  //#endregion
7477
7803
  //#region src/gen-media.ts
@@ -7520,6 +7846,37 @@ function encodeSlideMediaRels(layout, runtime) {
7520
7846
  /**
7521
7847
  * PptxGenJS: XML Generation
7522
7848
  */
7849
+ const _warnedTextRangeMsgs = /* @__PURE__ */ new Set();
7850
+ function warnTextRangeOnce(msg) {
7851
+ if (_warnedTextRangeMsgs.has(msg)) return;
7852
+ _warnedTextRangeMsgs.add(msg);
7853
+ console.warn(msg);
7854
+ }
7855
+ /**
7856
+ * Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
7857
+ * hundredths of a point for the `sz` attribute. Out-of-range sizes make
7858
+ * PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
7859
+ */
7860
+ function clampFontSizeSz(fontSizePts) {
7861
+ const raw = Math.round(fontSizePts * 100);
7862
+ const clamped = Math.min(4e5, Math.max(100, raw));
7863
+ if (clamped !== raw) warnTextRangeOnce(`Warning: fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
7864
+ return clamped;
7865
+ }
7866
+ /** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
7867
+ function clampCharSpacingSpc(charSpacingPts) {
7868
+ const raw = Math.round(charSpacingPts * 100);
7869
+ const clamped = Math.min(4e5, Math.max(-4e5, raw));
7870
+ if (clamped !== raw) warnTextRangeOnce(`Warning: charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
7871
+ return clamped;
7872
+ }
7873
+ /** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
7874
+ function clampLineSpacingPts(lineSpacingPts) {
7875
+ const raw = Math.round(lineSpacingPts * 100);
7876
+ const clamped = Math.min(158400, Math.max(0, raw));
7877
+ if (clamped !== raw) warnTextRangeOnce(`Warning: lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
7878
+ return clamped;
7879
+ }
7523
7880
  const ImageSizingXml = {
7524
7881
  cover: function(imgSize, boxDim) {
7525
7882
  const imgRatio = imgSize.h / imgSize.w;
@@ -7566,23 +7923,90 @@ const ImageSizingXml = {
7566
7923
  * @return {string} `<a:prstGeom>` XML
7567
7924
  */
7568
7925
  const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
7926
+ const SHAPE_LOCK_ATTRS = [
7927
+ "noGrp",
7928
+ "noSelect",
7929
+ "noRot",
7930
+ "noChangeAspect",
7931
+ "noMove",
7932
+ "noResize",
7933
+ "noEditPoints",
7934
+ "noAdjustHandles",
7935
+ "noChangeArrowheads",
7936
+ "noChangeShapeType",
7937
+ "noTextEdit"
7938
+ ];
7939
+ const PICTURE_LOCK_ATTRS = [
7940
+ "noGrp",
7941
+ "noSelect",
7942
+ "noRot",
7943
+ "noChangeAspect",
7944
+ "noMove",
7945
+ "noResize",
7946
+ "noEditPoints",
7947
+ "noAdjustHandles",
7948
+ "noChangeArrowheads",
7949
+ "noChangeShapeType",
7950
+ "noCrop"
7951
+ ];
7952
+ const GRAPHIC_FRAME_LOCK_ATTRS = [
7953
+ "noGrp",
7954
+ "noDrilldown",
7955
+ "noSelect",
7956
+ "noChangeAspect",
7957
+ "noMove",
7958
+ "noResize"
7959
+ ];
7960
+ /**
7961
+ * Serialize an object-lock element (`a:spLocks` / `a:picLocks` / `a:graphicFrameLocks`).
7962
+ * Only flags set to `true` AND valid for this element type are emitted; a flag set on an
7963
+ * unsupported element type is dropped with a warning (silent coercion is a footgun).
7964
+ * @param tag - locking element tag, e.g. `'a:spLocks'`
7965
+ * @param allowed - attribute names this element type supports, in desired emit order
7966
+ * @param locks - merged lock flags (callers fold any hard-coded default in first)
7967
+ * @param objectName - for the warning message
7968
+ * @returns the locking element string, or `''` when no applicable flag is set
7969
+ */
7970
+ function genXmlObjectLock(tag, allowed, locks, objectName) {
7971
+ if (!locks) return "";
7972
+ const lockMap = locks;
7973
+ 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.`);
7974
+ const attrs = allowed.filter((name) => lockMap[name] === true).map((name) => `${name}="1"`);
7975
+ return attrs.length > 0 ? `<${tag} ${attrs.join(" ")}/>` : "";
7976
+ }
7569
7977
  function genXmlPresetGeom(shapeName, options, cx, cy) {
7570
- let strXml = `<a:prstGeom prst="${shapeName}"><a:avLst>`;
7978
+ 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.`);
7979
+ let avLst = "";
7980
+ const emittedAdjNames = /* @__PURE__ */ new Set();
7981
+ const emitGuide = (name, fmlaVal) => {
7982
+ avLst += `<a:gd name="${name}" fmla="val ${fmlaVal}"/>`;
7983
+ emittedAdjNames.add(name);
7984
+ };
7571
7985
  if (options.rectRadius) {
7572
7986
  const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
7573
7987
  if (RECT_RADIUS_ADJ1_SHAPES.has(shapeName)) {
7574
- strXml += `<a:gd name="adj1" fmla="val ${adjVal}"/>`;
7575
- strXml += "<a:gd name=\"adj2\" fmla=\"val 0\"/>";
7576
- } else strXml += `<a:gd name="adj" fmla="val ${adjVal}"/>`;
7988
+ emitGuide("adj1", adjVal);
7989
+ emitGuide("adj2", 0);
7990
+ } else emitGuide("adj", adjVal);
7577
7991
  } else if (options.angleRange) {
7578
7992
  for (let i = 0; i < 2; i++) {
7579
7993
  const angle = options.angleRange[i];
7580
- strXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`;
7994
+ emitGuide(`adj${i + 1}`, convertRotationDegrees(angle));
7581
7995
  }
7582
- if (options.arcThicknessRatio) strXml += `<a:gd name="adj3" fmla="val ${Math.round(options.arcThicknessRatio * 5e4)}" />`;
7996
+ if (options.arcThicknessRatio) emitGuide("adj3", Math.round(options.arcThicknessRatio * 5e4));
7583
7997
  }
7584
- strXml += "</a:avLst></a:prstGeom>";
7585
- return strXml;
7998
+ if (options.shapeAdjust) (Array.isArray(options.shapeAdjust) ? options.shapeAdjust : [options.shapeAdjust]).forEach((adj) => {
7999
+ if (!adj || typeof adj.name !== "string" || adj.name.length === 0 || typeof adj.value !== "number" || !isFinite(adj.value)) {
8000
+ console.warn(`Warning: shapeAdjust entry ${JSON.stringify(adj)} is invalid (needs { name:string, value:number }) and was ignored.`);
8001
+ return;
8002
+ }
8003
+ if (emittedAdjNames.has(adj.name)) {
8004
+ console.warn(`Warning: shapeAdjust "${adj.name}" was ignored because rectRadius/angleRange already set that handle.`);
8005
+ return;
8006
+ }
8007
+ emitGuide(adj.name, Math.round(adj.value * 1e5));
8008
+ });
8009
+ return `<a:prstGeom prst="${shapeName}"><a:avLst>${avLst}</a:avLst></a:prstGeom>`;
7586
8010
  }
7587
8011
  /**
7588
8012
  * Emit an `<a:custGeom>` for a freeform path built from `points`.
@@ -7635,6 +8059,45 @@ function genXmlCustGeom(points, cx, cy, layout) {
7635
8059
  }
7636
8060
  const PLACEHOLDER_TYPE_MAP = PLACEHOLDER_TYPES;
7637
8061
  /**
8062
+ * Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
8063
+ * Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
8064
+ * outer edges render with the same border as its origin cell (Issue #680).
8065
+ * @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
8066
+ * @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
8067
+ */
8068
+ function genTableCellBorderXml(cellBorder) {
8069
+ let strXml = "";
8070
+ [
8071
+ {
8072
+ idx: 3,
8073
+ name: "lnL"
8074
+ },
8075
+ {
8076
+ idx: 1,
8077
+ name: "lnR"
8078
+ },
8079
+ {
8080
+ idx: 0,
8081
+ name: "lnT"
8082
+ },
8083
+ {
8084
+ idx: 2,
8085
+ name: "lnB"
8086
+ }
8087
+ ].forEach((obj) => {
8088
+ const border = cellBorder[obj.idx];
8089
+ if (!border) return;
8090
+ const cap = createLineCap(border.cap);
8091
+ if (border.type !== "none") {
8092
+ strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="${cap}" cmpd="sng" algn="ctr">`;
8093
+ strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
8094
+ 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"/>`;
8095
+ strXml += `</a:${obj.name}>`;
8096
+ } else strXml += `<a:${obj.name} w="0" cap="${cap}" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
8097
+ });
8098
+ return strXml;
8099
+ }
8100
+ /**
7638
8101
  * Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
7639
8102
  * @param {PresSlideInternal|SlideLayoutInternal} slideObject - slide object created within createSlideObject
7640
8103
  * @return {string} XML string with <p:cSld> as the root
@@ -7693,7 +8156,10 @@ function slideObjectToXml(slide) {
7693
8156
  intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1;
7694
8157
  });
7695
8158
  strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
7696
- 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>";
8159
+ strXml += `<p:cNvGraphicFramePr>${genXmlObjectLock("a:graphicFrameLocks", GRAPHIC_FRAME_LOCK_ATTRS, {
8160
+ noGrp: true,
8161
+ ...slideItemObj.options.objectLock
8162
+ }, 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>`;
7697
8163
  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>`;
7698
8164
  {
7699
8165
  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\"" : "");
@@ -7725,7 +8191,8 @@ function slideObjectToXml(slide) {
7725
8191
  return {
7726
8192
  _type: "tablecell",
7727
8193
  options: { rowspan },
7728
- _hmerge: true
8194
+ _hmerge: true,
8195
+ _spanOrigin: cell
7729
8196
  };
7730
8197
  });
7731
8198
  cells.splice(cIdx + 1, 0, ...vMergeCells);
@@ -7741,12 +8208,14 @@ function slideObjectToXml(slide) {
7741
8208
  const colspan = cell.options?.colspan;
7742
8209
  const _hmerge = cell._hmerge;
7743
8210
  if (rowspan && rowspan > 1) {
8211
+ const _spanOrigin = cell._spanOrigin || cell;
7744
8212
  const hMergeCell = {
7745
8213
  _type: "tablecell",
7746
8214
  options: { colspan },
7747
8215
  _rowContinue: rowspan - 1,
7748
8216
  _vmerge: true,
7749
- _hmerge
8217
+ _hmerge,
8218
+ _spanOrigin
7750
8219
  };
7751
8220
  nextRow.splice(cIdx, 0, hMergeCell);
7752
8221
  }
@@ -7756,7 +8225,7 @@ function slideObjectToXml(slide) {
7756
8225
  let intRowH = 0;
7757
8226
  if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]));
7758
8227
  else if (objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH))) intRowH = inch2Emu(Number(objTabOpts.rowH));
7759
- 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);
8228
+ 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);
7760
8229
  strXml += `<a:tr h="${intRowH}">`;
7761
8230
  cells.forEach((cellObj) => {
7762
8231
  const cell = cellObj;
@@ -7769,7 +8238,17 @@ function slideObjectToXml(slide) {
7769
8238
  let cellSpanAttrStr = Object.entries(cellSpanAttrs).filter(([, v]) => !!v).map(([k, v]) => `${String(k)}="${String(v)}"`).join(" ");
7770
8239
  if (cellSpanAttrStr) cellSpanAttrStr = " " + cellSpanAttrStr;
7771
8240
  if (cell._hmerge || cell._vmerge) {
7772
- strXml += `<a:tc${cellSpanAttrStr}><a:tcPr/></a:tc>`;
8241
+ const origin = cell._spanOrigin;
8242
+ let spanPrXml = "";
8243
+ if (origin) {
8244
+ const originOpts = origin.options || {};
8245
+ const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
8246
+ if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
8247
+ let spanFill = origin._optImp?.fill?.color ? origin._optImp.fill.color : origin._optImp?.fill && typeof origin._optImp.fill === "string" ? origin._optImp.fill : "";
8248
+ spanFill = spanFill || originOpts.fill ? originOpts.fill : "";
8249
+ if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
8250
+ }
8251
+ strXml += `<a:tc${cellSpanAttrStr}><a:tcPr>${spanPrXml}</a:tcPr></a:tc>`;
7773
8252
  return;
7774
8253
  }
7775
8254
  const cellOpts = cell.options || {};
@@ -7813,32 +8292,7 @@ function slideObjectToXml(slide) {
7813
8292
  else cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(cellMargin[2])}"`;
7814
8293
  strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`;
7815
8294
  const cellBorder = Array.isArray(cellOpts.border) ? cellOpts.border : null;
7816
- if (cellBorder) [
7817
- {
7818
- idx: 3,
7819
- name: "lnL"
7820
- },
7821
- {
7822
- idx: 1,
7823
- name: "lnR"
7824
- },
7825
- {
7826
- idx: 0,
7827
- name: "lnT"
7828
- },
7829
- {
7830
- idx: 2,
7831
- name: "lnB"
7832
- }
7833
- ].forEach((obj) => {
7834
- const border = cellBorder[obj.idx];
7835
- if (border.type !== "none") {
7836
- strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="flat" cmpd="sng" algn="ctr">`;
7837
- strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
7838
- 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"/>`;
7839
- strXml += `</a:${obj.name}>`;
7840
- } else strXml += `<a:${obj.name} w="0" cap="flat" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
7841
- });
8295
+ if (cellBorder) strXml += genTableCellBorderXml(cellBorder);
7842
8296
  strXml += cellFill;
7843
8297
  strXml += " </a:tcPr>";
7844
8298
  strXml += " </a:tc>";
@@ -7857,10 +8311,10 @@ function slideObjectToXml(slide) {
7857
8311
  if (!slideItemObj.options.line && cy === 0) cy = EMU * .3;
7858
8312
  if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {};
7859
8313
  if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
7860
- slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[0] || 0);
8314
+ slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[0] || 0);
7861
8315
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0);
7862
8316
  slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0);
7863
- slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[3] || 0);
8317
+ slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[3] || 0);
7864
8318
  } else if (typeof slideItemObj.options.margin === "number") {
7865
8319
  slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin);
7866
8320
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin);
@@ -7872,7 +8326,11 @@ function slideObjectToXml(slide) {
7872
8326
  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) : ""}"/>`;
7873
8327
  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"/>`;
7874
8328
  strSlideXml += "</p:cNvPr>";
7875
- strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"/>" : "/>");
8329
+ {
8330
+ const spLockXml = genXmlObjectLock("a:spLocks", SHAPE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName);
8331
+ strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"" : "");
8332
+ strSlideXml += spLockXml ? `>${spLockXml}</p:cNvSpPr>` : "/>";
8333
+ }
7876
8334
  strSlideXml += `<p:nvPr>${slideItemObj._type === "placeholder" ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`;
7877
8335
  strSlideXml += "</p:nvSpPr><p:spPr>";
7878
8336
  strSlideXml += `<a:xfrm${locationAttr}>`;
@@ -7882,7 +8340,8 @@ function slideObjectToXml(slide) {
7882
8340
  else strSlideXml += genXmlPresetGeom(slideItemObj.shape, slideItemObj.options, cx, cy);
7883
8341
  strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : "<a:noFill/>";
7884
8342
  if (slideItemObj.options.line) {
7885
- strSlideXml += slideItemObj.options.line.width ? `<a:ln w="${valToPts(slideItemObj.options.line.width)}">` : "<a:ln>";
8343
+ const lnAttrs = (slideItemObj.options.line.width ? ` w="${lineWidthToEmu(slideItemObj.options.line.width)}"` : "") + (slideItemObj.options.line.cap ? ` cap="${createLineCap(slideItemObj.options.line.cap)}"` : "");
8344
+ strSlideXml += `<a:ln${lnAttrs}>`;
7886
8345
  if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line);
7887
8346
  if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`;
7888
8347
  if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`;
@@ -7915,7 +8374,10 @@ function slideObjectToXml(slide) {
7915
8374
  if (slideItemObj.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}"/>`;
7916
8375
  if (slideItemObj.hyperlink?.slide) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}" action="ppaction://hlinksldjump"/>`;
7917
8376
  strSlideXml += " </p:cNvPr>";
7918
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
8377
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8378
+ noChangeAspect: true,
8379
+ ...slideItemObj.options.objectLock
8380
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7919
8381
  strSlideXml += " <p:nvPr>" + genXmlPlaceholder(placeholderObj) + "</p:nvPr>";
7920
8382
  strSlideXml += " </p:nvPicPr>";
7921
8383
  strSlideXml += "<p:blipFill>";
@@ -7990,7 +8452,7 @@ function slideObjectToXml(slide) {
7990
8452
  strSlideXml += "<p:pic>";
7991
8453
  strSlideXml += " <p:nvPicPr>";
7992
8454
  strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
7993
- strSlideXml += " <p:cNvPicPr/>";
8455
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7994
8456
  strSlideXml += " <p:nvPr>";
7995
8457
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
7996
8458
  strSlideXml += " </p:nvPr>";
@@ -8005,7 +8467,10 @@ function slideObjectToXml(slide) {
8005
8467
  strSlideXml += "<p:pic>";
8006
8468
  strSlideXml += " <p:nvPicPr>";
8007
8469
  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>`;
8008
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
8470
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8471
+ noChangeAspect: true,
8472
+ ...slideItemObj.options.objectLock
8473
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
8009
8474
  strSlideXml += " <p:nvPr>";
8010
8475
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
8011
8476
  strSlideXml += " <p:extLst>";
@@ -8069,7 +8534,7 @@ function slideObjectToXml(slide) {
8069
8534
  strSlideXml += "/>";
8070
8535
  strSlideXml += " <a:lstStyle><a:lvl1pPr>";
8071
8536
  if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
8072
- strSlideXml += `<a:defRPr sz="${Math.round((slide._slideNumberProps.fontSize || 12) * 100)}">`;
8537
+ strSlideXml += `<a:defRPr sz="${clampFontSizeSz(slide._slideNumberProps.fontSize || 12)}">`;
8073
8538
  if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color);
8074
8539
  if (slide._slideNumberProps.fontFace) strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>`;
8075
8540
  strSlideXml += "</a:defRPr>";
@@ -8158,7 +8623,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
8158
8623
  paragraphPropXml += "";
8159
8624
  break;
8160
8625
  }
8161
- if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${Math.round(textObj.options.lineSpacing * 100)}"/></a:lnSpc>`;
8626
+ if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${clampLineSpacingPts(textObj.options.lineSpacing)}"/></a:lnSpc>`;
8162
8627
  else if (textObj.options.lineSpacingMultiple) strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 1e5)}"/></a:lnSpc>`;
8163
8628
  if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`;
8164
8629
  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>`;
@@ -8212,7 +8677,7 @@ function genXmlTextRunProperties(opts, isDefault) {
8212
8677
  let runProps = "";
8213
8678
  const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
8214
8679
  runProps += "<" + runPropsTag + " lang=\"" + (opts.lang ? opts.lang : "en-US") + "\"" + (opts.lang ? " altLang=\"en-US\"" : "");
8215
- runProps += opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "";
8680
+ runProps += opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "";
8216
8681
  runProps += opts?.bold ? ` b="${opts.bold ? "1" : "0"}"` : "";
8217
8682
  runProps += opts?.italic ? ` i="${opts.italic ? "1" : "0"}"` : "";
8218
8683
  runProps += opts?.strike ? ` strike="${typeof opts.strike === "string" ? opts.strike : "sngStrike"}"` : "";
@@ -8223,17 +8688,23 @@ function genXmlTextRunProperties(opts, isDefault) {
8223
8688
  if (opts.baseline) runProps += ` baseline="${Math.round(opts.baseline * 50)}"`;
8224
8689
  else if (opts.subscript) runProps += " baseline=\"-40000\"";
8225
8690
  else if (opts.superscript) runProps += " baseline=\"30000\"";
8226
- runProps += opts.charSpacing ? ` spc="${Math.round(opts.charSpacing * 100)}" kern="0"` : "";
8691
+ runProps += opts.charSpacing ? ` spc="${clampCharSpacingSpc(opts.charSpacing)}" kern="0"` : "";
8227
8692
  runProps += " dirty=\"0\">";
8228
- if (opts.color || opts.fontFace || opts.outline || typeof opts.underline === "object" && opts.underline.color) {
8229
- if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${valToPts(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
8693
+ const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
8694
+ if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
8695
+ if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${lineWidthToEmu(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
8230
8696
  if (opts.color) runProps += genXmlColorSelection({
8231
8697
  color: opts.color,
8232
8698
  transparency: opts.transparency
8233
8699
  });
8700
+ if (opts.glow || hasShadow) {
8701
+ runProps += "<a:effectLst>";
8702
+ if (opts.glow) runProps += createGlowElement(opts.glow, DEF_TEXT_GLOW);
8703
+ if (hasShadow) runProps += createShadowElement$1(opts.shadow, DEF_TEXT_SHADOW);
8704
+ runProps += "</a:effectLst>";
8705
+ }
8234
8706
  if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`;
8235
8707
  if (typeof opts.underline === "object" && opts.underline.color) runProps += `<a:uFill>${genXmlColorSelection(opts.underline.color)}</a:uFill>`;
8236
- if (opts.glow) runProps += `<a:effectLst>${createGlowElement(opts.glow, DEF_TEXT_GLOW)}</a:effectLst>`;
8237
8708
  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"/>`;
8238
8709
  }
8239
8710
  if (opts.hyperlink) {
@@ -8263,6 +8734,28 @@ function genXmlTextRun(textObj) {
8263
8734
  return `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(String(textObj.text))}</a:t></a:r>`;
8264
8735
  }
8265
8736
  /**
8737
+ * Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
8738
+ * @param {TextFitShrinkProps} fit - shrink fit options
8739
+ * @return {string} XML string (`<a:normAutofit .../>`)
8740
+ * @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
8741
+ */
8742
+ function genXmlNormAutofit(fit) {
8743
+ let attrs = "";
8744
+ const pct = (val, name) => {
8745
+ if (val === void 0 || val === null) return null;
8746
+ if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
8747
+ console.warn(`Warning: fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
8748
+ return null;
8749
+ }
8750
+ return Math.round(val * 1e3);
8751
+ };
8752
+ const fontScale = pct(fit.fontScale, "fontScale");
8753
+ if (fontScale !== null) attrs += ` fontScale="${fontScale}"`;
8754
+ const lnSpcReduction = pct(fit.lnSpcReduction, "lnSpcReduction");
8755
+ if (lnSpcReduction !== null) attrs += ` lnSpcReduction="${lnSpcReduction}"`;
8756
+ return `<a:normAutofit${attrs}/>`;
8757
+ }
8758
+ /**
8266
8759
  * Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
8267
8760
  * @param {ISlideObject | TableCell} slideObject - various options
8268
8761
  * @return {string} XML string
@@ -8275,6 +8768,8 @@ function genXmlBodyProperties(slideObject) {
8275
8768
  if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`;
8276
8769
  if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`;
8277
8770
  if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`;
8771
+ if (slideObject.options._bodyProp.numCol) bodyProperties += ` numCol="${slideObject.options._bodyProp.numCol}"`;
8772
+ if (slideObject.options._bodyProp.spcCol) bodyProperties += ` spcCol="${slideObject.options._bodyProp.spcCol}"`;
8278
8773
  bodyProperties += " rtlCol=\"0\"";
8279
8774
  if (slideObject.options._bodyProp.anchor) bodyProperties += " anchor=\"" + slideObject.options._bodyProp.anchor + "\"";
8280
8775
  if (slideObject.options._bodyProp.vert) bodyProperties += " vert=\"" + slideObject.options._bodyProp.vert + "\"";
@@ -8285,9 +8780,11 @@ function genXmlBodyProperties(slideObject) {
8285
8780
  * @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
8286
8781
  */
8287
8782
  if (slideObject.options.fit) {
8288
- if (slideObject.options.fit === "none") bodyProperties += "";
8289
- else if (slideObject.options.fit === "shrink") bodyProperties += "<a:normAutofit/>";
8290
- else if (slideObject.options.fit === "resize") bodyProperties += "<a:spAutoFit/>";
8783
+ const fit = slideObject.options.fit;
8784
+ if (fit === "none") bodyProperties += "";
8785
+ else if (fit === "shrink") bodyProperties += "<a:normAutofit/>";
8786
+ else if (fit === "resize") bodyProperties += "<a:spAutoFit/>";
8787
+ else if (typeof fit === "object" && fit.type === "shrink") bodyProperties += genXmlNormAutofit(fit);
8291
8788
  }
8292
8789
  if (slideObject.options.shrinkText) bodyProperties += "<a:normAutofit/>";
8293
8790
  bodyProperties += slideObject.options._bodyProp.autoFit ? "<a:spAutoFit/>" : "";
@@ -8423,13 +8920,13 @@ function genXmlTextBody(slideObj) {
8423
8920
  }
8424
8921
  });
8425
8922
  if (slideObj._type === "tablecell" && (opts.fontSize || opts.fontFace)) if (opts.fontFace) {
8426
- strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\">";
8923
+ strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\">";
8427
8924
  strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`;
8428
8925
  strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`;
8429
8926
  strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`;
8430
8927
  strSlideXml += "</a:endParaRPr>";
8431
- } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
8432
- else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "") + " dirty=\"0\"/>";
8928
+ } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
8929
+ else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
8433
8930
  else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}" dirty="0"/>`;
8434
8931
  strSlideXml += "</a:p>";
8435
8932
  });
@@ -8905,7 +9402,7 @@ function genXmlTableStyleBorders(border) {
8905
9402
  xml += `<a:${side}>`;
8906
9403
  if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
8907
9404
  else {
8908
- xml += `<a:ln w="${valToPts(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
9405
+ xml += `<a:ln w="${lineWidthToEmu(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
8909
9406
  xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
8910
9407
  xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
8911
9408
  xml += "</a:ln>";
@@ -8983,7 +9480,7 @@ function makeXmlViewProps() {
8983
9480
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
8984
9481
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
8985
9482
  */
8986
- const VERSION = "5.2.0";
9483
+ const VERSION = "5.3.0";
8987
9484
  function standardLayoutToPresLayout(layout) {
8988
9485
  return {
8989
9486
  name: layout.name,
@@ -9315,6 +9812,18 @@ var PptxGenJS$1 = class {
9315
9812
  });
9316
9813
  arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
9317
9814
  return await Promise.all(arrMediaPromises).then(async () => {
9815
+ const canonicalMediaTargets = /* @__PURE__ */ new Map();
9816
+ for (const target of [
9817
+ ...this._slides,
9818
+ ...this._slideLayouts,
9819
+ this._masterSlide
9820
+ ]) for (const rel of target._relsMedia || []) {
9821
+ if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
9822
+ const key = (rel.extn || "") + "\0" + rel.data;
9823
+ const canonical = canonicalMediaTargets.get(key);
9824
+ if (canonical) rel.Target = canonical;
9825
+ else canonicalMediaTargets.set(key, rel.Target);
9826
+ }
9318
9827
  this._slides.forEach((slide) => {
9319
9828
  if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
9320
9829
  });
@@ -9666,6 +10175,6 @@ var PptxGenJS = class extends PptxGenJS$1 {
9666
10175
  }
9667
10176
  };
9668
10177
  //#endregion
9669
- export { AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_SERIES_PRIMARY, AXIS_ID_VALUE_PRIMARY, AXIS_ID_VALUE_SECONDARY, AlignH, AlignV, BARCHART_COLORS, BULLET_TYPES, CHART_TYPE, CRLF, ChartType, DEF_BULLET_MARGIN, DEF_CELL_BORDER, DEF_CELL_MARGIN_IN, DEF_CELL_MARGIN_PT, DEF_CHART_BORDER, DEF_CHART_GRIDLINE, DEF_FONT_COLOR, DEF_FONT_SIZE, DEF_FONT_TITLE_SIZE, DEF_PRES_LAYOUT, DEF_PRES_LAYOUT_NAME, DEF_SHAPE_LINE_COLOR, DEF_SHAPE_SHADOW, DEF_SLIDE_BKGD, DEF_SLIDE_MARGIN_IN, DEF_TEXT_GLOW, DEF_TEXT_SHADOW, EMU, EMU_PER_INCH, EMU_PER_POINT, IMG_BROKEN, IMG_PLAYBTN, IMG_SVG_PLACEHOLDER, LAYOUT_IDX_SERIES_BASE, LETTERS, LINEH_MODIFIER, MASTER_OBJECTS, ONEPT, OutputType, PIECHART_COLORS, PLACEHOLDER_TYPES, POINTS_PER_INCH, PptxGenJS, PptxGenJS as Presentation, PptxGenJS as default, REGEX_HEX_COLOR, SCHEME_COLOR_NAMES, SHAPE_TYPE, SLDNUMFLDID, SLIDE_OBJECT_TYPES, STANDARD_LAYOUTS, SchemeColor, ShapeType, TABLE_STYLE, TEXT_HALIGN, TEXT_VALIGN, emuToInches, emuToPixels, emuToPoints, inchesToEmu, pixelsToEmu, pointsToEmu, textRun, textRuns };
10178
+ export { AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_SERIES_PRIMARY, AXIS_ID_VALUE_PRIMARY, AXIS_ID_VALUE_SECONDARY, AlignH, AlignV, BARCHART_COLORS, BULLET_TYPES, CHART_TYPE, CRLF, ChartType, DEF_BULLET_MARGIN, DEF_CELL_BORDER, DEF_CELL_MARGIN_IN, DEF_CELL_MARGIN_PT, DEF_CHART_BORDER, DEF_CHART_GRIDLINE, DEF_FONT_COLOR, DEF_FONT_SIZE, DEF_FONT_TITLE_SIZE, DEF_PRES_LAYOUT, DEF_PRES_LAYOUT_NAME, DEF_SHAPE_LINE_COLOR, DEF_SHAPE_SHADOW, DEF_SLIDE_BKGD, DEF_SLIDE_MARGIN_IN, DEF_TEXT_GLOW, DEF_TEXT_SHADOW, EMU, EMU_PER_INCH, EMU_PER_POINT, IMG_BROKEN, IMG_PLAYBTN, IMG_SVG_PLACEHOLDER, LAYOUT_IDX_SERIES_BASE, LETTERS, LINEH_MODIFIER, MASTER_OBJECTS, ONEPT, OutputType, PIECHART_COLORS, PLACEHOLDER_TYPES, POINTS_PER_INCH, PptxGenJS, PptxGenJS as Presentation, PptxGenJS as default, REGEX_HEX_COLOR, SCHEME_COLOR_NAMES, SHAPE_TYPE, SLDNUMFLDID, SLIDE_OBJECT_TYPES, STANDARD_LAYOUTS, SchemeColor, ShapeType, TABLE_STYLE, TEXT_HALIGN, TEXT_VALIGN, VALID_SHAPE_PRESETS, coordToEmu, emuToInches, emuToPixels, emuToPoints, inchesToEmu, percentToEmu, pixelsToEmu, pointsToEmu, textRun, textRuns };
9670
10179
 
9671
10180
  //# sourceMappingURL=standalone.js.map