@shbernal/pptxgenjs 5.1.0 → 5.2.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 emuToInches, c as inchesToEmu, i as STANDARD_LAYOUTS } from "./units-DmzbVUNp.js";
2
- import { A as EMU, B as PLACEHOLDER_TYPES, C as DEF_PRES_LAYOUT_NAME, D as DEF_SLIDE_MARGIN_IN, F as LINEH_MODIFIER, H as SCHEME_COLOR_NAMES, K as SchemeColor, L as ONEPT, N as LAYOUT_IDX_SERIES_BASE, O as DEF_TEXT_GLOW, P as LETTERS, R as OutputType, S as DEF_PRES_LAYOUT, T as DEF_SHAPE_SHADOW, U as SHAPE_TYPE, V as REGEX_HEX_COLOR, W as SLDNUMFLDID, _ as DEF_CHART_BORDER, a as AXIS_ID_VALUE_SECONDARY, c as BARCHART_COLORS, f as ChartType, h as DEF_CELL_MARGIN_IN, i as AXIS_ID_VALUE_PRIMARY, j as IMG_BROKEN, k as DEF_TEXT_SHADOW, m as DEF_CELL_BORDER, n as AXIS_ID_CATEGORY_SECONDARY, o as AlignH, q as ShapeType, r as AXIS_ID_SERIES_PRIMARY, s as AlignV, t as AXIS_ID_CATEGORY_PRIMARY, u as CHART_TYPE, v as DEF_CHART_GRIDLINE, y as DEF_FONT_COLOR, z as PIECHART_COLORS } from "./core-enums-BCaI1VAf.js";
2
+ import { A as DEF_TEXT_GLOW, B as ONEPT, D as DEF_SHAPE_SHADOW, G as SCHEME_COLOR_NAMES, H as PIECHART_COLORS, I as LAYOUT_IDX_SERIES_BASE, K as SHAPE_TYPE, L as LETTERS, M as EMU, N as IMG_BROKEN, R as LINEH_MODIFIER, T as DEF_PRES_LAYOUT_NAME, U as PLACEHOLDER_TYPES, V as OutputType, W as REGEX_HEX_COLOR, X as ShapeType, Y as SchemeColor, _ as DEF_CELL_MARGIN_IN, a as AXIS_ID_SERIES_PRIMARY, b as DEF_CHART_GRIDLINE, c as AlignH, f as CHART_TYPE, g as DEF_CELL_BORDER, i as AXIS_ID_CATEGORY_SECONDARY, j as DEF_TEXT_SHADOW, k as DEF_SLIDE_MARGIN_IN, l as AlignV, m as ChartType, o as AXIS_ID_VALUE_PRIMARY, q as SLDNUMFLDID, r as AXIS_ID_CATEGORY_PRIMARY, s as AXIS_ID_VALUE_SECONDARY, u as BARCHART_COLORS, w as DEF_PRES_LAYOUT, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./core-interfaces-vUc0ElZs.js";
3
3
  import JSZip from "jszip";
4
4
  //#region src/gen-utils.ts
5
5
  /**
@@ -244,6 +244,17 @@ function genXmlGradientFill(gradient) {
244
244
  return strXml;
245
245
  }
246
246
  /**
247
+ * Create a native DrawingML pattern fill.
248
+ * @param {PatternFillProps} pattern pattern fill options
249
+ * @returns XML string
250
+ */
251
+ function genXmlPatternFill(pattern) {
252
+ if (!pattern) throw new Error("Pattern fill requires a pattern object.");
253
+ const fgColor = pattern.fgColor ?? "000000";
254
+ const bgColor = pattern.bgColor ?? "FFFFFF";
255
+ return `<a:pattFill prst="${pattern.preset}"><a:fgClr>${createColorElement(fgColor)}</a:fgClr><a:bgClr>${createColorElement(bgColor)}</a:bgClr></a:pattFill>`;
256
+ }
257
+ /**
247
258
  * Create color selection
248
259
  * @param {Color | ShapeFillProps | ShapeLineProps} props fill props
249
260
  * @returns XML string
@@ -268,6 +279,9 @@ function genXmlColorSelection(props) {
268
279
  case "gradient":
269
280
  outText += genXmlGradientFill(typeof props === "string" ? void 0 : props.gradient);
270
281
  break;
282
+ case "pattern":
283
+ outText += genXmlPatternFill(typeof props === "string" ? void 0 : props.pattern);
284
+ break;
271
285
  default:
272
286
  outText += "";
273
287
  break;
@@ -333,6 +347,123 @@ function svgMarkupToDataUri(svg) {
333
347
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
334
348
  return `data:image/svg+xml;base64,${btoa(binary)}`;
335
349
  }
350
+ /**
351
+ * Decode a base64 image payload (raw base64 or a `data:` URI) to bytes.
352
+ * - tolerant of the `data:[mime];base64,` prefix and of whitespace in the payload
353
+ * @param {string} b64 - base64 string or data URI
354
+ * @returns {Uint8Array | null} decoded bytes, or `null` when the payload is empty/undecodable
355
+ */
356
+ function decodeBase64ToBytes(b64) {
357
+ if (!b64) return null;
358
+ const comma = b64.indexOf("base64,");
359
+ const payload = (comma >= 0 ? b64.slice(comma + 7) : b64).replace(/\s/g, "");
360
+ if (!payload) return null;
361
+ try {
362
+ const binary = atob(payload);
363
+ const bytes = new Uint8Array(binary.length);
364
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
365
+ return bytes;
366
+ } catch {
367
+ return null;
368
+ }
369
+ }
370
+ /**
371
+ * Read the intrinsic pixel dimensions of a raster image from its header bytes.
372
+ * - synchronous: parses only file-format headers, never decodes pixels
373
+ * - supports PNG, JPEG, GIF, BMP, and WebP (VP8 / VP8L / VP8X)
374
+ * - vector (SVG) and unrecognized formats return `null` (no intrinsic pixel size)
375
+ *
376
+ * Used by image `sizing: 'cover' | 'contain'` to compute an aspect-correct
377
+ * `<a:srcRect>` crop from the *natural* image ratio rather than the displayed box.
378
+ * @param {string} dataB64 - base64 image payload or `data:` URI
379
+ * @returns {{ w: number, h: number } | null} natural pixel size, or `null` when unmeasurable
380
+ */
381
+ function getImageSizeFromBase64(dataB64) {
382
+ const b = decodeBase64ToBytes(dataB64);
383
+ if (!b || b.length < 24) return null;
384
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
385
+ const w = b[16] << 24 | b[17] << 16 | b[18] << 8 | b[19];
386
+ const h = b[20] << 24 | b[21] << 16 | b[22] << 8 | b[23];
387
+ return w > 0 && h > 0 ? {
388
+ w,
389
+ h
390
+ } : null;
391
+ }
392
+ if (b[0] === 71 && b[1] === 73 && b[2] === 70) {
393
+ const w = b[6] | b[7] << 8;
394
+ const h = b[8] | b[9] << 8;
395
+ return w > 0 && h > 0 ? {
396
+ w,
397
+ h
398
+ } : null;
399
+ }
400
+ if (b[0] === 66 && b[1] === 77) {
401
+ const w = b[18] | b[19] << 8 | b[20] << 16 | b[21] << 24;
402
+ const h = b[22] | b[23] << 8 | b[24] << 16 | b[25] << 24;
403
+ const aw = Math.abs(w);
404
+ const ah = Math.abs(h);
405
+ return aw > 0 && ah > 0 ? {
406
+ w: aw,
407
+ h: ah
408
+ } : null;
409
+ }
410
+ if (b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) {
411
+ const fourCC = String.fromCharCode(b[12], b[13], b[14], b[15]);
412
+ if (fourCC === "VP8 " && b.length >= 30) {
413
+ const w = (b[26] | b[27] << 8) & 16383;
414
+ const h = (b[28] | b[29] << 8) & 16383;
415
+ return w > 0 && h > 0 ? {
416
+ w,
417
+ h
418
+ } : null;
419
+ }
420
+ if (fourCC === "VP8L" && b.length >= 25) {
421
+ const bits = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
422
+ const w = (bits & 16383) + 1;
423
+ const h = (bits >> 14 & 16383) + 1;
424
+ return w > 0 && h > 0 ? {
425
+ w,
426
+ h
427
+ } : null;
428
+ }
429
+ if (fourCC === "VP8X" && b.length >= 30) {
430
+ const w = (b[24] | b[25] << 8 | b[26] << 16) + 1;
431
+ const h = (b[27] | b[28] << 8 | b[29] << 16) + 1;
432
+ return w > 0 && h > 0 ? {
433
+ w,
434
+ h
435
+ } : null;
436
+ }
437
+ return null;
438
+ }
439
+ if (b[0] === 255 && b[1] === 216) {
440
+ let i = 2;
441
+ while (i + 9 < b.length) {
442
+ if (b[i] !== 255) {
443
+ i++;
444
+ continue;
445
+ }
446
+ const marker = b[i + 1];
447
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
448
+ const h = b[i + 5] << 8 | b[i + 6];
449
+ const w = b[i + 7] << 8 | b[i + 8];
450
+ return w > 0 && h > 0 ? {
451
+ w,
452
+ h
453
+ } : null;
454
+ }
455
+ if (marker >= 208 && marker <= 217 || marker === 1) {
456
+ i += 2;
457
+ continue;
458
+ }
459
+ const segLen = b[i + 2] << 8 | b[i + 3];
460
+ if (segLen < 2) break;
461
+ i += 2 + segLen;
462
+ }
463
+ return null;
464
+ }
465
+ return null;
466
+ }
336
467
  //#endregion
337
468
  //#region src/gen-tables.ts
338
469
  /**
@@ -386,52 +517,62 @@ function parseTextToLines(cell, colWidth, verbose) {
386
517
  */
387
518
  let newLine = [];
388
519
  inputCells.forEach((cell) => {
389
- if (typeof cell.text === "string") {
390
- if (cell.text.split("\n").length > 1) cell.text.split("\n").forEach((textLine) => {
391
- newLine.push({
520
+ if (typeof cell.text !== "string") return;
521
+ if (cell.text.includes("\n")) {
522
+ const parts = cell.text.split("\n");
523
+ parts.forEach((part, partIdx) => {
524
+ if (partIdx === parts.length - 1) newLine.push({
392
525
  _type: "tablecell",
393
- text: textLine,
394
- options: {
395
- ...cell.options,
396
- breakLine: true
397
- }
526
+ text: part,
527
+ options: cell.options
398
528
  });
529
+ else {
530
+ newLine.push({
531
+ _type: "tablecell",
532
+ text: part,
533
+ options: {
534
+ ...cell.options,
535
+ breakLine: true
536
+ }
537
+ });
538
+ inputLines1.push(newLine);
539
+ newLine = [];
540
+ }
399
541
  });
400
- else newLine.push({
401
- _type: "tablecell",
402
- text: cell.text.trim(),
403
- options: cell.options
404
- });
405
- if (cell.options?.breakLine) {
406
- if (verbose) console.log(`inputCells: new line > ${JSON.stringify(newLine)}`);
407
- inputLines1.push(newLine);
408
- newLine = [];
409
- }
410
- }
411
- if (newLine.length > 0) {
542
+ } else newLine.push({
543
+ _type: "tablecell",
544
+ text: cell.text.trim(),
545
+ options: cell.options
546
+ });
547
+ if (cell.options?.breakLine) {
548
+ if (verbose) console.log(`inputCells: new line > ${JSON.stringify(newLine)}`);
412
549
  inputLines1.push(newLine);
413
550
  newLine = [];
414
551
  }
415
552
  });
553
+ if (newLine.length > 0) {
554
+ inputLines1.push(newLine);
555
+ newLine = [];
556
+ }
416
557
  if (verbose) {
417
558
  console.log(`[2/4] inputLines1 (${inputLines1.length})`);
418
559
  inputLines1.forEach((line, idx) => console.log(`[2/4] [${idx + 1}] line: ${JSON.stringify(line)}`));
419
560
  }
420
561
  inputLines1.forEach((line) => {
562
+ const lineTokens = [];
421
563
  line.forEach((cell) => {
422
- const lineCells = [];
423
564
  const lineWords = String(cell.text).split(" ");
424
565
  lineWords.forEach((word, idx) => {
425
566
  const cellProps = { ...cell.options };
426
567
  if (cellProps?.breakLine) cellProps.breakLine = idx + 1 === lineWords.length;
427
- lineCells.push({
568
+ lineTokens.push({
428
569
  _type: "tablecell",
429
570
  text: word + (idx + 1 < lineWords.length ? " " : ""),
430
571
  options: cellProps
431
572
  });
432
573
  });
433
- inputLines2.push(lineCells);
434
574
  });
575
+ inputLines2.push(lineTokens);
435
576
  });
436
577
  if (verbose) {
437
578
  console.log(`[3/4] inputLines2 (${inputLines2.length})`);
@@ -539,6 +680,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
539
680
  numCols += Number(cellOpts?.colspan ? cellOpts.colspan : 1);
540
681
  });
541
682
  if (tableProps.verbose) console.log(`| numCols ......................................... = ${numCols}`);
683
+ const colSpanDepths = new Array(numCols).fill(0);
542
684
  if (!tablePropW && tableProps.colW) {
543
685
  tableCalcW = Array.isArray(tableProps.colW) ? tableProps.colW.reduce((p, n) => p + n) * EMU : tableProps.colW * numCols || 0;
544
686
  if (tableProps.verbose) console.log(`| tableCalcW ...................................... = ${tableCalcW / EMU}`);
@@ -559,6 +701,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
559
701
  }
560
702
  let newTableRowSlide = { rows: [] };
561
703
  tableRows.forEach((row, iRow) => {
704
+ const hasActiveRowSpan = colSpanDepths.some((d) => d > 0);
562
705
  const rowCellLines = [];
563
706
  let maxCellMarTopEmu = 0;
564
707
  let maxCellMarBtmEmu = 0;
@@ -655,7 +798,7 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
655
798
  rowCellLines.forEach((cell) => {
656
799
  if (cell._lineHeight >= emuLineMaxH) emuLineMaxH = cell._lineHeight;
657
800
  });
658
- if (emuTabCurrH + emuLineMaxH > emuSlideTabH) {
801
+ if (emuTabCurrH + emuLineMaxH > emuSlideTabH && !hasActiveRowSpan) {
659
802
  if (tableProps.verbose) {
660
803
  console.log("\n|-----------------------------------------------------------------------|");
661
804
  console.log(`|-- NEW SLIDE CREATED (currTabH+currLineH > maxH) => ${(emuTabCurrH / EMU).toFixed(2)} + ${(srcCell._lineHeight / EMU).toFixed(2)} > ${emuSlideTabH / EMU}`);
@@ -699,6 +842,16 @@ function getSlidesForTableRows(tableRows = [], tableProps = {}, presLayout, mast
699
842
  if (rowCellLines.map((cell) => cell._lines.length).reduce((prev, next) => prev + next) === 0) isDone = true;
700
843
  }
701
844
  if (currTableRow.length > 0) newTableRowSlide.rows.push(currTableRow);
845
+ const occupiedBefore = [...colSpanDepths];
846
+ let colCursor = 0;
847
+ row.forEach((cell) => {
848
+ while (colCursor < numCols && occupiedBefore[colCursor] > 0) colCursor++;
849
+ const cellColspan = cell.options?.colspan ?? 1;
850
+ const cellRowspan = cell.options?.rowspan ?? 1;
851
+ if (cellRowspan > 1) for (let c = 0; c < cellColspan && colCursor + c < numCols; c++) colSpanDepths[colCursor + c] = cellRowspan;
852
+ colCursor += cellColspan;
853
+ });
854
+ for (let c = 0; c < numCols; c++) if (colSpanDepths[c] > 0) colSpanDepths[c]--;
702
855
  if (tableProps.verbose) console.log(`- SLIDE [${tableRowSlides.length}]: ROW [${iRow}]: ...COMPLETE ...... emuTabCurrH = ${(emuTabCurrH / EMU).toFixed(2)} ( emuSlideTabH = ${(emuSlideTabH / EMU).toFixed(2)} )`);
703
856
  });
704
857
  tableRowSlides.push(newTableRowSlide);
@@ -954,6 +1107,7 @@ function createSlideMaster(props, target) {
954
1107
  else if ("image" in object) addImageDefinition(tgt, object.image);
955
1108
  else if ("line" in object) addShapeDefinition(tgt, "line", object.line);
956
1109
  else if ("rect" in object) addShapeDefinition(tgt, "rect", object.rect);
1110
+ else if ("roundRect" in object) addShapeDefinition(tgt, "roundRect", object.roundRect);
957
1111
  else if ("text" in object) addTextDefinition(tgt, [{ text: object.text.text }], object.text.options || {}, false);
958
1112
  else if ("placeholder" in object) {
959
1113
  const placeholder = object.placeholder;
@@ -1295,6 +1449,7 @@ function addImageDefinition(target, opt) {
1295
1449
  flipV: opt.flipV || false,
1296
1450
  flipH: opt.flipH || false,
1297
1451
  transparency: opt.transparency || 0,
1452
+ duotone: opt.duotone,
1298
1453
  objectName,
1299
1454
  shadow: correctShadowOptions(opt.shadow)
1300
1455
  };
@@ -1344,7 +1499,7 @@ function addImageDefinition(target, opt) {
1344
1499
  type: "hyperlink",
1345
1500
  data: objHyperlink.slide ? "slide" : "dummy",
1346
1501
  rId: imageRelId,
1347
- Target: objHyperlink.url || String(objHyperlink.slide)
1502
+ Target: objHyperlink.url ? encodeXmlEntities(objHyperlink.url) : String(objHyperlink.slide)
1348
1503
  });
1349
1504
  objHyperlink._rId = imageRelId;
1350
1505
  newObject.hyperlink = objHyperlink;
@@ -2102,7 +2257,7 @@ async function createExcelWorksheet(chartObject, zip) {
2102
2257
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${intBubbleCols}" uniqueCount="${intBubbleCols}">`;
2103
2258
  else if (chartObject.opts._type === "scatter") strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${data.length}" uniqueCount="${data.length}">`;
2104
2259
  else if (IS_MULTI_CAT_AXES) {
2105
- let totCount = data.length;
2260
+ let totCount = data.length + 1;
2106
2261
  data[0].labels.forEach((arrLabel) => totCount += arrLabel.filter((label) => label && label !== "").length);
2107
2262
  strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${totCount}" uniqueCount="${totCount}">`;
2108
2263
  strSharedStrings += "<si><t/></si>";
@@ -2170,7 +2325,7 @@ async function createExcelWorksheet(chartObject, zip) {
2170
2325
  strSheetXml += "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"x14ac\" xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\">";
2171
2326
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") strSheetXml += `<dimension ref="A1:${getExcelColName(intBubbleCols)}${data[0].values.length + 1}"/>`;
2172
2327
  else if (chartObject.opts._type === "scatter") strSheetXml += `<dimension ref="A1:${getExcelColName(data.length)}${data[0].values.length + 1}"/>`;
2173
- else strSheetXml += `<dimension ref="A1:${getExcelColName(data.length + 1)}${data[0].values.length + 1}"/>`;
2328
+ else strSheetXml += `<dimension ref="A1:${getExcelColName(data.length + data[0].labels.length)}${data[0].values.length + 1}"/>`;
2174
2329
  strSheetXml += "<sheetViews><sheetView tabSelected=\"1\" workbookViewId=\"0\"><selection activeCell=\"B1\" sqref=\"B1\"/></sheetView></sheetViews>";
2175
2330
  strSheetXml += "<sheetFormatPr baseColWidth=\"10\" defaultRowHeight=\"16\"/>";
2176
2331
  if (chartObject.opts._type === "bubble" || chartObject.opts._type === "bubble3D") {
@@ -2222,82 +2377,28 @@ async function createExcelWorksheet(chartObject, zip) {
2222
2377
  strSheetXml += "</row>";
2223
2378
  });
2224
2379
  } else {
2225
- strSheetXml += `<row r="1" spans="1:${data.length + data[0].labels.length}">`;
2226
- for (let idx = 0; idx < data[0].labels.length; idx++) strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>0</v></c>`;
2227
- for (let idx = data[0].labels.length - 1; idx < data.length + data[0].labels.length - 1; idx++) strSheetXml += `<c r="${getExcelColName(idx + data[0].labels.length)}1" t="s"><v>${idx}</v></c>`;
2228
- strSheetXml += "</row>";
2229
- /**
2230
- * @example INPUT
2231
- * const LABELS = [
2232
- * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
2233
- * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
2234
- * ];
2235
- * const arrDataRegions = [
2236
- * { name: "West", labels: LABELS, values: [11, 8, 3, 0, 11, 3, 0, 0, 0] },
2237
- * { name: "Ctrl", labels: LABELS, values: [0, 11, 6, 19, 12, 5, 0, 0, 0] },
2238
- * { name: "East", labels: LABELS, values: [0, 3, 2, 0, 0, 0, 4, 3, 1] },
2239
- * ];
2240
- */
2241
- /**
2242
- * @example OUTPUT EXCEL SHEET
2243
- * |/|---A--|---B--|---C--|---D--|---E--|
2244
- * |1| | | West | Ctrl | East |
2245
- * |2| Mech | Gear | ## | ## | ## |
2246
- * |3| | Brng | ## | ## | ## |
2247
- * |4| | Motr | ## | ## | ## |
2248
- * |5| Elec | Swch | ## | ## | ## |
2249
- * |6| | Plug | ## | ## | ## |
2250
- * |7| | Cord | ## | ## | ## |
2251
- * |8| Hydr | Pump | ## | ## | ## |
2252
- * |9| | Leak | ## | ## | ## |
2253
- *|10| | Seal | ## | ## | ## |
2254
- */
2255
- /**
2256
- * @example OUTPUT EXCEL SHEET XML
2257
- * <row r="1" spans="1:5">
2258
- * <c r="A1" t="s"><v>0</v></c>
2259
- * <c r="B1" t="s"><v>0</v></c>
2260
- * <c r="C1" t="s"><v>1</v></c>
2261
- * <c r="D1" t="s"><v>2</v></c>
2262
- * <c r="E1" t="s"><v>3</v></c>
2263
- * </row>
2264
- * <row r="2" spans="1:5">
2265
- * <c r="A2" t="s"><v>4</v></c>
2266
- * <c r="B2" t="s"><v>7</v></c>
2267
- * <c r="C2" ><v>###</v></c>
2268
- * </row>
2269
- * <row r="3" spans="1:5">
2270
- * <c r="A3" />
2271
- * <c r="B3" t="s"><v>8</v></c>
2272
- * <c r="C3" ><v>###</v></c>
2273
- * </row>
2274
- */
2275
- /**
2276
- * @example SHARED-STRINGS
2277
- * 1=West, 2=Ctrl, 3=East, 4=Mech, 5=Elec, 6=Mydr, 7=Gear, 8=Brng, [...], 15=Seal
2278
- */
2279
- /**
2280
- * const LABELS = [
2281
- * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
2282
- * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
2283
- * ["2010", "", "", "", "", "", "", "", ""],
2284
- * ];
2285
- */
2286
2380
  const TOT_SER = data.length;
2287
2381
  const TOT_CAT = data[0].labels[0].length;
2288
2382
  const TOT_LVL = data[0].labels.length;
2383
+ const revLabelGroups = data[0].labels.slice().reverse();
2384
+ const ssLabelMap = /* @__PURE__ */ new Map();
2385
+ let ssIdx = TOT_SER + 1;
2386
+ revLabelGroups.forEach((labelsGroup, revLevelIdx) => {
2387
+ labelsGroup.forEach((label, rowIdx) => {
2388
+ if (label && label !== "") ssLabelMap.set(`${revLevelIdx}:${rowIdx}`, ssIdx++);
2389
+ });
2390
+ });
2391
+ strSheetXml += `<row r="1" spans="1:${TOT_SER + TOT_LVL}">`;
2392
+ for (let col = 1; col <= TOT_LVL; col++) strSheetXml += `<c r="${getExcelColName(col)}1" t="s"><v>0</v></c>`;
2393
+ for (let ser = 0; ser < TOT_SER; ser++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + ser + 1)}1" t="s"><v>${ser + 1}</v></c>`;
2394
+ strSheetXml += "</row>";
2289
2395
  for (let idx = 0; idx < TOT_CAT; idx++) {
2290
2396
  strSheetXml += `<row r="${idx + 2}" spans="1:${TOT_SER + TOT_LVL}">`;
2291
- let totLabels = TOT_SER;
2292
- const revLabelGroups = data[0].labels.slice().reverse();
2293
2397
  revLabelGroups.forEach((labelsGroup, idy) => {
2294
- if (labelsGroup[idx]) {
2295
- const totGrpLbls = idy === 0 ? 1 : revLabelGroups[idy - 1].filter((label) => label && label !== "").length;
2296
- totLabels += totGrpLbls;
2297
- strSheetXml += `<c r="${getExcelColName(idx + 1 + idy)}${idx + 2}" t="s"><v>${totLabels}</v></c>`;
2298
- }
2398
+ const colLabel = labelsGroup[idx];
2399
+ if (colLabel && colLabel !== "") strSheetXml += `<c r="${getExcelColName(idy + 1)}${idx + 2}" t="s"><v>${ssLabelMap.get(`${idy}:${idx}`)}</v></c>`;
2299
2400
  });
2300
- for (let idy = 0; idy < TOT_SER; idy++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] || 0}</v></c>`;
2401
+ for (let idy = 0; idy < TOT_SER; idy++) strSheetXml += `<c r="${getExcelColName(TOT_LVL + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] ?? ""}</v></c>`;
2301
2402
  strSheetXml += "</row>";
2302
2403
  }
2303
2404
  }
@@ -2440,6 +2541,13 @@ function makeXmlCharts(rel) {
2440
2541
  if (rel.opts.showLegend) {
2441
2542
  strXml += "<c:legend>";
2442
2543
  strXml += "<c:legendPos val=\"" + rel.opts.legendPos + "\"/>";
2544
+ if (Array.isArray(rel.opts._type)) {
2545
+ let seriesIdx = 0;
2546
+ rel.opts._type.forEach((type) => {
2547
+ if (type.options?.showLegend === false) for (let i = 0; i < type.data.length; i++) strXml += `<c:legendEntry><c:idx val="${seriesIdx + i}"/><c:delete val="1"/></c:legendEntry>`;
2548
+ seriesIdx += type.data.length;
2549
+ });
2550
+ }
2443
2551
  strXml += "<c:overlay val=\"0\"/>";
2444
2552
  if (rel.opts.legendFontFace || rel.opts.legendFontSize || rel.opts.legendColor) {
2445
2553
  strXml += "<c:txPr>";
@@ -2515,17 +2623,20 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2515
2623
  strXml += " <c:strCache><c:ptCount val=\"1\"/><c:pt idx=\"0\"><c:v>" + encodeXmlEntities(obj.name) + "</c:v></c:pt></c:strCache>";
2516
2624
  strXml += " </c:strRef>";
2517
2625
  strXml += " </c:tx>";
2518
- const seriesColor = opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null;
2626
+ const seriesOverride = opts.seriesOptions?.[obj._dataIndex];
2627
+ const seriesColor = seriesOverride?.color ?? (opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null);
2519
2628
  strXml += " <c:spPr>";
2520
2629
  if (seriesColor === "transparent") strXml += "<a:noFill/>";
2521
2630
  else if (opts.chartColorsOpacity) strXml += "<a:solidFill>" + createColorElement(seriesColor, `<a:alpha val="${Math.round(opts.chartColorsOpacity * 1e3)}"/>`) + "</a:solidFill>";
2522
2631
  else strXml += "<a:solidFill>" + createColorElement(seriesColor) + "</a:solidFill>";
2523
- if (chartType === "line" || chartType === "radar") if (opts.lineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
2524
- else {
2525
- strXml += `<a:ln w="${valToPts(opts.lineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(seriesColor)}</a:solidFill>`;
2526
- strXml += `<a:prstDash val="${opts.lineDashValues?.[colorIndex] ?? opts.lineDash ?? "solid"}"/><a:round/></a:ln>`;
2527
- }
2528
- 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>`;
2632
+ if (chartType === "line" || chartType === "radar") {
2633
+ const effectiveLineSize = seriesOverride?.lineSize ?? opts.lineSize;
2634
+ if (effectiveLineSize === 0) strXml += "<a:ln><a:noFill/></a:ln>";
2635
+ else {
2636
+ strXml += `<a:ln w="${valToPts(effectiveLineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(seriesColor)}</a:solidFill>`;
2637
+ strXml += `<a:prstDash val="${opts.lineDashValues?.[colorIndex] ?? opts.lineDash ?? "solid"}"/><a:round/></a:ln>`;
2638
+ }
2639
+ } 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>`;
2529
2640
  strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
2530
2641
  strXml += " </c:spPr>";
2531
2642
  if (chartType !== "line" && chartType !== "radar") strXml += " <c:invertIfNegative val=\"0\"/>";
@@ -2544,13 +2655,18 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId) {
2544
2655
  strXml += "</c:marker>";
2545
2656
  }
2546
2657
  if (chartType !== "radar") {
2658
+ const lblColor = seriesOverride?.dataLabelColor ?? opts.dataLabelColor ?? "000000";
2659
+ const lblBold = seriesOverride?.dataLabelFontBold ?? opts.dataLabelFontBold ?? false;
2660
+ const lblItalic = seriesOverride?.dataLabelFontItalic ?? opts.dataLabelFontItalic ?? false;
2661
+ const lblSize = seriesOverride?.dataLabelFontSize ?? opts.dataLabelFontSize ?? 12;
2662
+ const lblFace = seriesOverride?.dataLabelFontFace ?? opts.dataLabelFontFace ?? "Arial";
2547
2663
  strXml += "<c:dLbls>";
2548
2664
  strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || "General"}" sourceLinked="0"/>`;
2549
2665
  if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`;
2550
2666
  strXml += "<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>";
2551
- strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || 12) * 100)}" u="none">`;
2552
- strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || "000000")}</a:solidFill>`;
2553
- strXml += `<a:latin typeface="${opts.dataLabelFontFace || "Arial"}"/>`;
2667
+ strXml += `<a:defRPr b="${lblBold ? 1 : 0}" i="${lblItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(lblSize * 100)}" u="none">`;
2668
+ strXml += `<a:solidFill>${createColorElement(lblColor)}</a:solidFill>`;
2669
+ strXml += `<a:latin typeface="${lblFace}"/>`;
2554
2670
  strXml += "</a:defRPr></a:pPr></a:p></c:txPr>";
2555
2671
  if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`;
2556
2672
  strXml += "<c:showLegendKey val=\"0\"/>";
@@ -3524,6 +3640,15 @@ const ImageSizingXml = {
3524
3640
  const r = imgSize.w - (boxDim.x + boxDim.w);
3525
3641
  const t = boxDim.y;
3526
3642
  const b = imgSize.h - (boxDim.y + boxDim.h);
3643
+ if (l < 0 || r < 0 || t < 0 || b < 0) {
3644
+ const over = [
3645
+ l < 0 && `x (${l < 0 ? -l : 0} past left edge)`,
3646
+ r < 0 && `x+w (${-r} past right edge)`,
3647
+ t < 0 && `y (${-t} past top edge)`,
3648
+ b < 0 && `y+h (${-b} past bottom edge)`
3649
+ ].filter(Boolean).join(", ");
3650
+ throw new Error(`addImage sizing.type 'crop': crop window overflows image bounds — ${over}. Ensure x≥0, y≥0, x+w≤w, y+h≤h.`);
3651
+ }
3527
3652
  return `<a:srcRect l="${Math.round(1e5 * (l / imgSize.w))}" r="${Math.round(1e5 * (r / imgSize.w))}" t="${Math.round(1e5 * (t / imgSize.h))}" b="${Math.round(1e5 * (b / imgSize.h))}"/><a:stretch/>`;
3528
3653
  }
3529
3654
  };
@@ -3536,10 +3661,16 @@ const ImageSizingXml = {
3536
3661
  * @param {number} cy - shape height (EMU), used to scale `rectRadius`
3537
3662
  * @return {string} `<a:prstGeom>` XML
3538
3663
  */
3664
+ const RECT_RADIUS_ADJ1_SHAPES = new Set(["round2SameRect", "round2DiagRect"]);
3539
3665
  function genXmlPresetGeom(shapeName, options, cx, cy) {
3540
3666
  let strXml = `<a:prstGeom prst="${shapeName}"><a:avLst>`;
3541
- if (options.rectRadius) strXml += `<a:gd name="adj" fmla="val ${Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy))}"/>`;
3542
- else if (options.angleRange) {
3667
+ if (options.rectRadius) {
3668
+ const adjVal = Math.round(options.rectRadius * EMU * 1e5 / Math.min(cx, cy));
3669
+ if (RECT_RADIUS_ADJ1_SHAPES.has(shapeName)) {
3670
+ strXml += `<a:gd name="adj1" fmla="val ${adjVal}"/>`;
3671
+ strXml += "<a:gd name=\"adj2\" fmla=\"val 0\"/>";
3672
+ } else strXml += `<a:gd name="adj" fmla="val ${adjVal}"/>`;
3673
+ } else if (options.angleRange) {
3543
3674
  for (let i = 0; i < 2; i++) {
3544
3675
  const angle = options.angleRange[i];
3545
3676
  strXml += `<a:gd name="adj${i + 1}" fmla="val ${convertRotationDegrees(angle)}" />`;
@@ -3660,7 +3791,11 @@ function slideObjectToXml(slide) {
3660
3791
  strXml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${intTableNum * slide._slideNum + 1}" name="${slideItemObj.options.objectName}" descr="${encodeXmlEntities(slideItemObj.options.altText || "")}"/>`;
3661
3792
  strXml += "<p:cNvGraphicFramePr><a:graphicFrameLocks noGrp=\"1\"/></p:cNvGraphicFramePr> <p:nvPr><p:extLst><p:ext uri=\"{D42A27DB-BD31-4B8C-83A1-F6EECF244321}\"><p14:modId xmlns:p14=\"http://schemas.microsoft.com/office/powerpoint/2010/main\" val=\"1579011935\"/></p:ext></p:extLst></p:nvPr></p:nvGraphicFramePr>";
3662
3793
  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>`;
3663
- strXml += "<a:graphic><a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/table\"><a:tbl><a:tblPr/>";
3794
+ {
3795
+ 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\"" : "");
3796
+ const tblPr = objTabOpts.tableStyle ? `<a:tblPr${tblPrAttrs}><a:tableStyleId>${objTabOpts.tableStyle}</a:tableStyleId></a:tblPr>` : `<a:tblPr${tblPrAttrs}/>`;
3797
+ strXml += `<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl>${tblPr}`;
3798
+ }
3664
3799
  if (Array.isArray(objTabOpts.colW)) {
3665
3800
  strXml += "<a:tblGrid>";
3666
3801
  for (let col = 0; col < intColCnt; col++) {
@@ -3883,6 +4018,7 @@ function slideObjectToXml(slide) {
3883
4018
  if ((slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.extn === "svg") {
3884
4019
  strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid - 1}">`;
3885
4020
  strSlideXml += slideItemObj.options.transparency ? ` <a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1e3)}"/>` : "";
4021
+ strSlideXml += slideItemObj.options.duotone ? `<a:duotone>${createColorElement(slideItemObj.options.duotone.shadow)}${createColorElement(slideItemObj.options.duotone.highlight)}</a:duotone>` : "";
3886
4022
  strSlideXml += " <a:extLst>";
3887
4023
  strSlideXml += " <a:ext uri=\"{96DAC541-7B7A-43D3-8B79-37D633B846F1}\">";
3888
4024
  strSlideXml += ` <asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="rId${slideItemObj.imageRid}"/>`;
@@ -3892,6 +4028,7 @@ function slideObjectToXml(slide) {
3892
4028
  } else {
3893
4029
  strSlideXml += `<a:blip r:embed="rId${slideItemObj.imageRid}">`;
3894
4030
  strSlideXml += slideItemObj.options.transparency ? `<a:alphaModFix amt="${Math.round((100 - slideItemObj.options.transparency) * 1e3)}"/>` : "";
4031
+ strSlideXml += slideItemObj.options.duotone ? `<a:duotone>${createColorElement(slideItemObj.options.duotone.shadow)}${createColorElement(slideItemObj.options.duotone.highlight)}</a:duotone>` : "";
3895
4032
  strSlideXml += "</a:blip>";
3896
4033
  }
3897
4034
  if (sizing?.type) {
@@ -3899,10 +4036,17 @@ function slideObjectToXml(slide) {
3899
4036
  const boxH = sizing.h ? getSmartParseNumber(sizing.h, "Y", slide._presLayout) : cy;
3900
4037
  const boxX = getSmartParseNumber(sizing.x || 0, "X", slide._presLayout);
3901
4038
  const boxY = getSmartParseNumber(sizing.y || 0, "Y", slide._presLayout);
3902
- strSlideXml += ImageSizingXml[sizing.type]({
4039
+ let cropSize = {
3903
4040
  w: imgWidth,
3904
4041
  h: imgHeight
3905
- }, {
4042
+ };
4043
+ if (sizing.type === "cover" || sizing.type === "contain") {
4044
+ const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
4045
+ const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
4046
+ if (natural) cropSize = natural;
4047
+ else console.warn(`Warning: sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) to enable an aspect-correct crop.`);
4048
+ }
4049
+ strSlideXml += ImageSizingXml[sizing.type](cropSize, {
3906
4050
  w: boxW,
3907
4051
  h: boxH,
3908
4052
  x: boxX,
@@ -4298,14 +4442,19 @@ function genXmlTextBody(slideObj) {
4298
4442
  itext.options = itext.options || opts || {};
4299
4443
  if (idx === 0 && itext.options && !itext.options.bullet && opts.bullet) itext.options.bullet = opts.bullet;
4300
4444
  if (typeof itext.text === "string" || typeof itext.text === "number") itext.text = itext.text.toString().replace(/\r*\n/g, "\r\n");
4301
- if (itext.text.includes("\r\n") && itext.text.match(/\n$/g) === null) itext.text.split("\r\n").forEach((line) => {
4302
- itext.options.breakLine = true;
4303
- arrTextObjects.push({
4304
- text: line,
4305
- options: itext.options
4445
+ if (itext.text.includes("\r\n") && itext.text.match(/\n$/g) === null) {
4446
+ const lines = itext.text.split("\r\n");
4447
+ lines.forEach((line, lineIdx) => {
4448
+ const isLast = lineIdx === lines.length - 1;
4449
+ arrTextObjects.push({
4450
+ text: line,
4451
+ options: {
4452
+ ...itext.options,
4453
+ breakLine: isLast ? itext.options.breakLine : true
4454
+ }
4455
+ });
4306
4456
  });
4307
- });
4308
- else arrTextObjects.push(itext);
4457
+ } else arrTextObjects.push(itext);
4309
4458
  });
4310
4459
  const arrLines = [];
4311
4460
  let arrTexts = [];
@@ -4407,7 +4556,7 @@ function genXmlPlaceholder(placeholderObj) {
4407
4556
  * @param {PresSlideInternal} masterSlide - master slide
4408
4557
  * @returns XML
4409
4558
  */
4410
- function makeXmlContTypes(slides, slideLayouts, masterSlide) {
4559
+ function makeXmlContTypes(slides, slideLayouts, masterSlide, hasCustomProps) {
4411
4560
  let strXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n";
4412
4561
  strXml += "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">";
4413
4562
  strXml += "<Default Extension=\"xml\" ContentType=\"application/xml\"/>";
@@ -4457,6 +4606,7 @@ function makeXmlContTypes(slides, slideLayouts, masterSlide) {
4457
4606
  });
4458
4607
  strXml += " <Override PartName=\"/docProps/core.xml\" ContentType=\"application/vnd.openxmlformats-package.core-properties+xml\"/>";
4459
4608
  strXml += " <Override PartName=\"/docProps/app.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.extended-properties+xml\"/>";
4609
+ if (hasCustomProps) strXml += " <Override PartName=\"/docProps/custom.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.custom-properties+xml\"/>";
4460
4610
  strXml += "</Types>";
4461
4611
  return strXml;
4462
4612
  }
@@ -4464,13 +4614,15 @@ function makeXmlContTypes(slides, slideLayouts, masterSlide) {
4464
4614
  * Creates `_rels/.rels`
4465
4615
  * @returns XML
4466
4616
  */
4467
- function makeXmlRootRels() {
4468
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4617
+ function makeXmlRootRels(hasCustomProps) {
4618
+ let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4469
4619
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
4470
4620
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
4471
4621
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
4472
- <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
4473
- </Relationships>`;
4622
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>`;
4623
+ if (hasCustomProps) xml += "\n <Relationship Id=\"rId4\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties\" Target=\"docProps/custom.xml\"/>";
4624
+ xml += "\n </Relationships>";
4625
+ return xml;
4474
4626
  }
4475
4627
  /**
4476
4628
  * Creates `docProps/app.xml`
@@ -4536,6 +4688,23 @@ function makeXmlCore(title, subject, author, revision) {
4536
4688
  <dcterms:modified xsi:type="dcterms:W3CDTF">${(/* @__PURE__ */ new Date()).toISOString().replace(/\.\d\d\dZ/, "Z")}</dcterms:modified>
4537
4689
  </cp:coreProperties>`;
4538
4690
  }
4691
+ const CUSTOM_PROPS_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
4692
+ /**
4693
+ * Creates `docProps/custom.xml`
4694
+ * @param props - custom property name/value pairs
4695
+ * @returns XML
4696
+ */
4697
+ function makeXmlCustomProperties(props) {
4698
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4699
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">${props.map(({ name, value }, idx) => {
4700
+ let valueXml;
4701
+ if (typeof value === "boolean") valueXml = `<vt:bool>${value}</vt:bool>`;
4702
+ else if (value instanceof Date) valueXml = `<vt:filetime>${value.toISOString().replace(/\.\d{3}Z$/, "Z")}</vt:filetime>`;
4703
+ else if (typeof value === "number") valueXml = Number.isInteger(value) ? `<vt:i4>${value}</vt:i4>` : `<vt:r8>${value}</vt:r8>`;
4704
+ else valueXml = `<vt:lpwstr>${encodeXmlEntities(String(value))}</vt:lpwstr>`;
4705
+ return `<property fmtid="${CUSTOM_PROPS_FMTID}" pid="${idx + 2}" name="${encodeXmlEntities(name)}">${valueXml}</property>`;
4706
+ }).join("")}</Properties>`;
4707
+ }
4539
4708
  /**
4540
4709
  * Creates `ppt/_rels/presentation.xml.rels`
4541
4710
  * @param {PresSlideInternal[]} slides - Presenation Slides
@@ -4712,7 +4881,7 @@ function makeXmlTheme(pres) {
4712
4881
  */
4713
4882
  function makeXmlPresentation(pres) {
4714
4883
  let strXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4715
- <p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" ${pres.rtlMode ? "rtl=\"1\"" : ""} saveSubsetFonts="1" autoCompressPictures="0">`;
4884
+ <p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" ${pres.rtlMode ? "rtl=\"1\"" : ""} saveSubsetFonts="1" autoCompressPictures="0"${pres.firstSlideNum !== 1 ? ` firstSlideNum="${pres.firstSlideNum}"` : ""}>`;
4716
4885
  strXml += "<p:sldMasterIdLst><p:sldMasterId id=\"2147483648\" r:id=\"rId1\"/></p:sldMasterIdLst>";
4717
4886
  strXml += `<p:notesMasterIdLst><p:notesMasterId r:id="rId${pres.slides.length + 2}"/></p:notesMasterIdLst>`;
4718
4887
  strXml += "<p:sldIdLst>";
@@ -4751,9 +4920,96 @@ function makeXmlPresProps() {
4751
4920
  * @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
4752
4921
  * @return {string} XML
4753
4922
  */
4754
- function makeXmlTableStyles() {
4755
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4756
- <a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"/>`;
4923
+ function makeXmlTableStyles(tableStyles = []) {
4924
+ const open = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
4925
+ <a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"`;
4926
+ if (!tableStyles || tableStyles.length === 0) return `${open}/>`;
4927
+ let strXml = `${open}>`;
4928
+ tableStyles.forEach(({ guid, def }) => {
4929
+ strXml += `<a:tblStyle styleId="${guid}" styleName="${encodeXmlEntities(def.name)}">`;
4930
+ [
4931
+ ["wholeTbl", def.wholeTbl],
4932
+ ["band1H", def.band1H],
4933
+ ["band2H", def.band2H],
4934
+ ["band1V", def.band1V],
4935
+ ["band2V", def.band2V],
4936
+ ["lastCol", def.lastCol],
4937
+ ["firstCol", def.firstCol],
4938
+ ["lastRow", def.lastRow],
4939
+ ["firstRow", def.firstRow]
4940
+ ].forEach(([name, region]) => {
4941
+ if (region) strXml += genXmlTableStyleRegion(name, region);
4942
+ });
4943
+ strXml += "</a:tblStyle>";
4944
+ });
4945
+ strXml += "</a:tblStyleLst>";
4946
+ return strXml;
4947
+ }
4948
+ /**
4949
+ * Build one `CT_TablePartStyle` region (e.g. `firstRow`, `band1H`) for a custom table style.
4950
+ * Emits `tcTxStyle` (text) before `tcStyle` (cell fill/borders) per the schema sequence.
4951
+ * @param {string} name - region element name
4952
+ * @param {TableStyleRegionProps} region - region styling
4953
+ * @return {string} XML
4954
+ */
4955
+ function genXmlTableStyleRegion(name, region) {
4956
+ let xml = `<a:${name}>`;
4957
+ if (region.bold !== void 0 || region.italic !== void 0 || region.color) {
4958
+ const b = region.bold ? " b=\"on\"" : "";
4959
+ const i = region.italic ? " i=\"on\"" : "";
4960
+ xml += `<a:tcTxStyle${b}${i}><a:fontRef idx="minor"/>`;
4961
+ xml += region.color ? createColorElement(region.color) : "";
4962
+ xml += "</a:tcTxStyle>";
4963
+ }
4964
+ if (region.border !== void 0 || region.fill !== void 0) {
4965
+ xml += "<a:tcStyle>";
4966
+ if (region.border !== void 0) xml += genXmlTableStyleBorders(region.border);
4967
+ if (region.fill !== void 0) xml += `<a:fill><a:solidFill>${createColorElement(region.fill)}</a:solidFill></a:fill>`;
4968
+ xml += "</a:tcStyle>";
4969
+ }
4970
+ xml += `</a:${name}>`;
4971
+ return xml;
4972
+ }
4973
+ /**
4974
+ * Build the `tcBdr` border block for a custom table style region.
4975
+ * A single `BorderProps` styles all four sides plus the interior grid lines; a
4976
+ * TRBL array styles only the four outer sides. Sides are emitted in schema order.
4977
+ * @param {BorderProps | BorderProps[]} border - border definition
4978
+ * @return {string} XML
4979
+ */
4980
+ function genXmlTableStyleBorders(border) {
4981
+ let sides;
4982
+ if (Array.isArray(border)) {
4983
+ const [top, right, bottom, left] = border;
4984
+ sides = [
4985
+ ["left", left],
4986
+ ["right", right],
4987
+ ["top", top],
4988
+ ["bottom", bottom]
4989
+ ];
4990
+ } else sides = [
4991
+ ["left", border],
4992
+ ["right", border],
4993
+ ["top", border],
4994
+ ["bottom", border],
4995
+ ["insideH", border],
4996
+ ["insideV", border]
4997
+ ];
4998
+ let xml = "<a:tcBdr>";
4999
+ sides.forEach(([side, b]) => {
5000
+ if (!b) return;
5001
+ xml += `<a:${side}>`;
5002
+ if (b.type === "none") xml += "<a:ln><a:noFill/></a:ln>";
5003
+ else {
5004
+ xml += `<a:ln w="${valToPts(b.pt ?? 1)}" cap="flat" cmpd="sng" algn="ctr">`;
5005
+ xml += `<a:solidFill>${createColorElement(b.color ?? "666666")}</a:solidFill>`;
5006
+ xml += `<a:prstDash val="${b.type === "dash" ? "sysDash" : "solid"}"/>`;
5007
+ xml += "</a:ln>";
5008
+ }
5009
+ xml += `</a:${side}>`;
5010
+ });
5011
+ xml += "</a:tcBdr>";
5012
+ return xml;
4757
5013
  }
4758
5014
  /**
4759
5015
  * Creates `ppt/viewProps.xml`
@@ -4823,7 +5079,7 @@ function makeXmlViewProps() {
4823
5079
  * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
4824
5080
  * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
4825
5081
  */
4826
- const VERSION = "5.1.0";
5082
+ const VERSION = "5.2.0";
4827
5083
  function standardLayoutToPresLayout(layout) {
4828
5084
  return {
4829
5085
  name: layout.name,
@@ -4924,6 +5180,14 @@ var PptxGenJS = class {
4924
5180
  get title() {
4925
5181
  return this._title;
4926
5182
  }
5183
+ /** Slide number shown on the first slide (maps to firstSlideNum in presentation.xml) */
5184
+ _firstSlideNum;
5185
+ set firstSlideNum(value) {
5186
+ this._firstSlideNum = value;
5187
+ }
5188
+ get firstSlideNum() {
5189
+ return this._firstSlideNum;
5190
+ }
4927
5191
  /**
4928
5192
  * Whether Right-to-Left (RTL) mode is enabled
4929
5193
  * @type {boolean}
@@ -4950,6 +5214,9 @@ var PptxGenJS = class {
4950
5214
  get sections() {
4951
5215
  return this._sections;
4952
5216
  }
5217
+ /** custom document properties stored in docProps/custom.xml */
5218
+ _customProperties;
5219
+ _tableStyles;
4953
5220
  /** slide layout definition objects, used for generating slide layout files */
4954
5221
  _slideLayouts;
4955
5222
  get slideLayouts() {
@@ -4959,6 +5226,7 @@ var PptxGenJS = class {
4959
5226
  return {
4960
5227
  author: this.author,
4961
5228
  company: this.company,
5229
+ firstSlideNum: this.firstSlideNum,
4962
5230
  layout: this.layout,
4963
5231
  masterSlide: this._masterSlide,
4964
5232
  presLayout: this.presLayout,
@@ -5043,6 +5311,7 @@ var PptxGenJS = class {
5043
5311
  width: this.LAYOUTS[DEF_PRES_LAYOUT].width,
5044
5312
  height: this.LAYOUTS[DEF_PRES_LAYOUT].height
5045
5313
  };
5314
+ this._firstSlideNum = 1;
5046
5315
  this._rtlMode = false;
5047
5316
  this._slideLayouts = [{
5048
5317
  _margin: DEF_SLIDE_MARGIN_IN,
@@ -5058,6 +5327,8 @@ var PptxGenJS = class {
5058
5327
  }];
5059
5328
  this._slides = [];
5060
5329
  this._sections = [];
5330
+ this._customProperties = [];
5331
+ this._tableStyles = [];
5061
5332
  this._masterSlide = {
5062
5333
  addChart: null,
5063
5334
  addImage: null,
@@ -5086,7 +5357,8 @@ var PptxGenJS = class {
5086
5357
  */
5087
5358
  addNewSlide = (options) => {
5088
5359
  const nextOptions = options || {};
5089
- nextOptions.sectionTitle = this._sections.length > 0 && this._sections[this._sections.length - 1]._slides.some((slide) => slide._slideNum === this._slides[this._slides.length - 1]._slideNum) ? this._sections[this._sections.length - 1].title : null;
5360
+ const lastSlide = this._slides[this._slides.length - 1];
5361
+ nextOptions.sectionTitle = this._sections.find((sect) => sect._slides.some((s) => s._slideNum === lastSlide._slideNum))?.title ?? null;
5090
5362
  return this.addSlide(nextOptions);
5091
5363
  };
5092
5364
  /**
@@ -5156,16 +5428,18 @@ var PptxGenJS = class {
5156
5428
  zip.folder("ppt/theme");
5157
5429
  zip.folder("ppt/notesMasters").folder("_rels");
5158
5430
  zip.folder("ppt/notesSlides").folder("_rels");
5159
- zip.file("[Content_Types].xml", makeXmlContTypes(this._slides, this._slideLayouts, this._masterSlide));
5160
- zip.file("_rels/.rels", makeXmlRootRels());
5431
+ const hasCustomProps = this._customProperties.length > 0;
5432
+ zip.file("[Content_Types].xml", makeXmlContTypes(this._slides, this._slideLayouts, this._masterSlide, hasCustomProps));
5433
+ zip.file("_rels/.rels", makeXmlRootRels(hasCustomProps));
5161
5434
  zip.file("docProps/app.xml", makeXmlApp(this._slides, this.company));
5162
5435
  zip.file("docProps/core.xml", makeXmlCore(this.title, this.subject, this.author, this.revision));
5436
+ if (hasCustomProps) zip.file("docProps/custom.xml", makeXmlCustomProperties(this._customProperties));
5163
5437
  zip.file("ppt/_rels/presentation.xml.rels", makeXmlPresentationRels(this._slides));
5164
5438
  zip.file("ppt/theme/theme1.xml", makeXmlTheme(this.internalPresentation));
5165
5439
  zip.file("ppt/theme/theme2.xml", makeXmlTheme(this.internalPresentation));
5166
5440
  zip.file("ppt/presentation.xml", makeXmlPresentation(this.internalPresentation));
5167
5441
  zip.file("ppt/presProps.xml", makeXmlPresProps());
5168
- zip.file("ppt/tableStyles.xml", makeXmlTableStyles());
5442
+ zip.file("ppt/tableStyles.xml", makeXmlTableStyles(this._tableStyles));
5169
5443
  zip.file("ppt/viewProps.xml", makeXmlViewProps());
5170
5444
  this._slideLayouts.forEach((layout, idx) => {
5171
5445
  zip.file(`ppt/slideLayouts/slideLayout${idx + 1}.xml`, makeXmlLayout(layout));
@@ -5245,6 +5519,19 @@ var PptxGenJS = class {
5245
5519
  return await this._runtime.writeFile(fileName, data);
5246
5520
  }
5247
5521
  /**
5522
+ * Set a custom document property stored in `docProps/custom.xml`.
5523
+ * Calling with the same name replaces the existing value.
5524
+ * @param name - property name
5525
+ * @param value - string, integer/float number, boolean, or Date
5526
+ */
5527
+ setCustomProperty(name, value) {
5528
+ this._customProperties = this._customProperties.filter((p) => p.name !== name);
5529
+ this._customProperties.push({
5530
+ name,
5531
+ value
5532
+ });
5533
+ }
5534
+ /**
5248
5535
  * Add a new Section to Presentation
5249
5536
  * @param {ISectionProps} section - section properties
5250
5537
  * @example pptx.addSection({ title:'Charts' });
@@ -5353,6 +5640,31 @@ var PptxGenJS = class {
5353
5640
  if (newLayout._slideNumberProps && !this._masterSlide._slideNumberProps) this._masterSlide._slideNumberProps = newLayout._slideNumberProps;
5354
5641
  }
5355
5642
  /**
5643
+ * Register a reusable custom table style and return its GUID.
5644
+ * The style is written to `ppt/tableStyles.xml` and is editable in PowerPoint's
5645
+ * Table Styles gallery. Pass the returned GUID as `TableProps.tableStyle`, and use
5646
+ * the `has*` flags (`hasHeader`, `hasBandedRows`, …) to activate the matching regions.
5647
+ * @param {TableStyleProps} props - custom table style definition (requires `name`)
5648
+ * @returns {string} braced GUID to use as `tableStyle`
5649
+ * @example
5650
+ * const brand = pptx.defineTableStyle({
5651
+ * name: 'Brand Banded',
5652
+ * firstRow: { fill:'1A2B3C', color:'FFFFFF', bold:true },
5653
+ * band1H: { fill:'EAF1F8' },
5654
+ * })
5655
+ * slide.addTable(rows, { tableStyle: brand, hasHeader:true, hasBandedRows:true })
5656
+ */
5657
+ defineTableStyle(props) {
5658
+ if (!props || typeof props !== "object") throw new Error("defineTableStyle() requires a `{ name, ... }` object argument");
5659
+ if (!props.name || typeof props.name !== "string") throw new Error("defineTableStyle() requires a non-empty `name`");
5660
+ const guid = `{${getUuid("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx").toUpperCase()}}`;
5661
+ this._tableStyles.push({
5662
+ guid,
5663
+ def: props
5664
+ });
5665
+ return guid;
5666
+ }
5667
+ /**
5356
5668
  * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed
5357
5669
  * @param {string} eleId - table HTML element ID
5358
5670
  * @param {TableToSlidesProps} options - generation options
@@ -5364,4 +5676,4 @@ var PptxGenJS = class {
5364
5676
  //#endregion
5365
5677
  export { PptxGenJS as t };
5366
5678
 
5367
- //# sourceMappingURL=pptxgen-3gNqxx8n.js.map
5679
+ //# sourceMappingURL=pptxgen--5RWzhb4.js.map