@json-to-office/core-pptx 0.22.0 → 0.25.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.
package/dist/index.js CHANGED
@@ -25,6 +25,9 @@ var W = {
25
25
  THEME_COLOR_FALLBACK: "THEME_COLOR_FALLBACK",
26
26
  UNKNOWN_COLOR: "UNKNOWN_COLOR",
27
27
  GRID_POSITION_CLAMPED: "GRID_POSITION_CLAMPED",
28
+ TEXT_NO_CONTENT: "TEXT_NO_CONTENT",
29
+ UNKNOWN_PATTERN_PRESET: "UNKNOWN_PATTERN_PRESET",
30
+ ADVANCED_FILL_FALLBACK: "ADVANCED_FILL_FALLBACK",
28
31
  IMAGE_ZERO_BOX: "IMAGE_ZERO_BOX",
29
32
  FONT_UNRESOLVED: "FONT_UNRESOLVED"
30
33
  };
@@ -591,6 +594,16 @@ function resolvePagePlaceholders(text, ctx) {
591
594
  return text.replace(/\{PAGE_NUMBER\}/g, fmt(slideNumber)).replace(/\{PAGE_COUNT\}/g, fmt(totalSlides));
592
595
  }
593
596
  function renderTextComponent(slide, props, theme, warnings, slideCtx) {
597
+ const runs = props.runs && props.runs.length > 0 ? props.runs : void 0;
598
+ if (props.text === void 0 && !runs) {
599
+ warn(
600
+ warnings,
601
+ W.TEXT_NO_CONTENT,
602
+ 'Text component has neither "text" nor "runs" \u2014 skipped',
603
+ { component: "text" }
604
+ );
605
+ return;
606
+ }
594
607
  const style = props.style ? theme.styles?.[props.style] : void 0;
595
608
  const isHeadingStyle = props.style && /^(title|heading)/.test(props.style);
596
609
  const opts = {};
@@ -600,7 +613,10 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
600
613
  if (props.h !== void 0) opts.h = props.h;
601
614
  if (props.h === void 0) {
602
615
  const fontSize = props.fontSize ?? theme.defaults.fontSize ?? 18;
603
- const lines = (props.text.match(/\n/g)?.length ?? 0) + 1;
616
+ const lines = runs ? runs.reduce(
617
+ (count, run) => count + (run.breakLine ? 1 : 0) + (run.text.match(/\n/g)?.length ?? 0),
618
+ 1
619
+ ) : (props.text.match(/\n/g)?.length ?? 0) + 1;
604
620
  opts.h = Math.max(0.5, fontSize / 72 * 1.6 * lines);
605
621
  opts.isTextBox = true;
606
622
  }
@@ -611,6 +627,7 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
611
627
  theme,
612
628
  warnings
613
629
  );
630
+ const preAliasFamily = opts.fontFace;
614
631
  const bold = props.bold ?? style?.bold;
615
632
  const italic = props.italic ?? style?.italic;
616
633
  const fontWeight = props.fontWeight ?? style?.fontWeight;
@@ -661,7 +678,11 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
661
678
  }
662
679
  applyHyperlink(opts, props.hyperlink, "text", warnings);
663
680
  const lineSpacing = props.lineSpacing ?? style?.lineSpacing;
664
- if (lineSpacing !== void 0) opts.lineSpacing = lineSpacing;
681
+ if (props.lineSpacingMultiple !== void 0) {
682
+ opts.lineSpacingMultiple = props.lineSpacingMultiple;
683
+ } else if (lineSpacing !== void 0) {
684
+ opts.lineSpacing = lineSpacing;
685
+ }
665
686
  const charSpacing = props.charSpacing ?? style?.charSpacing;
666
687
  if (charSpacing !== void 0) opts.charSpacing = charSpacing;
667
688
  if (props.paraSpaceBefore !== void 0)
@@ -669,6 +690,49 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
669
690
  const paraSpaceAfter = props.paraSpaceAfter ?? style?.paraSpaceAfter;
670
691
  if (paraSpaceAfter !== void 0) opts.paraSpaceAfter = paraSpaceAfter;
671
692
  if (props.breakLine) opts.breakLine = true;
693
+ if (runs) {
694
+ const runSegments = runs.map((run) => {
695
+ const runOpts = {};
696
+ if (run.fontSize != null) runOpts.fontSize = run.fontSize;
697
+ if (run.fontFace != null) runOpts.fontFace = run.fontFace;
698
+ if (run.color != null)
699
+ runOpts.color = resolveColor(run.color, theme, warnings);
700
+ if (run.strike != null) runOpts.strike = run.strike;
701
+ if (run.underline !== void 0) {
702
+ if (typeof run.underline === "boolean") {
703
+ if (run.underline) runOpts.underline = { style: "sng" };
704
+ } else {
705
+ runOpts.underline = run.underline;
706
+ }
707
+ }
708
+ if (run.superscript != null) runOpts.superscript = run.superscript;
709
+ if (run.subscript != null) runOpts.subscript = run.subscript;
710
+ if (run.charSpacing != null) runOpts.charSpacing = run.charSpacing;
711
+ if (run.breakLine != null) runOpts.breakLine = run.breakLine;
712
+ const effWeight = run.fontWeight ?? fontWeight;
713
+ const effBold = run.bold ?? bold;
714
+ const effItalic = run.italic ?? italic;
715
+ if (effBold != null) runOpts.bold = effBold;
716
+ if (effItalic != null) runOpts.italic = effItalic;
717
+ if (effWeight != null || effBold === true) {
718
+ if (run.fontFace == null) {
719
+ const w = applyFontWeight({
720
+ family: preAliasFamily,
721
+ fontWeight: effWeight,
722
+ italic: effItalic,
723
+ bold: effBold
724
+ });
725
+ if (w.fontFace !== void 0) runOpts.fontFace = w.fontFace;
726
+ if (w.bold !== void 0) runOpts.bold = w.bold;
727
+ if (w.italic !== void 0) runOpts.italic = w.italic;
728
+ }
729
+ }
730
+ const runText = slideCtx ? resolvePagePlaceholders(run.text, slideCtx) : run.text;
731
+ return { text: runText, options: runOpts };
732
+ });
733
+ slide.addText(runSegments, opts);
734
+ return;
735
+ }
672
736
  const text = slideCtx ? resolvePagePlaceholders(props.text, slideCtx) : props.text;
673
737
  slide.addText(text, opts);
674
738
  }
@@ -842,6 +906,44 @@ async function renderImageComponent(slide, props, theme, warnings, slideWidth =
842
906
  slide.addImage(opts);
843
907
  }
844
908
 
909
+ // src/components/shape.ts
910
+ import { PATTERN_FILL_PRESETS } from "@json-to-office/shared-pptx";
911
+
912
+ // src/utils/fillXml.ts
913
+ var ANGLE_UNIT = 6e4;
914
+ var PCT_UNIT = 1e3;
915
+ var RADIAL_FOCUS_RECTS = {
916
+ center: { l: 5e4, t: 5e4, r: 5e4, b: 5e4 },
917
+ topLeft: { l: 0, t: 0, r: 1e5, b: 1e5 },
918
+ topRight: { l: 1e5, t: 0, r: 0, b: 1e5 },
919
+ bottomLeft: { l: 0, t: 1e5, r: 1e5, b: 0 },
920
+ bottomRight: { l: 1e5, t: 1e5, r: 0, b: 0 }
921
+ };
922
+ function gradientStopXml(color, pos, transparency, theme, warnings) {
923
+ const hex = resolveColor(color, theme, warnings).toUpperCase();
924
+ const alpha = transparency !== void 0 ? `<a:alpha val="${Math.round((100 - transparency) * PCT_UNIT)}"/>` : "";
925
+ return `<a:gs pos="${Math.round(pos * PCT_UNIT)}"><a:srgbClr val="${hex}">${alpha}</a:srgbClr></a:gs>`;
926
+ }
927
+ function buildGradientFillXml(gradient, theme, warnings) {
928
+ const stops = gradient.stops.map(
929
+ (stop) => gradientStopXml(stop.color, stop.pos, stop.transparency, theme, warnings)
930
+ ).join("");
931
+ let shade;
932
+ if (gradient.type === "radial") {
933
+ const rect = RADIAL_FOCUS_RECTS[gradient.focus ?? "center"];
934
+ shade = `<a:path path="circle"><a:fillToRect l="${rect.l}" t="${rect.t}" r="${rect.r}" b="${rect.b}"/></a:path>`;
935
+ } else {
936
+ const angle = ((gradient.angle ?? 0) % 360 + 360) % 360;
937
+ shade = `<a:lin ang="${Math.round(angle * ANGLE_UNIT)}" scaled="1"/>`;
938
+ }
939
+ return `<a:gradFill rotWithShape="1"><a:gsLst>${stops}</a:gsLst>${shade}</a:gradFill>`;
940
+ }
941
+ function buildPatternFillXml(pattern, theme, warnings) {
942
+ const fg = resolveColor(pattern.foreground, theme, warnings).toUpperCase();
943
+ const bg = resolveColor(pattern.background, theme, warnings).toUpperCase();
944
+ return `<a:pattFill prst="${pattern.preset}"><a:fgClr><a:srgbClr val="${fg}"/></a:fgClr><a:bgClr><a:srgbClr val="${bg}"/></a:bgClr></a:pattFill>`;
945
+ }
946
+
845
947
  // src/components/shape.ts
846
948
  var SHAPE_TYPE_MAP = {
847
949
  rect: "rect",
@@ -860,17 +962,67 @@ var SHAPE_TYPE_MAP = {
860
962
  heart: "heart",
861
963
  lightning: "lightningBolt"
862
964
  };
863
- function buildShapeOpts(props, theme, warnings) {
965
+ function applyShapeFill(opts, fill, theme, warnings, pendingFills) {
966
+ let gradient = fill.gradient;
967
+ let pattern = fill.pattern;
968
+ if (gradient && pattern) {
969
+ warn(
970
+ warnings,
971
+ W.ADVANCED_FILL_FALLBACK,
972
+ 'Shape fill sets both "gradient" and "pattern" \u2014 using the gradient',
973
+ { component: "shape" }
974
+ );
975
+ pattern = void 0;
976
+ }
977
+ let unknownPresetForeground;
978
+ if (pattern && !PATTERN_FILL_PRESETS.includes(pattern.preset)) {
979
+ warn(
980
+ warnings,
981
+ W.UNKNOWN_PATTERN_PRESET,
982
+ `Unknown pattern preset "${pattern.preset}" \u2014 falling back to solid foreground`,
983
+ { component: "shape" }
984
+ );
985
+ unknownPresetForeground = pattern.foreground;
986
+ pattern = void 0;
987
+ }
988
+ if (gradient || pattern) {
989
+ const sentinel = resolveColor(
990
+ fill.color ?? (gradient ? gradient.stops[0].color : pattern.foreground),
991
+ theme,
992
+ warnings
993
+ );
994
+ if (pendingFills) {
995
+ const xml = gradient ? buildGradientFillXml(gradient, theme, warnings) : buildPatternFillXml(pattern, theme, warnings);
996
+ const objectName = `__jto_fill_${pendingFills.length}__`;
997
+ pendingFills.push({ objectName, xml });
998
+ opts.objectName = objectName;
999
+ } else {
1000
+ warn(
1001
+ warnings,
1002
+ W.ADVANCED_FILL_FALLBACK,
1003
+ `${gradient ? "Gradient" : "Pattern"} fill requires the buffer generation pipeline \u2014 rendering a solid fill instead`,
1004
+ { component: "shape" }
1005
+ );
1006
+ }
1007
+ opts.fill = { color: sentinel };
1008
+ return;
1009
+ }
1010
+ const solid = fill.color ?? unknownPresetForeground;
1011
+ if (solid !== void 0) {
1012
+ opts.fill = { color: resolveColor(solid, theme, warnings) };
1013
+ if (fill.transparency !== void 0) {
1014
+ opts.fill.transparency = fill.transparency;
1015
+ }
1016
+ }
1017
+ }
1018
+ function buildShapeOpts(props, theme, warnings, pendingFills) {
864
1019
  const opts = {};
865
1020
  if (props.x !== void 0) opts.x = props.x;
866
1021
  if (props.y !== void 0) opts.y = props.y;
867
1022
  if (props.w !== void 0) opts.w = props.w;
868
1023
  if (props.h !== void 0) opts.h = props.h;
869
1024
  if (props.fill) {
870
- opts.fill = { color: resolveColor(props.fill.color, theme, warnings) };
871
- if (props.fill.transparency !== void 0) {
872
- opts.fill.transparency = props.fill.transparency;
873
- }
1025
+ applyShapeFill(opts, props.fill, theme, warnings, pendingFills);
874
1026
  }
875
1027
  if (props.line) {
876
1028
  opts.line = {};
@@ -886,6 +1038,9 @@ function buildShapeOpts(props, theme, warnings) {
886
1038
  opts.line.dashType = props.line.dashType;
887
1039
  }
888
1040
  if (props.rotate !== void 0) opts.rotate = props.rotate;
1041
+ if (props.angleRange !== void 0) opts.angleRange = props.angleRange;
1042
+ if (props.flipH !== void 0) opts.flipH = props.flipH;
1043
+ if (props.flipV !== void 0) opts.flipV = props.flipV;
889
1044
  if (props.rectRadius !== void 0) opts.rectRadius = props.rectRadius;
890
1045
  if (props.shadow) {
891
1046
  opts.shadow = {
@@ -899,7 +1054,7 @@ function buildShapeOpts(props, theme, warnings) {
899
1054
  }
900
1055
  return opts;
901
1056
  }
902
- function renderShapeComponent(slide, props, theme, pptx, warnings) {
1057
+ function renderShapeComponent(slide, props, theme, pptx, warnings, ctx) {
903
1058
  const shapeTypeName = SHAPE_TYPE_MAP[props.type] || props.type;
904
1059
  const shapeType = pptx.ShapeType[shapeTypeName];
905
1060
  if (!shapeType) {
@@ -910,7 +1065,7 @@ function renderShapeComponent(slide, props, theme, pptx, warnings) {
910
1065
  }
911
1066
  const style = props.style ? theme.styles?.[props.style] : void 0;
912
1067
  const isHeadingStyle = props.style && /^(title|heading)/.test(props.style);
913
- const opts = buildShapeOpts(props, theme, warnings);
1068
+ const opts = buildShapeOpts(props, theme, warnings, ctx?.pendingFills);
914
1069
  if (props.text && (!Array.isArray(props.text) || props.text.length > 0)) {
915
1070
  opts.shape = shapeType;
916
1071
  opts.fontSize = props.fontSize ?? style?.fontSize ?? theme.defaults.fontSize;
@@ -1262,6 +1417,14 @@ var CHART_TYPE_MAP = {
1262
1417
  radar: "radar",
1263
1418
  scatter: "scatter"
1264
1419
  };
1420
+ function resolveGridLine(gridLine, theme, warnings) {
1421
+ const resolved = {};
1422
+ if (gridLine.style !== void 0) resolved.style = gridLine.style;
1423
+ if (gridLine.size !== void 0) resolved.size = gridLine.size;
1424
+ if (gridLine.color !== void 0)
1425
+ resolved.color = resolveColor(gridLine.color, theme, warnings);
1426
+ return resolved;
1427
+ }
1265
1428
  function renderChartComponent(slide, props, theme, _pptx, warnings) {
1266
1429
  const chartType = CHART_TYPE_MAP[props.type];
1267
1430
  if (!chartType) {
@@ -1319,6 +1482,18 @@ function renderChartComponent(slide, props, theme, _pptx, warnings) {
1319
1482
  opts.legendColor = props.legendColor ? resolveColor(props.legendColor, theme, warnings) : themeTextColor;
1320
1483
  opts.catAxisLabelColor = props.catAxisLabelColor ? resolveColor(props.catAxisLabelColor, theme, warnings) : themeTextColor;
1321
1484
  opts.valAxisLabelColor = props.valAxisLabelColor ? resolveColor(props.valAxisLabelColor, theme, warnings) : themeTextColor;
1485
+ if (props.valAxisLabelFontSize !== void 0)
1486
+ opts.valAxisLabelFontSize = props.valAxisLabelFontSize;
1487
+ if (props.catAxisLineShow !== void 0)
1488
+ opts.catAxisLineShow = props.catAxisLineShow;
1489
+ if (props.valAxisLineShow !== void 0)
1490
+ opts.valAxisLineShow = props.valAxisLineShow;
1491
+ if (props.dataBorder !== void 0) {
1492
+ opts.dataBorder = {
1493
+ pt: props.dataBorder.pt,
1494
+ color: resolveColor(props.dataBorder.color, theme, warnings)
1495
+ };
1496
+ }
1322
1497
  if (props.showLegend !== void 0) opts.showLegend = props.showLegend;
1323
1498
  if (props.showTitle !== void 0) opts.showTitle = props.showTitle;
1324
1499
  if (props.showValue !== void 0) opts.showValue = props.showValue;
@@ -1345,6 +1520,10 @@ function renderChartComponent(slide, props, theme, _pptx, warnings) {
1345
1520
  opts.catAxisLabelRotate = props.catAxisLabelRotate;
1346
1521
  if (props.catAxisLabelFontSize !== void 0)
1347
1522
  opts.catAxisLabelFontSize = props.catAxisLabelFontSize;
1523
+ if (props.catAxisLabelFontFace !== void 0)
1524
+ opts.catAxisLabelFontFace = props.catAxisLabelFontFace;
1525
+ if (props.catGridLine !== void 0)
1526
+ opts.catGridLine = resolveGridLine(props.catGridLine, theme, warnings);
1348
1527
  if (props.valAxisTitle !== void 0) {
1349
1528
  opts.valAxisTitle = props.valAxisTitle;
1350
1529
  opts.showValAxisTitle = true;
@@ -1359,14 +1538,22 @@ function renderChartComponent(slide, props, theme, _pptx, warnings) {
1359
1538
  opts.valAxisLabelFormatCode = props.valAxisLabelFormatCode;
1360
1539
  if (props.valAxisMajorUnit !== void 0)
1361
1540
  opts.valAxisMajorUnit = props.valAxisMajorUnit;
1541
+ if (props.valAxisLabelFontFace !== void 0)
1542
+ opts.valAxisLabelFontFace = props.valAxisLabelFontFace;
1543
+ if (props.valGridLine !== void 0)
1544
+ opts.valGridLine = resolveGridLine(props.valGridLine, theme, warnings);
1362
1545
  if (props.barDir !== void 0) opts.barDir = props.barDir;
1363
1546
  if (props.barGrouping !== void 0) opts.barGrouping = props.barGrouping;
1364
1547
  if (props.barGapWidthPct !== void 0)
1365
1548
  opts.barGapWidthPct = props.barGapWidthPct;
1549
+ if (props.barOverlapPct !== void 0)
1550
+ opts.barOverlapPct = props.barOverlapPct;
1366
1551
  if (props.lineSmooth !== void 0) opts.lineSmooth = props.lineSmooth;
1367
1552
  if (props.lineDataSymbol !== void 0)
1368
1553
  opts.lineDataSymbol = props.lineDataSymbol;
1369
1554
  if (props.lineSize !== void 0) opts.lineSize = props.lineSize;
1555
+ if (props.lineDataSymbolSize !== void 0)
1556
+ opts.lineDataSymbolSize = props.lineDataSymbolSize;
1370
1557
  if (props.firstSliceAng !== void 0)
1371
1558
  opts.firstSliceAng = props.firstSliceAng;
1372
1559
  if (props.holeSize !== void 0) opts.holeSize = props.holeSize;
@@ -1403,7 +1590,7 @@ async function renderComponent(slide, component, theme, pptx, warnings, ctx) {
1403
1590
  );
1404
1591
  break;
1405
1592
  case "shape":
1406
- renderShapeComponent(slide, p, theme, pptx, warnings);
1593
+ renderShapeComponent(slide, p, theme, pptx, warnings, ctx);
1407
1594
  break;
1408
1595
  case "table":
1409
1596
  renderTableComponent(slide, p, theme, pptx, warnings);
@@ -1460,7 +1647,7 @@ function buildSlideTemplateProps(def, theme, warnings) {
1460
1647
 
1461
1648
  // src/core/render.ts
1462
1649
  import { mergeWithDefaults as mergeWithDefaults3 } from "@json-to-office/shared";
1463
- async function renderPresentation(processed, warnings) {
1650
+ async function renderPresentation(processed, warnings, pendingFills) {
1464
1651
  const pptx = new PptxGenJS();
1465
1652
  if (processed.metadata.title) pptx.title = processed.metadata.title;
1466
1653
  if (processed.metadata.author) pptx.author = processed.metadata.author;
@@ -1505,10 +1692,37 @@ async function renderPresentation(processed, warnings) {
1505
1692
  slideCtx,
1506
1693
  services: processed.services,
1507
1694
  slideWidth: processed.slideWidth,
1508
- slideHeight: processed.slideHeight
1695
+ slideHeight: processed.slideHeight,
1696
+ pendingFills
1509
1697
  };
1510
1698
  const slide = slideData.template ? pptx.addSlide({ masterName: slideData.template }) : pptx.addSlide();
1511
- if (slideData.background) {
1699
+ const templateDef = slideData.template ? templateMap.get(slideData.template) : void 0;
1700
+ if (slideData.template && !templateDef) {
1701
+ warn(
1702
+ warnings,
1703
+ W.MISSING_TEMPLATE,
1704
+ `Unknown template "${slideData.template}". Available: ${[...templateMap.keys()].join(", ")}`,
1705
+ { slide: slideIdx }
1706
+ );
1707
+ }
1708
+ const backgroundGradient = slideData.background?.gradient ?? (slideData.background ? void 0 : templateDef?.background?.gradient);
1709
+ if (backgroundGradient) {
1710
+ renderShapeComponent(
1711
+ slide,
1712
+ {
1713
+ type: "rect",
1714
+ x: 0,
1715
+ y: 0,
1716
+ w: processed.slideWidth,
1717
+ h: processed.slideHeight,
1718
+ fill: { gradient: backgroundGradient }
1719
+ },
1720
+ processed.theme,
1721
+ pptx,
1722
+ warnings,
1723
+ renderCtx
1724
+ );
1725
+ } else if (slideData.background) {
1512
1726
  if (slideData.background.color) {
1513
1727
  slide.background = {
1514
1728
  color: resolveColor(
@@ -1528,15 +1742,6 @@ async function renderPresentation(processed, warnings) {
1528
1742
  if (slideData.hidden) {
1529
1743
  slide.hidden = true;
1530
1744
  }
1531
- const templateDef = slideData.template ? templateMap.get(slideData.template) : void 0;
1532
- if (slideData.template && !templateDef) {
1533
- warn(
1534
- warnings,
1535
- W.MISSING_TEMPLATE,
1536
- `Unknown template "${slideData.template}". Available: ${[...templateMap.keys()].join(", ")}`,
1537
- { slide: slideIdx }
1538
- );
1539
- }
1540
1745
  const effectiveGrid = mergeGridConfigs(processed.grid, templateDef?.grid);
1541
1746
  if (templateDef?.objects) {
1542
1747
  for (const obj of templateDef.objects) {
@@ -1712,6 +1917,7 @@ async function resolveDocumentFonts(document, theme, warnings, fonts) {
1712
1917
  import { applyExportMode, scopedThemeName } from "@json-to-office/shared";
1713
1918
  import {
1714
1919
  collectImageSourceConflicts,
1920
+ collectTextContentConflicts,
1715
1921
  validateJsonPresentationDocument,
1716
1922
  validatePresentationDocument
1717
1923
  } from "@json-to-office/shared-pptx";
@@ -1721,6 +1927,23 @@ import JSZip from "jszip";
1721
1927
  var MEDIUM_STYLE_2_ACCENT_1 = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
1722
1928
  var NO_STYLE_NO_GRID = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}";
1723
1929
  var DEFAULT_GENERATED_AT = "2000-01-01T00:00:00.000Z";
1930
+ function applyPendingFills(xml, pendingFills) {
1931
+ let out = xml;
1932
+ for (const [index, fill] of pendingFills.entries()) {
1933
+ const marker = `name="${fill.objectName}"`;
1934
+ const markerIdx = out.indexOf(marker);
1935
+ if (markerIdx === -1) continue;
1936
+ const spEnd = out.indexOf("</p:sp>", markerIdx);
1937
+ const solidStart = out.indexOf("<a:solidFill>", markerIdx);
1938
+ const solidEndTag = "</a:solidFill>";
1939
+ const solidEnd = out.indexOf(solidEndTag, solidStart);
1940
+ if (solidStart !== -1 && solidEnd !== -1 && spEnd !== -1 && solidStart < spEnd) {
1941
+ out = out.slice(0, solidStart) + fill.xml + out.slice(solidEnd + solidEndTag.length);
1942
+ }
1943
+ out = out.slice(0, markerIdx) + `name="Fill ${index + 1}"` + out.slice(markerIdx + marker.length);
1944
+ }
1945
+ return out;
1946
+ }
1724
1947
  function resolveGeneratedAt(value) {
1725
1948
  const date = value === void 0 ? new Date(DEFAULT_GENERATED_AT) : new Date(value);
1726
1949
  if (Number.isNaN(date.getTime())) {
@@ -1820,9 +2043,21 @@ async function packagePresentationBuffer(buffer, options = {}) {
1820
2043
  let changed = false;
1821
2044
  for (const [path2, entry] of Object.entries(zip.files)) {
1822
2045
  if (!path2.match(/^ppt\/slides\/slide\d+\.xml$/)) continue;
1823
- const xml = await entry.async("string");
2046
+ let xml = await entry.async("string");
2047
+ let fileChanged = false;
1824
2048
  if (xml.includes(MEDIUM_STYLE_2_ACCENT_1)) {
1825
- zip.file(path2, xml.replaceAll(MEDIUM_STYLE_2_ACCENT_1, NO_STYLE_NO_GRID));
2049
+ xml = xml.replaceAll(MEDIUM_STYLE_2_ACCENT_1, NO_STYLE_NO_GRID);
2050
+ fileChanged = true;
2051
+ }
2052
+ if (options.pendingFills?.length) {
2053
+ const withFills = applyPendingFills(xml, options.pendingFills);
2054
+ if (withFills !== xml) {
2055
+ xml = withFills;
2056
+ fileChanged = true;
2057
+ }
2058
+ }
2059
+ if (fileChanged) {
2060
+ zip.file(path2, xml);
1826
2061
  changed = true;
1827
2062
  }
1828
2063
  }
@@ -1863,12 +2098,15 @@ function isPresentationComponentDefinition(definition) {
1863
2098
  const def = definition;
1864
2099
  return def.name === "pptx" && "props" in def;
1865
2100
  }
1866
- async function generatePresentation(document, options, warnings) {
2101
+ async function generatePresentation(document, options, warnings, pendingFills) {
1867
2102
  assertValidPresentation(document, options?.validation);
1868
2103
  if (!document || document.name !== "pptx") {
1869
2104
  throw new Error("Top-level component must be a pptx component");
1870
2105
  }
1871
- const sourceConflicts = collectImageSourceConflicts(document);
2106
+ const sourceConflicts = [
2107
+ ...collectImageSourceConflicts(document),
2108
+ ...collectTextContentConflicts(document)
2109
+ ];
1872
2110
  if (sourceConflicts.length > 0) {
1873
2111
  throw new Error(
1874
2112
  `Document validation failed:
@@ -1876,7 +2114,7 @@ ${sourceConflicts.map((e) => ` - ${e.path}: ${e.message}`).join("\n")}`
1876
2114
  );
1877
2115
  }
1878
2116
  const processed = processPresentation(document, options);
1879
- return await renderPresentation(processed, warnings);
2117
+ return await renderPresentation(processed, warnings, pendingFills);
1880
2118
  }
1881
2119
  async function generateBufferFromJson(jsonConfig, options) {
1882
2120
  const result = await generateBufferWithWarnings(jsonConfig, options);
@@ -1946,13 +2184,18 @@ async function generateBufferWithWarnings(jsonConfig, options) {
1946
2184
  [themeName]: resolvedTheme
1947
2185
  }
1948
2186
  };
2187
+ const pendingFills = [];
1949
2188
  const pptx = await generatePresentation(
1950
2189
  component,
1951
2190
  effectiveOptions,
1952
- warnings
2191
+ warnings,
2192
+ pendingFills
1953
2193
  );
1954
2194
  const data = await pptx.write({ outputType: "nodebuffer" });
1955
- const buffer = await packagePresentationBuffer(data, options);
2195
+ const buffer = await packagePresentationBuffer(data, {
2196
+ ...options,
2197
+ pendingFills
2198
+ });
1956
2199
  return { buffer, warnings };
1957
2200
  }
1958
2201
  async function generateAndSaveFromJson(jsonConfig, outputPath, options) {
@@ -2347,11 +2590,13 @@ function createBuilderImpl(state) {
2347
2590
  customThemes: effectiveCustomThemes,
2348
2591
  services: state.services
2349
2592
  });
2350
- const pptx = await renderPresentation(processed, warnings);
2593
+ const pendingFills = [];
2594
+ const pptx = await renderPresentation(processed, warnings, pendingFills);
2351
2595
  const data = await pptx.write({ outputType: "nodebuffer" });
2352
2596
  const buffer = await packagePresentationBuffer(data, {
2353
2597
  deterministic: options?.deterministic ?? state.packaging.deterministic,
2354
- generatedAt: options?.generatedAt ?? state.packaging.generatedAt
2598
+ generatedAt: options?.generatedAt ?? state.packaging.generatedAt,
2599
+ pendingFills
2355
2600
  });
2356
2601
  return { buffer, warnings };
2357
2602
  } catch (error) {