@shbernal/pptxgenjs 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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";
@@ -3779,6 +3844,7 @@ let MASTER_OBJECTS = /* @__PURE__ */ function(MASTER_OBJECTS) {
3779
3844
  }({});
3780
3845
  let SLIDE_OBJECT_TYPES = /* @__PURE__ */ function(SLIDE_OBJECT_TYPES) {
3781
3846
  SLIDE_OBJECT_TYPES["chart"] = "chart";
3847
+ SLIDE_OBJECT_TYPES["connector"] = "connector";
3782
3848
  SLIDE_OBJECT_TYPES["hyperlink"] = "hyperlink";
3783
3849
  SLIDE_OBJECT_TYPES["image"] = "image";
3784
3850
  SLIDE_OBJECT_TYPES["media"] = "media";
@@ -3790,6 +3856,16 @@ let SLIDE_OBJECT_TYPES = /* @__PURE__ */ function(SLIDE_OBJECT_TYPES) {
3790
3856
  SLIDE_OBJECT_TYPES["notes"] = "notes";
3791
3857
  return SLIDE_OBJECT_TYPES;
3792
3858
  }({});
3859
+ /**
3860
+ * Maps a friendly `ConnectorType` to its OOXML connector preset geometry (`prst`).
3861
+ * These are the canonical 1-segment straight, 3-segment bent (elbow), and 3-segment
3862
+ * curved connector presets PowerPoint uses by default.
3863
+ */
3864
+ const CONNECTOR_PRESETS = {
3865
+ straight: "straightConnector1",
3866
+ elbow: "bentConnector3",
3867
+ curved: "curvedConnector3"
3868
+ };
3793
3869
  let PLACEHOLDER_TYPES = /* @__PURE__ */ function(PLACEHOLDER_TYPES) {
3794
3870
  PLACEHOLDER_TYPES["title"] = "title";
3795
3871
  PLACEHOLDER_TYPES["body"] = "body";
@@ -3910,27 +3986,20 @@ const IMG_PLAYBTN = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAVnCAYAA
3910
3986
  * PptxGenJS: Utility Methods
3911
3987
  */
3912
3988
  /**
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
3989
+ * Resolve a user `Coord` (x/y/w/h) to EMU — the single user-coordinate → EMU boundary.
3990
+ * - bare `number` **inches** (no magnitude guessing); `"<n>%"` → percent of the slide axis;
3991
+ * `"<n>in"`/`"<n>pt"`/`"<n>emu"` explicit units (see {@link Coord} / {@link coordToEmu})
3992
+ * - `null`/`undefined` 0 (callers may omit a coordinate)
3993
+ * - throws on a non-finite number rather than silently collapsing the object to zero size
3994
+ * @param {Coord|null|undefined} size - user coordinate
3995
+ * @param {'X' | 'Y'} xyDir - axis (selects slide width vs height for percentages)
3996
+ * @param {PresLayout} layout - presentation layout (EMU dimensions)
3997
+ * @returns {Emu} resolved EMU value
3922
3998
  */
3923
3999
  function getSmartParseNumber(size, xyDir, layout) {
3924
- if (typeof size === "string" && !isNaN(Number(size))) size = Number(size);
4000
+ if (size === null || size === void 0) return 0;
3925
4001
  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;
4002
+ return coordToEmu(size, xyDir === "Y" ? layout.height : layout.width);
3934
4003
  }
3935
4004
  /**
3936
4005
  * Basic UUID Generator Adapted
@@ -4003,14 +4072,16 @@ function getDuplicateObjectNames(names) {
4003
4072
  return Array.from(dupes);
4004
4073
  }
4005
4074
  /**
4006
- * Convert inches into EMU
4007
- * @param {number|string} inches - as string or number
4008
- * @returns {number} EMU value
4075
+ * Convert inches into EMU.
4076
+ * - accepts a number (inches) or a numeric/`"<n>in"` string
4077
+ * - no magnitude guessing: values are always treated as inches (use {@link coordToEmu} for
4078
+ * user coordinates that may carry other units)
4079
+ * @param {number|string} inches - inches as number or string
4080
+ * @returns {Emu} EMU value
4009
4081
  */
4010
4082
  function inch2Emu(inches) {
4011
- if (typeof inches === "number" && inches > 100) return inches;
4012
4083
  if (typeof inches === "string") inches = Number(inches.replace(/in*/gi, ""));
4013
- return Math.round(EMU * inches);
4084
+ return inchesToEmu(inches);
4014
4085
  }
4015
4086
  /**
4016
4087
  * Convert `pt` into points (using `ONEPT`)
@@ -4022,6 +4093,33 @@ function valToPts(pt) {
4022
4093
  return isNaN(points) ? 0 : Math.round(points * ONEPT);
4023
4094
  }
4024
4095
  /**
4096
+ * Convert a transparency percentage (0-100) into a schema-valid `<a:alpha>` value
4097
+ * (ST_PositiveFixedPercentage, 0-100000). Out-of-range transparency yields an
4098
+ * alpha that PowerPoint rejects as needing repair, so clamp into range and warn.
4099
+ */
4100
+ function transparencyToAlpha(transparency) {
4101
+ const pct = Math.min(100, Math.max(0, transparency));
4102
+ if (pct !== transparency) console.warn(`Warning: transparency ${transparency} is outside the valid range 0-100; using ${pct}.`);
4103
+ return Math.round((100 - pct) * 1e3);
4104
+ }
4105
+ /** Convert an opacity (0-1) into a schema-valid `<a:alpha>` value (0-100000); clamps + warns on out-of-range input. */
4106
+ function opacityToAlpha(opacity) {
4107
+ const o = Math.min(1, Math.max(0, opacity));
4108
+ if (o !== opacity) console.warn(`Warning: opacity ${opacity} is outside the valid range 0-1; using ${o}.`);
4109
+ return Math.round(o * 1e5);
4110
+ }
4111
+ /**
4112
+ * Convert a line width (points) to EMU clamped into ST_LineWidth (0..20116800 EMU,
4113
+ * i.e. 0-1584pt). Out-of-range widths make PowerPoint report the package as needing
4114
+ * repair, so clamp into range and warn.
4115
+ */
4116
+ function lineWidthToEmu(widthPts) {
4117
+ const raw = valToPts(widthPts);
4118
+ const clamped = Math.min(20116800, Math.max(0, raw));
4119
+ if (clamped !== raw) console.warn(`Warning: line width ${widthPts} is outside the valid range 0-1584pt; using ${clamped / ONEPT}.`);
4120
+ return clamped;
4121
+ }
4122
+ /**
4025
4123
  * Convert degrees (0..360) to PowerPoint `rot` value
4026
4124
  * @param {number} d degrees
4027
4125
  * @returns {number} calculated `rot` value
@@ -4069,8 +4167,10 @@ function createColorElement(colorStr, innerElements) {
4069
4167
  }
4070
4168
  let colorVal = (colorStr || "").replace("#", "");
4071
4169
  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 || ""}`;
4170
+ if (!innerElements?.includes("<a:alpha")) {
4171
+ const alphaHex = colorVal.slice(6, 8);
4172
+ innerElements = `<a:alpha val="${Math.round(parseInt(alphaHex, 16) / 255 * 1e5)}"/>${innerElements || ""}`;
4173
+ }
4074
4174
  colorVal = colorVal.slice(0, 6);
4075
4175
  }
4076
4176
  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 +4196,38 @@ function createGlowElement(options, defaults) {
4096
4196
  };
4097
4197
  const size = Math.round(opts.size * ONEPT);
4098
4198
  const color = opts.color || "000000";
4099
- const opacity = Math.round((opts.opacity ?? 0) * 1e5);
4199
+ const opacity = opacityToAlpha(opts.opacity ?? 0);
4100
4200
  strXml += `<a:glow rad="${size}">`;
4101
4201
  strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4102
4202
  strXml += "</a:glow>";
4103
4203
  return strXml;
4104
4204
  }
4205
+ /**
4206
+ * Creates an `a:outerShdw`/`a:innerShdw` element for a text run or shape.
4207
+ * Returns the shadow element only (no wrapping `a:effectLst`) so callers can
4208
+ * combine it with other effects (e.g. glow) inside a single `a:effectLst`.
4209
+ * @param {ShadowProps} options shadow properties
4210
+ * @param {ShadowProps} defaults defaults for unspecified properties in `options`
4211
+ * @see http://officeopenxml.com/drwSp-effects.php
4212
+ * @returns {string} XML string, or '' when type is 'none'
4213
+ */
4214
+ function createShadowElement$1(options, defaults) {
4215
+ const opts = {
4216
+ ...defaults,
4217
+ ...options
4218
+ };
4219
+ if (opts.type === "none") return "";
4220
+ const type = opts.type || "outer";
4221
+ const blur = valToPts(opts.blur ?? 0);
4222
+ const offset = valToPts(opts.offset ?? 0);
4223
+ const angle = Math.round((opts.angle ?? 0) * 6e4);
4224
+ const opacity = Math.round((opts.opacity ?? .75) * 1e5);
4225
+ const color = opts.color || "000000";
4226
+ 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}">`;
4227
+ strXml += createColorElement(color, `<a:alpha val="${opacity}"/>`);
4228
+ strXml += `</a:${type}Shdw>`;
4229
+ return strXml;
4230
+ }
4105
4231
  function boolToXml(value) {
4106
4232
  return value ? "1" : "0";
4107
4233
  }
@@ -4112,8 +4238,8 @@ function normalizeGradientAngle(angle) {
4112
4238
  }
4113
4239
  function gradientStopColorAdjustments(stop) {
4114
4240
  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)}"/>`;
4241
+ if (stop.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(stop.alpha)}"/>`;
4242
+ if (stop.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(stop.transparency)}"/>`;
4117
4243
  return internalElements;
4118
4244
  }
4119
4245
  function normalizeGradientStops(stops) {
@@ -4163,6 +4289,17 @@ function genXmlPatternFill(pattern) {
4163
4289
  * @param {Color | ShapeFillProps | ShapeLineProps} props fill props
4164
4290
  * @returns XML string
4165
4291
  */
4292
+ /**
4293
+ * Map a friendly `LineCap` value to the OOXML `cap` attribute value (`flat`/`sq`/`rnd`).
4294
+ * @param {LineCap} [lineCap] - line cap style (defaults to `flat`)
4295
+ * @returns {string} value for the `cap` attribute on `<a:ln>`
4296
+ */
4297
+ function createLineCap(lineCap) {
4298
+ if (!lineCap || lineCap === "flat") return "flat";
4299
+ else if (lineCap === "square") return "sq";
4300
+ else if (lineCap === "round") return "rnd";
4301
+ else throw new Error(`Invalid line cap: ${String(lineCap)}`);
4302
+ }
4166
4303
  function genXmlColorSelection(props) {
4167
4304
  let fillType = "solid";
4168
4305
  let colorVal = "";
@@ -4173,8 +4310,8 @@ function genXmlColorSelection(props) {
4173
4310
  else {
4174
4311
  if (props.type) fillType = props.type;
4175
4312
  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)}"/>`;
4313
+ if (props.alpha) internalElements += `<a:alpha val="${transparencyToAlpha(props.alpha)}"/>`;
4314
+ if (props.transparency) internalElements += `<a:alpha val="${transparencyToAlpha(props.transparency)}"/>`;
4178
4315
  }
4179
4316
  switch (fillType) {
4180
4317
  case "solid":
@@ -4272,15 +4409,16 @@ function decodeBase64ToBytes(b64) {
4272
4409
  }
4273
4410
  }
4274
4411
  /**
4275
- * Read the intrinsic pixel dimensions of a raster image from its header bytes.
4412
+ * Read the intrinsic dimensions of an image from its header bytes.
4276
4413
  * - synchronous: parses only file-format headers, never decodes pixels
4277
- * - supports PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X)
4278
- * - vector (SVG) and unrecognized formats return `null` (no intrinsic pixel size)
4414
+ * - raster: PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X) — natural pixels
4415
+ * - vector: SVG intrinsic size from the root `<svg>` width/height or viewBox
4416
+ * - unrecognized formats return `null` (no measurable intrinsic size)
4279
4417
  *
4280
4418
  * Used by image `sizing: 'cover' | 'contain'` to compute an aspect-correct
4281
4419
  * `<a:srcRect>` crop from the *natural* image ratio rather than the displayed box.
4282
4420
  * @param {string} dataB64 - base64 image payload or `data:` URI
4283
- * @returns {{ w: number, h: number } | null} natural pixel size, or `null` when unmeasurable
4421
+ * @returns {{ w: number, h: number } | null} natural size, or `null` when unmeasurable
4284
4422
  */
4285
4423
  function getImageSizeFromBase64(dataB64) {
4286
4424
  const b = decodeBase64ToBytes(dataB64);
@@ -4366,8 +4504,46 @@ function getImageSizeFromBase64(dataB64) {
4366
4504
  }
4367
4505
  return null;
4368
4506
  }
4507
+ const text = utf8Decode(b);
4508
+ if (/<svg[\s>]/i.test(text)) return getSvgSizeFromMarkup(text);
4369
4509
  return null;
4370
4510
  }
4511
+ /**
4512
+ * Read the intrinsic size of an SVG document from its root `<svg>` element.
4513
+ * Follows the SVG sizing model: an explicit absolute `width`/`height` pair wins;
4514
+ * otherwise the `viewBox` width/height defines the size (and thus aspect ratio).
4515
+ * Percentage or missing `width`/`height` fall through to `viewBox`.
4516
+ * @param {string} svg - SVG markup
4517
+ * @returns {{ w: number, h: number } | null} intrinsic size, or `null` when undeterminable
4518
+ */
4519
+ function getSvgSizeFromMarkup(svg) {
4520
+ const openTag = /<svg\b[^>]*>/i.exec(svg)?.[0];
4521
+ if (!openTag) return null;
4522
+ const attr = (name) => new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(openTag)?.[1] ?? null;
4523
+ const absLength = (val) => {
4524
+ if (val == null || /%\s*$/.test(val)) return NaN;
4525
+ const m = /^\s*\+?(\d*\.?\d+)/.exec(val);
4526
+ return m ? parseFloat(m[1]) : NaN;
4527
+ };
4528
+ let w = absLength(attr("width"));
4529
+ let h = absLength(attr("height"));
4530
+ if (!(w > 0 && h > 0)) {
4531
+ const vb = attr("viewBox");
4532
+ const p = vb ? vb.trim().split(/[\s,]+/).map(Number) : [];
4533
+ if (p.length === 4 && p[2] > 0 && p[3] > 0) {
4534
+ w = p[2];
4535
+ h = p[3];
4536
+ }
4537
+ }
4538
+ return w > 0 && h > 0 ? {
4539
+ w,
4540
+ h
4541
+ } : null;
4542
+ }
4543
+ /** Decode UTF-8 bytes to a string, isomorphic across Node and browsers. */
4544
+ function utf8Decode(bytes) {
4545
+ return new TextDecoder().decode(bytes);
4546
+ }
4371
4547
  //#endregion
4372
4548
  //#region src/gen-tables.ts
4373
4549
  /**
@@ -4518,6 +4694,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4518
4694
  let emuSlideTabH = EMU * 1;
4519
4695
  let emuTabCurrH = 0;
4520
4696
  let numCols = 0;
4697
+ let warnedNoTabH = false;
4521
4698
  const tableRowSlides = [];
4522
4699
  const tablePropX = getSmartParseNumber(tableProps.x, "X", presLayout);
4523
4700
  const tablePropY = getSmartParseNumber(tableProps.y, "Y", presLayout);
@@ -4537,6 +4714,15 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4537
4714
  if (emuSlideTabH < tablePropH) emuSlideTabH = tablePropH;
4538
4715
  }
4539
4716
  }
4717
+ if (emuSlideTabH <= 0) {
4718
+ const emuStartY = tableRowSlides.length === 0 ? tablePropY || inch2Emu(arrInchMargins[0]) : inch2Emu(tableProps.autoPageSlideStartY || tableProps.newSlideStartY || arrInchMargins[0]);
4719
+ const fallbackH = presLayout.height - emuStartY - inch2Emu(arrInchMargins[2]);
4720
+ if (!warnedNoTabH) {
4721
+ console.warn("addTable/autoPage: the table height (`h`) leaves no room to paginate; ignoring it and using the slide height. Increase `h` or decrease `y`.");
4722
+ warnedNoTabH = true;
4723
+ }
4724
+ emuSlideTabH = fallbackH > 0 ? fallbackH : presLayout.height;
4725
+ }
4540
4726
  }
4541
4727
  if (tableProps.verbose) {
4542
4728
  console.log("[[VERBOSE MODE]]");
@@ -4709,7 +4895,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4709
4895
  console.log("|-----------------------------------------------------------------------|\n\n");
4710
4896
  }
4711
4897
  if (currTableRow.length > 0 && currTableRow.map((cell) => Array.isArray(cell.text) ? cell.text.length : 0).reduce((p, n) => p + n) > 0) newTableRowSlide.rows.push(currTableRow);
4712
- tableRowSlides.push(newTableRowSlide);
4898
+ if (newTableRowSlide.rows.length > 0) tableRowSlides.push(newTableRowSlide);
4713
4899
  newTableRowSlide = { rows: [] };
4714
4900
  currTableRow = [];
4715
4901
  row.forEach((cell) => currTableRow.push({
@@ -4758,7 +4944,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4758
4944
  for (let c = 0; c < numCols; c++) if (colSpanDepths[c] > 0) colSpanDepths[c]--;
4759
4945
  if (tableProps.verbose) console.log(`- SLIDE [${tableRowSlides.length}]: ROW [${iRow}]: ...COMPLETE ...... emuTabCurrH = ${(emuTabCurrH / EMU).toFixed(2)} ( emuSlideTabH = ${(emuSlideTabH / EMU).toFixed(2)} )`);
4760
4946
  });
4761
- tableRowSlides.push(newTableRowSlide);
4947
+ if (newTableRowSlide.rows.length > 0 || tableRowSlides.length === 0) tableRowSlides.push(newTableRowSlide);
4762
4948
  if (tableProps.verbose) {
4763
4949
  console.log("\n|================================================|");
4764
4950
  console.log(`| FINAL: tableRowSlides.length = ${tableRowSlides.length}`);
@@ -4768,6 +4954,47 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
4768
4954
  return tableRowSlides;
4769
4955
  }
4770
4956
  /**
4957
+ * Convert a computed CSS border (width string + color string) from `getComputedStyle` into a
4958
+ * pptx `BorderProps`.
4959
+ *
4960
+ * Preserves *fractional* widths: a hairline CSS border such as `0.5px` must not be rounded to
4961
+ * `0pt` and silently vanish — the table serializer (`valToPts`) emits fractional points just
4962
+ * fine, so there is no reason to integer-round here (upstream gitbrent/PptxGenJS#1235). A
4963
+ * computed width of `0` (or a non-finite value) yields `{ type: 'none' }` so we never emit a
4964
+ * zero-width line.
4965
+ * @param {string} widthStr - computed `border-<side>-width`, e.g. `"0.5px"`
4966
+ * @param {string} colorStr - computed `border-<side>-color`, e.g. `"rgb(102, 102, 102)"`
4967
+ * @returns {BorderProps} border props for the cell side
4968
+ */
4969
+ function htmlBorderToProps(widthStr, colorStr) {
4970
+ const pt = Number(String(widthStr).replace("px", ""));
4971
+ if (!isFinite(pt) || pt <= 0) return { type: "none" };
4972
+ const arrRGB = String(colorStr).replace(/\s+/gi, "").replace("rgba(", "").replace("rgb(", "").replace(")", "").split(",");
4973
+ return {
4974
+ pt,
4975
+ color: rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2]))
4976
+ };
4977
+ }
4978
+ /**
4979
+ * Resolve a single HTML-table column width for `tableToSlides`.
4980
+ *
4981
+ * Precedence: an explicit `data-pptx-width` wins outright; otherwise the proportional width
4982
+ * derived from the live table is used, raised to `data-pptx-min-width` when that floor is larger.
4983
+ *
4984
+ * Hidden tables report `offsetWidth` 0 for every cell, which makes `calcWidth` non-finite (a 0/0
4985
+ * proportional calc). Fall back to `0` there so an explicit `data-pptx-width` / `data-pptx-min-width`
4986
+ * override still drives the column instead of emitting a `NaN` width (upstream gitbrent/PptxGenJS#1157).
4987
+ * @param {number} calcWidth - proportional width derived from `offsetWidth` (may be `NaN` for hidden tables)
4988
+ * @param {number} setWidth - `data-pptx-width` override (`0`/`NaN` when absent or invalid)
4989
+ * @param {number} minWidth - `data-pptx-min-width` floor (`0`/`NaN` when absent or invalid)
4990
+ * @returns {number} resolved column width
4991
+ */
4992
+ function resolveHtmlColWidth(calcWidth, setWidth, minWidth) {
4993
+ const safeCalc = isFinite(calcWidth) ? calcWidth : 0;
4994
+ if (isFinite(setWidth) && setWidth > 0) return setWidth;
4995
+ return isFinite(minWidth) && minWidth > safeCalc ? minWidth : safeCalc;
4996
+ }
4997
+ /**
4771
4998
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
4772
4999
  * @param {TableToSlidesHost} pptx - pptxgenjs instance
4773
5000
  * @param {string} tabEleId - HTMLElementID of the table
@@ -4831,12 +5058,10 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4831
5058
  });
4832
5059
  arrTabColW.forEach((colW, idxW) => {
4833
5060
  const intCalcWidth = Number((Number(emuSlideTabW) * (colW / intTabW * 100) / 100 / EMU).toFixed(2));
4834
- let intMinWidth = 0;
4835
- const colSelectorMin = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
4836
- if (colSelectorMin) intMinWidth = Number(colSelectorMin.getAttribute("data-pptx-min-width"));
4837
- const colSelectorSet = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
4838
- if (colSelectorSet) intMinWidth = Number(colSelectorSet.getAttribute("data-pptx-width"));
4839
- arrColW.push(intMinWidth > intCalcWidth ? intMinWidth : intCalcWidth);
5061
+ const headCell = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
5062
+ const intSetWidth = headCell ? Number(headCell.getAttribute("data-pptx-width")) : 0;
5063
+ const intMinWidth = headCell ? Number(headCell.getAttribute("data-pptx-min-width")) : 0;
5064
+ arrColW.push(resolveHtmlColWidth(intCalcWidth, intSetWidth, intMinWidth));
4840
5065
  });
4841
5066
  if (opts.verbose) console.log(`| arrColW ......................................... = [${arrColW.join(", ")}]`);
4842
5067
  [
@@ -4915,12 +5140,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4915
5140
  "bottom",
4916
5141
  "left"
4917
5142
  ].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
- };
5143
+ const style = window.getComputedStyle(cell);
5144
+ cellBorder[idxb] = htmlBorderToProps(style.getPropertyValue("border-" + val + "-width"), style.getPropertyValue("border-" + val + "-color"));
4924
5145
  });
4925
5146
  cellOpts.border = cellBorder;
4926
5147
  }
@@ -4990,6 +5211,8 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
4990
5211
  */
4991
5212
  /** counter for included charts (used for index in their filenames) */
4992
5213
  let _chartCounter = 0;
5214
+ /** DPI PowerPoint assumes when sizing an inserted raster image (natural pixels / 96 == inches) */
5215
+ const IMAGE_NATURAL_DPI = 96;
4993
5216
  function normalizeBorderTuple(border) {
4994
5217
  return Array.isArray(border) ? border : [
4995
5218
  border,
@@ -5012,7 +5235,7 @@ function createSlideMaster(props, target) {
5012
5235
  else if ("line" in object) addShapeDefinition(tgt, "line", object.line);
5013
5236
  else if ("rect" in object) addShapeDefinition(tgt, "rect", object.rect);
5014
5237
  else if ("roundRect" in object) addShapeDefinition(tgt, "roundRect", object.roundRect);
5015
- else if ("text" in object) addTextDefinition(tgt, [{ text: object.text.text }], object.text.options || {}, false);
5238
+ else if ("text" in object) addTextDefinition(tgt, Array.isArray(object.text.text) ? object.text.text : [{ text: object.text.text }], object.text.options || {}, false);
5016
5239
  else if ("placeholder" in object) {
5017
5240
  const placeholder = object.placeholder;
5018
5241
  const { name, type, ...rawPlaceholderOptions } = placeholder.options;
@@ -5026,6 +5249,26 @@ function createSlideMaster(props, target) {
5026
5249
  if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
5027
5250
  }
5028
5251
  /**
5252
+ * Round and clamp an integer chart percentage/angle option into a schema-valid range.
5253
+ *
5254
+ * Several chart attributes are bounded integer types whose out-of-range values make
5255
+ * PowerPoint report the package as needing repair: `<c:overlap>` (ST_Overlap, -100..100),
5256
+ * `<c:gapWidth>`/`<c:gapDepth>` (ST_GapAmount, 0..500), `<c:holeSize>` (ST_HoleSize, 10..90)
5257
+ * and `<c:firstSliceAng>` (ST_FirstSliceAng, 0..360). Missing/non-numeric input returns
5258
+ * `undefined` so the caller can apply its own default; an out-of-range value is clamped
5259
+ * and a warning is emitted (per the library's warn-rather-than-degrade policy).
5260
+ * @param value - caller-supplied option value
5261
+ * @param min - inclusive lower bound
5262
+ * @param max - inclusive upper bound
5263
+ * @param name - option name, for the warning message
5264
+ */
5265
+ function clampChartPct(value, min, max, name) {
5266
+ if (typeof value !== "number" || isNaN(value)) return void 0;
5267
+ const clamped = Math.min(max, Math.max(min, Math.round(value)));
5268
+ if (clamped !== value) console.warn(`Warning: ${name} ${value} is outside the valid range ${min}-${max}; using ${clamped}.`);
5269
+ return clamped;
5270
+ }
5271
+ /**
5029
5272
  * Generate the chart based on input data.
5030
5273
  * OOXML Chart Spec: ISO/IEC 29500-1:2016(E)
5031
5274
  *
@@ -5197,7 +5440,13 @@ function addChartDefinition(target, type, data, opt) {
5197
5440
  "marker",
5198
5441
  "filled"
5199
5442
  ].includes(options.radarStyle || "")) options.radarStyle = "standard";
5200
- options.lineDataSymbolSize = options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize) ? options.lineDataSymbolSize : 6;
5443
+ {
5444
+ const rawSymbolSize = options.lineDataSymbolSize;
5445
+ const hasSymbolSize = rawSymbolSize != null && !isNaN(rawSymbolSize);
5446
+ const symbolSize = Math.min(72, Math.max(2, Math.round(hasSymbolSize ? rawSymbolSize : 6)));
5447
+ if (hasSymbolSize && symbolSize !== rawSymbolSize) console.warn(`Warning: lineDataSymbolSize ${rawSymbolSize} is outside the valid marker size range (integer 2-72); using ${symbolSize}.`);
5448
+ options.lineDataSymbolSize = symbolSize;
5449
+ }
5201
5450
  options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(.75);
5202
5451
  const chartLayout = options.layout;
5203
5452
  if (chartLayout) [
@@ -5247,8 +5496,11 @@ function addChartDefinition(target, type, data, opt) {
5247
5496
  options.v3DRotY = typeof options.v3DRotY === "number" && !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30;
5248
5497
  options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true;
5249
5498
  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;
5499
+ options.barGapWidthPct = clampChartPct(options.barGapWidthPct, 0, 500, "barGapWidthPct") ?? 150;
5500
+ options.barGapDepthPct = clampChartPct(options.barGapDepthPct, 0, 500, "barGapDepthPct") ?? 150;
5501
+ options.barOverlapPct = clampChartPct(options.barOverlapPct, -100, 100, "barOverlapPct");
5502
+ options.holeSize = clampChartPct(options.holeSize, 10, 90, "holeSize");
5503
+ options.firstSliceAng = clampChartPct(options.firstSliceAng, 0, 360, "firstSliceAng");
5252
5504
  options.chartColors = Array.isArray(options.chartColors) ? options.chartColors : options._type === "pie" || options._type === "doughnut" ? PIECHART_COLORS : BARCHART_COLORS;
5253
5505
  options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : void 0;
5254
5506
  options.border = options.border && typeof options.border === "object" ? options.border : void 0;
@@ -5337,16 +5589,29 @@ function addImageDefinition(target, opt) {
5337
5589
  else if (strImageData?.toLowerCase().includes("image/svg+xml")) strImgExtn = "svg";
5338
5590
  newObject._type = "image";
5339
5591
  newObject.image = strImagePath || "preencoded.png";
5592
+ let defWidth = intWidth;
5593
+ let defHeight = intHeight;
5594
+ if ((!intWidth || !intHeight) && strImageData && strImgExtn !== "svg") {
5595
+ const natural = getImageSizeFromBase64(strImageData);
5596
+ if (natural) {
5597
+ if (!intWidth && !intHeight) {
5598
+ defWidth = natural.w / IMAGE_NATURAL_DPI;
5599
+ defHeight = natural.h / IMAGE_NATURAL_DPI;
5600
+ } else if (typeof intWidth === "number" && intWidth && !intHeight) defHeight = intWidth * (natural.h / natural.w);
5601
+ else if (typeof intHeight === "number" && intHeight && !intWidth) defWidth = intHeight * (natural.w / natural.h);
5602
+ }
5603
+ }
5340
5604
  const objectOptions = {
5341
5605
  x: intPosX || 0,
5342
5606
  y: intPosY || 0,
5343
- w: intWidth || 1,
5344
- h: intHeight || 1,
5607
+ w: defWidth || 1,
5608
+ h: defHeight || 1,
5345
5609
  altText: opt.altText || "",
5346
5610
  rounding: typeof opt.rounding === "boolean" ? opt.rounding : false,
5347
5611
  shape: opt.shape,
5348
5612
  points: opt.points,
5349
5613
  rectRadius: opt.rectRadius,
5614
+ shapeAdjust: opt.shapeAdjust,
5350
5615
  sizing,
5351
5616
  placeholder: opt.placeholder,
5352
5617
  rotate: opt.rotate || 0,
@@ -5355,6 +5620,7 @@ function addImageDefinition(target, opt) {
5355
5620
  transparency: opt.transparency || 0,
5356
5621
  duotone: opt.duotone,
5357
5622
  objectName,
5623
+ objectLock: opt.objectLock,
5358
5624
  shadow: correctShadowOptions(opt.shadow)
5359
5625
  };
5360
5626
  newObject.options = objectOptions;
@@ -5384,7 +5650,10 @@ function addImageDefinition(target, opt) {
5384
5650
  });
5385
5651
  newObject.imageRid = imageRelId + 1;
5386
5652
  } else {
5387
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strImagePath && item.type === "image/" + strImgExtn && !item.isDuplicate);
5653
+ const dupeItem = target._relsMedia.find((item) => {
5654
+ if (item.isDuplicate || !item.Target || item.type !== "image/" + strImgExtn) return false;
5655
+ return strImagePath ? item.path === strImagePath : !!strImageData && item.data === strImageData;
5656
+ });
5388
5657
  target._relsMedia.push({
5389
5658
  path: strImagePath || "preencoded." + strImgExtn,
5390
5659
  type: "image/" + strImgExtn,
@@ -5442,6 +5711,7 @@ function addMediaDefinition(target, opt) {
5442
5711
  slideData.options.h = intSizeY;
5443
5712
  slideData.options.objectName = objectName;
5444
5713
  if (opt.altText) slideData.options.altText = opt.altText;
5714
+ if (opt.objectLock) slideData.options.objectLock = opt.objectLock;
5445
5715
  /**
5446
5716
  * NOTE:
5447
5717
  * - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1!
@@ -5471,7 +5741,10 @@ function addMediaDefinition(target, opt) {
5471
5741
  Target: `../media/image-${target._slideNum}-${target._relsMedia.length + 1}.png`
5472
5742
  });
5473
5743
  } else {
5474
- const dupeItem = target._relsMedia.find((item) => item.path && item.path === strPath && item.type === strType + "/" + strExtn && !item.isDuplicate);
5744
+ const dupeItem = target._relsMedia.find((item) => {
5745
+ if (item.isDuplicate || !item.Target || item.type !== strType + "/" + strExtn) return false;
5746
+ return strPath ? item.path === strPath : !!strData && item.data === strData;
5747
+ });
5475
5748
  const relId1 = getNewRelId(target);
5476
5749
  target._relsMedia.push({
5477
5750
  path: strPath || "preencoded" + strExtn,
@@ -5536,12 +5809,14 @@ function addShapeDefinition(target, shapeName, opts) {
5536
5809
  const options = typeof opts === "object" ? opts : {};
5537
5810
  options.line = options.line || { type: "none" };
5538
5811
  options.shadow = correctShadowOptions(options.shadow);
5812
+ const resolvedShapeName = typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName;
5539
5813
  const newObject = {
5540
5814
  _type: "text",
5541
- shape: (typeof shapeName === "string" && SHAPE_NAME_ALIASES[shapeName] ? SHAPE_NAME_ALIASES[shapeName] : shapeName) || "rect",
5815
+ shape: resolvedShapeName || "rect",
5542
5816
  options
5543
5817
  };
5544
5818
  if (!shapeName) throw new Error("Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`");
5819
+ 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
5820
  const newLineOpts = {
5546
5821
  type: options.line.type || "solid",
5547
5822
  color: options.line.color || "333333",
@@ -5570,6 +5845,51 @@ function addShapeDefinition(target, shapeName, opts) {
5570
5845
  target._slideObjects.push(newObject);
5571
5846
  }
5572
5847
  /**
5848
+ * Adds a connector object to a slide definition.
5849
+ * A connector is a line between two points emitted as a PowerPoint connector (`<p:cxnSp>`).
5850
+ * Endpoints are converted to a bounding box (`x/y/w/h`) plus `flipH`/`flipV` so the box can be
5851
+ * oriented from any corner; the connector preset geometry is derived from `type`.
5852
+ * @param {PresSlideInternal} target - slide the connector is added to
5853
+ * @param {ConnectorProps} opts - connector options (endpoints + line styling)
5854
+ */
5855
+ function addConnectorDefinition(target, opts) {
5856
+ if (!opts || [
5857
+ opts.x1,
5858
+ opts.y1,
5859
+ opts.x2,
5860
+ opts.y2
5861
+ ].some((v) => typeof v === "undefined")) throw new Error("addConnector requires { x1, y1, x2, y2 }. Example: `slide.addConnector({ x1:1, y1:1, x2:4, y2:3 })`");
5862
+ const preset = CONNECTOR_PRESETS[opts.type || "straight"];
5863
+ if (!preset) throw new Error(`Invalid connector type "${String(opts.type)}". Use 'straight', 'elbow', or 'curved'.`);
5864
+ const x1 = getSmartParseNumber(opts.x1, "X", target._presLayout) / EMU;
5865
+ const y1 = getSmartParseNumber(opts.y1, "Y", target._presLayout) / EMU;
5866
+ const x2 = getSmartParseNumber(opts.x2, "X", target._presLayout) / EMU;
5867
+ const y2 = getSmartParseNumber(opts.y2, "Y", target._presLayout) / EMU;
5868
+ const newObject = {
5869
+ _type: "connector",
5870
+ shape: preset,
5871
+ options: {
5872
+ x: Math.min(x1, x2),
5873
+ y: Math.min(y1, y2),
5874
+ w: Math.abs(x2 - x1),
5875
+ h: Math.abs(y2 - y1),
5876
+ flipH: x2 < x1,
5877
+ flipV: y2 < y1,
5878
+ line: {
5879
+ type: "solid",
5880
+ color: opts.color || "333333",
5881
+ width: typeof opts.width === "number" ? opts.width : 1,
5882
+ dashType: opts.dashType || "solid",
5883
+ beginArrowType: opts.beginArrowType,
5884
+ endArrowType: opts.endArrowType
5885
+ },
5886
+ altText: opts.altText,
5887
+ objectName: opts.objectName ? encodeXmlEntities(validateObjectName(opts.objectName, "connector")) : `Connector ${target._slideObjects.filter((obj) => obj._type === "connector").length}`
5888
+ }
5889
+ };
5890
+ target._slideObjects.push(newObject);
5891
+ }
5892
+ /**
5573
5893
  * Adds a table object to a slide definition.
5574
5894
  * @param {PresSlideInternal} target - slide object that the table should be added to
5575
5895
  * @param {TableRow[]} tableRows - table data
@@ -5638,9 +5958,8 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5638
5958
  }
5639
5959
  arrRows.push(newRow);
5640
5960
  });
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);
5961
+ if (opt.x === void 0 || opt.x === null) opt.x = .5;
5962
+ if (opt.y === void 0 || opt.y === null) opt.y = .5;
5644
5963
  opt.fontSize = opt.fontSize || 12;
5645
5964
  opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN;
5646
5965
  if (typeof opt.margin === "number") opt.margin = [
@@ -5672,6 +5991,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5672
5991
  });
5673
5992
  }
5674
5993
  opt.autoPage = typeof opt.autoPage === "boolean" ? opt.autoPage : false;
5994
+ opt.autoPagePlaceholder = typeof opt.autoPagePlaceholder === "boolean" ? opt.autoPagePlaceholder : false;
5675
5995
  opt.autoPageRepeatHeader = typeof opt.autoPageRepeatHeader === "boolean" ? opt.autoPageRepeatHeader : false;
5676
5996
  opt.autoPageHeaderRows = typeof opt.autoPageHeaderRows !== "undefined" && !isNaN(Number(opt.autoPageHeaderRows)) ? Number(opt.autoPageHeaderRows) : 1;
5677
5997
  opt.autoPageLineWeight = typeof opt.autoPageLineWeight !== "undefined" && !isNaN(Number(opt.autoPageLineWeight)) ? Number(opt.autoPageLineWeight) : 0;
@@ -5709,12 +6029,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5709
6029
  console.warn("addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.");
5710
6030
  opt.colW = void 0;
5711
6031
  }
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);
6032
+ } else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU - arrTableMargin[1] - arrTableMargin[3]);
5718
6033
  arrRows.forEach((row) => {
5719
6034
  row.forEach((cell, idy) => {
5720
6035
  if (typeof cell === "number" || typeof cell === "string") row[idy] = {
@@ -5740,12 +6055,14 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
5740
6055
  });
5741
6056
  } else {
5742
6057
  if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
6058
+ const sourcePlaceholders = opt.autoPagePlaceholder && Array.isArray(target._slideObjects) ? target._slideObjects.filter((obj) => obj._type !== "table" && obj.options?.placeholder) : [];
5743
6059
  getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
5744
6060
  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]);
6061
+ if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
5746
6062
  {
5747
6063
  const newSlide = getSlide(target._slideNum + idx);
5748
6064
  opt.autoPage = false;
6065
+ if (idx > 0 && sourcePlaceholders.length > 0) sourcePlaceholders.forEach((ph) => newSlide._slideObjects.push(structuredClone(ph)));
5749
6066
  createHyperlinkRels(newSlide, slide.rows);
5750
6067
  newSlide.addTable(slide.rows, { ...opt });
5751
6068
  if (idx > 0) newAutoPagedSlides.push(newSlide);
@@ -5814,6 +6131,10 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
5814
6131
  itemOpts._bodyProp.anchor = !itemOpts.placeholder ? "ctr" : void 0;
5815
6132
  itemOpts._bodyProp.vert = itemOpts.vert;
5816
6133
  itemOpts._bodyProp.wrap = typeof itemOpts.wrap === "boolean" ? itemOpts.wrap : true;
6134
+ 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)");
6135
+ else itemOpts._bodyProp.numCol = Math.round(itemOpts.columns);
6136
+ 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)");
6137
+ else itemOpts._bodyProp.spcCol = valToPts(itemOpts.columnSpacing);
5817
6138
  if (itemOpts.inset && !isNaN(Number(itemOpts.inset)) || itemOpts.inset === 0) {
5818
6139
  itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset);
5819
6140
  itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset);
@@ -6094,6 +6415,16 @@ var Slide = class {
6094
6415
  return this;
6095
6416
  }
6096
6417
  /**
6418
+ * Add a connector (a line drawn between two points, emitted as a PowerPoint `<p:cxnSp>`).
6419
+ * @param {ConnectorProps} options - connector endpoints (`x1,y1,x2,y2`) and line styling
6420
+ * @return {Slide} this Slide
6421
+ * @example slide.addConnector({ type: 'elbow', x1: 1, y1: 1, x2: 5, y2: 3, endArrowType: 'triangle' })
6422
+ */
6423
+ addConnector(options) {
6424
+ addConnectorDefinition(this, options);
6425
+ return this;
6426
+ }
6427
+ /**
6097
6428
  * Add table to Slide
6098
6429
  * @param {TableRow[]} tableRows - table rows
6099
6430
  * @param {TableProps} options - table options
@@ -6323,6 +6654,22 @@ async function createExcelWorksheet(chartObject, zip) {
6323
6654
  });
6324
6655
  }
6325
6656
  /**
6657
+ * Emit the `<a:latin>/<a:ea>/<a:cs>` font trio for a chart text run.
6658
+ *
6659
+ * In DrawingML run properties a typeface applies only to the script class of
6660
+ * its element: `<a:latin>` covers Latin/ASCII, `<a:ea>` covers East Asian, and
6661
+ * `<a:cs>` covers complex scripts. Emitting `<a:latin>` alone leaves East Asian
6662
+ * (e.g. Chinese) and complex-script glyphs falling back to the theme font, so a
6663
+ * user-specified font never takes effect for that text — most visibly on
6664
+ * PowerPoint for Mac. Stamping the same typeface onto all three classes is what
6665
+ * choosing a font in PowerPoint's UI does (upstream gitbrent/PptxGenJS#1420).
6666
+ * @param {string} typeface - font face name
6667
+ * @return {string} `<a:latin/><a:ea/><a:cs/>` XML
6668
+ */
6669
+ function createChartTextFonts(typeface) {
6670
+ return `<a:latin typeface="${typeface}"/><a:ea typeface="${typeface}"/><a:cs typeface="${typeface}"/>`;
6671
+ }
6672
+ /**
6326
6673
  * Main entry point method for create charts
6327
6674
  * @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
6328
6675
  * @param {ISlideRelChart} rel - chart object
@@ -6332,6 +6679,10 @@ function makeXmlCharts(rel) {
6332
6679
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
6333
6680
  let usesSecondaryValAxis = false;
6334
6681
  let usesSecondaryCatAxis = false;
6682
+ let primaryCatAxisValType = null;
6683
+ let secondaryCatAxisValType = null;
6684
+ let primaryCatAxisHasCategoryChart = false;
6685
+ let secondaryCatAxisHasCategoryChart = false;
6335
6686
  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
6687
  strXml += "<c:date1904 val=\"0\"/>";
6337
6688
  strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? "1" : "0"}"/>`;
@@ -6344,6 +6695,8 @@ function makeXmlCharts(rel) {
6344
6695
  fontSize: rel.opts.titleFontSize || 18,
6345
6696
  titleAlign: rel.opts.titleAlign,
6346
6697
  titleBold: rel.opts.titleBold,
6698
+ titleItalic: rel.opts.titleItalic,
6699
+ titleUnderline: rel.opts.titleUnderline,
6347
6700
  titlePos: rel.opts.titlePos,
6348
6701
  titleRotate: rel.opts.titleRotate
6349
6702
  }, rel.opts.x, rel.opts.y);
@@ -6376,18 +6729,37 @@ function makeXmlCharts(rel) {
6376
6729
  const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY;
6377
6730
  usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis;
6378
6731
  usesSecondaryCatAxis = usesSecondaryCatAxis || options.secondaryCatAxis;
6732
+ const usesValueXAxis = type.type === "scatter" || type.type === "bubble" || type.type === "bubble3D";
6733
+ if (options.secondaryCatAxis) if (usesValueXAxis) secondaryCatAxisValType = type.type;
6734
+ else secondaryCatAxisHasCategoryChart = true;
6735
+ else if (usesValueXAxis) primaryCatAxisValType = type.type;
6736
+ else primaryCatAxisHasCategoryChart = true;
6379
6737
  strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId);
6380
6738
  });
6381
6739
  else strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY);
6382
6740
  if (rel.opts._type !== "pie" && rel.opts._type !== "doughnut") {
6383
6741
  if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) throw new Error("Secondary axis must be used by one of the multiple charts");
6742
+ const comboCatAxisType = (isSecondary) => {
6743
+ const valType = isSecondary ? secondaryCatAxisValType : primaryCatAxisValType;
6744
+ const hasCategoryChart = isSecondary ? secondaryCatAxisHasCategoryChart : primaryCatAxisHasCategoryChart;
6745
+ if (!valType) return {};
6746
+ if (hasCategoryChart) {
6747
+ 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.`);
6748
+ return {};
6749
+ }
6750
+ return { _type: valType };
6751
+ };
6384
6752
  if (rel.opts.catAxes) {
6385
6753
  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
6754
  strXml += makeCatAxis({
6387
6755
  ...rel.opts,
6388
- ...rel.opts.catAxes[0]
6756
+ ...rel.opts.catAxes[0],
6757
+ ...comboCatAxisType(false)
6389
6758
  }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6390
- } else strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6759
+ } else strXml += makeCatAxis({
6760
+ ...rel.opts,
6761
+ ...comboCatAxisType(false)
6762
+ }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY);
6391
6763
  if (rel.opts.valAxes) {
6392
6764
  strXml += makeValAxis({
6393
6765
  ...rel.opts,
@@ -6404,9 +6776,13 @@ function makeXmlCharts(rel) {
6404
6776
  }
6405
6777
  if (rel.opts?.catAxes && rel.opts?.catAxes[1]) strXml += makeCatAxis({
6406
6778
  ...rel.opts,
6407
- ...rel.opts.catAxes[1]
6779
+ ...rel.opts.catAxes[1],
6780
+ ...comboCatAxisType(true)
6781
+ }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY);
6782
+ else if (usesSecondaryCatAxis && (!rel.opts.catAxes || !rel.opts.catAxes[1])) strXml += makeCatAxis({
6783
+ ...rel.opts,
6784
+ ...comboCatAxisType(true)
6408
6785
  }, 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
6786
  }
6411
6787
  if (rel.opts.showDataTable) {
6412
6788
  strXml += "<c:dTable>";
@@ -6461,8 +6837,7 @@ function makeXmlCharts(rel) {
6461
6837
  strXml += " <a:pPr>";
6462
6838
  strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : "<a:defRPr>";
6463
6839
  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 + "\"/>";
6840
+ if (rel.opts.legendFontFace) strXml += createChartTextFonts(rel.opts.legendFontFace);
6466
6841
  strXml += " </a:defRPr>";
6467
6842
  strXml += " </a:pPr>";
6468
6843
  strXml += " <a:endParaRPr lang=\"en-US\"/>";
@@ -6500,6 +6875,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6500
6875
  let idxColLtr = 1;
6501
6876
  let optsChartData;
6502
6877
  let strXml = "";
6878
+ const valFmtCode = encodeXmlEntities(opts.valLabelFormatCode || opts.dataTableFormatCode || opts.dataLabelFormatCode || "General");
6503
6879
  switch (chartType) {
6504
6880
  case "area":
6505
6881
  case "bar":
@@ -6543,7 +6919,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6543
6919
  } 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
6920
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
6545
6921
  strXml += " </c:spPr>";
6546
- if (chartType !== "line" && chartType !== "radar") strXml += " <c:invertIfNegative val=\"0\"/>";
6922
+ if (chartType === "bar" || chartType === "bar3D") strXml += " <c:invertIfNegative val=\"0\"/>";
6547
6923
  if (chartType === "line" || chartType === "radar") {
6548
6924
  strXml += "<c:marker>";
6549
6925
  strXml += " <c:symbol val=\"" + opts.lineDataSymbol + "\"/>";
@@ -6558,6 +6934,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6558
6934
  strXml += " </c:spPr>";
6559
6935
  strXml += "</c:marker>";
6560
6936
  }
6937
+ {
6938
+ 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;
6939
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, barVaryColors);
6940
+ }
6561
6941
  if (chartType !== "radar") {
6562
6942
  const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
6563
6943
  const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
@@ -6565,12 +6945,15 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6565
6945
  const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
6566
6946
  const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
6567
6947
  strXml += "<c:dLbls>";
6948
+ if (obj.customLabels?.length) obj.customLabels.forEach((lbl, idx) => {
6949
+ if (lbl) strXml += makeCustomDLblXml(idx, lbl, opts);
6950
+ });
6568
6951
  strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
6569
6952
  if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
6570
6953
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6571
6954
  strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
6572
6955
  strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
6573
- strXml += `<a:latin typeface="${lblFace}"/>`;
6956
+ strXml += createChartTextFonts(lblFace);
6574
6957
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6575
6958
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6576
6959
  strXml += "<c:showLegendKey val=\"0\"/>";
@@ -6579,29 +6962,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6579
6962
  strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
6580
6963
  strXml += "</c:dLbls>";
6581
6964
  }
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
6965
  strXml += "<c:cat>";
6606
6966
  if (opts.catLabelFormatCode) {
6607
6967
  strXml += " <c:numRef>";
@@ -6638,10 +6998,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6638
6998
  strXml += " <c:numRef>";
6639
6999
  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
7000
  strXml += " <c:numCache>";
6641
- strXml += " <c:formatCode>" + (opts.valLabelFormatCode || opts.dataTableFormatCode || "General") + "</c:formatCode>";
7001
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6642
7002
  strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`;
6643
7003
  obj.values.forEach((value, idx) => {
6644
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7004
+ strXml += numCachePt(idx, value);
6645
7005
  });
6646
7006
  strXml += " </c:numCache>";
6647
7007
  strXml += " </c:numRef>";
@@ -6657,7 +7017,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6657
7017
  strXml += " <a:p><a:pPr>";
6658
7018
  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
7019
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6660
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
7020
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6661
7021
  strXml += " </a:defRPr>";
6662
7022
  strXml += " </a:pPr></a:p>";
6663
7023
  strXml += " </c:txPr>";
@@ -6673,6 +7033,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6673
7033
  if (chartType === "bar") {
6674
7034
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6675
7035
  strXml += ` <c:overlap val="${opts.barOverlapPct != null ? opts.barOverlapPct : (opts.barGrouping || "").includes("tacked") ? 100 : 0}"/>`;
7036
+ strXml += createSerLinesElement(opts.barSeriesLine);
6676
7037
  } else if (chartType === "bar3D") {
6677
7038
  strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`;
6678
7039
  strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`;
@@ -6724,6 +7085,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6724
7085
  strXml += "<a:effectLst/>";
6725
7086
  strXml += "</c:spPr>";
6726
7087
  strXml += "</c:marker>";
7088
+ {
7089
+ const scatterVaryColors = data.length === 1 && opts.chartColors !== BARCHART_COLORS ? opts.chartColors || BARCHART_COLORS : null;
7090
+ strXml += makeSeriesDataPointsXml(chartType, obj, opts, scatterVaryColors);
7091
+ }
6727
7092
  if (opts.showLabel) {
6728
7093
  const chartUuid = getUuid("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
6729
7094
  if (obj.labels[0] && (opts.dataLabelFormatScatter === "custom" || opts.dataLabelFormatScatter === "customXY")) {
@@ -6742,13 +7107,13 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6742
7107
  strXml += " <a:pPr>";
6743
7108
  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
7109
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6745
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7110
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6746
7111
  strXml += " </a:defRPr>";
6747
7112
  strXml += " </a:pPr>";
6748
7113
  strXml += " <a:r>";
6749
7114
  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
7115
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6751
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7116
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6752
7117
  strXml += " </a:rPr>";
6753
7118
  strXml += " <a:t>" + encodeXmlEntities(label) + "</a:t>";
6754
7119
  strXml += " </a:r>";
@@ -6828,7 +7193,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6828
7193
  strXml += " <a:pPr>";
6829
7194
  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
7195
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6831
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7196
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6832
7197
  strXml += " </a:defRPr>";
6833
7198
  strXml += " </a:pPr>";
6834
7199
  strXml += ` <a:endParaRPr lang="${opts.lang || "en-US"}"/>`;
@@ -6849,31 +7214,14 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6849
7214
  strXml += "</c:dLbls>";
6850
7215
  }
6851
7216
  }
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
7217
  strXml += "<c:xVal>";
6870
7218
  strXml += " <c:numRef>";
6871
7219
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6872
7220
  strXml += " <c:numCache>";
6873
- strXml += " <c:formatCode>General</c:formatCode>";
7221
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6874
7222
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6875
7223
  data[0].values.forEach((value, idx) => {
6876
- if (value != null) strXml += `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7224
+ strXml += numCachePt(idx, value);
6877
7225
  });
6878
7226
  strXml += " </c:numCache>";
6879
7227
  strXml += " </c:numRef>";
@@ -6882,10 +7230,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6882
7230
  strXml += " <c:numRef>";
6883
7231
  strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`;
6884
7232
  strXml += " <c:numCache>";
6885
- strXml += " <c:formatCode>General</c:formatCode>";
7233
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6886
7234
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6887
7235
  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>`;
7236
+ strXml += numCachePt(idx, obj.values[idx]);
6889
7237
  });
6890
7238
  strXml += " </c:numCache>";
6891
7239
  strXml += " </c:numRef>";
@@ -6901,7 +7249,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6901
7249
  strXml += " <a:p><a:pPr>";
6902
7250
  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
7251
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
6904
- strXml += " <a:latin typeface=\"" + (opts.dataLabelFontFace || "Arial") + "\"/>";
7252
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
6905
7253
  strXml += " </a:defRPr>";
6906
7254
  strXml += " </a:pPr></a:p>";
6907
7255
  strXml += " </c:txPr>";
@@ -6951,10 +7299,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6951
7299
  strXml += " <c:numRef>";
6952
7300
  strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`;
6953
7301
  strXml += " <c:numCache>";
6954
- strXml += " <c:formatCode>General</c:formatCode>";
7302
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6955
7303
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6956
7304
  data[0].values.forEach((value, idx) => {
6957
- strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
7305
+ strXml += numCachePt(idx, value);
6958
7306
  });
6959
7307
  strXml += " </c:numCache>";
6960
7308
  strXml += " </c:numRef>";
@@ -6964,10 +7312,10 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6964
7312
  strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`;
6965
7313
  idxColLtr++;
6966
7314
  strXml += " <c:numCache>";
6967
- strXml += " <c:formatCode>General</c:formatCode>";
7315
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
6968
7316
  strXml += ` <c:ptCount val="${data[0].values.length}"/>`;
6969
7317
  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>`;
7318
+ strXml += numCachePt(idx, obj.values[idx]);
6971
7319
  });
6972
7320
  strXml += " </c:numCache>";
6973
7321
  strXml += " </c:numRef>";
@@ -6980,7 +7328,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6980
7328
  strXml += " <c:formatCode>General</c:formatCode>";
6981
7329
  strXml += ` <c:ptCount val="${obj.sizes.length}"/>`;
6982
7330
  obj.sizes.forEach((value, idx) => {
6983
- strXml += `<c:pt idx="${idx}"><c:v>${value ?? ""}</c:v></c:pt>`;
7331
+ strXml += numCachePt(idx, value);
6984
7332
  });
6985
7333
  strXml += " </c:numCache>";
6986
7334
  strXml += " </c:numRef>";
@@ -6993,12 +7341,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
6993
7341
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
6994
7342
  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
7343
  strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
6996
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7344
+ strXml += createChartTextFonts(opts.dataLabelFontFace || "Arial");
6997
7345
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
6998
7346
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
6999
7347
  strXml += "<c:showLegendKey val=\"0\"/>";
7000
7348
  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"/>`;
7349
+ strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? "1" : "0"}"/><c:showPercent val="0"/><c:showBubbleSize val="${opts.showBubbleSize ? "1" : "0"}"/>`;
7002
7350
  strXml += "<c:extLst>";
7003
7351
  strXml += " <c:ext uri=\"{CE6537A1-D6FC-4f65-9D91-7224C49458BB}\" xmlns:c15=\"http://schemas.microsoft.com/office/drawing/2012/chart\">";
7004
7352
  strXml += " <c15:showLeaderLines val=\"" + (opts.showLeaderLines ? "1" : "0") + "\"/>";
@@ -7032,33 +7380,37 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7032
7380
  else strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7033
7381
  strXml += " </c:spPr>";
7034
7382
  optsChartData.labels[0].forEach((_label, idx) => {
7383
+ const ptStyle = optsChartData.pointStyles?.[idx];
7035
7384
  strXml += "<c:dPt>";
7036
7385
  strXml += ` <c:idx val="${idx}"/>`;
7037
7386
  strXml += " <c:bubble3D val=\"0\"/>";
7038
7387
  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>`;
7388
+ strXml += `<a:solidFill>${createColorElement(ptStyle?.fill || opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`;
7389
+ if (ptStyle?.border) strXml += createChartBorderLine(ptStyle.border);
7390
+ 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
7391
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7042
7392
  strXml += " </c:spPr>";
7043
7393
  strXml += "</c:dPt>";
7044
7394
  });
7045
7395
  strXml += "<c:dLbls>";
7046
7396
  optsChartData.labels[0].forEach((_label, idx) => {
7397
+ const customLbl = optsChartData.customLabels?.[idx];
7047
7398
  strXml += "<c:dLbl>";
7048
7399
  strXml += ` <c:idx val="${idx}"/>`;
7400
+ 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
7401
  strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
7050
7402
  strXml += " <c:spPr/><c:txPr>";
7051
7403
  strXml += " <a:bodyPr/><a:lstStyle/>";
7052
7404
  strXml += " <a:p><a:pPr>";
7053
7405
  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
7406
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
7055
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7407
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
7056
7408
  strXml += " </a:defRPr>";
7057
7409
  strXml += " </a:pPr></a:p>";
7058
7410
  strXml += " </c:txPr>";
7059
7411
  if (chartType === "pie" && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
7060
7412
  strXml += " <c:showLegendKey val=\"0\"/>";
7061
- strXml += " <c:showVal val=\"" + (opts.showValue ? "1" : "0") + "\"/>";
7413
+ strXml += " <c:showVal val=\"" + (customLbl ? "0" : opts.showValue ? "1" : "0") + "\"/>";
7062
7414
  strXml += " <c:showCatName val=\"" + (opts.showLabel ? "1" : "0") + "\"/>";
7063
7415
  strXml += " <c:showSerName val=\"" + (opts.showSerName ? "1" : "0") + "\"/>";
7064
7416
  strXml += " <c:showPercent val=\"" + (opts.showPercent ? "1" : "0") + "\"/>";
@@ -7073,7 +7425,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7073
7425
  strXml += " <a:pPr>";
7074
7426
  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
7427
  strXml += " <a:solidFill>" + createColorElement(opts.dataLabelColor || "000000") + "</a:solidFill>";
7076
- strXml += ` <a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
7428
+ strXml += " " + createChartTextFonts(opts.dataLabelFontFace || "Arial");
7077
7429
  strXml += " </a:defRPr>";
7078
7430
  strXml += " </a:pPr>";
7079
7431
  strXml += " </a:p>";
@@ -7086,6 +7438,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7086
7438
  strXml += " <c:showPercent val=\"1\"/>";
7087
7439
  strXml += " <c:showBubbleSize val=\"0\"/>";
7088
7440
  strXml += ` <c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
7441
+ strXml += createLeaderLinesElement(opts);
7089
7442
  strXml += "</c:dLbls>";
7090
7443
  strXml += "<c:cat>";
7091
7444
  strXml += " <c:strRef>";
@@ -7102,6 +7455,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
7102
7455
  strXml += " <c:numRef>";
7103
7456
  strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`;
7104
7457
  strXml += " <c:numCache>";
7458
+ strXml += " <c:formatCode>" + valFmtCode + "</c:formatCode>";
7105
7459
  strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`;
7106
7460
  optsChartData.values.forEach((value, idx) => {
7107
7461
  strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ""}</c:v></c:pt>`;
@@ -7177,7 +7531,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
7177
7531
  strXml += " <a:pPr>";
7178
7532
  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
7533
  strXml += " <a:solidFill>" + createColorElement(opts.catAxisLabelColor || "000000") + "</a:solidFill>";
7180
- strXml += " <a:latin typeface=\"" + (opts.catAxisLabelFontFace || "Arial") + "\"/>";
7534
+ strXml += " " + createChartTextFonts(opts.catAxisLabelFontFace || "Arial");
7181
7535
  strXml += " </a:defRPr>";
7182
7536
  strXml += " </a:pPr>";
7183
7537
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7270,7 +7624,7 @@ function makeValAxis(opts, valAxisId) {
7270
7624
  strXml += " <a:pPr>";
7271
7625
  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
7626
  strXml += " <a:solidFill>" + createColorElement(opts.valAxisLabelColor || "000000") + "</a:solidFill>";
7273
- strXml += " <a:latin typeface=\"" + (opts.valAxisLabelFontFace || "Arial") + "\"/>";
7627
+ strXml += " " + createChartTextFonts(opts.valAxisLabelFontFace || "Arial");
7274
7628
  strXml += " </a:defRPr>";
7275
7629
  strXml += " </a:pPr>";
7276
7630
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7326,7 +7680,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
7326
7680
  strXml += " <a:pPr>";
7327
7681
  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
7682
  strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || "000000")}</a:solidFill>`;
7329
- strXml += ` <a:latin typeface="${opts.serAxisLabelFontFace || "Arial"}"/>`;
7683
+ strXml += " " + createChartTextFonts(opts.serAxisLabelFontFace || "Arial");
7330
7684
  strXml += " </a:defRPr>";
7331
7685
  strXml += " </a:pPr>";
7332
7686
  strXml += " <a:endParaRPr lang=\"" + (opts.lang || "en-US") + "\"/>";
@@ -7366,17 +7720,31 @@ function genXmlTitle(opts, chartX, chartY) {
7366
7720
  const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : "<a:bodyPr/>";
7367
7721
  const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : "";
7368
7722
  const titleBold = opts.titleBold ? 1 : 0;
7723
+ const titleItalic = opts.titleItalic ? 1 : 0;
7724
+ const titleUnderline = opts.titleUnderline ? "sng" : "none";
7369
7725
  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>`;
7726
+ const hasX = opts.titlePos && typeof opts.titlePos.x === "number";
7727
+ const hasY = opts.titlePos && typeof opts.titlePos.y === "number";
7728
+ if (hasX || hasY) {
7729
+ let modes = "";
7730
+ let vals = "";
7731
+ if (hasX) {
7732
+ const totalX = opts.titlePos.x + chartX;
7733
+ let valX = totalX === 0 ? 0 : totalX * (totalX / 5) / 10;
7734
+ if (valX >= 1) valX = valX / 10;
7735
+ if (valX >= .1) valX = valX / 10;
7736
+ modes += "<c:xMode val=\"edge\"/>";
7737
+ vals += `<c:x val="${valX}"/>`;
7738
+ }
7739
+ if (hasY) {
7740
+ const totalY = opts.titlePos.y + chartY;
7741
+ let valY = totalY === 0 ? 0 : totalY * (totalY / 5) / 10;
7742
+ if (valY >= 1) valY = valY / 10;
7743
+ if (valY >= .1) valY = valY / 10;
7744
+ modes += "<c:yMode val=\"edge\"/>";
7745
+ vals += `<c:y val="${valY}"/>`;
7746
+ }
7747
+ layout = `<c:layout><c:manualLayout>${modes}${vals}</c:manualLayout></c:layout>`;
7380
7748
  }
7381
7749
  return `<c:title>
7382
7750
  <c:tx>
@@ -7385,15 +7753,15 @@ function genXmlTitle(opts, chartX, chartY) {
7385
7753
  <a:lstStyle/>
7386
7754
  <a:p>
7387
7755
  ${align}
7388
- <a:defRPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7756
+ <a:defRPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7389
7757
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7390
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7758
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7391
7759
  </a:defRPr>
7392
7760
  </a:pPr>
7393
7761
  <a:r>
7394
- <a:rPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
7762
+ <a:rPr ${sizeAttr} b="${titleBold}" i="${titleItalic}" u="${titleUnderline}" strike="noStrike">
7395
7763
  <a:solidFill>${createColorElement(opts.color || "000000")}</a:solidFill>
7396
- <a:latin typeface="${opts.fontFace || "Arial"}"/>
7764
+ ${createChartTextFonts(opts.fontFace || "Arial")}
7397
7765
  </a:rPr>
7398
7766
  <a:t>${encodeXmlEntities(opts.title) || ""}</a:t>
7399
7767
  </a:r>
@@ -7467,11 +7835,123 @@ function createGridLineElement(glOpts) {
7467
7835
  strXml += "</c:majorGridlines>";
7468
7836
  return strXml;
7469
7837
  }
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}`);
7838
+ /**
7839
+ * Build a `<c:pt>` numeric-cache data point, or '' to leave a gap.
7840
+ *
7841
+ * `<c:v>` inside a `<c:numCache>` is an `xsd:double`; emitting `NaN`, `Infinity`
7842
+ * or an empty string yields an invalid value that makes PowerPoint report the
7843
+ * package as needing repair. Null/undefined are intentional gaps and are skipped
7844
+ * silently (a sparse, idx-keyed cache is valid); other non-finite numbers are
7845
+ * skipped with a warning, per the library's "warn rather than emit a degenerate
7846
+ * result" policy.
7847
+ * @param idx - zero-based data-point index (emitted as `idx`)
7848
+ * @param value - numeric value (or null/undefined gap)
7849
+ */
7850
+ function numCachePt(idx, value) {
7851
+ if (value == null) return "";
7852
+ if (!Number.isFinite(value)) {
7853
+ console.warn(`Warning: chart value "${value}" at index ${idx} is not a finite number; data point omitted.`);
7854
+ return "";
7855
+ }
7856
+ return `<c:pt idx="${idx}"><c:v>${value}</c:v></c:pt>`;
7857
+ }
7858
+ /**
7859
+ * Build a `<c:serLines>` ("Series Lines") element for a bar chart.
7860
+ * @param opt - `true` for PowerPoint automatic styling, an {@link OptsChartGridLine}
7861
+ * to customize the line, or falsy / `{ style: 'none' }` to omit the element.
7862
+ */
7863
+ function createSerLinesElement(opt) {
7864
+ if (!opt) return "";
7865
+ if (opt === true) return "<c:serLines/>";
7866
+ if (opt.style === "none") return "";
7867
+ let strXml = "<c:serLines><c:spPr>";
7868
+ strXml += `<a:ln w="${valToPts(opt.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(opt.cap || DEF_CHART_GRIDLINE.cap)}">`;
7869
+ strXml += `<a:solidFill><a:srgbClr val="${opt.color || DEF_CHART_GRIDLINE.color}"/></a:solidFill>`;
7870
+ strXml += `<a:prstDash val="${opt.style || DEF_CHART_GRIDLINE.style}"/><a:round/>`;
7871
+ strXml += "</a:ln></c:spPr></c:serLines>";
7872
+ return strXml;
7873
+ }
7874
+ /**
7875
+ * Build the `<c:leaderLines>` element for pie/doughnut data labels.
7876
+ *
7877
+ * Schema position: inside `<c:dLbls>`, immediately after `<c:showLeaderLines>`
7878
+ * (CT_DLbls / Group_DLbls order: showLeaderLines → leaderLines).
7879
+ *
7880
+ * Returns `''` unless the caller both enabled leader lines (`showLeaderLines`)
7881
+ * and configured their appearance (`leaderLineColor` / `leaderLineSize`). When
7882
+ * appearance is unset we leave the element off so PowerPoint applies its
7883
+ * automatic leader-line color, matching prior behavior.
7884
+ *
7885
+ * @param opts - chart options (reads `showLeaderLines`, `leaderLineColor`, `leaderLineSize`)
7886
+ */
7887
+ function createLeaderLinesElement(opts) {
7888
+ if (!opts.showLeaderLines) return "";
7889
+ if (!opts.leaderLineColor && opts.leaderLineSize == null) return "";
7890
+ return `<c:leaderLines><c:spPr><a:ln w="${valToPts(opts.leaderLineSize ?? .75)}" cap="flat"><a:solidFill>${createColorElement(opts.leaderLineColor || "808080")}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln><a:effectLst/></c:spPr></c:leaderLines>`;
7891
+ }
7892
+ function makeCustomDLblXml(idx, text, opts) {
7893
+ const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
7894
+ const bold = opts.dataLabelFontBold ? "1" : "0";
7895
+ const italic = opts.dataLabelFontItalic ? "1" : "0";
7896
+ const color = createColorElement(opts.dataLabelColor || "000000");
7897
+ const face = opts.dataLabelFontFace || "Arial";
7898
+ const lang = opts.lang || "en-US";
7899
+ 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>`;
7900
+ }
7901
+ /**
7902
+ * Build an `<a:ln>` border element from a per-data-point `BorderProps`.
7903
+ * @param border - point border style (`type`, `color`, `pt`)
7904
+ */
7905
+ function createChartBorderLine(border) {
7906
+ if (border.type === "none") return "<a:ln><a:noFill/></a:ln>";
7907
+ const dash = border.type === "dash" ? "dash" : "solid";
7908
+ 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>`;
7909
+ }
7910
+ /**
7911
+ * Build `<c:dPt>` entries for a series in the bar/line/area/scatter loops.
7912
+ *
7913
+ * Merges two sources into a single `c:dPt` per index so we never emit a
7914
+ * duplicate `<c:idx>` (which corrupts the chart):
7915
+ * - legacy single-series color-vary fills (bar/scatter), supplied via `varyColors`
7916
+ * - per-point `pointStyles` border/fill overrides
7917
+ *
7918
+ * Must be emitted in schema position *before* `c:dLbls` (CT_*Ser order).
7919
+ * RADAR is skipped: extra per-point markup historically corrupts the chart.
7920
+ *
7921
+ * @param chartType - series chart type
7922
+ * @param obj - series data (reads `values`, `pointStyles`)
7923
+ * @param opts - chart options (fill/shadow/lineSize context)
7924
+ * @param varyColors - color array when single-series color-vary applies, else `null`
7925
+ */
7926
+ function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
7927
+ if (chartType === "radar") return "";
7928
+ const pointStyles = obj.pointStyles;
7929
+ if (!varyColors && !pointStyles?.length) return "";
7930
+ const isBar = chartType === "bar" || chartType === "bar3D";
7931
+ const isScatter = chartType === "scatter";
7932
+ let xml = "";
7933
+ obj.values.forEach((value, index) => {
7934
+ const ptStyle = pointStyles?.[index];
7935
+ const arrColors = varyColors ? value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : varyColors : null;
7936
+ const fillColor = ptStyle?.fill || (arrColors ? arrColors[index % arrColors.length] : null);
7937
+ const border = ptStyle?.border;
7938
+ if (!fillColor && !border) return;
7939
+ xml += "<c:dPt>";
7940
+ xml += `<c:idx val="${index}"/>`;
7941
+ if (isBar) xml += "<c:invertIfNegative val=\"0\"/>";
7942
+ xml += "<c:bubble3D val=\"0\"/>";
7943
+ xml += "<c:spPr>";
7944
+ if ((isBar || isScatter) && opts.lineSize === 0 && !border && !ptStyle?.fill) xml += "<a:ln><a:noFill/></a:ln>";
7945
+ else {
7946
+ if (fillColor) if (chartType === "bar3D") xml += `<a:ln><a:solidFill>${createColorElement(fillColor)}</a:solidFill></a:ln>`;
7947
+ else xml += `<a:solidFill>${createColorElement(fillColor)}</a:solidFill>`;
7948
+ if (border) xml += createChartBorderLine(border);
7949
+ }
7950
+ xml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
7951
+ xml += "</c:spPr>";
7952
+ xml += "</c:dPt>";
7953
+ });
7954
+ return xml;
7475
7955
  }
7476
7956
  //#endregion
7477
7957
  //#region src/gen-media.ts
@@ -7484,9 +7964,11 @@ function hasEncodingPath(rel) {
7484
7964
  /**
7485
7965
  * Encode Image/Audio/Video into base64
7486
7966
  * @param {PresSlideInternal | SlideLayoutInternal} layout - slide layout
7967
+ * @param {RuntimeAdapter} runtime - runtime adapter (Node/browser media loader)
7968
+ * @param {'throw' | 'placeholder'} onMediaError - failure policy: reject the export (default) or substitute a placeholder and warn
7487
7969
  * @return {Promise} promise
7488
7970
  */
7489
- function encodeSlideMediaRels(layout, runtime) {
7971
+ function encodeSlideMediaRels(layout, runtime, onMediaError = "throw") {
7490
7972
  const imageProms = [];
7491
7973
  const candidateRels = layout._relsMedia.filter((rel) => rel.type !== "online" && !rel.data && hasEncodingPath(rel));
7492
7974
  const unqPaths = [];
@@ -7504,9 +7986,13 @@ function encodeSlideMediaRels(layout, runtime) {
7504
7986
  if (rel.isSvgPng) await runtime.createSvgPngPreview(rel);
7505
7987
  return "done";
7506
7988
  } catch (ex) {
7507
- rel.data = IMG_BROKEN;
7508
- candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path).forEach((dupe) => dupe.data = rel.data);
7509
- throw ex;
7989
+ if (onMediaError === "placeholder") {
7990
+ console.warn(`[WARNING] Failed to load media "${rel.path}"; embedding a broken-image placeholder. (${String(ex)})`);
7991
+ rel.data = IMG_BROKEN;
7992
+ candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path).forEach((dupe) => dupe.data = rel.data);
7993
+ return "done";
7994
+ }
7995
+ throw new Error(`Failed to load media "${rel.path}" during export.`, { cause: ex });
7510
7996
  }
7511
7997
  })());
7512
7998
  });
@@ -7520,6 +8006,37 @@ function encodeSlideMediaRels(layout, runtime) {
7520
8006
  /**
7521
8007
  * PptxGenJS: XML Generation
7522
8008
  */
8009
+ const _warnedTextRangeMsgs = /* @__PURE__ */ new Set();
8010
+ function warnTextRangeOnce(msg) {
8011
+ if (_warnedTextRangeMsgs.has(msg)) return;
8012
+ _warnedTextRangeMsgs.add(msg);
8013
+ console.warn(msg);
8014
+ }
8015
+ /**
8016
+ * Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
8017
+ * hundredths of a point for the `sz` attribute. Out-of-range sizes make
8018
+ * PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
8019
+ */
8020
+ function clampFontSizeSz(fontSizePts) {
8021
+ const raw = Math.round(fontSizePts * 100);
8022
+ const clamped = Math.min(4e5, Math.max(100, raw));
8023
+ if (clamped !== raw) warnTextRangeOnce(`Warning: fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
8024
+ return clamped;
8025
+ }
8026
+ /** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
8027
+ function clampCharSpacingSpc(charSpacingPts) {
8028
+ const raw = Math.round(charSpacingPts * 100);
8029
+ const clamped = Math.min(4e5, Math.max(-4e5, raw));
8030
+ if (clamped !== raw) warnTextRangeOnce(`Warning: charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
8031
+ return clamped;
8032
+ }
8033
+ /** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
8034
+ function clampLineSpacingPts(lineSpacingPts) {
8035
+ const raw = Math.round(lineSpacingPts * 100);
8036
+ const clamped = Math.min(158400, Math.max(0, raw));
8037
+ if (clamped !== raw) warnTextRangeOnce(`Warning: lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
8038
+ return clamped;
8039
+ }
7523
8040
  const ImageSizingXml = {
7524
8041
  cover: function(imgSize, boxDim) {
7525
8042
  const imgRatio = imgSize.h / imgSize.w;
@@ -7566,23 +8083,90 @@ const ImageSizingXml = {
7566
8083
  * @return {string} `<a:prstGeom>` XML
7567
8084
  */
7568
8085
  const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
8086
+ const SHAPE_LOCK_ATTRS = [
8087
+ "noGrp",
8088
+ "noSelect",
8089
+ "noRot",
8090
+ "noChangeAspect",
8091
+ "noMove",
8092
+ "noResize",
8093
+ "noEditPoints",
8094
+ "noAdjustHandles",
8095
+ "noChangeArrowheads",
8096
+ "noChangeShapeType",
8097
+ "noTextEdit"
8098
+ ];
8099
+ const PICTURE_LOCK_ATTRS = [
8100
+ "noGrp",
8101
+ "noSelect",
8102
+ "noRot",
8103
+ "noChangeAspect",
8104
+ "noMove",
8105
+ "noResize",
8106
+ "noEditPoints",
8107
+ "noAdjustHandles",
8108
+ "noChangeArrowheads",
8109
+ "noChangeShapeType",
8110
+ "noCrop"
8111
+ ];
8112
+ const GRAPHIC_FRAME_LOCK_ATTRS = [
8113
+ "noGrp",
8114
+ "noDrilldown",
8115
+ "noSelect",
8116
+ "noChangeAspect",
8117
+ "noMove",
8118
+ "noResize"
8119
+ ];
8120
+ /**
8121
+ * Serialize an object-lock element (`a:spLocks` / `a:picLocks` / `a:graphicFrameLocks`).
8122
+ * Only flags set to `true` AND valid for this element type are emitted; a flag set on an
8123
+ * unsupported element type is dropped with a warning (silent coercion is a footgun).
8124
+ * @param tag - locking element tag, e.g. `'a:spLocks'`
8125
+ * @param allowed - attribute names this element type supports, in desired emit order
8126
+ * @param locks - merged lock flags (callers fold any hard-coded default in first)
8127
+ * @param objectName - for the warning message
8128
+ * @returns the locking element string, or `''` when no applicable flag is set
8129
+ */
8130
+ function genXmlObjectLock(tag, allowed, locks, objectName) {
8131
+ if (!locks) return "";
8132
+ const lockMap = locks;
8133
+ 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.`);
8134
+ const attrs = allowed.filter((name) => lockMap[name] === true).map((name) => `${name}="1"`);
8135
+ return attrs.length > 0 ? `<${tag} ${attrs.join(" ")}/>` : "";
8136
+ }
7569
8137
  function genXmlPresetGeom(shapeName, options, cx, cy) {
7570
- let strXml = `<a:prstGeom prst="${shapeName}"><a:avLst>`;
8138
+ 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.`);
8139
+ let avLst = "";
8140
+ const emittedAdjNames = /* @__PURE__ */ new Set();
8141
+ const emitGuide = (name, fmlaVal) => {
8142
+ avLst += `<a:gd name="${name}" fmla="val ${fmlaVal}"/>`;
8143
+ emittedAdjNames.add(name);
8144
+ };
7571
8145
  if (options.rectRadius) {
7572
8146
  const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
7573
8147
  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}"/>`;
8148
+ emitGuide("adj1", adjVal);
8149
+ emitGuide("adj2", 0);
8150
+ } else emitGuide("adj", adjVal);
7577
8151
  } else if (options.angleRange) {
7578
8152
  for (let i = 0; i < 2; i++) {
7579
8153
  const angle = options.angleRange[i];
7580
- strXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`;
8154
+ emitGuide(`adj${i + 1}`, convertRotationDegrees(angle));
7581
8155
  }
7582
- if (options.arcThicknessRatio) strXml += `<a:gd name="adj3" fmla="val ${Math.round(options.arcThicknessRatio * 5e4)}" />`;
8156
+ if (options.arcThicknessRatio) emitGuide("adj3", Math.round(options.arcThicknessRatio * 5e4));
7583
8157
  }
7584
- strXml += "</a:avLst></a:prstGeom>";
7585
- return strXml;
8158
+ if (options.shapeAdjust) (Array.isArray(options.shapeAdjust) ? options.shapeAdjust : [options.shapeAdjust]).forEach((adj) => {
8159
+ if (!adj || typeof adj.name !== "string" || adj.name.length === 0 || typeof adj.value !== "number" || !isFinite(adj.value)) {
8160
+ console.warn(`Warning: shapeAdjust entry ${JSON.stringify(adj)} is invalid (needs { name:string, value:number }) and was ignored.`);
8161
+ return;
8162
+ }
8163
+ if (emittedAdjNames.has(adj.name)) {
8164
+ console.warn(`Warning: shapeAdjust "${adj.name}" was ignored because rectRadius/angleRange already set that handle.`);
8165
+ return;
8166
+ }
8167
+ emitGuide(adj.name, Math.round(adj.value * 1e5));
8168
+ });
8169
+ return `<a:prstGeom prst="${shapeName}"><a:avLst>${avLst}</a:avLst></a:prstGeom>`;
7586
8170
  }
7587
8171
  /**
7588
8172
  * Emit an `<a:custGeom>` for a freeform path built from `points`.
@@ -7635,6 +8219,45 @@ function genXmlCustGeom(points, cx, cy, layout) {
7635
8219
  }
7636
8220
  const PLACEHOLDER_TYPE_MAP = PLACEHOLDER_TYPES;
7637
8221
  /**
8222
+ * Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
8223
+ * Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
8224
+ * outer edges render with the same border as its origin cell (Issue #680).
8225
+ * @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
8226
+ * @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
8227
+ */
8228
+ function genTableCellBorderXml(cellBorder) {
8229
+ let strXml = "";
8230
+ [
8231
+ {
8232
+ idx: 3,
8233
+ name: "lnL"
8234
+ },
8235
+ {
8236
+ idx: 1,
8237
+ name: "lnR"
8238
+ },
8239
+ {
8240
+ idx: 0,
8241
+ name: "lnT"
8242
+ },
8243
+ {
8244
+ idx: 2,
8245
+ name: "lnB"
8246
+ }
8247
+ ].forEach((obj) => {
8248
+ const border = cellBorder[obj.idx];
8249
+ if (!border) return;
8250
+ const cap = createLineCap(border.cap);
8251
+ if (border.type !== "none") {
8252
+ strXml += `<a:${obj.name} w="${valToPts(border.pt)}" cap="${cap}" cmpd="sng" algn="ctr">`;
8253
+ strXml += `<a:solidFill>${createColorElement(border.color)}</a:solidFill>`;
8254
+ 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"/>`;
8255
+ strXml += `</a:${obj.name}>`;
8256
+ } else strXml += `<a:${obj.name} w="0" cap="${cap}" cmpd="sng" algn="ctr"><a:noFill/></a:${obj.name}>`;
8257
+ });
8258
+ return strXml;
8259
+ }
8260
+ /**
7638
8261
  * Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
7639
8262
  * @param {PresSlideInternal|SlideLayoutInternal} slideObject - slide object created within createSlideObject
7640
8263
  * @return {string} XML string with <p:cSld> as the root
@@ -7693,7 +8316,10 @@ function slideObjectToXml(slide) {
7693
8316
  intColCnt += cellOpts?.colspan ? Number(cellOpts.colspan) : 1;
7694
8317
  });
7695
8318
  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>";
8319
+ strXml += `<p:cNvGraphicFramePr>${genXmlObjectLock("a:graphicFrameLocks", GRAPHIC_FRAME_LOCK_ATTRS, {
8320
+ noGrp: true,
8321
+ ...slideItemObj.options.objectLock
8322
+ }, 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
8323
  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
8324
  {
7699
8325
  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 +8351,8 @@ function slideObjectToXml(slide) {
7725
8351
  return {
7726
8352
  _type: "tablecell",
7727
8353
  options: { rowspan },
7728
- _hmerge: true
8354
+ _hmerge: true,
8355
+ _spanOrigin: cell
7729
8356
  };
7730
8357
  });
7731
8358
  cells.splice(cIdx + 1, 0, ...vMergeCells);
@@ -7741,12 +8368,14 @@ function slideObjectToXml(slide) {
7741
8368
  const colspan = cell.options?.colspan;
7742
8369
  const _hmerge = cell._hmerge;
7743
8370
  if (rowspan && rowspan > 1) {
8371
+ const _spanOrigin = cell._spanOrigin || cell;
7744
8372
  const hMergeCell = {
7745
8373
  _type: "tablecell",
7746
8374
  options: { colspan },
7747
8375
  _rowContinue: rowspan - 1,
7748
8376
  _vmerge: true,
7749
- _hmerge
8377
+ _hmerge,
8378
+ _spanOrigin
7750
8379
  };
7751
8380
  nextRow.splice(cIdx, 0, hMergeCell);
7752
8381
  }
@@ -7756,7 +8385,7 @@ function slideObjectToXml(slide) {
7756
8385
  let intRowH = 0;
7757
8386
  if (Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx]) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx]));
7758
8387
  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);
8388
+ 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
8389
  strXml += `<a:tr h="${intRowH}">`;
7761
8390
  cells.forEach((cellObj) => {
7762
8391
  const cell = cellObj;
@@ -7769,7 +8398,17 @@ function slideObjectToXml(slide) {
7769
8398
  let cellSpanAttrStr = Object.entries(cellSpanAttrs).filter(([, v]) => !!v).map(([k, v]) => `${String(k)}="${String(v)}"`).join(" ");
7770
8399
  if (cellSpanAttrStr) cellSpanAttrStr = " " + cellSpanAttrStr;
7771
8400
  if (cell._hmerge || cell._vmerge) {
7772
- strXml += `<a:tc${cellSpanAttrStr}><a:tcPr/></a:tc>`;
8401
+ const origin = cell._spanOrigin;
8402
+ let spanPrXml = "";
8403
+ if (origin) {
8404
+ const originOpts = origin.options || {};
8405
+ const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
8406
+ if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
8407
+ let spanFill = origin._optImp?.fill?.color ? origin._optImp.fill.color : origin._optImp?.fill && typeof origin._optImp.fill === "string" ? origin._optImp.fill : "";
8408
+ spanFill = spanFill || originOpts.fill ? originOpts.fill : "";
8409
+ if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
8410
+ }
8411
+ strXml += `<a:tc${cellSpanAttrStr}><a:tcPr>${spanPrXml}</a:tcPr></a:tc>`;
7773
8412
  return;
7774
8413
  }
7775
8414
  const cellOpts = cell.options || {};
@@ -7813,32 +8452,7 @@ function slideObjectToXml(slide) {
7813
8452
  else cellMarginXml = ` marL="${inch2Emu(cellMargin[3])}" marR="${inch2Emu(cellMargin[1])}" marT="${inch2Emu(cellMargin[0])}" marB="${inch2Emu(cellMargin[2])}"`;
7814
8453
  strXml += `<a:tc${cellSpanAttrStr}>${genXmlTextBody(cell)}<a:tcPr${cellMarginXml}${cellValign}${cellTextDir}>`;
7815
8454
  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
- });
8455
+ if (cellBorder) strXml += genTableCellBorderXml(cellBorder);
7842
8456
  strXml += cellFill;
7843
8457
  strXml += " </a:tcPr>";
7844
8458
  strXml += " </a:tc>";
@@ -7857,10 +8471,10 @@ function slideObjectToXml(slide) {
7857
8471
  if (!slideItemObj.options.line && cy === 0) cy = EMU * .3;
7858
8472
  if (!slideItemObj.options._bodyProp) slideItemObj.options._bodyProp = {};
7859
8473
  if (slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin)) {
7860
- slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[0] || 0);
8474
+ slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[0] || 0);
7861
8475
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin[1] || 0);
7862
8476
  slideItemObj.options._bodyProp.bIns = valToPts(slideItemObj.options.margin[2] || 0);
7863
- slideItemObj.options._bodyProp.tIns = valToPts(slideItemObj.options.margin[3] || 0);
8477
+ slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin[3] || 0);
7864
8478
  } else if (typeof slideItemObj.options.margin === "number") {
7865
8479
  slideItemObj.options._bodyProp.lIns = valToPts(slideItemObj.options.margin);
7866
8480
  slideItemObj.options._bodyProp.rIns = valToPts(slideItemObj.options.margin);
@@ -7872,7 +8486,11 @@ function slideObjectToXml(slide) {
7872
8486
  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
8487
  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
8488
  strSlideXml += "</p:cNvPr>";
7875
- strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"/>" : "/>");
8489
+ {
8490
+ const spLockXml = genXmlObjectLock("a:spLocks", SHAPE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName);
8491
+ strSlideXml += "<p:cNvSpPr" + (slideItemObj.options?.isTextBox ? " txBox=\"1\"" : "");
8492
+ strSlideXml += spLockXml ? `>${spLockXml}</p:cNvSpPr>` : "/>";
8493
+ }
7876
8494
  strSlideXml += `<p:nvPr>${slideItemObj._type === "placeholder" ? genXmlPlaceholder(slideItemObj) : genXmlPlaceholder(placeholderObj)}</p:nvPr>`;
7877
8495
  strSlideXml += "</p:nvSpPr><p:spPr>";
7878
8496
  strSlideXml += `<a:xfrm${locationAttr}>`;
@@ -7882,7 +8500,8 @@ function slideObjectToXml(slide) {
7882
8500
  else strSlideXml += genXmlPresetGeom(slideItemObj.shape, slideItemObj.options, cx, cy);
7883
8501
  strSlideXml += slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : "<a:noFill/>";
7884
8502
  if (slideItemObj.options.line) {
7885
- strSlideXml += slideItemObj.options.line.width ? `<a:ln w="${valToPts(slideItemObj.options.line.width)}">` : "<a:ln>";
8503
+ const lnAttrs = (slideItemObj.options.line.width ? ` w="${lineWidthToEmu(slideItemObj.options.line.width)}"` : "") + (slideItemObj.options.line.cap ? ` cap="${createLineCap(slideItemObj.options.line.cap)}"` : "");
8504
+ strSlideXml += `<a:ln${lnAttrs}>`;
7886
8505
  if (slideItemObj.options.line.color) strSlideXml += genXmlColorSelection(slideItemObj.options.line);
7887
8506
  if (slideItemObj.options.line.dashType) strSlideXml += `<a:prstDash val="${slideItemObj.options.line.dashType}"/>`;
7888
8507
  if (slideItemObj.options.line.beginArrowType) strSlideXml += `<a:headEnd type="${slideItemObj.options.line.beginArrowType}"/>`;
@@ -7908,6 +8527,24 @@ function slideObjectToXml(slide) {
7908
8527
  strSlideXml += genXmlTextBody(slideItemObj);
7909
8528
  strSlideXml += "</p:sp>";
7910
8529
  break;
8530
+ case "connector":
8531
+ strSlideXml += "<p:cxnSp><p:nvCxnSpPr>";
8532
+ strSlideXml += `<p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
8533
+ strSlideXml += "<p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr><p:spPr>";
8534
+ strSlideXml += `<a:xfrm${locationAttr}><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
8535
+ strSlideXml += `<a:prstGeom prst="${slideItemObj.shape}"><a:avLst/></a:prstGeom>`;
8536
+ {
8537
+ const ln = slideItemObj.options.line || {};
8538
+ const lnAttrs = (ln.width ? ` w="${lineWidthToEmu(ln.width)}"` : "") + (ln.cap ? ` cap="${createLineCap(ln.cap)}"` : "");
8539
+ strSlideXml += `<a:ln${lnAttrs}>`;
8540
+ if (ln.color) strSlideXml += genXmlColorSelection(ln);
8541
+ if (ln.dashType) strSlideXml += `<a:prstDash val="${ln.dashType}"/>`;
8542
+ if (ln.beginArrowType) strSlideXml += `<a:headEnd type="${ln.beginArrowType}"/>`;
8543
+ if (ln.endArrowType) strSlideXml += `<a:tailEnd type="${ln.endArrowType}"/>`;
8544
+ strSlideXml += "</a:ln>";
8545
+ }
8546
+ strSlideXml += "</p:spPr></p:cxnSp>";
8547
+ break;
7911
8548
  case "image":
7912
8549
  strSlideXml += "<p:pic>";
7913
8550
  strSlideXml += " <p:nvPicPr>";
@@ -7915,7 +8552,10 @@ function slideObjectToXml(slide) {
7915
8552
  if (slideItemObj.hyperlink?.url) strSlideXml += `<a:hlinkClick r:id="rId${slideItemObj.hyperlink._rId}" tooltip="${slideItemObj.hyperlink.tooltip ? encodeXmlEntities(slideItemObj.hyperlink.tooltip) : ""}"/>`;
7916
8553
  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
8554
  strSlideXml += " </p:cNvPr>";
7918
- strSlideXml += " <p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>";
8555
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8556
+ noChangeAspect: true,
8557
+ ...slideItemObj.options.objectLock
8558
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7919
8559
  strSlideXml += " <p:nvPr>" + genXmlPlaceholder(placeholderObj) + "</p:nvPr>";
7920
8560
  strSlideXml += " </p:nvPicPr>";
7921
8561
  strSlideXml += "<p:blipFill>";
@@ -7948,7 +8588,7 @@ function slideObjectToXml(slide) {
7948
8588
  const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
7949
8589
  const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
7950
8590
  if (natural) cropSize = natural;
7951
- else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) to enable an aspect-correct crop.`);
8591
+ else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) or an SVG with width/height or a viewBox to enable an aspect-correct crop.`);
7952
8592
  }
7953
8593
  strSlideXml += ImageSizingXml[sizing.type](cropSize, {
7954
8594
  w: boxW,
@@ -7967,6 +8607,16 @@ function slideObjectToXml(slide) {
7967
8607
  strSlideXml += " </a:xfrm>";
7968
8608
  if (slideItemObj.options.points) strSlideXml += " " + genXmlCustGeom(slideItemObj.options.points, imgWidth, imgHeight, slide._presLayout);
7969
8609
  else strSlideXml += " " + genXmlPresetGeom(slideItemObj.options.shape ?? (rounding ? "ellipse" : "rect"), slideItemObj.options, imgWidth, imgHeight);
8610
+ if (slideItemObj.options.line) {
8611
+ const imgLine = slideItemObj.options.line;
8612
+ const lnAttrs = (imgLine.width ? ` w="${lineWidthToEmu(imgLine.width)}"` : "") + (imgLine.cap ? ` cap="${createLineCap(imgLine.cap)}"` : "");
8613
+ strSlideXml += `<a:ln${lnAttrs}>`;
8614
+ if (imgLine.color) strSlideXml += genXmlColorSelection(imgLine);
8615
+ if (imgLine.dashType) strSlideXml += `<a:prstDash val="${imgLine.dashType}"/>`;
8616
+ if (imgLine.beginArrowType) strSlideXml += `<a:headEnd type="${imgLine.beginArrowType}"/>`;
8617
+ if (imgLine.endArrowType) strSlideXml += `<a:tailEnd type="${imgLine.endArrowType}"/>`;
8618
+ strSlideXml += "</a:ln>";
8619
+ }
7970
8620
  if (slideItemObj.options.shadow && slideItemObj.options.shadow.type !== "none") {
7971
8621
  const sh = slideItemObj.options.shadow;
7972
8622
  const shadowType = sh.type || "outer";
@@ -7990,7 +8640,7 @@ function slideObjectToXml(slide) {
7990
8640
  strSlideXml += "<p:pic>";
7991
8641
  strSlideXml += " <p:nvPicPr>";
7992
8642
  strSlideXml += `<p:cNvPr id="${slideItemObj.mediaRid + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
7993
- strSlideXml += " <p:cNvPicPr/>";
8643
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, slideItemObj.options.objectLock, slideItemObj.options.objectName)}</p:cNvPicPr>`;
7994
8644
  strSlideXml += " <p:nvPr>";
7995
8645
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
7996
8646
  strSlideXml += " </p:nvPr>";
@@ -8005,7 +8655,10 @@ function slideObjectToXml(slide) {
8005
8655
  strSlideXml += "<p:pic>";
8006
8656
  strSlideXml += " <p:nvPicPr>";
8007
8657
  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>";
8658
+ strSlideXml += ` <p:cNvPicPr>${genXmlObjectLock("a:picLocks", PICTURE_LOCK_ATTRS, {
8659
+ noChangeAspect: true,
8660
+ ...slideItemObj.options.objectLock
8661
+ }, slideItemObj.options.objectName)}</p:cNvPicPr>`;
8009
8662
  strSlideXml += " <p:nvPr>";
8010
8663
  strSlideXml += ` <a:videoFile r:link="rId${slideItemObj.mediaRid}"/>`;
8011
8664
  strSlideXml += " <p:extLst>";
@@ -8069,7 +8722,7 @@ function slideObjectToXml(slide) {
8069
8722
  strSlideXml += "/>";
8070
8723
  strSlideXml += " <a:lstStyle><a:lvl1pPr>";
8071
8724
  if (slide._slideNumberProps.fontFace || slide._slideNumberProps.fontSize || slide._slideNumberProps.color) {
8072
- strSlideXml += `<a:defRPr sz="${Math.round((slide._slideNumberProps.fontSize || 12) * 100)}">`;
8725
+ strSlideXml += `<a:defRPr sz="${clampFontSizeSz(slide._slideNumberProps.fontSize || 12)}">`;
8073
8726
  if (slide._slideNumberProps.color) strSlideXml += genXmlColorSelection(slide._slideNumberProps.color);
8074
8727
  if (slide._slideNumberProps.fontFace) strSlideXml += `<a:latin typeface="${slide._slideNumberProps.fontFace}"/><a:ea typeface="${slide._slideNumberProps.fontFace}"/><a:cs typeface="${slide._slideNumberProps.fontFace}"/>`;
8075
8728
  strSlideXml += "</a:defRPr>";
@@ -8158,7 +8811,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
8158
8811
  paragraphPropXml += "";
8159
8812
  break;
8160
8813
  }
8161
- if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${Math.round(textObj.options.lineSpacing * 100)}"/></a:lnSpc>`;
8814
+ if (textObj.options.lineSpacing) strXmlLnSpc = `<a:lnSpc><a:spcPts val="${clampLineSpacingPts(textObj.options.lineSpacing)}"/></a:lnSpc>`;
8162
8815
  else if (textObj.options.lineSpacingMultiple) strXmlLnSpc = `<a:lnSpc><a:spcPct val="${Math.round(textObj.options.lineSpacingMultiple * 1e5)}"/></a:lnSpc>`;
8163
8816
  if (textObj.options.indentLevel && !isNaN(Number(textObj.options.indentLevel)) && textObj.options.indentLevel > 0) paragraphPropXml += ` lvl="${textObj.options.indentLevel}"`;
8164
8817
  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>`;
@@ -8166,9 +8819,17 @@ function genXmlParagraphProperties(textObj, isDefault) {
8166
8819
  if (typeof textObj.options.bullet === "object") {
8167
8820
  if (textObj?.options?.bullet?.indent) bulletMarL = valToPts(textObj.options.bullet.indent);
8168
8821
  if (textObj.options.bullet.color) strXmlBulletColor = `<a:buClr>${createColorElement(textObj.options.bullet.color)}</a:buClr>`;
8822
+ let bulletSizePct = 1e5;
8823
+ if (textObj.options.bullet.size !== void 0) {
8824
+ const bulletSize = Number(textObj.options.bullet.size);
8825
+ if (isNaN(bulletSize) || bulletSize < 25 || bulletSize > 400) console.warn("Warning: `bullet.size` must be a percentage between 25 and 400!");
8826
+ else bulletSizePct = Math.round(bulletSize * 1e3);
8827
+ }
8828
+ const strXmlBulletSize = `<a:buSzPct val="${bulletSizePct}"/>`;
8829
+ const strXmlBulletFont = textObj.options.bullet.fontFace ? `<a:buFont typeface="${encodeXmlEntities(textObj.options.bullet.fontFace)}"/>` : "";
8169
8830
  if (textObj.options.bullet.type && textObj.options.bullet.type.toString().toLowerCase() === "number") {
8170
8831
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
8171
- strXmlBullet = `<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="${textObj.options.bullet.style || "arabicPeriod"}" startAt="${textObj.options.bullet.numberStartAt || textObj.options.bullet.startAt || "1"}"/>`;
8832
+ strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont || "<a:buFont typeface=\"+mj-lt\"/>"}<a:buAutoNum type="${textObj.options.bullet.style || "arabicPeriod"}" startAt="${textObj.options.bullet.numberStartAt || textObj.options.bullet.startAt || "1"}"/>`;
8172
8833
  } else if (textObj.options.bullet.characterCode) {
8173
8834
  let bulletCode = `&#x${textObj.options.bullet.characterCode};`;
8174
8835
  if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.characterCode)) {
@@ -8176,7 +8837,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
8176
8837
  bulletCode = "&#x2022;";
8177
8838
  }
8178
8839
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
8179
- strXmlBullet = "<a:buSzPct val=\"100000\"/><a:buChar char=\"" + bulletCode + "\"/>";
8840
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
8180
8841
  } else if (textObj.options.bullet.code) {
8181
8842
  let bulletCode = `&#x${textObj.options.bullet.code};`;
8182
8843
  if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.code)) {
@@ -8184,10 +8845,10 @@ function genXmlParagraphProperties(textObj, isDefault) {
8184
8845
  bulletCode = "&#x2022;";
8185
8846
  }
8186
8847
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
8187
- strXmlBullet = "<a:buSzPct val=\"100000\"/><a:buChar char=\"" + bulletCode + "\"/>";
8848
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
8188
8849
  } else {
8189
8850
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
8190
- strXmlBullet = `<a:buSzPct val="100000"/><a:buChar char="&#x2022;"/>`;
8851
+ strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont}<a:buChar char="&#x2022;"/>`;
8191
8852
  }
8192
8853
  } else if (textObj.options.bullet) {
8193
8854
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
@@ -8212,7 +8873,7 @@ function genXmlTextRunProperties(opts, isDefault) {
8212
8873
  let runProps = "";
8213
8874
  const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
8214
8875
  runProps += "<" + runPropsTag + " lang=\"" + (opts.lang ? opts.lang : "en-US") + "\"" + (opts.lang ? " altLang=\"en-US\"" : "");
8215
- runProps += opts.fontSize ? ` sz="${Math.round(opts.fontSize * 100)}"` : "";
8876
+ runProps += opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "";
8216
8877
  runProps += opts?.bold ? ` b="${opts.bold ? "1" : "0"}"` : "";
8217
8878
  runProps += opts?.italic ? ` i="${opts.italic ? "1" : "0"}"` : "";
8218
8879
  runProps += opts?.strike ? ` strike="${typeof opts.strike === "string" ? opts.strike : "sngStrike"}"` : "";
@@ -8223,17 +8884,23 @@ function genXmlTextRunProperties(opts, isDefault) {
8223
8884
  if (opts.baseline) runProps += ` baseline="${Math.round(opts.baseline * 50)}"`;
8224
8885
  else if (opts.subscript) runProps += " baseline=\"-40000\"";
8225
8886
  else if (opts.superscript) runProps += " baseline=\"30000\"";
8226
- runProps += opts.charSpacing ? ` spc="${Math.round(opts.charSpacing * 100)}" kern="0"` : "";
8887
+ runProps += opts.charSpacing ? ` spc="${clampCharSpacingSpc(opts.charSpacing)}" kern="0"` : "";
8227
8888
  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>`;
8889
+ const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
8890
+ if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
8891
+ if (opts.outline && typeof opts.outline === "object") runProps += `<a:ln w="${lineWidthToEmu(opts.outline.size || .75)}">${genXmlColorSelection(opts.outline.color || "FFFFFF")}</a:ln>`;
8230
8892
  if (opts.color) runProps += genXmlColorSelection({
8231
8893
  color: opts.color,
8232
8894
  transparency: opts.transparency
8233
8895
  });
8896
+ if (opts.glow || hasShadow) {
8897
+ runProps += "<a:effectLst>";
8898
+ if (opts.glow) runProps += createGlowElement(opts.glow, DEF_TEXT_GLOW);
8899
+ if (hasShadow) runProps += createShadowElement$1(opts.shadow, DEF_TEXT_SHADOW);
8900
+ runProps += "</a:effectLst>";
8901
+ }
8234
8902
  if (opts.highlight) runProps += `<a:highlight>${createColorElement(opts.highlight)}</a:highlight>`;
8235
8903
  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
8904
  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
8905
  }
8239
8906
  if (opts.hyperlink) {
@@ -8263,6 +8930,28 @@ function genXmlTextRun(textObj) {
8263
8930
  return `<a:r>${genXmlTextRunProperties(textObj.options, false)}<a:t>${encodeXmlEntities(String(textObj.text))}</a:t></a:r>`;
8264
8931
  }
8265
8932
  /**
8933
+ * Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
8934
+ * @param {TextFitShrinkProps} fit - shrink fit options
8935
+ * @return {string} XML string (`<a:normAutofit .../>`)
8936
+ * @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
8937
+ */
8938
+ function genXmlNormAutofit(fit) {
8939
+ let attrs = "";
8940
+ const pct = (val, name) => {
8941
+ if (val === void 0 || val === null) return null;
8942
+ if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
8943
+ console.warn(`Warning: fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
8944
+ return null;
8945
+ }
8946
+ return Math.round(val * 1e3);
8947
+ };
8948
+ const fontScale = pct(fit.fontScale, "fontScale");
8949
+ if (fontScale !== null) attrs += ` fontScale="${fontScale}"`;
8950
+ const lnSpcReduction = pct(fit.lnSpcReduction, "lnSpcReduction");
8951
+ if (lnSpcReduction !== null) attrs += ` lnSpcReduction="${lnSpcReduction}"`;
8952
+ return `<a:normAutofit${attrs}/>`;
8953
+ }
8954
+ /**
8266
8955
  * Builds `<a:bodyPr></a:bodyPr>` tag for "genXmlTextBody()"
8267
8956
  * @param {ISlideObject | TableCell} slideObject - various options
8268
8957
  * @return {string} XML string
@@ -8275,6 +8964,8 @@ function genXmlBodyProperties(slideObject) {
8275
8964
  if (slideObject.options._bodyProp.tIns || slideObject.options._bodyProp.tIns === 0) bodyProperties += ` tIns="${slideObject.options._bodyProp.tIns}"`;
8276
8965
  if (slideObject.options._bodyProp.rIns || slideObject.options._bodyProp.rIns === 0) bodyProperties += ` rIns="${slideObject.options._bodyProp.rIns}"`;
8277
8966
  if (slideObject.options._bodyProp.bIns || slideObject.options._bodyProp.bIns === 0) bodyProperties += ` bIns="${slideObject.options._bodyProp.bIns}"`;
8967
+ if (slideObject.options._bodyProp.numCol) bodyProperties += ` numCol="${slideObject.options._bodyProp.numCol}"`;
8968
+ if (slideObject.options._bodyProp.spcCol) bodyProperties += ` spcCol="${slideObject.options._bodyProp.spcCol}"`;
8278
8969
  bodyProperties += " rtlCol=\"0\"";
8279
8970
  if (slideObject.options._bodyProp.anchor) bodyProperties += " anchor=\"" + slideObject.options._bodyProp.anchor + "\"";
8280
8971
  if (slideObject.options._bodyProp.vert) bodyProperties += " vert=\"" + slideObject.options._bodyProp.vert + "\"";
@@ -8285,9 +8976,11 @@ function genXmlBodyProperties(slideObject) {
8285
8976
  * @see: http://www.datypic.com/sc/ooxml/g-a_EG_TextAutofit.html
8286
8977
  */
8287
8978
  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/>";
8979
+ const fit = slideObject.options.fit;
8980
+ if (fit === "none") bodyProperties += "";
8981
+ else if (fit === "shrink") bodyProperties += "<a:normAutofit/>";
8982
+ else if (fit === "resize") bodyProperties += "<a:spAutoFit/>";
8983
+ else if (typeof fit === "object" && fit.type === "shrink") bodyProperties += genXmlNormAutofit(fit);
8291
8984
  }
8292
8985
  if (slideObject.options.shrinkText) bodyProperties += "<a:normAutofit/>";
8293
8986
  bodyProperties += slideObject.options._bodyProp.autoFit ? "<a:spAutoFit/>" : "";
@@ -8423,13 +9116,13 @@ function genXmlTextBody(slideObj) {
8423
9116
  }
8424
9117
  });
8425
9118
  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\">";
9119
+ strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\">";
8427
9120
  strSlideXml += `<a:latin typeface="${opts.fontFace}" charset="0"/>`;
8428
9121
  strSlideXml += `<a:ea typeface="${opts.fontFace}" charset="0"/>`;
8429
9122
  strSlideXml += `<a:cs typeface="${opts.fontFace}" charset="0"/>`;
8430
9123
  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\"/>";
9124
+ } else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
9125
+ else if (reqsClosingFontSize) strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}"` + (opts.fontSize ? ` sz="${clampFontSizeSz(opts.fontSize)}"` : "") + " dirty=\"0\"/>";
8433
9126
  else strSlideXml += `<a:endParaRPr lang="${opts.lang || "en-US"}" dirty="0"/>`;
8434
9127
  strSlideXml += "</a:p>";
8435
9128
  });
@@ -8770,11 +9463,51 @@ function getLayoutIdxForSlide(slides, slideLayouts, slideNumber) {
8770
9463
  return 1;
8771
9464
  }
8772
9465
  /**
9466
+ * Theme `<a:clrScheme>` slots in OOXML document order, with their default Office color child.
9467
+ * `dk1`/`lt1` default to `sysClr` (windowText/window); the rest are `srgbClr`. A user override
9468
+ * for any slot is emitted as `<a:srgbClr>` (see `buildThemeClrScheme`).
9469
+ */
9470
+ const THEME_CLR_SCHEME_DEFAULTS = [
9471
+ ["dk1", "<a:sysClr val=\"windowText\" lastClr=\"000000\"/>"],
9472
+ ["lt1", "<a:sysClr val=\"window\" lastClr=\"FFFFFF\"/>"],
9473
+ ["dk2", "<a:srgbClr val=\"44546A\"/>"],
9474
+ ["lt2", "<a:srgbClr val=\"E7E6E6\"/>"],
9475
+ ["accent1", "<a:srgbClr val=\"4472C4\"/>"],
9476
+ ["accent2", "<a:srgbClr val=\"ED7D31\"/>"],
9477
+ ["accent3", "<a:srgbClr val=\"A5A5A5\"/>"],
9478
+ ["accent4", "<a:srgbClr val=\"FFC000\"/>"],
9479
+ ["accent5", "<a:srgbClr val=\"5B9BD5\"/>"],
9480
+ ["accent6", "<a:srgbClr val=\"70AD47\"/>"],
9481
+ ["hlink", "<a:srgbClr val=\"0563C1\"/>"],
9482
+ ["folHlink", "<a:srgbClr val=\"954F72\"/>"]
9483
+ ];
9484
+ /**
9485
+ * Build the theme `<a:clrScheme>` block, applying any caller-supplied color overrides over the
9486
+ * default Office scheme. Invalid (non 6-digit-hex) overrides warn and keep the default rather
9487
+ * than emitting a degenerate color.
9488
+ * @param {ThemeColorScheme} [scheme] - per-slot hex overrides
9489
+ * @return {string} the `<a:clrScheme>...</a:clrScheme>` XML
9490
+ */
9491
+ function buildThemeClrScheme(scheme) {
9492
+ return `<a:clrScheme name="Office">${THEME_CLR_SCHEME_DEFAULTS.map(([slot, defaultChild]) => {
9493
+ const override = scheme?.[slot];
9494
+ let child = defaultChild;
9495
+ if (typeof override === "string" && override.length > 0) {
9496
+ const hex = override.replace("#", "");
9497
+ if (REGEX_HEX_COLOR.test(hex)) child = `<a:srgbClr val="${hex.toUpperCase()}"/>`;
9498
+ else console.warn(`makeXmlTheme: colorScheme.${slot} "${override}" is not a 6-digit hex color; keeping the Office default.`);
9499
+ }
9500
+ return `<a:${slot}>${child}</a:${slot}>`;
9501
+ }).join("")}</a:clrScheme>`;
9502
+ }
9503
+ /**
8773
9504
  * Creates `ppt/theme/theme1.xml`
8774
9505
  * @return {string} XML
8775
9506
  */
8776
9507
  function makeXmlTheme(pres) {
8777
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="4472C4"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="5B9BD5"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="954F72"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont>${pres.theme?.headFontFace ? `<a:latin typeface="${pres.theme?.headFontFace}"/>` : "<a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/>"}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont>${pres.theme?.bodyFontFace ? `<a:latin typeface="${pres.theme?.bodyFontFace}"/>` : "<a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/>"}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>`;
9508
+ const majorFont = pres.theme?.headFontFace ? `<a:latin typeface="${pres.theme?.headFontFace}"/>` : "<a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/>";
9509
+ const minorFont = pres.theme?.bodyFontFace ? `<a:latin typeface="${pres.theme?.bodyFontFace}"/>` : "<a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/>";
9510
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements>${buildThemeClrScheme(pres.theme?.colorScheme)}<a:fontScheme name="Office"><a:majorFont>${majorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont>${minorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>`;
8778
9511
  }
8779
9512
  /**
8780
9513
  * Create presentation file (`ppt/presentation.xml`)
@@ -8905,7 +9638,7 @@ function genXmlTableStyleBorders(border) {
8905
9638
  xml += `<a:${side}>`;
8906
9639
  if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
8907
9640
  else {
8908
- xml += `<a:ln w="${valToPts(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
9641
+ xml += `<a:ln w="${lineWidthToEmu(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
8909
9642
  xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
8910
9643
  xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
8911
9644
  xml += "</a:ln>";
@@ -8983,7 +9716,7 @@ function makeXmlViewProps() {
8983
9716
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
8984
9717
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
8985
9718
  */
8986
- const VERSION = "5.2.0";
9719
+ const VERSION = "5.4.0";
8987
9720
  function standardLayoutToPresLayout(layout) {
8988
9721
  return {
8989
9722
  name: layout.name,
@@ -9235,6 +9968,7 @@ var PptxGenJS$1 = class {
9235
9968
  this._tableStyles = [];
9236
9969
  this._masterSlide = {
9237
9970
  addChart: null,
9971
+ addConnector: null,
9238
9972
  addImage: null,
9239
9973
  addMedia: null,
9240
9974
  addNotes: null,
@@ -9307,14 +10041,27 @@ var PptxGenJS$1 = class {
9307
10041
  const arrChartPromises = [];
9308
10042
  let arrMediaPromises = [];
9309
10043
  const zip = new import_jszip_min.default();
10044
+ const onMediaError = props.onMediaError ?? "throw";
9310
10045
  this._slides.forEach((slide) => {
9311
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime));
10046
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime, onMediaError));
9312
10047
  });
9313
10048
  this._slideLayouts.forEach((layout) => {
9314
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime));
10049
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime, onMediaError));
9315
10050
  });
9316
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
10051
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime, onMediaError));
9317
10052
  return await Promise.all(arrMediaPromises).then(async () => {
10053
+ const canonicalMediaTargets = /* @__PURE__ */ new Map();
10054
+ for (const target of [
10055
+ ...this._slides,
10056
+ ...this._slideLayouts,
10057
+ this._masterSlide
10058
+ ]) for (const rel of target._relsMedia || []) {
10059
+ if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
10060
+ const key = (rel.extn || "") + "\0" + rel.data;
10061
+ const canonical = canonicalMediaTargets.get(key);
10062
+ if (canonical) rel.Target = canonical;
10063
+ else canonicalMediaTargets.set(key, rel.Target);
10064
+ }
9318
10065
  this._slides.forEach((slide) => {
9319
10066
  if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
9320
10067
  });
@@ -9367,14 +10114,18 @@ var PptxGenJS$1 = class {
9367
10114
  });
9368
10115
  this.createChartMediaRels(this._masterSlide, zip, arrChartPromises);
9369
10116
  return await Promise.all(arrChartPromises).then(async () => {
10117
+ const compression = props.compression === false ? "STORE" : "DEFLATE";
9370
10118
  if (props.outputType === "STREAM") return await zip.generateAsync({
9371
10119
  type: "nodebuffer",
9372
- compression: props.compression ? "DEFLATE" : "STORE"
10120
+ compression
10121
+ });
10122
+ else if (props.outputType) return await zip.generateAsync({
10123
+ type: props.outputType,
10124
+ compression
9373
10125
  });
9374
- else if (props.outputType) return await zip.generateAsync({ type: props.outputType });
9375
10126
  else return await zip.generateAsync({
9376
10127
  type: "blob",
9377
- compression: props.compression ? "DEFLATE" : "STORE"
10128
+ compression
9378
10129
  });
9379
10130
  });
9380
10131
  });
@@ -9397,10 +10148,12 @@ var PptxGenJS$1 = class {
9397
10148
  */
9398
10149
  async write(props) {
9399
10150
  const propsOutpType = typeof props === "object" && props?.outputType ? props.outputType : props ? props : null;
9400
- const propsCompress = typeof props === "object" && props?.compression ? props.compression : false;
10151
+ const propsCompress = typeof props === "object" ? props?.compression : void 0;
10152
+ const propsMediaError = typeof props === "object" ? props?.onMediaError : void 0;
9401
10153
  return await this.exportPresentation({
9402
10154
  compression: propsCompress,
9403
- outputType: propsOutpType
10155
+ outputType: propsOutpType,
10156
+ onMediaError: propsMediaError
9404
10157
  });
9405
10158
  }
9406
10159
  /**
@@ -9414,11 +10167,12 @@ var PptxGenJS$1 = class {
9414
10167
  console.warn("[WARNING] writeFile(string) is deprecated - pass { fileName } instead.");
9415
10168
  props = { fileName: props };
9416
10169
  }
9417
- const { fileName: rawName = "Presentation.pptx", compression = false } = props;
10170
+ const { fileName: rawName = "Presentation.pptx", compression, onMediaError } = props;
9418
10171
  const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
9419
10172
  const data = await this.exportPresentation({
9420
10173
  compression,
9421
- outputType: this._runtime.writeFileOutputType
10174
+ outputType: this._runtime.writeFileOutputType,
10175
+ onMediaError
9422
10176
  });
9423
10177
  return await this._runtime.writeFile(fileName, data);
9424
10178
  }
@@ -9666,6 +10420,6 @@ var PptxGenJS = class extends PptxGenJS$1 {
9666
10420
  }
9667
10421
  };
9668
10422
  //#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 };
10423
+ 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, CONNECTOR_PRESETS, 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
10424
 
9671
10425
  //# sourceMappingURL=standalone.js.map