@shbernal/pptxgenjs 5.3.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.
@@ -1,5 +1,5 @@
1
1
  import { a as coordToEmu, i as STANDARD_LAYOUTS, l as inchesToEmu, o as emuToInches } from "./units-BMrBTU0-.js";
2
- import { A as DEF_TEXT_GLOW, B as ONEPT, D as DEF_SHAPE_SHADOW, G as SCHEME_COLOR_NAMES, H as PIECHART_COLORS, I as LAYOUT_IDX_SERIES_BASE, K as SHAPE_TYPE, L as LETTERS, M as EMU, N as IMG_BROKEN, R as LINEH_MODIFIER, T as DEF_PRES_LAYOUT_NAME, U as PLACEHOLDER_TYPES, V as OutputType, W as REGEX_HEX_COLOR, X as ShapeType, Y as SchemeColor, _ as DEF_CELL_MARGIN_IN, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_GRIDLINE, c as AlignH, et as VALID_SHAPE_PRESETS, f as CHART_TYPE, g as DEF_CELL_BORDER, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_SHADOW, k as DEF_SLIDE_MARGIN_IN, l as AlignV, m as ChartType, o as AXIS_ID_VALUE_PRIMARY, q as SLDNUMFLDID, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, u as BARCHART_COLORS, w as DEF_PRES_LAYOUT, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./core-interfaces-BFXQk67T.js";
2
+ import { A as DEF_SLIDE_MARGIN_IN, E as DEF_PRES_LAYOUT_NAME, G as REGEX_HEX_COLOR, H as OutputType, J as SLDNUMFLDID, K as SCHEME_COLOR_NAMES, L as LAYOUT_IDX_SERIES_BASE, M as DEF_TEXT_SHADOW, N as EMU, O as DEF_SHAPE_SHADOW, P as IMG_BROKEN, R as LETTERS, S as DEF_FONT_COLOR, T as DEF_PRES_LAYOUT, U as PIECHART_COLORS, V as ONEPT, W as PLACEHOLDER_TYPES, X as SchemeColor, Z as ShapeType, _ as DEF_CELL_BORDER, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_BORDER, c as AlignH, f as CHART_TYPE, h as ChartType, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_GLOW, l as AlignV, o as AXIS_ID_VALUE_PRIMARY, p as CONNECTOR_PRESETS, q as SHAPE_TYPE, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, tt as VALID_SHAPE_PRESETS, u as BARCHART_COLORS, v as DEF_CELL_MARGIN_IN, x as DEF_CHART_GRIDLINE, z as LINEH_MODIFIER } from "./core-interfaces-C091uvh_.js";
3
3
  import JSZip from "jszip";
4
4
  //#region src/gen-utils.ts
5
5
  /**
@@ -429,15 +429,16 @@ function decodeBase64ToBytes(b64) {
429
429
  }
430
430
  }
431
431
  /**
432
- * Read the intrinsic pixel dimensions of a raster image from its header bytes.
432
+ * Read the intrinsic dimensions of an image from its header bytes.
433
433
  * - synchronous: parses only file-format headers, never decodes pixels
434
- * - supports PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X)
435
- * - vector (SVG) and unrecognized formats return `null` (no intrinsic pixel size)
434
+ * - raster: PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X) — natural pixels
435
+ * - vector: SVG intrinsic size from the root `<svg>` width/height or viewBox
436
+ * - unrecognized formats return `null` (no measurable intrinsic size)
436
437
  *
437
438
  * Used by image `sizing: 'cover' | 'contain'` to compute an aspect-correct
438
439
  * `<a:srcRect>` crop from the *natural* image ratio rather than the displayed box.
439
440
  * @param {string} dataB64 - base64 image payload or `data:` URI
440
- * @returns {{ w: number, h: number } | null} natural pixel size, or `null` when unmeasurable
441
+ * @returns {{ w: number, h: number } | null} natural size, or `null` when unmeasurable
441
442
  */
442
443
  function getImageSizeFromBase64(dataB64) {
443
444
  const b = decodeBase64ToBytes(dataB64);
@@ -523,8 +524,46 @@ function getImageSizeFromBase64(dataB64) {
523
524
  }
524
525
  return null;
525
526
  }
527
+ const text = utf8Decode(b);
528
+ if (/<svg[\s>]/i.test(text)) return getSvgSizeFromMarkup(text);
526
529
  return null;
527
530
  }
531
+ /**
532
+ * Read the intrinsic size of an SVG document from its root `<svg>` element.
533
+ * Follows the SVG sizing model: an explicit absolute `width`/`height` pair wins;
534
+ * otherwise the `viewBox` width/height defines the size (and thus aspect ratio).
535
+ * Percentage or missing `width`/`height` fall through to `viewBox`.
536
+ * @param {string} svg - SVG markup
537
+ * @returns {{ w: number, h: number } | null} intrinsic size, or `null` when undeterminable
538
+ */
539
+ function getSvgSizeFromMarkup(svg) {
540
+ const openTag = /<svg\b[^>]*>/i.exec(svg)?.[0];
541
+ if (!openTag) return null;
542
+ const attr = (name) => new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(openTag)?.[1] ?? null;
543
+ const absLength = (val) => {
544
+ if (val == null || /%\s*$/.test(val)) return NaN;
545
+ const m = /^\s*\+?(\d*\.?\d+)/.exec(val);
546
+ return m ? parseFloat(m[1]) : NaN;
547
+ };
548
+ let w = absLength(attr("width"));
549
+ let h = absLength(attr("height"));
550
+ if (!(w > 0 && h > 0)) {
551
+ const vb = attr("viewBox");
552
+ const p = vb ? vb.trim().split(/[\s,]+/).map(Number) : [];
553
+ if (p.length === 4 && p[2] > 0 && p[3] > 0) {
554
+ w = p[2];
555
+ h = p[3];
556
+ }
557
+ }
558
+ return w > 0 && h > 0 ? {
559
+ w,
560
+ h
561
+ } : null;
562
+ }
563
+ /** Decode UTF-8 bytes to a string, isomorphic across Node and browsers. */
564
+ function utf8Decode(bytes) {
565
+ return new TextDecoder().decode(bytes);
566
+ }
528
567
  //#endregion
529
568
  //#region src/gen-tables.ts
530
569
  /**
@@ -675,6 +714,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
675
714
  let emuSlideTabH = EMU * 1;
676
715
  let emuTabCurrH = 0;
677
716
  let numCols = 0;
717
+ let warnedNoTabH = false;
678
718
  const tableRowSlides = [];
679
719
  const tablePropX = getSmartParseNumber(tableProps.x, "X", presLayout);
680
720
  const tablePropY = getSmartParseNumber(tableProps.y, "Y", presLayout);
@@ -694,6 +734,15 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
694
734
  if (emuSlideTabH < tablePropH) emuSlideTabH = tablePropH;
695
735
  }
696
736
  }
737
+ if (emuSlideTabH <= 0) {
738
+ const emuStartY = tableRowSlides.length === 0 ? tablePropY || inch2Emu(arrInchMargins[0]) : inch2Emu(tableProps.autoPageSlideStartY || tableProps.newSlideStartY || arrInchMargins[0]);
739
+ const fallbackH = presLayout.height - emuStartY - inch2Emu(arrInchMargins[2]);
740
+ if (!warnedNoTabH) {
741
+ console.warn("addTable/autoPage: the table height (`h`) leaves no room to paginate; ignoring it and using the slide height. Increase `h` or decrease `y`.");
742
+ warnedNoTabH = true;
743
+ }
744
+ emuSlideTabH = fallbackH > 0 ? fallbackH : presLayout.height;
745
+ }
697
746
  }
698
747
  if (tableProps.verbose) {
699
748
  console.log("[[VERBOSE MODE]]");
@@ -866,7 +915,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
866
915
  console.log("|-----------------------------------------------------------------------|\n\n");
867
916
  }
868
917
  if (currTableRow.length > 0 && currTableRow.map((cell) => Array.isArray(cell.text) ? cell.text.length : 0).reduce((p, n) => p + n) > 0) newTableRowSlide.rows.push(currTableRow);
869
- tableRowSlides.push(newTableRowSlide);
918
+ if (newTableRowSlide.rows.length > 0) tableRowSlides.push(newTableRowSlide);
870
919
  newTableRowSlide = { rows: [] };
871
920
  currTableRow = [];
872
921
  row.forEach((cell) => currTableRow.push({
@@ -915,7 +964,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
915
964
  for (let c = 0; c < numCols; c++) if (colSpanDepths[c] > 0) colSpanDepths[c]--;
916
965
  if (tableProps.verbose) console.log(`- SLIDE [${tableRowSlides.length}]: ROW [${iRow}]: ...COMPLETE ...... emuTabCurrH = ${(emuTabCurrH / EMU).toFixed(2)} ( emuSlideTabH = ${(emuSlideTabH / EMU).toFixed(2)} )`);
917
966
  });
918
- tableRowSlides.push(newTableRowSlide);
967
+ if (newTableRowSlide.rows.length > 0 || tableRowSlides.length === 0) tableRowSlides.push(newTableRowSlide);
919
968
  if (tableProps.verbose) {
920
969
  console.log("\n|================================================|");
921
970
  console.log(`| FINAL: tableRowSlides.length = ${tableRowSlides.length}`);
@@ -947,6 +996,25 @@ function htmlBorderToProps(widthStr, colorStr) {
947
996
  };
948
997
  }
949
998
  /**
999
+ * Resolve a single HTML-table column width for `tableToSlides`.
1000
+ *
1001
+ * Precedence: an explicit `data-pptx-width` wins outright; otherwise the proportional width
1002
+ * derived from the live table is used, raised to `data-pptx-min-width` when that floor is larger.
1003
+ *
1004
+ * Hidden tables report `offsetWidth` 0 for every cell, which makes `calcWidth` non-finite (a 0/0
1005
+ * proportional calc). Fall back to `0` there so an explicit `data-pptx-width` / `data-pptx-min-width`
1006
+ * override still drives the column instead of emitting a `NaN` width (upstream gitbrent/PptxGenJS#1157).
1007
+ * @param {number} calcWidth - proportional width derived from `offsetWidth` (may be `NaN` for hidden tables)
1008
+ * @param {number} setWidth - `data-pptx-width` override (`0`/`NaN` when absent or invalid)
1009
+ * @param {number} minWidth - `data-pptx-min-width` floor (`0`/`NaN` when absent or invalid)
1010
+ * @returns {number} resolved column width
1011
+ */
1012
+ function resolveHtmlColWidth(calcWidth, setWidth, minWidth) {
1013
+ const safeCalc = isFinite(calcWidth) ? calcWidth : 0;
1014
+ if (isFinite(setWidth) && setWidth > 0) return setWidth;
1015
+ return isFinite(minWidth) && minWidth > safeCalc ? minWidth : safeCalc;
1016
+ }
1017
+ /**
950
1018
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
951
1019
  * @param {TableToSlidesHost} pptx - pptxgenjs instance
952
1020
  * @param {string} tabEleId - HTMLElementID of the table
@@ -1010,12 +1078,10 @@ function genTableToSlides(pptx, tabEleId, options = {}, masterSlide) {
1010
1078
  });
1011
1079
  arrTabColW.forEach((colW, idxW) => {
1012
1080
  const intCalcWidth = Number((Number(emuSlideTabW) * (colW / intTabW * 100) / 100 / EMU).toFixed(2));
1013
- let intMinWidth = 0;
1014
- const colSelectorMin = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
1015
- if (colSelectorMin) intMinWidth = Number(colSelectorMin.getAttribute("data-pptx-min-width"));
1016
- const colSelectorSet = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
1017
- if (colSelectorSet) intMinWidth = Number(colSelectorSet.getAttribute("data-pptx-width"));
1018
- arrColW.push(intMinWidth > intCalcWidth ? intMinWidth : intCalcWidth);
1081
+ const headCell = document.querySelector(`#${tabEleId} thead tr:first-child th:nth-child(${idxW + 1})`);
1082
+ const intSetWidth = headCell ? Number(headCell.getAttribute("data-pptx-width")) : 0;
1083
+ const intMinWidth = headCell ? Number(headCell.getAttribute("data-pptx-min-width")) : 0;
1084
+ arrColW.push(resolveHtmlColWidth(intCalcWidth, intSetWidth, intMinWidth));
1019
1085
  });
1020
1086
  if (opts.verbose) console.log(`| arrColW ......................................... = [${arrColW.join(", ")}]`);
1021
1087
  [
@@ -1189,7 +1255,7 @@ function createSlideMaster(props, target) {
1189
1255
  else if ("line" in object) addShapeDefinition(tgt, "line", object.line);
1190
1256
  else if ("rect" in object) addShapeDefinition(tgt, "rect", object.rect);
1191
1257
  else if ("roundRect" in object) addShapeDefinition(tgt, "roundRect", object.roundRect);
1192
- else if ("text" in object) addTextDefinition(tgt, [{ text: object.text.text }], object.text.options || {}, false);
1258
+ else if ("text" in object) addTextDefinition(tgt, Array.isArray(object.text.text) ? object.text.text : [{ text: object.text.text }], object.text.options || {}, false);
1193
1259
  else if ("placeholder" in object) {
1194
1260
  const placeholder = object.placeholder;
1195
1261
  const { name, type, ...rawPlaceholderOptions } = placeholder.options;
@@ -1799,6 +1865,51 @@ function addShapeDefinition(target, shapeName, opts) {
1799
1865
  target._slideObjects.push(newObject);
1800
1866
  }
1801
1867
  /**
1868
+ * Adds a connector object to a slide definition.
1869
+ * A connector is a line between two points emitted as a PowerPoint connector (`<p:cxnSp>`).
1870
+ * Endpoints are converted to a bounding box (`x/y/w/h`) plus `flipH`/`flipV` so the box can be
1871
+ * oriented from any corner; the connector preset geometry is derived from `type`.
1872
+ * @param {PresSlideInternal} target - slide the connector is added to
1873
+ * @param {ConnectorProps} opts - connector options (endpoints + line styling)
1874
+ */
1875
+ function addConnectorDefinition(target, opts) {
1876
+ if (!opts || [
1877
+ opts.x1,
1878
+ opts.y1,
1879
+ opts.x2,
1880
+ opts.y2
1881
+ ].some((v) => typeof v === "undefined")) throw new Error("addConnector requires { x1, y1, x2, y2 }. Example: `slide.addConnector({ x1:1, y1:1, x2:4, y2:3 })`");
1882
+ const preset = CONNECTOR_PRESETS[opts.type || "straight"];
1883
+ if (!preset) throw new Error(`Invalid connector type "${String(opts.type)}". Use 'straight', 'elbow', or 'curved'.`);
1884
+ const x1 = getSmartParseNumber(opts.x1, "X", target._presLayout) / EMU;
1885
+ const y1 = getSmartParseNumber(opts.y1, "Y", target._presLayout) / EMU;
1886
+ const x2 = getSmartParseNumber(opts.x2, "X", target._presLayout) / EMU;
1887
+ const y2 = getSmartParseNumber(opts.y2, "Y", target._presLayout) / EMU;
1888
+ const newObject = {
1889
+ _type: "connector",
1890
+ shape: preset,
1891
+ options: {
1892
+ x: Math.min(x1, x2),
1893
+ y: Math.min(y1, y2),
1894
+ w: Math.abs(x2 - x1),
1895
+ h: Math.abs(y2 - y1),
1896
+ flipH: x2 < x1,
1897
+ flipV: y2 < y1,
1898
+ line: {
1899
+ type: "solid",
1900
+ color: opts.color || "333333",
1901
+ width: typeof opts.width === "number" ? opts.width : 1,
1902
+ dashType: opts.dashType || "solid",
1903
+ beginArrowType: opts.beginArrowType,
1904
+ endArrowType: opts.endArrowType
1905
+ },
1906
+ altText: opts.altText,
1907
+ objectName: opts.objectName ? encodeXmlEntities(validateObjectName(opts.objectName, "connector")) : `Connector ${target._slideObjects.filter((obj) => obj._type === "connector").length}`
1908
+ }
1909
+ };
1910
+ target._slideObjects.push(newObject);
1911
+ }
1912
+ /**
1802
1913
  * Adds a table object to a slide definition.
1803
1914
  * @param {PresSlideInternal} target - slide object that the table should be added to
1804
1915
  * @param {TableRow[]} tableRows - table data
@@ -1900,6 +2011,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
1900
2011
  });
1901
2012
  }
1902
2013
  opt.autoPage = typeof opt.autoPage === "boolean" ? opt.autoPage : false;
2014
+ opt.autoPagePlaceholder = typeof opt.autoPagePlaceholder === "boolean" ? opt.autoPagePlaceholder : false;
1903
2015
  opt.autoPageRepeatHeader = typeof opt.autoPageRepeatHeader === "boolean" ? opt.autoPageRepeatHeader : false;
1904
2016
  opt.autoPageHeaderRows = typeof opt.autoPageHeaderRows !== "undefined" && !isNaN(Number(opt.autoPageHeaderRows)) ? Number(opt.autoPageHeaderRows) : 1;
1905
2017
  opt.autoPageLineWeight = typeof opt.autoPageLineWeight !== "undefined" && !isNaN(Number(opt.autoPageLineWeight)) ? Number(opt.autoPageLineWeight) : 0;
@@ -1963,12 +2075,14 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
1963
2075
  });
1964
2076
  } else {
1965
2077
  if (opt.autoPageRepeatHeader) opt._arrObjTabHeadRows = arrRows.filter((_row, idx) => idx < (opt.autoPageHeaderRows || 1));
2078
+ const sourcePlaceholders = opt.autoPagePlaceholder && Array.isArray(target._slideObjects) ? target._slideObjects.filter((obj) => obj._type !== "table" && obj.options?.placeholder) : [];
1966
2079
  getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach((slide, idx) => {
1967
2080
  if (!getSlide(target._slideNum + idx)) slides.push(addSlide({ masterName: slideLayout?._name || void 0 }));
1968
2081
  if (idx > 0) opt.y = opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0];
1969
2082
  {
1970
2083
  const newSlide = getSlide(target._slideNum + idx);
1971
2084
  opt.autoPage = false;
2085
+ if (idx > 0 && sourcePlaceholders.length > 0) sourcePlaceholders.forEach((ph) => newSlide._slideObjects.push(structuredClone(ph)));
1972
2086
  createHyperlinkRels(newSlide, slide.rows);
1973
2087
  newSlide.addTable(slide.rows, { ...opt });
1974
2088
  if (idx > 0) newAutoPagedSlides.push(newSlide);
@@ -2321,6 +2435,16 @@ var Slide = class {
2321
2435
  return this;
2322
2436
  }
2323
2437
  /**
2438
+ * Add a connector (a line drawn between two points, emitted as a PowerPoint `<p:cxnSp>`).
2439
+ * @param {ConnectorProps} options - connector endpoints (`x1,y1,x2,y2`) and line styling
2440
+ * @return {Slide} this Slide
2441
+ * @example slide.addConnector({ type: 'elbow', x1: 1, y1: 1, x2: 5, y2: 3, endArrowType: 'triangle' })
2442
+ */
2443
+ addConnector(options) {
2444
+ addConnectorDefinition(this, options);
2445
+ return this;
2446
+ }
2447
+ /**
2324
2448
  * Add table to Slide
2325
2449
  * @param {TableRow[]} tableRows - table rows
2326
2450
  * @param {TableProps} options - table options
@@ -3334,6 +3458,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
3334
3458
  strXml += " <c:showPercent val=\"1\"/>";
3335
3459
  strXml += " <c:showBubbleSize val=\"0\"/>";
3336
3460
  strXml += ` <c:showLeaderLines val="${opts.showLeaderLines ? "1" : "0"}"/>`;
3461
+ strXml += createLeaderLinesElement(opts);
3337
3462
  strXml += "</c:dLbls>";
3338
3463
  strXml += "<c:cat>";
3339
3464
  strXml += " <c:strRef>";
@@ -3766,6 +3891,24 @@ function createSerLinesElement(opt) {
3766
3891
  strXml += "</a:ln></c:spPr></c:serLines>";
3767
3892
  return strXml;
3768
3893
  }
3894
+ /**
3895
+ * Build the `<c:leaderLines>` element for pie/doughnut data labels.
3896
+ *
3897
+ * Schema position: inside `<c:dLbls>`, immediately after `<c:showLeaderLines>`
3898
+ * (CT_DLbls / Group_DLbls order: showLeaderLines → leaderLines).
3899
+ *
3900
+ * Returns `''` unless the caller both enabled leader lines (`showLeaderLines`)
3901
+ * and configured their appearance (`leaderLineColor` / `leaderLineSize`). When
3902
+ * appearance is unset we leave the element off so PowerPoint applies its
3903
+ * automatic leader-line color, matching prior behavior.
3904
+ *
3905
+ * @param opts - chart options (reads `showLeaderLines`, `leaderLineColor`, `leaderLineSize`)
3906
+ */
3907
+ function createLeaderLinesElement(opts) {
3908
+ if (!opts.showLeaderLines) return "";
3909
+ if (!opts.leaderLineColor && opts.leaderLineSize == null) return "";
3910
+ return `<c:leaderLines><c:spPr><a:ln w="${valToPts(opts.leaderLineSize ?? .75)}" cap="flat"><a:solidFill>${createColorElement(opts.leaderLineColor || "808080")}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln><a:effectLst/></c:spPr></c:leaderLines>`;
3911
+ }
3769
3912
  function makeCustomDLblXml(idx, text, opts) {
3770
3913
  const sz = Math.round((opts.dataLabelFontSize || 12) * 100);
3771
3914
  const bold = opts.dataLabelFontBold ? "1" : "0";
@@ -3841,9 +3984,11 @@ function hasEncodingPath(rel) {
3841
3984
  /**
3842
3985
  * Encode Image/Audio/Video into base64
3843
3986
  * @param {PresSlideInternal | SlideLayoutInternal} layout - slide layout
3987
+ * @param {RuntimeAdapter} runtime - runtime adapter (Node/browser media loader)
3988
+ * @param {'throw' | 'placeholder'} onMediaError - failure policy: reject the export (default) or substitute a placeholder and warn
3844
3989
  * @return {Promise} promise
3845
3990
  */
3846
- function encodeSlideMediaRels(layout, runtime) {
3991
+ function encodeSlideMediaRels(layout, runtime, onMediaError = "throw") {
3847
3992
  const imageProms = [];
3848
3993
  const candidateRels = layout._relsMedia.filter((rel) => rel.type !== "online" && !rel.data && hasEncodingPath(rel));
3849
3994
  const unqPaths = [];
@@ -3861,9 +4006,13 @@ function encodeSlideMediaRels(layout, runtime) {
3861
4006
  if (rel.isSvgPng) await runtime.createSvgPngPreview(rel);
3862
4007
  return "done";
3863
4008
  } catch (ex) {
3864
- rel.data = IMG_BROKEN;
3865
- candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path).forEach((dupe) => dupe.data = rel.data);
3866
- throw ex;
4009
+ if (onMediaError === "placeholder") {
4010
+ console.warn(`[WARNING] Failed to load media "${rel.path}"; embedding a broken-image placeholder. (${String(ex)})`);
4011
+ rel.data = IMG_BROKEN;
4012
+ candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path).forEach((dupe) => dupe.data = rel.data);
4013
+ return "done";
4014
+ }
4015
+ throw new Error(`Failed to load media "${rel.path}" during export.`, { cause: ex });
3867
4016
  }
3868
4017
  })());
3869
4018
  });
@@ -4398,6 +4547,24 @@ function slideObjectToXml(slide) {
4398
4547
  strSlideXml += genXmlTextBody(slideItemObj);
4399
4548
  strSlideXml += "</p:sp>";
4400
4549
  break;
4550
+ case "connector":
4551
+ strSlideXml += "<p:cxnSp><p:nvCxnSpPr>";
4552
+ strSlideXml += `<p:cNvPr id="${idx + 2}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
4553
+ strSlideXml += "<p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr><p:spPr>";
4554
+ strSlideXml += `<a:xfrm${locationAttr}><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
4555
+ strSlideXml += `<a:prstGeom prst="${slideItemObj.shape}"><a:avLst/></a:prstGeom>`;
4556
+ {
4557
+ const ln = slideItemObj.options.line || {};
4558
+ const lnAttrs = (ln.width ? ` w="${lineWidthToEmu(ln.width)}"` : "") + (ln.cap ? ` cap="${createLineCap(ln.cap)}"` : "");
4559
+ strSlideXml += `<a:ln${lnAttrs}>`;
4560
+ if (ln.color) strSlideXml += genXmlColorSelection(ln);
4561
+ if (ln.dashType) strSlideXml += `<a:prstDash val="${ln.dashType}"/>`;
4562
+ if (ln.beginArrowType) strSlideXml += `<a:headEnd type="${ln.beginArrowType}"/>`;
4563
+ if (ln.endArrowType) strSlideXml += `<a:tailEnd type="${ln.endArrowType}"/>`;
4564
+ strSlideXml += "</a:ln>";
4565
+ }
4566
+ strSlideXml += "</p:spPr></p:cxnSp>";
4567
+ break;
4401
4568
  case "image":
4402
4569
  strSlideXml += "<p:pic>";
4403
4570
  strSlideXml += " <p:nvPicPr>";
@@ -4441,7 +4608,7 @@ function slideObjectToXml(slide) {
4441
4608
  const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
4442
4609
  const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
4443
4610
  if (natural) cropSize = natural;
4444
- else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) to enable an aspect-correct crop.`);
4611
+ else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) or an SVG with width/height or a viewBox to enable an aspect-correct crop.`);
4445
4612
  }
4446
4613
  strSlideXml += ImageSizingXml[sizing.type](cropSize, {
4447
4614
  w: boxW,
@@ -4460,6 +4627,16 @@ function slideObjectToXml(slide) {
4460
4627
  strSlideXml += " </a:xfrm>";
4461
4628
  if (slideItemObj.options.points) strSlideXml += " " + genXmlCustGeom(slideItemObj.options.points, imgWidth, imgHeight, slide._presLayout);
4462
4629
  else strSlideXml += " " + genXmlPresetGeom(slideItemObj.options.shape ?? (rounding ? "ellipse" : "rect"), slideItemObj.options, imgWidth, imgHeight);
4630
+ if (slideItemObj.options.line) {
4631
+ const imgLine = slideItemObj.options.line;
4632
+ const lnAttrs = (imgLine.width ? ` w="${lineWidthToEmu(imgLine.width)}"` : "") + (imgLine.cap ? ` cap="${createLineCap(imgLine.cap)}"` : "");
4633
+ strSlideXml += `<a:ln${lnAttrs}>`;
4634
+ if (imgLine.color) strSlideXml += genXmlColorSelection(imgLine);
4635
+ if (imgLine.dashType) strSlideXml += `<a:prstDash val="${imgLine.dashType}"/>`;
4636
+ if (imgLine.beginArrowType) strSlideXml += `<a:headEnd type="${imgLine.beginArrowType}"/>`;
4637
+ if (imgLine.endArrowType) strSlideXml += `<a:tailEnd type="${imgLine.endArrowType}"/>`;
4638
+ strSlideXml += "</a:ln>";
4639
+ }
4463
4640
  if (slideItemObj.options.shadow && slideItemObj.options.shadow.type !== "none") {
4464
4641
  const sh = slideItemObj.options.shadow;
4465
4642
  const shadowType = sh.type || "outer";
@@ -4662,9 +4839,17 @@ function genXmlParagraphProperties(textObj, isDefault) {
4662
4839
  if (typeof textObj.options.bullet === "object") {
4663
4840
  if (textObj?.options?.bullet?.indent) bulletMarL = valToPts(textObj.options.bullet.indent);
4664
4841
  if (textObj.options.bullet.color) strXmlBulletColor = `<a:buClr>${createColorElement(textObj.options.bullet.color)}</a:buClr>`;
4842
+ let bulletSizePct = 1e5;
4843
+ if (textObj.options.bullet.size !== void 0) {
4844
+ const bulletSize = Number(textObj.options.bullet.size);
4845
+ if (isNaN(bulletSize) || bulletSize < 25 || bulletSize > 400) console.warn("Warning: `bullet.size` must be a percentage between 25 and 400!");
4846
+ else bulletSizePct = Math.round(bulletSize * 1e3);
4847
+ }
4848
+ const strXmlBulletSize = `<a:buSzPct val="${bulletSizePct}"/>`;
4849
+ const strXmlBulletFont = textObj.options.bullet.fontFace ? `<a:buFont typeface="${encodeXmlEntities(textObj.options.bullet.fontFace)}"/>` : "";
4665
4850
  if (textObj.options.bullet.type && textObj.options.bullet.type.toString().toLowerCase() === "number") {
4666
4851
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
4667
- 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"}"/>`;
4852
+ strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont || "<a:buFont typeface=\"+mj-lt\"/>"}<a:buAutoNum type="${textObj.options.bullet.style || "arabicPeriod"}" startAt="${textObj.options.bullet.numberStartAt || textObj.options.bullet.startAt || "1"}"/>`;
4668
4853
  } else if (textObj.options.bullet.characterCode) {
4669
4854
  let bulletCode = `&#x${textObj.options.bullet.characterCode};`;
4670
4855
  if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.characterCode)) {
@@ -4672,7 +4857,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
4672
4857
  bulletCode = "&#x2022;";
4673
4858
  }
4674
4859
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
4675
- strXmlBullet = "<a:buSzPct val=\"100000\"/><a:buChar char=\"" + bulletCode + "\"/>";
4860
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
4676
4861
  } else if (textObj.options.bullet.code) {
4677
4862
  let bulletCode = `&#x${textObj.options.bullet.code};`;
4678
4863
  if (!/^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.code)) {
@@ -4680,10 +4865,10 @@ function genXmlParagraphProperties(textObj, isDefault) {
4680
4865
  bulletCode = "&#x2022;";
4681
4866
  }
4682
4867
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
4683
- strXmlBullet = "<a:buSzPct val=\"100000\"/><a:buChar char=\"" + bulletCode + "\"/>";
4868
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + "<a:buChar char=\"" + bulletCode + "\"/>";
4684
4869
  } else {
4685
4870
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
4686
- strXmlBullet = `<a:buSzPct val="100000"/><a:buChar char="&#x2022;"/>`;
4871
+ strXmlBullet = `${strXmlBulletSize}${strXmlBulletFont}<a:buChar char="&#x2022;"/>`;
4687
4872
  }
4688
4873
  } else if (textObj.options.bullet) {
4689
4874
  paragraphPropXml += ` marL="${textObj.options.indentLevel && textObj.options.indentLevel > 0 ? bulletMarL + bulletMarL * textObj.options.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
@@ -5298,11 +5483,51 @@ function getLayoutIdxForSlide(slides, slideLayouts, slideNumber) {
5298
5483
  return 1;
5299
5484
  }
5300
5485
  /**
5486
+ * Theme `<a:clrScheme>` slots in OOXML document order, with their default Office color child.
5487
+ * `dk1`/`lt1` default to `sysClr` (windowText/window); the rest are `srgbClr`. A user override
5488
+ * for any slot is emitted as `<a:srgbClr>` (see `buildThemeClrScheme`).
5489
+ */
5490
+ const THEME_CLR_SCHEME_DEFAULTS = [
5491
+ ["dk1", "<a:sysClr val=\"windowText\" lastClr=\"000000\"/>"],
5492
+ ["lt1", "<a:sysClr val=\"window\" lastClr=\"FFFFFF\"/>"],
5493
+ ["dk2", "<a:srgbClr val=\"44546A\"/>"],
5494
+ ["lt2", "<a:srgbClr val=\"E7E6E6\"/>"],
5495
+ ["accent1", "<a:srgbClr val=\"4472C4\"/>"],
5496
+ ["accent2", "<a:srgbClr val=\"ED7D31\"/>"],
5497
+ ["accent3", "<a:srgbClr val=\"A5A5A5\"/>"],
5498
+ ["accent4", "<a:srgbClr val=\"FFC000\"/>"],
5499
+ ["accent5", "<a:srgbClr val=\"5B9BD5\"/>"],
5500
+ ["accent6", "<a:srgbClr val=\"70AD47\"/>"],
5501
+ ["hlink", "<a:srgbClr val=\"0563C1\"/>"],
5502
+ ["folHlink", "<a:srgbClr val=\"954F72\"/>"]
5503
+ ];
5504
+ /**
5505
+ * Build the theme `<a:clrScheme>` block, applying any caller-supplied color overrides over the
5506
+ * default Office scheme. Invalid (non 6-digit-hex) overrides warn and keep the default rather
5507
+ * than emitting a degenerate color.
5508
+ * @param {ThemeColorScheme} [scheme] - per-slot hex overrides
5509
+ * @return {string} the `<a:clrScheme>...</a:clrScheme>` XML
5510
+ */
5511
+ function buildThemeClrScheme(scheme) {
5512
+ return `<a:clrScheme name="Office">${THEME_CLR_SCHEME_DEFAULTS.map(([slot, defaultChild]) => {
5513
+ const override = scheme?.[slot];
5514
+ let child = defaultChild;
5515
+ if (typeof override === "string" && override.length > 0) {
5516
+ const hex = override.replace("#", "");
5517
+ if (REGEX_HEX_COLOR.test(hex)) child = `<a:srgbClr val="${hex.toUpperCase()}"/>`;
5518
+ else console.warn(`makeXmlTheme: colorScheme.${slot} "${override}" is not a 6-digit hex color; keeping the Office default.`);
5519
+ }
5520
+ return `<a:${slot}>${child}</a:${slot}>`;
5521
+ }).join("")}</a:clrScheme>`;
5522
+ }
5523
+ /**
5301
5524
  * Creates `ppt/theme/theme1.xml`
5302
5525
  * @return {string} XML
5303
5526
  */
5304
5527
  function makeXmlTheme(pres) {
5305
- 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>`;
5528
+ const majorFont = pres.theme?.headFontFace ? `<a:latin typeface="${pres.theme?.headFontFace}"/>` : "<a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/>";
5529
+ const minorFont = pres.theme?.bodyFontFace ? `<a:latin typeface="${pres.theme?.bodyFontFace}"/>` : "<a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/>";
5530
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements>${buildThemeClrScheme(pres.theme?.colorScheme)}<a:fontScheme name="Office"><a:majorFont>${majorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont>${minorFont}<a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>`;
5306
5531
  }
5307
5532
  /**
5308
5533
  * Create presentation file (`ppt/presentation.xml`)
@@ -5511,7 +5736,7 @@ function makeXmlViewProps() {
5511
5736
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
5512
5737
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
5513
5738
  */
5514
- const VERSION = "5.3.0";
5739
+ const VERSION = "5.4.0";
5515
5740
  function standardLayoutToPresLayout(layout) {
5516
5741
  return {
5517
5742
  name: layout.name,
@@ -5763,6 +5988,7 @@ var PptxGenJS = class {
5763
5988
  this._tableStyles = [];
5764
5989
  this._masterSlide = {
5765
5990
  addChart: null,
5991
+ addConnector: null,
5766
5992
  addImage: null,
5767
5993
  addMedia: null,
5768
5994
  addNotes: null,
@@ -5835,13 +6061,14 @@ var PptxGenJS = class {
5835
6061
  const arrChartPromises = [];
5836
6062
  let arrMediaPromises = [];
5837
6063
  const zip = new JSZip();
6064
+ const onMediaError = props.onMediaError ?? "throw";
5838
6065
  this._slides.forEach((slide) => {
5839
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime));
6066
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, this._runtime, onMediaError));
5840
6067
  });
5841
6068
  this._slideLayouts.forEach((layout) => {
5842
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime));
6069
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, this._runtime, onMediaError));
5843
6070
  });
5844
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime));
6071
+ arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(this._masterSlide, this._runtime, onMediaError));
5845
6072
  return await Promise.all(arrMediaPromises).then(async () => {
5846
6073
  const canonicalMediaTargets = /* @__PURE__ */ new Map();
5847
6074
  for (const target of [
@@ -5907,14 +6134,18 @@ var PptxGenJS = class {
5907
6134
  });
5908
6135
  this.createChartMediaRels(this._masterSlide, zip, arrChartPromises);
5909
6136
  return await Promise.all(arrChartPromises).then(async () => {
6137
+ const compression = props.compression === false ? "STORE" : "DEFLATE";
5910
6138
  if (props.outputType === "STREAM") return await zip.generateAsync({
5911
6139
  type: "nodebuffer",
5912
- compression: props.compression ? "DEFLATE" : "STORE"
6140
+ compression
6141
+ });
6142
+ else if (props.outputType) return await zip.generateAsync({
6143
+ type: props.outputType,
6144
+ compression
5913
6145
  });
5914
- else if (props.outputType) return await zip.generateAsync({ type: props.outputType });
5915
6146
  else return await zip.generateAsync({
5916
6147
  type: "blob",
5917
- compression: props.compression ? "DEFLATE" : "STORE"
6148
+ compression
5918
6149
  });
5919
6150
  });
5920
6151
  });
@@ -5937,10 +6168,12 @@ var PptxGenJS = class {
5937
6168
  */
5938
6169
  async write(props) {
5939
6170
  const propsOutpType = typeof props === "object" && props?.outputType ? props.outputType : props ? props : null;
5940
- const propsCompress = typeof props === "object" && props?.compression ? props.compression : false;
6171
+ const propsCompress = typeof props === "object" ? props?.compression : void 0;
6172
+ const propsMediaError = typeof props === "object" ? props?.onMediaError : void 0;
5941
6173
  return await this.exportPresentation({
5942
6174
  compression: propsCompress,
5943
- outputType: propsOutpType
6175
+ outputType: propsOutpType,
6176
+ onMediaError: propsMediaError
5944
6177
  });
5945
6178
  }
5946
6179
  /**
@@ -5954,11 +6187,12 @@ var PptxGenJS = class {
5954
6187
  console.warn("[WARNING] writeFile(string) is deprecated - pass { fileName } instead.");
5955
6188
  props = { fileName: props };
5956
6189
  }
5957
- const { fileName: rawName = "Presentation.pptx", compression = false } = props;
6190
+ const { fileName: rawName = "Presentation.pptx", compression, onMediaError } = props;
5958
6191
  const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
5959
6192
  const data = await this.exportPresentation({
5960
6193
  compression,
5961
- outputType: this._runtime.writeFileOutputType
6194
+ outputType: this._runtime.writeFileOutputType,
6195
+ onMediaError
5962
6196
  });
5963
6197
  return await this._runtime.writeFile(fileName, data);
5964
6198
  }
@@ -6120,4 +6354,4 @@ var PptxGenJS = class {
6120
6354
  //#endregion
6121
6355
  export { PptxGenJS as t };
6122
6356
 
6123
- //# sourceMappingURL=pptxgen-B-mAxCRC.js.map
6357
+ //# sourceMappingURL=pptxgen-S8dEuBnC.js.map