pptx-kit-preview 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4177 @@
1
+ import { getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFonts, getPresentationTheme, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlink, getShapeHyperlinkTooltip, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFormat, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeTextAnchor, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getTableCellAnchor, getTableCellBorders, getTableCellFill, getTableCellMargins, getTableCellParagraphs, getTableCellSpan, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, getTableStyleFlags, isChartShape, isParagraphBulletPicture, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isTableShape, resolveDeckBodyTextColor } from "pptx-kit";
2
+ //#region src/text-layout.ts
3
+ const SANS = "Carlito";
4
+ const SERIF = "Caladea";
5
+ const ARIAL = "Liberation Sans";
6
+ const TIMES = "Liberation Serif";
7
+ const MONO = "Liberation Mono";
8
+ const substituteFamily = (family) => {
9
+ if (!family) return SANS;
10
+ const f = family.trim().toLowerCase();
11
+ if (f.startsWith("calibri") || f.startsWith("aptos")) return SANS;
12
+ if (f.startsWith("cambria")) return SERIF;
13
+ if (f === "arial" || f === "helvetica" || f === "helvetica neue" || f.startsWith("arial ")) return ARIAL;
14
+ if (f === "times new roman" || f === "times" || f.startsWith("times ")) return TIMES;
15
+ if (f === "courier new" || f === "courier" || f === "consolas" || f.startsWith("courier ")) return MONO;
16
+ return SANS;
17
+ };
18
+ const isCjk = (cp) => cp >= 12352 && cp <= 12447 || cp >= 12448 && cp <= 12543 || cp >= 19968 && cp <= 40959 || cp >= 44032 && cp <= 55215;
19
+ const AVG_GLYPH_W_RATIO$1 = .55;
20
+ const defaultMeasurer = (text, spec) => {
21
+ let w = 0;
22
+ for (const ch of text) {
23
+ const ratio = isCjk(ch.codePointAt(0) ?? 0) ? 1 : AVG_GLYPH_W_RATIO$1;
24
+ w += spec.sizePx * ratio;
25
+ }
26
+ const n = [...text].length;
27
+ if (n > 1) w += spec.letterSpacingPx * (n - 1);
28
+ return { widthPx: w };
29
+ };
30
+ const FALLBACK_ASCENT = .9;
31
+ const FALLBACK_DESCENT = .22;
32
+ const FALLBACK_LINEGAP = .08;
33
+ const CENTER_ANCHOR_DROP = .036;
34
+ const GRID_NUDGE_X = -.75;
35
+ const specOf = (piece) => ({
36
+ family: piece.family,
37
+ sizePx: piece.sizePx,
38
+ bold: piece.bold,
39
+ italic: piece.italic,
40
+ letterSpacingPx: piece.letterSpacingPx
41
+ });
42
+ const bulletSpec = (b) => ({
43
+ family: b.family,
44
+ sizePx: b.sizePx,
45
+ bold: false,
46
+ italic: false,
47
+ letterSpacingPx: 0
48
+ });
49
+ const escapeXml$1 = (s) => s.replace(/[&<>"]/g, (c) => ({
50
+ "&": "&amp;",
51
+ "<": "&lt;",
52
+ ">": "&gt;",
53
+ "\"": "&quot;"
54
+ })[c] ?? c);
55
+ const fmt = (n) => {
56
+ const r = Math.round(n * 100) / 100;
57
+ return Object.is(r, -0) ? "0" : String(r);
58
+ };
59
+ const layoutTextSvg = (input, measure) => {
60
+ const widthCache = /* @__PURE__ */ new Map();
61
+ const metricCache = /* @__PURE__ */ new Map();
62
+ const key = (text, s) => `${s.family}|${s.sizePx}|${s.bold}|${s.italic}|${s.letterSpacingPx}|${text}`;
63
+ const mWidth = (text, s) => {
64
+ const k = key(text, s);
65
+ let w = widthCache.get(k);
66
+ if (w === void 0) {
67
+ w = measure(text, s).widthPx;
68
+ widthCache.set(k, w);
69
+ }
70
+ return w;
71
+ };
72
+ const mMetrics = (piece) => {
73
+ const s = specOf(piece);
74
+ const k = `${s.family}|${s.sizePx}|${s.bold}|${s.italic}`;
75
+ let m = metricCache.get(k);
76
+ if (!m) {
77
+ const r = measure("Mg", s);
78
+ m = r.ascentPx !== void 0 && r.descentPx !== void 0 ? {
79
+ a: r.ascentPx,
80
+ d: r.descentPx,
81
+ g: r.lineGapPx ?? 0
82
+ } : {
83
+ a: piece.sizePx * FALLBACK_ASCENT,
84
+ d: piece.sizePx * FALLBACK_DESCENT,
85
+ g: piece.sizePx * FALLBACK_LINEGAP
86
+ };
87
+ metricCache.set(k, m);
88
+ }
89
+ return m;
90
+ };
91
+ const buildLines = (contentLeft, contentRight) => {
92
+ const lines = [];
93
+ let cursorY = 0;
94
+ for (const para of input.paragraphs) {
95
+ cursorY += para.spcBefPx;
96
+ const wrapLeft = contentLeft + para.marLpx;
97
+ const wrapRight = contentRight - para.marRpx;
98
+ const firstLeft = wrapLeft + para.firstIndentPx;
99
+ const hasText = para.pieces.some((p) => !p.isBreak && p.text !== "");
100
+ const bullet = para.bullet && hasText ? para.bullet : null;
101
+ const bulletLead = bullet ? bullet.imageHref ? bullet.sizePx + mWidth(" ", bulletSpec(bullet)) : mWidth(`${bullet.text} `, bulletSpec(bullet)) : 0;
102
+ const avail = Math.max(1, wrapRight - wrapLeft);
103
+ const tokens = [];
104
+ for (const piece of para.pieces) {
105
+ if (piece.isBreak) {
106
+ tokens.push({
107
+ text: "",
108
+ piece,
109
+ isSpace: false,
110
+ isBreak: true,
111
+ width: 0
112
+ });
113
+ continue;
114
+ }
115
+ for (const seg of piece.text.match(/\s+|\S+/g) ?? []) {
116
+ const isSpace = /^\s+$/.test(seg);
117
+ const w = mWidth(seg, specOf(piece));
118
+ if (input.wrap && !isSpace && w > avail - bulletLead && [...seg].length > 1) for (const ch of seg) tokens.push({
119
+ text: ch,
120
+ piece,
121
+ isSpace: false,
122
+ isBreak: false,
123
+ width: mWidth(ch, specOf(piece))
124
+ });
125
+ else tokens.push({
126
+ text: seg,
127
+ piece,
128
+ isSpace,
129
+ isBreak: false,
130
+ width: w
131
+ });
132
+ }
133
+ }
134
+ const wrapped = wrapTokens(tokens, input.wrap, wrapRight - firstLeft - bulletLead, avail);
135
+ const paraLines = wrapped.length > 0 ? wrapped : [[]];
136
+ for (let li = 0; li < paraLines.length; li++) {
137
+ const toks = paraLines[li];
138
+ let ascent = 0;
139
+ let descent = 0;
140
+ let lineGap = 0;
141
+ for (const t of toks) {
142
+ if (t.isSpace || t.isBreak) continue;
143
+ const m = mMetrics(t.piece);
144
+ if (m.a > ascent) ascent = m.a;
145
+ if (m.d > descent) descent = m.d;
146
+ if (m.g > lineGap) lineGap = m.g;
147
+ }
148
+ if (ascent === 0) {
149
+ ascent = para.fallbackSizePx * FALLBACK_ASCENT;
150
+ descent = para.fallbackSizePx * FALLBACK_DESCENT;
151
+ lineGap = para.fallbackSizePx * FALLBACK_LINEGAP;
152
+ }
153
+ const isFirst = li === 0;
154
+ const lineLeft = bullet ? Math.max(wrapLeft, firstLeft + bulletLead) : (isFirst ? firstLeft : wrapLeft) + bulletLead;
155
+ const line = {
156
+ tokens: toks,
157
+ ascent,
158
+ descent,
159
+ lineGap,
160
+ topY: cursorY,
161
+ advance: 0,
162
+ anchorX: lineLeft,
163
+ textAnchor: "start",
164
+ bullet: null
165
+ };
166
+ if (para.align === "center") {
167
+ line.textAnchor = "middle";
168
+ line.anchorX = (lineLeft + wrapRight) / 2;
169
+ } else if (para.align === "right") {
170
+ line.textAnchor = "end";
171
+ line.anchorX = wrapRight;
172
+ }
173
+ if (isFirst && bullet) line.bullet = {
174
+ x: firstLeft,
175
+ baselineDy: 0,
176
+ b: bullet
177
+ };
178
+ line.advance = lineAdvance(line, para);
179
+ cursorY += line.advance;
180
+ lines.push(line);
181
+ }
182
+ cursorY += para.spcAftPx;
183
+ }
184
+ return {
185
+ lines,
186
+ blockH: cursorY
187
+ };
188
+ };
189
+ const vert = input.vert ?? "none";
190
+ const cx = input.boxXpx + input.boxWpx / 2;
191
+ const cy = input.boxYpx + input.boxHpx / 2;
192
+ const frame = vert === "none" ? {
193
+ x: input.boxXpx,
194
+ y: input.boxYpx,
195
+ w: input.boxWpx,
196
+ h: input.boxHpx
197
+ } : {
198
+ x: cx - input.boxHpx / 2,
199
+ y: cy - input.boxWpx / 2,
200
+ w: input.boxHpx,
201
+ h: input.boxWpx
202
+ };
203
+ 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;
206
+ return `<g transform="rotate(${vert === "cw90" ? 90 : 270} ${fmt(cx)} ${fmt(cy)})">${body}</g>`;
207
+ };
208
+ const anchorOffsetY = (frameY, frameH, blockH, anchor, firstLine) => {
209
+ let offsetY = frameY;
210
+ if (anchor === "center") offsetY = frameY + (frameH - blockH) / 2;
211
+ else if (anchor === "bottom") offsetY = frameY + (frameH - blockH);
212
+ if ((anchor === "center" || anchor === "bottom") && firstLine) offsetY += CENTER_ANCHOR_DROP * (firstLine.ascent + firstLine.descent);
213
+ return offsetY;
214
+ };
215
+ const placeSingle = (frame, anchor, buildLines) => {
216
+ const { lines, blockH } = buildLines(frame.x, frame.x + frame.w);
217
+ 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
+ }));
223
+ };
224
+ const placeColumns = (frame, columns, anchor, buildLines) => {
225
+ const gap = columns.gapPx;
226
+ const colW = Math.max(1, (frame.w - (columns.count - 1) * gap) / columns.count);
227
+ const { lines } = buildLines(frame.x, frame.x + colW);
228
+ let col = 0;
229
+ let colStartTopY = 0;
230
+ const colHasLine = [];
231
+ let tallest = 0;
232
+ const placed = [];
233
+ for (const line of lines) {
234
+ const localBottom = line.topY - colStartTopY + line.advance;
235
+ if (col < columns.count - 1 && localBottom > frame.h && colHasLine[col]) {
236
+ col += 1;
237
+ colStartTopY = line.topY;
238
+ }
239
+ const localTopY = line.topY - colStartTopY;
240
+ placed.push({
241
+ line,
242
+ localTopY,
243
+ col
244
+ });
245
+ colHasLine[col] = true;
246
+ if (localTopY + line.advance > tallest) tallest = localTopY + line.advance;
247
+ }
248
+ 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
+ }));
254
+ };
255
+ const emitPlacements = (placements) => {
256
+ const parts = [];
257
+ for (const { line, baselineY, dx } of placements) {
258
+ if (line.bullet) {
259
+ const b = line.bullet.b;
260
+ if (b.imageHref) parts.push(`<image x="${fmt(line.bullet.x + dx + GRID_NUDGE_X)}" y="${fmt(baselineY - b.sizePx)}" width="${fmt(b.sizePx)}" height="${fmt(b.sizePx)}" href="${b.imageHref}" xlink:href="${b.imageHref}" preserveAspectRatio="xMidYMid meet"/>`);
261
+ else parts.push(`<text x="${fmt(line.bullet.x + dx + GRID_NUDGE_X)}" y="${fmt(baselineY)}" font-family="${escapeXml$1(b.family)}" font-size="${fmt(b.sizePx)}" fill="${b.fillHex}" xml:space="preserve">${escapeXml$1(b.text)}</text>`);
262
+ }
263
+ parts.push(emitLine(line, baselineY, dx));
264
+ }
265
+ return parts.join("");
266
+ };
267
+ const topPad = (line) => {
268
+ const lead = line.advance - (line.ascent + line.descent);
269
+ return lead > 0 ? lead / 2 : 0;
270
+ };
271
+ const lineAdvance = (line, para) => {
272
+ const natural = line.ascent + line.descent + line.lineGap;
273
+ let adv;
274
+ if (para.lineSpacing?.kind === "pct") adv = para.lineSpacing.value * natural;
275
+ else if (para.lineSpacing?.kind === "pts") adv = para.lineSpacing.px;
276
+ else adv = natural;
277
+ return adv * para.lineAdvanceScale;
278
+ };
279
+ const emitLine = (line, baselineY, dx) => {
280
+ const toks = [...line.tokens];
281
+ while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
282
+ const content = toks.filter((t) => !t.isBreak);
283
+ if (content.length === 0) return "";
284
+ const tspans = groupTokens(content).map((g) => tspan(g)).join("");
285
+ if (tspans === "") return "";
286
+ return `<text x="${fmt(line.anchorX + dx + GRID_NUDGE_X)}" y="${fmt(baselineY)}" text-anchor="${line.textAnchor}" xml:space="preserve">${tspans}</text>`;
287
+ };
288
+ const groupTokens = (toks) => {
289
+ const groups = [];
290
+ for (const t of toks) {
291
+ if (t.isBreak) continue;
292
+ const last = groups[groups.length - 1];
293
+ if (last && samePiece(last.piece, t.piece)) last.text += t.text;
294
+ else groups.push({
295
+ text: t.text,
296
+ piece: t.piece
297
+ });
298
+ }
299
+ return groups;
300
+ };
301
+ const samePiece = (a, b) => a.family === b.family && a.sizePx === b.sizePx && a.bold === b.bold && a.italic === b.italic && a.letterSpacingPx === b.letterSpacingPx && a.fillHex === b.fillHex && a.underline === b.underline && a.strike === b.strike && a.superSub === b.superSub && a.href === b.href;
302
+ const tspan = (g) => {
303
+ const p = g.piece;
304
+ const sizePx = p.superSub !== 0 ? p.sizePx * .65 : p.sizePx;
305
+ const attrs = [
306
+ `font-family="${escapeXml$1(p.family)}"`,
307
+ `font-size="${fmt(sizePx)}"`,
308
+ `fill="${p.fillHex}"`
309
+ ];
310
+ if (p.bold) attrs.push("font-weight=\"700\"");
311
+ if (p.italic) attrs.push("font-style=\"italic\"");
312
+ const deco = [];
313
+ if (p.underline) deco.push("underline");
314
+ if (p.strike) deco.push("line-through");
315
+ if (deco.length) attrs.push(`text-decoration="${deco.join(" ")}"`);
316
+ if (p.letterSpacingPx !== 0) attrs.push(`letter-spacing="${fmt(p.letterSpacingPx)}"`);
317
+ if (p.superSub === 1) attrs.push(`baseline-shift="${fmt(p.sizePx * .33)}"`);
318
+ else if (p.superSub === -1) attrs.push(`baseline-shift="${fmt(-p.sizePx * .16)}"`);
319
+ return `<tspan ${attrs.join(" ")}>${escapeXml$1(g.text)}</tspan>`;
320
+ };
321
+ const wrapTokens = (tokens, wrap, firstAvail, avail) => {
322
+ const lines = [];
323
+ let cur = [];
324
+ let lineW = 0;
325
+ let trailingSpaceW = 0;
326
+ let first = true;
327
+ const trimTrailing = () => {
328
+ while (cur.length > 0 && (cur[cur.length - 1].isSpace || cur[cur.length - 1].isBreak)) cur.pop();
329
+ };
330
+ const close = () => {
331
+ trimTrailing();
332
+ lines.push(cur);
333
+ cur = [];
334
+ lineW = 0;
335
+ trailingSpaceW = 0;
336
+ first = false;
337
+ };
338
+ for (const tok of tokens) {
339
+ if (tok.isBreak) {
340
+ cur.push(tok);
341
+ close();
342
+ continue;
343
+ }
344
+ if (tok.isSpace) {
345
+ cur.push(tok);
346
+ lineW += tok.width;
347
+ trailingSpaceW += tok.width;
348
+ continue;
349
+ }
350
+ const limit = first ? firstAvail : avail;
351
+ const contentW = lineW - trailingSpaceW;
352
+ if (wrap && contentW > 0 && contentW + tok.width > limit + .5) {
353
+ close();
354
+ cur.push(tok);
355
+ lineW = tok.width;
356
+ } else {
357
+ cur.push(tok);
358
+ lineW += tok.width;
359
+ }
360
+ trailingSpaceW = 0;
361
+ }
362
+ trimTrailing();
363
+ if (cur.length > 0) lines.push(cur);
364
+ return lines;
365
+ };
366
+ //#endregion
367
+ //#region src/render-slide.ts
368
+ const DEFAULT_SIZE = {
369
+ width: 12192e3,
370
+ height: 6858e3
371
+ };
372
+ const EMU_PER_PX = 9525;
373
+ const PX_PER_PT = 96 / 72;
374
+ const DEFAULT_BODY_PT = 18;
375
+ const DEFAULT_TITLE_PT = 44;
376
+ const DEFAULT_FONT = "Calibri, 'Helvetica Neue', Arial, sans-serif";
377
+ const DEFAULT_INSET_X = 91440;
378
+ const DEFAULT_INSET_Y = 45720;
379
+ const u8ToBase64 = (data) => {
380
+ let s = "";
381
+ const chunk = 32768;
382
+ for (let i = 0; i < data.length; i += chunk) s += String.fromCharCode(...data.subarray(i, i + chunk));
383
+ return btoa(s);
384
+ };
385
+ const imageMime = {
386
+ png: "image/png",
387
+ jpeg: "image/jpeg",
388
+ gif: "image/gif",
389
+ bmp: "image/bmp",
390
+ tiff: "image/tiff",
391
+ webp: "image/webp",
392
+ svg: "image/svg+xml"
393
+ };
394
+ const bytesToDataUrl = (bytes) => {
395
+ const fmt = detectImageFormatLocal(bytes);
396
+ return `data:${fmt ? imageMime[fmt] ?? "image/png" : "image/png"};base64,${u8ToBase64(bytes)}`;
397
+ };
398
+ const EXT_TO_MIME = {
399
+ png: "image/png",
400
+ jpg: "image/jpeg",
401
+ jpeg: "image/jpeg",
402
+ gif: "image/gif",
403
+ bmp: "image/bmp",
404
+ tif: "image/tiff",
405
+ tiff: "image/tiff",
406
+ webp: "image/webp",
407
+ svg: "image/svg+xml",
408
+ avif: "image/avif",
409
+ heic: "image/heic",
410
+ heif: "image/heif"
411
+ };
412
+ const mimeFromPartName = (name) => {
413
+ if (!name) return null;
414
+ const dot = name.lastIndexOf(".");
415
+ if (dot < 0) return null;
416
+ return EXT_TO_MIME[name.slice(dot + 1).toLowerCase()] ?? null;
417
+ };
418
+ const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, format) => {
419
+ let mime = null;
420
+ if (bytes && format) mime = imageMime[format] ?? null;
421
+ if (bytes && !mime) mime = mimeFromPartName(getShapeImagePartName(shape));
422
+ if (bytes && mime) {
423
+ const dataUrl = `data:${mime};base64,${u8ToBase64(bytes)}`;
424
+ const crop = getShapeImageCrop(shape);
425
+ let imgX = x, imgY = y, imgW = w, imgH = h;
426
+ let clipDef = "";
427
+ let clipAttr = "";
428
+ const cropL = crop?.left ?? 0;
429
+ const cropT = crop?.top ?? 0;
430
+ const cropR = crop?.right ?? 0;
431
+ const cropB = crop?.bottom ?? 0;
432
+ if (cropL > 0 || cropT > 0 || cropR > 0 || cropB > 0) {
433
+ const scaleX = 1 / Math.max(.001, 1 - cropL - cropR);
434
+ const scaleY = 1 / Math.max(.001, 1 - cropT - cropB);
435
+ imgW = w * scaleX;
436
+ imgH = h * scaleY;
437
+ imgX = x - imgW * cropL;
438
+ imgY = y - imgH * cropT;
439
+ const clipId = mintId();
440
+ clipDef = `<defs><clipPath id="${clipId}"><rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}"/></clipPath></defs>`;
441
+ clipAttr = ` clip-path="url(#${clipId})"`;
442
+ }
443
+ const brightness = getShapeImageBrightness(shape) ?? 0;
444
+ const contrast = getShapeImageContrast(shape) ?? 1;
445
+ const opacity = getShapeImageOpacity(shape) ?? 1;
446
+ const grayscale = isShapeImageGrayscale(shape);
447
+ const biLevel = getShapeImageBiLevelThreshold(shape);
448
+ const duotone = getShapeImageDuotone(pres, shape);
449
+ let filterAttr = "";
450
+ if (brightness !== 0 || contrast !== 1 || grayscale || biLevel !== null || duotone && (duotone.firstColor || duotone.secondColor)) {
451
+ const fid = mintId();
452
+ const prims = [];
453
+ if (brightness !== 0 || contrast !== 1) prims.push(`<feComponentTransfer><feFuncR type="linear" slope="${contrast}" intercept="${brightness}"/><feFuncG type="linear" slope="${contrast}" intercept="${brightness}"/><feFuncB type="linear" slope="${contrast}" intercept="${brightness}"/></feComponentTransfer>`);
454
+ if (grayscale) prims.push(`<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`);
455
+ if (duotone && duotone.firstColor && duotone.secondColor) {
456
+ const [r1, g1, b1] = hexChannels(duotone.firstColor);
457
+ const [r2, g2, b2] = hexChannels(duotone.secondColor);
458
+ const steps = 16;
459
+ const tR = [];
460
+ const tG = [];
461
+ const tB = [];
462
+ for (let i = 0; i < steps; i++) {
463
+ const t = i / (steps - 1);
464
+ tR.push(((r1 + (r2 - r1) * t) / 255).toFixed(4));
465
+ tG.push(((g1 + (g2 - g1) * t) / 255).toFixed(4));
466
+ tB.push(((b1 + (b2 - b1) * t) / 255).toFixed(4));
467
+ }
468
+ prims.push(`<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`, `<feComponentTransfer><feFuncR type="table" tableValues="${tR.join(" ")}"/><feFuncG type="table" tableValues="${tG.join(" ")}"/><feFuncB type="table" tableValues="${tB.join(" ")}"/></feComponentTransfer>`);
469
+ }
470
+ if (biLevel !== null) {
471
+ const t = biLevel / 100;
472
+ const steps = 32;
473
+ const vals = [];
474
+ for (let i = 0; i < steps; i++) vals.push(i / (steps - 1) >= t ? "1" : "0");
475
+ const tableStr = vals.join(" ");
476
+ prims.push(`<feComponentTransfer><feFuncR type="discrete" tableValues="${tableStr}"/><feFuncG type="discrete" tableValues="${tableStr}"/><feFuncB type="discrete" tableValues="${tableStr}"/></feComponentTransfer>`);
477
+ }
478
+ clipDef += `<defs><filter id="${fid}">${prims.join("")}</filter></defs>`;
479
+ filterAttr = ` filter="url(#${fid})"`;
480
+ }
481
+ const opacityAttr = opacity !== 1 ? ` opacity="${opacity.toFixed(3)}"` : "";
482
+ 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><g${transform}>${textOverlay}</g>`;
483
+ }
484
+ const linkUrl = getShapeImageLinkUrl(shape);
485
+ const label = !bytes ? linkUrl ? `picture (link: ${linkUrl.length > 48 ? linkUrl.slice(0, 45) + "…" : linkUrl})` : "picture (no bytes)" : `picture (${format ?? "unknown"}${bytes ? `, ${bytes.byteLength} B` : ""})`;
486
+ 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>`;
487
+ };
488
+ const renderPicturePlaceholderLabel = (x, y, w, h, text) => {
489
+ const cx = x + w / 2;
490
+ const cy = y + h / 2;
491
+ return `${`<title>${escapeXml(text)}</title>`}${`<text x="${E(cx)}" y="${E(cy)}" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-weight="600" font-size="${(13 * PX_PER_PT).toFixed(2)}" fill="#374151">${escapeXml(text)}</text>`}`;
492
+ };
493
+ const escapeXml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
494
+ const SCHEME_TO_THEME = {
495
+ tx1: "dark1",
496
+ bg1: "light1",
497
+ tx2: "dark2",
498
+ bg2: "light2",
499
+ dk1: "dark1",
500
+ lt1: "light1",
501
+ dk2: "dark2",
502
+ lt2: "light2",
503
+ accent1: "accent1",
504
+ accent2: "accent2",
505
+ accent3: "accent3",
506
+ accent4: "accent4",
507
+ accent5: "accent5",
508
+ accent6: "accent6",
509
+ hlink: "hyperlink",
510
+ folHlink: "followedHyperlink"
511
+ };
512
+ const hexChannels = (hex) => {
513
+ const h = hex.startsWith("#") ? hex.slice(1) : hex;
514
+ return [
515
+ Number.parseInt(h.slice(0, 2), 16) || 0,
516
+ Number.parseInt(h.slice(2, 4), 16) || 0,
517
+ Number.parseInt(h.slice(4, 6), 16) || 0
518
+ ];
519
+ };
520
+ const mixHex = (aHex, bHex, t) => {
521
+ const aa = aHex.startsWith("#") ? aHex.slice(1) : aHex;
522
+ const bb = bHex.startsWith("#") ? bHex.slice(1) : bHex;
523
+ const part = (h, off) => Number.parseInt(h.slice(off, off + 2), 16);
524
+ const r = Math.round(part(aa, 0) * t + part(bb, 0) * (1 - t));
525
+ const g = Math.round(part(aa, 2) * t + part(bb, 2) * (1 - t));
526
+ const b = Math.round(part(aa, 4) * t + part(bb, 4) * (1 - t));
527
+ const h = (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, "0").toUpperCase();
528
+ return `#${h(r)}${h(g)}${h(b)}`;
529
+ };
530
+ const normalizeHex = (s) => {
531
+ if (s.startsWith("#")) return s;
532
+ if (/^[0-9A-Fa-f]{6}$/.test(s)) return `#${s}`;
533
+ if (/^[0-9A-Fa-f]{8}$/.test(s)) return `#${s.slice(2)}`;
534
+ return s;
535
+ };
536
+ const resolveColor = (c, theme, fallback = "#1F2937") => {
537
+ if (!c) return fallback;
538
+ let token = null;
539
+ if (c.startsWith("scheme:")) token = c.slice(7);
540
+ else if (SCHEME_TO_THEME[c]) token = c;
541
+ if (token !== null) {
542
+ if (theme) {
543
+ const key = SCHEME_TO_THEME[activeColorMap?.[token] ?? token] ?? SCHEME_TO_THEME[token];
544
+ if (key) return normalizeHex(theme[key]);
545
+ }
546
+ if (token === "tx1" || token === "dk1") return "#000000";
547
+ if (token === "bg1" || token === "lt1") return "#FFFFFF";
548
+ if (token === "tx2" || token === "dk2") return "#1F2937";
549
+ if (token === "bg2" || token === "lt2") return "#E5E7EB";
550
+ return fallback;
551
+ }
552
+ return normalizeHex(c);
553
+ };
554
+ let nextDefId = 0;
555
+ const mintId = () => `pkdef-${(nextDefId++).toString(36)}`;
556
+ let activeColorMap = null;
557
+ let activeDeckTextColor = "#000000";
558
+ const gradientDef = (grad, theme) => {
559
+ const id = mintId();
560
+ const stops = grad.stops.map((s) => `<stop offset="${s.offset.toFixed(4)}" stop-color="${resolveColor(s.color, theme, "#E5E7EB")}"/>`).join("");
561
+ if (grad.path === "circle" || grad.path === "rect" || grad.path === "shape") {
562
+ const focus = grad.focus ?? {
563
+ left: .5,
564
+ top: .5,
565
+ right: .5,
566
+ bottom: .5
567
+ };
568
+ const cx = (focus.left + focus.right) / 2;
569
+ const cy = (focus.top + focus.bottom) / 2;
570
+ const reversed = grad.stops.slice().reverse().map((s) => `<stop offset="${(1 - s.offset).toFixed(4)}" stop-color="${resolveColor(s.color, theme, "#E5E7EB")}"/>`).join("");
571
+ return {
572
+ defs: `<defs><radialGradient id="${id}" gradientUnits="objectBoundingBox" cx="${cx.toFixed(4)}" cy="${cy.toFixed(4)}" r="${Math.max(.5, Math.max(cx, cy, 1 - cx, 1 - cy)).toFixed(4)}">${reversed}</radialGradient></defs>`,
573
+ fillAttr: `url(#${id})`
574
+ };
575
+ }
576
+ const angleRad = (grad.angleDeg ?? 0) * Math.PI / 180;
577
+ const dx = Math.cos(angleRad) / 2;
578
+ const dy = Math.sin(angleRad) / 2;
579
+ const x1 = .5 - dx;
580
+ const y1 = .5 - dy;
581
+ const x2 = .5 + dx;
582
+ const y2 = .5 + dy;
583
+ return {
584
+ defs: `<defs><linearGradient id="${id}" gradientUnits="objectBoundingBox" x1="${x1.toFixed(4)}" y1="${y1.toFixed(4)}" x2="${x2.toFixed(4)}" y2="${y2.toFixed(4)}">${stops}</linearGradient></defs>`,
585
+ fillAttr: `url(#${id})`
586
+ };
587
+ };
588
+ const patternDef = (pat) => {
589
+ const id = mintId();
590
+ const fg = pat.foreground;
591
+ const bg = pat.background;
592
+ const preset = pat.preset;
593
+ let body = "";
594
+ const W = 8;
595
+ const H = 8;
596
+ const stripe = (orientation, width = 1) => {
597
+ if (orientation === "h") return `<path d="M0 ${H / 2}H${W}" stroke="${fg}" stroke-width="${width}"/>`;
598
+ if (orientation === "v") return `<path d="M${W / 2} 0V${H}" stroke="${fg}" stroke-width="${width}"/>`;
599
+ if (orientation === "d") return `<path d="M0 0L${W} ${H}" stroke="${fg}" stroke-width="${width}"/>`;
600
+ return `<path d="M${W} 0L0 ${H}" stroke="${fg}" stroke-width="${width}"/>`;
601
+ };
602
+ const BAYER4 = [
603
+ 0,
604
+ 8,
605
+ 2,
606
+ 10,
607
+ 12,
608
+ 4,
609
+ 14,
610
+ 6,
611
+ 3,
612
+ 11,
613
+ 1,
614
+ 9,
615
+ 15,
616
+ 7,
617
+ 13,
618
+ 5
619
+ ];
620
+ const screen = (density) => {
621
+ const threshold = density * 16;
622
+ const out = [];
623
+ for (let i = 0; i < 16; i++) if (BAYER4[i] < threshold) {
624
+ const cx = i % 4 * 2;
625
+ const cy = Math.floor(i / 4) * 2;
626
+ out.push(`<rect x="${cx}" y="${cy}" width="2" height="2" fill="${fg}"/>`);
627
+ }
628
+ return out.join("");
629
+ };
630
+ const pctMatch = /^pct(\d+)$/.exec(preset);
631
+ 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);
636
+ else if (preset === "ltUpDiag" || preset === "wdUpDiag") body = stripe("d", .8);
637
+ else if (preset === "dkUpDiag") body = stripe("d", 2);
638
+ else if (preset === "ltDnDiag" || preset === "wdDnDiag") body = stripe("a", .8);
639
+ else if (preset === "dkDnDiag") body = stripe("a", 2);
640
+ else if (preset === "ltHorzCross" || preset === "smGrid" || preset === "cross") body = stripe("h", .8) + stripe("v", .8);
641
+ else if (preset === "dkHorzCross" || preset === "lgGrid" || preset === "plaid") body = stripe("h", 2) + stripe("v", 2);
642
+ else if (preset === "diagCross" || preset === "trellis" || preset === "shingle" || preset === "dashUpDiag" || preset === "dashDnDiag") body = stripe("d", .8) + stripe("a", .8);
643
+ else if (preset === "dkUpDiagStripe" || preset === "dkDnDiagStripe") body = stripe(preset === "dkUpDiagStripe" ? "d" : "a", 2);
644
+ else if (preset === "wave" || preset === "zigZag") body = `<path d="M0 4Q2 2 4 4T8 4" stroke="${fg}" stroke-width="0.8" fill="none"/>`;
645
+ else if (preset === "weave" || preset === "divot") body = `<path d="M0 0L4 4 0 8M4 0L8 4 4 8" stroke="${fg}" stroke-width="0.8" fill="none"/>`;
646
+ else if (preset === "sphere") body = `<circle cx="4" cy="4" r="3" fill="${fg}" fill-opacity="0.7"/>`;
647
+ else if (preset === "solidDmnd" || preset === "openDmnd") body = `<path d="M4 1L7 4 4 7 1 4Z" fill="${preset === "solidDmnd" ? fg : "none"}" stroke="${fg}" stroke-width="0.6"/>`;
648
+ else body = screen(.5);
649
+ return {
650
+ defs: `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" width="${W}" height="${H}"><rect width="${W}" height="${H}" fill="${bg}"/>${body}</pattern></defs>`,
651
+ fillAttr: `url(#${id})`
652
+ };
653
+ };
654
+ const DASH_PATTERNS = {
655
+ solid: "",
656
+ dot: "1 3",
657
+ dash: "4 3",
658
+ lgDash: "8 3",
659
+ dashDot: "4 3 1 3",
660
+ lgDashDot: "8 3 1 3",
661
+ lgDashDotDot: "8 3 1 3 1 3",
662
+ sysDash: "3 1",
663
+ sysDot: "1 1",
664
+ sysDashDot: "3 1 1 1",
665
+ sysDashDotDot: "3 1 1 1 1 1"
666
+ };
667
+ const arrowSize = (size) => size === "sm" ? 3 : size === "lg" ? 7 : 5;
668
+ const buildArrowMarker = (type, width, length, color, orient) => {
669
+ const id = mintId();
670
+ const w = arrowSize(width);
671
+ const h = arrowSize(length);
672
+ let body;
673
+ switch (type) {
674
+ case "triangle":
675
+ case "arrow":
676
+ body = `<path d="M0 0L${w} ${h / 2}L0 ${h}z" fill="${color}"/>`;
677
+ break;
678
+ case "stealth":
679
+ body = `<path d="M0 0L${w} ${h / 2}L0 ${h}L${w * .4} ${h / 2}z" fill="${color}"/>`;
680
+ break;
681
+ case "diamond":
682
+ body = `<path d="M0 ${h / 2}L${w / 2} 0L${w} ${h / 2}L${w / 2} ${h}z" fill="${color}"/>`;
683
+ break;
684
+ case "oval":
685
+ body = `<ellipse cx="${w / 2}" cy="${h / 2}" rx="${w / 2}" ry="${h / 2}" fill="${color}"/>`;
686
+ break;
687
+ default: body = "";
688
+ }
689
+ return {
690
+ id,
691
+ def: `<defs><marker id="${id}" viewBox="0 0 ${w} ${h}" refX="${w}" refY="${h / 2}" markerWidth="${w}" markerHeight="${h}" orient="${orient}">${body}</marker></defs>`
692
+ };
693
+ };
694
+ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
695
+ let fillColor;
696
+ let defs = "";
697
+ if (fill.kind === "solid") {
698
+ let resolved = null;
699
+ if (shape && pres) resolved = getShapeFillColorResolved(pres, shape);
700
+ fillColor = resolved ?? resolveColor(fill.color, theme, "#E5E7EB");
701
+ } else if (fill.kind === "none") fillColor = "none";
702
+ else if (fill.kind === "gradient") {
703
+ const grad = shape ? pres ? getShapeGradientFillEffective(pres, shape) : getShapeGradientFill(shape) : null;
704
+ if (grad) {
705
+ const built = gradientDef(grad, theme);
706
+ defs = built.defs;
707
+ fillColor = built.fillAttr;
708
+ } else fillColor = (shape && pres ? getShapeFillColorResolved(pres, shape) : null) ?? "none";
709
+ } else if (fill.kind === "pattern") {
710
+ const pat = shape && pres ? getShapePatternFill(pres, shape) : null;
711
+ if (pat) {
712
+ const built = patternDef(pat);
713
+ defs = built.defs;
714
+ fillColor = built.fillAttr;
715
+ } else fillColor = "#BFDBFE";
716
+ } else if (fill.kind === "image") fillColor = "#DDD6FE";
717
+ else fillColor = "none";
718
+ let strokeColor = "none";
719
+ let strokeWidth = 0;
720
+ const strokeAttrParts = [];
721
+ let markerAttrs = "";
722
+ if (stroke.kind === "solid") {
723
+ let resolved = null;
724
+ if (shape && pres) resolved = getShapeStrokeColorResolved(pres, shape);
725
+ strokeColor = resolved ?? resolveColor(stroke.color, theme, "#9CA3AF");
726
+ strokeWidth = stroke.widthEmu ?? 9525;
727
+ if (shape) {
728
+ const dash = getShapeStrokeDash(shape);
729
+ if (dash && dash !== "solid") {
730
+ const pattern = DASH_PATTERNS[dash];
731
+ if (pattern) {
732
+ const swPx = strokeWidth / EMU_PER_PX;
733
+ const arr = pattern.split(" ").map((n) => (Number.parseFloat(n) * swPx).toFixed(2)).join(" ");
734
+ strokeAttrParts.push(`stroke-dasharray="${arr}"`);
735
+ }
736
+ }
737
+ const cap = getShapeStrokeCap(shape);
738
+ if (cap === "rnd") strokeAttrParts.push("stroke-linecap=\"round\"");
739
+ else if (cap === "sq") strokeAttrParts.push("stroke-linecap=\"square\"");
740
+ else if (cap === "flat") strokeAttrParts.push("stroke-linecap=\"butt\"");
741
+ const join = getShapeStrokeJoin(shape);
742
+ if (join === "round") strokeAttrParts.push("stroke-linejoin=\"round\"");
743
+ else if (join === "bevel") strokeAttrParts.push("stroke-linejoin=\"bevel\"");
744
+ else if (join === "miter") strokeAttrParts.push("stroke-linejoin=\"miter\"");
745
+ if (getShapeStrokeCompound(shape) === "dbl") strokeWidth = Math.max(strokeWidth, 19050);
746
+ const head = getShapeStrokeArrow(shape, "head");
747
+ const tail = getShapeStrokeArrow(shape, "tail");
748
+ if (head && head.type !== "none") {
749
+ const m = buildArrowMarker(head.type, head.width, head.length, strokeColor, "auto-start-reverse");
750
+ defs += m.def;
751
+ markerAttrs += ` marker-start="url(#${m.id})"`;
752
+ }
753
+ if (tail && tail.type !== "none") {
754
+ const m = buildArrowMarker(tail.type, tail.width, tail.length, strokeColor, "auto");
755
+ defs += m.def;
756
+ markerAttrs += ` marker-end="url(#${m.id})"`;
757
+ }
758
+ }
759
+ }
760
+ return {
761
+ fill: fillColor,
762
+ stroke: strokeColor,
763
+ strokeWidth,
764
+ defs,
765
+ strokeAttrs: strokeAttrParts.join(" "),
766
+ markerAttrs
767
+ };
768
+ };
769
+ const polygon = (n, rotation = -Math.PI / 2) => {
770
+ const out = [];
771
+ for (let i = 0; i < n; i++) {
772
+ const a = rotation + i * 2 * Math.PI / n;
773
+ out.push([.5 + .5 * Math.cos(a), .5 + .5 * Math.sin(a)]);
774
+ }
775
+ return out;
776
+ };
777
+ const star = (points, innerRatio = .42) => {
778
+ const out = [];
779
+ const rotation = -Math.PI / 2;
780
+ for (let i = 0; i < points * 2; i++) {
781
+ const a = rotation + i * Math.PI / points;
782
+ const r = i % 2 === 0 ? .5 : .5 * innerRatio;
783
+ out.push([.5 + r * Math.cos(a), .5 + r * Math.sin(a)]);
784
+ }
785
+ return out;
786
+ };
787
+ const PRESET_POINTS = {
788
+ triangle: () => [
789
+ [.5, 0],
790
+ [1, 1],
791
+ [0, 1]
792
+ ],
793
+ rtTriangle: () => [
794
+ [0, 0],
795
+ [1, 1],
796
+ [0, 1]
797
+ ],
798
+ diamond: () => [
799
+ [.5, 0],
800
+ [1, .5],
801
+ [.5, 1],
802
+ [0, .5]
803
+ ],
804
+ parallelogram: () => [
805
+ [.25, 0],
806
+ [1, 0],
807
+ [.75, 1],
808
+ [0, 1]
809
+ ],
810
+ trapezoid: () => [
811
+ [.25, 0],
812
+ [.75, 0],
813
+ [1, 1],
814
+ [0, 1]
815
+ ],
816
+ pentagon: () => polygon(5),
817
+ hexagon: () => polygon(6),
818
+ heptagon: () => polygon(7),
819
+ octagon: () => polygon(8),
820
+ decagon: () => polygon(10),
821
+ dodecagon: () => polygon(12),
822
+ star4: () => star(4),
823
+ star5: () => star(5),
824
+ star6: () => star(6),
825
+ star7: () => star(7),
826
+ star8: () => star(8),
827
+ star10: () => star(10),
828
+ star12: () => star(12),
829
+ star16: () => star(16),
830
+ star24: () => star(24),
831
+ 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
+ ],
868
+ leftRightArrow: () => [
869
+ [0, .5],
870
+ [.18, .2],
871
+ [.18, .35],
872
+ [.82, .35],
873
+ [.82, .2],
874
+ [1, .5],
875
+ [.82, .8],
876
+ [.82, .65],
877
+ [.18, .65],
878
+ [.18, .8]
879
+ ],
880
+ upDownArrow: () => [
881
+ [.5, 0],
882
+ [.2, .18],
883
+ [.35, .18],
884
+ [.35, .82],
885
+ [.2, .82],
886
+ [.5, 1],
887
+ [.8, .82],
888
+ [.65, .82],
889
+ [.65, .18],
890
+ [.8, .18]
891
+ ],
892
+ chevron: () => [
893
+ [0, 0],
894
+ [.7, 0],
895
+ [1, .5],
896
+ [.7, 1],
897
+ [0, 1],
898
+ [.3, .5]
899
+ ],
900
+ bentArrow: () => [
901
+ [0, .45],
902
+ [.55, .45],
903
+ [.55, .25],
904
+ [.55, .05],
905
+ [.95, .05],
906
+ [.95, .55],
907
+ [.8, .55],
908
+ [.8, .85],
909
+ [0, .85]
910
+ ],
911
+ homePlate: () => [
912
+ [0, 0],
913
+ [.75, 0],
914
+ [1, .5],
915
+ [.75, 1],
916
+ [0, 1]
917
+ ]
918
+ };
919
+ const PRESET_PATHS = {
920
+ wedgeRectCallout: (x, y, w, h) => {
921
+ const bodyH = h * .78;
922
+ const tailTipX = x + w * .12;
923
+ const tailTipY = y + h;
924
+ const tailBaseLeft = x + w * .18;
925
+ const tailBaseRight = x + w * .32;
926
+ const bodyB = y + bodyH;
927
+ return `M${x},${y} L${x + w},${y} L${x + w},${bodyB} L${tailBaseRight},${bodyB} L${tailTipX},${tailTipY} L${tailBaseLeft},${bodyB} L${x},${bodyB} Z`;
928
+ },
929
+ wedgeRoundRectCallout: (x, y, w, h) => {
930
+ const r = Math.min(w, h) * .08;
931
+ const bodyH = h * .78;
932
+ const tailTipX = x + w * .12;
933
+ const tailTipY = y + h;
934
+ const tailBaseLeft = x + w * .18;
935
+ const tailBaseRight = x + w * .32;
936
+ const bodyB = y + bodyH;
937
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${bodyB - r} A${r},${r} 0 0 1 ${x + w - r},${bodyB} L${tailBaseRight},${bodyB} L${tailTipX},${tailTipY} L${tailBaseLeft},${bodyB} L${x + r},${bodyB} A${r},${r} 0 0 1 ${x},${bodyB - r} L${x},${y + r} A${r},${r} 0 0 1 ${x + r},${y} Z`;
938
+ },
939
+ wedgeEllipseCallout: (x, y, w, h) => {
940
+ const bodyCy = y + h * .39;
941
+ const bodyRy = h * .39;
942
+ const cx = x + w / 2;
943
+ const bodyRx = w / 2;
944
+ const tailTipX = x + w * .12;
945
+ const tailTipY = y + h;
946
+ const tailBaseAngle = 1.5;
947
+ return `M${cx + bodyRx * Math.cos(tailBaseAngle - .18)},${bodyCy + bodyRy * Math.sin(tailBaseAngle - .18)} A${bodyRx},${bodyRy} 0 1 0 ${cx + bodyRx * Math.cos(1.68)},${bodyCy + bodyRy * Math.sin(1.68)} L${tailTipX},${tailTipY} Z`;
948
+ },
949
+ cloudCallout: (x, y, w, h) => {
950
+ const bodyH = h * .78;
951
+ const cx = x + w / 2;
952
+ const cy = y + bodyH / 2;
953
+ const rx = w / 2 * .92;
954
+ const ry = bodyH / 2 * .85;
955
+ const lobes = 10;
956
+ const path = [];
957
+ for (let i = 0; i < lobes; i++) {
958
+ const a = i / lobes * 2 * Math.PI - Math.PI / 2;
959
+ const lobeRx = rx * .32;
960
+ const lobeRy = ry * .32;
961
+ const px0 = cx + rx * Math.cos(a);
962
+ const py0 = cy + ry * Math.sin(a);
963
+ if (i === 0) path.push(`M${px0 - lobeRx},${py0}`);
964
+ path.push(`A${lobeRx},${lobeRy} 0 1 1 ${px0 + lobeRx},${py0}`);
965
+ const nextA = (i + 1) / lobes * 2 * Math.PI - Math.PI / 2;
966
+ const nextX = cx + rx * Math.cos(nextA) - lobeRx;
967
+ const nextY = cy + ry * Math.sin(nextA);
968
+ path.push(`L${nextX},${nextY}`);
969
+ }
970
+ path.push("Z");
971
+ const tailX = x + w * .18;
972
+ const tailY = y + h * .95;
973
+ return `${path.join(" ")} M${tailX - 6},${tailY - 14} a4,3 0 1 0 1,0 Z M${tailX},${tailY} a6,4 0 1 0 1,0 Z`;
974
+ },
975
+ heart: (x, y, w, h) => {
976
+ const cx = x + w / 2;
977
+ const top = y + h * .27;
978
+ return `M${cx},${y + h} C${x},${y + h * .55} ${x},${top} ${cx},${y + h * .4} C${x + w},${top} ${x + w},${y + h * .55} ${cx},${y + h} Z`;
979
+ },
980
+ sun: (x, y, w, h) => {
981
+ const cx = x + w / 2;
982
+ const cy = y + h / 2;
983
+ const innerR = Math.min(w, h) * .25;
984
+ const outerR = Math.min(w, h) * .5;
985
+ const rays = 12;
986
+ const path = [];
987
+ for (let i = 0; i < rays * 2; i++) {
988
+ const r = i % 2 === 0 ? outerR : innerR;
989
+ const a = i / (rays * 2) * 2 * Math.PI - Math.PI / 2;
990
+ const px0 = cx + r * Math.cos(a);
991
+ const py0 = cy + r * Math.sin(a);
992
+ path.push(`${i === 0 ? "M" : "L"}${px0},${py0}`);
993
+ }
994
+ path.push("Z");
995
+ return path.join(" ");
996
+ },
997
+ smileyFace: (x, y, w, h) => {
998
+ const cx = x + w / 2;
999
+ const cy = y + h / 2;
1000
+ const r = Math.min(w, h) / 2 - 1;
1001
+ const eyeR = r * .07;
1002
+ const eyeOff = r * .32;
1003
+ const mouthW = r * .5;
1004
+ const mouthY = cy + r * .18;
1005
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r},${cy} A${r},${r} 0 1 0 ${cx + r},${cy} Z M${cx - eyeOff + eyeR},${cy - eyeOff} A${eyeR},${eyeR} 0 1 1 ${cx - eyeOff - eyeR},${cy - eyeOff} A${eyeR},${eyeR} 0 1 1 ${cx - eyeOff + eyeR},${cy - eyeOff} Z M${cx + eyeOff + eyeR},${cy - eyeOff} A${eyeR},${eyeR} 0 1 1 ${cx + eyeOff - eyeR},${cy - eyeOff} A${eyeR},${eyeR} 0 1 1 ${cx + eyeOff + eyeR},${cy - eyeOff} Z M${cx - mouthW},${mouthY} Q${cx},${mouthY + r * .32} ${cx + mouthW},${mouthY}`;
1006
+ },
1007
+ lightningBolt: (x, y, w, h) => {
1008
+ return `M${x + w * .5},${y} L${x + w * .15},${y + h * .55} L${x + w * .45},${y + h * .55} L${x + w * .3},${y + h} L${x + w * .85},${y + h * .4} L${x + w * .55},${y + h * .4} L${x + w * .7},${y} Z`;
1009
+ },
1010
+ flowChartProcess: (x, y, w, h) => `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z`,
1011
+ flowChartAlternateProcess: (x, y, w, h) => {
1012
+ const r = Math.min(w, h) * .18;
1013
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${y + h - r} A${r},${r} 0 0 1 ${x + w - r},${y + h} L${x + r},${y + h} A${r},${r} 0 0 1 ${x},${y + h - r} L${x},${y + r} A${r},${r} 0 0 1 ${x + r},${y} Z`;
1014
+ },
1015
+ flowChartDecision: (x, y, w, h) => {
1016
+ const cx = x + w / 2;
1017
+ const cy = y + h / 2;
1018
+ return `M${cx},${y} L${x + w},${cy} L${cx},${y + h} L${x},${cy} Z`;
1019
+ },
1020
+ flowChartTerminator: (x, y, w, h) => {
1021
+ const r = h / 2;
1022
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w - r},${y + h} L${x + r},${y + h} A${r},${r} 0 0 1 ${x + r},${y} Z`;
1023
+ },
1024
+ flowChartConnector: (x, y, w, h) => {
1025
+ const cx = x + w / 2;
1026
+ const cy = y + h / 2;
1027
+ const r = Math.min(w, h) / 2;
1028
+ return `M${cx + r},${cy} A${r},${r} 0 1 1 ${cx - r},${cy} A${r},${r} 0 1 1 ${cx + r},${cy} Z`;
1029
+ },
1030
+ flowChartDocument: (x, y, w, h) => {
1031
+ const wave = h * .18;
1032
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h - wave} C${x + w * .75},${y + h + wave * .5} ${x + w * .25},${y + h - wave * 2} ${x},${y + h - wave * .5} Z`;
1033
+ },
1034
+ flowChartMultidocument: (x, y, w, h) => {
1035
+ const inset = w * .06;
1036
+ return `${`M${x + inset},${y + inset * .6} L${x + w},${y + inset * .6} L${x + w},${y + h - inset * .6} L${x + w - inset},${y + h - inset * .6} L${x + w - inset},${y + inset * .6}`} Z ${`M${x},${y + inset * 1.2} L${x + w - inset},${y + inset * 1.2} L${x + w - inset},${y + h * .85} C${x + (w - inset) * .75},${y + h + 6} ${x + (w - inset) * .25},${y + h * .75} ${x},${y + h * .95} Z`}`;
1037
+ },
1038
+ flowChartPredefinedProcess: (x, y, w, h) => {
1039
+ const inset = w * .1;
1040
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z M${x + inset},${y} L${x + inset},${y + h} M${x + w - inset},${y} L${x + w - inset},${y + h}`;
1041
+ },
1042
+ flowChartInternalStorage: (x, y, w, h) => {
1043
+ const inset = Math.min(w, h) * .1;
1044
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z M${x + inset},${y} L${x + inset},${y + h} M${x},${y + inset} L${x + w},${y + inset}`;
1045
+ },
1046
+ flowChartManualInput: (x, y, w, h) => {
1047
+ return `M${x},${y + h * .35} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z`;
1048
+ },
1049
+ flowChartManualOperation: (x, y, w, h) => {
1050
+ return `M${x},${y} L${x + w},${y} L${x + w * .8},${y + h} L${x + w * .2},${y + h} Z`;
1051
+ },
1052
+ flowChartInputOutput: (x, y, w, h) => {
1053
+ const skew = w * .18;
1054
+ return `M${x + skew},${y} L${x + w},${y} L${x + w - skew},${y + h} L${x},${y + h} Z`;
1055
+ },
1056
+ flowChartPunchedTape: (x, y, w, h) => {
1057
+ const wave = h * .12;
1058
+ return `M${x},${y + wave} C${x + w * .25},${y - wave} ${x + w * .75},${y + wave * 2} ${x + w},${y + wave} L${x + w},${y + h - wave} C${x + w * .75},${y + h + wave} ${x + w * .25},${y + h - wave * 2} ${x},${y + h - wave} Z`;
1059
+ },
1060
+ flowChartCard: (x, y, w, h) => {
1061
+ const cut = h * .3;
1062
+ return `M${x + cut},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} L${x},${y + cut} Z`;
1063
+ },
1064
+ flowChartPunchedCard: (x, y, w, h) => {
1065
+ const cut = h * .2;
1066
+ return `M${x + cut},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} L${x},${y + cut} Z`;
1067
+ },
1068
+ flowChartOnlineStorage: (x, y, w, h) => {
1069
+ const cap = w * .12;
1070
+ return `M${x + cap},${y} L${x + w},${y} L${x + w},${y + h} L${x + cap},${y + h} A${cap},${h / 2} 0 0 1 ${x + cap},${y} Z`;
1071
+ },
1072
+ flowChartMagneticDisk: (x, y, w, h) => {
1073
+ const er = h * .12;
1074
+ return `M${x},${y + er} A${w / 2},${er} 0 0 1 ${x + w},${y + er} L${x + w},${y + h - er} A${w / 2},${er} 0 0 1 ${x},${y + h - er} Z M${x},${y + er} A${w / 2},${er} 0 0 0 ${x + w},${y + er}`;
1075
+ },
1076
+ flowChartMagneticDrum: (x, y, w, h) => {
1077
+ const er = w * .12;
1078
+ return `M${x + er},${y} L${x + w - er},${y} A${er},${h / 2} 0 0 1 ${x + w - er},${y + h} L${x + er},${y + h} A${er},${h / 2} 0 0 1 ${x + er},${y} Z M${x + w - er},${y} A${er},${h / 2} 0 0 0 ${x + w - er},${y + h}`;
1079
+ },
1080
+ flowChartMagneticTape: (x, y, w, h) => {
1081
+ const cx = x + w / 2;
1082
+ const cy = y + h / 2;
1083
+ const r = Math.min(w, h) / 2;
1084
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r * .7},${cy + r * .7} L${x + w},${y + h} L${cx + r},${cy} Z`;
1085
+ },
1086
+ flowChartSummingJunction: (x, y, w, h) => {
1087
+ const cx = x + w / 2;
1088
+ const cy = y + h / 2;
1089
+ const r = Math.min(w, h) / 2;
1090
+ const off = r * Math.SQRT1_2;
1091
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r},${cy} A${r},${r} 0 1 0 ${cx + r},${cy} Z M${cx - off},${cy - off} L${cx + off},${cy + off} M${cx - off},${cy + off} L${cx + off},${cy - off}`;
1092
+ },
1093
+ flowChartOr: (x, y, w, h) => {
1094
+ const cx = x + w / 2;
1095
+ const cy = y + h / 2;
1096
+ const r = Math.min(w, h) / 2;
1097
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r},${cy} A${r},${r} 0 1 0 ${cx + r},${cy} Z M${cx - r},${cy} L${cx + r},${cy} M${cx},${cy - r} L${cx},${cy + r}`;
1098
+ },
1099
+ flowChartCollate: (x, y, w, h) => {
1100
+ return `M${x},${y} L${x + w},${y} L${x},${y + h} L${x + w},${y + h} Z`;
1101
+ },
1102
+ flowChartSort: (x, y, w, h) => {
1103
+ const cx = x + w / 2;
1104
+ const cy = y + h / 2;
1105
+ return `M${cx},${y} L${x + w},${cy} L${cx},${y + h} L${x},${cy} Z M${x},${cy} L${x + w},${cy}`;
1106
+ },
1107
+ flowChartExtract: (x, y, w, h) => {
1108
+ return `M${x + w / 2},${y} L${x + w},${y + h} L${x},${y + h} Z`;
1109
+ },
1110
+ flowChartMerge: (x, y, w, h) => {
1111
+ return `M${x},${y} L${x + w},${y} L${x + w / 2},${y + h} Z`;
1112
+ },
1113
+ flowChartOfflineStorage: (x, y, w, h) => {
1114
+ return `M${x + w / 2},${y} L${x + w},${y + h} L${x},${y + h} Z M${x + w * .25},${y + h * .75} L${x + w * .75},${y + h * .75}`;
1115
+ },
1116
+ flowChartDelay: (x, y, w, h) => {
1117
+ const r = h / 2;
1118
+ return `M${x},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w - r},${y + h} L${x},${y + h} Z`;
1119
+ },
1120
+ flowChartDisplay: (x, y, w, h) => {
1121
+ const r = h / 2;
1122
+ return `M${x + r * .5},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w - r},${y + h} L${x + r * .5},${y + h} L${x},${y + h / 2} Z`;
1123
+ },
1124
+ flowChartPreparation: (x, y, w, h) => {
1125
+ const cut = w * .18;
1126
+ return `M${x + cut},${y} L${x + w - cut},${y} L${x + w},${y + h / 2} L${x + w - cut},${y + h} L${x + cut},${y + h} L${x},${y + h / 2} Z`;
1127
+ },
1128
+ notchedRightArrow: (x, y, w, h) => {
1129
+ return `M${x},${y + h * .3} L${x + w * .65},${y + h * .3} L${x + w * .65},${y} L${x + w},${y + h / 2} L${x + w * .65},${y + h} L${x + w * .65},${y + h * .7} L${x},${y + h * .7} L${x + w * .15},${y + h / 2} Z`;
1130
+ },
1131
+ stripedRightArrow: (x, y, w, h) => {
1132
+ const stripe = w * .04;
1133
+ return `M${x},${y + h * .3} L${x + stripe},${y + h * .3} L${x + stripe},${y + h * .7} L${x},${y + h * .7} Z M${x + stripe * 2.5},${y + h * .3} L${x + stripe * 3.5},${y + h * .3} L${x + stripe * 3.5},${y + h * .7} L${x + stripe * 2.5},${y + h * .7} Z M${x + stripe * 5},${y + h * .3} L${x + w * .65},${y + h * .3} L${x + w * .65},${y} L${x + w},${y + h / 2} L${x + w * .65},${y + h} L${x + w * .65},${y + h * .7} L${x + stripe * 5},${y + h * .7} Z`;
1134
+ },
1135
+ curvedRightArrow: (x, y, w, h) => {
1136
+ return `M${x},${y + h * .6} Q${x + w * .5},${y} ${x + w * .85},${y + h * .25} L${x + w},${y + h * .45} L${x + w * .85},${y + h * .55} L${x + w * .7},${y + h * .35} Q${x + w * .45},${y + h * .18} ${x + w * .15},${y + h * .75} Z`;
1137
+ },
1138
+ uturnArrow: (x, y, w, h) => {
1139
+ return `M${x},${y + h} L${x},${y + h / 2} A${w * .4},${h * .4} 0 0 1 ${x + w * .8},${y + h / 2} L${x + w * .8},${y + h * .25} L${x + w},${y + h * .45} L${x + w * .8},${y + h * .65} L${x + w * .8},${y + h / 2} A${w * .2},${h * .25} 0 0 0 ${x + w * .2},${y + h / 2} L${x + w * .2},${y + h} Z`;
1140
+ },
1141
+ leftBracket: (x, y, w, h) => {
1142
+ return `M${x + w},${y} L${x},${y} L${x},${y + h} L${x + w},${y + h}`;
1143
+ },
1144
+ rightBracket: (x, y, w, h) => {
1145
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h}`;
1146
+ },
1147
+ bracketPair: (x, y, w, h) => {
1148
+ return `M${x + w * .1},${y} L${x},${y} L${x},${y + h} L${x + w * .1},${y + h} M${x + w * .9},${y} L${x + w},${y} L${x + w},${y + h} L${x + w * .9},${y + h}`;
1149
+ },
1150
+ leftBrace: (x, y, w, h) => {
1151
+ const mid = y + h / 2;
1152
+ return `M${x + w},${y} Q${x},${y} ${x},${mid - 8} Q${x},${mid} ${x - 4},${mid} Q${x},${mid} ${x},${mid + 8} Q${x},${y + h} ${x + w},${y + h}`;
1153
+ },
1154
+ rightBrace: (x, y, w, h) => {
1155
+ const mid = y + h / 2;
1156
+ return `M${x},${y} Q${x + w},${y} ${x + w},${mid - 8} Q${x + w},${mid} ${x + w + 4},${mid} Q${x + w},${mid} ${x + w},${mid + 8} Q${x + w},${y + h} ${x},${y + h}`;
1157
+ },
1158
+ bracePair: (x, y, w, h) => {
1159
+ const mid = y + h / 2;
1160
+ return `M${x + w * .12},${y} Q${x},${y} ${x},${mid - 8} Q${x},${mid} ${x - 4},${mid} Q${x},${mid} ${x},${mid + 8} Q${x},${y + h} ${x + w * .12},${y + h} M${x + w * .88},${y} Q${x + w},${y} ${x + w},${mid - 8} Q${x + w},${mid} ${x + w + 4},${mid} Q${x + w},${mid} ${x + w},${mid + 8} Q${x + w},${y + h} ${x + w * .88},${y + h}`;
1161
+ },
1162
+ snip1Rect: (x, y, w, h) => {
1163
+ const c = Math.min(w, h) * .18;
1164
+ return `M${x},${y} L${x + w - c},${y} L${x + w},${y + c} L${x + w},${y + h} L${x},${y + h} Z`;
1165
+ },
1166
+ snip2SameRect: (x, y, w, h) => {
1167
+ const c = Math.min(w, h) * .18;
1168
+ return `M${x + c},${y} L${x + w - c},${y} L${x + w},${y + c} L${x + w},${y + h} L${x},${y + h} L${x},${y + c} Z`;
1169
+ },
1170
+ snip2DiagRect: (x, y, w, h) => {
1171
+ const c = Math.min(w, h) * .18;
1172
+ return `M${x},${y} L${x + w - c},${y} L${x + w},${y + c} L${x + w},${y + h} L${x + c},${y + h} L${x},${y + h - c} Z`;
1173
+ },
1174
+ snipRoundRect: (x, y, w, h) => {
1175
+ const c = Math.min(w, h) * .18;
1176
+ return `M${x + c},${y} A${c},${c} 0 0 0 ${x},${y + c} L${x},${y + h} L${x + w},${y + h} L${x + w},${y + c} L${x + w - c},${y} Z`;
1177
+ },
1178
+ round1Rect: (x, y, w, h) => {
1179
+ const r = Math.min(w, h) * .18;
1180
+ return `M${x},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${y + h} L${x},${y + h} Z`;
1181
+ },
1182
+ round2SameRect: (x, y, w, h) => {
1183
+ const r = Math.min(w, h) * .18;
1184
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${y + h} L${x},${y + h} L${x},${y + r} A${r},${r} 0 0 1 ${x + r},${y} Z`;
1185
+ },
1186
+ round2DiagRect: (x, y, w, h) => {
1187
+ const r = Math.min(w, h) * .18;
1188
+ return `M${x},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${y + h} L${x + r},${y + h} A${r},${r} 0 0 1 ${x},${y + h - r} Z`;
1189
+ },
1190
+ ribbon: (x, y, w, h) => {
1191
+ const notch = w * .06;
1192
+ const bodyTop = y + h * .2;
1193
+ const bodyBot = y + h * .8;
1194
+ return `M${x},${bodyTop} L${x + notch * 2},${y} L${x + w - notch * 2},${y} L${x + w},${bodyTop} L${x + w * .85},${bodyTop + (bodyBot - bodyTop) / 2} L${x + w},${bodyBot} L${x + w - notch * 2},${y + h} L${x + w - notch * 4},${bodyBot} L${x + notch * 4},${bodyBot} L${x + notch * 2},${y + h} L${x},${bodyBot} L${x + w * .15},${bodyTop + (bodyBot - bodyTop) / 2} Z`;
1195
+ },
1196
+ ribbon2: (x, y, w, h) => {
1197
+ const notch = w * .06;
1198
+ const bodyTop = y + h * .2;
1199
+ const bodyBot = y + h * .8;
1200
+ return `M${x},${bodyBot} L${x + notch * 2},${y + h} L${x + w - notch * 2},${y + h} L${x + w},${bodyBot} L${x + w * .85},${bodyTop + (bodyBot - bodyTop) / 2} L${x + w},${bodyTop} L${x + w - notch * 2},${y} L${x + w - notch * 4},${bodyTop} L${x + notch * 4},${bodyTop} L${x + notch * 2},${y} L${x},${bodyTop} L${x + w * .15},${bodyTop + (bodyBot - bodyTop) / 2} Z`;
1201
+ },
1202
+ verticalScroll: (x, y, w, h) => {
1203
+ const r = w * .08;
1204
+ return `M${x + r},${y + r} A${r},${r} 0 0 1 ${x + r * 2},${y} L${x + w},${y} L${x + w},${y + h - r} A${r},${r} 0 0 1 ${x + w - r * 2},${y + h} L${x},${y + h} L${x},${y + r} A${r},${r} 0 0 1 ${x + r},${y + r} Z`;
1205
+ },
1206
+ horizontalScroll: (x, y, w, h) => {
1207
+ const r = h * .08;
1208
+ return `M${x + r},${y + r} A${r},${r} 0 0 1 ${x},${y + r * 2} L${x},${y + h} L${x + w - r},${y + h} A${r},${r} 0 0 1 ${x + w},${y + h - r * 2} L${x + w},${y} L${x + r},${y} A${r},${r} 0 0 1 ${x + r},${y + r} Z`;
1209
+ },
1210
+ wave: (x, y, w, h) => {
1211
+ return `M${x},${y + h * .5} C${x + w * .25},${y - h * .1} ${x + w * .5},${y + h * .85} ${x + w * .75},${y + h * .3} C${x + w * .85},${y + h * .05} ${x + w * .95},${y + h * .4} ${x + w},${y + h * .5} L${x + w},${y + h} C${x + w * .75},${y + h * .4} ${x + w * .5},${y + h * 1.1} ${x + w * .25},${y + h * .55} C${x + w * .15},${y + h * .3} ${x + w * .05},${y + h * .6} ${x},${y + h * .55} Z`;
1212
+ },
1213
+ doubleWave: (x, y, w, h) => {
1214
+ return `M${x},${y + h * .4} C${x + w * .15},${y - h * .05} ${x + w * .35},${y + h * .65} ${x + w * .5},${y + h * .3} C${x + w * .65},${y - h * .05} ${x + w * .85},${y + h * .65} ${x + w},${y + h * .4} L${x + w},${y + h} C${x + w * .85},${y + h * .4} ${x + w * .65},${y + h * 1.05} ${x + w * .5},${y + h * .7} C${x + w * .35},${y + h * 1.05} ${x + w * .15},${y + h * .4} ${x},${y + h} Z`;
1215
+ },
1216
+ mathPlus: (x, y, w, h) => {
1217
+ const t = Math.min(w, h) * .2;
1218
+ const cx = x + w / 2;
1219
+ const cy = y + h / 2;
1220
+ return `M${cx - t / 2},${y} L${cx + t / 2},${y} L${cx + t / 2},${cy - t / 2} L${x + w},${cy - t / 2} L${x + w},${cy + t / 2} L${cx + t / 2},${cy + t / 2} L${cx + t / 2},${y + h} L${cx - t / 2},${y + h} L${cx - t / 2},${cy + t / 2} L${x},${cy + t / 2} L${x},${cy - t / 2} L${cx - t / 2},${cy - t / 2} Z`;
1221
+ },
1222
+ mathMinus: (x, y, w, h) => {
1223
+ const t = h * .3;
1224
+ const cy = y + h / 2;
1225
+ return `M${x},${cy - t / 2} L${x + w},${cy - t / 2} L${x + w},${cy + t / 2} L${x},${cy + t / 2} Z`;
1226
+ },
1227
+ mathMultiply: (x, y, w, h) => {
1228
+ const t = Math.min(w, h) * .16;
1229
+ const cx = x + w / 2;
1230
+ const cy = y + h / 2;
1231
+ return `M${x},${y + t} L${x + t},${y} L${cx},${cy - t} L${x + w - t},${y} L${x + w},${y + t} L${cx + t},${cy} L${x + w},${y + h - t} L${x + w - t},${y + h} L${cx},${cy + t} L${x + t},${y + h} L${x},${y + h - t} L${cx - t},${cy} Z`;
1232
+ },
1233
+ mathDivide: (x, y, w, h) => {
1234
+ const dot = Math.min(w, h) * .1;
1235
+ const cx = x + w / 2;
1236
+ const cy = y + h / 2;
1237
+ return `M${x},${cy - dot * .4} L${x + w},${cy - dot * .4} L${x + w},${cy + dot * .4} L${x},${cy + dot * .4} Z M${cx - dot},${y + h * .18} A${dot},${dot} 0 1 0 ${cx + dot},${y + h * .18} A${dot},${dot} 0 1 0 ${cx - dot},${y + h * .18} Z M${cx - dot},${y + h * .82} A${dot},${dot} 0 1 0 ${cx + dot},${y + h * .82} A${dot},${dot} 0 1 0 ${cx - dot},${y + h * .82} Z`;
1238
+ },
1239
+ mathEqual: (x, y, w, h) => {
1240
+ const t = h * .2;
1241
+ return `M${x},${y + h * .3 - t / 2} L${x + w},${y + h * .3 - t / 2} L${x + w},${y + h * .3 + t / 2} L${x},${y + h * .3 + t / 2} Z M${x},${y + h * .7 - t / 2} L${x + w},${y + h * .7 - t / 2} L${x + w},${y + h * .7 + t / 2} L${x},${y + h * .7 + t / 2} Z`;
1242
+ },
1243
+ mathNotEqual: (x, y, w, h) => {
1244
+ const t = h * .15;
1245
+ const cy = y + h / 2;
1246
+ return `M${x},${cy - h * .18 - t / 2} L${x + w},${cy - h * .18 - t / 2} L${x + w},${cy - h * .18 + t / 2} L${x},${cy - h * .18 + t / 2} Z M${x},${cy + h * .18 - t / 2} L${x + w},${cy + h * .18 - t / 2} L${x + w},${cy + h * .18 + t / 2} L${x},${cy + h * .18 + t / 2} Z M${x + w * .7},${y} L${x + w * .85},${y} L${x + w * .3},${y + h} L${x + w * .15},${y + h} Z`;
1247
+ },
1248
+ actionButtonBlank: (x, y, w, h) => {
1249
+ const r = Math.min(w, h) * .06;
1250
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 1 ${x + w},${y + r} L${x + w},${y + h - r} A${r},${r} 0 0 1 ${x + w - r},${y + h} L${x + r},${y + h} A${r},${r} 0 0 1 ${x},${y + h - r} L${x},${y + r} A${r},${r} 0 0 1 ${x + r},${y} Z`;
1251
+ },
1252
+ irregularSeal1: (x, y, w, h) => {
1253
+ const cx = x + w / 2;
1254
+ const cy = y + h / 2;
1255
+ const rx = w / 2;
1256
+ const ry = h / 2;
1257
+ const offsets = [
1258
+ 1,
1259
+ .45,
1260
+ .95,
1261
+ .5,
1262
+ 1,
1263
+ .4,
1264
+ .9,
1265
+ .55,
1266
+ 1,
1267
+ .45,
1268
+ .95,
1269
+ .5,
1270
+ 1,
1271
+ .4,
1272
+ .9,
1273
+ .55
1274
+ ];
1275
+ const points = [];
1276
+ for (let i = 0; i < offsets.length; i++) {
1277
+ const a = i / offsets.length * 2 * Math.PI - Math.PI / 2;
1278
+ const r = offsets[i] ?? 1;
1279
+ const px0 = cx + rx * r * Math.cos(a);
1280
+ const py0 = cy + ry * r * Math.sin(a);
1281
+ points.push(`${i === 0 ? "M" : "L"}${px0},${py0}`);
1282
+ }
1283
+ points.push("Z");
1284
+ return points.join(" ");
1285
+ },
1286
+ irregularSeal2: (x, y, w, h) => {
1287
+ const cx = x + w / 2;
1288
+ const cy = y + h / 2;
1289
+ const rx = w / 2;
1290
+ const ry = h / 2;
1291
+ const offsets = [
1292
+ 1,
1293
+ .4,
1294
+ .95,
1295
+ .5,
1296
+ .9,
1297
+ .35,
1298
+ .85,
1299
+ .45,
1300
+ 1,
1301
+ .4,
1302
+ .95,
1303
+ .5,
1304
+ .9,
1305
+ .35,
1306
+ .85,
1307
+ .45,
1308
+ 1,
1309
+ .4,
1310
+ .95,
1311
+ .5,
1312
+ .9,
1313
+ .35,
1314
+ .85,
1315
+ .45
1316
+ ];
1317
+ const points = [];
1318
+ for (let i = 0; i < offsets.length; i++) {
1319
+ const a = i / offsets.length * 2 * Math.PI - Math.PI / 2;
1320
+ const r = offsets[i] ?? 1;
1321
+ const px0 = cx + rx * r * Math.cos(a);
1322
+ const py0 = cy + ry * r * Math.sin(a);
1323
+ points.push(`${i === 0 ? "M" : "L"}${px0},${py0}`);
1324
+ }
1325
+ points.push("Z");
1326
+ return points.join(" ");
1327
+ },
1328
+ cloud: (x, y, w, h) => {
1329
+ const cx = x + w / 2;
1330
+ const cy = y + h / 2;
1331
+ const rx = w / 2 * .92;
1332
+ const ry = h / 2 * .85;
1333
+ const lobes = 10;
1334
+ const path = [];
1335
+ for (let i = 0; i < lobes; i++) {
1336
+ const a = i / lobes * 2 * Math.PI - Math.PI / 2;
1337
+ const lobeRx = rx * .32;
1338
+ const lobeRy = ry * .32;
1339
+ const px0 = cx + rx * Math.cos(a);
1340
+ const py0 = cy + ry * Math.sin(a);
1341
+ if (i === 0) path.push(`M${px0 - lobeRx},${py0}`);
1342
+ path.push(`A${lobeRx},${lobeRy} 0 1 1 ${px0 + lobeRx},${py0}`);
1343
+ const nextA = (i + 1) / lobes * 2 * Math.PI - Math.PI / 2;
1344
+ const nextX = cx + rx * Math.cos(nextA) - lobeRx;
1345
+ const nextY = cy + ry * Math.sin(nextA);
1346
+ path.push(`L${nextX},${nextY}`);
1347
+ }
1348
+ path.push("Z");
1349
+ return path.join(" ");
1350
+ },
1351
+ pie: (x, y, w, h) => {
1352
+ const cx = x + w / 2;
1353
+ const cy = y + h / 2;
1354
+ const r = Math.min(w, h) / 2;
1355
+ return `M${cx},${cy} L${cx + r},${cy} A${r},${r} 0 1 1 ${cx},${cy - r} Z`;
1356
+ },
1357
+ chord: (x, y, w, h) => {
1358
+ const cx = x + w / 2;
1359
+ const cy = y + h / 2;
1360
+ const rx = w / 2;
1361
+ const ry = h / 2;
1362
+ return `M${cx + rx},${cy} A${rx},${ry} 0 1 1 ${cx - rx},${cy} L${cx + rx},${cy} Z`;
1363
+ },
1364
+ teardrop: (x, y, w, h) => {
1365
+ const cx = x + w / 2;
1366
+ const cy = y + h / 2;
1367
+ const rx = w / 2;
1368
+ const ry = h / 2;
1369
+ return `M${cx},${y} L${x + w},${y} L${x + w},${cy} A${rx},${ry} 0 1 1 ${cx - rx},${cy} A${rx},${ry} 0 0 1 ${cx},${y} Z`;
1370
+ },
1371
+ arc: (x, y, w, h) => {
1372
+ const cx = x + w / 2;
1373
+ const cy = y + h / 2;
1374
+ const rx = w / 2;
1375
+ const ry = h / 2;
1376
+ return `M${cx + rx},${cy} A${rx},${ry} 0 1 1 ${cx},${cy + ry}`;
1377
+ },
1378
+ blockArc: (x, y, w, h) => {
1379
+ const cx = x + w / 2;
1380
+ const cy = y + h / 2;
1381
+ const outerR = Math.min(w, h) / 2;
1382
+ const innerR = outerR * .6;
1383
+ return `M${cx + outerR},${cy} A${outerR},${outerR} 0 1 1 ${cx},${cy - outerR} L${cx},${cy - innerR} A${innerR},${innerR} 0 1 0 ${cx + innerR},${cy} Z`;
1384
+ },
1385
+ moon: (x, y, w, h) => {
1386
+ const cx = x + w / 2;
1387
+ const cy = y + h / 2;
1388
+ const rx = w / 2;
1389
+ const ry = h / 2;
1390
+ const innerRx = rx * .78;
1391
+ const offsetX = rx * .32;
1392
+ return `M${cx + rx},${cy} A${rx},${ry} 0 1 1 ${cx - rx},${cy} A${rx},${ry} 0 1 1 ${cx + rx},${cy} M${cx - rx + offsetX + innerRx},${cy} A${innerRx},${ry * .95} 0 1 1 ${cx - rx + offsetX - innerRx},${cy} A${innerRx},${ry * .95} 0 1 1 ${cx - rx + offsetX + innerRx},${cy}`;
1393
+ },
1394
+ plus: (x, y, w, h) => {
1395
+ const t = Math.min(w, h) * .3;
1396
+ const cx = x + w / 2;
1397
+ const cy = y + h / 2;
1398
+ return `M${cx - t / 2},${y} L${cx + t / 2},${y} L${cx + t / 2},${cy - t / 2} L${x + w},${cy - t / 2} L${x + w},${cy + t / 2} L${cx + t / 2},${cy + t / 2} L${cx + t / 2},${y + h} L${cx - t / 2},${y + h} L${cx - t / 2},${cy + t / 2} L${x},${cy + t / 2} L${x},${cy - t / 2} L${cx - t / 2},${cy - t / 2} Z`;
1399
+ },
1400
+ plaque: (x, y, w, h) => {
1401
+ const r = Math.min(w, h) * .18;
1402
+ return `M${x + r},${y} L${x + w - r},${y} A${r},${r} 0 0 0 ${x + w},${y + r} L${x + w},${y + h - r} A${r},${r} 0 0 0 ${x + w - r},${y + h} L${x + r},${y + h} A${r},${r} 0 0 0 ${x},${y + h - r} L${x},${y + r} A${r},${r} 0 0 0 ${x + r},${y} Z`;
1403
+ },
1404
+ can: (x, y, w, h) => {
1405
+ const er = h * .12;
1406
+ return `M${x},${y + er} A${w / 2},${er} 0 0 1 ${x + w},${y + er} L${x + w},${y + h - er} A${w / 2},${er} 0 0 1 ${x},${y + h - er} Z M${x},${y + er} A${w / 2},${er} 0 0 0 ${x + w},${y + er}`;
1407
+ },
1408
+ cube: (x, y, w, h) => {
1409
+ const d = Math.min(w, h) * .2;
1410
+ return `M${x},${y + d} L${x + d},${y} L${x + w},${y} L${x + w},${y + h - d} L${x + w - d},${y + h} L${x},${y + h} Z M${x},${y + d} L${x + w - d},${y + d} L${x + w},${y} M${x + w - d},${y + d} L${x + w - d},${y + h}`;
1411
+ },
1412
+ bevel: (x, y, w, h) => {
1413
+ const d = Math.min(w, h) * .12;
1414
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z M${x + d},${y + d} L${x + w - d},${y + d} L${x + w - d},${y + h - d} L${x + d},${y + h - d} Z M${x},${y} L${x + d},${y + d} M${x + w},${y} L${x + w - d},${y + d} M${x},${y + h} L${x + d},${y + h - d} M${x + w},${y + h} L${x + w - d},${y + h - d}`;
1415
+ },
1416
+ donut: (x, y, w, h) => {
1417
+ const cx = x + w / 2;
1418
+ const cy = y + h / 2;
1419
+ const r = Math.min(w, h) / 2;
1420
+ const innerR = r * .65;
1421
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r},${cy} A${r},${r} 0 1 0 ${cx + r},${cy} Z M${cx + innerR},${cy} A${innerR},${innerR} 0 1 1 ${cx - innerR},${cy} A${innerR},${innerR} 0 1 1 ${cx + innerR},${cy} Z`;
1422
+ },
1423
+ noSmoking: (x, y, w, h) => {
1424
+ const cx = x + w / 2;
1425
+ const cy = y + h / 2;
1426
+ const r = Math.min(w, h) / 2;
1427
+ const innerR = r * .78;
1428
+ const t = r * .12;
1429
+ return `M${cx + r},${cy} A${r},${r} 0 1 0 ${cx - r},${cy} A${r},${r} 0 1 0 ${cx + r},${cy} Z M${cx + innerR},${cy} A${innerR},${innerR} 0 1 1 ${cx - innerR},${cy} A${innerR},${innerR} 0 1 1 ${cx + innerR},${cy} Z M${cx - r * .71 - t},${cy - r * .71 + t} L${cx - r * .71 + t},${cy - r * .71 - t} L${cx + r * .71 + t},${cy + r * .71 - t} L${cx + r * .71 - t},${cy + r * .71 + t} Z`;
1430
+ },
1431
+ frame: (x, y, w, h) => {
1432
+ const f = Math.min(w, h) * .1;
1433
+ return `M${x},${y} L${x + w},${y} L${x + w},${y + h} L${x},${y + h} Z M${x + f},${y + f} L${x + f},${y + h - f} L${x + w - f},${y + h - f} L${x + w - f},${y + f} Z`;
1434
+ },
1435
+ halfFrame: (x, y, w, h) => {
1436
+ const f = Math.min(w, h) * .15;
1437
+ return `M${x},${y} L${x + w},${y} L${x + w - f},${y + f} L${x + f},${y + f} L${x + f},${y + h - f} L${x},${y + h} Z`;
1438
+ },
1439
+ corner: (x, y, w, h) => {
1440
+ const tx = w * .4;
1441
+ const ty = h * .4;
1442
+ return `M${x},${y} L${x + tx},${y} L${x + tx},${y + h - ty} L${x + w},${y + h - ty} L${x + w},${y + h} L${x},${y + h} Z`;
1443
+ },
1444
+ diagStripe: (x, y, w, h) => {
1445
+ return `M${x},${y} L${x + w * .6},${y} L${x},${y + h * .6} Z`;
1446
+ },
1447
+ ellipseRibbon: (x, y, w, h) => {
1448
+ const notch = w * .08;
1449
+ const bodyTop = y + h * .2;
1450
+ const bodyBot = y + h * .85;
1451
+ const arcDip = h * .15;
1452
+ return `M${x},${bodyTop} C${x + w * .3},${bodyTop - arcDip} ${x + w * .7},${bodyTop - arcDip} ${x + w},${bodyTop} L${x + w * .85},${bodyTop + (bodyBot - bodyTop) / 2} L${x + w},${bodyBot} L${x + w - notch * 2},${y + h} L${x + w - notch * 4},${bodyBot} C${x + w * .7},${bodyBot + arcDip * .4} ${x + w * .3},${bodyBot + arcDip * .4} L${x + notch * 4},${bodyBot} L${x + notch * 2},${y + h} L${x},${bodyBot} L${x + w * .15},${bodyTop + (bodyBot - bodyTop) / 2} Z`;
1453
+ },
1454
+ ellipseRibbon2: (x, y, w, h) => {
1455
+ const notch = w * .08;
1456
+ const bodyTop = y + h * .15;
1457
+ const bodyBot = y + h * .8;
1458
+ const arcRise = h * .15;
1459
+ return `M${x},${bodyBot} C${x + w * .3},${bodyBot + arcRise} ${x + w * .7},${bodyBot + arcRise} ${x + w},${bodyBot} L${x + w * .85},${bodyTop + (bodyBot - bodyTop) / 2} L${x + w},${bodyTop} L${x + w - notch * 2},${y} L${x + w - notch * 4},${bodyTop} C${x + w * .7},${bodyTop - arcRise * .4} ${x + w * .3},${bodyTop - arcRise * .4} L${x + notch * 4},${bodyTop} L${x + notch * 2},${y} L${x},${bodyTop} L${x + w * .15},${bodyTop + (bodyBot - bodyTop) / 2} Z`;
1460
+ },
1461
+ quadArrow: (x, y, w, h) => {
1462
+ const cx = x + w / 2;
1463
+ const cy = y + h / 2;
1464
+ const tip = .2;
1465
+ const stem = .15;
1466
+ const headW = .35;
1467
+ return `M${cx},${y} L${cx + headW * w},${y + tip * h} L${cx + stem * w},${y + tip * h} L${cx + stem * w},${cy - stem * h} L${x + w - tip * w},${cy - stem * h} L${x + w - tip * w},${cy - headW * h} L${x + w},${cy} L${x + w - tip * w},${cy + headW * h} L${x + w - tip * w},${cy + stem * h} L${cx + stem * w},${cy + stem * h} L${cx + stem * w},${y + h - tip * h} L${cx + headW * w},${y + h - tip * h} L${cx},${y + h} L${cx - headW * w},${y + h - tip * h} L${cx - stem * w},${y + h - tip * h} L${cx - stem * w},${cy + stem * h} L${x + tip * w},${cy + stem * h} L${x + tip * w},${cy + headW * h} L${x},${cy} L${x + tip * w},${cy - headW * h} L${x + tip * w},${cy - stem * h} L${cx - stem * w},${cy - stem * h} L${cx - stem * w},${y + tip * h} L${cx - headW * w},${y + tip * h} Z`;
1468
+ },
1469
+ leftRightUpArrow: (x, y, w, h) => {
1470
+ const cx = x + w / 2;
1471
+ const tip = .2;
1472
+ const stem = .15;
1473
+ const headW = .35;
1474
+ return `M${cx},${y} L${cx + headW * w},${y + tip * h} L${cx + stem * w},${y + tip * h} L${cx + stem * w},${y + h - tip * h} L${x + w - tip * w},${y + h - tip * h} L${x + w - tip * w},${y + h - headW * h} L${x + w},${y + h} L${x + w - tip * w},${y + h + headW * h} L${x + w - tip * w},${y + h - tip * h} L${cx - stem * w},${y + h - tip * h} L${cx - stem * w},${y + tip * h} L${cx - headW * w},${y + tip * h} Z`;
1475
+ },
1476
+ bentUpArrow: (x, y, w, h) => {
1477
+ const stem = .3;
1478
+ const tip = .25;
1479
+ return `M${x},${y + h * .55} L${x + w * .5},${y + h * .55} L${x + w * .5},${y + tip * h} L${x + w * (.5 - stem * .5)},${y + tip * h} L${x + w * .75},${y} L${x + w},${y + tip * h} L${x + w * .65},${y + tip * h} L${x + w * .65},${y + h * .55 + h * .4} L${x},${y + h * .55 + h * .4} Z`;
1480
+ },
1481
+ curvedLeftArrow: (x, y, w, h) => {
1482
+ return `M${x + w},${y + h * .6} Q${x + w * .5},${y} ${x + w * .15},${y + h * .25} L${x},${y + h * .45} L${x + w * .15},${y + h * .55} L${x + w * .3},${y + h * .35} Q${x + w * .55},${y + h * .18} ${x + w * .85},${y + h * .75} Z`;
1483
+ },
1484
+ curvedUpArrow: (x, y, w, h) => {
1485
+ return `M${x + w * .4},${y + h} Q${x},${y + h * .5} ${x + w * .25},${y + h * .15} L${x + w * .45},${y} L${x + w * .55},${y + h * .15} L${x + w * .35},${y + h * .3} Q${x + w * .18},${y + h * .55} ${x + w * .75},${y + h * .85} Z`;
1486
+ },
1487
+ curvedDownArrow: (x, y, w, h) => {
1488
+ return `M${x + w * .4},${y} Q${x},${y + h * .5} ${x + w * .25},${y + h * .85} L${x + w * .45},${y + h} L${x + w * .55},${y + h * .85} L${x + w * .35},${y + h * .7} Q${x + w * .18},${y + h * .45} ${x + w * .75},${y + h * .15} Z`;
1489
+ },
1490
+ swooshArrow: (x, y, w, h) => {
1491
+ return `M${x},${y + h * .75} C${x + w * .35},${y + h} ${x + w * .65},${y + h * .3} ${x + w * .75},${y + h * .2} L${x + w * .65},${y + h * .05} L${x + w},${y + h * .18} L${x + w * .78},${y + h * .45} L${x + w * .7},${y + h * .3} C${x + w * .55},${y + h * .6} ${x + w * .35},${y + h * .9} ${x},${y + h * .85} Z`;
1492
+ },
1493
+ circularArrow: (x, y, w, h) => {
1494
+ const cx = x + w / 2;
1495
+ const cy = y + h / 2;
1496
+ const outerR = Math.min(w, h) / 2;
1497
+ const innerR = outerR * .62;
1498
+ const midR = (outerR + innerR) / 2;
1499
+ return `M${cx},${cy - outerR} A${outerR},${outerR} 0 1 1 ${cx - outerR},${cy} L${cx - midR - midR * .25},${cy + outerR * .15} L${cx - midR + midR * .25},${cy + outerR * .3} L${cx - innerR},${cy} A${innerR},${innerR} 0 1 0 ${cx},${cy - innerR} Z`;
1500
+ },
1501
+ leftCircularArrow: (x, y, w, h) => {
1502
+ const cx = x + w / 2;
1503
+ const cy = y + h / 2;
1504
+ const outerR = Math.min(w, h) / 2;
1505
+ const innerR = outerR * .62;
1506
+ return `M${cx},${cy - outerR} A${outerR},${outerR} 0 1 0 ${cx + outerR},${cy} L${cx + (outerR + innerR) / 2 + (outerR + innerR) / 2 * .25},${cy + outerR * .15} L${cx + (outerR + innerR) / 2 - (outerR + innerR) / 2 * .25},${cy + outerR * .3} L${cx + innerR},${cy} A${innerR},${innerR} 0 1 1 ${cx},${cy - innerR} Z`;
1507
+ },
1508
+ leftRightCircularArrow: (x, y, w, h) => {
1509
+ const cx = x + w / 2;
1510
+ const cy = y + h / 2;
1511
+ const outerR = Math.min(w, h) / 2;
1512
+ const innerR = outerR * .62;
1513
+ return `M${cx - outerR * .15},${cy - outerR * 1.05} L${cx + outerR * .15},${cy - outerR * 1.05} L${cx + outerR * .1},${cy - outerR * .85} A${outerR},${outerR} 0 1 1 ${cx - outerR * .1},${cy - outerR * .85} Z M${cx},${cy - innerR} A${innerR},${innerR} 0 1 0 ${cx},${cy + innerR}`;
1514
+ },
1515
+ rightArrowCallout: (x, y, w, h) => {
1516
+ const headW = w * .25;
1517
+ return `M${x},${y + h * .3} L${x + w - headW},${y + h * .3} L${x + w - headW},${y} L${x + w},${y + h / 2} L${x + w - headW},${y + h} L${x + w - headW},${y + h * .7} L${x},${y + h * .7} Z`;
1518
+ },
1519
+ leftArrowCallout: (x, y, w, h) => {
1520
+ const headW = w * .25;
1521
+ return `M${x + headW},${y + h * .3} L${x + w},${y + h * .3} L${x + w},${y + h * .7} L${x + headW},${y + h * .7} L${x + headW},${y + h} L${x},${y + h / 2} L${x + headW},${y} Z`;
1522
+ },
1523
+ upArrowCallout: (x, y, w, h) => {
1524
+ const headH = h * .25;
1525
+ return `M${x + w * .3},${y + headH} L${x + w * .3},${y + h} L${x + w * .7},${y + h} L${x + w * .7},${y + headH} L${x + w},${y + headH} L${x + w / 2},${y} L${x},${y + headH} Z`;
1526
+ },
1527
+ downArrowCallout: (x, y, w, h) => {
1528
+ const headH = h * .25;
1529
+ return `M${x + w * .3},${y} L${x + w * .7},${y} L${x + w * .7},${y + h - headH} L${x + w},${y + h - headH} L${x + w / 2},${y + h} L${x},${y + h - headH} L${x + w * .3},${y + h - headH} Z`;
1530
+ },
1531
+ leftRightArrowCallout: (x, y, w, h) => {
1532
+ const headW = w * .2;
1533
+ return `M${x},${y + h / 2} L${x + headW},${y} L${x + headW},${y + h * .3} L${x + w - headW},${y + h * .3} L${x + w - headW},${y} L${x + w},${y + h / 2} L${x + w - headW},${y + h} L${x + w - headW},${y + h * .7} L${x + headW},${y + h * .7} L${x + headW},${y + h} Z`;
1534
+ },
1535
+ upDownArrowCallout: (x, y, w, h) => {
1536
+ const headH = h * .2;
1537
+ return `M${x + w / 2},${y} L${x},${y + headH} L${x + w * .3},${y + headH} L${x + w * .3},${y + h - headH} L${x},${y + h - headH} L${x + w / 2},${y + h} L${x + w},${y + h - headH} L${x + w * .7},${y + h - headH} L${x + w * .7},${y + headH} L${x + w},${y + headH} Z`;
1538
+ },
1539
+ quadArrowCallout: (x, y, w, h) => {
1540
+ const cx = x + w / 2;
1541
+ const cy = y + h / 2;
1542
+ const tip = .16;
1543
+ const stem = .18;
1544
+ const headW = .32;
1545
+ const txt = .28;
1546
+ return `M${cx - txt * w},${cy - txt * h} L${cx - txt * w},${cy - stem * h} L${cx - stem * w},${cy - stem * h} L${cx - stem * w},${y + tip * h} L${cx - headW * w},${y + tip * h} L${cx},${y} L${cx + headW * w},${y + tip * h} L${cx + stem * w},${y + tip * h} L${cx + stem * w},${cy - stem * h} L${cx + txt * w},${cy - stem * h} L${cx + txt * w},${cy - txt * h} L${x + w - tip * w},${cy - txt * h} L${x + w - tip * w},${cy - headW * h} L${x + w},${cy} L${x + w - tip * w},${cy + headW * h} L${x + w - tip * w},${cy + txt * h} L${cx + txt * w},${cy + txt * h} L${cx + txt * w},${cy + stem * h} L${cx + stem * w},${cy + stem * h} L${cx + stem * w},${y + h - tip * h} L${cx + headW * w},${y + h - tip * h} L${cx},${y + h} L${cx - headW * w},${y + h - tip * h} L${cx - stem * w},${y + h - tip * h} L${cx - stem * w},${cy + stem * h} L${cx - txt * w},${cy + stem * h} L${cx - txt * w},${cy + txt * h} L${x + tip * w},${cy + txt * h} L${x + tip * w},${cy + headW * h} L${x},${cy} L${x + tip * w},${cy - headW * h} L${x + tip * w},${cy - txt * h} Z`;
1547
+ },
1548
+ actionButtonHome: (x, y, w, h) => {
1549
+ const cx = x + w / 2;
1550
+ const cy = y + h / 2;
1551
+ const s = Math.min(w, h) * .3;
1552
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s},${cy + s * .6} L${cx - s},${cy - s * .1} L${cx},${cy - s * .7} L${cx + s},${cy - s * .1} L${cx + s},${cy + s * .6} L${cx + s * .3},${cy + s * .6} L${cx + s * .3},${cy + s * .1} L${cx - s * .3},${cy + s * .1} L${cx - s * .3},${cy + s * .6} Z`;
1553
+ },
1554
+ actionButtonForwardNext: (x, y, w, h) => {
1555
+ const cx = x + w / 2;
1556
+ const cy = y + h / 2;
1557
+ const s = Math.min(w, h) * .3;
1558
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s * .7},${cy - s} L${cx + s * .7},${cy} L${cx - s * .7},${cy + s} Z`;
1559
+ },
1560
+ actionButtonBackPrevious: (x, y, w, h) => {
1561
+ const cx = x + w / 2;
1562
+ const cy = y + h / 2;
1563
+ const s = Math.min(w, h) * .3;
1564
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx + s * .7},${cy - s} L${cx - s * .7},${cy} L${cx + s * .7},${cy + s} Z`;
1565
+ },
1566
+ actionButtonEnd: (x, y, w, h) => {
1567
+ const cx = x + w / 2;
1568
+ const cy = y + h / 2;
1569
+ const s = Math.min(w, h) * .3;
1570
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s},${cy - s} L${cx + s * .4},${cy} L${cx - s},${cy + s} Z M${cx + s * .5},${cy - s} L${cx + s},${cy - s} L${cx + s},${cy + s} L${cx + s * .5},${cy + s} Z`;
1571
+ },
1572
+ actionButtonBeginning: (x, y, w, h) => {
1573
+ const cx = x + w / 2;
1574
+ const cy = y + h / 2;
1575
+ const s = Math.min(w, h) * .3;
1576
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx + s},${cy - s} L${cx - s * .4},${cy} L${cx + s},${cy + s} Z M${cx - s * .5},${cy - s} L${cx - s},${cy - s} L${cx - s},${cy + s} L${cx - s * .5},${cy + s} Z`;
1577
+ },
1578
+ actionButtonReturn: (x, y, w, h) => {
1579
+ const cx = x + w / 2;
1580
+ const cy = y + h / 2;
1581
+ const s = Math.min(w, h) * .28;
1582
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx + s},${cy - s} L${cx + s * .4},${cy - s} L${cx + s * .4},${cy + s * .2} L${cx - s * .2},${cy + s * .2} L${cx - s * .2},${cy - s * .2} L${cx - s},${cy + s * .2} L${cx - s * .2},${cy + s} L${cx - s * .2},${cy + s * .5} L${cx + s},${cy + s * .5} Z`;
1583
+ },
1584
+ actionButtonHelp: (x, y, w, h) => {
1585
+ const cx = x + w / 2;
1586
+ const cy = y + h / 2;
1587
+ const s = Math.min(w, h) * .3;
1588
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s * .4},${cy - s * .4} Q${cx - s * .4},${cy - s} ${cx},${cy - s} Q${cx + s * .4},${cy - s} ${cx + s * .4},${cy - s * .4} Q${cx + s * .4},${cy} ${cx},${cy} L${cx},${cy + s * .4} M${cx - s * .18},${cy + s * .8} L${cx + s * .18},${cy + s * .8} L${cx + s * .18},${cy + s} L${cx - s * .18},${cy + s} Z`;
1589
+ },
1590
+ actionButtonInformation: (x, y, w, h) => {
1591
+ const cx = x + w / 2;
1592
+ const cy = y + h / 2;
1593
+ const s = Math.min(w, h) * .3;
1594
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s * .2},${cy - s * .7} L${cx + s * .2},${cy - s * .7} L${cx + s * .2},${cy - s * .35} L${cx - s * .2},${cy - s * .35} Z M${cx - s * .2},${cy - s * .1} L${cx + s * .2},${cy - s * .1} L${cx + s * .2},${cy + s} L${cx - s * .2},${cy + s} Z`;
1595
+ },
1596
+ actionButtonDocument: (x, y, w, h) => {
1597
+ const cx = x + w / 2;
1598
+ const cy = y + h / 2;
1599
+ const s = Math.min(w, h) * .3;
1600
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s * .6},${cy - s} L${cx + s * .3},${cy - s} L${cx + s * .6},${cy - s * .7} L${cx + s * .6},${cy + s} L${cx - s * .6},${cy + s} Z M${cx + s * .3},${cy - s} L${cx + s * .3},${cy - s * .7} L${cx + s * .6},${cy - s * .7}`;
1601
+ },
1602
+ actionButtonSound: (x, y, w, h) => {
1603
+ const cx = x + w / 2;
1604
+ const cy = y + h / 2;
1605
+ const s = Math.min(w, h) * .3;
1606
+ return `${PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""} M${cx - s},${cy - s * .4} L${cx - s * .3},${cy - s * .4} L${cx + s * .3},${cy - s} L${cx + s * .3},${cy + s} L${cx - s * .3},${cy + s * .4} L${cx - s},${cy + s * .4} Z M${cx + s * .55},${cy - s * .4} Q${cx + s * .95},${cy} ${cx + s * .55},${cy + s * .4}`;
1607
+ },
1608
+ actionButtonMovie: (x, y, w, h) => {
1609
+ const cx = x + w / 2;
1610
+ const cy = y + h / 2;
1611
+ const s = Math.min(w, h) * .3;
1612
+ const out = [PRESET_PATHS.actionButtonBlank?.(x, y, w, h) ?? ""];
1613
+ out.push(`M${cx - s},${cy - s * .6} L${cx + s},${cy - s * .6} L${cx + s},${cy + s * .6} L${cx - s},${cy + s * .6} Z`);
1614
+ for (let i = 0; i < 4; i++) {
1615
+ const px0 = cx - s + (i + .5) * (s * 2 / 4);
1616
+ out.push(`M${px0 - s * .08},${cy - s * .45} L${px0 + s * .08},${cy - s * .45} L${px0 + s * .08},${cy - s * .3} L${px0 - s * .08},${cy - s * .3} Z`);
1617
+ out.push(`M${px0 - s * .08},${cy + s * .3} L${px0 + s * .08},${cy + s * .3} L${px0 + s * .08},${cy + s * .45} L${px0 - s * .08},${cy + s * .45} Z`);
1618
+ }
1619
+ return out.join(" ");
1620
+ },
1621
+ borderCallout1: (x, y, w, h) => {
1622
+ return `M${x},${y} L${x + w * .6},${y} L${x + w * .6},${y + h * .5} L${x},${y + h * .5} Z M${x + w * .6},${y + h * .5} L${x + w},${y + h}`;
1623
+ },
1624
+ borderCallout2: (x, y, w, h) => {
1625
+ return `M${x},${y} L${x + w * .6},${y} L${x + w * .6},${y + h * .5} L${x},${y + h * .5} Z M${x + w * .6},${y + h * .5} L${x + w * .85},${y + h * .75} L${x + w},${y + h}`;
1626
+ },
1627
+ borderCallout3: (x, y, w, h) => {
1628
+ return `M${x},${y} L${x + w * .6},${y} L${x + w * .6},${y + h * .5} L${x},${y + h * .5} Z M${x + w * .6},${y + h * .5} L${x + w * .75},${y + h * .65} L${x + w * .9},${y + h * .65} L${x + w},${y + h}`;
1629
+ },
1630
+ accentCallout1: (x, y, w, h) => {
1631
+ return `M${x},${y} L${x + w * .6},${y} L${x + w * .6},${y + h * .5} L${x},${y + h * .5} Z M${x + w * .58},${y} L${x + w * .58},${y + h * .5} M${x + w * .6},${y + h * .5} L${x + w},${y + h}`;
1632
+ },
1633
+ accentBorderCallout1: (x, y, w, h) => {
1634
+ return PRESET_PATHS.accentCallout1?.(x, y, w, h) ?? "";
1635
+ },
1636
+ callout1: (x, y, w, h) => {
1637
+ return `M${x},${y + h * .5} L${x + w},${y + h}`;
1638
+ },
1639
+ callout2: (x, y, w, h) => {
1640
+ return `M${x},${y + h * .5} L${x + w * .6},${y + h * .7} L${x + w},${y + h}`;
1641
+ },
1642
+ callout3: (x, y, w, h) => {
1643
+ return `M${x},${y + h * .5} L${x + w * .4},${y + h * .55} L${x + w * .7},${y + h * .8} L${x + w},${y + h}`;
1644
+ },
1645
+ straightConnector1: (x, y, w, h) => `M${x},${y} L${x + w},${y + h}`,
1646
+ bentConnector2: (x, y, w, h) => `M${x},${y} L${x + w},${y} L${x + w},${y + h}`,
1647
+ bentConnector3: (x, y, w, h) => `M${x},${y} L${x + w / 2},${y} L${x + w / 2},${y + h} L${x + w},${y + h}`,
1648
+ bentConnector4: (x, y, w, h) => `M${x},${y} L${x + w * .33},${y} L${x + w * .33},${y + h * .5} L${x + w * .66},${y + h * .5} L${x + w * .66},${y + h} L${x + w},${y + h}`,
1649
+ bentConnector5: (x, y, w, h) => `M${x},${y} L${x + w * .25},${y} L${x + w * .25},${y + h * .5} L${x + w * .75},${y + h * .5} L${x + w * .75},${y + h} L${x + w},${y + h}`,
1650
+ curvedConnector2: (x, y, w, h) => `M${x},${y} Q${x + w},${y} ${x + w},${y + h}`,
1651
+ curvedConnector3: (x, y, w, h) => `M${x},${y} C${x + w * .5},${y} ${x + w * .5},${y + h} ${x + w},${y + h}`,
1652
+ curvedConnector4: (x, y, w, h) => `M${x},${y} C${x + w * .33},${y} ${x + w * .33},${y + h * .5} ${x + w * .5},${y + h * .5} C${x + w * .66},${y + h * .5} ${x + w * .66},${y + h} ${x + w},${y + h}`,
1653
+ curvedConnector5: (x, y, w, h) => `M${x},${y} C${x + w * .25},${y} ${x + w * .25},${y + h * .25} ${x + w * .5},${y + h * .5} C${x + w * .75},${y + h * .75} ${x + w * .75},${y + h} ${x + w},${y + h}`
1654
+ };
1655
+ const ALIGNMENT_TO_CSS = {
1656
+ left: "left",
1657
+ center: "center",
1658
+ right: "right",
1659
+ justify: "justify"
1660
+ };
1661
+ const ANCHOR_TO_CSS = {
1662
+ top: "flex-start",
1663
+ center: "center",
1664
+ bottom: "flex-end"
1665
+ };
1666
+ const placeholderDefaultPt = (phType) => {
1667
+ if (phType === "title" || phType === "ctrTitle") return DEFAULT_TITLE_PT;
1668
+ if (phType === "subTitle") return 32;
1669
+ if (phType === "ftr" || phType === "dt" || phType === "sldNum") return 12;
1670
+ return DEFAULT_BODY_PT;
1671
+ };
1672
+ const bulletChar = (level) => level <= 0 ? "•" : level === 1 ? "◦" : "▪";
1673
+ const bulletAutoNumType = (style) => {
1674
+ if (style === "number") return "arabicPeriod";
1675
+ if (style !== null && typeof style === "object" && "autoNum" in style) return style.autoNum ?? null;
1676
+ return null;
1677
+ };
1678
+ const toRoman = (n) => {
1679
+ if (n <= 0) return String(n);
1680
+ const map = [
1681
+ [1e3, "M"],
1682
+ [900, "CM"],
1683
+ [500, "D"],
1684
+ [400, "CD"],
1685
+ [100, "C"],
1686
+ [90, "XC"],
1687
+ [50, "L"],
1688
+ [40, "XL"],
1689
+ [10, "X"],
1690
+ [9, "IX"],
1691
+ [5, "V"],
1692
+ [4, "IV"],
1693
+ [1, "I"]
1694
+ ];
1695
+ let out = "";
1696
+ let r = n;
1697
+ for (const [v, s] of map) while (r >= v) {
1698
+ out += s;
1699
+ r -= v;
1700
+ }
1701
+ return out;
1702
+ };
1703
+ const toAlpha = (n) => {
1704
+ if (n <= 0) return String(n);
1705
+ let r = n;
1706
+ let out = "";
1707
+ while (r > 0) {
1708
+ r -= 1;
1709
+ out = String.fromCharCode(65 + r % 26) + out;
1710
+ r = Math.floor(r / 26);
1711
+ }
1712
+ return out;
1713
+ };
1714
+ const formatAutoNum = (token, n) => {
1715
+ const arabic = String(n);
1716
+ switch (token) {
1717
+ case "arabicPlain": return arabic;
1718
+ case "arabicPeriod": return `${arabic}.`;
1719
+ case "arabicParenR": return `${arabic})`;
1720
+ case "arabicParenBoth": return `(${arabic})`;
1721
+ case "romanUcPeriod": return `${toRoman(n)}.`;
1722
+ case "romanLcPeriod": return `${toRoman(n).toLowerCase()}.`;
1723
+ case "romanUcParenR": return `${toRoman(n)})`;
1724
+ case "romanLcParenR": return `${toRoman(n).toLowerCase()})`;
1725
+ case "romanUcParenBoth": return `(${toRoman(n)})`;
1726
+ case "romanLcParenBoth": return `(${toRoman(n).toLowerCase()})`;
1727
+ case "alphaUcPeriod": return `${toAlpha(n)}.`;
1728
+ case "alphaLcPeriod": return `${toAlpha(n).toLowerCase()}.`;
1729
+ case "alphaUcParenR": return `${toAlpha(n)})`;
1730
+ case "alphaLcParenR": return `${toAlpha(n).toLowerCase()})`;
1731
+ case "alphaUcParenBoth": return `(${toAlpha(n)})`;
1732
+ case "alphaLcParenBoth": return `(${toAlpha(n).toLowerCase()})`;
1733
+ default: return `${arabic}.`;
1734
+ }
1735
+ };
1736
+ const renderRun = (text, format, theme, effectivePt, _wasDefault = false) => {
1737
+ if (text === "") return "";
1738
+ const styles = [];
1739
+ styles.push(`font-size:${(effectivePt * PX_PER_PT).toFixed(2)}px`);
1740
+ styles.push(`line-height:1.05`);
1741
+ if (format?.font) styles.push(`font-family:${escapeXml(format.font)}, ${DEFAULT_FONT}`);
1742
+ if (format?.bold) styles.push("font-weight:700");
1743
+ if (format?.italic) styles.push("font-style:italic");
1744
+ const underline = format?.underline;
1745
+ const strike = format?.strike;
1746
+ const hasUnderline = underline !== void 0 && underline !== false && underline !== "none";
1747
+ const hasStrike = strike !== void 0 && strike !== false && strike !== "noStrike";
1748
+ if (hasUnderline && hasStrike) styles.push("text-decoration:underline line-through");
1749
+ else if (hasUnderline) styles.push("text-decoration:underline");
1750
+ else if (hasStrike) styles.push("text-decoration:line-through");
1751
+ if (format?.color !== void 0 && format.color !== null) styles.push(`color:${resolveColor(format.color, theme, "#000000")}`);
1752
+ if (format?.spc !== void 0 && format.spc !== 0) {
1753
+ const trackingPx = format.spc / 100 * PX_PER_PT;
1754
+ styles.push(`letter-spacing:${trackingPx.toFixed(3)}px`);
1755
+ }
1756
+ if (format?.baseline !== void 0 && format.baseline !== 0) {
1757
+ const direction = format.baseline > 0 ? "super" : "sub";
1758
+ styles.push(`vertical-align:${direction}`);
1759
+ styles.push("font-size:0.65em");
1760
+ }
1761
+ if (format?.cap === "all") styles.push("text-transform:uppercase");
1762
+ else if (format?.cap === "small") styles.push("font-variant:small-caps");
1763
+ if (format?.highlight !== void 0 && format.highlight !== null) styles.push(`background-color:${resolveColor(format.highlight, theme, "#FFFF00")}`);
1764
+ const html = text.split("\n").map((part) => escapeXml(part)).join("<br/>");
1765
+ return `<span style="${styles.join(";")}">${html}</span>`;
1766
+ };
1767
+ const LINE_HEIGHT = 1.05;
1768
+ const AVG_GLYPH_W_RATIO = .55;
1769
+ const hasUnderlineFmt = (fmt) => {
1770
+ const u = fmt?.underline;
1771
+ return u !== void 0 && u !== false && u !== "none";
1772
+ };
1773
+ const hasStrikeFmt = (fmt) => {
1774
+ const s = fmt?.strike;
1775
+ return s !== void 0 && s !== false && s !== "noStrike";
1776
+ };
1777
+ const alignOf = (a) => a === "center" || a === "right" || a === "justify" ? a : "left";
1778
+ const verticalLayoutOf = (vert) => {
1779
+ switch (vert) {
1780
+ case "vert":
1781
+ case "eaVert":
1782
+ case "wordArtVert":
1783
+ case "wordArtVertRtl": return "cw90";
1784
+ case "vert270":
1785
+ case "mongolianVert": return "cw270";
1786
+ case null: return "none";
1787
+ }
1788
+ };
1789
+ const buildAndLayoutSvgText = (a) => {
1790
+ const scale = a.autoFitScale;
1791
+ const paragraphs = a.paraData.map((para, pi) => {
1792
+ const pieces = [];
1793
+ for (const run of para.runs) {
1794
+ if (run.text === "\n" && run.fmt === null) {
1795
+ pieces.push(breakPiece());
1796
+ continue;
1797
+ }
1798
+ let fmt = run.fmt;
1799
+ if (run.href) {
1800
+ const hlinkColor = a.theme ? normalizeHex(a.theme.hyperlink) : "#0563C1";
1801
+ fmt = {
1802
+ ...fmt,
1803
+ color: fmt?.color ?? hlinkColor,
1804
+ underline: fmt?.underline ?? true
1805
+ };
1806
+ }
1807
+ const family = substituteFamily(fmt?.font ?? a.themeFace);
1808
+ const sizePx = run.sizePt * scale * PX_PER_PT;
1809
+ const fillHex = fmt?.color !== void 0 && fmt.color !== null ? resolveColor(fmt.color, a.theme, "#000000") : a.defaultColor;
1810
+ const superSub = fmt?.baseline !== void 0 && fmt.baseline !== 0 ? fmt.baseline > 0 ? 1 : -1 : 0;
1811
+ const letterSpacingPx = fmt?.spc !== void 0 && fmt.spc !== 0 ? fmt.spc / 100 * PX_PER_PT : 0;
1812
+ const caps = fmt?.cap === "all" || fmt?.cap === "small";
1813
+ const base = {
1814
+ family,
1815
+ sizePx,
1816
+ bold: fmt?.bold ?? false,
1817
+ italic: fmt?.italic ?? false,
1818
+ letterSpacingPx,
1819
+ fillHex,
1820
+ underline: hasUnderlineFmt(fmt),
1821
+ strike: hasStrikeFmt(fmt),
1822
+ superSub,
1823
+ href: run.href ?? null
1824
+ };
1825
+ const segs = run.text.split("\n");
1826
+ segs.forEach((seg, i) => {
1827
+ pieces.push({
1828
+ ...base,
1829
+ text: caps ? seg.toUpperCase() : seg,
1830
+ isBreak: false
1831
+ });
1832
+ if (i < segs.length - 1) pieces.push(breakPiece());
1833
+ });
1834
+ }
1835
+ const marLpx = para.indent.leftEmu !== null ? para.indent.leftEmu / EMU_PER_PX * scale : para.level > 0 ? para.level * 24 * PX_PER_PT * scale : 0;
1836
+ const marRpx = para.indent.rightEmu !== null ? para.indent.rightEmu / EMU_PER_PX * scale : 0;
1837
+ const firstIndentPx = para.indent.firstLineEmu !== null ? para.indent.firstLineEmu / EMU_PER_PX * scale : 0;
1838
+ const spcBefPx = para.spcBefPts !== null && para.spcBefPts > 0 ? para.spcBefPts * PX_PER_PT * scale : 0;
1839
+ const spcAftPx = para.spcAftPts !== null && para.spcAftPts > 0 ? para.spcAftPts * PX_PER_PT * scale : 0;
1840
+ const lineSpacing = para.lineSpacing?.kind === "pct" ? {
1841
+ kind: "pct",
1842
+ value: para.lineSpacing.value
1843
+ } : para.lineSpacing?.kind === "pts" ? {
1844
+ kind: "pts",
1845
+ px: para.lineSpacing.value * PX_PER_PT * scale
1846
+ } : null;
1847
+ return {
1848
+ align: alignOf(para.align),
1849
+ marLpx,
1850
+ marRpx,
1851
+ firstIndentPx,
1852
+ spcBefPx,
1853
+ spcAftPx,
1854
+ lineSpacing,
1855
+ lineAdvanceScale: a.lineHeightScale,
1856
+ bullet: buildBullet(a, para, pi),
1857
+ pieces,
1858
+ fallbackSizePx: a.defaultPt * scale * PX_PER_PT
1859
+ };
1860
+ });
1861
+ return layoutTextSvg({
1862
+ boxXpx: a.innerX / EMU_PER_PX,
1863
+ boxYpx: a.innerY / EMU_PER_PX,
1864
+ boxWpx: a.innerW / EMU_PER_PX,
1865
+ boxHpx: a.innerH / EMU_PER_PX,
1866
+ anchor: a.anchor,
1867
+ wrap: a.wrap,
1868
+ paragraphs,
1869
+ vert: a.vert,
1870
+ columns: a.columns
1871
+ }, a.measure);
1872
+ };
1873
+ const breakPiece = () => ({
1874
+ text: "",
1875
+ family: "",
1876
+ sizePx: 0,
1877
+ bold: false,
1878
+ italic: false,
1879
+ letterSpacingPx: 0,
1880
+ fillHex: "#000000",
1881
+ underline: false,
1882
+ strike: false,
1883
+ superSub: 0,
1884
+ href: null,
1885
+ isBreak: true
1886
+ });
1887
+ const buildBullet = (a, para, pi) => {
1888
+ const explicitChar = para.bulletStyle !== null && typeof para.bulletStyle === "object" && "char" in para.bulletStyle ? para.bulletStyle.char : null;
1889
+ const numberLabel = a.numberLabels[pi] ?? null;
1890
+ if (!(para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0)) return null;
1891
+ const char = para.bulletIsPicture ? "■" : numberLabel ?? explicitChar ?? bulletChar(para.level);
1892
+ const baseSizePx = a.defaultPt * PX_PER_PT * a.autoFitScale;
1893
+ const sizePx = para.bulletDetail.sizePct !== null ? baseSizePx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * a.autoFitScale : baseSizePx;
1894
+ const fillHex = para.bulletDetail.color ? resolveColor(para.bulletDetail.color, a.theme, "#000000") : a.defaultColor;
1895
+ return {
1896
+ text: char,
1897
+ family: substituteFamily(para.bulletDetail.font ?? a.themeFace),
1898
+ sizePx,
1899
+ fillHex,
1900
+ ...para.bulletImageHref ? { imageHref: para.bulletImageHref } : {}
1901
+ };
1902
+ };
1903
+ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
1904
+ let paragraphCount;
1905
+ try {
1906
+ paragraphCount = getShapeParagraphCount(shape);
1907
+ } catch {
1908
+ return "";
1909
+ }
1910
+ if (paragraphCount === 0) return "";
1911
+ const defaultPt = placeholderDefaultPt(phType);
1912
+ const themeFonts = getPresentationFonts(pres);
1913
+ const themeFace = phType === "title" || phType === "ctrTitle" ? themeFonts?.majorLatin ?? null : themeFonts?.minorLatin ?? null;
1914
+ const effectiveDefaultFont = themeFace ? `${escapeXml(themeFace)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
1915
+ let effectiveBody;
1916
+ try {
1917
+ effectiveBody = getShapeBodyPrEffective(pres, shape);
1918
+ } catch {
1919
+ effectiveBody = {
1920
+ anchor: getShapeTextAnchor(shape),
1921
+ wrap: null,
1922
+ vert: getShapeTextDirection(shape),
1923
+ margins: getShapeTextMargins(shape) ?? {
1924
+ left: null,
1925
+ top: null,
1926
+ right: null,
1927
+ bottom: null
1928
+ }
1929
+ };
1930
+ }
1931
+ const isAutoshape = !isShapePlaceholder(shape) && !isShapeTextBox(shape);
1932
+ const defaultAlign = isAutoshape ? "center" : "left";
1933
+ const defaultAnchor = isAutoshape ? "center" : "top";
1934
+ const anchor = effectiveBody.anchor ?? defaultAnchor;
1935
+ const margins = effectiveBody.margins;
1936
+ const lIns = margins.left ?? DEFAULT_INSET_X;
1937
+ const tIns = margins.top ?? DEFAULT_INSET_Y;
1938
+ const rIns = margins.right ?? DEFAULT_INSET_X;
1939
+ 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);
1944
+ if (innerW <= 0 || innerH <= 0) return "";
1945
+ const paraData = [];
1946
+ let hasAnyText = false;
1947
+ for (let p = 0; p < paragraphCount; p++) {
1948
+ let effective;
1949
+ try {
1950
+ effective = getParagraphPropertiesEffective(pres, shape, p);
1951
+ } catch {
1952
+ effective = {
1953
+ align: getParagraphAlignment(shape, p),
1954
+ level: getParagraphLevel(shape, p),
1955
+ marL: null,
1956
+ marR: null,
1957
+ indent: null,
1958
+ lineSpacing: null,
1959
+ spcBefPts: null,
1960
+ spcAftPts: null,
1961
+ rtl: null,
1962
+ bullet: null
1963
+ };
1964
+ }
1965
+ const align = effective.align ?? defaultAlign;
1966
+ const level = effective.level;
1967
+ const bulletStyle = getParagraphBullet(shape, p) ?? effective.bullet;
1968
+ let bulletDetail = {
1969
+ color: null,
1970
+ sizePct: null,
1971
+ sizePts: null,
1972
+ font: null
1973
+ };
1974
+ try {
1975
+ bulletDetail = getParagraphBulletStyle(pres, shape, p);
1976
+ } catch {}
1977
+ let bulletIsPicture = false;
1978
+ try {
1979
+ bulletIsPicture = isParagraphBulletPicture(shape, p);
1980
+ } catch {}
1981
+ let bulletImageHref = null;
1982
+ if (bulletIsPicture) try {
1983
+ const bytes = getParagraphBulletImageBytes(shape, p);
1984
+ if (bytes) bulletImageHref = bytesToDataUrl(bytes);
1985
+ } catch {}
1986
+ const lineSpacing = effective.lineSpacing;
1987
+ let spcBefPts = effective.spcBefPts;
1988
+ let spcAftPts = effective.spcAftPts;
1989
+ const indent = {
1990
+ leftEmu: effective.marL,
1991
+ rightEmu: effective.marR,
1992
+ firstLineEmu: effective.indent
1993
+ };
1994
+ try {
1995
+ const spacing = getParagraphSpacing(shape, p);
1996
+ if (spacing.beforePts !== null) spcBefPts = spacing.beforePts;
1997
+ if (spacing.afterPts !== null) spcAftPts = spacing.afterPts;
1998
+ } catch {}
1999
+ try {
2000
+ const lit = getParagraphIndent(shape, p);
2001
+ if (lit.leftEmu !== null) indent.leftEmu = lit.leftEmu;
2002
+ if (lit.rightEmu !== null) indent.rightEmu = lit.rightEmu;
2003
+ if (lit.firstLineEmu !== null) indent.firstLineEmu = lit.firstLineEmu;
2004
+ } catch {}
2005
+ const runs = [];
2006
+ let elements = [];
2007
+ try {
2008
+ elements = getShapeParagraphElements(shape, p);
2009
+ } catch {
2010
+ elements = [];
2011
+ }
2012
+ let rIdx = 0;
2013
+ for (const el of elements) {
2014
+ if (el.kind === "br") {
2015
+ runs.push({
2016
+ text: "\n",
2017
+ fmt: null,
2018
+ sizePt: defaultPt
2019
+ });
2020
+ continue;
2021
+ }
2022
+ const txt = el.text;
2023
+ let fmt = el.format;
2024
+ let href;
2025
+ let hrefTip;
2026
+ if (el.kind === "r") {
2027
+ try {
2028
+ fmt = getShapeRunFormatEffective(pres, shape, p, rIdx);
2029
+ } catch {
2030
+ fmt = getShapeRunFormat(shape, p, rIdx);
2031
+ }
2032
+ try {
2033
+ href = getShapeRunHyperlink(shape, p, rIdx) ?? void 0;
2034
+ if (!href) {
2035
+ const act = getShapeRunClickAction(shape, p, rIdx);
2036
+ if (act?.kind === "slide") {
2037
+ const idx = getSlideIndex(pres, act.slide);
2038
+ if (idx >= 0) href = `#slide-${idx + 1}`;
2039
+ } else if (act?.kind === "url") href = act.url;
2040
+ }
2041
+ if (href) hrefTip = getShapeRunHyperlinkTooltip(shape, p, rIdx) ?? void 0;
2042
+ } catch {
2043
+ href = void 0;
2044
+ }
2045
+ rIdx++;
2046
+ }
2047
+ const sizePt = fmt?.size ?? defaultPt;
2048
+ if (txt) hasAnyText = true;
2049
+ runs.push({
2050
+ text: txt,
2051
+ fmt,
2052
+ sizePt,
2053
+ ...href !== void 0 ? { href } : {},
2054
+ ...hrefTip !== void 0 ? { hrefTip } : {}
2055
+ });
2056
+ }
2057
+ paraData.push({
2058
+ align,
2059
+ level,
2060
+ bulletStyle,
2061
+ bulletDetail,
2062
+ bulletIsPicture,
2063
+ bulletImageHref,
2064
+ runs,
2065
+ lineSpacing,
2066
+ spcBefPts,
2067
+ spcAftPts,
2068
+ indent
2069
+ });
2070
+ }
2071
+ if (!hasAnyText) return "";
2072
+ const authoredAutofit = getShapeTextAutoFitParams(shape);
2073
+ let autoFitScale = authoredAutofit?.fontScale ?? 1;
2074
+ let lineHeightScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
2075
+ if (!authoredAutofit) {
2076
+ const innerWPx = innerW / EMU_PER_PX;
2077
+ const innerHPx = innerH / EMU_PER_PX;
2078
+ let totalH = 0;
2079
+ for (const para of paraData) {
2080
+ let maxSize = defaultPt;
2081
+ let totalChars = 0;
2082
+ let cjkChars = 0;
2083
+ for (const run of para.runs) {
2084
+ if (run.sizePt > maxSize) maxSize = run.sizePt;
2085
+ totalChars += run.text.length;
2086
+ for (let i = 0; i < run.text.length; i++) {
2087
+ const c = run.text.charCodeAt(i);
2088
+ if (c >= 12352 && c <= 12447 || c >= 12448 && c <= 12543 || c >= 19968 && c <= 40959 || c >= 44032 && c <= 55215) cjkChars++;
2089
+ }
2090
+ }
2091
+ if (totalChars === 0) totalChars = 1;
2092
+ const cjkRatio = cjkChars / totalChars;
2093
+ const glyphRatio = cjkRatio * 1 + (1 - cjkRatio) * AVG_GLYPH_W_RATIO;
2094
+ const sizePx = maxSize * PX_PER_PT;
2095
+ const charsPerLine = Math.max(1, Math.floor(innerWPx / Math.max(1, sizePx * glyphRatio)));
2096
+ const lineCount = Math.max(1, Math.ceil(totalChars / charsPerLine));
2097
+ totalH += sizePx * LINE_HEIGHT * lineCount;
2098
+ }
2099
+ if (totalH > innerHPx) autoFitScale = Math.max(.4, innerHPx / totalH);
2100
+ }
2101
+ LINE_HEIGHT * lineHeightScale;
2102
+ const numberLabels = Array.from({ length: paraData.length }, () => null);
2103
+ {
2104
+ let counter = 0;
2105
+ let activeLevel = -1;
2106
+ let activeType = null;
2107
+ for (let i = 0; i < paraData.length; i++) {
2108
+ const para = paraData[i];
2109
+ const num = bulletAutoNumType(para.bulletStyle);
2110
+ if (num === null) {
2111
+ counter = 0;
2112
+ activeLevel = -1;
2113
+ activeType = null;
2114
+ continue;
2115
+ }
2116
+ if (para.level !== activeLevel || num !== activeType) {
2117
+ counter = 1;
2118
+ activeLevel = para.level;
2119
+ activeType = num;
2120
+ } else counter += 1;
2121
+ numberLabels[i] = formatAutoNum(num, counter);
2122
+ }
2123
+ }
2124
+ const paragraphs = [];
2125
+ for (let pi = 0; pi < paraData.length; pi++) {
2126
+ const para = paraData[pi];
2127
+ const runHtmls = para.runs.map((run) => {
2128
+ let runFmt = run.fmt;
2129
+ if (run.href) {
2130
+ const hlinkColor = theme ? normalizeHex(theme.hyperlink) : "#0563C1";
2131
+ runFmt = {
2132
+ ...runFmt,
2133
+ color: runFmt?.color ?? hlinkColor,
2134
+ underline: runFmt?.underline ?? true
2135
+ };
2136
+ }
2137
+ const span = renderRun(run.text, runFmt, theme, run.sizePt * autoFitScale, run.fmt?.size === void 0);
2138
+ if (!run.href) return span;
2139
+ const targetAttrs = run.href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
2140
+ const titleAttr = run.hrefTip ? ` title="${escapeXml(run.hrefTip)}"` : "";
2141
+ return `<a href="${escapeXml(run.href)}"${targetAttrs}${titleAttr} style="color:inherit;text-decoration:inherit">${span}</a>`;
2142
+ });
2143
+ let lineHeightCss = "";
2144
+ if (para.lineSpacing?.kind === "pct") lineHeightCss = `line-height:${para.lineSpacing.value.toFixed(3)}`;
2145
+ else if (para.lineSpacing?.kind === "pts") lineHeightCss = `line-height:${(para.lineSpacing.value * PX_PER_PT * autoFitScale).toFixed(2)}px`;
2146
+ const marginTopCss = para.spcBefPts !== null && para.spcBefPts > 0 ? `margin-top:${(para.spcBefPts * PX_PER_PT * autoFitScale).toFixed(2)}px` : "";
2147
+ const marginBottomCss = para.spcAftPts !== null && para.spcAftPts > 0 ? `margin-bottom:${(para.spcAftPts * PX_PER_PT * autoFitScale).toFixed(2)}px` : "";
2148
+ const leftPx = para.indent.leftEmu !== null ? para.indent.leftEmu / EMU_PER_PX * autoFitScale : para.level > 0 ? para.level * 24 * PX_PER_PT * autoFitScale : 0;
2149
+ const rightPx = para.indent.rightEmu !== null ? para.indent.rightEmu / EMU_PER_PX * autoFitScale : 0;
2150
+ const firstLinePx = para.indent.firstLineEmu !== null ? para.indent.firstLineEmu / EMU_PER_PX * autoFitScale : 0;
2151
+ const pStyles = [
2152
+ marginTopCss || (marginBottomCss ? "" : "margin:0"),
2153
+ marginBottomCss,
2154
+ "padding:0",
2155
+ `text-align:${ALIGNMENT_TO_CSS[para.align] ?? "left"}`,
2156
+ lineHeightCss,
2157
+ leftPx > 0 ? `padding-left:${leftPx.toFixed(2)}px` : "",
2158
+ rightPx > 0 ? `padding-right:${rightPx.toFixed(2)}px` : "",
2159
+ firstLinePx !== 0 ? `text-indent:${firstLinePx.toFixed(2)}px` : ""
2160
+ ].filter(Boolean);
2161
+ let prefix = "";
2162
+ const explicitChar = para.bulletStyle !== null && typeof para.bulletStyle === "object" && "char" in para.bulletStyle ? para.bulletStyle.char : null;
2163
+ const numberLabel = numberLabels[pi];
2164
+ const showBullet = para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0;
2165
+ if (showBullet && para.bulletImageHref) {
2166
+ const baseBulletPx = defaultPt * PX_PER_PT * autoFitScale;
2167
+ const bulletPx = para.bulletDetail.sizePct !== null ? baseBulletPx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * autoFitScale : baseBulletPx;
2168
+ const imgStyles = [
2169
+ `width:${bulletPx.toFixed(2)}px`,
2170
+ `height:${bulletPx.toFixed(2)}px`,
2171
+ "display:inline-block",
2172
+ "vertical-align:baseline",
2173
+ `margin-right:${(.4 * defaultPt * PX_PER_PT * autoFitScale).toFixed(2)}px`
2174
+ ];
2175
+ prefix = `<img src="${para.bulletImageHref}" alt="" style="${imgStyles.join(";")}"/>`;
2176
+ } else if (showBullet) {
2177
+ const char = para.bulletIsPicture ? "■" : numberLabel ?? explicitChar ?? bulletChar(para.level);
2178
+ const bulletStyles = [`margin-right:${(.4 * defaultPt * PX_PER_PT * autoFitScale).toFixed(2)}px`];
2179
+ if (para.bulletDetail.color) bulletStyles.push(`color:${resolveColor(para.bulletDetail.color, theme, "#000000")}`);
2180
+ if (para.bulletDetail.sizePct !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePct * 100).toFixed(1)}%`);
2181
+ 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}`);
2183
+ prefix = `<span style="${bulletStyles.join(";")}">${escapeXml(char)}</span>`;
2184
+ }
2185
+ paragraphs.push(`<p style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "&#8203;"}</p>`);
2186
+ }
2187
+ const justify = ANCHOR_TO_CSS[anchor] ?? "flex-start";
2188
+ const defaultColor = activeDeckTextColor;
2189
+ if (ctx.mode === "svg") {
2190
+ const svgScale = authoredAutofit?.fontScale ?? 1;
2191
+ const svgLineScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
2192
+ const svgVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
2193
+ const svgCols = getShapeTextColumns(shape);
2194
+ const svgColumns = svgVert === "none" && svgCols && svgCols.count >= 2 ? {
2195
+ count: svgCols.count,
2196
+ gapPx: svgCols.gapEmu !== void 0 ? svgCols.gapEmu / EMU_PER_PX : 12
2197
+ } : null;
2198
+ const svgInner = buildAndLayoutSvgText({
2199
+ pres,
2200
+ shape,
2201
+ theme,
2202
+ paraData,
2203
+ numberLabels,
2204
+ autoFitScale: svgScale,
2205
+ lineHeightScale: svgLineScale,
2206
+ defaultPt,
2207
+ themeFace,
2208
+ defaultColor,
2209
+ anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
2210
+ wrap: effectiveBody.wrap !== "none",
2211
+ innerX,
2212
+ innerY,
2213
+ innerW,
2214
+ innerH,
2215
+ measure: ctx.measure,
2216
+ vert: svgVert,
2217
+ columns: svgColumns
2218
+ });
2219
+ const bodyRotDegSvg = getShapeTextBodyRotationDeg(shape);
2220
+ if (bodyRotDegSvg !== null && bodyRotDegSvg !== 0) {
2221
+ const pivotX = innerX + innerW / 2;
2222
+ const pivotY = innerY + innerH / 2;
2223
+ return `<g transform="rotate(${bodyRotDegSvg} ${E(pivotX)} ${E(pivotY)})">${svgInner}</g>`;
2224
+ }
2225
+ return svgInner;
2226
+ }
2227
+ const vert = effectiveBody.vert ?? getShapeTextDirection(shape);
2228
+ let writingMode = "";
2229
+ let extraTransform = "";
2230
+ if (vert === "vert" || vert === "eaVert") writingMode = "writing-mode:vertical-rl";
2231
+ else if (vert === "vert270" || vert === "mongolianVert") {
2232
+ writingMode = "writing-mode:vertical-lr";
2233
+ if (vert === "vert270") extraTransform = ";transform:rotate(180deg)";
2234
+ } else if (vert === "wordArtVert") writingMode = "writing-mode:vertical-rl;text-orientation:upright";
2235
+ else if (vert === "wordArtVertRtl") writingMode = "writing-mode:vertical-rl;text-orientation:upright;direction:rtl";
2236
+ const cols = getShapeTextColumns(shape);
2237
+ let colStyles = "";
2238
+ if (cols && cols.count >= 2) {
2239
+ const gapPx = cols.gapEmu !== void 0 ? (cols.gapEmu / EMU_PER_PX).toFixed(2) : "12";
2240
+ colStyles = `;column-count:${cols.count};column-gap:${gapPx}px`;
2241
+ }
2242
+ const vertStyles = (writingMode ? `;${writingMode}${extraTransform}` : "") + colStyles;
2243
+ const body = `<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:visible;font-family:${effectiveDefaultFont};color:${defaultColor};${effectiveBody.wrap === "none" ? "white-space:nowrap" : "word-break:break-word"}${vertStyles}">${paragraphs.join("")}</div>`;
2244
+ const foreign = `<foreignObject x="${E(innerX)}" y="${E(innerY)}" width="${E(innerW)}" height="${E(innerH)}" overflow="visible">${body}</foreignObject>`;
2245
+ const bodyRotDeg = getShapeTextBodyRotationDeg(shape);
2246
+ if (bodyRotDeg !== null && bodyRotDeg !== 0) {
2247
+ const pivotX = innerX + innerW / 2;
2248
+ const pivotY = innerY + innerH / 2;
2249
+ return `<g transform="rotate(${bodyRotDeg} ${E(pivotX)} ${E(pivotY)})">${foreign}</g>`;
2250
+ }
2251
+ return foreign;
2252
+ };
2253
+ const E = (n) => (n / EMU_PER_PX).toFixed(2);
2254
+ const px = (n) => n.toFixed(2);
2255
+ const accentSequence = (theme) => {
2256
+ const fallbacks = [
2257
+ "#5B9BD5",
2258
+ "#ED7D31",
2259
+ "#A5A5A5",
2260
+ "#FFC000",
2261
+ "#4472C4",
2262
+ "#70AD47"
2263
+ ];
2264
+ if (!theme) return fallbacks;
2265
+ const hexes = [
2266
+ theme.accent1,
2267
+ theme.accent2,
2268
+ theme.accent3,
2269
+ theme.accent4,
2270
+ theme.accent5,
2271
+ theme.accent6
2272
+ ].map((c) => normalizeHex(c)).filter((c) => /^#[0-9A-Fa-f]{6}$/.test(c));
2273
+ return hexes.length > 0 ? hexes : fallbacks;
2274
+ };
2275
+ const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true) => {
2276
+ const x = xEmu / EMU_PER_PX;
2277
+ const y = yEmu / EMU_PER_PX;
2278
+ const w = wEmu / EMU_PER_PX;
2279
+ const h = hEmu / EMU_PER_PX;
2280
+ const titleStrip = hasTitle && !titleOverlay ? 18 : 0;
2281
+ const legendStrip = hasLegend && !legendOverlay ? 18 : 0;
2282
+ const padding = 8;
2283
+ const yAxisGutter = hasAxes ? 40 : 0;
2284
+ const xAxisGutter = hasAxes ? 18 : 0;
2285
+ return {
2286
+ x,
2287
+ y,
2288
+ w,
2289
+ h,
2290
+ plotX: x + padding + yAxisGutter,
2291
+ plotY: y + titleStrip + padding,
2292
+ plotW: Math.max(0, w - 2 * padding - yAxisGutter),
2293
+ plotH: Math.max(0, h - titleStrip - legendStrip - xAxisGutter - 2 * padding),
2294
+ titleY: y + (titleOverlay ? 14 : titleStrip - 2),
2295
+ legendY: y + h - (legendOverlay ? 18 : legendStrip / 2)
2296
+ };
2297
+ };
2298
+ const niceStep = (range, target = 5) => {
2299
+ if (range <= 0) return 1;
2300
+ const rawStep = range / target;
2301
+ const base = Math.pow(10, Math.floor(Math.log10(rawStep)));
2302
+ const m = rawStep / base;
2303
+ return (m < 1.5 ? 1 : m < 3 ? 2 : m < 7 ? 5 : 10) * base;
2304
+ };
2305
+ const niceTicks = (min, max, target = 5) => {
2306
+ const range = max - min;
2307
+ if (range <= 0) return [min];
2308
+ const step = niceStep(range, target);
2309
+ const start = Math.ceil(min / step) * step;
2310
+ const ticks = [];
2311
+ for (let v = start; v <= max + step / 2; v += step) ticks.push(v);
2312
+ return ticks;
2313
+ };
2314
+ const formatTick = (v) => {
2315
+ if (v === 0) return "0";
2316
+ const abs = Math.abs(v);
2317
+ if (abs >= 1e9) return `${(v / 1e9).toFixed(1)}B`;
2318
+ if (abs >= 1e6) return `${(v / 1e6).toFixed(1)}M`;
2319
+ if (abs >= 1e4) return `${(v / 1e3).toFixed(0)}K`;
2320
+ if (abs >= 1e3) return `${(v / 1e3).toFixed(1)}K`;
2321
+ if (Number.isInteger(v)) return v.toString();
2322
+ return v.toFixed(abs < 1 ? 2 : 1);
2323
+ };
2324
+ const formatAxisLabel = (v, formatCode) => {
2325
+ if (!formatCode) return formatTick(v);
2326
+ let prefix = "";
2327
+ let suffix = "";
2328
+ let body = formatCode;
2329
+ const leadMatch = /^"((?:\\.|[^"\\])*)"/.exec(body);
2330
+ if (leadMatch) {
2331
+ prefix = leadMatch[1].replace(/\\(.)/g, "$1");
2332
+ body = body.slice(leadMatch[0].length);
2333
+ } else if (body.startsWith("$") || body.startsWith("¥") || /^[£€]/.test(body)) {
2334
+ prefix = body[0];
2335
+ body = body.slice(1);
2336
+ }
2337
+ const tailMatch = /"((?:\\.|[^"\\])*)"$/.exec(body);
2338
+ if (tailMatch) {
2339
+ suffix = tailMatch[1].replace(/\\(.)/g, "$1");
2340
+ body = body.slice(0, body.length - tailMatch[0].length);
2341
+ }
2342
+ if (body.includes("%")) {
2343
+ const decMatch = /0\.(0+)%/.exec(body);
2344
+ const dec = decMatch ? decMatch[1].length : 0;
2345
+ return prefix + `${(v * 100).toFixed(dec)}%` + suffix;
2346
+ }
2347
+ if (body.includes("#,##") || /\.(0+)/.test(body) || /^0+$/.test(body)) return prefix + formatWithGrouping(v, body) + suffix;
2348
+ return prefix + formatTick(v) + suffix;
2349
+ };
2350
+ const formatWithGrouping = (v, fmt) => {
2351
+ const decMatch = /\.(0+)/.exec(fmt);
2352
+ const dec = decMatch ? decMatch[1].length : 0;
2353
+ const [intPart, fracPart] = Math.abs(v).toFixed(dec).split(".");
2354
+ const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2355
+ return (v < 0 ? "-" : "") + grouped + (fracPart ? `.${fracPart}` : "");
2356
+ };
2357
+ const DISPLAY_UNIT_DIVISOR = {
2358
+ hundreds: 100,
2359
+ thousands: 1e3,
2360
+ tenThousands: 1e4,
2361
+ hundredThousands: 1e5,
2362
+ millions: 1e6,
2363
+ tenMillions: 1e7,
2364
+ hundredMillions: 1e8,
2365
+ billions: 1e9,
2366
+ trillions: 0xe8d4a51000
2367
+ };
2368
+ const DISPLAY_UNIT_LABEL = {
2369
+ hundreds: "Hundreds",
2370
+ thousands: "Thousands",
2371
+ tenThousands: "Ten Thousands",
2372
+ hundredThousands: "Hundred Thousands",
2373
+ millions: "Millions",
2374
+ tenMillions: "Ten Millions",
2375
+ hundredMillions: "Hundred Millions",
2376
+ billions: "Billions",
2377
+ trillions: "Trillions"
2378
+ };
2379
+ const DEFAULT_AXIS_COLOR = "#000000";
2380
+ const DEFAULT_GRID_COLOR = "#D9D9D9";
2381
+ const AXIS_TICK_LEN = 5;
2382
+ const axisTickAttrs = (style) => {
2383
+ const sz = style?.sizePt ?? 10;
2384
+ const fill = style?.color ?? "#6B7280";
2385
+ const weight = style?.bold ? " font-weight=\"600\"" : "";
2386
+ const italic = style?.italic ? " font-style=\"italic\"" : "";
2387
+ return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
2388
+ };
2389
+ const renderValueAxis = (f, axis) => {
2390
+ const ticks = axis.majorUnit ? (() => {
2391
+ const out = [];
2392
+ const start = Math.ceil(axis.min / axis.majorUnit) * axis.majorUnit;
2393
+ for (let t = start; t <= axis.max + 1e-9; t += axis.majorUnit) out.push(t);
2394
+ return out.length > 0 ? out : niceTicks(axis.min, axis.max);
2395
+ })() : niceTicks(axis.min, axis.max);
2396
+ const out = [];
2397
+ const range = axis.max - axis.min || 1;
2398
+ const fmtTick = (t) => axis.percent ? `${Math.round(t * 100)}%` : formatAxisLabel(axis.displayUnits ? t / DISPLAY_UNIT_DIVISOR[axis.displayUnits] : t, axis.numberFormat);
2399
+ const showGrid = axis.majorGridlines ?? false;
2400
+ const gridStroke = axis.majorGridlineColor ?? DEFAULT_GRID_COLOR;
2401
+ const axisColor = axis.lineColor ?? DEFAULT_AXIS_COLOR;
2402
+ const tickMark = axis.majorTickMark ?? "out";
2403
+ const tickLen = AXIS_TICK_LEN;
2404
+ for (const t of ticks) if (axis.orientation === "vertical") {
2405
+ const yp = f.plotY + f.plotH - (t - axis.min) / range * f.plotH;
2406
+ if (showGrid) out.push(`<line x1="${px(f.plotX)}" y1="${px(yp)}" x2="${px(f.plotX + f.plotW)}" y2="${px(yp)}" stroke="${gridStroke}" stroke-width="0.5"/>`);
2407
+ if (tickMark !== "none") {
2408
+ const tx1 = tickMark === "in" ? f.plotX : f.plotX - tickLen;
2409
+ const tx2 = tickMark === "out" ? f.plotX : tickMark === "cross" ? f.plotX + tickLen : f.plotX + tickLen;
2410
+ out.push(`<line x1="${px(tx1)}" y1="${px(yp)}" x2="${px(tx2)}" y2="${px(yp)}" stroke="${axisColor}" stroke-width="1"/>`);
2411
+ }
2412
+ const labelX = f.plotX - 4;
2413
+ const rot = axis.labelRotationDeg ?? 0;
2414
+ const transform = rot ? ` transform="rotate(${rot} ${px(labelX)} ${px(yp)})"` : "";
2415
+ out.push(`<text x="${px(labelX)}" y="${px(yp)}" text-anchor="end" dominant-baseline="middle" ${axisTickAttrs(axis.labelStyle)}${transform}>${escapeXml(fmtTick(t))}</text>`);
2416
+ } else {
2417
+ const xp = f.plotX + (t - axis.min) / range * f.plotW;
2418
+ if (showGrid) out.push(`<line x1="${px(xp)}" y1="${px(f.plotY)}" x2="${px(xp)}" y2="${px(f.plotY + f.plotH)}" stroke="${gridStroke}" stroke-width="0.5"/>`);
2419
+ if (tickMark !== "none") {
2420
+ const baseY = f.plotY + f.plotH;
2421
+ const ty1 = tickMark === "in" ? baseY : baseY + tickLen;
2422
+ const ty2 = tickMark === "out" ? baseY : tickMark === "cross" ? baseY - tickLen : baseY - tickLen;
2423
+ out.push(`<line x1="${px(xp)}" y1="${px(ty1)}" x2="${px(xp)}" y2="${px(ty2)}" stroke="${axisColor}" stroke-width="1"/>`);
2424
+ }
2425
+ const horizLabelY = f.plotY + f.plotH + 12;
2426
+ const rotH = axis.labelRotationDeg ?? 0;
2427
+ const transformH = rotH ? ` transform="rotate(${rotH} ${px(xp)} ${px(horizLabelY)})"` : "";
2428
+ out.push(`<text x="${px(xp)}" y="${px(horizLabelY)}" text-anchor="middle" dominant-baseline="middle" ${axisTickAttrs(axis.labelStyle)}${transformH}>${escapeXml(fmtTick(t))}</text>`);
2429
+ }
2430
+ if (axis.displayUnits) {
2431
+ const lbl = DISPLAY_UNIT_LABEL[axis.displayUnits];
2432
+ if (axis.orientation === "vertical") {
2433
+ const lblX = f.plotX - 26;
2434
+ const lblY = f.plotY + f.plotH / 2;
2435
+ out.push(`<text x="${px(lblX)}" y="${px(lblY)}" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#6B7280" font-style="italic" transform="rotate(-90 ${px(lblX)} ${px(lblY)})">${escapeXml(lbl)}</text>`);
2436
+ } else out.push(`<text x="${px(f.plotX + f.plotW)}" y="${px(f.plotY + f.plotH + 22)}" text-anchor="end" font-family="sans-serif" font-size="9" fill="#6B7280" font-style="italic">${escapeXml(lbl)}</text>`);
2437
+ }
2438
+ if (axis.orientation === "vertical") out.push(`<line x1="${px(f.plotX)}" y1="${px(f.plotY)}" x2="${px(f.plotX)}" y2="${px(f.plotY + f.plotH)}" stroke="${axisColor}" stroke-width="1"/>`);
2439
+ else {
2440
+ const zeroY = f.plotY + f.plotH - (0 - axis.min) / range * f.plotH;
2441
+ const spineY = axis.min <= 0 && axis.max >= 0 ? zeroY : f.plotY + f.plotH;
2442
+ out.push(`<line x1="${px(f.plotX)}" y1="${px(spineY)}" x2="${px(f.plotX + f.plotW)}" y2="${px(spineY)}" stroke="${axisColor}" stroke-width="1"/>`);
2443
+ }
2444
+ return out.join("");
2445
+ };
2446
+ const renderCategoryAxis = (f, orientation, cats, pointCount, skip = 1, labelStyle, labelRotationDeg, labelAlign, lineColor) => {
2447
+ const labels = [];
2448
+ for (let i = 0; i < pointCount; i++) labels.push(cats[i] ?? (i + 1).toString());
2449
+ const out = [];
2450
+ if (orientation === "horizontal") {
2451
+ const step = pointCount > 1 ? f.plotW / pointCount : 0;
2452
+ const truncLen = labelRotationDeg && Math.abs(labelRotationDeg) >= 30 ? 28 : 14;
2453
+ for (let i = 0; i < pointCount; i++) {
2454
+ if (skip > 1 && i % skip !== 0) continue;
2455
+ const cx = f.plotX + (i + .5) * step;
2456
+ const cy = f.plotY + f.plotH + 12;
2457
+ const truncated = labels[i] !== void 0 && labels[i].length > truncLen ? `${labels[i].slice(0, truncLen - 2)}…` : labels[i] ?? "";
2458
+ const transform = labelRotationDeg && labelRotationDeg !== 0 ? ` transform="rotate(${labelRotationDeg} ${px(cx)} ${px(cy)})"` : "";
2459
+ const anchor = labelAlign === "l" ? "start" : labelAlign === "r" ? "end" : labelAlign === "ctr" ? "middle" : labelRotationDeg && labelRotationDeg > 0 ? "end" : labelRotationDeg && labelRotationDeg < 0 ? "start" : "middle";
2460
+ out.push(`<text x="${px(cx)}" y="${px(cy)}" text-anchor="${anchor}" dominant-baseline="middle" ${axisTickAttrs(labelStyle)}${transform}>${escapeXml(truncated)}</text>`);
2461
+ }
2462
+ } else {
2463
+ const step = pointCount > 0 ? f.plotH / pointCount : 0;
2464
+ const truncLen = labelRotationDeg && Math.abs(labelRotationDeg) >= 30 ? 28 : 14;
2465
+ for (let i = 0; i < pointCount; i++) {
2466
+ if (skip > 1 && i % skip !== 0) continue;
2467
+ const cy = f.plotY + (pointCount - 1 - i + .5) * step;
2468
+ const lx = f.plotX - 4;
2469
+ const truncated = labels[i] !== void 0 && labels[i].length > truncLen ? `${labels[i].slice(0, truncLen - 2)}…` : labels[i] ?? "";
2470
+ const transform = labelRotationDeg && labelRotationDeg !== 0 ? ` transform="rotate(${labelRotationDeg} ${px(lx)} ${px(cy)})"` : "";
2471
+ out.push(`<text x="${px(lx)}" y="${px(cy)}" text-anchor="end" dominant-baseline="middle" ${axisTickAttrs(labelStyle)}${transform}>${escapeXml(truncated)}</text>`);
2472
+ }
2473
+ }
2474
+ const axisColor = lineColor ?? DEFAULT_AXIS_COLOR;
2475
+ if (orientation === "horizontal") {
2476
+ const baseY = f.plotY + f.plotH;
2477
+ out.push(`<line x1="${px(f.plotX)}" y1="${px(baseY)}" x2="${px(f.plotX + f.plotW)}" y2="${px(baseY)}" stroke="${axisColor}" stroke-width="1"/>`);
2478
+ const stepB = pointCount > 0 ? f.plotW / pointCount : 0;
2479
+ for (let i = 0; i <= pointCount; i++) {
2480
+ const bx = f.plotX + i * stepB;
2481
+ out.push(`<line x1="${px(bx)}" y1="${px(baseY)}" x2="${px(bx)}" y2="${px(baseY + AXIS_TICK_LEN)}" stroke="${axisColor}" stroke-width="1"/>`);
2482
+ }
2483
+ } else {
2484
+ out.push(`<line x1="${px(f.plotX)}" y1="${px(f.plotY)}" x2="${px(f.plotX)}" y2="${px(f.plotY + f.plotH)}" stroke="${axisColor}" stroke-width="1"/>`);
2485
+ const stepB = pointCount > 0 ? f.plotH / pointCount : 0;
2486
+ for (let i = 0; i <= pointCount; i++) {
2487
+ const by = f.plotY + i * stepB;
2488
+ out.push(`<line x1="${px(f.plotX - AXIS_TICK_LEN)}" y1="${px(by)}" x2="${px(f.plotX)}" y2="${px(by)}" stroke="${axisColor}" stroke-width="1"/>`);
2489
+ }
2490
+ }
2491
+ return out.join("");
2492
+ };
2493
+ const seriesMinMax = (spec) => {
2494
+ const isStacked = spec.grouping === "stacked" || spec.grouping === "percentStacked";
2495
+ const isPercent = spec.grouping === "percentStacked";
2496
+ let min = Infinity;
2497
+ let max = -Infinity;
2498
+ if (isPercent) {
2499
+ min = 0;
2500
+ max = 1;
2501
+ } else if (isStacked) {
2502
+ const N = spec.series.reduce((n, s) => Math.max(n, s.values.length), 0);
2503
+ for (let c = 0; c < N; c++) {
2504
+ let pos = 0;
2505
+ let neg = 0;
2506
+ for (const s of spec.series) {
2507
+ const v = s.values[c];
2508
+ if (v !== null && v !== void 0 && Number.isFinite(v)) if (v >= 0) pos += v;
2509
+ else neg += v;
2510
+ }
2511
+ if (pos > max) max = pos;
2512
+ if (neg < min) min = neg;
2513
+ }
2514
+ } else for (const s of spec.series) for (const v of s.values) if (v !== null && Number.isFinite(v)) {
2515
+ if (v < min) min = v;
2516
+ if (v > max) max = v;
2517
+ }
2518
+ if (!Number.isFinite(min)) min = 0;
2519
+ if (!Number.isFinite(max)) max = 1;
2520
+ if (max === min) max = min + 1;
2521
+ if (min > 0) min = 0;
2522
+ if (isPercent) return {
2523
+ min: 0,
2524
+ max: 1,
2525
+ step: .2
2526
+ };
2527
+ const step = niceStep(max - min);
2528
+ max = (Math.floor(max / step) + 1) * step;
2529
+ if (spec.valueAxis?.min !== void 0) min = spec.valueAxis.min;
2530
+ if (spec.valueAxis?.max !== void 0) max = spec.valueAxis.max;
2531
+ if (max === min) max = min + 1;
2532
+ return {
2533
+ min,
2534
+ max,
2535
+ step
2536
+ };
2537
+ };
2538
+ const renderChartTitle = (f, title, style) => {
2539
+ if (!title) return "";
2540
+ const sz = style?.sizePt ?? 13;
2541
+ const fill = style?.color ?? "#1F2937";
2542
+ const weight = style?.bold === false ? "400" : "600";
2543
+ 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>`;
2545
+ };
2546
+ const renderChartLegend = (f, names, colors, position = "b", textStyle, markerSymbols) => {
2547
+ if (names.length === 0) return "";
2548
+ const sz = textStyle?.sizePt ?? 11;
2549
+ const fill = textStyle?.color ?? "#374151";
2550
+ const weight = textStyle?.bold ? " font-weight=\"600\"" : "";
2551
+ const italic = textStyle?.italic ? " font-style=\"italic\"" : "";
2552
+ const textAttrs = `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
2553
+ const swatch = (i, swatchX, swatchY) => {
2554
+ const color = colors[i % colors.length];
2555
+ const sym = markerSymbols?.[i];
2556
+ if (markerSymbols !== void 0 && sym !== "none") {
2557
+ const r = 4.5;
2558
+ return seriesMarker(autoMarkerSymbol(sym, i), swatchX + r, swatchY + r, r, color);
2559
+ }
2560
+ return `<rect x="${px(swatchX)}" y="${px(swatchY)}" width="9" height="9" fill="${color}"/>`;
2561
+ };
2562
+ const out = [];
2563
+ if (position === "b") {
2564
+ const itemPx = Math.min(140, f.w / names.length);
2565
+ const totalW = itemPx * names.length;
2566
+ const startX = f.x + (f.w - totalW) / 2;
2567
+ for (let i = 0; i < names.length; i++) {
2568
+ const swatchX = startX + i * itemPx + 4;
2569
+ const swatchY = f.legendY - 4;
2570
+ const labelX = swatchX + 14;
2571
+ out.push(swatch(i, swatchX, swatchY), `<text x="${px(labelX)}" y="${px(f.legendY)}" dominant-baseline="middle" ${textAttrs}>${escapeXml(names[i] ?? `Series ${i + 1}`)}</text>`);
2572
+ }
2573
+ return out.join("");
2574
+ }
2575
+ if (position === "t") {
2576
+ const itemPx = Math.min(140, f.w / names.length);
2577
+ const totalW = itemPx * names.length;
2578
+ const startX = f.x + (f.w - totalW) / 2;
2579
+ const yTop = f.y + 4;
2580
+ for (let i = 0; i < names.length; i++) {
2581
+ const cx = startX + i * itemPx;
2582
+ out.push(swatch(i, cx + 4, yTop), `<text x="${px(cx + 18)}" y="${px(yTop + 8)}" dominant-baseline="middle" ${textAttrs}>${escapeXml(names[i] ?? `Series ${i + 1}`)}</text>`);
2583
+ }
2584
+ return out.join("");
2585
+ }
2586
+ const lineH = 14;
2587
+ const totalH = names.length * lineH;
2588
+ const yStart = position === "tr" ? f.y + 12 : Math.max(f.y + 12, f.y + (f.h - totalH) / 2);
2589
+ const xCol = position === "l" ? f.x + 6 : position === "tr" ? f.x + f.w - 100 : f.x + f.w - 100;
2590
+ for (let i = 0; i < names.length; i++) {
2591
+ const yp = yStart + i * lineH;
2592
+ out.push(swatch(i, xCol, yp - 4), `<text x="${px(xCol + 14)}" y="${px(yp + 4)}" dominant-baseline="middle" ${textAttrs}>${escapeXml(names[i] ?? `Series ${i + 1}`)}</text>`);
2593
+ }
2594
+ return out.join("");
2595
+ };
2596
+ const pointCount = (spec) => {
2597
+ if (spec.categories.length > 0) return spec.categories.length;
2598
+ let n = 0;
2599
+ for (const s of spec.series) if (s.values.length > n) n = s.values.length;
2600
+ return n;
2601
+ };
2602
+ const renderColumnChart = (f, spec, colors) => {
2603
+ const N = pointCount(spec);
2604
+ if (N === 0 || spec.series.length === 0) return "";
2605
+ const grouping = spec.grouping ?? "clustered";
2606
+ const isStacked = grouping === "stacked" || grouping === "percentStacked";
2607
+ const isPercent = grouping === "percentStacked";
2608
+ const { min, max } = seriesMinMax(spec);
2609
+ const range = max - min || 1;
2610
+ const groupW = f.plotW / N;
2611
+ const gapPctC = (spec.gapWidthPct ?? 150) / 100;
2612
+ const overlapPctC = (spec.overlapPct ?? (isStacked ? 100 : 0)) / 100;
2613
+ const Sc = spec.series.length;
2614
+ const clusterUnitsC = isStacked ? 1 : 1 + (Sc - 1) * (1 - overlapPctC);
2615
+ const barW = groupW / Math.max(.5, clusterUnitsC + gapPctC);
2616
+ const baseY = f.plotY + f.plotH - (0 - min) / range * f.plotH;
2617
+ const showLabelFor = (s) => spec.series[s]?.dataLabels?.showValue ?? spec.dataLabels?.showValue ?? false;
2618
+ const out = [];
2619
+ for (let c = 0; c < N; c++) if (isStacked) {
2620
+ let total = 0;
2621
+ if (isPercent) for (const s of spec.series) total += Math.max(0, s.values[c] ?? 0);
2622
+ let posAcc = 0;
2623
+ let negAcc = 0;
2624
+ for (let s = 0; s < spec.series.length; s++) {
2625
+ let v = spec.series[s]?.values[c] ?? 0;
2626
+ if (isPercent) {
2627
+ if (total === 0) continue;
2628
+ v = Math.max(0, v) / total;
2629
+ }
2630
+ const base = v >= 0 ? posAcc : negAcc;
2631
+ const stackedTop = base + v;
2632
+ const stackedBase = base;
2633
+ const x0 = f.plotX + c * groupW + (groupW - barW) / 2;
2634
+ const y0 = f.plotY + f.plotH - (Math.max(stackedTop, stackedBase) - min) / range * f.plotH;
2635
+ const y1 = f.plotY + f.plotH - (Math.min(stackedTop, stackedBase) - min) / range * f.plotH;
2636
+ const h = Math.abs(y1 - y0);
2637
+ out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(barW)}" height="${px(h)}" fill="${spec.series[s]?.color ?? colors[s % colors.length]}"/>`);
2638
+ if (showLabelFor(s) && Math.abs(v) > 0) {
2639
+ const labelY = (y0 + y1) / 2 + 3;
2640
+ const labelText = isPercent ? `${Math.round(v * 100)}%` : formatDataLabelValue(spec, s, v);
2641
+ out.push(`<text x="${px(x0 + barW / 2)}" y="${px(labelY)}" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#FFFFFF" font-weight="600">${labelText}</text>`);
2642
+ }
2643
+ if (v >= 0) posAcc = stackedTop;
2644
+ else negAcc = stackedTop;
2645
+ }
2646
+ } else {
2647
+ const clusterW = barW * clusterUnitsC;
2648
+ const clusterStartX = f.plotX + c * groupW + (groupW - clusterW) / 2;
2649
+ const stride = barW * (1 - overlapPctC);
2650
+ for (let s = 0; s < spec.series.length; s++) {
2651
+ const v = spec.series[s]?.values[c] ?? 0;
2652
+ const x0 = clusterStartX + s * stride;
2653
+ const top = f.plotY + f.plotH - (v - min) / range * f.plotH;
2654
+ const y0 = Math.min(top, baseY);
2655
+ const h = Math.abs(top - baseY);
2656
+ const baseColor = spec.varyColors && spec.series.length === 1 ? colors[c % colors.length] : spec.series[s]?.color ?? colors[s % colors.length];
2657
+ const fillColor = v < 0 && spec.series[s]?.invertIfNegative ? mixHex(baseColor, "#000000", .55) : baseColor;
2658
+ out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(barW)}" height="${px(h)}" fill="${fillColor}"/>`);
2659
+ if (showLabelFor(s)) {
2660
+ const pos = spec.series[s]?.dataLabels?.position ?? spec.dataLabels?.position;
2661
+ let labelY;
2662
+ let fill = "#374151";
2663
+ if (pos === "ctr") {
2664
+ labelY = y0 + h / 2 + 3;
2665
+ fill = "#FFFFFF";
2666
+ } else if (pos === "inEnd") {
2667
+ labelY = v >= 0 ? y0 + 9 : y0 + h - 3;
2668
+ fill = "#FFFFFF";
2669
+ } else if (pos === "inBase") {
2670
+ labelY = v >= 0 ? y0 + h - 3 : y0 + 9;
2671
+ fill = "#FFFFFF";
2672
+ } else labelY = v >= 0 ? y0 - 2 : y0 + h + 9;
2673
+ out.push(`<text x="${px(x0 + barW / 2)}" y="${px(labelY)}" text-anchor="middle" ${dataLabelTextAttrs(spec, s, fill)}>${formatDataLabelValue(spec, s, v)}</text>`);
2674
+ }
2675
+ }
2676
+ }
2677
+ out.push(`<line x1="${px(f.plotX)}" y1="${px(baseY)}" x2="${px(f.plotX + f.plotW)}" y2="${px(baseY)}" stroke="#9CA3AF" stroke-width="0.5"/>`);
2678
+ for (let s = 0; s < spec.series.length; s++) {
2679
+ const series = spec.series[s];
2680
+ if (!series?.trendline) continue;
2681
+ const tlColor = series.trendline.color ?? series.color ?? colors[s % colors.length];
2682
+ const xs = [];
2683
+ const ys = [];
2684
+ const seriesValues = series.values.slice(0, N);
2685
+ for (let c = 0; c < N; c++) {
2686
+ const v = seriesValues[c];
2687
+ if (v === null || v === void 0 || !Number.isFinite(v)) continue;
2688
+ const cx = f.plotX + (c + .5) * groupW;
2689
+ const cy = f.plotY + f.plotH - (v - min) / range * f.plotH;
2690
+ xs.push(cx);
2691
+ ys.push(cy);
2692
+ }
2693
+ if (xs.length < 2) continue;
2694
+ out.push(trendlinePath(xs, ys, series.trendline, tlColor));
2695
+ }
2696
+ return out.join("");
2697
+ };
2698
+ const trendlinePath = (xs, ys, tl, color) => {
2699
+ const n = xs.length;
2700
+ const stepX = n >= 2 ? (xs[n - 1] - xs[0]) / (n - 1) : 0;
2701
+ const extendBefore = (tl.backward ?? 0) * stepX;
2702
+ const extendAfter = (tl.forward ?? 0) * stepX;
2703
+ let pts = [];
2704
+ switch (tl.type) {
2705
+ case "movingAvg": {
2706
+ const period = Math.max(2, Math.min(n, tl.period ?? 3));
2707
+ for (let i = period - 1; i < n; i++) {
2708
+ let sum = 0;
2709
+ for (let j = 0; j < period; j++) sum += ys[i - j];
2710
+ pts.push([xs[i], sum / period]);
2711
+ }
2712
+ break;
2713
+ }
2714
+ case "log": {
2715
+ const ix = xs.map((_, i) => i + 1);
2716
+ const sumLn = ix.reduce((a, x) => a + Math.log(x), 0);
2717
+ const sumY = ys.reduce((a, y) => a + y, 0);
2718
+ const sumLnY = ix.reduce((a, x, i) => a + Math.log(x) * ys[i], 0);
2719
+ const sumLn2 = ix.reduce((a, x) => a + Math.log(x) ** 2, 0);
2720
+ const b = (n * sumLnY - sumLn * sumY) / (n * sumLn2 - sumLn ** 2 || 1);
2721
+ const a = (sumY - b * sumLn) / n;
2722
+ pts = xs.map((x, i) => [x, a + b * Math.log(i + 1)]);
2723
+ break;
2724
+ }
2725
+ case "exp":
2726
+ if (ys.every((y) => y > 0)) {
2727
+ const ix = xs.map((_, i) => i);
2728
+ const lnY = ys.map((y) => Math.log(y));
2729
+ const meanX = ix.reduce((a, b) => a + b, 0) / n;
2730
+ const meanLnY = lnY.reduce((a, b) => a + b, 0) / n;
2731
+ let num = 0;
2732
+ let den = 0;
2733
+ for (let i = 0; i < n; i++) {
2734
+ num += (ix[i] - meanX) * (lnY[i] - meanLnY);
2735
+ den += (ix[i] - meanX) ** 2;
2736
+ }
2737
+ const b = den === 0 ? 0 : num / den;
2738
+ const a = Math.exp(meanLnY - b * meanX);
2739
+ pts = xs.map((x, i) => [x, a * Math.exp(b * i)]);
2740
+ }
2741
+ if (pts.length === 0) pts = linearFit(xs, ys, extendBefore, extendAfter);
2742
+ break;
2743
+ default: pts = linearFit(xs, ys, extendBefore, extendAfter);
2744
+ }
2745
+ if (pts.length < 2) return "";
2746
+ const path = `<path d="${pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${px(x)},${px(y)}`).join(" ")}" fill="none" stroke="${color}" stroke-width="1.5" stroke-dasharray="6 3" stroke-linecap="round"/>`;
2747
+ if (tl.name !== void 0 && tl.name.length > 0) {
2748
+ const [lx, ly] = pts[pts.length - 1];
2749
+ return path + `<text x="${px(lx + 4)}" y="${px(ly)}" dominant-baseline="middle" font-family="sans-serif" font-size="9" fill="${color}">${escapeXml(tl.name)}</text>`;
2750
+ }
2751
+ return path;
2752
+ };
2753
+ const linearFit = (xs, ys, extendBefore = 0, extendAfter = 0) => {
2754
+ const n = xs.length;
2755
+ const meanX = xs.reduce((a, b) => a + b, 0) / n;
2756
+ const meanY = ys.reduce((a, b) => a + b, 0) / n;
2757
+ let num = 0;
2758
+ let den = 0;
2759
+ for (let i = 0; i < n; i++) {
2760
+ num += (xs[i] - meanX) * (ys[i] - meanY);
2761
+ den += (xs[i] - meanX) ** 2;
2762
+ }
2763
+ const slope = den === 0 ? 0 : num / den;
2764
+ const intercept = meanY - slope * meanX;
2765
+ const xStart = xs[0] - extendBefore;
2766
+ const xEnd = xs[n - 1] + extendAfter;
2767
+ return [[xStart, intercept + slope * xStart], [xEnd, intercept + slope * xEnd]];
2768
+ };
2769
+ const smoothPath = (pts) => {
2770
+ if (pts.length < 2) return "";
2771
+ const tension = .5;
2772
+ const parts = [];
2773
+ const [x0, y0] = pts[0];
2774
+ parts.push(`M${px(x0)},${px(y0)}`);
2775
+ for (let i = 0; i < pts.length - 1; i++) {
2776
+ const [,] = pts[i];
2777
+ const p0 = pts[i - 1] ?? pts[i];
2778
+ const p1 = pts[i];
2779
+ const p2 = pts[i + 1];
2780
+ const p3 = pts[i + 2] ?? p2;
2781
+ const cp1x = p1[0] + (p2[0] - p0[0]) * tension / 3;
2782
+ const cp1y = p1[1] + (p2[1] - p0[1]) * tension / 3;
2783
+ const cp2x = p2[0] - (p3[0] - p1[0]) * tension / 3;
2784
+ const cp2y = p2[1] - (p3[1] - p1[1]) * tension / 3;
2785
+ parts.push(`C${px(cp1x)},${px(cp1y)} ${px(cp2x)},${px(cp2y)} ${px(p2[0])},${px(p2[1])}`);
2786
+ }
2787
+ return parts.join(" ");
2788
+ };
2789
+ const AUTO_MARKER_SYMBOLS = [
2790
+ "diamond",
2791
+ "square",
2792
+ "triangle",
2793
+ "x",
2794
+ "star",
2795
+ "dot",
2796
+ "plus",
2797
+ "dash"
2798
+ ];
2799
+ const autoMarkerSymbol = (symbol, seriesIdx) => symbol !== void 0 && symbol !== "auto" ? symbol : AUTO_MARKER_SYMBOLS[seriesIdx % AUTO_MARKER_SYMBOLS.length];
2800
+ const seriesMarker = (symbol, cx, cy, r, color) => {
2801
+ switch (symbol) {
2802
+ case "square": return `<rect x="${px(cx - r)}" y="${px(cy - r)}" width="${px(r * 2)}" height="${px(r * 2)}" fill="${color}"/>`;
2803
+ case "diamond": return `<polygon points="${px(cx)},${px(cy - r)} ${px(cx + r)},${px(cy)} ${px(cx)},${px(cy + r)} ${px(cx - r)},${px(cy)}" fill="${color}"/>`;
2804
+ case "triangle": return `<polygon points="${px(cx)},${px(cy - r)} ${px(cx + r)},${px(cy + r)} ${px(cx - r)},${px(cy + r)}" fill="${color}"/>`;
2805
+ case "star": return `<polygon points="${(() => {
2806
+ const pts = [];
2807
+ for (let i = 0; i < 10; i++) {
2808
+ const ang = -Math.PI / 2 + i * Math.PI / 5;
2809
+ const rr = i % 2 === 0 ? r : r * .45;
2810
+ pts.push(`${px(cx + rr * Math.cos(ang))},${px(cy + rr * Math.sin(ang))}`);
2811
+ }
2812
+ return pts.join(" ");
2813
+ })()}" fill="${color}"/>`;
2814
+ case "x": return `<g stroke="${color}" stroke-width="1.2" stroke-linecap="round"><line x1="${px(cx - r)}" y1="${px(cy - r)}" x2="${px(cx + r)}" y2="${px(cy + r)}"/><line x1="${px(cx - r)}" y1="${px(cy + r)}" x2="${px(cx + r)}" y2="${px(cy - r)}"/></g>`;
2815
+ case "plus": return `<g stroke="${color}" stroke-width="1.2" stroke-linecap="round"><line x1="${px(cx - r)}" y1="${px(cy)}" x2="${px(cx + r)}" y2="${px(cy)}"/><line x1="${px(cx)}" y1="${px(cy - r)}" x2="${px(cx)}" y2="${px(cy + r)}"/></g>`;
2816
+ case "dash": return `<line x1="${px(cx - r)}" y1="${px(cy)}" x2="${px(cx + r)}" y2="${px(cy)}" stroke="${color}" stroke-width="${Math.max(1.5, r * .6).toFixed(2)}" stroke-linecap="round"/>`;
2817
+ case "dot": return `<circle cx="${px(cx)}" cy="${px(cy)}" r="${(r * .6).toFixed(2)}" fill="${color}"/>`;
2818
+ default: return `<circle cx="${px(cx)}" cy="${px(cy)}" r="${px(r)}" fill="${color}"/>`;
2819
+ }
2820
+ };
2821
+ const formatChartValue = (v) => {
2822
+ if (!Number.isFinite(v)) return "";
2823
+ if (Number.isInteger(v)) return String(v);
2824
+ const abs = Math.abs(v);
2825
+ if (abs >= 1e3) return Math.round(v).toString();
2826
+ if (abs >= 10) return v.toFixed(1);
2827
+ return v.toFixed(2).replace(/\.?0+$/, "");
2828
+ };
2829
+ const formatDataLabelValue = (spec, seriesIdx, v) => {
2830
+ const nf = spec.series[seriesIdx]?.dataLabels?.numberFormat ?? spec.dataLabels?.numberFormat;
2831
+ return nf ? formatAxisLabel(v, nf) : formatChartValue(v);
2832
+ };
2833
+ const dataLabelTextAttrs = (spec, seriesIdx, fallbackFill, fallbackSizePt = 9, fallbackBold = false) => {
2834
+ const style = spec.series[seriesIdx]?.dataLabels?.textStyle ?? spec.dataLabels?.textStyle;
2835
+ const sz = style?.sizePt ?? fallbackSizePt;
2836
+ const fill = style?.color ?? fallbackFill;
2837
+ const weight = style?.bold ?? fallbackBold ? " font-weight=\"600\"" : "";
2838
+ const italic = style?.italic ? " font-style=\"italic\"" : "";
2839
+ return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}"${weight}${italic}`;
2840
+ };
2841
+ const renderBarChart = (f, spec, colors) => {
2842
+ const N = pointCount(spec);
2843
+ if (N === 0 || spec.series.length === 0) return "";
2844
+ const grouping = spec.grouping ?? "clustered";
2845
+ const isStacked = grouping === "stacked" || grouping === "percentStacked";
2846
+ const isPercent = grouping === "percentStacked";
2847
+ const { min, max } = seriesMinMax(spec);
2848
+ const range = max - min || 1;
2849
+ const groupH = f.plotH / N;
2850
+ const gapPctB = (spec.gapWidthPct ?? 150) / 100;
2851
+ const overlapPctB = (spec.overlapPct ?? (isStacked ? 100 : 0)) / 100;
2852
+ const Sb = spec.series.length;
2853
+ const clusterUnitsB = isStacked ? 1 : 1 + (Sb - 1) * (1 - overlapPctB);
2854
+ const barH = groupH / Math.max(.5, clusterUnitsB + gapPctB);
2855
+ const baseX = f.plotX + (0 - min) / range * f.plotW;
2856
+ const showLabelForBar = (s) => spec.series[s]?.dataLabels?.showValue ?? spec.dataLabels?.showValue ?? false;
2857
+ const out = [];
2858
+ for (let c = 0; c < N; c++) if (isStacked) {
2859
+ let total = 0;
2860
+ if (isPercent) for (const s of spec.series) total += Math.max(0, s.values[c] ?? 0);
2861
+ let posAcc = 0;
2862
+ let negAcc = 0;
2863
+ for (let s = 0; s < spec.series.length; s++) {
2864
+ let v = spec.series[s]?.values[c] ?? 0;
2865
+ if (isPercent) {
2866
+ if (total === 0) continue;
2867
+ v = Math.max(0, v) / total;
2868
+ }
2869
+ const base = v >= 0 ? posAcc : negAcc;
2870
+ const stackedTop = base + v;
2871
+ const y0 = f.plotY + (N - 1 - c) * groupH + (groupH - barH) / 2;
2872
+ const x0 = f.plotX + (Math.min(base, stackedTop) - min) / range * f.plotW;
2873
+ const x1 = f.plotX + (Math.max(base, stackedTop) - min) / range * f.plotW;
2874
+ const w = Math.abs(x1 - x0);
2875
+ out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(w)}" height="${px(barH)}" fill="${spec.series[s]?.color ?? colors[s % colors.length]}"/>`);
2876
+ if (showLabelForBar(s) && Math.abs(v) > 0) {
2877
+ const labelX = (x0 + x1) / 2;
2878
+ const labelText = isPercent ? `${Math.round(v * 100)}%` : formatDataLabelValue(spec, s, v);
2879
+ out.push(`<text x="${px(labelX)}" y="${px(y0 + barH / 2 + 3)}" text-anchor="middle" font-family="sans-serif" font-size="9" fill="#FFFFFF" font-weight="600">${labelText}</text>`);
2880
+ }
2881
+ if (v >= 0) posAcc = stackedTop;
2882
+ else negAcc = stackedTop;
2883
+ }
2884
+ } else {
2885
+ const clusterH = barH * clusterUnitsB;
2886
+ const clusterStartY = f.plotY + (N - 1 - c) * groupH + (groupH - clusterH) / 2;
2887
+ const strideB = barH * (1 - overlapPctB);
2888
+ for (let s = 0; s < spec.series.length; s++) {
2889
+ const v = spec.series[s]?.values[c] ?? 0;
2890
+ const y0 = clusterStartY + (Sb - 1 - s) * strideB;
2891
+ const tip = f.plotX + (v - min) / range * f.plotW;
2892
+ const x0 = Math.min(tip, baseX);
2893
+ const w = Math.abs(tip - baseX);
2894
+ const baseColor = spec.varyColors && spec.series.length === 1 ? colors[c % colors.length] : spec.series[s]?.color ?? colors[s % colors.length];
2895
+ const fillColor = v < 0 && spec.series[s]?.invertIfNegative ? mixHex(baseColor, "#000000", .55) : baseColor;
2896
+ out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(w)}" height="${px(barH)}" fill="${fillColor}"/>`);
2897
+ if (showLabelForBar(s)) {
2898
+ const pos = spec.series[s]?.dataLabels?.position ?? spec.dataLabels?.position;
2899
+ let labelX;
2900
+ let anchor;
2901
+ let fill = "#374151";
2902
+ if (pos === "ctr") {
2903
+ labelX = x0 + w / 2;
2904
+ anchor = "middle";
2905
+ fill = "#FFFFFF";
2906
+ } else if (pos === "inEnd") {
2907
+ labelX = v >= 0 ? x0 + w - 4 : x0 + 4;
2908
+ anchor = v >= 0 ? "end" : "start";
2909
+ fill = "#FFFFFF";
2910
+ } else if (pos === "inBase") {
2911
+ labelX = v >= 0 ? x0 + 4 : x0 + w - 4;
2912
+ anchor = v >= 0 ? "start" : "end";
2913
+ fill = "#FFFFFF";
2914
+ } else {
2915
+ labelX = v >= 0 ? x0 + w + 2 : x0 - 2;
2916
+ anchor = v >= 0 ? "start" : "end";
2917
+ }
2918
+ out.push(`<text x="${px(labelX)}" y="${px(y0 + barH / 2 + 3)}" text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, fill)}>${formatDataLabelValue(spec, s, v)}</text>`);
2919
+ }
2920
+ }
2921
+ }
2922
+ out.push(`<line x1="${px(baseX)}" y1="${px(f.plotY)}" x2="${px(baseX)}" y2="${px(f.plotY + f.plotH)}" stroke="#9CA3AF" stroke-width="0.5"/>`);
2923
+ return out.join("");
2924
+ };
2925
+ const renderLineChart = (f, spec, colors, fill) => {
2926
+ const N = pointCount(spec);
2927
+ if (N === 0 || spec.series.length === 0) return "";
2928
+ const grouping = spec.grouping ?? "clustered";
2929
+ const isStacked = grouping === "stacked" || grouping === "percentStacked";
2930
+ const isPercent = grouping === "percentStacked";
2931
+ const { min, max } = seriesMinMax(spec);
2932
+ const range = max - min || 1;
2933
+ const step = N > 1 ? f.plotW / (N - 1) : 0;
2934
+ const baseY = f.plotY + f.plotH - (0 - min) / range * f.plotH;
2935
+ const out = [];
2936
+ 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"/>`);
2937
+ const accumulated = Array.from({ length: N }, () => 0);
2938
+ for (let s = 0; s < spec.series.length; s++) {
2939
+ const series = spec.series[s];
2940
+ if (!series) continue;
2941
+ const color = series.color ?? colors[s % colors.length];
2942
+ const dba = spec.dispBlanksAs ?? "gap";
2943
+ const ptsRaw = [];
2944
+ const basePtsRaw = [];
2945
+ for (let c = 0; c < N; c++) {
2946
+ const xp = f.plotX + c * step;
2947
+ const rawV = series.values[c];
2948
+ const isNullish = rawV === null || rawV === void 0 || !Number.isFinite(rawV);
2949
+ let v;
2950
+ if (isNullish) if (dba === "zero") v = 0;
2951
+ else {
2952
+ ptsRaw.push(null);
2953
+ basePtsRaw.push(null);
2954
+ continue;
2955
+ }
2956
+ else v = rawV;
2957
+ const baseAt = accumulated[c] ?? 0;
2958
+ if (isPercent) {
2959
+ let total = 0;
2960
+ for (const s2 of spec.series) total += Math.max(0, s2.values[c] ?? 0);
2961
+ v = total === 0 ? 0 : Math.max(0, v) / total;
2962
+ }
2963
+ const top = isStacked ? baseAt + v : v;
2964
+ const yp = f.plotY + f.plotH - (top - min) / range * f.plotH;
2965
+ const yBase = isStacked ? f.plotY + f.plotH - (baseAt - min) / range * f.plotH : baseY;
2966
+ ptsRaw.push([xp, yp]);
2967
+ basePtsRaw.push([xp, yBase]);
2968
+ if (isStacked) accumulated[c] = top;
2969
+ }
2970
+ const pts = dba === "span" ? ptsRaw.filter((p) => p !== null) : ptsRaw.filter((p) => p !== null);
2971
+ const basePts = dba === "span" ? basePtsRaw.filter((p) => p !== null) : basePtsRaw.filter((p) => p !== null);
2972
+ const dPath = series.smooth && pts.length > 2 ? smoothPath(pts) : (() => {
2973
+ let path = "";
2974
+ let starting = true;
2975
+ for (const p of ptsRaw) {
2976
+ if (p === null) {
2977
+ if (dba === "gap") starting = true;
2978
+ continue;
2979
+ }
2980
+ path += `${starting ? "M" : "L"}${px(p[0])},${px(p[1])} `;
2981
+ starting = false;
2982
+ }
2983
+ return path.trim();
2984
+ })();
2985
+ if (fill) {
2986
+ const back = basePts.slice().reverse().map(([xp, yp]) => `L${px(xp)},${px(yp)}`).join(" ");
2987
+ out.push(`<path d="${dPath} ${back} Z" fill="${color}" stroke="none"/>`);
2988
+ }
2989
+ const lineWPx = series.lineWidthEmu ? Math.max(.3, series.lineWidthEmu / EMU_PER_PX) : 1.5;
2990
+ const dashAttr = series.lineDash ? (() => {
2991
+ const sw = lineWPx;
2992
+ const pat = DASH_PATTERNS[series.lineDash];
2993
+ if (!pat) return "";
2994
+ return ` stroke-dasharray="${pat.split(" ").map((n) => (Number.parseFloat(n) * sw).toFixed(2)).join(" ")}"`;
2995
+ })() : "";
2996
+ out.push(`<path d="${dPath}" fill="none" stroke="${color}" stroke-width="${lineWPx.toFixed(2)}" stroke-linejoin="round" stroke-linecap="round"${dashAttr}/>`);
2997
+ if (!isStacked) {
2998
+ const explicitSymbol = series.markerSymbol !== void 0 && series.markerSymbol !== "auto" && series.markerSymbol !== "none";
2999
+ if (series.markerSymbol !== "none" && !fill && (spec.lineMarkers === true || explicitSymbol)) {
3000
+ const symbol = autoMarkerSymbol(series.markerSymbol, s);
3001
+ const size = series.markerSizePt ?? 5;
3002
+ const r = Math.max(1, size * .5);
3003
+ for (const [xp, yp] of pts) out.push(seriesMarker(symbol, xp, yp, r, color));
3004
+ }
3005
+ }
3006
+ if (series.dataLabels?.showValue ?? spec.dataLabels?.showValue ?? false) {
3007
+ const lblPos = series.dataLabels?.position ?? spec.dataLabels?.position;
3008
+ const computeAttrs = (xp, yp) => {
3009
+ switch (lblPos) {
3010
+ case "ctr": return {
3011
+ x: xp,
3012
+ y: yp + 3,
3013
+ anchor: "middle"
3014
+ };
3015
+ case "b": return {
3016
+ x: xp,
3017
+ y: yp + 13,
3018
+ anchor: "middle"
3019
+ };
3020
+ case "l": return {
3021
+ x: xp - 6,
3022
+ y: yp + 3,
3023
+ anchor: "end"
3024
+ };
3025
+ case "r": return {
3026
+ x: xp + 6,
3027
+ y: yp + 3,
3028
+ anchor: "start"
3029
+ };
3030
+ default: return {
3031
+ x: xp,
3032
+ y: yp - 5,
3033
+ anchor: "middle"
3034
+ };
3035
+ }
3036
+ };
3037
+ for (let c = 0; c < N; c++) {
3038
+ const p = ptsRaw[c];
3039
+ if (p == null) continue;
3040
+ const v = series.values[c];
3041
+ if (v === null || v === void 0 || !Number.isFinite(v)) continue;
3042
+ const [xp, yp] = p;
3043
+ const { x: lx, y: ly, anchor } = computeAttrs(xp, yp);
3044
+ out.push(`<text x="${px(lx)}" y="${px(ly)}" text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, "#374151")}>${formatDataLabelValue(spec, s, v)}</text>`);
3045
+ }
3046
+ }
3047
+ if (!isStacked && series.trendline) {
3048
+ const finiteXs = [];
3049
+ const finiteYs = [];
3050
+ for (const [xp, yp] of pts) if (Number.isFinite(yp)) {
3051
+ finiteXs.push(xp);
3052
+ finiteYs.push(yp);
3053
+ }
3054
+ if (finiteXs.length >= 2) {
3055
+ const tlColor = series.trendline.color ?? color;
3056
+ out.push(trendlinePath(finiteXs, finiteYs, series.trendline, tlColor));
3057
+ }
3058
+ }
3059
+ }
3060
+ if (!isStacked && (spec.dropLines || spec.hiLowLines) && spec.series.length > 0) for (let c = 0; c < N; c++) {
3061
+ const xp = f.plotX + c * step;
3062
+ if (spec.dropLines) {
3063
+ const firstVal = spec.series[0]?.values[c];
3064
+ if (firstVal !== null && firstVal !== void 0 && Number.isFinite(firstVal)) {
3065
+ const yp = f.plotY + f.plotH - (firstVal - min) / range * f.plotH;
3066
+ out.push(`<line x1="${px(xp)}" y1="${px(yp)}" x2="${px(xp)}" y2="${px(baseY)}" stroke="#9CA3AF" stroke-width="0.5" stroke-dasharray="2 2"/>`);
3067
+ }
3068
+ }
3069
+ if (spec.hiLowLines) {
3070
+ let hiV = -Infinity;
3071
+ let loV = Infinity;
3072
+ for (const s of spec.series) {
3073
+ const v = s.values[c];
3074
+ if (v === null || v === void 0 || !Number.isFinite(v)) continue;
3075
+ if (v > hiV) hiV = v;
3076
+ if (v < loV) loV = v;
3077
+ }
3078
+ if (hiV > loV) {
3079
+ const yHi = f.plotY + f.plotH - (hiV - min) / range * f.plotH;
3080
+ const yLo = f.plotY + f.plotH - (loV - min) / range * f.plotH;
3081
+ out.push(`<line x1="${px(xp)}" y1="${px(yHi)}" x2="${px(xp)}" y2="${px(yLo)}" stroke="#4B5563" stroke-width="1"/>`);
3082
+ }
3083
+ }
3084
+ }
3085
+ return out.join("");
3086
+ };
3087
+ const renderPieChart = (f, spec, colors, doughnut) => {
3088
+ const series = spec.series[0];
3089
+ if (!series) return "";
3090
+ const values = series.values.map((v) => Math.max(0, v ?? 0));
3091
+ const total = values.reduce((a, b) => a + b, 0);
3092
+ if (total === 0) return "";
3093
+ const radius = Math.min(f.plotW, f.plotH) / 2 - 2;
3094
+ const cx = f.plotX + f.plotW / 2;
3095
+ const cy = f.plotY + f.plotH / 2;
3096
+ const innerR = doughnut ? radius * ((spec.holeSizePct ?? 55) / 100) : 0;
3097
+ const startDeg = spec.firstSliceAngleDeg ?? 0;
3098
+ let acc = -Math.PI / 2 + startDeg * Math.PI / 180;
3099
+ const out = [];
3100
+ for (let i = 0; i < values.length; i++) {
3101
+ const v = values[i] ?? 0;
3102
+ const angle = v / total * 2 * Math.PI;
3103
+ const start = acc;
3104
+ const end = acc + angle;
3105
+ acc = end;
3106
+ const largeArc = angle > Math.PI ? 1 : 0;
3107
+ const explOffset = (series.pointExplosions?.[i] ?? 0) / 100 * radius;
3108
+ const midAngle = (start + end) / 2;
3109
+ const sx = cx + Math.cos(midAngle) * explOffset;
3110
+ const sy = cy + Math.sin(midAngle) * explOffset;
3111
+ const ox1 = sx + radius * Math.cos(start);
3112
+ const oy1 = sy + radius * Math.sin(start);
3113
+ const ox2 = sx + radius * Math.cos(end);
3114
+ const oy2 = sy + radius * Math.sin(end);
3115
+ const color = series.pointColors?.[i] ?? series.color ?? colors[i % colors.length];
3116
+ if (doughnut) {
3117
+ const ix1 = sx + innerR * Math.cos(start);
3118
+ const iy1 = sy + innerR * Math.sin(start);
3119
+ const ix2 = sx + innerR * Math.cos(end);
3120
+ const iy2 = sy + innerR * Math.sin(end);
3121
+ const d = `M${px(ox1)},${px(oy1)} A${px(radius)},${px(radius)} 0 ${largeArc} 1 ${px(ox2)},${px(oy2)} L${px(ix2)},${px(iy2)} A${px(innerR)},${px(innerR)} 0 ${largeArc} 0 ${px(ix1)},${px(iy1)} Z`;
3122
+ out.push(`<path d="${d}" fill="${color}" stroke="#FFFFFF" stroke-width="0.6"/>`);
3123
+ } else {
3124
+ const d = `M${px(sx)},${px(sy)} L${px(ox1)},${px(oy1)} A${px(radius)},${px(radius)} 0 ${largeArc} 1 ${px(ox2)},${px(oy2)} Z`;
3125
+ out.push(`<path d="${d}" fill="${color}" stroke="#FFFFFF" stroke-width="0.6"/>`);
3126
+ }
3127
+ const labelMid = (start + end) / 2;
3128
+ const pos = spec.series[0]?.dataLabels?.position ?? spec.dataLabels?.position;
3129
+ let labelR;
3130
+ let labelFill = "#FFFFFF";
3131
+ if (pos === "inEnd") labelR = radius - 12;
3132
+ else if (pos === "outEnd") {
3133
+ labelR = radius + 12;
3134
+ labelFill = "#374151";
3135
+ } else labelR = doughnut ? (radius + innerR) / 2 : radius * .6;
3136
+ const labelX = sx + labelR * Math.cos(labelMid);
3137
+ const labelY = sy + labelR * Math.sin(labelMid);
3138
+ const labels = [];
3139
+ if (spec.dataLabels?.showValue) labels.push(formatDataLabelValue(spec, 0, v));
3140
+ if (spec.dataLabels?.showPercent) labels.push(`${(v / total * 100).toFixed(0)}%`);
3141
+ if (spec.dataLabels?.showCategory) {
3142
+ const catLabel = spec.categories[i];
3143
+ if (catLabel) labels.push(catLabel);
3144
+ }
3145
+ if (labels.length > 0) out.push(`<text x="${px(labelX)}" y="${px(labelY)}" text-anchor="middle" dominant-baseline="middle" ${dataLabelTextAttrs(spec, 0, labelFill, 10, true)}>${escapeXml(labels.join(spec.series[0]?.dataLabels?.separator ?? spec.dataLabels?.separator ?? " "))}</text>`);
3146
+ }
3147
+ return out.join("");
3148
+ };
3149
+ const scatterAxisBounds = (vals) => {
3150
+ let min = Infinity;
3151
+ let max = -Infinity;
3152
+ for (const v of vals) if (Number.isFinite(v)) {
3153
+ if (v < min) min = v;
3154
+ if (v > max) max = v;
3155
+ }
3156
+ if (!Number.isFinite(min)) return {
3157
+ min: 0,
3158
+ max: 1
3159
+ };
3160
+ if (min === max) return {
3161
+ min: min - 1,
3162
+ max: max + 1
3163
+ };
3164
+ const ticks = niceTicks(min, max);
3165
+ const step = ticks.length >= 2 ? ticks[1] - ticks[0] : (max - min) / 4 || 1;
3166
+ return {
3167
+ min: Math.floor(min / step) * step,
3168
+ max: Math.ceil(max / step) * step
3169
+ };
3170
+ };
3171
+ const xyPoints = (series) => {
3172
+ const hasX = series.xValues !== void 0 && series.xValues.length > 0;
3173
+ const n = Math.max(series.xValues?.length ?? 0, series.values.length);
3174
+ const out = [];
3175
+ for (let i = 0; i < n; i++) {
3176
+ let x;
3177
+ if (hasX) {
3178
+ const xv = series.xValues[i];
3179
+ if (xv === null || xv === void 0 || !Number.isFinite(xv)) continue;
3180
+ x = xv;
3181
+ } else x = i + 1;
3182
+ const y = series.values[i];
3183
+ if (y === null || y === void 0 || !Number.isFinite(y)) continue;
3184
+ const sz = series.bubbleSizes?.[i];
3185
+ out.push({
3186
+ x,
3187
+ y,
3188
+ size: sz != null && Number.isFinite(sz) ? sz : 0
3189
+ });
3190
+ }
3191
+ return out;
3192
+ };
3193
+ const renderScatterAxes = (f, spec, xB, yB) => {
3194
+ const yAxis = {
3195
+ orientation: "vertical",
3196
+ min: yB.min,
3197
+ max: yB.max,
3198
+ ...spec.valueAxis?.numberFormat !== void 0 ? { numberFormat: spec.valueAxis.numberFormat } : {}
3199
+ };
3200
+ const xAxis = {
3201
+ orientation: "horizontal",
3202
+ min: xB.min,
3203
+ max: xB.max
3204
+ };
3205
+ return renderValueAxis(f, yAxis) + renderValueAxis(f, xAxis);
3206
+ };
3207
+ const renderScatterChart = (f, spec, colors) => {
3208
+ const perSeries = spec.series.map((s) => xyPoints(s));
3209
+ const allX = [];
3210
+ const allY = [];
3211
+ for (const pts of perSeries) for (const p of pts) {
3212
+ allX.push(p.x);
3213
+ allY.push(p.y);
3214
+ }
3215
+ if (allX.length === 0) return "";
3216
+ const xB = scatterAxisBounds(allX);
3217
+ const yB = scatterAxisBounds(allY);
3218
+ if (spec.valueAxis?.min !== void 0) yB.min = spec.valueAxis.min;
3219
+ if (spec.valueAxis?.max !== void 0) yB.max = spec.valueAxis.max;
3220
+ const xRange = xB.max - xB.min || 1;
3221
+ const yRange = yB.max - yB.min || 1;
3222
+ const projX = (x) => f.plotX + (x - xB.min) / xRange * f.plotW;
3223
+ const projY = (y) => f.plotY + f.plotH - (y - yB.min) / yRange * f.plotH;
3224
+ const out = [renderScatterAxes(f, spec, xB, yB)];
3225
+ const style = spec.scatterStyle;
3226
+ const showLine = style === "line" || style === "lineMarker" || style === "smooth" || style === "smoothMarker";
3227
+ const smooth = style === "smooth" || style === "smoothMarker";
3228
+ const showMarker = style === void 0 || style === "marker" || style === "lineMarker" || style === "smoothMarker";
3229
+ for (let s = 0; s < spec.series.length; s++) {
3230
+ const series = spec.series[s];
3231
+ const pts = perSeries[s];
3232
+ if (pts.length === 0) continue;
3233
+ const color = series.color ?? colors[s % colors.length];
3234
+ const proj = pts.map((p) => [projX(p.x), projY(p.y)]);
3235
+ if (showLine && proj.length >= 2) {
3236
+ const d = smooth && proj.length > 2 ? smoothPath(proj) : proj.map(([xp, yp], i) => `${i === 0 ? "M" : "L"}${px(xp)},${px(yp)}`).join(" ");
3237
+ const lineWPx = series.lineWidthEmu ? Math.max(.3, series.lineWidthEmu / EMU_PER_PX) : 1.5;
3238
+ out.push(`<path d="${d}" fill="none" stroke="${color}" stroke-width="${lineWPx.toFixed(2)}" stroke-linejoin="round" stroke-linecap="round"/>`);
3239
+ }
3240
+ const sym = series.markerSymbol;
3241
+ if (sym === "none" ? false : showMarker || sym !== void 0 && sym !== "auto") {
3242
+ const r = Math.max(1.5, (series.markerSizePt ?? 5) * .5);
3243
+ const glyph = autoMarkerSymbol(sym, s);
3244
+ for (const [xp, yp] of proj) out.push(seriesMarker(glyph, xp, yp, r, color));
3245
+ }
3246
+ }
3247
+ return out.join("");
3248
+ };
3249
+ const renderBubbleChart = (f, spec, colors) => {
3250
+ const perSeries = spec.series.map((s) => xyPoints(s));
3251
+ const allX = [];
3252
+ const allY = [];
3253
+ let maxSize = 0;
3254
+ for (const pts of perSeries) for (const p of pts) {
3255
+ allX.push(p.x);
3256
+ allY.push(p.y);
3257
+ if (p.size > maxSize) maxSize = p.size;
3258
+ }
3259
+ if (allX.length === 0) return "";
3260
+ const xB = scatterAxisBounds(allX);
3261
+ const yB = scatterAxisBounds(allY);
3262
+ if (spec.valueAxis?.min !== void 0) yB.min = spec.valueAxis.min;
3263
+ if (spec.valueAxis?.max !== void 0) yB.max = spec.valueAxis.max;
3264
+ const xRange = xB.max - xB.min || 1;
3265
+ const yRange = yB.max - yB.min || 1;
3266
+ const projX = (x) => f.plotX + (x - xB.min) / xRange * f.plotW;
3267
+ const projY = (y) => f.plotY + f.plotH - (y - yB.min) / yRange * f.plotH;
3268
+ const out = [renderScatterAxes(f, spec, xB, yB)];
3269
+ const scaleFraction = spec.bubbleScale !== void 0 ? spec.bubbleScale / 100 : .25;
3270
+ const maxRadiusPx = Math.max(2, scaleFraction * Math.min(f.plotW, f.plotH) * .5);
3271
+ const areaMode = (spec.bubbleSizeRepresents ?? "area") === "area";
3272
+ const radiusFor = (size) => {
3273
+ if (maxSize <= 0 || size <= 0) return Math.max(1.5, maxRadiusPx * .15);
3274
+ const frac = areaMode ? Math.sqrt(size / maxSize) : size / maxSize;
3275
+ return Math.max(1.5, maxRadiusPx * frac);
3276
+ };
3277
+ for (let s = 0; s < spec.series.length; s++) {
3278
+ const series = spec.series[s];
3279
+ const pts = perSeries[s];
3280
+ const color = series.color ?? colors[s % colors.length];
3281
+ for (const p of pts) {
3282
+ const cx = projX(p.x);
3283
+ const cy = projY(p.y);
3284
+ const r = radiusFor(p.size);
3285
+ out.push(`<circle cx="${px(cx)}" cy="${px(cy)}" r="${r.toFixed(2)}" fill="${color}" fill-opacity="0.55" stroke="${color}" stroke-width="0.75"/>`);
3286
+ }
3287
+ }
3288
+ return out.join("");
3289
+ };
3290
+ const renderRadarChart = (f, spec, colors) => {
3291
+ const N = pointCount(spec);
3292
+ if (N === 0 || spec.series.length === 0) return "";
3293
+ let max = -Infinity;
3294
+ let min = 0;
3295
+ for (const sr of spec.series) for (const v of sr.values) if (v !== null && Number.isFinite(v) && v > max) max = v;
3296
+ if (!Number.isFinite(max)) max = 1;
3297
+ if (spec.valueAxis?.min !== void 0) min = spec.valueAxis.min;
3298
+ if (spec.valueAxis?.max !== void 0) max = spec.valueAxis.max;
3299
+ if (max <= min) max = min + 1;
3300
+ const range = max - min;
3301
+ const cx = f.plotX + f.plotW / 2;
3302
+ const cy = f.plotY + f.plotH / 2;
3303
+ const R = Math.max(4, Math.min(f.plotW, f.plotH) / 2 - 14);
3304
+ const angleAt = (i) => -Math.PI / 2 + i * 2 * Math.PI / N;
3305
+ const out = [];
3306
+ for (const t of niceTicks(min, max)) {
3307
+ if (t < min || t > max + 1e-9) continue;
3308
+ const rr = (t - min) / range * R;
3309
+ if (rr <= 0) continue;
3310
+ const ring = [];
3311
+ for (let i = 0; i < N; i++) {
3312
+ const a = angleAt(i);
3313
+ ring.push(`${px(cx + rr * Math.cos(a))},${px(cy + rr * Math.sin(a))}`);
3314
+ }
3315
+ out.push(`<polygon points="${ring.join(" ")}" fill="none" stroke="#E5E7EB" stroke-width="0.5"/>`);
3316
+ out.push(`<text x="${px(cx + 3)}" y="${px(cy - rr)}" dominant-baseline="middle" font-family="sans-serif" font-size="8" fill="#9CA3AF">${escapeXml(formatTick(t))}</text>`);
3317
+ }
3318
+ for (let i = 0; i < N; i++) {
3319
+ const a = angleAt(i);
3320
+ out.push(`<line x1="${px(cx)}" y1="${px(cy)}" x2="${px(cx + R * Math.cos(a))}" y2="${px(cy + R * Math.sin(a))}" stroke="#D1D5DB" stroke-width="0.5"/>`);
3321
+ const cat = spec.categories[i] ?? String(i + 1);
3322
+ const lx = cx + (R + 8) * Math.cos(a);
3323
+ const ly = cy + (R + 8) * Math.sin(a);
3324
+ const cosA = Math.cos(a);
3325
+ const anchor = Math.abs(cosA) < .3 ? "middle" : cosA > 0 ? "start" : "end";
3326
+ const label = cat.length > 12 ? `${cat.slice(0, 11)}…` : cat;
3327
+ out.push(`<text x="${px(lx)}" y="${px(ly)}" text-anchor="${anchor}" dominant-baseline="middle" ${axisTickAttrs(spec.categoryAxisLabelStyle)}>${escapeXml(label)}</text>`);
3328
+ }
3329
+ const filled = spec.radarStyle === "filled";
3330
+ const showMarker = spec.radarStyle === "marker";
3331
+ for (let s = 0; s < spec.series.length; s++) {
3332
+ const series = spec.series[s];
3333
+ const color = series.color ?? colors[s % colors.length];
3334
+ const proj = [];
3335
+ for (let i = 0; i < N; i++) {
3336
+ const v = series.values[i];
3337
+ if (v === null || v === void 0 || !Number.isFinite(v)) continue;
3338
+ const rr = (v - min) / range * R;
3339
+ const a = angleAt(i);
3340
+ proj.push([cx + rr * Math.cos(a), cy + rr * Math.sin(a)]);
3341
+ }
3342
+ if (proj.length < 2) continue;
3343
+ const ptsStr = proj.map(([xp, yp]) => `${px(xp)},${px(yp)}`).join(" ");
3344
+ const lineWPx = series.lineWidthEmu ? Math.max(.3, series.lineWidthEmu / EMU_PER_PX) : 1.8;
3345
+ out.push(filled ? `<polygon points="${ptsStr}" fill="${color}" fill-opacity="0.3" stroke="${color}" stroke-width="${lineWPx.toFixed(2)}" stroke-linejoin="round"/>` : `<polygon points="${ptsStr}" fill="none" stroke="${color}" stroke-width="${lineWPx.toFixed(2)}" stroke-linejoin="round"/>`);
3346
+ if (showMarker) {
3347
+ const r = Math.max(1.5, (series.markerSizePt ?? 5) * .5);
3348
+ const glyph = autoMarkerSymbol(series.markerSymbol, s);
3349
+ for (const [xp, yp] of proj) out.push(seriesMarker(glyph, xp, yp, r, color));
3350
+ }
3351
+ }
3352
+ return out.join("");
3353
+ };
3354
+ const renderChart = (shape, x, y, w, h, transform, theme) => {
3355
+ let spec = null;
3356
+ try {
3357
+ spec = getShapeChartSpec(shape);
3358
+ } catch {
3359
+ return null;
3360
+ }
3361
+ if (!spec) return null;
3362
+ const colors = accentSequence(theme);
3363
+ const isCartesian = spec.kind === "column" || spec.kind === "bar" || spec.kind === "line" || spec.kind === "area";
3364
+ const hasAxes = isCartesian || spec.kind === "scatter" || spec.kind === "bubble";
3365
+ 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);
3367
+ const allNamesForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? Array.from(spec.categories) : spec.series.map((s) => s.name);
3368
+ 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
+ const hiddenSet = new Set(spec.legend?.hiddenIndices ?? []);
3370
+ const seriesNamesForLegend = allNamesForLegend.filter((_, i) => !hiddenSet.has(i));
3371
+ const seriesColorsForLegend = allColorsForLegend.filter((_, i) => !hiddenSet.has(i));
3372
+ const explicitMarker = (sym) => sym !== void 0 && sym !== "auto" && sym !== "none";
3373
+ const markerSymbolsForLegend = spec.kind === "line" && (spec.lineMarkers === true || spec.series.some((s) => explicitMarker(s.markerSymbol))) ? spec.series.map((s) => s.markerSymbol).filter((_, i) => !hiddenSet.has(i)) : void 0;
3374
+ let finiteCount = 0;
3375
+ for (const s of spec.series) for (const v of s.values) if (v !== null && Number.isFinite(v)) finiteCount++;
3376
+ let plot = "";
3377
+ let axes = "";
3378
+ if (isCartesian) {
3379
+ const { min, max, step } = seriesMinMax(spec);
3380
+ const N = pointCount(spec);
3381
+ const majorUnit = spec.valueAxis?.majorUnit ?? step;
3382
+ const numberFormat = spec.valueAxis?.numberFormat;
3383
+ const axisExtras = {
3384
+ ...majorUnit !== void 0 ? { majorUnit } : {},
3385
+ ...numberFormat !== void 0 ? { numberFormat } : {},
3386
+ ...spec.valueAxisMajorGridlines !== void 0 ? { majorGridlines: spec.valueAxisMajorGridlines } : {},
3387
+ ...spec.valueAxisLabelStyle !== void 0 ? { labelStyle: spec.valueAxisLabelStyle } : {},
3388
+ ...spec.valueAxisMajorGridlineColor !== void 0 ? { majorGridlineColor: spec.valueAxisMajorGridlineColor } : {},
3389
+ ...spec.valueAxisMajorTickMark !== void 0 ? { majorTickMark: spec.valueAxisMajorTickMark } : {},
3390
+ ...spec.valueAxisLineColor !== void 0 ? { lineColor: spec.valueAxisLineColor } : {},
3391
+ ...spec.valueAxisLabelRotationDeg !== void 0 ? { labelRotationDeg: spec.valueAxisLabelRotationDeg } : {},
3392
+ ...spec.valueAxis?.displayUnits !== void 0 ? { displayUnits: spec.valueAxis.displayUnits } : {}
3393
+ };
3394
+ const isPercentStacked = spec.grouping === "percentStacked";
3395
+ const valueAxis = spec.kind === "bar" ? {
3396
+ orientation: "horizontal",
3397
+ min,
3398
+ max,
3399
+ percent: isPercentStacked,
3400
+ ...axisExtras
3401
+ } : {
3402
+ orientation: "vertical",
3403
+ min,
3404
+ max,
3405
+ percent: isPercentStacked,
3406
+ ...axisExtras
3407
+ };
3408
+ if (!spec.valueAxisHidden) axes = renderValueAxis(f, valueAxis);
3409
+ const labelsHidden = spec.categoryAxisHidden || spec.categoryAxisTickLabelPos === "none";
3410
+ if (N > 0 && !labelsHidden) axes += renderCategoryAxis(f, spec.kind === "bar" ? "vertical" : "horizontal", spec.categories, N, spec.categoryAxisTickLabelSkip ?? 1, spec.categoryAxisLabelStyle, spec.categoryAxisLabelRotationDeg, spec.categoryAxisLabelAlign, spec.categoryAxisLineColor);
3411
+ }
3412
+ switch (spec.kind) {
3413
+ case "column":
3414
+ case "bar":
3415
+ plot = spec.kind === "column" ? renderColumnChart(f, spec, colors) : renderBarChart(f, spec, colors);
3416
+ break;
3417
+ case "line":
3418
+ plot = renderLineChart(f, spec, colors, false);
3419
+ break;
3420
+ case "area":
3421
+ plot = renderLineChart(f, spec, colors, true);
3422
+ break;
3423
+ case "pie":
3424
+ plot = renderPieChart(f, spec, colors, false);
3425
+ break;
3426
+ case "doughnut":
3427
+ plot = renderPieChart(f, spec, colors, true);
3428
+ break;
3429
+ case "scatter":
3430
+ plot = renderScatterChart(f, spec, colors);
3431
+ break;
3432
+ case "radar":
3433
+ plot = renderRadarChart(f, spec, colors);
3434
+ break;
3435
+ case "bubble":
3436
+ plot = renderBubbleChart(f, spec, colors);
3437
+ break;
3438
+ default: return null;
3439
+ }
3440
+ const emptyHint = finiteCount === 0 ? `<text x="${px(f.plotX + f.plotW / 2)}" y="${px(f.plotY + f.plotH / 2)}" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="12" fill="#9CA3AF">${escapeXml(`chart (${spec.kind}) — no data`)}</text>` : "";
3441
+ const axisTitleAttrs = (style) => {
3442
+ const sz = style?.sizePt ?? 11;
3443
+ const fill = style?.color ?? "#374151";
3444
+ const weight = style?.bold === false ? "400" : "600";
3445
+ const italicAttr = style?.italic ? " font-style=\"italic\"" : "";
3446
+ return `font-family="sans-serif" font-size="${sz.toFixed(1)}" fill="${fill}" font-weight="${weight}"${italicAttr}`;
3447
+ };
3448
+ const valueAxisTitleRot = spec.valueAxisTitleRotationDeg ?? -90;
3449
+ 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>` : "";
3450
+ const catTitleRot = spec.categoryAxisTitleRotationDeg ?? 0;
3451
+ const catTitleCx = f.plotX + f.plotW / 2;
3452
+ const catTitleCy = f.plotY + f.plotH + 16;
3453
+ const catTitleTransform = catTitleRot !== 0 ? ` transform="rotate(${catTitleRot} ${px(catTitleCx)} ${px(catTitleCy)})"` : "";
3454
+ const categoryAxisTitleSvg = spec.categoryAxisTitle ? `<text x="${px(catTitleCx)}" y="${px(catTitleCy)}" text-anchor="middle" ${axisTitleAttrs(spec.categoryAxisTitleStyle)}${catTitleTransform}>${escapeXml(spec.categoryAxisTitle)}</text>` : "";
3455
+ return [
3456
+ `<g${transform}>`,
3457
+ `<rect x="${px(f.x)}" y="${px(f.y)}" width="${px(f.w)}" height="${px(f.h)}" fill="${spec.chartAreaFill ?? "#FFFFFF"}" stroke="${spec.chartAreaStrokeColor ?? "none"}" stroke-width="0.6"${spec.roundedCorners ? " rx=\"6\" ry=\"6\"" : ""}/>`,
3458
+ spec.plotAreaFill || spec.plotAreaStrokeColor ? `<rect x="${px(f.plotX)}" y="${px(f.plotY)}" width="${px(f.plotW)}" height="${px(f.plotH)}" fill="${spec.plotAreaFill ?? "none"}" stroke="${spec.plotAreaStrokeColor ?? "none"}" stroke-width="0.6"/>` : "",
3459
+ renderChartTitle(f, spec.title ?? "", spec.titleStyle),
3460
+ axes,
3461
+ valueAxisTitleSvg,
3462
+ categoryAxisTitleSvg,
3463
+ plot,
3464
+ emptyHint,
3465
+ hasLegend ? renderChartLegend(f, seriesNamesForLegend, seriesColorsForLegend, spec.legend?.position ?? "b", spec.legend?.textStyle, markerSymbolsForLegend) : "",
3466
+ "</g>"
3467
+ ].join("");
3468
+ };
3469
+ const cellParaData = (paragraphs) => {
3470
+ let hasText = false;
3471
+ return {
3472
+ paraData: paragraphs.map((para) => {
3473
+ const runs = [];
3474
+ for (const el of para.elements) {
3475
+ if (el.kind === "br") {
3476
+ runs.push({
3477
+ text: "\n",
3478
+ fmt: null,
3479
+ sizePt: DEFAULT_BODY_PT
3480
+ });
3481
+ continue;
3482
+ }
3483
+ if (el.text.trim()) hasText = true;
3484
+ runs.push({
3485
+ text: el.text,
3486
+ fmt: el.format,
3487
+ sizePt: el.format?.size ?? DEFAULT_BODY_PT
3488
+ });
3489
+ }
3490
+ return {
3491
+ align: para.align ?? "left",
3492
+ level: 0,
3493
+ bulletStyle: null,
3494
+ bulletDetail: {
3495
+ color: null,
3496
+ sizePct: null,
3497
+ sizePts: null,
3498
+ font: null
3499
+ },
3500
+ bulletIsPicture: false,
3501
+ bulletImageHref: null,
3502
+ runs,
3503
+ lineSpacing: null,
3504
+ spcBefPts: null,
3505
+ spcAftPts: null,
3506
+ indent: {
3507
+ leftEmu: null,
3508
+ rightEmu: null,
3509
+ firstLineEmu: null
3510
+ }
3511
+ };
3512
+ }),
3513
+ hasText
3514
+ };
3515
+ };
3516
+ const renderTableCellText = (paragraphs, cx, cy, cw, ch, color, pres, shape, theme, themeFace, ctx, vAnchor, margins) => {
3517
+ const { paraData, hasText } = cellParaData(paragraphs);
3518
+ if (!hasText) return "";
3519
+ const defaultPadPx = 4;
3520
+ const padL = margins.left !== null ? margins.left / EMU_PER_PX : defaultPadPx;
3521
+ const padR = margins.right !== null ? margins.right / EMU_PER_PX : defaultPadPx;
3522
+ const padT = margins.top !== null ? margins.top / EMU_PER_PX : defaultPadPx;
3523
+ const padB = margins.bottom !== null ? margins.bottom / EMU_PER_PX : defaultPadPx;
3524
+ const innerX = cx + padL;
3525
+ const innerY = cy + padT;
3526
+ const innerW = Math.max(0, cw - padL - padR);
3527
+ const innerH = Math.max(0, ch - padT - padB);
3528
+ if (innerW <= 0 || innerH <= 0) return "";
3529
+ if (ctx.mode === "svg") return buildAndLayoutSvgText({
3530
+ pres,
3531
+ shape,
3532
+ theme,
3533
+ paraData,
3534
+ numberLabels: paraData.map(() => null),
3535
+ autoFitScale: 1,
3536
+ lineHeightScale: 1,
3537
+ defaultPt: DEFAULT_BODY_PT,
3538
+ themeFace,
3539
+ defaultColor: color,
3540
+ anchor: vAnchor,
3541
+ wrap: true,
3542
+ innerX: innerX * EMU_PER_PX,
3543
+ innerY: innerY * EMU_PER_PX,
3544
+ innerW: innerW * EMU_PER_PX,
3545
+ innerH: innerH * EMU_PER_PX,
3546
+ measure: ctx.measure,
3547
+ vert: "none",
3548
+ columns: null
3549
+ });
3550
+ const justify = vAnchor === "top" ? "flex-start" : vAnchor === "bottom" ? "flex-end" : "center";
3551
+ const familyFont = themeFace ? `${escapeXml(themeFace)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
3552
+ const body = paraData.map((para) => {
3553
+ const runHtml = para.runs.map((run) => renderRun(run.text, run.fmt, theme, run.sizePt, run.fmt?.size === void 0)).join("");
3554
+ return `<p style="margin:0;padding:0;text-align:${ALIGNMENT_TO_CSS[para.align] ?? "left"};line-height:1.2">${runHtml || "&#8203;"}</p>`;
3555
+ }).join("");
3556
+ 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;font-family:${familyFont};color:${color};word-break:break-word">${body}</div></foreignObject>`;
3557
+ };
3558
+ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
3559
+ let dims;
3560
+ let widths;
3561
+ let heights;
3562
+ let cells;
3563
+ try {
3564
+ dims = getTableDimensions(shape);
3565
+ widths = getTableColumnWidths(shape);
3566
+ heights = getTableRowHeights(shape);
3567
+ cells = getTableCells(shape);
3568
+ } catch {
3569
+ return null;
3570
+ }
3571
+ if (dims.rows === 0 || dims.cols === 0) return null;
3572
+ const xPx = x / EMU_PER_PX;
3573
+ const yPx = y / EMU_PER_PX;
3574
+ const widthsPx = widths.map((w0) => w0 / EMU_PER_PX);
3575
+ const heightsPx = heights.map((h0) => h0 / EMU_PER_PX);
3576
+ const wSum = widthsPx.reduce((a, b) => a + b, 0);
3577
+ const hSum = heightsPx.reduce((a, b) => a + b, 0);
3578
+ const wScale = wSum > 0 ? w / EMU_PER_PX / wSum : 1;
3579
+ const hScale = hSum > 0 ? h / EMU_PER_PX / hSum : 1;
3580
+ const colXs = [xPx];
3581
+ for (let c = 0; c < widthsPx.length; c++) colXs.push((colXs[c] ?? xPx) + (widthsPx[c] ?? 0) * wScale);
3582
+ const rowYs = [yPx];
3583
+ for (let r = 0; r < heightsPx.length; r++) rowYs.push((rowYs[r] ?? yPx) + (heightsPx[r] ?? 0) * hScale);
3584
+ const flags = getTableStyleFlags(shape);
3585
+ const accent = theme ? normalizeHex(theme.accent1) : "#4472C4";
3586
+ const headerFill = accent;
3587
+ const bandFill = mixHex(accent, "#FFFFFF", .92);
3588
+ const textColor = activeDeckTextColor;
3589
+ const tableThemeFace = getPresentationFonts(pres)?.minorLatin ?? null;
3590
+ const out = [];
3591
+ out.push(`<g${transform}>`);
3592
+ out.push(`<rect x="${px(xPx)}" y="${px(yPx)}" width="${px((colXs[widthsPx.length] ?? xPx) - xPx)}" height="${px((rowYs[heightsPx.length] ?? yPx) - yPx)}" fill="#FFFFFF"/>`);
3593
+ const borderEdges = [];
3594
+ for (let r = 0; r < dims.rows; r++) for (let c = 0; c < dims.cols; c++) {
3595
+ const cell = cells[r]?.[c];
3596
+ if (!cell) continue;
3597
+ const typedCell = cell;
3598
+ const span = getTableCellSpan(typedCell);
3599
+ if (span.hMerge || span.vMerge) continue;
3600
+ const cx = colXs[c] ?? xPx;
3601
+ const cy = rowYs[r] ?? yPx;
3602
+ const endCol = Math.min(dims.cols, c + span.gridSpan);
3603
+ const endRow = Math.min(dims.rows, r + span.rowSpan);
3604
+ const cw = (colXs[endCol] ?? cx) - cx;
3605
+ const ch = (rowYs[endRow] ?? cy) - cy;
3606
+ const fill = getTableCellFill(cell);
3607
+ let resolvedFill;
3608
+ if (fill) resolvedFill = resolveColor(fill, theme, "#FFFFFF");
3609
+ else if (flags.firstRow && r === 0) resolvedFill = headerFill;
3610
+ else if (flags.lastRow && r === dims.rows - 1) resolvedFill = headerFill;
3611
+ else if (flags.firstCol && c === 0) resolvedFill = bandFill;
3612
+ else if (flags.lastCol && c === dims.cols - 1) resolvedFill = bandFill;
3613
+ else if (flags.bandRow && r % 2 === (flags.firstRow ? 0 : 1)) resolvedFill = bandFill;
3614
+ else if (flags.bandCol && c % 2 === (flags.firstCol ? 0 : 1)) resolvedFill = bandFill;
3615
+ else resolvedFill = "none";
3616
+ const cellTextColor = resolvedFill === headerFill ? "#FFFFFF" : textColor;
3617
+ out.push(`<rect x="${px(cx)}" y="${px(cy)}" width="${px(cw)}" height="${px(ch)}" fill="${resolvedFill}"/>`);
3618
+ const borders = getTableCellBorders(pres, typedCell);
3619
+ const dashAttr = (dash, widthPx) => {
3620
+ if (!dash || dash === "solid") return "";
3621
+ const pat = DASH_PATTERNS[dash];
3622
+ if (!pat) return "";
3623
+ return ` stroke-dasharray="${pat.split(" ").map((n) => (Number.parseFloat(n) * widthPx).toFixed(2)).join(" ")}"`;
3624
+ };
3625
+ const edge = (side, x1, y1, x2, y2) => {
3626
+ const b = borders[side];
3627
+ if (!b) return;
3628
+ const sw = b.widthEmu ? Math.max(.4, b.widthEmu / EMU_PER_PX) : .5;
3629
+ const col = b.color ?? "#9CA3AF";
3630
+ borderEdges.push(`<line x1="${px(x1)}" y1="${px(y1)}" x2="${px(x2)}" y2="${px(y2)}" stroke="${col}" stroke-width="${px(sw)}"${dashAttr(b.dash, sw)}/>`);
3631
+ };
3632
+ edge("left", cx, cy, cx, cy + ch);
3633
+ edge("right", cx + cw, cy, cx + cw, cy + ch);
3634
+ edge("top", cx, cy, cx + cw, cy);
3635
+ edge("bottom", cx, cy + ch, cx + cw, cy + ch);
3636
+ if (borders.tlToBr) {
3637
+ const sw = borders.tlToBr.widthEmu ? Math.max(.4, borders.tlToBr.widthEmu / EMU_PER_PX) : .5;
3638
+ borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy)}" x2="${px(cx + cw)}" y2="${px(cy + ch)}" stroke="${borders.tlToBr.color ?? "#9CA3AF"}" stroke-width="${px(sw)}"${dashAttr(borders.tlToBr.dash, sw)}/>`);
3639
+ }
3640
+ if (borders.blToTr) {
3641
+ const sw = borders.blToTr.widthEmu ? Math.max(.4, borders.blToTr.widthEmu / EMU_PER_PX) : .5;
3642
+ borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy + ch)}" x2="${px(cx + cw)}" y2="${px(cy)}" stroke="${borders.blToTr.color ?? "#9CA3AF"}" stroke-width="${px(sw)}"${dashAttr(borders.blToTr.dash, sw)}/>`);
3643
+ }
3644
+ const defaultColor = "#9CA3AF";
3645
+ if (!borders.left) borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy)}" x2="${px(cx)}" y2="${px(cy + ch)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
3646
+ if (!borders.right) borderEdges.push(`<line x1="${px(cx + cw)}" y1="${px(cy)}" x2="${px(cx + cw)}" y2="${px(cy + ch)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
3647
+ if (!borders.top) borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy)}" x2="${px(cx + cw)}" y2="${px(cy)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
3648
+ if (!borders.bottom) borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy + ch)}" x2="${px(cx + cw)}" y2="${px(cy + ch)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
3649
+ const cellParagraphs = getTableCellParagraphs(cell);
3650
+ const vAnchor = getTableCellAnchor(typedCell) ?? "top";
3651
+ const cellMargins = getTableCellMargins(typedCell);
3652
+ out.push(renderTableCellText(cellParagraphs, cx, cy, cw, ch, cellTextColor, pres, shape, theme, tableThemeFace, ctx, vAnchor, cellMargins));
3653
+ }
3654
+ out.push(borderEdges.join(""));
3655
+ out.push("</g>");
3656
+ return out.join("");
3657
+ };
3658
+ /**
3659
+ * Converts an OOXML `<a:arcTo>` (center-parameterized: start at the pen
3660
+ * point, angle `stAng`, sweep `swAng`, both in 60000ths of a degree, on a
3661
+ * `wR`×`hR` axis-aligned ellipse) into cubic-Bézier segments in the path's
3662
+ * own coordinate space.
3663
+ *
3664
+ * We approximate with cubics rather than emit an SVG `A` arc because the
3665
+ * endpoint-parameterized `A` command can't represent a sweep ≥ 360° (full
3666
+ * ellipses appear in custom geometry) and forces large-arc / sweep flag
3667
+ * bookkeeping for the signed `swAng`; splitting into ≤ 90° cubic segments
3668
+ * sidesteps both and stays accurate.
3669
+ */
3670
+ const arcToCubicSegments = (startX, startY, wR, hR, stAng, swAng) => {
3671
+ const toRad = (a) => a / 6e4 * (Math.PI / 180);
3672
+ const st = toRad(stAng);
3673
+ const sw = toRad(swAng);
3674
+ const cx = startX - wR * Math.cos(st);
3675
+ const cy = startY - hR * Math.sin(st);
3676
+ const segCount = Math.max(1, Math.ceil(Math.abs(sw) / (Math.PI / 2)));
3677
+ const delta = sw / segCount;
3678
+ const k = 4 / 3 * Math.tan(delta / 4);
3679
+ const segs = [];
3680
+ let a0 = st;
3681
+ for (let i = 0; i < segCount; i++) {
3682
+ const a1 = a0 + delta;
3683
+ const cos0 = Math.cos(a0);
3684
+ const sin0 = Math.sin(a0);
3685
+ const cos1 = Math.cos(a1);
3686
+ const sin1 = Math.sin(a1);
3687
+ const p1x = cx + wR * cos1;
3688
+ const p1y = cy + hR * sin1;
3689
+ segs.push({
3690
+ c1x: cx + wR * cos0 - k * wR * sin0,
3691
+ c1y: cy + hR * sin0 + k * hR * cos0,
3692
+ c2x: p1x + k * wR * sin1,
3693
+ c2y: p1y - k * hR * cos1,
3694
+ ex: p1x,
3695
+ ey: p1y
3696
+ });
3697
+ a0 = a1;
3698
+ }
3699
+ return segs;
3700
+ };
3701
+ /**
3702
+ * Renders an evaluated {@link CustomGeometry} as one `<path>` per
3703
+ * `GeomPath`, scaling each path's coordinate space (its own `w`/`h`, or
3704
+ * the shape extents when those are absent) onto the shape's slide box
3705
+ * `(x, y, w, h)` in EMU. Returns `''` when nothing drawable remains, so
3706
+ * the caller can fall back to the labelled rect.
3707
+ *
3708
+ * Per-path `fill="none"` paints stroke-only; `stroke="0"` paints
3709
+ * fill-only — mirroring `ST_PathFillMode` / the path `stroke` flag.
3710
+ */
3711
+ const customGeometryToSvg = (geom, x, y, w, h, fill, stroke, strokeWidthEmu, strokeExtra, markerExtra) => {
3712
+ const out = [];
3713
+ for (const path of geom.paths) {
3714
+ const cw = path.w ?? w;
3715
+ const ch = path.h ?? h;
3716
+ if (cw === 0 || ch === 0) continue;
3717
+ const sx = w / cw;
3718
+ const sy = h / ch;
3719
+ const fx = (gx) => ((x + gx * sx) / EMU_PER_PX).toFixed(2);
3720
+ const fy = (gy) => ((y + gy * sy) / EMU_PER_PX).toFixed(2);
3721
+ const pt = (gx, gy) => `${fx(gx)},${fy(gy)}`;
3722
+ let curX = 0;
3723
+ let curY = 0;
3724
+ let startX = 0;
3725
+ let startY = 0;
3726
+ const d = [];
3727
+ for (const cmd of path.commands) switch (cmd.kind) {
3728
+ case "moveTo":
3729
+ curX = cmd.pt.x;
3730
+ curY = cmd.pt.y;
3731
+ startX = curX;
3732
+ startY = curY;
3733
+ d.push(`M${pt(curX, curY)}`);
3734
+ break;
3735
+ case "lnTo":
3736
+ curX = cmd.pt.x;
3737
+ curY = cmd.pt.y;
3738
+ d.push(`L${pt(curX, curY)}`);
3739
+ break;
3740
+ case "quadBezTo":
3741
+ d.push(`Q${pt(cmd.pts[0].x, cmd.pts[0].y)} ${pt(cmd.pts[1].x, cmd.pts[1].y)}`);
3742
+ curX = cmd.pts[1].x;
3743
+ curY = cmd.pts[1].y;
3744
+ break;
3745
+ case "cubicBezTo":
3746
+ d.push(`C${pt(cmd.pts[0].x, cmd.pts[0].y)} ${pt(cmd.pts[1].x, cmd.pts[1].y)} ${pt(cmd.pts[2].x, cmd.pts[2].y)}`);
3747
+ curX = cmd.pts[2].x;
3748
+ curY = cmd.pts[2].y;
3749
+ break;
3750
+ case "arcTo": {
3751
+ const segs = arcToCubicSegments(curX, curY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng);
3752
+ for (const s of segs) d.push(`C${pt(s.c1x, s.c1y)} ${pt(s.c2x, s.c2y)} ${pt(s.ex, s.ey)}`);
3753
+ const last = segs[segs.length - 1];
3754
+ if (last) {
3755
+ curX = last.ex;
3756
+ curY = last.ey;
3757
+ }
3758
+ break;
3759
+ }
3760
+ case "close":
3761
+ d.push("Z");
3762
+ curX = startX;
3763
+ curY = startY;
3764
+ break;
3765
+ }
3766
+ if (d.length === 0) continue;
3767
+ const pathFill = path.fill === "none" ? "none" : fill;
3768
+ const strokeAttrs = path.stroke ? ` stroke="${stroke}" stroke-width="${E(strokeWidthEmu)}"${strokeExtra}${markerExtra}` : " stroke=\"none\"";
3769
+ out.push(`<path d="${d.join(" ")}" fill="${pathFill}"${strokeAttrs} fill-rule="evenodd"/>`);
3770
+ }
3771
+ return out.join("");
3772
+ };
3773
+ const renderShape = (shape, pres, theme, ctx) => {
3774
+ const bounds = getShapeBoundsResolved(pres, shape);
3775
+ if (!bounds) return "";
3776
+ const x = bounds.x;
3777
+ const y = bounds.y;
3778
+ const w = bounds.w;
3779
+ const h = bounds.h;
3780
+ const kind = getShapeKind(shape);
3781
+ if (kind === "connector" ? w <= 0 && h <= 0 : w <= 0 || h <= 0) return "";
3782
+ const fill = getShapeFillEffective(pres, shape);
3783
+ const stroke = getShapeStrokeEffective(pres, shape);
3784
+ const rotation = getShapeRotation(shape);
3785
+ const flip = getShapeFlip(shape) ?? {
3786
+ horizontal: false,
3787
+ vertical: false
3788
+ };
3789
+ const phType = getShapePlaceholderType(shape);
3790
+ const cx = x + w / 2;
3791
+ const cy = y + h / 2;
3792
+ const transforms = [];
3793
+ if (rotation !== 0) transforms.push(`rotate(${rotation} ${E(cx)} ${E(cy)})`);
3794
+ if (flip.horizontal) transforms.push(`translate(${E(2 * cx)} 0) scale(-1 1)`);
3795
+ if (flip.vertical) transforms.push(`translate(0 ${E(2 * cy)}) scale(1 -1)`);
3796
+ const transform = transforms.length > 0 ? ` transform="${transforms.join(" ")}"` : "";
3797
+ const textTransform = rotation !== 0 ? ` transform="rotate(${rotation} ${E(cx)} ${E(cy)})"` : "";
3798
+ const textOverlay = kind === "shape" || kind === "graphicFrame" ? renderTextBody(pres, shape, {
3799
+ x,
3800
+ y,
3801
+ w,
3802
+ h
3803
+ }, theme, phType, ctx) : "";
3804
+ if (kind === "picture") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageBytes(shape), getShapeImageFormat(shape));
3805
+ if (kind === "shape" && fill.kind === "image") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageFillBytes(shape), getShapeImageFormat(shape));
3806
+ if (kind === "connector") {
3807
+ const p = paint(shape, fill, stroke, theme, false, pres);
3808
+ const sw = p.strokeWidth || 19050;
3809
+ let x1 = x;
3810
+ let y1 = y;
3811
+ let x2 = x + w;
3812
+ let y2 = y + h;
3813
+ if (flip.horizontal) {
3814
+ x1 = x + w;
3815
+ x2 = x;
3816
+ }
3817
+ if (flip.vertical) {
3818
+ y1 = y + h;
3819
+ y2 = y;
3820
+ }
3821
+ const strokeColor = p.stroke === "none" ? resolveColor("scheme:tx1", theme, "#1F2937") : p.stroke;
3822
+ const sa = p.strokeAttrs ? ` ${p.strokeAttrs}` : "";
3823
+ const ma = p.markerAttrs ?? "";
3824
+ 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}/>`;
3826
+ const px1 = x1 / EMU_PER_PX;
3827
+ const py1 = y1 / EMU_PER_PX;
3828
+ const px2 = x2 / EMU_PER_PX;
3829
+ const py2 = y2 / EMU_PER_PX;
3830
+ let d = `M${px1.toFixed(2)} ${py1.toFixed(2)}`;
3831
+ if (preset === "bentConnector2") d += ` L${px2.toFixed(2)} ${py1.toFixed(2)} L${px2.toFixed(2)} ${py2.toFixed(2)}`;
3832
+ else if (preset === "bentConnector3") {
3833
+ const midX = (px1 + px2) / 2;
3834
+ d += ` L${midX.toFixed(2)} ${py1.toFixed(2)} L${midX.toFixed(2)} ${py2.toFixed(2)} L${px2.toFixed(2)} ${py2.toFixed(2)}`;
3835
+ } else if (preset === "bentConnector4") {
3836
+ const mx = (px1 + px2) / 2;
3837
+ const my = (py1 + py2) / 2;
3838
+ d += ` L${mx.toFixed(2)} ${py1.toFixed(2)} L${mx.toFixed(2)} ${my.toFixed(2)} L${px2.toFixed(2)} ${my.toFixed(2)} L${px2.toFixed(2)} ${py2.toFixed(2)}`;
3839
+ } else if (preset === "bentConnector5") {
3840
+ const q1x = px1 + (px2 - px1) / 3;
3841
+ const q2x = px1 + 2 * (px2 - px1) / 3;
3842
+ const my = (py1 + py2) / 2;
3843
+ d += ` L${q1x.toFixed(2)} ${py1.toFixed(2)} L${q1x.toFixed(2)} ${my.toFixed(2)} L${q2x.toFixed(2)} ${my.toFixed(2)} L${q2x.toFixed(2)} ${py2.toFixed(2)} L${px2.toFixed(2)} ${py2.toFixed(2)}`;
3844
+ } else if (preset === "curvedConnector2") d += ` Q${px2.toFixed(2)} ${py1.toFixed(2)} ${px2.toFixed(2)} ${py2.toFixed(2)}`;
3845
+ else if (preset === "curvedConnector3" || preset === "curvedConnector4" || preset === "curvedConnector5") {
3846
+ const c1x = px1 + (px2 - px1) / 3;
3847
+ const c2x = px1 + 2 * (px2 - px1) / 3;
3848
+ d += ` C${c1x.toFixed(2)} ${py1.toFixed(2)} ${c2x.toFixed(2)} ${py2.toFixed(2)} ${px2.toFixed(2)} ${py2.toFixed(2)}`;
3849
+ } 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}/>`;
3851
+ }
3852
+ if (kind === "group") {
3853
+ const xform = getGroupTransform(shape);
3854
+ const children = getGroupChildren(shape);
3855
+ if (children.length === 0) return "";
3856
+ const tParts = [];
3857
+ if (xform && rotation !== 0) {
3858
+ const cxG = (xform.outer.x + xform.outer.w / 2) / EMU_PER_PX;
3859
+ const cyG = (xform.outer.y + xform.outer.h / 2) / EMU_PER_PX;
3860
+ tParts.push(`rotate(${rotation} ${cxG.toFixed(2)} ${cyG.toFixed(2)})`);
3861
+ }
3862
+ if (xform && flip.horizontal) {
3863
+ const cxG = (xform.outer.x + xform.outer.w / 2) / EMU_PER_PX;
3864
+ tParts.push(`translate(${(2 * cxG).toFixed(2)} 0) scale(-1 1)`);
3865
+ }
3866
+ if (xform && flip.vertical) {
3867
+ const cyG = (xform.outer.y + xform.outer.h / 2) / EMU_PER_PX;
3868
+ tParts.push(`translate(0 ${(2 * cyG).toFixed(2)}) scale(1 -1)`);
3869
+ }
3870
+ if (xform) {
3871
+ const ox = xform.outer.x;
3872
+ const oy = xform.outer.y;
3873
+ const ow = xform.outer.w;
3874
+ const oh = xform.outer.h;
3875
+ const ix = xform.inner.x;
3876
+ const iy = xform.inner.y;
3877
+ const iw = xform.inner.w || 1;
3878
+ const ih = xform.inner.h || 1;
3879
+ const sx = (ow / iw).toFixed(6);
3880
+ const sy = (oh / ih).toFixed(6);
3881
+ const tx = ((ox - ix * (ow / iw)) / EMU_PER_PX).toFixed(2);
3882
+ const ty = ((oy - iy * (oh / ih)) / EMU_PER_PX).toFixed(2);
3883
+ tParts.push(`translate(${tx} ${ty})`, `scale(${sx} ${sy})`);
3884
+ }
3885
+ return `<g${tParts.length > 0 ? ` transform="${tParts.join(" ")}"` : ""}>${children.map((c) => renderShape(c, pres, theme, ctx)).join("")}</g>`;
3886
+ }
3887
+ const p = paint(shape, fill, stroke, theme, phType !== null, pres);
3888
+ if (kind === "graphicFrame") {
3889
+ if (isChartShape(shape)) {
3890
+ const chartSvg = renderChart(shape, x, y, w, h, transform, theme);
3891
+ if (chartSvg) return chartSvg;
3892
+ }
3893
+ if (isTableShape(shape)) {
3894
+ const tableSvg = renderTable(shape, pres, x, y, w, h, transform, theme, ctx);
3895
+ if (tableSvg) return tableSvg;
3896
+ }
3897
+ let label = "graphicFrame";
3898
+ try {
3899
+ if (isChartShape(shape)) label = "chart (unsupported kind)";
3900
+ else if (isTableShape(shape)) {
3901
+ const t = getTableDimensions(shape);
3902
+ label = `table (${t.rows}×${t.cols})`;
3903
+ }
3904
+ } catch {}
3905
+ return `<g data-pptx-fallback="${label.startsWith("chart") ? "chart" : "graphicFrame"}"${transform}><rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" fill="${p.fill === "none" ? "#F9FAFB" : p.fill}" stroke="#9CA3AF" stroke-width="${E(9525)}" stroke-dasharray="${E(5e4)},${E(3e4)}"/>${renderPicturePlaceholderLabel(x, y, w, h, label)}${textOverlay}</g>`;
3906
+ }
3907
+ const rawPreset = getShapePreset(shape);
3908
+ const preset = rawPreset ?? "rect";
3909
+ const sa = p.strokeAttrs ? ` ${p.strokeAttrs}` : "";
3910
+ const ma = p.markerAttrs ?? "";
3911
+ const customGeom = rawPreset === null ? getShapeCustomGeometry(shape) : null;
3912
+ let geomSvg = customGeom !== null ? customGeometryToSvg(customGeom, x, y, w, h, p.fill, p.stroke, p.strokeWidth, sa, ma) : "";
3913
+ const isCustGeom = geomSvg === "" && rawPreset === null && getShapeXmlString(shape).includes("custGeom");
3914
+ if (geomSvg !== "") {} else if (preset === "rect") 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}/>`;
3915
+ else if (preset === "roundRect") {
3916
+ const adjVal = getShapeAdjustValues(shape).adj ?? 16667;
3917
+ const ratio = Math.max(0, Math.min(.5, adjVal / 1e5));
3918
+ const r = E(Math.min(w, h) * ratio);
3919
+ geomSvg = `<rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" rx="${r}" ry="${r}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma}/>`;
3920
+ } else if (preset === "ellipse" || preset === "oval") geomSvg = `<ellipse cx="${E(cx)}" cy="${E(cy)}" rx="${E(w / 2)}" ry="${E(h / 2)}" fill="${p.fill}" stroke="${p.stroke}" stroke-width="${E(p.strokeWidth)}"${sa}${ma}/>`;
3921
+ else {
3922
+ const pathFn = PRESET_PATHS[preset];
3923
+ 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
+ else {
3925
+ 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}/>`;
3927
+ 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
+ }
3929
+ }
3930
+ const fx = buildEffectsFilter(pres, shape);
3931
+ const filterAttr = fx ? ` filter="url(#${fx.id})"` : "";
3932
+ let fxDefs = fx ? fx.defs : "";
3933
+ const reflection = buildReflection(pres, shape, geomSvg, {
3934
+ x,
3935
+ y,
3936
+ w,
3937
+ h
3938
+ });
3939
+ geomSvg = `<g${filterAttr}>${geomSvg}</g>`;
3940
+ if (reflection) {
3941
+ geomSvg = reflection.svg + geomSvg;
3942
+ fxDefs += reflection.defs;
3943
+ }
3944
+ const url = getShapeHyperlink(shape);
3945
+ const tooltip = getShapeHyperlinkTooltip(shape);
3946
+ const shapeName = (() => {
3947
+ try {
3948
+ return getShapeName(shape);
3949
+ } catch {
3950
+ return null;
3951
+ }
3952
+ })();
3953
+ const altTitle = (() => {
3954
+ try {
3955
+ return getShapeAltTitle(shape);
3956
+ } catch {
3957
+ return null;
3958
+ }
3959
+ })();
3960
+ const altDesc = (() => {
3961
+ try {
3962
+ return getShapeDescription(shape);
3963
+ } catch {
3964
+ return null;
3965
+ }
3966
+ })();
3967
+ const a11yLabel = altTitle ?? altDesc ?? null;
3968
+ const nameAttr = shapeName ? ` data-pptx-shape-name="${escapeXml(shapeName)}"` : "";
3969
+ const ariaAttr = a11yLabel ? ` role="img" aria-label="${escapeXml(a11yLabel)}"` : "";
3970
+ const placedText = textOverlay ? `<g${textTransform}>${textOverlay}</g>` : "";
3971
+ const custGeomAttr = isCustGeom ? " data-pptx-fallback=\"custGeom\"" : "";
3972
+ const inner = `${p.defs}${fxDefs}<g${nameAttr}${ariaAttr}${custGeomAttr}><g${transform}>${geomSvg}</g>${placedText}</g>`;
3973
+ const titleEl = tooltip ? `<title>${escapeXml(tooltip)}</title>` : "";
3974
+ if (url) return `<a href="${escapeXml(url)}" target="_blank" rel="noopener noreferrer">${titleEl}${inner}</a>`;
3975
+ const action = getShapeClickAction(shape);
3976
+ if (action) {
3977
+ let href = null;
3978
+ if (action.kind === "slide") {
3979
+ const idx = getSlideIndex(pres, action.slide);
3980
+ if (idx >= 0) href = `#slide-${idx + 1}`;
3981
+ } else if (action.kind === "url") href = action.url;
3982
+ if (href !== null) {
3983
+ const targetAttrs = href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
3984
+ return `<a href="${escapeXml(href)}"${targetAttrs}>${titleEl}${inner}</a>`;
3985
+ }
3986
+ }
3987
+ return inner;
3988
+ };
3989
+ const buildReflection = (pres, shape, geomRaw, box) => {
3990
+ let effects;
3991
+ try {
3992
+ effects = getShapeEffectsEffective(pres, shape);
3993
+ } catch {
3994
+ return null;
3995
+ }
3996
+ const refl = effects.find((e) => e.kind === "reflection");
3997
+ if (!refl || refl.kind !== "reflection") return null;
3998
+ const f = refl.scaleY ?? -1;
3999
+ const startA = refl.startOpacity ?? 1;
4000
+ const endA = refl.opacity ?? 0;
4001
+ const contactPx = (box.y + box.h) / EMU_PER_PX;
4002
+ const transform = `translate(0 ${(refl.distEmu / EMU_PER_PX + contactPx * (1 - f)).toFixed(2)}) scale(1 ${f})`;
4003
+ const maskId = mintId();
4004
+ const gradId = mintId();
4005
+ 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>`;
4006
+ return {
4007
+ svg: `<g transform="${transform}" mask="url(#${maskId})" data-pptx-reflection="1">${geomRaw}</g>`,
4008
+ defs
4009
+ };
4010
+ };
4011
+ const buildEffectsFilter = (pres, shape) => {
4012
+ let effects;
4013
+ try {
4014
+ effects = getShapeEffectsEffective(pres, shape);
4015
+ } catch {
4016
+ return null;
4017
+ }
4018
+ if (effects.length === 0) return null;
4019
+ const id = mintId();
4020
+ const primitives = [];
4021
+ const layers = [];
4022
+ for (const e of effects) if (e.kind === "outerShdw") {
4023
+ const rad = e.angleDeg * Math.PI / 180;
4024
+ const dx = e.distEmu * Math.cos(rad) / EMU_PER_PX;
4025
+ const dy = e.distEmu * Math.sin(rad) / EMU_PER_PX;
4026
+ const blurPx = e.blurEmu / EMU_PER_PX / 2;
4027
+ const opacity = e.opacity ?? 1;
4028
+ const color = e.color || "#000000";
4029
+ const out = `shdwOut${primitives.length}`;
4030
+ primitives.push(`<feDropShadow dx="${dx.toFixed(2)}" dy="${dy.toFixed(2)}" stdDeviation="${blurPx.toFixed(2)}" flood-color="${color}" flood-opacity="${opacity.toFixed(3)}" result="${out}"/>`);
4031
+ layers.push(out);
4032
+ } else if (e.kind === "innerShdw") {
4033
+ const rad = e.angleDeg * Math.PI / 180;
4034
+ const dx = e.distEmu * Math.cos(rad) / EMU_PER_PX;
4035
+ const dy = e.distEmu * Math.sin(rad) / EMU_PER_PX;
4036
+ const blurPx = e.blurEmu / EMU_PER_PX / 2;
4037
+ const color = e.color || "#000000";
4038
+ const opacity = e.opacity ?? 1;
4039
+ const i = primitives.length;
4040
+ 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
+ layers.push(`innerOut${i}`);
4042
+ } else if (e.kind === "glow") {
4043
+ const blurPx = e.radiusEmu / EMU_PER_PX / 2;
4044
+ const color = e.color || "#FFFFFF";
4045
+ const opacity = e.opacity ?? 1;
4046
+ 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}"/>`);
4048
+ layers.push(`glowOut${i}`);
4049
+ } else if (e.kind === "softEdge") {
4050
+ const blurPx = e.radiusEmu / EMU_PER_PX / 2;
4051
+ const i = primitives.length;
4052
+ primitives.push(`<feGaussianBlur in="SourceGraphic" stdDeviation="${blurPx.toFixed(2)}" result="softOut${i}"/>`);
4053
+ layers.length = 0;
4054
+ layers.push(`softOut${i}`);
4055
+ } else if (e.kind === "blur") {
4056
+ const blurPx = e.radiusEmu / EMU_PER_PX / 2;
4057
+ const i = primitives.length;
4058
+ primitives.push(`<feGaussianBlur in="SourceGraphic" stdDeviation="${blurPx.toFixed(2)}" result="blurOut${i}"/>`);
4059
+ layers.length = 0;
4060
+ layers.push(`blurOut${i}`);
4061
+ }
4062
+ if (layers.length === 0) return null;
4063
+ const replacedSource = effects.some((e) => e.kind === "softEdge" || e.kind === "blur");
4064
+ const mergeChildren = layers.map((l) => `<feMergeNode in="${l}"/>`).join("");
4065
+ const sourceMerge = replacedSource ? "" : "<feMergeNode in=\"SourceGraphic\"/>";
4066
+ primitives.push(`<feMerge>${mergeChildren}${sourceMerge}</feMerge>`);
4067
+ return {
4068
+ id,
4069
+ defs: `<defs><filter id="${id}" x="-25%" y="-25%" width="150%" height="150%">${primitives.join("")}</filter></defs>`
4070
+ };
4071
+ };
4072
+ const renderSlideSvg = (pres, slide, opts = {}) => {
4073
+ const size = getSlideSize(pres) ?? DEFAULT_SIZE;
4074
+ const W = size.width;
4075
+ const H = size.height;
4076
+ const theme = getPresentationTheme(pres);
4077
+ activeColorMap = getEffectiveColorMap(slide);
4078
+ activeDeckTextColor = resolveDeckBodyTextColor(slide) ?? "#000000";
4079
+ const ctx = {
4080
+ mode: opts.textLayout ?? "foreignObject",
4081
+ measure: opts.measureText ?? defaultMeasurer
4082
+ };
4083
+ let bg = getSlideBackground(slide);
4084
+ if (bg.kind === "inherit") {
4085
+ const layout = getSlideLayout(slide);
4086
+ if (layout) {
4087
+ const layoutBg = getSlideLayoutBackground(layout);
4088
+ if (layoutBg.kind !== "inherit") bg = layoutBg;
4089
+ else {
4090
+ const masterBg = getSlideMasterBackground(pres, layout);
4091
+ if (masterBg.kind !== "inherit") bg = masterBg;
4092
+ }
4093
+ }
4094
+ }
4095
+ let bgColor = "#FFFFFF";
4096
+ let bgGradient = "";
4097
+ let bgGradientDefs = "";
4098
+ if (bg.kind === "solid") bgColor = resolveColor(bg.color, theme, "#FFFFFF");
4099
+ else if (bg.kind === "gradient") {
4100
+ let grad = getSlideBackgroundGradientFill(slide);
4101
+ if (!grad) {
4102
+ const layout = getSlideLayout(slide);
4103
+ if (layout) {
4104
+ grad = getSlideLayoutBackgroundGradientFill(layout);
4105
+ if (!grad) grad = getSlideMasterBackgroundGradientFill(pres, layout);
4106
+ }
4107
+ }
4108
+ if (grad) {
4109
+ const built = gradientDef(grad, theme);
4110
+ bgGradientDefs = built.defs;
4111
+ bgGradient = `<rect width="${E(W)}" height="${E(H)}" fill="${built.fillAttr}"/>`;
4112
+ }
4113
+ } else if (bg.kind === "pattern") {
4114
+ let pat = getSlideBackgroundPatternFill(pres, slide);
4115
+ if (!pat) {
4116
+ const layout = getSlideLayout(slide);
4117
+ if (layout) {
4118
+ pat = getSlideLayoutBackgroundPatternFill(pres, layout);
4119
+ if (!pat) pat = getSlideMasterBackgroundPatternFill(pres, layout);
4120
+ }
4121
+ }
4122
+ if (pat) {
4123
+ const built = patternDef(pat);
4124
+ bgGradientDefs += built.defs;
4125
+ bgGradient = `<rect width="${E(W)}" height="${E(H)}" fill="${built.fillAttr}"/>`;
4126
+ }
4127
+ } else if (theme && bg.kind === "inherit") bgColor = normalizeHex(theme.light1);
4128
+ let bgImage = "";
4129
+ if (bg.kind === "image") {
4130
+ let bytes = getSlideBackgroundImageBytes(slide);
4131
+ if (!bytes && pres) {
4132
+ const layout = getSlideLayout(slide);
4133
+ if (layout) {
4134
+ bytes = getSlideLayoutBackgroundImageBytes(pres, layout);
4135
+ if (!bytes) bytes = getSlideMasterBackgroundImageBytes(pres, layout);
4136
+ }
4137
+ }
4138
+ if (bytes) {
4139
+ const fmt = detectImageFormatLocal(bytes);
4140
+ const dataUrl = `data:${fmt ? imageMime[fmt] ?? "image/png" : "image/png"};base64,${u8ToBase64(bytes)}`;
4141
+ bgImage = `<image x="0" y="0" width="${E(W)}" height="${E(H)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="xMidYMid slice"/>`;
4142
+ }
4143
+ }
4144
+ let layoutBgShapes = "";
4145
+ const layoutForBg = getSlideLayout(slide);
4146
+ if (layoutForBg) try {
4147
+ const masterShapes = getSlideMasterShapes(pres, layoutForBg);
4148
+ const layoutShapes = getSlideLayoutShapes(pres, layoutForBg);
4149
+ layoutBgShapes = [...masterShapes, ...layoutShapes].map((s) => renderShape(s, pres, theme, ctx)).join("");
4150
+ } catch {
4151
+ layoutBgShapes = "";
4152
+ }
4153
+ const shapesSvg = getSlideShapes(slide).map((s) => renderShape(s, pres, theme, ctx)).join("");
4154
+ return [
4155
+ `<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">`,
4156
+ bgGradientDefs,
4157
+ `<rect width="${E(W)}" height="${E(H)}" fill="${bgColor}"/>`,
4158
+ bgGradient,
4159
+ bgImage,
4160
+ layoutBgShapes,
4161
+ shapesSvg,
4162
+ "</svg>"
4163
+ ].join("");
4164
+ };
4165
+ const detectImageFormatLocal = (bytes) => {
4166
+ if (bytes.length < 4) return null;
4167
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "png";
4168
+ if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "jpeg";
4169
+ if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return "gif";
4170
+ if (bytes[0] === 66 && bytes[1] === 77) return "bmp";
4171
+ if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "webp";
4172
+ return null;
4173
+ };
4174
+ //#endregion
4175
+ 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
+
4177
+ //# sourceMappingURL=src-CDTTqUfI.js.map