pptx-kit-preview 0.5.0 → 0.6.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.
@@ -30,7 +30,7 @@ const defaultMeasurer = (text, spec) => {
30
30
  const FALLBACK_ASCENT = .9;
31
31
  const FALLBACK_DESCENT = .22;
32
32
  const FALLBACK_LINEGAP = .08;
33
- const CENTER_ANCHOR_DROP = .036;
33
+ const BASELINE_LEADING_DROP = .036;
34
34
  const GRID_NUDGE_X = -.75;
35
35
  const specOf = (piece) => ({
36
36
  family: piece.family,
@@ -56,7 +56,7 @@ const fmt = (n) => {
56
56
  const r = Math.round(n * 100) / 100;
57
57
  return Object.is(r, -0) ? "0" : String(r);
58
58
  };
59
- const layoutTextSvg = (input, measure) => {
59
+ const layoutCore = (input, measure) => {
60
60
  const widthCache = /* @__PURE__ */ new Map();
61
61
  const metricCache = /* @__PURE__ */ new Map();
62
62
  const key = (text, s) => `${s.family}|${s.sizePx}|${s.bold}|${s.italic}|${s.letterSpacingPx}|${text}`;
@@ -170,11 +170,21 @@ const layoutTextSvg = (input, measure) => {
170
170
  line.textAnchor = "end";
171
171
  line.anchorX = wrapRight;
172
172
  }
173
- if (isFirst && bullet) line.bullet = {
174
- x: firstLeft,
175
- baselineDy: 0,
176
- b: bullet
177
- };
173
+ if (isFirst && bullet) {
174
+ let bulletX = firstLeft;
175
+ if (para.align === "center" || para.align === "right") {
176
+ let end = toks.length;
177
+ while (end > 0 && (toks[end - 1].isSpace || toks[end - 1].isBreak)) end--;
178
+ let lineW = 0;
179
+ for (let ti = 0; ti < end; ti++) if (!toks[ti].isBreak) lineW += toks[ti].width;
180
+ bulletX = (para.align === "right" ? wrapRight - lineW : (lineLeft + wrapRight) / 2 - lineW / 2) - (lineLeft - firstLeft);
181
+ }
182
+ line.bullet = {
183
+ x: bulletX,
184
+ baselineDy: 0,
185
+ b: bullet
186
+ };
187
+ }
178
188
  line.advance = lineAdvance(line, para);
179
189
  cursorY += line.advance;
180
190
  lines.push(line);
@@ -189,7 +199,7 @@ const layoutTextSvg = (input, measure) => {
189
199
  const vert = input.vert ?? "none";
190
200
  const cx = input.boxXpx + input.boxWpx / 2;
191
201
  const cy = input.boxYpx + input.boxHpx / 2;
192
- const frame = vert === "none" ? {
202
+ const frame = vert === "none" || vert === "upright" ? {
193
203
  x: input.boxXpx,
194
204
  y: input.boxYpx,
195
205
  w: input.boxWpx,
@@ -201,25 +211,43 @@ const layoutTextSvg = (input, measure) => {
201
211
  h: input.boxWpx
202
212
  };
203
213
  const columns = vert === "none" ? input.columns ?? null : null;
204
- const body = emitPlacements(columns && columns.count >= 2 ? placeColumns(frame, columns, input.anchor, buildLines) : placeSingle(frame, input.anchor, buildLines));
205
- if (vert === "none") return body;
214
+ const { placements, requiredH } = columns && columns.count >= 2 ? placeColumns(frame, columns, input.anchor, buildLines) : placeSingle(frame, input.anchor, buildLines);
215
+ return {
216
+ placements,
217
+ requiredH,
218
+ vert,
219
+ cx,
220
+ cy
221
+ };
222
+ };
223
+ const layoutTextSvg = (input, measure) => {
224
+ const { placements, vert, cx, cy } = layoutCore(input, measure);
225
+ const body = emitPlacements(placements);
226
+ if (vert === "none" || vert === "upright") return body;
206
227
  return `<g transform="rotate(${vert === "cw90" ? 90 : 270} ${fmt(cx)} ${fmt(cy)})">${body}</g>`;
207
228
  };
229
+ /** Content height (px) the body would occupy at the given input's font sizes —
230
+ * for a single column the block height, for multi-column the tallest filled
231
+ * column. The SVG normAutofit path uses this to pick a shrink scale. */
232
+ const measureTextBodyHeight = (input, measure) => layoutCore(input, measure).requiredH;
208
233
  const anchorOffsetY = (frameY, frameH, blockH, anchor, firstLine) => {
209
234
  let offsetY = frameY;
210
235
  if (anchor === "center") offsetY = frameY + (frameH - blockH) / 2;
211
236
  else if (anchor === "bottom") offsetY = frameY + (frameH - blockH);
212
- if ((anchor === "center" || anchor === "bottom") && firstLine) offsetY += CENTER_ANCHOR_DROP * (firstLine.ascent + firstLine.descent);
237
+ if (firstLine) offsetY += BASELINE_LEADING_DROP * (firstLine.ascent + firstLine.descent);
213
238
  return offsetY;
214
239
  };
215
240
  const placeSingle = (frame, anchor, buildLines) => {
216
241
  const { lines, blockH } = buildLines(frame.x, frame.x + frame.w);
217
242
  const offsetY = anchorOffsetY(frame.y, frame.h, blockH, anchor, lines[0]);
218
- return lines.map((line) => ({
219
- line,
220
- baselineY: offsetY + line.topY + topPad(line) + line.ascent,
221
- dx: 0
222
- }));
243
+ return {
244
+ placements: lines.map((line) => ({
245
+ line,
246
+ baselineY: offsetY + line.topY + topPad(line) + line.ascent,
247
+ dx: 0
248
+ })),
249
+ requiredH: blockH
250
+ };
223
251
  };
224
252
  const placeColumns = (frame, columns, anchor, buildLines) => {
225
253
  const gap = columns.gapPx;
@@ -246,11 +274,14 @@ const placeColumns = (frame, columns, anchor, buildLines) => {
246
274
  if (localTopY + line.advance > tallest) tallest = localTopY + line.advance;
247
275
  }
248
276
  const offsetY = anchorOffsetY(frame.y, frame.h, tallest, anchor, lines[0]);
249
- return placed.map(({ line, localTopY, col: c }) => ({
250
- line,
251
- baselineY: offsetY + localTopY + topPad(line) + line.ascent,
252
- dx: c * (colW + gap)
253
- }));
277
+ return {
278
+ placements: placed.map(({ line, localTopY, col: c }) => ({
279
+ line,
280
+ baselineY: offsetY + localTopY + topPad(line) + line.ascent,
281
+ dx: c * (colW + gap)
282
+ })),
283
+ requiredH: tallest
284
+ };
254
285
  };
255
286
  const emitPlacements = (placements) => {
256
287
  const parts = [];
@@ -348,8 +379,8 @@ const wrapTokens = (tokens, wrap, firstAvail, avail) => {
348
379
  continue;
349
380
  }
350
381
  const limit = first ? firstAvail : avail;
351
- const contentW = lineW - trailingSpaceW;
352
- if (wrap && contentW > 0 && contentW + tok.width > limit + .5) {
382
+ const hasContent = lineW - trailingSpaceW > 0;
383
+ if (wrap && hasContent && lineW + tok.width > limit + .5) {
353
384
  close();
354
385
  cur.push(tok);
355
386
  lineW = tok.width;
@@ -374,6 +405,7 @@ const PX_PER_PT = 96 / 72;
374
405
  const DEFAULT_BODY_PT = 18;
375
406
  const DEFAULT_TITLE_PT = 44;
376
407
  const DEFAULT_FONT = "Calibri, 'Helvetica Neue', Arial, sans-serif";
408
+ const DEFAULT_BULLET_FONT = "Arial";
377
409
  const DEFAULT_INSET_X = 91440;
378
410
  const DEFAULT_INSET_Y = 45720;
379
411
  const u8ToBase64 = (data) => {
@@ -629,15 +661,15 @@ const patternDef = (pat) => {
629
661
  };
630
662
  const pctMatch = /^pct(\d+)$/.exec(preset);
631
663
  if (pctMatch) body = screen(Math.min(100, Math.max(0, Number.parseInt(pctMatch[1], 10))) / 100);
632
- else if (preset === "horzBrick" || preset === "ltHorizontal" || preset === "narHorz") body = stripe("h", .8);
633
- else if (preset === "dkHorizontal") body = stripe("h", 2);
634
- else if (preset === "ltVertical" || preset === "narVert") body = stripe("v", .8);
635
- else if (preset === "dkVertical") body = stripe("v", 2);
664
+ else if (preset === "horz" || preset === "ltHorz" || preset === "narHorz" || preset === "dashHorz" || preset === "horzBrick") body = stripe("h", .8);
665
+ else if (preset === "dkHorz") body = stripe("h", 2);
666
+ else if (preset === "vert" || preset === "ltVert" || preset === "narVert" || preset === "dashVert") body = stripe("v", .8);
667
+ else if (preset === "dkVert") body = stripe("v", 2);
636
668
  else if (preset === "ltUpDiag" || preset === "wdUpDiag") body = stripe("d", .8);
637
669
  else if (preset === "dkUpDiag") body = stripe("d", 2);
638
670
  else if (preset === "ltDnDiag" || preset === "wdDnDiag") body = stripe("a", .8);
639
671
  else if (preset === "dkDnDiag") body = stripe("a", 2);
640
- else if (preset === "ltHorzCross" || preset === "smGrid" || preset === "cross") body = stripe("h", .8) + stripe("v", .8);
672
+ else if (preset === "ltHorzCross" || preset === "smGrid" || preset === "cross" || preset === "dotGrid") body = stripe("h", .8) + stripe("v", .8);
641
673
  else if (preset === "dkHorzCross" || preset === "lgGrid" || preset === "plaid") body = stripe("h", 2) + stripe("v", 2);
642
674
  else if (preset === "diagCross" || preset === "trellis" || preset === "shingle" || preset === "dashUpDiag" || preset === "dashDnDiag") body = stripe("d", .8) + stripe("a", .8);
643
675
  else if (preset === "dkUpDiagStripe" || preset === "dkDnDiagStripe") body = stripe(preset === "dkUpDiagStripe" ? "d" : "a", 2);
@@ -829,42 +861,54 @@ const PRESET_POINTS = {
829
861
  star16: () => star(16),
830
862
  star24: () => star(24),
831
863
  star32: () => star(32),
832
- rightArrow: () => [
833
- [0, .3],
834
- [.65, .3],
835
- [.65, 0],
836
- [1, .5],
837
- [.65, 1],
838
- [.65, .7],
839
- [0, .7]
840
- ],
841
- leftArrow: () => [
842
- [1, .3],
843
- [.35, .3],
844
- [.35, 0],
845
- [0, .5],
846
- [.35, 1],
847
- [.35, .7],
848
- [1, .7]
849
- ],
850
- upArrow: () => [
851
- [.3, 1],
852
- [.3, .35],
853
- [0, .35],
854
- [.5, 0],
855
- [1, .35],
856
- [.7, .35],
857
- [.7, 1]
858
- ],
859
- downArrow: () => [
860
- [.3, 0],
861
- [.3, .65],
862
- [0, .65],
863
- [.5, 1],
864
- [1, .65],
865
- [.7, .65],
866
- [.7, 0]
867
- ],
864
+ rightArrow: (w, h) => {
865
+ const bx = 1 - Math.min(1, .5 * Math.min(w, h) / w);
866
+ return [
867
+ [0, .25],
868
+ [bx, .25],
869
+ [bx, 0],
870
+ [1, .5],
871
+ [bx, 1],
872
+ [bx, .75],
873
+ [0, .75]
874
+ ];
875
+ },
876
+ leftArrow: (w, h) => {
877
+ const bx = Math.min(1, .5 * Math.min(w, h) / w);
878
+ return [
879
+ [1, .25],
880
+ [bx, .25],
881
+ [bx, 0],
882
+ [0, .5],
883
+ [bx, 1],
884
+ [bx, .75],
885
+ [1, .75]
886
+ ];
887
+ },
888
+ upArrow: (w, h) => {
889
+ const by = Math.min(1, .5 * Math.min(w, h) / h);
890
+ return [
891
+ [.25, 1],
892
+ [.25, by],
893
+ [0, by],
894
+ [.5, 0],
895
+ [1, by],
896
+ [.75, by],
897
+ [.75, 1]
898
+ ];
899
+ },
900
+ downArrow: (w, h) => {
901
+ const by = 1 - Math.min(1, .5 * Math.min(w, h) / h);
902
+ return [
903
+ [.25, 0],
904
+ [.25, by],
905
+ [0, by],
906
+ [.5, 1],
907
+ [1, by],
908
+ [.75, by],
909
+ [.75, 0]
910
+ ];
911
+ },
868
912
  leftRightArrow: () => [
869
913
  [0, .5],
870
914
  [.18, .2],
@@ -1766,6 +1810,8 @@ const renderRun = (text, format, theme, effectivePt, _wasDefault = false) => {
1766
1810
  };
1767
1811
  const LINE_HEIGHT = 1.05;
1768
1812
  const AVG_GLYPH_W_RATIO = .55;
1813
+ const AUTOFIT_FLOOR = .25;
1814
+ const AUTOFIT_STEP = .05;
1769
1815
  const hasUnderlineFmt = (fmt) => {
1770
1816
  const u = fmt?.underline;
1771
1817
  return u !== void 0 && u !== false && u !== "none";
@@ -1778,15 +1824,15 @@ const alignOf = (a) => a === "center" || a === "right" || a === "justify" ? a :
1778
1824
  const verticalLayoutOf = (vert) => {
1779
1825
  switch (vert) {
1780
1826
  case "vert":
1781
- case "eaVert":
1827
+ case "eaVert": return "cw90";
1782
1828
  case "wordArtVert":
1783
- case "wordArtVertRtl": return "cw90";
1829
+ case "wordArtVertRtl": return "upright";
1784
1830
  case "vert270":
1785
1831
  case "mongolianVert": return "cw270";
1786
1832
  case null: return "none";
1787
1833
  }
1788
1834
  };
1789
- const buildAndLayoutSvgText = (a) => {
1835
+ const buildSvgTextInput = (a) => {
1790
1836
  const scale = a.autoFitScale;
1791
1837
  const paragraphs = a.paraData.map((para, pi) => {
1792
1838
  const pieces = [];
@@ -1800,7 +1846,7 @@ const buildAndLayoutSvgText = (a) => {
1800
1846
  const hlinkColor = a.theme ? normalizeHex(a.theme.hyperlink) : "#0563C1";
1801
1847
  fmt = {
1802
1848
  ...fmt,
1803
- color: fmt?.color ?? hlinkColor,
1849
+ color: hlinkColor,
1804
1850
  underline: fmt?.underline ?? true
1805
1851
  };
1806
1852
  }
@@ -1824,9 +1870,19 @@ const buildAndLayoutSvgText = (a) => {
1824
1870
  };
1825
1871
  const segs = run.text.split("\n");
1826
1872
  segs.forEach((seg, i) => {
1827
- pieces.push({
1873
+ const segText = caps ? seg.toUpperCase() : seg;
1874
+ if (a.vert === "upright") for (const g of Array.from(segText)) {
1875
+ const last = pieces[pieces.length - 1];
1876
+ if (last !== void 0 && !last.isBreak) pieces.push(breakPiece());
1877
+ pieces.push({
1878
+ ...base,
1879
+ text: g,
1880
+ isBreak: false
1881
+ });
1882
+ }
1883
+ else pieces.push({
1828
1884
  ...base,
1829
- text: caps ? seg.toUpperCase() : seg,
1885
+ text: segText,
1830
1886
  isBreak: false
1831
1887
  });
1832
1888
  if (i < segs.length - 1) pieces.push(breakPiece());
@@ -1858,7 +1914,7 @@ const buildAndLayoutSvgText = (a) => {
1858
1914
  fallbackSizePx: a.defaultPt * scale * PX_PER_PT
1859
1915
  };
1860
1916
  });
1861
- return layoutTextSvg({
1917
+ return {
1862
1918
  boxXpx: a.innerX / EMU_PER_PX,
1863
1919
  boxYpx: a.innerY / EMU_PER_PX,
1864
1920
  boxWpx: a.innerW / EMU_PER_PX,
@@ -1868,8 +1924,9 @@ const buildAndLayoutSvgText = (a) => {
1868
1924
  paragraphs,
1869
1925
  vert: a.vert,
1870
1926
  columns: a.columns
1871
- }, a.measure);
1927
+ };
1872
1928
  };
1929
+ const buildAndLayoutSvgText = (a) => layoutTextSvg(buildSvgTextInput(a), a.measure);
1873
1930
  const breakPiece = () => ({
1874
1931
  text: "",
1875
1932
  family: "",
@@ -1889,17 +1946,52 @@ const buildBullet = (a, para, pi) => {
1889
1946
  const numberLabel = a.numberLabels[pi] ?? null;
1890
1947
  if (!(para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0)) return null;
1891
1948
  const char = para.bulletIsPicture ? "■" : numberLabel ?? explicitChar ?? bulletChar(para.level);
1892
- const baseSizePx = a.defaultPt * PX_PER_PT * a.autoFitScale;
1949
+ const baseSizePx = (para.runs.find((r) => r.text !== "\n" && r.text !== "")?.sizePt ?? a.defaultPt) * PX_PER_PT * a.autoFitScale;
1893
1950
  const sizePx = para.bulletDetail.sizePct !== null ? baseSizePx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * a.autoFitScale : baseSizePx;
1894
1951
  const fillHex = para.bulletDetail.color ? resolveColor(para.bulletDetail.color, a.theme, "#000000") : a.defaultColor;
1895
1952
  return {
1896
1953
  text: char,
1897
- family: substituteFamily(para.bulletDetail.font ?? a.themeFace),
1954
+ family: substituteFamily(para.bulletDetail.font ?? DEFAULT_BULLET_FONT),
1898
1955
  sizePx,
1899
1956
  fillHex,
1900
1957
  ...para.bulletImageHref ? { imageHref: para.bulletImageHref } : {}
1901
1958
  };
1902
1959
  };
1960
+ const presetTextRect = (preset) => {
1961
+ switch (preset) {
1962
+ case "triangle": return {
1963
+ l: .25,
1964
+ t: .5,
1965
+ r: .75,
1966
+ b: 1
1967
+ };
1968
+ case "diamond": return {
1969
+ l: .25,
1970
+ t: .25,
1971
+ r: .75,
1972
+ b: .75
1973
+ };
1974
+ case "pentagon": return {
1975
+ l: .191,
1976
+ t: .236,
1977
+ r: .809,
1978
+ b: 1
1979
+ };
1980
+ case "star5": return {
1981
+ l: .309,
1982
+ t: .382,
1983
+ r: .691,
1984
+ b: .764
1985
+ };
1986
+ case "leftRightArrow": return {
1987
+ l: .18,
1988
+ t: .35,
1989
+ r: .82,
1990
+ b: .65
1991
+ };
1992
+ default: return null;
1993
+ }
1994
+ };
1903
1995
  const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
1904
1996
  let paragraphCount;
1905
1997
  try {
@@ -1937,11 +2029,33 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
1937
2029
  const tIns = margins.top ?? DEFAULT_INSET_Y;
1938
2030
  const rIns = margins.right ?? DEFAULT_INSET_X;
1939
2031
  const bIns = margins.bottom ?? DEFAULT_INSET_Y;
1940
- const innerX = bounds.x + lIns;
1941
- const innerY = bounds.y + tIns;
1942
- const innerW = Math.max(0, bounds.w - lIns - rIns);
1943
- const innerH = Math.max(0, bounds.h - tIns - bIns);
2032
+ const pRect = presetTextRect(getShapePreset(shape));
2033
+ const rectX = pRect ? bounds.x + pRect.l * bounds.w : bounds.x;
2034
+ const rectY = pRect ? bounds.y + pRect.t * bounds.h : bounds.y;
2035
+ const rectW = pRect ? (pRect.r - pRect.l) * bounds.w : bounds.w;
2036
+ const rectH = pRect ? (pRect.b - pRect.t) * bounds.h : bounds.h;
2037
+ let innerX = rectX + lIns;
2038
+ let innerY = rectY + tIns;
2039
+ let innerW = rectW - lIns - rIns;
2040
+ let innerH = rectH - tIns - bIns;
2041
+ if (pRect && (innerW <= 0 || innerH <= 0)) {
2042
+ innerX = rectX;
2043
+ innerY = rectY;
2044
+ innerW = rectW;
2045
+ innerH = rectH;
2046
+ }
1944
2047
  if (innerW <= 0 || innerH <= 0) return "";
2048
+ const svgTextRect = (v) => v === "none" || v === "upright" ? {
2049
+ x: innerX,
2050
+ y: innerY,
2051
+ w: innerW,
2052
+ h: innerH
2053
+ } : {
2054
+ x: bounds.x + tIns,
2055
+ y: bounds.y + lIns,
2056
+ w: Math.max(0, bounds.w - tIns - bIns),
2057
+ h: Math.max(0, bounds.h - lIns - rIns)
2058
+ };
1945
2059
  const paraData = [];
1946
2060
  let hasAnyText = false;
1947
2061
  for (let p = 0; p < paragraphCount; p++) {
@@ -2121,6 +2235,45 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2121
2235
  numberLabels[i] = formatAutoNum(num, counter);
2122
2236
  }
2123
2237
  }
2238
+ if (authoredAutofit && autoFitScale === 1) {
2239
+ const fitVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
2240
+ const fitCols = getShapeTextColumns(shape);
2241
+ const fitColumns = fitVert === "none" && fitCols && fitCols.count >= 2 ? {
2242
+ count: fitCols.count,
2243
+ gapPx: fitCols.gapEmu !== void 0 ? fitCols.gapEmu / EMU_PER_PX : 12
2244
+ } : null;
2245
+ const fitRect = svgTextRect(fitVert);
2246
+ const fitBoxPx = (fitVert === "cw90" || fitVert === "cw270" ? fitRect.w : fitRect.h) / EMU_PER_PX;
2247
+ const fitArgsBase = {
2248
+ pres,
2249
+ shape,
2250
+ theme,
2251
+ paraData,
2252
+ numberLabels,
2253
+ lineHeightScale,
2254
+ defaultPt,
2255
+ themeFace,
2256
+ defaultColor: activeDeckTextColor,
2257
+ anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
2258
+ wrap: effectiveBody.wrap !== "none",
2259
+ innerX: fitRect.x,
2260
+ innerY: fitRect.y,
2261
+ innerW: fitRect.w,
2262
+ innerH: fitRect.h,
2263
+ measure: ctx.measure,
2264
+ vert: fitVert,
2265
+ columns: fitColumns
2266
+ };
2267
+ let s = 1;
2268
+ while (s > AUTOFIT_FLOOR) {
2269
+ if (measureTextBodyHeight(buildSvgTextInput({
2270
+ ...fitArgsBase,
2271
+ autoFitScale: s
2272
+ }), ctx.measure) <= fitBoxPx) break;
2273
+ s -= AUTOFIT_STEP;
2274
+ }
2275
+ autoFitScale = Math.max(AUTOFIT_FLOOR, s);
2276
+ }
2124
2277
  const paragraphs = [];
2125
2278
  for (let pi = 0; pi < paraData.length; pi++) {
2126
2279
  const para = paraData[pi];
@@ -2130,7 +2283,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2130
2283
  const hlinkColor = theme ? normalizeHex(theme.hyperlink) : "#0563C1";
2131
2284
  runFmt = {
2132
2285
  ...runFmt,
2133
- color: runFmt?.color ?? hlinkColor,
2286
+ color: hlinkColor,
2134
2287
  underline: runFmt?.underline ?? true
2135
2288
  };
2136
2289
  }
@@ -2162,8 +2315,8 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2162
2315
  const explicitChar = para.bulletStyle !== null && typeof para.bulletStyle === "object" && "char" in para.bulletStyle ? para.bulletStyle.char : null;
2163
2316
  const numberLabel = numberLabels[pi];
2164
2317
  const showBullet = para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0;
2318
+ const baseBulletPx = (para.runs.find((r) => r.text !== "\n" && r.text !== "")?.sizePt ?? defaultPt) * PX_PER_PT * autoFitScale;
2165
2319
  if (showBullet && para.bulletImageHref) {
2166
- const baseBulletPx = defaultPt * PX_PER_PT * autoFitScale;
2167
2320
  const bulletPx = para.bulletDetail.sizePct !== null ? baseBulletPx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * autoFitScale : baseBulletPx;
2168
2321
  const imgStyles = [
2169
2322
  `width:${bulletPx.toFixed(2)}px`,
@@ -2179,7 +2332,8 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2179
2332
  if (para.bulletDetail.color) bulletStyles.push(`color:${resolveColor(para.bulletDetail.color, theme, "#000000")}`);
2180
2333
  if (para.bulletDetail.sizePct !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePct * 100).toFixed(1)}%`);
2181
2334
  else if (para.bulletDetail.sizePts !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePts * PX_PER_PT * autoFitScale).toFixed(2)}px`);
2182
- if (para.bulletDetail.font) bulletStyles.push(`font-family:${escapeXml(para.bulletDetail.font)}, ${DEFAULT_FONT}`);
2335
+ else bulletStyles.push(`font-size:${baseBulletPx.toFixed(2)}px`);
2336
+ bulletStyles.push(para.bulletDetail.font ? `font-family:${escapeXml(para.bulletDetail.font)}, ${DEFAULT_FONT}` : `font-family:${DEFAULT_BULLET_FONT}, ${DEFAULT_FONT}`);
2183
2337
  prefix = `<span style="${bulletStyles.join(";")}">${escapeXml(char)}</span>`;
2184
2338
  }
2185
2339
  paragraphs.push(`<p style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "&#8203;"}</p>`);
@@ -2187,7 +2341,6 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2187
2341
  const justify = ANCHOR_TO_CSS[anchor] ?? "flex-start";
2188
2342
  const defaultColor = activeDeckTextColor;
2189
2343
  if (ctx.mode === "svg") {
2190
- const svgScale = authoredAutofit?.fontScale ?? 1;
2191
2344
  const svgLineScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
2192
2345
  const svgVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
2193
2346
  const svgCols = getShapeTextColumns(shape);
@@ -2195,31 +2348,37 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2195
2348
  count: svgCols.count,
2196
2349
  gapPx: svgCols.gapEmu !== void 0 ? svgCols.gapEmu / EMU_PER_PX : 12
2197
2350
  } : null;
2198
- const svgInner = buildAndLayoutSvgText({
2351
+ const { x: vInnerX, y: vInnerY, w: vInnerW, h: vInnerH } = svgTextRect(svgVert);
2352
+ if (vInnerW <= 0 || vInnerH <= 0) return "";
2353
+ const svgArgsBase = {
2199
2354
  pres,
2200
2355
  shape,
2201
2356
  theme,
2202
2357
  paraData,
2203
2358
  numberLabels,
2204
- autoFitScale: svgScale,
2205
2359
  lineHeightScale: svgLineScale,
2206
2360
  defaultPt,
2207
2361
  themeFace,
2208
2362
  defaultColor,
2209
2363
  anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
2210
2364
  wrap: effectiveBody.wrap !== "none",
2211
- innerX,
2212
- innerY,
2213
- innerW,
2214
- innerH,
2365
+ innerX: vInnerX,
2366
+ innerY: vInnerY,
2367
+ innerW: vInnerW,
2368
+ innerH: vInnerH,
2215
2369
  measure: ctx.measure,
2216
2370
  vert: svgVert,
2217
2371
  columns: svgColumns
2372
+ };
2373
+ const svgScale = authoredAutofit ? autoFitScale : 1;
2374
+ const svgInner = buildAndLayoutSvgText({
2375
+ ...svgArgsBase,
2376
+ autoFitScale: svgScale
2218
2377
  });
2219
2378
  const bodyRotDegSvg = getShapeTextBodyRotationDeg(shape);
2220
2379
  if (bodyRotDegSvg !== null && bodyRotDegSvg !== 0) {
2221
- const pivotX = innerX + innerW / 2;
2222
- const pivotY = innerY + innerH / 2;
2380
+ const pivotX = vInnerX + vInnerW / 2;
2381
+ const pivotY = vInnerY + vInnerH / 2;
2223
2382
  return `<g transform="rotate(${bodyRotDegSvg} ${E(pivotX)} ${E(pivotY)})">${svgInner}</g>`;
2224
2383
  }
2225
2384
  return svgInner;
@@ -2272,15 +2431,16 @@ const accentSequence = (theme) => {
2272
2431
  ].map((c) => normalizeHex(c)).filter((c) => /^#[0-9A-Fa-f]{6}$/.test(c));
2273
2432
  return hexes.length > 0 ? hexes : fallbacks;
2274
2433
  };
2275
- const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true) => {
2434
+ const DEFAULT_CHART_TITLE_PT = 13;
2435
+ const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true, titlePx = DEFAULT_CHART_TITLE_PT * PX_PER_PT) => {
2276
2436
  const x = xEmu / EMU_PER_PX;
2277
2437
  const y = yEmu / EMU_PER_PX;
2278
2438
  const w = wEmu / EMU_PER_PX;
2279
2439
  const h = hEmu / EMU_PER_PX;
2280
- const titleStrip = hasTitle && !titleOverlay ? 18 : 0;
2440
+ const titleStrip = hasTitle && !titleOverlay ? Math.round(titlePx * 2.4) : 0;
2281
2441
  const legendStrip = hasLegend && !legendOverlay ? 18 : 0;
2282
2442
  const padding = 8;
2283
- const yAxisGutter = hasAxes ? 40 : 0;
2443
+ const yAxisGutter = hasAxes ? 32 : 0;
2284
2444
  const xAxisGutter = hasAxes ? 18 : 0;
2285
2445
  return {
2286
2446
  x,
@@ -2291,7 +2451,7 @@ const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = f
2291
2451
  plotY: y + titleStrip + padding,
2292
2452
  plotW: Math.max(0, w - 2 * padding - yAxisGutter),
2293
2453
  plotH: Math.max(0, h - titleStrip - legendStrip - xAxisGutter - 2 * padding),
2294
- titleY: y + (titleOverlay ? 14 : titleStrip - 2),
2454
+ titleY: y + (titleOverlay ? 14 : padding + titlePx * .7),
2295
2455
  legendY: y + h - (legendOverlay ? 18 : legendStrip / 2)
2296
2456
  };
2297
2457
  };
@@ -2379,12 +2539,13 @@ const DISPLAY_UNIT_LABEL = {
2379
2539
  const DEFAULT_AXIS_COLOR = "#000000";
2380
2540
  const DEFAULT_GRID_COLOR = "#D9D9D9";
2381
2541
  const AXIS_TICK_LEN = 5;
2542
+ const chartFontPx = (sizePt) => (sizePt * PX_PER_PT).toFixed(1);
2382
2543
  const axisTickAttrs = (style) => {
2383
2544
  const sz = style?.sizePt ?? 10;
2384
2545
  const fill = style?.color ?? "#6B7280";
2385
2546
  const weight = style?.bold ? " font-weight=\"600\"" : "";
2386
2547
  const italic = style?.italic ? " font-style=\"italic\"" : "";
2387
- return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
2548
+ return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}"${weight}${italic}`;
2388
2549
  };
2389
2550
  const renderValueAxis = (f, axis) => {
2390
2551
  const ticks = axis.majorUnit ? (() => {
@@ -2537,11 +2698,11 @@ const seriesMinMax = (spec) => {
2537
2698
  };
2538
2699
  const renderChartTitle = (f, title, style) => {
2539
2700
  if (!title) return "";
2540
- const sz = style?.sizePt ?? 13;
2701
+ const sz = style?.sizePt ?? DEFAULT_CHART_TITLE_PT;
2541
2702
  const fill = style?.color ?? "#1F2937";
2542
2703
  const weight = style?.bold === false ? "400" : "600";
2543
2704
  const fontStyleAttr = style?.italic ? " font-style=\"italic\"" : "";
2544
- return `<text x="${px(f.x + f.w / 2)}" y="${px(f.titleY)}" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}" font-weight="${weight}"${fontStyleAttr}>${escapeXml(title)}</text>`;
2705
+ return `<text x="${px(f.x + f.w / 2)}" y="${px(f.titleY)}" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${fontStyleAttr}>${escapeXml(title)}</text>`;
2545
2706
  };
2546
2707
  const renderChartLegend = (f, names, colors, position = "b", textStyle, markerSymbols) => {
2547
2708
  if (names.length === 0) return "";
@@ -2549,7 +2710,7 @@ const renderChartLegend = (f, names, colors, position = "b", textStyle, markerSy
2549
2710
  const fill = textStyle?.color ?? "#374151";
2550
2711
  const weight = textStyle?.bold ? " font-weight=\"600\"" : "";
2551
2712
  const italic = textStyle?.italic ? " font-style=\"italic\"" : "";
2552
- const textAttrs = `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
2713
+ const textAttrs = `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}"${weight}${italic}`;
2553
2714
  const swatch = (i, swatchX, swatchY) => {
2554
2715
  const color = colors[i % colors.length];
2555
2716
  const sym = markerSymbols?.[i];
@@ -2836,7 +2997,7 @@ const dataLabelTextAttrs = (spec, seriesIdx, fallbackFill, fallbackSizePt = 9, f
2836
2997
  const fill = style?.color ?? fallbackFill;
2837
2998
  const weight = style?.bold ?? fallbackBold ? " font-weight=\"600\"" : "";
2838
2999
  const italic = style?.italic ? " font-style=\"italic\"" : "";
2839
- return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
3000
+ return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}"${weight}${italic}`;
2840
3001
  };
2841
3002
  const renderBarChart = (f, spec, colors) => {
2842
3003
  const N = pointCount(spec);
@@ -2930,7 +3091,8 @@ const renderLineChart = (f, spec, colors, fill) => {
2930
3091
  const isPercent = grouping === "percentStacked";
2931
3092
  const { min, max } = seriesMinMax(spec);
2932
3093
  const range = max - min || 1;
2933
- const step = N > 1 ? f.plotW / (N - 1) : 0;
3094
+ const band = N > 1 ? fill ? f.plotW / (N - 1) : f.plotW / N : 0;
3095
+ const xAt = (c) => fill ? f.plotX + c * band : f.plotX + (c + .5) * band;
2934
3096
  const baseY = f.plotY + f.plotH - (0 - min) / range * f.plotH;
2935
3097
  const out = [];
2936
3098
  out.push(`<line x1="${px(f.plotX)}" y1="${px(baseY)}" x2="${px(f.plotX + f.plotW)}" y2="${px(baseY)}" stroke="#E5E7EB" stroke-width="0.5"/>`);
@@ -2943,7 +3105,7 @@ const renderLineChart = (f, spec, colors, fill) => {
2943
3105
  const ptsRaw = [];
2944
3106
  const basePtsRaw = [];
2945
3107
  for (let c = 0; c < N; c++) {
2946
- const xp = f.plotX + c * step;
3108
+ const xp = xAt(c);
2947
3109
  const rawV = series.values[c];
2948
3110
  const isNullish = rawV === null || rawV === void 0 || !Number.isFinite(rawV);
2949
3111
  let v;
@@ -3058,7 +3220,7 @@ const renderLineChart = (f, spec, colors, fill) => {
3058
3220
  }
3059
3221
  }
3060
3222
  if (!isStacked && (spec.dropLines || spec.hiLowLines) && spec.series.length > 0) for (let c = 0; c < N; c++) {
3061
- const xp = f.plotX + c * step;
3223
+ const xp = xAt(c);
3062
3224
  if (spec.dropLines) {
3063
3225
  const firstVal = spec.series[0]?.values[c];
3064
3226
  if (firstVal !== null && firstVal !== void 0 && Number.isFinite(firstVal)) {
@@ -3363,7 +3525,7 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
3363
3525
  const isCartesian = spec.kind === "column" || spec.kind === "bar" || spec.kind === "line" || spec.kind === "area";
3364
3526
  const hasAxes = isCartesian || spec.kind === "scatter" || spec.kind === "bubble";
3365
3527
  const hasLegend = spec.legend !== void 0 && spec.legend.position !== null;
3366
- const f = layoutChart(x, y, w, h, !!spec.title, hasAxes, spec.titleOverlay ?? false, spec.legend?.overlay ?? false, hasLegend);
3528
+ const f = layoutChart(x, y, w, h, !!spec.title, hasAxes, spec.titleOverlay ?? false, spec.legend?.overlay ?? false, hasLegend, (spec.titleStyle?.sizePt ?? DEFAULT_CHART_TITLE_PT) * PX_PER_PT);
3367
3529
  const allNamesForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? Array.from(spec.categories) : spec.series.map((s) => s.name);
3368
3530
  const allColorsForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? spec.categories.map((_, i) => spec.series[0]?.pointColors?.[i] ?? spec.series[0]?.color ?? colors[i % colors.length] ?? "#888") : spec.series.map((s, i) => s.color ?? colors[i % colors.length] ?? "#888");
3369
3531
  const hiddenSet = new Set(spec.legend?.hiddenIndices ?? []);
@@ -3443,7 +3605,7 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
3443
3605
  const fill = style?.color ?? "#374151";
3444
3606
  const weight = style?.bold === false ? "400" : "600";
3445
3607
  const italicAttr = style?.italic ? " font-style=\"italic\"" : "";
3446
- return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}" font-weight="${weight}"${italicAttr}`;
3608
+ return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${italicAttr}`;
3447
3609
  };
3448
3610
  const valueAxisTitleRot = spec.valueAxisTitleRotationDeg ?? -90;
3449
3611
  const valueAxisTitleSvg = spec.valueAxisTitle ? `<text x="${px(f.plotX - 26)}" y="${px(f.plotY + f.plotH / 2)}" text-anchor="middle" ${axisTitleAttrs(spec.valueAxisTitleStyle)} transform="rotate(${valueAxisTitleRot} ${px(f.plotX - 26)} ${px(f.plotY + f.plotH / 2)})">${escapeXml(spec.valueAxisTitle)}</text>` : "";
@@ -3821,8 +3983,10 @@ const renderShape = (shape, pres, theme, ctx) => {
3821
3983
  const strokeColor = p.stroke === "none" ? resolveColor("scheme:tx1", theme, "#1F2937") : p.stroke;
3822
3984
  const sa = p.strokeAttrs ? ` ${p.strokeAttrs}` : "";
3823
3985
  const ma = p.markerAttrs ?? "";
3986
+ const capDefault = sa.includes("stroke-linecap") ? "" : " stroke-linecap=\"round\"";
3987
+ const joinDefault = sa.includes("stroke-linejoin") ? "" : " stroke-linejoin=\"round\"";
3824
3988
  const preset = getShapePreset(shape) ?? "line";
3825
- if (preset === "straightConnector1" || preset === "line") return `${p.defs}<line x1="${E(x1)}" y1="${E(y1)}" x2="${E(x2)}" y2="${E(y2)}" stroke="${strokeColor}" stroke-width="${E(sw)}" stroke-linecap="round"${sa}${ma}${transform}/>`;
3989
+ if (preset === "straightConnector1" || preset === "line") return `${p.defs}<line x1="${E(x1)}" y1="${E(y1)}" x2="${E(x2)}" y2="${E(y2)}" stroke="${strokeColor}" stroke-width="${E(sw)}"${capDefault}${sa}${ma}${transform}/>`;
3826
3990
  const px1 = x1 / EMU_PER_PX;
3827
3991
  const py1 = y1 / EMU_PER_PX;
3828
3992
  const px2 = x2 / EMU_PER_PX;
@@ -3847,7 +4011,7 @@ const renderShape = (shape, pres, theme, ctx) => {
3847
4011
  const c2x = px1 + 2 * (px2 - px1) / 3;
3848
4012
  d += ` C${c1x.toFixed(2)} ${py1.toFixed(2)} ${c2x.toFixed(2)} ${py2.toFixed(2)} ${px2.toFixed(2)} ${py2.toFixed(2)}`;
3849
4013
  } else d += ` L${px2.toFixed(2)} ${py2.toFixed(2)}`;
3850
- return `${p.defs}<path d="${d}" fill="none" stroke="${strokeColor}" stroke-width="${E(sw)}" stroke-linecap="round" stroke-linejoin="round"${sa}${ma}${transform}/>`;
4014
+ return `${p.defs}<path d="${d}" fill="none" stroke="${strokeColor}" stroke-width="${E(sw)}"${capDefault}${joinDefault}${sa}${ma}${transform}/>`;
3851
4015
  }
3852
4016
  if (kind === "group") {
3853
4017
  const xform = getGroupTransform(shape);
@@ -3923,7 +4087,7 @@ const renderShape = (shape, pres, theme, ctx) => {
3923
4087
  if (pathFn) geomSvg = `<path d="${pathFn(x / EMU_PER_PX, y / EMU_PER_PX, w / EMU_PER_PX, h / EMU_PER_PX)}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}" fill-rule="evenodd"${sa}${ma}/>`;
3924
4088
  else {
3925
4089
  const pointsFn = PRESET_POINTS[preset];
3926
- if (pointsFn) geomSvg = `<polygon points="${pointsFn().map(([nx, ny]) => `${E(x + nx * w)},${E(y + ny * h)}`).join(" ")}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma}/>`;
4090
+ if (pointsFn) geomSvg = `<polygon points="${pointsFn(w / EMU_PER_PX, h / EMU_PER_PX).map(([nx, ny]) => `${E(x + nx * w)},${E(y + ny * h)}`).join(" ")}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma}/>`;
3927
4091
  else geomSvg = `<rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma} data-pptx-preset="${escapeXml(preset)}"><title>${escapeXml(`preset: ${preset}`)}</title></rect>`;
3928
4092
  }
3929
4093
  }
@@ -4040,11 +4204,13 @@ const buildEffectsFilter = (pres, shape) => {
4040
4204
  primitives.push(`<feGaussianBlur in="SourceAlpha" stdDeviation="${blurPx.toFixed(2)}" result="innerBlur${i}"/>`, `<feOffset in="innerBlur${i}" dx="${dx.toFixed(2)}" dy="${dy.toFixed(2)}" result="innerOff${i}"/>`, `<feComposite in="innerOff${i}" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="innerMask${i}"/>`, `<feFlood flood-color="${color}" flood-opacity="${opacity.toFixed(3)}" result="innerCol${i}"/>`, `<feComposite in="innerCol${i}" in2="innerMask${i}" operator="in" result="innerOut${i}"/>`);
4041
4205
  layers.push(`innerOut${i}`);
4042
4206
  } else if (e.kind === "glow") {
4043
- const blurPx = e.radiusEmu / EMU_PER_PX / 2;
4207
+ const radiusPx = e.radiusEmu / EMU_PER_PX;
4208
+ const dilatePx = radiusPx * .5;
4209
+ const featherPx = radiusPx * .3;
4044
4210
  const color = e.color || "#FFFFFF";
4045
4211
  const opacity = e.opacity ?? 1;
4046
4212
  const i = primitives.length;
4047
- primitives.push(`<feMorphology in="SourceAlpha" operator="dilate" radius="${(blurPx / 4).toFixed(2)}" result="glowExp${i}"/>`, `<feGaussianBlur in="glowExp${i}" stdDeviation="${blurPx.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}"/>`);
4213
+ 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}"/>`);
4048
4214
  layers.push(`glowOut${i}`);
4049
4215
  } else if (e.kind === "softEdge") {
4050
4216
  const blurPx = e.radiusEmu / EMU_PER_PX / 2;
@@ -4174,4 +4340,4 @@ const detectImageFormatLocal = (bytes) => {
4174
4340
  //#endregion
4175
4341
  export { SERIF as a, substituteFamily as c, SANS as i, ARIAL as n, TIMES as o, MONO as r, defaultMeasurer as s, renderSlideSvg as t };
4176
4342
 
4177
- //# sourceMappingURL=src-CDTTqUfI.js.map
4343
+ //# sourceMappingURL=src-DDyW3xOV.js.map