@office-kit/pptx-preview 0.14.0 → 0.15.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,4 +1,4 @@
1
- import { emu, getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFirstSlideNumber, getPresentationFonts, getPresentationTheme, getPresetGeometry, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBounds, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFillOpacity, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlinkTooltip, getShapeId, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFillLayout, getShapeImageFormat, getShapeImageIntrinsicSize, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeStrokeOpacity, getShapeText3D, getShapeTextAnchor, getShapeTextAutoFit, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundImageCrop, getSlideBackgroundImageFillLayout, getSlideBackgroundImageIntrinsicSize, getSlideBackgroundImageOpacity, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getSlides, getTableCellAnchor, getTableCellAppearanceEffective, getTableCellMargins, getTableCellParagraphs, getTableCellRunFormatEffective, getTableCellSpan, getTableCellText3D, getTableCellTextDirection, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, isChartShape, isParagraphBulletPicture, isShapeHidden, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isSlideBackgroundGraphicsHidden, isSlideLayoutBackgroundGraphicsHidden, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
1
+ import { emu, getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphElementLanguages, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFirstSlideNumber, getPresentationFonts, getPresentationTheme, getPresetGeometry, getShape3D, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBounds, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFillOpacity, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlinkTooltip, getShapeId, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFillLayout, getShapeImageFormat, getShapeImageIntrinsicSize, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeGradient, getShapeStrokeJoin, getShapeStrokeOpacity, getShapeStrokeSketch, getShapeText3D, getShapeTextAnchor, getShapeTextAutoFit, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundImageCrop, getSlideBackgroundImageFillLayout, getSlideBackgroundImageIntrinsicSize, getSlideBackgroundImageOpacity, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getSlides, getTableBackgroundEffective, getTableCellAnchor, getTableCellAppearanceEffective, getTableCellMargins, getTableCellParagraphs, getTableCellRunFormatEffective, getTableCellSpan, getTableCellText3D, getTableCellTextDirection, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, isChartShape, isParagraphBulletPicture, isShapeHidden, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isSlideBackgroundGraphicsHidden, isSlideLayoutBackgroundGraphicsHidden, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
2
2
  //#region src/text-body-rect.ts
3
3
  /**
4
4
  * Where a shape lays out its text, as fractions of its own extent: the
@@ -29,7 +29,7 @@ function shapeTextRect(shape) {
29
29
  * {@link shapeTextRect}, or `null` for the whole box) placed in `bounds`, less
30
30
  * the body insets. All coordinates and margins must use the same unit
31
31
  * (normally EMU). When margins collapse a region, the region is kept without
32
- * them, as PowerPoint does.
32
+ * them, as the reference desktop app does.
33
33
  */
34
34
  function resolveTextBodyRect(bounds, margins, region) {
35
35
  const x = bounds.x + (region?.l ?? 0) * bounds.w;
@@ -53,17 +53,17 @@ function resolveTextBodyRect(bounds, margins, region) {
53
53
  }
54
54
  //#endregion
55
55
  //#region src/text-body-style.ts
56
- /** Mac PowerPoint uses zero spacing when `spcCol` is absent. */
56
+ /** The reference desktop app on Mac uses zero spacing when `spcCol` is absent. */
57
57
  const DEFAULT_COLUMN_GAP_PX = 0;
58
- const EMU_PER_PX$2 = 9525;
58
+ const EMU_PER_PX$3 = 9525;
59
59
  const NONE = {
60
60
  declarations: "",
61
61
  transform: ""
62
62
  };
63
63
  /**
64
64
  * `<a:bodyPr vert="…"/>` as CSS. `writing-mode` is the right primitive for
65
- * the East-Asian and Mongolian cases, and `text-orientation` for the WordArt
66
- * ones, which stack upright glyphs. `vert270` reads bottom-to-top, which no
65
+ * the East-Asian and Mongolian cases, and `text-orientation` for the
66
+ * `wordArtVert` ones, which stack upright glyphs. `vert270` reads bottom-to-top, which no
67
67
  * writing-mode expresses on its own — hence the half turn. Its columns still
68
68
  * follow the OOXML vertical-rl progression before that half turn; using
69
69
  * vertical-lr here reverses the column order relative to the SVG renderer.
@@ -101,7 +101,7 @@ function verticalTextStyle(vert) {
101
101
  */
102
102
  function textColumnsStyle(cols) {
103
103
  if (!cols || cols.count < 2) return "";
104
- const gapPx = cols.gapEmu !== void 0 ? (cols.gapEmu / EMU_PER_PX$2).toFixed(2) : DEFAULT_COLUMN_GAP_PX;
104
+ const gapPx = cols.gapEmu !== void 0 ? (cols.gapEmu / EMU_PER_PX$3).toFixed(2) : DEFAULT_COLUMN_GAP_PX;
105
105
  return `column-count:${cols.count};column-gap:${gapPx}px`;
106
106
  }
107
107
  //#endregion
@@ -217,6 +217,30 @@ const paragraphNumberLabels = (paraData) => {
217
217
  return numberLabels;
218
218
  };
219
219
  //#endregion
220
+ //#region src/decimal-separator.ts
221
+ const separators = /* @__PURE__ */ new Map();
222
+ /**
223
+ * The character a decimal tab stop aligns on for a run's language
224
+ * (`<a:rPr lang>`): `,` for `de-DE`, `.` for `en-US`. Runs without a
225
+ * language, or with one Intl does not know, align on `.`. The reference desktop app on Mac
226
+ * uses the run language too, not the UI or system locale (native capture
227
+ * 2026-10-07: Set Proofing Language to German moved alignment to `,`).
228
+ */
229
+ function decimalSeparatorOf(lang) {
230
+ if (!lang) return ".";
231
+ let separator = separators.get(lang);
232
+ if (separator === void 0) {
233
+ separator = ".";
234
+ try {
235
+ separator = new Intl.NumberFormat(lang, { numberingSystem: "latn" }).formatToParts(1.5).find((part) => part.type === "decimal")?.value ?? ".";
236
+ } catch (error) {
237
+ if (!(error instanceof RangeError)) throw error;
238
+ }
239
+ separators.set(lang, separator);
240
+ }
241
+ return separator;
242
+ }
243
+ //#endregion
220
244
  //#region src/emf.ts
221
245
  const WORD_BYTES = 4;
222
246
  const SHORT_BYTES = 2;
@@ -493,7 +517,7 @@ const FALLBACK_DESCENT = .22;
493
517
  const FALLBACK_LINEGAP = .08;
494
518
  const BASELINE_LEADING_DROP = .036;
495
519
  const GRID_NUDGE_X = -.75;
496
- const EMU_PER_PX$1 = 9525;
520
+ const EMU_PER_PX$2 = 9525;
497
521
  let reflectionNamespace = 0;
498
522
  const EAST_ASIAN_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/;
499
523
  const CLOSING_PUNCT = /^[、。,.・)」』】〉》”’!?:;ー〜…,.!?:;)\]}]+$/;
@@ -678,7 +702,7 @@ const layoutCore = (input, measure) => {
678
702
  fieldWidth = decimalWidth = 0;
679
703
  } else {
680
704
  fieldWidth += token.width;
681
- const decimal = token.text.indexOf(".");
705
+ const decimal = token.text.indexOf(token.piece.decimalSeparator ?? ".");
682
706
  decimalWidth = decimal < 0 ? decimalWidth + token.width : mWidth(token.text.slice(0, decimal), {
683
707
  ...specOf(token.piece),
684
708
  sizePx: renderedSizePxOf(token.piece)
@@ -836,7 +860,7 @@ const lineContentExtent = (line, dx) => {
836
860
  };
837
861
  };
838
862
  const resolveFillPaints = (placements) => {
839
- if (!placements.some(({ line }) => line.tokens.some((t) => t.piece.fillPaint))) return {
863
+ if (!placements.some(({ line }) => line.tokens.some((t) => t.piece.fillPaint || t.piece.outlinePaint))) return {
840
864
  defs: "",
841
865
  placements
842
866
  };
@@ -864,12 +888,7 @@ const resolveFillPaints = (placements) => {
864
888
  };
865
889
  const painted = /* @__PURE__ */ new Map();
866
890
  let defs = "";
867
- const pieces = /* @__PURE__ */ new Map();
868
- const repaint = (piece) => {
869
- const paint = piece.fillPaint;
870
- if (paint === void 0) return piece;
871
- let next = pieces.get(piece);
872
- if (next) return next;
891
+ const resolve = (paint) => {
873
892
  let fill = painted.get(paint);
874
893
  if (fill === void 0) {
875
894
  const built = paint(bounds);
@@ -877,9 +896,16 @@ const resolveFillPaints = (placements) => {
877
896
  fill = built.fill;
878
897
  painted.set(paint, fill);
879
898
  }
899
+ return fill;
900
+ };
901
+ const pieces = /* @__PURE__ */ new Map();
902
+ const repaint = (piece) => {
903
+ let next = pieces.get(piece);
904
+ if (next) return next;
880
905
  next = {
881
906
  ...piece,
882
- fillHex: fill
907
+ ...piece.fillPaint ? { fillHex: resolve(piece.fillPaint) } : {},
908
+ ...piece.outlinePaint ? { outlineHex: resolve(piece.outlinePaint) } : {}
883
909
  };
884
910
  pieces.set(piece, next);
885
911
  return next;
@@ -888,7 +914,7 @@ const resolveFillPaints = (placements) => {
888
914
  ...placement,
889
915
  line: {
890
916
  ...placement.line,
891
- tokens: placement.line.tokens.map((token) => token.piece.fillPaint ? {
917
+ tokens: placement.line.tokens.map((token) => token.piece.fillPaint || token.piece.outlinePaint ? {
892
918
  ...token,
893
919
  piece: repaint(token.piece)
894
920
  } : token)
@@ -1086,7 +1112,7 @@ const emitTextReflections = (groups, textAnchor, x0, baselineY, descent, nextEff
1086
1112
  continue;
1087
1113
  }
1088
1114
  const scaleY = reflection.scaleY ?? 1;
1089
- const distPx = (reflection.offsetEmu ?? 0) / EMU_PER_PX$1;
1115
+ const distPx = (reflection.offsetEmu ?? 0) / EMU_PER_PX$2;
1090
1116
  const contactY = baselineY + descent;
1091
1117
  const angleRad = (reflection.angleDeg ?? 0) * Math.PI / 180;
1092
1118
  const offsetX = distPx * Math.cos(angleRad);
@@ -1099,7 +1125,7 @@ const emitTextReflections = (groups, textAnchor, x0, baselineY, descent, nextEff
1099
1125
  const gradientId = `${id}-gradient`;
1100
1126
  const maskId = `${id}-mask`;
1101
1127
  const filterId = `${id}-blur`;
1102
- const blurPx = Math.max(0, (reflection.blurEmu ?? 0) / EMU_PER_PX$1 / 2);
1128
+ const blurPx = Math.max(0, (reflection.blurEmu ?? 0) / EMU_PER_PX$2 / 2);
1103
1129
  const fadeDirection = reflection.fadeDirection;
1104
1130
  const defs = `<defs><linearGradient id="${gradientId}" ${fadeDirection === void 0 || fadeDirection === 90 ? "x1=\"0\" y1=\"1\" x2=\"0\" y2=\"0\"" : (() => {
1105
1131
  const radians = fadeDirection * Math.PI / 180;
@@ -1135,8 +1161,8 @@ const emitTextOuterEffects = (groups, textAnchor, x0, baselineY, nextId) => {
1135
1161
  if (shadow) {
1136
1162
  const id = nextId("outer-shadow");
1137
1163
  const angle = (shadow.angleDeg ?? 45) * Math.PI / 180;
1138
- const distance = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1;
1139
- const blur = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$1 / 2);
1164
+ const distance = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$2;
1165
+ const blur = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$2 / 2);
1140
1166
  const glyphSize = renderedSizePxOf(group.piece);
1141
1167
  const pad = Math.max(1, distance + blur * 3);
1142
1168
  const color = shadow.color ?? "#000000";
@@ -1146,7 +1172,7 @@ const emitTextOuterEffects = (groups, textAnchor, x0, baselineY, nextId) => {
1146
1172
  }
1147
1173
  if (glow) {
1148
1174
  const id = nextId("glow");
1149
- const radius = Math.max(0, (glow.radiusEmu ?? 63500) / EMU_PER_PX$1);
1175
+ const radius = Math.max(0, (glow.radiusEmu ?? 63500) / EMU_PER_PX$2);
1150
1176
  const glyphSize = renderedSizePxOf(group.piece);
1151
1177
  const pad = Math.max(1, radius * 2);
1152
1178
  const color = glow.color ?? "#FFFF00";
@@ -1170,9 +1196,9 @@ const emitTextInnerShadows = (groups, textAnchor, x0, baselineY, nextId) => {
1170
1196
  }
1171
1197
  const id = nextId("inner-shadow");
1172
1198
  const angle = (shadow.angleDeg ?? 45) * Math.PI / 180;
1173
- const dx = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1 * Math.cos(angle);
1174
- const dy = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1 * Math.sin(angle);
1175
- const blurPx = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$1 / 2);
1199
+ const dx = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$2 * Math.cos(angle);
1200
+ const dy = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$2 * Math.sin(angle);
1201
+ const blurPx = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$2 / 2);
1176
1202
  const color = shadow.color ?? "#000000";
1177
1203
  const opacity = Math.max(0, Math.min(1, shadow.opacity ?? 1));
1178
1204
  const defs = `<defs><filter id="${id}" x="-25%" y="-25%" width="150%" height="150%"><feGaussianBlur in="SourceAlpha" stdDeviation="${fmt(blurPx)}" result="innerBlur"/><feOffset in="innerBlur" dx="${fmt(dx)}" dy="${fmt(dy)}" result="innerOff"/><feComposite in="innerOff" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="innerMask"/><feFlood flood-color="${escapeXml$1(color)}" flood-opacity="${opacity.toFixed(3)}" result="innerColor"/><feComposite in="innerColor" in2="innerMask" operator="in" result="innerOut"/><feComposite in="innerOut" in2="SourceAlpha" operator="in"/></filter></defs>`;
@@ -1186,8 +1212,8 @@ const emitTextBevel = (groups, textAnchor, x0, baselineY, nextId) => {
1186
1212
  const bevel = groups.find((g) => g.piece.bevel && !g.isTab && g.width > 0)?.piece.bevel;
1187
1213
  if (!bevel) return "";
1188
1214
  const id = nextId("bevel");
1189
- const widthPx = bevel.widthEmu / EMU_PER_PX$1;
1190
- const surfaceScale = bevel.heightEmu / EMU_PER_PX$1 * bevel.relief;
1215
+ const widthPx = bevel.widthEmu / EMU_PER_PX$2;
1216
+ const surfaceScale = bevel.heightEmu / EMU_PER_PX$2 * bevel.relief;
1191
1217
  const table = [...bevel.profile.slice(1).map(() => 0), ...bevel.profile];
1192
1218
  const flat = Math.sin(bevel.elevationDeg * Math.PI / 180);
1193
1219
  const lightGain = bevel.contrast / (1 - flat);
@@ -1461,7 +1487,7 @@ function browserTextMeasurer() {
1461
1487
  }
1462
1488
  //#endregion
1463
1489
  //#region src/text-bevel.ts
1464
- const DEFAULT_BEVEL_EMU = 76200;
1490
+ const DEFAULT_BEVEL_EMU$1 = 76200;
1465
1491
  const DEFAULT_PRESET = "circle";
1466
1492
  const DEFAULT_RIG = {
1467
1493
  type: "threePt",
@@ -1582,8 +1608,8 @@ const RELIEF = 2.5;
1582
1608
  const textBevelOf = (text3d) => {
1583
1609
  const bevel = text3d?.bevelTop;
1584
1610
  if (!bevel) return null;
1585
- const widthEmu = bevel.widthEmu ?? DEFAULT_BEVEL_EMU;
1586
- const heightEmu = bevel.heightEmu ?? DEFAULT_BEVEL_EMU;
1611
+ const widthEmu = bevel.widthEmu ?? DEFAULT_BEVEL_EMU$1;
1612
+ const heightEmu = bevel.heightEmu ?? DEFAULT_BEVEL_EMU$1;
1587
1613
  if (widthEmu <= 0 || heightEmu <= 0) return null;
1588
1614
  const rig = text3d.scene?.lightRig ?? DEFAULT_RIG;
1589
1615
  const look = RIG_LOOK(rig.type);
@@ -1603,6 +1629,63 @@ const textBevelOf = (text3d) => {
1603
1629
  };
1604
1630
  };
1605
1631
  //#endregion
1632
+ //#region src/shape-3d.ts
1633
+ const EMU_PER_PX$1 = 9525;
1634
+ const DEFAULT_BEVEL_EMU = 76200;
1635
+ const LIGHT_AZIMUTH_DEG = 225;
1636
+ const LIGHT_ELEVATION_DEG = 45;
1637
+ const radians = (degrees) => degrees * Math.PI / 180;
1638
+ /**
1639
+ * An SVG `matrix(...)` for the camera's explicit rotation about the point
1640
+ * (`cx`, `cy`) in px, or `null` when the camera keeps the shape face-on.
1641
+ * The camera turns, so the shape appears turned the opposite way: a camera
1642
+ * revolution of +6° (Picture Style "Rotated, White") tilts the picture 6°
1643
+ * counter-clockwise.
1644
+ */
1645
+ const cameraTransform = (shape3d, cx, cy) => {
1646
+ const rotation = shape3d?.scene?.cameraRotation;
1647
+ if (!rotation) return null;
1648
+ const { latitudeDeg, longitudeDeg, revolutionDeg } = rotation;
1649
+ if (latitudeDeg === 0 && longitudeDeg === 0 && revolutionDeg === 0) return null;
1650
+ const lon = radians(-longitudeDeg);
1651
+ const lat = radians(-latitudeDeg);
1652
+ const rev = radians(-revolutionDeg);
1653
+ const ex = [Math.cos(lon), Math.sin(lon) * Math.sin(lat)];
1654
+ const ey = [0, Math.cos(lat)];
1655
+ const cos = Math.cos(rev);
1656
+ const sin = Math.sin(rev);
1657
+ const turn = (v) => [cos * v[0] - sin * v[1], sin * v[0] + cos * v[1]];
1658
+ const [a, b] = turn(ex);
1659
+ const [c, d] = turn(ey);
1660
+ return `matrix(${[
1661
+ a,
1662
+ b,
1663
+ c,
1664
+ d,
1665
+ cx - a * cx - c * cy,
1666
+ cy - b * cx - d * cy
1667
+ ].map((n) => n.toFixed(6)).join(" ")})`;
1668
+ };
1669
+ /**
1670
+ * The `<filter>` body that shades a top bevel onto whatever the shape painted
1671
+ * (picture, fill and border alike): a lit height map built from the blurred
1672
+ * outline, multiplied in so flat areas keep their color and slopes facing
1673
+ * away from the light darken, plus a specular highlight on the lit slopes.
1674
+ * `null` when the shape has no top bevel.
1675
+ */
1676
+ const bevelFilterPrimitives = (shape3d) => {
1677
+ const bevel = shape3d?.bevelTop;
1678
+ if (!bevel) return null;
1679
+ const widthPx = (bevel.widthEmu ?? DEFAULT_BEVEL_EMU) / EMU_PER_PX$1;
1680
+ const heightPx = (bevel.heightEmu ?? DEFAULT_BEVEL_EMU) / EMU_PER_PX$1;
1681
+ if (widthPx <= 0 || heightPx <= 0) return null;
1682
+ const blur = widthPx / 2;
1683
+ const surfaceScale = Math.min(10, Math.max(1, heightPx / widthPx * 6));
1684
+ const diffuse = 1 / Math.sin(radians(LIGHT_ELEVATION_DEG));
1685
+ const light = `<feDistantLight azimuth="${LIGHT_AZIMUTH_DEG}" elevation="${LIGHT_ELEVATION_DEG}"/>`;
1686
+ return `<feGaussianBlur in="SourceAlpha" stdDeviation="${blur.toFixed(2)}" result="bevelHeight"/><feDiffuseLighting in="bevelHeight" surfaceScale="${surfaceScale.toFixed(2)}" diffuseConstant="${diffuse.toFixed(4)}" lighting-color="#fff" result="bevelShade">${light}</feDiffuseLighting><feComposite in="SourceGraphic" in2="bevelShade" operator="arithmetic" k1="1" result="bevelLit"/><feSpecularLighting in="bevelHeight" surfaceScale="${surfaceScale.toFixed(2)}" specularConstant="0.6" specularExponent="20" lighting-color="#fff" result="bevelSpec">${light}</feSpecularLighting><feComposite in="bevelSpec" in2="SourceAlpha" operator="in" result="bevelGloss"/><feComposite in="bevelLit" in2="bevelGloss" operator="arithmetic" k2="1" k3="1"/>`;
1687
+ };
1688
+ //#endregion
1606
1689
  //#region src/render-slide.ts
1607
1690
  const clickActionHref = (pres, action) => {
1608
1691
  if (!action) return void 0;
@@ -1749,20 +1832,64 @@ const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, f
1749
1832
  }
1750
1833
  const opacityAttr = opacity !== 1 ? ` opacity="${opacity.toFixed(3)}"` : "";
1751
1834
  const border = outline.stroke !== "none" && outline.strokeWidth > 0 ? `<g${transform} fill="none" stroke="${outline.stroke}" stroke-width="${E(outline.strokeWidth)}"${outline.strokeAttrs ? ` ${outline.strokeAttrs}` : ""}>${pictureClipGeometry(shape, preset, x, y, w, h)}</g>` : "";
1835
+ const under = outline.fill !== "none" ? `${outline.defs}<g${transform} fill="${outline.fill}"${outline.fillAttrs}>${pictureClipGeometry(shape, preset, x, y, w, h)}</g>` : "";
1752
1836
  if (layout?.mode === "tile") {
1753
1837
  const intrinsic = getShapeImageIntrinsicSize(shape);
1754
1838
  if (intrinsic) {
1755
1839
  const pattern = imageTilePattern(dataUrl, layout, intrinsic, x, y, w, h, cropL, cropT, cropR, cropB);
1756
1840
  const geometry = pictureClipGeometry(shape, preset, x, y, w, h);
1757
- return `${clipDef}${pattern.defs}<g${transform} fill="${pattern.fill}"${filterAttr}${opacityAttr}>${geometry}</g>${border}<g${transform}>${textOverlay}</g>`;
1841
+ return `${clipDef}${pattern.defs}${pictureEffects(pres, shape, {
1842
+ x,
1843
+ y,
1844
+ w,
1845
+ h
1846
+ }, `${under}<g${transform} fill="${pattern.fill}"${filterAttr}${opacityAttr}>${geometry}</g>${border}`)}<g${transform}>${textOverlay}</g>`;
1758
1847
  }
1759
1848
  }
1760
- return `${clipDef}<g${transform}${clipAttr}><image x="${E(imgX)}" y="${E(imgY)}" width="${E(imgW)}" height="${E(imgH)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="none"${filterAttr}${opacityAttr}/></g>${border}<g${transform}>${textOverlay}</g>`;
1849
+ return `${clipDef}${pictureEffects(pres, shape, {
1850
+ x,
1851
+ y,
1852
+ w,
1853
+ h
1854
+ }, `${under}<g${transform}${clipAttr}><image x="${E(imgX)}" y="${E(imgY)}" width="${E(imgW)}" height="${E(imgH)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="none"${filterAttr}${opacityAttr}/></g>${border}`)}<g${transform}>${textOverlay}</g>`;
1761
1855
  }
1762
1856
  const linkUrl = getShapeImageLinkUrl(shape);
1763
1857
  const label = !bytes ? linkUrl ? `picture (link: ${linkUrl.length > 48 ? linkUrl.slice(0, 45) + "…" : linkUrl})` : "picture (no bytes)" : `picture (${format ?? "unknown"}${bytes ? `, ${bytes.byteLength} B` : ""})`;
1764
1858
  return `<g data-pptx-fallback="image"${transform}><rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" fill="#F3F4F6" stroke="#9CA3AF" stroke-width="${E(9525)}" stroke-dasharray="${E(5e4)},${E(3e4)}"/>${renderPicturePlaceholderLabel(x, y, w, h, label)}${textOverlay}</g>`;
1765
1859
  };
1860
+ /**
1861
+ * Wraps a picture's markup in its 3-D approximation (bevel lighting, then the
1862
+ * camera rotation) and its `<a:effectLst>` (shadows, glow, soft edge and the
1863
+ * reflection), the same effects a shape gets.
1864
+ */
1865
+ const pictureEffects = (pres, shape, box, markup) => {
1866
+ const shape3d = getShape3D(shape);
1867
+ let defs = "";
1868
+ let content = markup;
1869
+ let mirrored = markup;
1870
+ const bevel = bevelFilterPrimitives(shape3d);
1871
+ if (bevel) {
1872
+ const id = mintId();
1873
+ defs += `<defs><filter id="${id}" x="-10%" y="-10%" width="120%" height="120%" color-interpolation-filters="sRGB">${bevel}</filter></defs>`;
1874
+ content = `<g filter="url(#${id})">${content}</g>`;
1875
+ }
1876
+ const camera = cameraTransform(shape3d, (box.x + box.w / 2) / EMU_PER_PX, (box.y + box.h / 2) / EMU_PER_PX);
1877
+ if (camera) {
1878
+ content = `<g transform="${camera}">${content}</g>`;
1879
+ mirrored = `<g transform="${camera}">${mirrored}</g>`;
1880
+ }
1881
+ const reflection = buildReflection(pres, shape, mirrored, box);
1882
+ const fx = buildEffectsFilter(pres, shape);
1883
+ if (fx) {
1884
+ defs += fx.defs;
1885
+ content = `<g filter="url(#${fx.id})">${content}</g>`;
1886
+ }
1887
+ if (reflection) {
1888
+ defs += reflection.defs;
1889
+ content = reflection.svg + content;
1890
+ }
1891
+ return defs + content;
1892
+ };
1766
1893
  const imageTilePattern = (dataUrl, layout, intrinsic, x, y, w, h, cropL = 0, cropT = 0, cropR = 0, cropB = 0) => {
1767
1894
  const sourceW = intrinsic.width * (layout.scaleX ?? 1);
1768
1895
  const sourceH = intrinsic.height * (layout.scaleY ?? 1);
@@ -2064,7 +2191,22 @@ const patternDef = (pat) => {
2064
2191
  fillAttr: `url(#${id})`
2065
2192
  };
2066
2193
  };
2067
- const textFillPaint = (fill, theme) => (box) => {
2194
+ const textFillPaint = (fill, theme, solid) => (box) => {
2195
+ if (fill.kind === "none") return {
2196
+ defs: "",
2197
+ fill: "none"
2198
+ };
2199
+ if (fill.kind === "image") {
2200
+ if (!fill.bytes) return {
2201
+ defs: "",
2202
+ fill: solid
2203
+ };
2204
+ const id = mintId();
2205
+ return {
2206
+ defs: `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" x="${box.x.toFixed(2)}" y="${box.y.toFixed(2)}" width="${box.w.toFixed(2)}" height="${box.h.toFixed(2)}"><image href="${bytesToDataUrl(fill.bytes)}" width="${box.w.toFixed(2)}" height="${box.h.toFixed(2)}" preserveAspectRatio="none"/></pattern></defs>`,
2207
+ fill: `url(#${id})`
2208
+ };
2209
+ }
2068
2210
  if (fill.kind === "pattern") {
2069
2211
  const built = patternDef({
2070
2212
  preset: fill.preset,
@@ -2122,6 +2264,32 @@ const buildArrowMarker = (type, width, length, color, orient) => {
2122
2264
  def: `<defs><marker id="${id}" viewBox="0 0 ${w} ${h}" refX="${w}" refY="${h / 2}" markerWidth="${w}" markerHeight="${h}" orient="${orient}">${body}</marker></defs>`
2123
2265
  };
2124
2266
  };
2267
+ const unrotatedGradientTransform = (grad, shape, pres) => {
2268
+ const bounds = shape && (pres ? getShapeBoundsResolved(pres, shape) : getShapeBounds(shape));
2269
+ if (grad.rotateWithShape !== false || !shape || !bounds || bounds.w <= 0 || bounds.h <= 0) return "";
2270
+ const angle = getShapeRotation(shape) * Math.PI / 180;
2271
+ const cos = Math.cos(angle);
2272
+ const sin = Math.sin(angle);
2273
+ const w = bounds.w;
2274
+ const h = bounds.h;
2275
+ const rotatedW = Math.abs(cos) * w + Math.abs(sin) * h;
2276
+ const rotatedH = Math.abs(sin) * w + Math.abs(cos) * h;
2277
+ const flip = getShapeFlip(shape);
2278
+ const sx = flip?.horizontal ? -1 : 1;
2279
+ const sy = flip?.vertical ? -1 : 1;
2280
+ const a = sx * cos * rotatedW / w;
2281
+ const b = -sy * sin * rotatedW / h;
2282
+ const c = sx * sin * rotatedH / w;
2283
+ const d = sy * cos * rotatedH / h;
2284
+ return ` gradientTransform="matrix(${[
2285
+ a,
2286
+ b,
2287
+ c,
2288
+ d,
2289
+ (1 - a - c) / 2,
2290
+ (1 - b - d) / 2
2291
+ ].map((value) => value.toFixed(6)).join(" ")})"`;
2292
+ };
2125
2293
  const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
2126
2294
  let fillColor;
2127
2295
  let fillAttrs = "";
@@ -2136,33 +2304,7 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
2136
2304
  else if (fill.kind === "gradient") {
2137
2305
  const grad = shape ? pres ? getShapeGradientFillEffective(pres, shape) : getShapeGradientFill(shape) : null;
2138
2306
  if (grad) {
2139
- let transform = "";
2140
- const bounds = shape && (pres ? getShapeBoundsResolved(pres, shape) : getShapeBounds(shape));
2141
- if (grad.rotateWithShape === false && shape && bounds && bounds.w > 0 && bounds.h > 0) {
2142
- const angle = getShapeRotation(shape) * Math.PI / 180;
2143
- const cos = Math.cos(angle);
2144
- const sin = Math.sin(angle);
2145
- const w = bounds.w;
2146
- const h = bounds.h;
2147
- const rotatedW = Math.abs(cos) * w + Math.abs(sin) * h;
2148
- const rotatedH = Math.abs(sin) * w + Math.abs(cos) * h;
2149
- const flip = getShapeFlip(shape);
2150
- const sx = flip?.horizontal ? -1 : 1;
2151
- const sy = flip?.vertical ? -1 : 1;
2152
- const a = sx * cos * rotatedW / w;
2153
- const b = -sy * sin * rotatedW / h;
2154
- const c = sx * sin * rotatedH / w;
2155
- const d = sy * cos * rotatedH / h;
2156
- transform = ` gradientTransform="matrix(${[
2157
- a,
2158
- b,
2159
- c,
2160
- d,
2161
- (1 - a - c) / 2,
2162
- (1 - b - d) / 2
2163
- ].map((value) => value.toFixed(6)).join(" ")})"`;
2164
- }
2165
- const built = gradientDef(grad, theme, transform);
2307
+ const built = gradientDef(grad, theme, unrotatedGradientTransform(grad, shape, pres));
2166
2308
  defs = built.defs;
2167
2309
  fillColor = built.fillAttr;
2168
2310
  } else fillColor = (shape && pres ? getShapeFillColorResolved(pres, shape) : null) ?? "none";
@@ -2179,10 +2321,34 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
2179
2321
  let strokeWidth = 0;
2180
2322
  const strokeAttrParts = [];
2181
2323
  let markerAttrs = "";
2324
+ let headColor = strokeColor;
2325
+ let tailColor = strokeColor;
2182
2326
  if (stroke.kind === "solid") {
2183
2327
  let resolved = null;
2184
2328
  if (shape && pres) resolved = getShapeStrokeColorResolved(pres, shape);
2185
2329
  strokeColor = resolved ?? resolveColor(stroke.color, theme, "#9CA3AF");
2330
+ headColor = strokeColor;
2331
+ tailColor = strokeColor;
2332
+ } else if (stroke.kind === "gradient") {
2333
+ const grad = shape ? getShapeStrokeGradient(shape, pres) : null;
2334
+ const bounds = shape && (pres ? getShapeBoundsResolved(pres, shape) : getShapeBounds(shape));
2335
+ if (grad && bounds) {
2336
+ const stopColor = (stop) => stop ? stop.resolvedColor ?? resolveColor(stop.color, theme, "#9CA3AF") : "#9CA3AF";
2337
+ const ordered = [...grad.stops].sort((a, b) => a.offset - b.offset);
2338
+ headColor = stopColor(ordered[0]);
2339
+ tailColor = stopColor(ordered[ordered.length - 1]);
2340
+ const widthEmu = stroke.widthEmu ?? 9525;
2341
+ const built = bounds.w > 0 && bounds.h > 0 ? gradientDef(grad, theme, unrotatedGradientTransform(grad, shape, pres)) : gradientDef(grad, theme, "", {
2342
+ x: (bounds.w > 0 ? bounds.x : bounds.x - widthEmu / 2) / EMU_PER_PX,
2343
+ y: (bounds.h > 0 ? bounds.y : bounds.y - widthEmu / 2) / EMU_PER_PX,
2344
+ w: Math.max(bounds.w, widthEmu) / EMU_PER_PX,
2345
+ h: Math.max(bounds.h, widthEmu) / EMU_PER_PX
2346
+ });
2347
+ defs += built.defs;
2348
+ strokeColor = built.fillAttr;
2349
+ }
2350
+ }
2351
+ if (strokeColor !== "none" && (stroke.kind === "solid" || stroke.kind === "gradient")) {
2186
2352
  strokeWidth = stroke.widthEmu ?? 9525;
2187
2353
  if (shape) {
2188
2354
  const opacity = getShapeStrokeOpacity(shape, pres);
@@ -2208,12 +2374,12 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
2208
2374
  const head = getShapeStrokeArrow(shape, "head", pres);
2209
2375
  const tail = getShapeStrokeArrow(shape, "tail", pres);
2210
2376
  if (head && head.type !== "none") {
2211
- const m = buildArrowMarker(head.type, head.width, head.length, strokeColor, "auto-start-reverse");
2377
+ const m = buildArrowMarker(head.type, head.width, head.length, headColor, "auto-start-reverse");
2212
2378
  defs += m.def;
2213
2379
  markerAttrs += ` marker-start="url(#${m.id})"`;
2214
2380
  }
2215
2381
  if (tail && tail.type !== "none") {
2216
- const m = buildArrowMarker(tail.type, tail.width, tail.length, strokeColor, "auto");
2382
+ const m = buildArrowMarker(tail.type, tail.width, tail.length, tailColor, "auto");
2217
2383
  defs += m.def;
2218
2384
  markerAttrs += ` marker-end="url(#${m.id})"`;
2219
2385
  }
@@ -2260,7 +2426,7 @@ const cssColorWithOpacity = (hex, opacity) => {
2260
2426
  };
2261
2427
  const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault = false, suppressLineHeight = false, lineHeightOverride = void 0) => {
2262
2428
  if (text === "") return "";
2263
- if (format?.textFill) {
2429
+ if (format?.textFill || format?.outline?.fill) {
2264
2430
  const { textFill: _fill, outline: _outline, shadow: _shadow, glow: _glow, underline: _underline, strike: _strike, ...layout } = format;
2265
2431
  return renderRun(text, {
2266
2432
  ...layout,
@@ -2320,6 +2486,10 @@ const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault =
2320
2486
  const LINE_HEIGHT = 1.05;
2321
2487
  const AUTOFIT_FLOOR = .25;
2322
2488
  const AUTOFIT_STEP = .05;
2489
+ const decimalSeparatorField = (lang) => {
2490
+ const separator = decimalSeparatorOf(lang);
2491
+ return separator === "." ? {} : { decimalSeparator: separator };
2492
+ };
2323
2493
  const underlineStyleOf = (fmt) => {
2324
2494
  const u = fmt?.underline;
2325
2495
  if (u === void 0 || u === false || u === "none") return "none";
@@ -2349,7 +2519,7 @@ const strikeStyleOf = (fmt) => {
2349
2519
  if (s === "dblStrike") return "double";
2350
2520
  return s !== void 0 && s !== false && s !== "noStrike";
2351
2521
  };
2352
- const hasPaintedTextFill = (run) => !!run.fmt?.textFill && !run.href;
2522
+ const hasPaintedTextFill = (run) => !!(run.fmt?.textFill || run.fmt?.outline?.fill) && !run.href;
2353
2523
  const alignOf = (a) => a === "center" || a === "right" || a === "justify" ? a : "left";
2354
2524
  const verticalLayoutOf = (vert) => {
2355
2525
  switch (vert) {
@@ -2427,7 +2597,10 @@ const buildSvgTextInput = (a) => {
2427
2597
  letterSpacingPx,
2428
2598
  kerning,
2429
2599
  fillHex,
2430
- ...fmt?.textFill && !run.href ? { fillPaint: textFillPaint(fmt.textFill, a.theme) } : {},
2600
+ ...hasPaintedTextFill(run) ? { fillPaint: fmt?.textFill ? textFillPaint(fmt.textFill, a.theme, fillHex) : () => ({
2601
+ defs: "",
2602
+ fill: fillHex
2603
+ }) } : {},
2431
2604
  ...fmt?.underlineColor !== void 0 && fmt.underlineColor !== null ? { underlineHex: resolveColor(fmt.underlineColor, a.theme, fillHex) } : {},
2432
2605
  underline: underlineStyleOf(fmt),
2433
2606
  strike: strikeStyleOf(fmt),
@@ -2436,6 +2609,21 @@ const buildSvgTextInput = (a) => {
2436
2609
  outlineHex: resolveColor(fmt.outline.color, a.theme, "#000000"),
2437
2610
  outlineWidthPx: (fmt.outline.widthEmu ?? 9525) / EMU_PER_PX
2438
2611
  } : {},
2612
+ ...fmt?.outline?.fill && !run.href ? (() => {
2613
+ const fill = fmt.outline.fill;
2614
+ const first = fill.stops[0];
2615
+ return {
2616
+ outlineHex: first ? first.resolvedColor ?? resolveColor(first.color, a.theme, "#000000") : "#000000",
2617
+ outlinePaint: (box) => {
2618
+ const built = gradientDef(fill, a.theme, "", box);
2619
+ return {
2620
+ defs: built.defs,
2621
+ fill: built.fillAttr
2622
+ };
2623
+ },
2624
+ outlineWidthPx: (fmt.outline.widthEmu ?? 9525) / EMU_PER_PX
2625
+ };
2626
+ })() : {},
2439
2627
  ...fmt?.shadow ? { shadow: {
2440
2628
  ...fmt.shadow,
2441
2629
  ...fmt.shadow.color ? { color: resolveColor(fmt.shadow.color, a.theme, "#000000") } : {}
@@ -2452,7 +2640,8 @@ const buildSvgTextInput = (a) => {
2452
2640
  } } : {},
2453
2641
  ...a.bevel ? { bevel: a.bevel } : {},
2454
2642
  href: run.href ?? null,
2455
- ...run.hrefTip !== void 0 ? { hrefTip: run.hrefTip } : {}
2643
+ ...run.hrefTip !== void 0 ? { hrefTip: run.hrefTip } : {},
2644
+ ...run.decimalSeparator !== void 0 ? { decimalSeparator: run.decimalSeparator } : {}
2456
2645
  };
2457
2646
  const segs = run.text.split("\n");
2458
2647
  segs.forEach((seg, i) => {
@@ -2684,10 +2873,11 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
2684
2873
  } catch {
2685
2874
  elements = [];
2686
2875
  }
2876
+ const languages = elements.length ? getParagraphElementLanguages(shape, p) : [];
2687
2877
  let rIdx = 0;
2688
2878
  let fieldIndex = 0;
2689
2879
  let breakIndex = 0;
2690
- for (const el of elements) {
2880
+ for (const [elementIndex, el] of elements.entries()) {
2691
2881
  if (el.kind === "br") {
2692
2882
  const fmt = getShapeRunFormatEffective(pres, shape, p, { breakIndex: breakIndex++ });
2693
2883
  runs.push({
@@ -2722,6 +2912,7 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
2722
2912
  text: txt,
2723
2913
  fmt,
2724
2914
  sizePt,
2915
+ ...decimalSeparatorField(languages[elementIndex]),
2725
2916
  ...href !== void 0 ? { href } : {},
2726
2917
  ...hrefTip !== void 0 ? { hrefTip } : {}
2727
2918
  });
@@ -2861,9 +3052,15 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
2861
3052
  let runFmt = run.fmt;
2862
3053
  if (run.href) {
2863
3054
  const hlinkColor = theme ? normalizeHex(theme.hyperlink) : "#0563C1";
2864
- const { textFill: _linkFill, ...linkFmt } = runFmt ?? {};
3055
+ const { textFill: _linkFill, outline: linkOutline, ...linkFmt } = runFmt ?? {};
3056
+ const { fill: outlineFill, ...solidOutline } = linkOutline ?? {};
3057
+ const outlineColor = outlineFill?.stops[0]?.color ?? solidOutline.color;
2865
3058
  runFmt = {
2866
3059
  ...linkFmt,
3060
+ ...linkOutline ? { outline: {
3061
+ ...solidOutline,
3062
+ ...outlineColor !== void 0 ? { color: outlineColor } : {}
3063
+ } } : {},
2867
3064
  color: hlinkColor,
2868
3065
  underline: runFmt?.underline ?? true
2869
3066
  };
@@ -2894,8 +3091,8 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
2894
3091
  "padding:0",
2895
3092
  `text-align:${ALIGNMENT_TO_CSS[para.align] ?? "left"}`,
2896
3093
  lineHeightCss,
2897
- leftPx > 0 ? `padding-left:${leftPx.toFixed(2)}px` : "",
2898
- rightPx > 0 ? `padding-right:${rightPx.toFixed(2)}px` : "",
3094
+ leftPx > 0 ? `padding-inline-start:${leftPx.toFixed(2)}px` : "",
3095
+ rightPx > 0 ? `padding-inline-end:${rightPx.toFixed(2)}px` : "",
2899
3096
  firstLinePx !== 0 ? `text-indent:${firstLinePx.toFixed(2)}px` : ""
2900
3097
  ].filter(Boolean);
2901
3098
  let prefix = "";
@@ -4554,10 +4751,11 @@ const cellParaData = (paragraphs, cell, pres) => {
4554
4751
  const bulletIsPicture = isParagraphBulletPicture(cell, index);
4555
4752
  const bulletImageBytes = bulletIsPicture ? getParagraphBulletImageBytes(cell, index) : null;
4556
4753
  const runs = [];
4754
+ const languages = getParagraphElementLanguages(cell, index);
4557
4755
  let rIdx = 0;
4558
4756
  let fieldIndex = 0;
4559
4757
  let breakIndex = 0;
4560
- for (const el of para.elements) {
4758
+ for (const [elementIndex, el] of para.elements.entries()) {
4561
4759
  if (el.kind === "br") {
4562
4760
  const fmt = getTableCellRunFormatEffective(pres, cell, index, { breakIndex: breakIndex++ });
4563
4761
  runs.push({
@@ -4574,6 +4772,7 @@ const cellParaData = (paragraphs, cell, pres) => {
4574
4772
  text: el.text,
4575
4773
  fmt,
4576
4774
  sizePt: fmt?.size ?? DEFAULT_BODY_PT,
4775
+ ...decimalSeparatorField(languages[elementIndex]),
4577
4776
  ...href ? {
4578
4777
  href,
4579
4778
  ...el.tooltip !== void 0 ? { hrefTip: el.tooltip } : {}
@@ -4679,6 +4878,33 @@ const renderTableCellText = (cell, paragraphs, cx, cy, cw, ch, color, pres, shap
4679
4878
  const body = renderHtmlParagraphs(paraData, numberLabels, theme, 1, DEFAULT_BODY_PT, color).join("");
4680
4879
  return `<foreignObject x="${px(innerX)}" y="${px(innerY)}" width="${px(innerW)}" height="${px(innerH)}"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;flex-direction:column;justify-content:${justify};width:100%;height:100%;box-sizing:border-box;overflow:hidden;line-height:${LINE_HEIGHT};font-family:${familyFont};color:${color};word-break:break-word;${directionStyle}">${body}</div></foreignObject>${fillOverlay()}${bevelOverlay()}`;
4681
4880
  };
4881
+ const TWO_STOP_TABLE_GRADIENT_EXPONENT = 1.85;
4882
+ const TWO_STOP_TABLE_GRADIENT_SAMPLES = 16;
4883
+ const twoStopTableGradient = (grad, theme) => {
4884
+ if (grad.path !== void 0 || grad.stops.length !== 2) return grad;
4885
+ const [start, end] = [...grad.stops].sort((a, b) => a.offset - b.offset);
4886
+ if (!start || !end || end.offset <= start.offset) return grad;
4887
+ const span = end.offset - start.offset;
4888
+ const colorOf = (stop) => stop.resolvedColor ?? resolveColor(stop.color, theme, "#E5E7EB");
4889
+ const startColor = colorOf(start);
4890
+ const endColor = colorOf(end);
4891
+ const stops = Array.from({ length: 17 }, (_, i) => {
4892
+ const along = i / TWO_STOP_TABLE_GRADIENT_SAMPLES;
4893
+ const startWeight = (1 - along) ** TWO_STOP_TABLE_GRADIENT_EXPONENT;
4894
+ const color = mixHex(startColor, endColor, startWeight);
4895
+ const opacity = start.opacity === void 0 && end.opacity === void 0 ? void 0 : (start.opacity ?? 1) * startWeight + (end.opacity ?? 1) * (1 - startWeight);
4896
+ return {
4897
+ offset: start.offset + along * span,
4898
+ color,
4899
+ resolvedColor: color,
4900
+ ...opacity === void 0 ? {} : { opacity }
4901
+ };
4902
+ });
4903
+ return {
4904
+ ...grad,
4905
+ stops
4906
+ };
4907
+ };
4682
4908
  const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
4683
4909
  let dims;
4684
4910
  let widths;
@@ -4711,6 +4937,16 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
4711
4937
  const tableThemeFace = getPresentationFonts(pres)?.minorLatin ?? null;
4712
4938
  const out = [];
4713
4939
  out.push(`<g${transform}>`);
4940
+ const background = getTableBackgroundEffective(pres, shape);
4941
+ const tableW = (colXs[dims.cols] ?? xPx) - xPx;
4942
+ const tableH = (rowYs[dims.rows] ?? yPx) - yPx;
4943
+ if (background?.fill.kind === "solid") {
4944
+ const opacity = background.fillOpacity !== void 0 && background.fillOpacity < 1 ? ` fill-opacity="${background.fillOpacity.toFixed(3)}"` : "";
4945
+ out.push(`<rect x="${px(xPx)}" y="${px(yPx)}" width="${px(tableW)}" height="${px(tableH)}" fill="${resolveColor(background.fill.color, theme, "#FFFFFF")}"${opacity}/>`);
4946
+ } else if (background?.gradient) {
4947
+ const built = gradientDef(twoStopTableGradient(background.gradient, theme), theme);
4948
+ out.push(`${built.defs}<rect x="${px(xPx)}" y="${px(yPx)}" width="${px(tableW)}" height="${px(tableH)}" fill="${built.fillAttr}"/>`);
4949
+ }
4714
4950
  const borderEdges = [];
4715
4951
  const borderEdgeCandidates = /* @__PURE__ */ new Map();
4716
4952
  for (let r = 0; r < dims.rows; r++) for (let c = 0; c < dims.cols; c++) {
@@ -4733,7 +4969,8 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
4733
4969
  else if (fill.kind !== "inherit") resolvedFill = "none";
4734
4970
  else resolvedFill = "none";
4735
4971
  const cellTextColor = textColor;
4736
- out.push(`<g data-pptx-cell="${r},${c}"><rect x="${px(cx)}" y="${px(cy)}" width="${px(cw)}" height="${px(ch)}" fill="${resolvedFill}"/>`);
4972
+ const fillOpacity = fill.kind === "solid" && appearance.fillOpacity !== void 0 && appearance.fillOpacity < 1 ? ` fill-opacity="${appearance.fillOpacity.toFixed(3)}"` : "";
4973
+ out.push(`<g data-pptx-cell="${r},${c}"><rect x="${px(cx)}" y="${px(cy)}" width="${px(cw)}" height="${px(ch)}" fill="${resolvedFill}"${fillOpacity}/>`);
4737
4974
  const borders = appearance.borders;
4738
4975
  const dashAttr = (dash, widthPx) => {
4739
4976
  if (!dash || dash === "solid") return "";
@@ -4893,6 +5130,148 @@ const PATH_SHADE = {
4893
5130
  lighten: "fill=\"#fff\" fill-opacity=\"0.4\"",
4894
5131
  lightenLess: "fill=\"#fff\" fill-opacity=\"0.2\""
4895
5132
  };
5133
+ const SKETCH_AMPLITUDE = {
5134
+ curved: .008,
5135
+ freehand: .014,
5136
+ scribble: .022
5137
+ };
5138
+ const SKETCH_STEP = .12;
5139
+ const seededRandom = (seed) => {
5140
+ let state = seed >>> 0;
5141
+ return () => {
5142
+ state = state + 1831565813 >>> 0;
5143
+ let t = state;
5144
+ t = Math.imul(t ^ t >>> 15, t | 1);
5145
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
5146
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
5147
+ };
5148
+ };
5149
+ /**
5150
+ * An approximation of a sketched outline (`ask:lineSketchStyleProps`): every
5151
+ * edge of `geom` is redrawn as a seeded hand-drawn wobble. Only used for a
5152
+ * preset geometry the writer left crisp; sketches the reference desktop app wrote already
5153
+ * carry their hand-drawn path as custom geometry.
5154
+ */
5155
+ const sketchGeometry = (geom, sketch, w, h, seed) => {
5156
+ const random = seededRandom(seed);
5157
+ const jitter = (amp) => (random() * 2 - 1) * amp;
5158
+ const paths = geom.paths.map((path) => {
5159
+ const cw = path.w ?? w;
5160
+ const ch = path.h ?? h;
5161
+ const size = Math.max(Math.min(cw, ch), Math.max(cw, ch) / 4);
5162
+ const amp = SKETCH_AMPLITUDE[sketch] * size;
5163
+ const step = SKETCH_STEP * Math.max(cw, ch);
5164
+ const nudge = (pt) => ({
5165
+ x: pt.x + jitter(amp),
5166
+ y: pt.y + jitter(amp)
5167
+ });
5168
+ const commands = [];
5169
+ let curX = 0;
5170
+ let curY = 0;
5171
+ let startX = 0;
5172
+ let startY = 0;
5173
+ const wobbleTo = (x, y) => {
5174
+ const dx = x - curX;
5175
+ const dy = y - curY;
5176
+ const len = Math.hypot(dx, dy);
5177
+ const count = step > 0 ? Math.max(1, Math.round(len / step)) : 1;
5178
+ const nx = len > 0 ? -dy / len : 0;
5179
+ const ny = len > 0 ? dx / len : 0;
5180
+ let px = curX;
5181
+ let py = curY;
5182
+ for (let i = 1; i <= count; i++) {
5183
+ const last = i === count;
5184
+ const ex = curX + dx * i / count + (last ? 0 : jitter(amp / 2));
5185
+ const ey = curY + dy * i / count + (last ? 0 : jitter(amp / 2));
5186
+ const bend = jitter(amp);
5187
+ const control = {
5188
+ x: (px + ex) / 2 + nx * bend,
5189
+ y: (py + ey) / 2 + ny * bend
5190
+ };
5191
+ commands.push({
5192
+ kind: "quadBezTo",
5193
+ pts: [control, {
5194
+ x: ex,
5195
+ y: ey
5196
+ }]
5197
+ });
5198
+ px = ex;
5199
+ py = ey;
5200
+ }
5201
+ curX = x;
5202
+ curY = y;
5203
+ };
5204
+ for (const cmd of path.commands) switch (cmd.kind) {
5205
+ case "moveTo":
5206
+ curX = cmd.pt.x;
5207
+ curY = cmd.pt.y;
5208
+ startX = curX;
5209
+ startY = curY;
5210
+ commands.push(cmd);
5211
+ break;
5212
+ case "lnTo":
5213
+ wobbleTo(cmd.pt.x, cmd.pt.y);
5214
+ break;
5215
+ case "quadBezTo":
5216
+ commands.push({
5217
+ kind: "quadBezTo",
5218
+ pts: [nudge(cmd.pts[0]), cmd.pts[1]]
5219
+ });
5220
+ curX = cmd.pts[1].x;
5221
+ curY = cmd.pts[1].y;
5222
+ break;
5223
+ case "cubicBezTo":
5224
+ commands.push({
5225
+ kind: "cubicBezTo",
5226
+ pts: [
5227
+ nudge(cmd.pts[0]),
5228
+ nudge(cmd.pts[1]),
5229
+ cmd.pts[2]
5230
+ ]
5231
+ });
5232
+ curX = cmd.pts[2].x;
5233
+ curY = cmd.pts[2].y;
5234
+ break;
5235
+ case "arcTo":
5236
+ for (const s of arcToCubicSegments(curX, curY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng)) {
5237
+ commands.push({
5238
+ kind: "cubicBezTo",
5239
+ pts: [
5240
+ nudge({
5241
+ x: s.c1x,
5242
+ y: s.c1y
5243
+ }),
5244
+ nudge({
5245
+ x: s.c2x,
5246
+ y: s.c2y
5247
+ }),
5248
+ {
5249
+ x: s.ex,
5250
+ y: s.ey
5251
+ }
5252
+ ]
5253
+ });
5254
+ curX = s.ex;
5255
+ curY = s.ey;
5256
+ }
5257
+ break;
5258
+ case "close":
5259
+ if (curX !== startX || curY !== startY) wobbleTo(startX, startY);
5260
+ commands.push(cmd);
5261
+ curX = startX;
5262
+ curY = startY;
5263
+ break;
5264
+ }
5265
+ return {
5266
+ ...path,
5267
+ commands
5268
+ };
5269
+ });
5270
+ return {
5271
+ ...geom,
5272
+ paths
5273
+ };
5274
+ };
4896
5275
  /**
4897
5276
  * Renders an evaluated {@link CustomGeometry} (a `<a:custGeom>` or a preset
4898
5277
  * from `getPresetGeometry`) as `<path>`s in the shape's slide box. Returns
@@ -5109,6 +5488,11 @@ const renderShapeContent = (shape, pres, theme, ctx) => {
5109
5488
  const customGeom = rawPreset === null ? getShapeCustomGeometry(shape) : null;
5110
5489
  let geomSvg = customGeom !== null ? customGeometryToSvg(customGeom, x, y, w, h, p.fill, fa, p.stroke, p.strokeWidth, sa, ma) : "";
5111
5490
  const isCustGeom = geomSvg === "" && rawPreset === null && getShapeXmlString(shape).includes("custGeom");
5491
+ const sketch = rawPreset !== null ? getShapeStrokeSketch(shape) : null;
5492
+ if (geomSvg !== "") {} else if (sketch !== null && p.stroke !== "none") {
5493
+ const geometry = getPresetGeometry(preset, bounds, getShapeAdjustValues(shape));
5494
+ if (geometry !== null) geomSvg = customGeometryToSvg(sketchGeometry(geometry, sketch, w, h, getShapeId(shape)), x, y, w, h, p.fill, fa, p.stroke, p.strokeWidth, sa, ma);
5495
+ }
5112
5496
  if (geomSvg !== "") {} else if (preset === "rect") geomSvg = `<rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" fill="${p.fill}"${fa} stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma}/>`;
5113
5497
  else {
5114
5498
  const geometry = getPresetGeometry(preset, bounds, getShapeAdjustValues(shape));
@@ -5201,11 +5585,14 @@ const buildReflection = (pres, shape, geomRaw, box) => {
5201
5585
  const f = refl.scaleY ?? -1;
5202
5586
  const startA = refl.startOpacity ?? 1;
5203
5587
  const endA = refl.opacity ?? 0;
5588
+ const startPos = refl.startPosition ?? 0;
5589
+ const endPos = Math.max(startPos, refl.endPosition ?? 1);
5204
5590
  const contactPx = (box.y + box.h) / EMU_PER_PX;
5205
5591
  const transform = `translate(0 ${(refl.distEmu / EMU_PER_PX + contactPx * (1 - f)).toFixed(2)}) scale(1 ${f})`;
5206
5592
  const maskId = mintId();
5207
5593
  const gradId = mintId();
5208
- const defs = `<defs><linearGradient id="${gradId}" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity="${startA.toFixed(3)}"/><stop offset="1" stop-color="#fff" stop-opacity="${endA.toFixed(3)}"/></linearGradient><mask id="${maskId}" maskContentUnits="objectBoundingBox"><rect width="1" height="1" fill="url(#${gradId})"/></mask></defs>`;
5594
+ const [y1, y2] = f < 0 ? [1, 0] : [0, 1];
5595
+ const defs = `<defs><linearGradient id="${gradId}" x1="0" y1="${y1}" x2="0" y2="${y2}"><stop offset="${startPos.toFixed(3)}" stop-color="#fff" stop-opacity="${startA.toFixed(3)}"/><stop offset="${endPos.toFixed(3)}" stop-color="#fff" stop-opacity="${endA.toFixed(3)}"/></linearGradient><mask id="${maskId}" maskContentUnits="objectBoundingBox"><rect width="1" height="1" fill="url(#${gradId})"/></mask></defs>`;
5209
5596
  return {
5210
5597
  svg: `<g transform="${transform}" mask="url(#${maskId})" data-pptx-reflection="1">${geomRaw}</g>`,
5211
5598
  defs
@@ -5253,9 +5640,9 @@ const buildEffectsFilter = (pres, shape) => {
5253
5640
  primitives.push(`<feMorphology in="SourceAlpha" operator="dilate" radius="${dilatePx.toFixed(2)}" result="glowExp${i}"/>`, `<feGaussianBlur in="glowExp${i}" stdDeviation="${featherPx.toFixed(2)}" result="glowBlur${i}"/>`, `<feFlood flood-color="${color}" flood-opacity="${opacity.toFixed(3)}" result="glowCol${i}"/>`, `<feComposite in="glowCol${i}" in2="glowBlur${i}" operator="in" result="glowOut${i}"/>`);
5254
5641
  layers.push(`glowOut${i}`);
5255
5642
  } else if (e.kind === "softEdge") {
5256
- const blurPx = e.radiusEmu / EMU_PER_PX / 2;
5643
+ const radiusPx = e.radiusEmu / EMU_PER_PX;
5257
5644
  const i = primitives.length;
5258
- primitives.push(`<feGaussianBlur in="SourceGraphic" stdDeviation="${blurPx.toFixed(2)}" result="softOut${i}"/>`);
5645
+ primitives.push(`<feMorphology in="SourceAlpha" operator="erode" radius="${(radiusPx / 2).toFixed(2)}" result="softCore${i}"/>`, `<feGaussianBlur in="softCore${i}" stdDeviation="${(radiusPx / 4).toFixed(2)}" result="softMask${i}"/>`, `<feComposite in="SourceGraphic" in2="softMask${i}" operator="in" result="softOut${i}"/>`);
5259
5646
  layers.length = 0;
5260
5647
  layers.push(`softOut${i}`);
5261
5648
  } else if (e.kind === "blur") {
@@ -5297,14 +5684,33 @@ const topLevelShapes = (shapes, opts) => {
5297
5684
  }
5298
5685
  return out;
5299
5686
  };
5300
- const renderSlideSvg = (pres, slide, opts = {}) => {
5687
+ const renderSlideSvg = (pres, slide, opts = {}) => renderSurface(pres, {
5688
+ slide,
5689
+ layout: getSlideLayout(slide),
5690
+ master: false
5691
+ }, opts);
5692
+ /**
5693
+ * Draws a slide layout — or, with `master`, its slide master — the way
5694
+ * the reference desktop app's Slide Master view shows it behind the placeholders: the
5695
+ * background and the decorative (non-placeholder) shapes of the master and,
5696
+ * for a layout, of the layout itself. A layout that hides background graphics
5697
+ * leaves the master's shapes out. Placeholders are not drawn; an editor draws
5698
+ * them with their prompt text over this.
5699
+ */
5700
+ const renderSlideLayoutSvg = (pres, layout, opts = {}) => renderSurface(pres, {
5701
+ slide: null,
5702
+ layout,
5703
+ master: opts.master ?? false
5704
+ }, opts);
5705
+ const renderSurface = (pres, surface, opts) => {
5706
+ const { slide, master } = surface;
5301
5707
  const size = getSlideSize(pres) ?? DEFAULT_SIZE;
5302
5708
  const W = size.width;
5303
5709
  const H = size.height;
5304
5710
  const theme = getPresentationTheme(pres);
5305
- activeColorMap = getEffectiveColorMap(slide);
5306
- activeDeckTextColor = resolveDeckBodyTextColor(slide) ?? "#000000";
5307
- const position = getSlides(pres).indexOf(slide);
5711
+ activeColorMap = getEffectiveColorMap(slide ?? surface.layout);
5712
+ activeDeckTextColor = resolveDeckBodyTextColor(slide ?? surface.layout) ?? "#000000";
5713
+ const position = slide ? getSlides(pres).indexOf(slide) : 0;
5308
5714
  activeSlideNumber = String(getPresentationFirstSlideNumber(pres) + (position < 0 ? 0 : position));
5309
5715
  const ctx = {
5310
5716
  groupScale: {
@@ -5322,11 +5728,11 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5322
5728
  },
5323
5729
  inverseGroupTransform: ""
5324
5730
  };
5325
- let bg = getSlideBackground(slide);
5731
+ let bg = slide ? getSlideBackground(slide) : { kind: "inherit" };
5326
5732
  if (bg.kind === "inherit") {
5327
- const layout = getSlideLayout(slide);
5733
+ const layout = surface.layout;
5328
5734
  if (layout) {
5329
- const layoutBg = getSlideLayoutBackground(layout);
5735
+ const layoutBg = master ? { kind: "inherit" } : getSlideLayoutBackground(layout);
5330
5736
  if (layoutBg.kind !== "inherit") bg = layoutBg;
5331
5737
  else {
5332
5738
  const masterBg = getSlideMasterBackground(pres, layout);
@@ -5341,11 +5747,11 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5341
5747
  let bgGradientDefs = "";
5342
5748
  if (bg.kind === "solid") bgColor = resolveColor(bg.color, theme, "#FFFFFF");
5343
5749
  else if (bg.kind === "gradient") {
5344
- let grad = getSlideBackgroundGradientFill(slide);
5750
+ let grad = slide ? getSlideBackgroundGradientFill(slide) : null;
5345
5751
  if (!grad) {
5346
- const layout = getSlideLayout(slide);
5752
+ const layout = surface.layout;
5347
5753
  if (layout) {
5348
- grad = getSlideLayoutBackgroundGradientFill(layout);
5754
+ grad = master ? null : getSlideLayoutBackgroundGradientFill(layout);
5349
5755
  if (!grad) grad = getSlideMasterBackgroundGradientFill(pres, layout);
5350
5756
  }
5351
5757
  }
@@ -5355,11 +5761,11 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5355
5761
  bgGradient = `<rect width="${E(W)}" height="${E(H)}" fill="${built.fillAttr}"/>`;
5356
5762
  }
5357
5763
  } else if (bg.kind === "pattern") {
5358
- let pat = getSlideBackgroundPatternFill(pres, slide);
5764
+ let pat = slide ? getSlideBackgroundPatternFill(pres, slide) : null;
5359
5765
  if (!pat) {
5360
- const layout = getSlideLayout(slide);
5766
+ const layout = surface.layout;
5361
5767
  if (layout) {
5362
- pat = getSlideLayoutBackgroundPatternFill(pres, layout);
5768
+ pat = master ? null : getSlideLayoutBackgroundPatternFill(pres, layout);
5363
5769
  if (!pat) pat = getSlideMasterBackgroundPatternFill(pres, layout);
5364
5770
  }
5365
5771
  }
@@ -5371,23 +5777,23 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5371
5777
  } else if (theme && bg.kind === "inherit") bgColor = normalizeHex(theme.light1);
5372
5778
  let bgImage = "";
5373
5779
  if (bg.kind === "image") {
5374
- let bytes = getSlideBackgroundImageBytes(slide);
5780
+ let bytes = slide ? getSlideBackgroundImageBytes(slide) : null;
5375
5781
  if (!bytes && pres) {
5376
- const layout = getSlideLayout(slide);
5782
+ const layout = surface.layout;
5377
5783
  if (layout) {
5378
- bytes = getSlideLayoutBackgroundImageBytes(pres, layout);
5784
+ bytes = master ? null : getSlideLayoutBackgroundImageBytes(pres, layout);
5379
5785
  if (!bytes) bytes = getSlideMasterBackgroundImageBytes(pres, layout);
5380
5786
  }
5381
5787
  }
5382
5788
  if (bytes) {
5383
5789
  const fmt = detectImageFormatLocal(bytes);
5384
5790
  const dataUrl = `data:${fmt ? imageMime[fmt] ?? "image/png" : "image/png"};base64,${u8ToBase64(bytes)}`;
5385
- const layout = getSlideBackgroundImageFillLayout(slide);
5386
- const crop = getSlideBackgroundImageCrop(slide);
5791
+ const layout = slide ? getSlideBackgroundImageFillLayout(slide) : null;
5792
+ const crop = slide ? getSlideBackgroundImageCrop(slide) : null;
5387
5793
  const cropLeft = crop?.left ?? 0, cropTop = crop?.top ?? 0;
5388
5794
  const cropRight = crop?.right ?? 0, cropBottom = crop?.bottom ?? 0;
5389
- const opacity = getSlideBackgroundImageOpacity(slide) ?? 1;
5390
- const intrinsic = layout?.mode === "tile" ? getSlideBackgroundImageIntrinsicSize(slide) : null;
5795
+ const opacity = (slide && getSlideBackgroundImageOpacity(slide)) ?? 1;
5796
+ const intrinsic = layout?.mode === "tile" && slide ? getSlideBackgroundImageIntrinsicSize(slide) : null;
5391
5797
  if (layout?.mode === "tile" && intrinsic) {
5392
5798
  const pattern = imageTilePattern(dataUrl, layout, intrinsic, 0, 0, W, H, cropLeft, cropTop, cropRight, cropBottom);
5393
5799
  bgImage = `${pattern.defs}<rect width="${E(W)}" height="${E(H)}" fill="${pattern.fill}" opacity="${opacity}"/>`;
@@ -5408,10 +5814,11 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5408
5814
  }
5409
5815
  }
5410
5816
  let layoutBgShapes = "";
5411
- const layoutForBg = getSlideLayout(slide);
5412
- if (layoutForBg && !isSlideBackgroundGraphicsHidden(slide)) try {
5413
- const masterShapes = topLevelShapes(isSlideLayoutBackgroundGraphicsHidden(layoutForBg) ? [] : getSlideMasterShapes(pres, layoutForBg), { dropPlaceholders: true });
5414
- const layoutShapes = topLevelShapes(getSlideLayoutShapes(pres, layoutForBg), { dropPlaceholders: true });
5817
+ const layoutForBg = surface.layout;
5818
+ const paintBackground = opts.background ?? true;
5819
+ if (paintBackground && layoutForBg && !(slide && isSlideBackgroundGraphicsHidden(slide))) try {
5820
+ const masterShapes = topLevelShapes(!master && isSlideLayoutBackgroundGraphicsHidden(layoutForBg) ? [] : getSlideMasterShapes(pres, layoutForBg), { dropPlaceholders: true });
5821
+ const layoutShapes = topLevelShapes(master ? [] : getSlideLayoutShapes(pres, layoutForBg), { dropPlaceholders: true });
5415
5822
  const bgCtx = {
5416
5823
  ...ctx,
5417
5824
  ownShapes: false
@@ -5420,12 +5827,12 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
5420
5827
  } catch {
5421
5828
  layoutBgShapes = "";
5422
5829
  }
5423
- const shapesSvg = topLevelShapes(getSlideShapes(slide), { dropPlaceholders: false }).map((s) => renderShape(s, pres, theme, ctx)).join("");
5830
+ const shapesSvg = topLevelShapes(slide ? getSlideShapes(slide) : [], { dropPlaceholders: false }).map((s) => renderShape(s, pres, theme, ctx)).join("");
5424
5831
  const backgroundSvg = `${bgOpacity === void 0 ? "" : `<rect width="${E(W)}" height="${E(H)}" fill="#FFFFFF"/>`}<rect width="${E(W)}" height="${E(H)}" fill="${bgColor}"${bgOpacityAttr}/>${bgGradient}${bgImage}`;
5425
5832
  return [
5426
5833
  `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${E(W)} ${E(H)}" preserveAspectRatio="xMidYMid meet">`,
5427
5834
  bgGradientDefs,
5428
- ctx.background.id ? `<defs><g id="${ctx.background.id}">${backgroundSvg}</g></defs><use href="#${ctx.background.id}" xlink:href="#${ctx.background.id}"/>` : backgroundSvg,
5835
+ ctx.background.id ? `<defs><g id="${ctx.background.id}">${backgroundSvg}</g></defs>${paintBackground ? `<use href="#${ctx.background.id}" xlink:href="#${ctx.background.id}"/>` : ""}` : paintBackground ? backgroundSvg : "",
5429
5836
  layoutBgShapes,
5430
5837
  shapesSvg,
5431
5838
  "</svg>"
@@ -5601,7 +6008,7 @@ const auditTextLayout = (pres, options = {}) => {
5601
6008
  *
5602
6009
  * `1` for a shape with no `<a:normAutofit>`: `<a:noAutofit>` overflows and
5603
6010
  * `<a:spAutoFit>` grows the box instead, so neither shrinks the text. A shape
5604
- * with a baked `fontScale` reports that, unchanged — it is what PowerPoint
6011
+ * with a baked `fontScale` reports that, unchanged — it is what the reference desktop app
5605
6012
  * itself applied.
5606
6013
  */
5607
6014
  function shapeAutoFitScale(pres, shape, options = {}) {
@@ -5634,6 +6041,6 @@ function shapeTextAnchorOffset(pres, shape, options = {}) {
5634
6041
  };
5635
6042
  }
5636
6043
  //#endregion
5637
- export { textColumnsStyle as _, renderTextEffectsSvg as a, shapeTextRect as b, MONO as c, TIMES as d, defaultMeasurer as f, textUnderlineStyle as g, paragraphNumberLabels as h, renderSlideSvg as i, SANS as l, substituteFamily as m, shapeTextAnchorOffset as n, ARIAL as o, isCjk as p, auditTextLayout as r, AVG_GLYPH_W_RATIO as s, shapeAutoFitScale as t, SERIF as u, verticalTextStyle as v, resolveTextBodyRect as y };
6044
+ export { shapeTextRect as S, paragraphNumberLabels as _, renderSlideSvg as a, verticalTextStyle as b, AVG_GLYPH_W_RATIO as c, SERIF as d, TIMES as f, decimalSeparatorOf as g, substituteFamily as h, renderSlideLayoutSvg as i, MONO as l, isCjk as m, shapeTextAnchorOffset as n, renderTextEffectsSvg as o, defaultMeasurer as p, auditTextLayout as r, ARIAL as s, shapeAutoFitScale as t, SANS as u, textUnderlineStyle as v, resolveTextBodyRect as x, textColumnsStyle as y };
5638
6045
 
5639
- //# sourceMappingURL=src-Bwmf324Z.js.map
6046
+ //# sourceMappingURL=src-pb3C61GD.js.map