@shotstack/shotstack-canvas 1.7.2 → 1.8.1

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.
@@ -31,9 +31,52 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var entry_node_exports = {};
32
32
  __export(entry_node_exports, {
33
33
  CanvasRichTextAssetSchema: () => CanvasRichTextAssetSchema,
34
+ CanvasSvgAssetSchema: () => CanvasSvgAssetSchema,
35
+ arcToCubicBeziers: () => arcToCubicBeziers,
36
+ centerPath: () => centerPath,
37
+ commandsToPathString: () => commandsToPathString,
38
+ computePathBounds: () => computePathBounds3,
39
+ createNodePainter: () => createNodePainter,
34
40
  createTextEngine: () => createTextEngine,
41
+ generateArrowPath: () => generateArrowPath,
42
+ generateCirclePath: () => generateCirclePath,
43
+ generateCrossPath: () => generateCrossPath,
44
+ generateEllipsePath: () => generateEllipsePath,
45
+ generateHeartPath: () => generateHeartPath,
46
+ generateLinePath: () => generateLinePath,
47
+ generatePolygonPath: () => generatePolygonPath,
48
+ generatePolygonPoints: () => generatePolygonPoints,
49
+ generateRectanglePath: () => generateRectanglePath,
50
+ generateRingPath: () => generateRingPath,
51
+ generateShapePath: () => generateShapePath,
52
+ generateStarPath: () => generateStarPath,
53
+ generateStarPoints: () => generateStarPoints,
54
+ generateTrianglePath: () => generateTrianglePath,
55
+ importSvg: () => importSvg,
35
56
  isGlyphFill: () => isGlyphFill2,
36
- isShadowFill: () => isShadowFill2
57
+ isShadowFill: () => isShadowFill2,
58
+ normalizePath: () => normalizePath,
59
+ normalizePathString: () => normalizePathString,
60
+ normalizePathToSize: () => normalizePathToSize,
61
+ parseSvgMarkup: () => parseSvgMarkup,
62
+ parseSvgPath: () => parseSvgPath,
63
+ pointsToPath: () => pointsToPath,
64
+ quadraticToCubic: () => quadraticToCubic,
65
+ reverseWindingOrder: () => reverseWindingOrder,
66
+ rotatePath: () => rotatePath,
67
+ scalePath: () => scalePath,
68
+ svgAssetSchema: () => import_zod2.svgAssetSchema,
69
+ svgGradientStopSchema: () => import_zod2.svgGradientStopSchema,
70
+ svgLinearGradientFillSchema: () => import_zod2.svgLinearGradientFillSchema,
71
+ svgRadialGradientFillSchema: () => import_zod2.svgRadialGradientFillSchema,
72
+ svgShadowSchema: () => import_zod2.svgShadowSchema,
73
+ svgShapeSchema: () => import_zod2.svgShapeSchema,
74
+ svgSolidFillSchema: () => import_zod2.svgSolidFillSchema,
75
+ svgStrokeSchema: () => import_zod2.svgStrokeSchema,
76
+ svgToAsset: () => svgToAsset,
77
+ svgToSingleAsset: () => svgToSingleAsset,
78
+ svgTransformSchema: () => import_zod2.svgTransformSchema,
79
+ translatePath: () => translatePath
37
80
  });
38
81
  module.exports = __toCommonJS(entry_node_exports);
39
82
 
@@ -194,6 +237,7 @@ var CanvasRichTextAssetSchema = import_zod2.richTextAssetSchema.extend({
194
237
  animation: canvasAnimationSchema.optional(),
195
238
  customFonts: import_zod.z.array(customFontSchema).optional()
196
239
  }).strict();
240
+ var CanvasSvgAssetSchema = import_zod2.svgAssetSchema;
197
241
 
198
242
  // src/wasm/hb-loader.ts
199
243
  var import_meta = {};
@@ -430,6 +474,9 @@ var FontRegistry = class _FontRegistry {
430
474
  fonts = /* @__PURE__ */ new Map();
431
475
  blobs = /* @__PURE__ */ new Map();
432
476
  fontkitFonts = /* @__PURE__ */ new Map();
477
+ fontkitBaseFonts = /* @__PURE__ */ new Map();
478
+ colorEmojiFonts = /* @__PURE__ */ new Set();
479
+ colorEmojiFontBytes = /* @__PURE__ */ new Map();
433
480
  wasmBaseURL;
434
481
  initPromise;
435
482
  emojiFallbackDesc;
@@ -493,6 +540,49 @@ var FontRegistry = class _FontRegistry {
493
540
  setEmojiFallback(desc) {
494
541
  this.emojiFallbackDesc = desc;
495
542
  }
543
+ isColorEmojiFont(family) {
544
+ return this.colorEmojiFonts.has(family);
545
+ }
546
+ getColorEmojiFontBytes(family) {
547
+ return this.colorEmojiFontBytes.get(family);
548
+ }
549
+ getColorEmojiFontFamilies() {
550
+ return Array.from(this.colorEmojiFonts);
551
+ }
552
+ detectColorEmojiFont(fkFont) {
553
+ try {
554
+ if (fkFont.directory && fkFont.directory.tables) {
555
+ const tables = fkFont.directory.tables;
556
+ if (tables.CBDT || tables.CBLC) {
557
+ return true;
558
+ }
559
+ if (tables.sbix) {
560
+ return true;
561
+ }
562
+ if (tables.COLR && tables.CPAL) {
563
+ return true;
564
+ }
565
+ if (tables.SVG) {
566
+ return true;
567
+ }
568
+ }
569
+ if (fkFont.availableTables) {
570
+ const tables = fkFont.availableTables;
571
+ if (tables.includes("CBDT") || tables.includes("CBLC") || tables.includes("sbix") || tables.includes("COLR") && tables.includes("CPAL") || tables.includes("SVG")) {
572
+ return true;
573
+ }
574
+ }
575
+ if (fkFont._tables) {
576
+ const tableNames = Object.keys(fkFont._tables);
577
+ if (tableNames.includes("CBDT") || tableNames.includes("CBLC") || tableNames.includes("sbix") || tableNames.includes("COLR") && tableNames.includes("CPAL") || tableNames.includes("SVG")) {
578
+ return true;
579
+ }
580
+ }
581
+ return false;
582
+ } catch {
583
+ return false;
584
+ }
585
+ }
496
586
  constructor(wasmBaseURL) {
497
587
  this.wasmBaseURL = wasmBaseURL;
498
588
  }
@@ -561,13 +651,29 @@ var FontRegistry = class _FontRegistry {
561
651
  try {
562
652
  const buffer = typeof Buffer !== "undefined" ? Buffer.from(bytes) : new Uint8Array(bytes);
563
653
  const fkFont = fontkit.create(buffer);
654
+ const baseFontKey = desc.family;
655
+ if (!this.fontkitBaseFonts.has(baseFontKey)) {
656
+ this.fontkitBaseFonts.set(baseFontKey, fkFont);
657
+ }
658
+ const isColorEmojiFont = this.detectColorEmojiFont(fkFont);
659
+ if (isColorEmojiFont) {
660
+ this.colorEmojiFonts.add(desc.family);
661
+ if (!this.colorEmojiFontBytes.has(desc.family)) {
662
+ this.colorEmojiFontBytes.set(desc.family, bytes.slice(0));
663
+ }
664
+ console.log(`\u{1F3A8} Registered color emoji font: ${desc.family}`);
665
+ }
564
666
  if (fkFont.variationAxes && Object.keys(fkFont.variationAxes).length > 0) {
565
667
  const variationFont = fkFont.getVariation({ wght: weightNum });
566
668
  this.fontkitFonts.set(k, variationFont);
567
- console.log(`\u2705 Registered variable font: ${desc.family} weight=${weightNum}`);
669
+ if (!isColorEmojiFont) {
670
+ console.log(`\u2705 Registered variable font: ${desc.family} weight=${weightNum}`);
671
+ }
568
672
  } else {
569
673
  this.fontkitFonts.set(k, fkFont);
570
- console.log(`\u2705 Registered static font: ${desc.family}`);
674
+ if (!isColorEmojiFont) {
675
+ console.log(`\u2705 Registered static font: ${desc.family}`);
676
+ }
571
677
  }
572
678
  } catch (err) {
573
679
  console.warn(`\u26A0\uFE0F Fontkit failed for ${desc.family}:`, err);
@@ -606,6 +712,9 @@ var FontRegistry = class _FontRegistry {
606
712
  const installed = await this.tryFallbackInstall(desc);
607
713
  f = installed ? this.fonts.get(k) : void 0;
608
714
  }
715
+ if (!f) {
716
+ f = await this.tryDeriveFromExistingFont(desc);
717
+ }
609
718
  if (!f) throw new Error(`Font not registered for ${k}`);
610
719
  return f;
611
720
  } catch (err) {
@@ -617,6 +726,53 @@ var FontRegistry = class _FontRegistry {
617
726
  );
618
727
  }
619
728
  }
729
+ async tryDeriveFromExistingFont(desc) {
730
+ const targetFamily = desc.family;
731
+ const targetWeight = normalizeWeight(desc.weight);
732
+ const targetWeightNum = parseInt(targetWeight, 10);
733
+ let existingBlob;
734
+ for (const [key, blob] of this.blobs) {
735
+ if (key.startsWith(`${targetFamily}__`)) {
736
+ existingBlob = blob;
737
+ break;
738
+ }
739
+ }
740
+ const baseFkFont = this.fontkitBaseFonts.get(targetFamily);
741
+ if (!existingBlob || !baseFkFont) {
742
+ return void 0;
743
+ }
744
+ const hasWeightAxis = baseFkFont.variationAxes && baseFkFont.variationAxes["wght"] !== void 0;
745
+ if (!hasWeightAxis) {
746
+ console.warn(
747
+ `Font ${targetFamily} is not a variable font, cannot derive weight ${targetWeight}`
748
+ );
749
+ return void 0;
750
+ }
751
+ const k = this.key(desc);
752
+ const face = this.hb.createFace(existingBlob, 0);
753
+ const font = this.hb.createFont(face);
754
+ const upem = face.upem || 1e3;
755
+ font.setScale(upem, upem);
756
+ if (typeof font.setVariations === "function") {
757
+ try {
758
+ font.setVariations(`wght=${targetWeightNum}`);
759
+ } catch (e) {
760
+ }
761
+ }
762
+ this.faces.set(k, face);
763
+ this.fonts.set(k, font);
764
+ try {
765
+ const variationFont = baseFkFont.getVariation({ wght: targetWeightNum });
766
+ this.fontkitFonts.set(k, variationFont);
767
+ console.log(
768
+ `\u2705 Derived variable font: ${targetFamily} weight=${targetWeightNum} from existing registration`
769
+ );
770
+ } catch (err) {
771
+ this.fontkitFonts.set(k, baseFkFont);
772
+ console.warn(`\u26A0\uFE0F Could not apply weight variation, using base font for ${targetFamily}`);
773
+ }
774
+ return font;
775
+ }
620
776
  async getFace(desc) {
621
777
  try {
622
778
  if (!this.hb) await this.init();
@@ -626,6 +782,10 @@ var FontRegistry = class _FontRegistry {
626
782
  const installed = await this.tryFallbackInstall(desc);
627
783
  face = installed ? this.faces.get(k) : void 0;
628
784
  }
785
+ if (!face) {
786
+ await this.tryDeriveFromExistingFont(desc);
787
+ face = this.faces.get(k);
788
+ }
629
789
  return face;
630
790
  } catch (err) {
631
791
  throw new Error(
@@ -696,6 +856,9 @@ var FontRegistry = class _FontRegistry {
696
856
  this.faces.clear();
697
857
  this.blobs.clear();
698
858
  this.fontkitFonts.clear();
859
+ this.fontkitBaseFonts.clear();
860
+ this.colorEmojiFonts.clear();
861
+ this.colorEmojiFontBytes.clear();
699
862
  this.hb = void 0;
700
863
  this.initPromise = void 0;
701
864
  } catch (err) {
@@ -820,7 +983,10 @@ var LayoutEngine = class {
820
983
  const charIndex = g.cl;
821
984
  let char;
822
985
  if (charIndex >= 0 && charIndex < input.length) {
823
- char = input[charIndex];
986
+ const codePoint = input.codePointAt(charIndex);
987
+ if (codePoint !== void 0) {
988
+ char = String.fromCodePoint(codePoint);
989
+ }
824
990
  }
825
991
  return {
826
992
  id: g.g,
@@ -1005,13 +1171,27 @@ async function buildDrawOps(p) {
1005
1171
  });
1006
1172
  }
1007
1173
  for (const glyph of line.glyphs) {
1174
+ const glyphX = xCursor + glyph.xOffset;
1175
+ const glyphY = baselineY + glyph.yOffset;
1176
+ const glyphFamily = glyph.fontDesc?.family;
1177
+ const isColorEmoji = glyph.isColorEmoji || glyphFamily && p.isColorEmojiFont && p.isColorEmojiFont(glyphFamily);
1178
+ if (isColorEmoji && glyph.char) {
1179
+ textOps.push({
1180
+ op: "DrawColorEmoji",
1181
+ char: glyph.char,
1182
+ x: glyphX,
1183
+ y: glyphY,
1184
+ fontSize: p.font.size,
1185
+ fontFamily: glyphFamily || "NotoColorEmoji"
1186
+ });
1187
+ xCursor += glyph.xAdvance;
1188
+ continue;
1189
+ }
1008
1190
  const path = await p.glyphPathProvider(glyph.id, glyph.fontDesc);
1009
1191
  if (!path || path === "M 0 0") {
1010
1192
  xCursor += glyph.xAdvance;
1011
1193
  continue;
1012
1194
  }
1013
- const glyphX = xCursor + glyph.xOffset;
1014
- const glyphY = baselineY + glyph.yOffset;
1015
1195
  const pb = computePathBounds(path);
1016
1196
  const x1 = glyphX + scale * pb.x;
1017
1197
  const x2 = glyphX + scale * (pb.x + pb.w);
@@ -1834,14 +2014,14 @@ async function createNodePainter(opts) {
1834
2014
  ctx.setTransform(1, 0, 0, 1, 0, 0);
1835
2015
  offscreenCtx.setTransform(1, 0, 0, 1, 0, 0);
1836
2016
  const hasBackground = !!(op.bg && op.bg.color);
1837
- needsAlphaExtraction = !hasBackground;
1838
- if (op.bg && op.bg.color) {
1839
- ctx.clearRect(0, 0, op.width, op.height);
2017
+ const hasRoundedBackground = hasBackground && op.bg && op.bg.radius && op.bg.radius > 0;
2018
+ needsAlphaExtraction = !!hasRoundedBackground;
2019
+ ctx.clearRect(0, 0, op.width, op.height);
2020
+ offscreenCtx.clearRect(0, 0, op.width, op.height);
2021
+ if (hasBackground && op.bg && op.bg.color) {
1840
2022
  const { color, opacity, radius } = op.bg;
1841
2023
  const c = parseHex6(color, opacity);
1842
- ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1843
- if (radius && radius > 0) {
1844
- needsAlphaExtraction = true;
2024
+ if (hasRoundedBackground) {
1845
2025
  ctx.save();
1846
2026
  ctx.fillStyle = "rgb(255, 255, 255)";
1847
2027
  ctx.fillRect(0, 0, op.width, op.height);
@@ -1863,18 +2043,9 @@ async function createNodePainter(opts) {
1863
2043
  offscreenCtx.fill();
1864
2044
  offscreenCtx.restore();
1865
2045
  } else {
2046
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1866
2047
  ctx.fillRect(0, 0, op.width, op.height);
1867
2048
  }
1868
- } else {
1869
- ctx.clearRect(0, 0, op.width, op.height);
1870
- ctx.save();
1871
- ctx.fillStyle = "rgb(255, 255, 255)";
1872
- ctx.fillRect(0, 0, op.width, op.height);
1873
- ctx.restore();
1874
- offscreenCtx.save();
1875
- offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1876
- offscreenCtx.fillRect(0, 0, op.width, op.height);
1877
- offscreenCtx.restore();
1878
2049
  }
1879
2050
  continue;
1880
2051
  }
@@ -1986,6 +2157,97 @@ async function createNodePainter(opts) {
1986
2157
  });
1987
2158
  continue;
1988
2159
  }
2160
+ if (op.op === "DrawColorEmoji") {
2161
+ renderToBoth((context) => {
2162
+ context.save();
2163
+ context.font = `${op.fontSize}px "${op.fontFamily}"`;
2164
+ context.textBaseline = "alphabetic";
2165
+ context.fillText(op.char, op.x, op.y);
2166
+ context.restore();
2167
+ });
2168
+ continue;
2169
+ }
2170
+ if (op.op === "Ellipse") {
2171
+ renderToBoth((context) => {
2172
+ context.save();
2173
+ const bbox = op.gradientBBox ?? {
2174
+ x: op.cx - op.rx,
2175
+ y: op.cy - op.ry,
2176
+ w: op.rx * 2,
2177
+ h: op.ry * 2
2178
+ };
2179
+ const fill = makeGradientFromBBox(context, op.fill, bbox);
2180
+ context.fillStyle = fill;
2181
+ context.beginPath();
2182
+ const rotation = (op.rotation ?? 0) * Math.PI / 180;
2183
+ context.ellipse(op.cx, op.cy, op.rx, op.ry, rotation, 0, Math.PI * 2);
2184
+ context.fill();
2185
+ context.restore();
2186
+ });
2187
+ continue;
2188
+ }
2189
+ if (op.op === "EllipseStroke") {
2190
+ renderToBoth((context) => {
2191
+ context.save();
2192
+ applyStrokeSpec(context, op.stroke);
2193
+ context.beginPath();
2194
+ const rotation = (op.rotation ?? 0) * Math.PI / 180;
2195
+ context.ellipse(op.cx, op.cy, op.rx, op.ry, rotation, 0, Math.PI * 2);
2196
+ context.stroke();
2197
+ context.restore();
2198
+ });
2199
+ continue;
2200
+ }
2201
+ if (op.op === "Polygon") {
2202
+ renderToBoth((context) => {
2203
+ context.save();
2204
+ const bbox = op.gradientBBox ?? computePointsBounds(op.points);
2205
+ const fill = makeGradientFromBBox(context, op.fill, bbox);
2206
+ context.fillStyle = fill;
2207
+ context.beginPath();
2208
+ drawPolygonPath(context, op.points, op.closed ?? true);
2209
+ context.fill();
2210
+ context.restore();
2211
+ });
2212
+ continue;
2213
+ }
2214
+ if (op.op === "PolygonStroke") {
2215
+ renderToBoth((context) => {
2216
+ context.save();
2217
+ applyStrokeSpec(context, op.stroke);
2218
+ context.beginPath();
2219
+ drawPolygonPath(context, op.points, op.closed ?? true);
2220
+ context.stroke();
2221
+ context.restore();
2222
+ });
2223
+ continue;
2224
+ }
2225
+ if (op.op === "ShapePath") {
2226
+ renderToBoth((context) => {
2227
+ context.save();
2228
+ context.translate(op.x, op.y);
2229
+ const bbox = op.gradientBBox ?? computePathBounds2(op.path);
2230
+ const fill = makeGradientFromBBox(context, op.fill, bbox);
2231
+ context.fillStyle = fill;
2232
+ context.beginPath();
2233
+ drawSvgPathOnCtx(context, op.path);
2234
+ context.fill();
2235
+ context.restore();
2236
+ });
2237
+ continue;
2238
+ }
2239
+ if (op.op === "ShapePathStroke") {
2240
+ renderToBoth((context) => {
2241
+ context.save();
2242
+ context.translate(op.x, op.y);
2243
+ applyStrokeSpec(context, op.stroke);
2244
+ context.beginPath();
2245
+ drawSvgPathOnCtx(context, op.path);
2246
+ context.stroke();
2247
+ context.restore();
2248
+ });
2249
+ continue;
2250
+ }
1989
2251
  }
1990
2252
  if (needsAlphaExtraction) {
1991
2253
  const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -2203,6 +2465,48 @@ function roundRectPath(ctx, x, y, w, h, r) {
2203
2465
  function tokenizePath2(d) {
2204
2466
  return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
2205
2467
  }
2468
+ function applyStrokeSpec(ctx, stroke) {
2469
+ const c = parseHex6(stroke.color, stroke.opacity);
2470
+ ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
2471
+ ctx.lineWidth = stroke.width;
2472
+ ctx.lineCap = stroke.lineCap ?? "butt";
2473
+ ctx.lineJoin = stroke.lineJoin ?? "miter";
2474
+ if (stroke.dashArray && stroke.dashArray.length > 0) {
2475
+ ctx.setLineDash(stroke.dashArray);
2476
+ }
2477
+ }
2478
+ function computePointsBounds(points) {
2479
+ if (points.length === 0) {
2480
+ return { x: 0, y: 0, w: 0, h: 0 };
2481
+ }
2482
+ let minX = points[0].x;
2483
+ let minY = points[0].y;
2484
+ let maxX = points[0].x;
2485
+ let maxY = points[0].y;
2486
+ for (let i = 1; i < points.length; i++) {
2487
+ const p = points[i];
2488
+ if (p.x < minX) minX = p.x;
2489
+ if (p.y < minY) minY = p.y;
2490
+ if (p.x > maxX) maxX = p.x;
2491
+ if (p.y > maxY) maxY = p.y;
2492
+ }
2493
+ return {
2494
+ x: minX,
2495
+ y: minY,
2496
+ w: maxX - minX,
2497
+ h: maxY - minY
2498
+ };
2499
+ }
2500
+ function drawPolygonPath(ctx, points, closed) {
2501
+ if (points.length === 0) return;
2502
+ ctx.moveTo(points[0].x, points[0].y);
2503
+ for (let i = 1; i < points.length; i++) {
2504
+ ctx.lineTo(points[i].x, points[i].y);
2505
+ }
2506
+ if (closed) {
2507
+ ctx.closePath();
2508
+ }
2509
+ }
2206
2510
 
2207
2511
  // src/io/node.ts
2208
2512
  var import_promises = require("fs/promises");
@@ -2451,7 +2755,1150 @@ var isGlyphFill2 = (op) => {
2451
2755
  return op.op === "FillPath" && op.isShadow !== true;
2452
2756
  };
2453
2757
 
2758
+ // src/core/shape-generators.ts
2759
+ var KAPPA = 0.5522847498307936;
2760
+ var DEG_TO_RAD = Math.PI / 180;
2761
+ function generateShapePath(params) {
2762
+ switch (params.shape) {
2763
+ case "rectangle":
2764
+ return generateRectanglePath(params);
2765
+ case "circle":
2766
+ return generateCirclePath(params);
2767
+ case "ellipse":
2768
+ return generateEllipsePath(params);
2769
+ case "line":
2770
+ return generateLinePath(params);
2771
+ case "triangle":
2772
+ return generateTrianglePath(params);
2773
+ case "polygon":
2774
+ return generatePolygonPath(params);
2775
+ case "star":
2776
+ return generateStarPath(params);
2777
+ case "arrow":
2778
+ return generateArrowPath(params);
2779
+ case "heart":
2780
+ return generateHeartPath(params);
2781
+ case "cross":
2782
+ return generateCrossPath(params);
2783
+ case "ring":
2784
+ return generateRingPath(params);
2785
+ }
2786
+ }
2787
+ function generateRectanglePath(params) {
2788
+ const { width, height, cornerRadius = 0 } = params;
2789
+ if (cornerRadius === 0) {
2790
+ return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
2791
+ }
2792
+ const r = Math.min(cornerRadius, width / 2, height / 2);
2793
+ return [
2794
+ `M ${r} 0`,
2795
+ `L ${width - r} 0`,
2796
+ `Q ${width} 0 ${width} ${r}`,
2797
+ `L ${width} ${height - r}`,
2798
+ `Q ${width} ${height} ${width - r} ${height}`,
2799
+ `L ${r} ${height}`,
2800
+ `Q 0 ${height} 0 ${height - r}`,
2801
+ `L 0 ${r}`,
2802
+ `Q 0 0 ${r} 0`,
2803
+ "Z"
2804
+ ].join(" ");
2805
+ }
2806
+ function generateCirclePath(params) {
2807
+ const { radius } = params;
2808
+ return generateEllipsePath({ radiusX: radius, radiusY: radius });
2809
+ }
2810
+ function generateEllipsePath(params) {
2811
+ const { radiusX, radiusY } = params;
2812
+ const kx = radiusX * KAPPA;
2813
+ const ky = radiusY * KAPPA;
2814
+ return [
2815
+ `M ${radiusX} 0`,
2816
+ `C ${radiusX} ${ky} ${kx} ${radiusY} 0 ${radiusY}`,
2817
+ `C ${-kx} ${radiusY} ${-radiusX} ${ky} ${-radiusX} 0`,
2818
+ `C ${-radiusX} ${-ky} ${-kx} ${-radiusY} 0 ${-radiusY}`,
2819
+ `C ${kx} ${-radiusY} ${radiusX} ${-ky} ${radiusX} 0`,
2820
+ "Z"
2821
+ ].join(" ");
2822
+ }
2823
+ function generateLinePath(params) {
2824
+ const { length, thickness } = params;
2825
+ const halfThickness = thickness / 2;
2826
+ return [
2827
+ `M 0 ${-halfThickness}`,
2828
+ `L ${length} ${-halfThickness}`,
2829
+ `L ${length} ${halfThickness}`,
2830
+ `L 0 ${halfThickness}`,
2831
+ "Z"
2832
+ ].join(" ");
2833
+ }
2834
+ function generateTrianglePath(params) {
2835
+ const { width, height } = params;
2836
+ const halfWidth = width / 2;
2837
+ return `M 0 ${height} L ${halfWidth} 0 L ${width} ${height} Z`;
2838
+ }
2839
+ function generatePolygonPath(params) {
2840
+ const { sides, radius } = params;
2841
+ const points = generatePolygonPoints(sides, radius);
2842
+ return pointsToPath(points, true);
2843
+ }
2844
+ function generatePolygonPoints(sides, radius) {
2845
+ const points = [];
2846
+ const angleStep = 2 * Math.PI / sides;
2847
+ const startAngle = -Math.PI / 2;
2848
+ for (let i = 0; i < sides; i++) {
2849
+ const angle = startAngle + i * angleStep;
2850
+ points.push({
2851
+ x: radius * Math.cos(angle),
2852
+ y: radius * Math.sin(angle)
2853
+ });
2854
+ }
2855
+ return points;
2856
+ }
2857
+ function generateStarPath(params) {
2858
+ const { points, outerRadius, innerRadius } = params;
2859
+ const starPoints = generateStarPoints(points, outerRadius, innerRadius);
2860
+ return pointsToPath(starPoints, true);
2861
+ }
2862
+ function generateStarPoints(pointCount, outerRadius, innerRadius) {
2863
+ const points = [];
2864
+ const totalPoints = pointCount * 2;
2865
+ const angleStep = Math.PI / pointCount;
2866
+ const startAngle = -Math.PI / 2;
2867
+ for (let i = 0; i < totalPoints; i++) {
2868
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
2869
+ const angle = startAngle + i * angleStep;
2870
+ points.push({
2871
+ x: radius * Math.cos(angle),
2872
+ y: radius * Math.sin(angle)
2873
+ });
2874
+ }
2875
+ return points;
2876
+ }
2877
+ function generateArrowPath(params) {
2878
+ const { length, headWidth, headLength, shaftWidth } = params;
2879
+ const halfShaft = shaftWidth / 2;
2880
+ const halfHead = headWidth / 2;
2881
+ const shaftLength = length - headLength;
2882
+ return [
2883
+ `M 0 ${-halfShaft}`,
2884
+ `L ${shaftLength} ${-halfShaft}`,
2885
+ `L ${shaftLength} ${-halfHead}`,
2886
+ `L ${length} 0`,
2887
+ `L ${shaftLength} ${halfHead}`,
2888
+ `L ${shaftLength} ${halfShaft}`,
2889
+ `L 0 ${halfShaft}`,
2890
+ "Z"
2891
+ ].join(" ");
2892
+ }
2893
+ function generateHeartPath(params) {
2894
+ const { size } = params;
2895
+ const s = size / 2;
2896
+ return [
2897
+ `M 0 ${s * 0.3}`,
2898
+ `C 0 ${-s * 0.3} ${-s} ${-s * 0.3} ${-s} ${s * 0.3}`,
2899
+ `C ${-s} ${s * 0.7} 0 ${s} 0 ${s * 1.2}`,
2900
+ `C 0 ${s} ${s} ${s * 0.7} ${s} ${s * 0.3}`,
2901
+ `C ${s} ${-s * 0.3} 0 ${-s * 0.3} 0 ${s * 0.3}`,
2902
+ "Z"
2903
+ ].join(" ");
2904
+ }
2905
+ function generateCrossPath(params) {
2906
+ const { width, height, thickness } = params;
2907
+ const halfThickness = thickness / 2;
2908
+ const halfWidth = width / 2;
2909
+ const halfHeight = height / 2;
2910
+ return [
2911
+ `M ${-halfThickness} ${-halfHeight}`,
2912
+ `L ${halfThickness} ${-halfHeight}`,
2913
+ `L ${halfThickness} ${-halfThickness}`,
2914
+ `L ${halfWidth} ${-halfThickness}`,
2915
+ `L ${halfWidth} ${halfThickness}`,
2916
+ `L ${halfThickness} ${halfThickness}`,
2917
+ `L ${halfThickness} ${halfHeight}`,
2918
+ `L ${-halfThickness} ${halfHeight}`,
2919
+ `L ${-halfThickness} ${halfThickness}`,
2920
+ `L ${-halfWidth} ${halfThickness}`,
2921
+ `L ${-halfWidth} ${-halfThickness}`,
2922
+ `L ${-halfThickness} ${-halfThickness}`,
2923
+ "Z"
2924
+ ].join(" ");
2925
+ }
2926
+ function generateRingPath(params) {
2927
+ const { outerRadius, innerRadius } = params;
2928
+ const outerPath = generateCirclePath({ radius: outerRadius });
2929
+ const innerPath = generateCirclePath({ radius: innerRadius });
2930
+ return `${outerPath} ${reverseWindingOrder(innerPath)}`;
2931
+ }
2932
+ function pointsToPath(points, closed = true) {
2933
+ if (points.length === 0) return "";
2934
+ const commands = [`M ${points[0].x} ${points[0].y}`];
2935
+ for (let i = 1; i < points.length; i++) {
2936
+ commands.push(`L ${points[i].x} ${points[i].y}`);
2937
+ }
2938
+ if (closed) {
2939
+ commands.push("Z");
2940
+ }
2941
+ return commands.join(" ");
2942
+ }
2943
+ function reverseWindingOrder(path) {
2944
+ const commands = parsePathCommands(path);
2945
+ const reversed = [];
2946
+ const points = [];
2947
+ let currentX = 0;
2948
+ let currentY = 0;
2949
+ for (const cmd of commands) {
2950
+ if (cmd.type === "M" || cmd.type === "L") {
2951
+ points.push({ x: cmd.x, y: cmd.y });
2952
+ currentX = cmd.x;
2953
+ currentY = cmd.y;
2954
+ } else if (cmd.type === "C") {
2955
+ points.push({ x: cmd.x, y: cmd.y });
2956
+ currentX = cmd.x;
2957
+ currentY = cmd.y;
2958
+ }
2959
+ }
2960
+ if (points.length === 0) return path;
2961
+ points.reverse();
2962
+ reversed.push(`M ${points[0].x} ${points[0].y}`);
2963
+ for (let i = 1; i < points.length; i++) {
2964
+ reversed.push(`L ${points[i].x} ${points[i].y}`);
2965
+ }
2966
+ reversed.push("Z");
2967
+ return reversed.join(" ");
2968
+ }
2969
+ function parsePathCommands(path) {
2970
+ const commands = [];
2971
+ const regex = /([MLCQZ])\s*([^MLCQZ]*)/gi;
2972
+ let match;
2973
+ while ((match = regex.exec(path)) !== null) {
2974
+ const type = match[1].toUpperCase();
2975
+ const args = match[2].trim().split(/[\s,]+/).filter((s) => s.length > 0).map(parseFloat);
2976
+ if (type === "M" || type === "L") {
2977
+ commands.push({ type, x: args[0], y: args[1] });
2978
+ } else if (type === "C") {
2979
+ commands.push({
2980
+ type,
2981
+ x1: args[0],
2982
+ y1: args[1],
2983
+ x2: args[2],
2984
+ y2: args[3],
2985
+ x: args[4],
2986
+ y: args[5]
2987
+ });
2988
+ } else if (type === "Q") {
2989
+ commands.push({
2990
+ type,
2991
+ x1: args[0],
2992
+ y1: args[1],
2993
+ x: args[2],
2994
+ y: args[3]
2995
+ });
2996
+ } else if (type === "Z") {
2997
+ commands.push({ type });
2998
+ }
2999
+ }
3000
+ return commands;
3001
+ }
3002
+ function computePathBounds3(path) {
3003
+ const commands = parsePathCommands(path);
3004
+ let minX = Infinity;
3005
+ let minY = Infinity;
3006
+ let maxX = -Infinity;
3007
+ let maxY = -Infinity;
3008
+ const updateBounds = (x, y) => {
3009
+ minX = Math.min(minX, x);
3010
+ minY = Math.min(minY, y);
3011
+ maxX = Math.max(maxX, x);
3012
+ maxY = Math.max(maxY, y);
3013
+ };
3014
+ for (const cmd of commands) {
3015
+ if (cmd.x !== void 0 && cmd.y !== void 0) {
3016
+ updateBounds(cmd.x, cmd.y);
3017
+ }
3018
+ if (cmd.x1 !== void 0 && cmd.y1 !== void 0) {
3019
+ updateBounds(cmd.x1, cmd.y1);
3020
+ }
3021
+ if (cmd.x2 !== void 0 && cmd.y2 !== void 0) {
3022
+ updateBounds(cmd.x2, cmd.y2);
3023
+ }
3024
+ }
3025
+ if (minX === Infinity) {
3026
+ return { x: 0, y: 0, w: 0, h: 0 };
3027
+ }
3028
+ return {
3029
+ x: minX,
3030
+ y: minY,
3031
+ w: maxX - minX,
3032
+ h: maxY - minY
3033
+ };
3034
+ }
3035
+ function translatePath(path, dx, dy) {
3036
+ return path.replace(
3037
+ /(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
3038
+ (match, x, y) => {
3039
+ const newX = parseFloat(x) + dx;
3040
+ const newY = parseFloat(y) + dy;
3041
+ return `${newX} ${newY}`;
3042
+ }
3043
+ );
3044
+ }
3045
+ function scalePath(path, sx, sy = sx) {
3046
+ return path.replace(
3047
+ /(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
3048
+ (match, x, y) => {
3049
+ const newX = parseFloat(x) * sx;
3050
+ const newY = parseFloat(y) * sy;
3051
+ return `${newX} ${newY}`;
3052
+ }
3053
+ );
3054
+ }
3055
+ function rotatePath(path, angleDegrees) {
3056
+ const angleRadians = angleDegrees * DEG_TO_RAD;
3057
+ const cos = Math.cos(angleRadians);
3058
+ const sin = Math.sin(angleRadians);
3059
+ return path.replace(
3060
+ /(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
3061
+ (match, x, y) => {
3062
+ const px = parseFloat(x);
3063
+ const py = parseFloat(y);
3064
+ const newX = px * cos - py * sin;
3065
+ const newY = px * sin + py * cos;
3066
+ return `${newX} ${newY}`;
3067
+ }
3068
+ );
3069
+ }
3070
+ function centerPath(path) {
3071
+ const bounds = computePathBounds3(path);
3072
+ const cx = bounds.x + bounds.w / 2;
3073
+ const cy = bounds.y + bounds.h / 2;
3074
+ return translatePath(path, -cx, -cy);
3075
+ }
3076
+ function normalizePathToSize(path, targetWidth, targetHeight) {
3077
+ const bounds = computePathBounds3(path);
3078
+ if (bounds.w === 0 || bounds.h === 0) return path;
3079
+ const scaleX = targetWidth / bounds.w;
3080
+ const scaleY = targetHeight / bounds.h;
3081
+ const scale = Math.min(scaleX, scaleY);
3082
+ let normalized = translatePath(path, -bounds.x, -bounds.y);
3083
+ normalized = scalePath(normalized, scale);
3084
+ return normalized;
3085
+ }
3086
+
3087
+ // src/core/svg-path-utils.ts
3088
+ var TAU = Math.PI * 2;
3089
+ var PATH_COMMAND_REGEX = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
3090
+ var NUMBER_REGEX = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g;
3091
+ function parseSvgPath(pathData) {
3092
+ const commands = [];
3093
+ let currentX = 0;
3094
+ let currentY = 0;
3095
+ let startX = 0;
3096
+ let startY = 0;
3097
+ let lastCommand = null;
3098
+ let lastControlX = 0;
3099
+ let lastControlY = 0;
3100
+ const matches = pathData.matchAll(PATH_COMMAND_REGEX);
3101
+ for (const match of matches) {
3102
+ const commandType = match[1];
3103
+ const argsString = match[2];
3104
+ const args = extractNumbers(argsString);
3105
+ const isRelative = commandType === commandType.toLowerCase();
3106
+ switch (commandType.toUpperCase()) {
3107
+ case "M": {
3108
+ const coords = parseCoordinatePairs(args, isRelative, currentX, currentY);
3109
+ for (let i = 0; i < coords.length; i++) {
3110
+ const { x, y } = coords[i];
3111
+ if (i === 0) {
3112
+ commands.push({ type: "M", x, y });
3113
+ startX = x;
3114
+ startY = y;
3115
+ } else {
3116
+ commands.push({ type: "L", x, y });
3117
+ }
3118
+ currentX = x;
3119
+ currentY = y;
3120
+ }
3121
+ break;
3122
+ }
3123
+ case "L": {
3124
+ const coords = parseCoordinatePairs(args, isRelative, currentX, currentY);
3125
+ for (const { x, y } of coords) {
3126
+ commands.push({ type: "L", x, y });
3127
+ currentX = x;
3128
+ currentY = y;
3129
+ }
3130
+ break;
3131
+ }
3132
+ case "H": {
3133
+ for (const arg of args) {
3134
+ const x = isRelative ? currentX + arg : arg;
3135
+ commands.push({ type: "L", x, y: currentY });
3136
+ currentX = x;
3137
+ }
3138
+ break;
3139
+ }
3140
+ case "V": {
3141
+ for (const arg of args) {
3142
+ const y = isRelative ? currentY + arg : arg;
3143
+ commands.push({ type: "L", x: currentX, y });
3144
+ currentY = y;
3145
+ }
3146
+ break;
3147
+ }
3148
+ case "C": {
3149
+ for (let i = 0; i + 5 < args.length + 1; i += 6) {
3150
+ let x1 = args[i];
3151
+ let y1 = args[i + 1];
3152
+ let x2 = args[i + 2];
3153
+ let y2 = args[i + 3];
3154
+ let x = args[i + 4];
3155
+ let y = args[i + 5];
3156
+ if (isRelative) {
3157
+ x1 += currentX;
3158
+ y1 += currentY;
3159
+ x2 += currentX;
3160
+ y2 += currentY;
3161
+ x += currentX;
3162
+ y += currentY;
3163
+ }
3164
+ commands.push({ type: "C", x1, y1, x2, y2, x, y });
3165
+ lastControlX = x2;
3166
+ lastControlY = y2;
3167
+ currentX = x;
3168
+ currentY = y;
3169
+ }
3170
+ break;
3171
+ }
3172
+ case "S": {
3173
+ for (let i = 0; i + 3 < args.length + 1; i += 4) {
3174
+ let x1;
3175
+ let y1;
3176
+ if (lastCommand === "C" || lastCommand === "S") {
3177
+ x1 = 2 * currentX - lastControlX;
3178
+ y1 = 2 * currentY - lastControlY;
3179
+ } else {
3180
+ x1 = currentX;
3181
+ y1 = currentY;
3182
+ }
3183
+ let x2 = args[i];
3184
+ let y2 = args[i + 1];
3185
+ let x = args[i + 2];
3186
+ let y = args[i + 3];
3187
+ if (isRelative) {
3188
+ x2 += currentX;
3189
+ y2 += currentY;
3190
+ x += currentX;
3191
+ y += currentY;
3192
+ }
3193
+ commands.push({ type: "C", x1, y1, x2, y2, x, y });
3194
+ lastControlX = x2;
3195
+ lastControlY = y2;
3196
+ currentX = x;
3197
+ currentY = y;
3198
+ lastCommand = "S";
3199
+ }
3200
+ break;
3201
+ }
3202
+ case "Q": {
3203
+ for (let i = 0; i + 3 < args.length + 1; i += 4) {
3204
+ let x1 = args[i];
3205
+ let y1 = args[i + 1];
3206
+ let x = args[i + 2];
3207
+ let y = args[i + 3];
3208
+ if (isRelative) {
3209
+ x1 += currentX;
3210
+ y1 += currentY;
3211
+ x += currentX;
3212
+ y += currentY;
3213
+ }
3214
+ commands.push({ type: "Q", x1, y1, x, y });
3215
+ lastControlX = x1;
3216
+ lastControlY = y1;
3217
+ currentX = x;
3218
+ currentY = y;
3219
+ }
3220
+ break;
3221
+ }
3222
+ case "T": {
3223
+ for (let i = 0; i + 1 < args.length + 1; i += 2) {
3224
+ let x1;
3225
+ let y1;
3226
+ if (lastCommand === "Q" || lastCommand === "T") {
3227
+ x1 = 2 * currentX - lastControlX;
3228
+ y1 = 2 * currentY - lastControlY;
3229
+ } else {
3230
+ x1 = currentX;
3231
+ y1 = currentY;
3232
+ }
3233
+ let x = args[i];
3234
+ let y = args[i + 1];
3235
+ if (isRelative) {
3236
+ x += currentX;
3237
+ y += currentY;
3238
+ }
3239
+ commands.push({ type: "Q", x1, y1, x, y });
3240
+ lastControlX = x1;
3241
+ lastControlY = y1;
3242
+ currentX = x;
3243
+ currentY = y;
3244
+ lastCommand = "T";
3245
+ }
3246
+ break;
3247
+ }
3248
+ case "A": {
3249
+ for (let i = 0; i + 6 < args.length + 1; i += 7) {
3250
+ const rx = Math.abs(args[i]);
3251
+ const ry = Math.abs(args[i + 1]);
3252
+ const xAxisRotation = args[i + 2];
3253
+ const largeArcFlag = args[i + 3] !== 0;
3254
+ const sweepFlag = args[i + 4] !== 0;
3255
+ let x = args[i + 5];
3256
+ let y = args[i + 6];
3257
+ if (isRelative) {
3258
+ x += currentX;
3259
+ y += currentY;
3260
+ }
3261
+ commands.push({
3262
+ type: "A",
3263
+ rx,
3264
+ ry,
3265
+ xAxisRotation,
3266
+ largeArcFlag,
3267
+ sweepFlag,
3268
+ x,
3269
+ y
3270
+ });
3271
+ currentX = x;
3272
+ currentY = y;
3273
+ }
3274
+ break;
3275
+ }
3276
+ case "Z": {
3277
+ commands.push({ type: "Z" });
3278
+ currentX = startX;
3279
+ currentY = startY;
3280
+ break;
3281
+ }
3282
+ }
3283
+ lastCommand = commandType.toUpperCase();
3284
+ }
3285
+ return commands;
3286
+ }
3287
+ function normalizePath(commands) {
3288
+ const normalized = [];
3289
+ let currentX = 0;
3290
+ let currentY = 0;
3291
+ for (const cmd of commands) {
3292
+ switch (cmd.type) {
3293
+ case "M":
3294
+ case "L":
3295
+ case "C":
3296
+ case "Q":
3297
+ case "Z":
3298
+ normalized.push(cmd);
3299
+ if (cmd.type !== "Z") {
3300
+ currentX = cmd.x;
3301
+ currentY = cmd.y;
3302
+ }
3303
+ break;
3304
+ case "A": {
3305
+ const cubicCurves = arcToCubicBeziers(
3306
+ currentX,
3307
+ currentY,
3308
+ cmd.rx,
3309
+ cmd.ry,
3310
+ cmd.xAxisRotation,
3311
+ cmd.largeArcFlag,
3312
+ cmd.sweepFlag,
3313
+ cmd.x,
3314
+ cmd.y
3315
+ );
3316
+ normalized.push(...cubicCurves);
3317
+ currentX = cmd.x;
3318
+ currentY = cmd.y;
3319
+ break;
3320
+ }
3321
+ }
3322
+ }
3323
+ return normalized;
3324
+ }
3325
+ function commandsToPathString(commands) {
3326
+ const parts = [];
3327
+ for (const cmd of commands) {
3328
+ switch (cmd.type) {
3329
+ case "M":
3330
+ parts.push(`M ${cmd.x} ${cmd.y}`);
3331
+ break;
3332
+ case "L":
3333
+ parts.push(`L ${cmd.x} ${cmd.y}`);
3334
+ break;
3335
+ case "C":
3336
+ parts.push(`C ${cmd.x1} ${cmd.y1} ${cmd.x2} ${cmd.y2} ${cmd.x} ${cmd.y}`);
3337
+ break;
3338
+ case "Q":
3339
+ parts.push(`Q ${cmd.x1} ${cmd.y1} ${cmd.x} ${cmd.y}`);
3340
+ break;
3341
+ case "Z":
3342
+ parts.push("Z");
3343
+ break;
3344
+ }
3345
+ }
3346
+ return parts.join(" ");
3347
+ }
3348
+ function normalizePathString(pathData) {
3349
+ const parsed = parseSvgPath(pathData);
3350
+ const normalized = normalizePath(parsed);
3351
+ return commandsToPathString(normalized);
3352
+ }
3353
+ function extractNumbers(str) {
3354
+ const matches = str.match(NUMBER_REGEX);
3355
+ return matches ? matches.map(parseFloat) : [];
3356
+ }
3357
+ function parseCoordinatePairs(args, isRelative, currentX, currentY) {
3358
+ const coords = [];
3359
+ for (let i = 0; i + 1 < args.length + 1; i += 2) {
3360
+ let x = args[i];
3361
+ let y = args[i + 1];
3362
+ if (isRelative && coords.length === 0) {
3363
+ x += currentX;
3364
+ y += currentY;
3365
+ } else if (isRelative) {
3366
+ x += coords[coords.length - 1].x;
3367
+ y += coords[coords.length - 1].y;
3368
+ }
3369
+ coords.push({ x, y });
3370
+ }
3371
+ return coords;
3372
+ }
3373
+ function arcToCubicBeziers(x1, y1, rx, ry, phi, largeArc, sweep, x2, y2) {
3374
+ if (rx === 0 || ry === 0) {
3375
+ return [{ type: "L", x: x2, y: y2 }];
3376
+ }
3377
+ if (x1 === x2 && y1 === y2) {
3378
+ return [];
3379
+ }
3380
+ const sinPhi = Math.sin(phi * Math.PI / 180);
3381
+ const cosPhi = Math.cos(phi * Math.PI / 180);
3382
+ const x1p = cosPhi * (x1 - x2) / 2 + sinPhi * (y1 - y2) / 2;
3383
+ const y1p = -sinPhi * (x1 - x2) / 2 + cosPhi * (y1 - y2) / 2;
3384
+ const x1pSq = x1p * x1p;
3385
+ const y1pSq = y1p * y1p;
3386
+ let rxSq = rx * rx;
3387
+ let rySq = ry * ry;
3388
+ const lambda = x1pSq / rxSq + y1pSq / rySq;
3389
+ if (lambda > 1) {
3390
+ const sqrtLambda = Math.sqrt(lambda);
3391
+ rx *= sqrtLambda;
3392
+ ry *= sqrtLambda;
3393
+ rxSq = rx * rx;
3394
+ rySq = ry * ry;
3395
+ }
3396
+ let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
3397
+ sq = sq < 0 ? 0 : sq;
3398
+ const coef = (largeArc === sweep ? -1 : 1) * Math.sqrt(sq);
3399
+ const cxp = coef * rx * y1p / ry;
3400
+ const cyp = -coef * ry * x1p / rx;
3401
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
3402
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
3403
+ const theta1 = vectorAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
3404
+ let dTheta = vectorAngle(
3405
+ (x1p - cxp) / rx,
3406
+ (y1p - cyp) / ry,
3407
+ (-x1p - cxp) / rx,
3408
+ (-y1p - cyp) / ry
3409
+ );
3410
+ if (!sweep && dTheta > 0) {
3411
+ dTheta -= TAU;
3412
+ } else if (sweep && dTheta < 0) {
3413
+ dTheta += TAU;
3414
+ }
3415
+ const segments = Math.max(Math.ceil(Math.abs(dTheta) / (Math.PI / 2)), 1);
3416
+ const segmentAngle = dTheta / segments;
3417
+ const curves = [];
3418
+ let currentTheta = theta1;
3419
+ for (let i = 0; i < segments; i++) {
3420
+ const nextTheta = currentTheta + segmentAngle;
3421
+ const curve = arcSegmentToCubic(cx, cy, rx, ry, phi, currentTheta, nextTheta);
3422
+ curves.push(curve);
3423
+ currentTheta = nextTheta;
3424
+ }
3425
+ return curves;
3426
+ }
3427
+ function vectorAngle(ux, uy, vx, vy) {
3428
+ const sign = ux * vy - uy * vx < 0 ? -1 : 1;
3429
+ const dot = ux * vx + uy * vy;
3430
+ const uLen = Math.sqrt(ux * ux + uy * uy);
3431
+ const vLen = Math.sqrt(vx * vx + vy * vy);
3432
+ let cos = dot / (uLen * vLen);
3433
+ cos = Math.max(-1, Math.min(1, cos));
3434
+ return sign * Math.acos(cos);
3435
+ }
3436
+ function arcSegmentToCubic(cx, cy, rx, ry, phi, theta1, theta2) {
3437
+ const sinPhi = Math.sin(phi * Math.PI / 180);
3438
+ const cosPhi = Math.cos(phi * Math.PI / 180);
3439
+ const dTheta = theta2 - theta1;
3440
+ const t = 4 / 3 * Math.tan(dTheta / 4);
3441
+ const x1 = Math.cos(theta1);
3442
+ const y1 = Math.sin(theta1);
3443
+ const x2 = Math.cos(theta2);
3444
+ const y2 = Math.sin(theta2);
3445
+ const dx1 = -t * y1;
3446
+ const dy1 = t * x1;
3447
+ const dx2 = t * y2;
3448
+ const dy2 = -t * x2;
3449
+ const transform = (px, py) => {
3450
+ const x = rx * px;
3451
+ const y = ry * py;
3452
+ return {
3453
+ x: cosPhi * x - sinPhi * y + cx,
3454
+ y: sinPhi * x + cosPhi * y + cy
3455
+ };
3456
+ };
3457
+ const p1 = transform(x1, y1);
3458
+ const cp1 = transform(x1 + dx1, y1 + dy1);
3459
+ const cp2 = transform(x2 + dx2, y2 + dy2);
3460
+ const p2 = transform(x2, y2);
3461
+ return {
3462
+ type: "C",
3463
+ x1: cp1.x,
3464
+ y1: cp1.y,
3465
+ x2: cp2.x,
3466
+ y2: cp2.y,
3467
+ x: p2.x,
3468
+ y: p2.y
3469
+ };
3470
+ }
3471
+ function quadraticToCubic(x0, y0, qx, qy, x, y) {
3472
+ return {
3473
+ type: "C",
3474
+ x1: x0 + 2 / 3 * (qx - x0),
3475
+ y1: y0 + 2 / 3 * (qy - y0),
3476
+ x2: x + 2 / 3 * (qx - x),
3477
+ y2: y + 2 / 3 * (qy - y),
3478
+ x,
3479
+ y
3480
+ };
3481
+ }
3482
+
3483
+ // src/core/svg-import.ts
3484
+ var SVG_ATTRS_REGEX = /<svg([^>]*)>/i;
3485
+ var PATH_TAG_REGEX = /<path([^/>]*)\/?>/gi;
3486
+ var RECT_TAG_REGEX = /<rect([^/>]*)\/?>/gi;
3487
+ var CIRCLE_TAG_REGEX = /<circle([^/>]*)\/?>/gi;
3488
+ var ELLIPSE_TAG_REGEX = /<ellipse([^/>]*)\/?>/gi;
3489
+ var LINE_TAG_REGEX = /<line([^/>]*)\/?>/gi;
3490
+ var POLYLINE_TAG_REGEX = /<polyline([^/>]*)\/?>/gi;
3491
+ var POLYGON_TAG_REGEX = /<polygon([^/>]*)\/?>/gi;
3492
+ function extractAttribute(attrs, name) {
3493
+ const regex = new RegExp(`${name}\\s*=\\s*["']([^"']*)["']`, "i");
3494
+ const match = attrs.match(regex);
3495
+ return match ? match[1] : void 0;
3496
+ }
3497
+ function parseColor(color) {
3498
+ if (!color || color === "none" || color === "transparent") {
3499
+ return void 0;
3500
+ }
3501
+ color = color.trim();
3502
+ if (color.startsWith("#")) {
3503
+ if (color.length === 4) {
3504
+ const r = color[1];
3505
+ const g = color[2];
3506
+ const b = color[3];
3507
+ return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
3508
+ }
3509
+ return color.toUpperCase();
3510
+ }
3511
+ const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
3512
+ if (rgbMatch) {
3513
+ const r = parseInt(rgbMatch[1], 10);
3514
+ const g = parseInt(rgbMatch[2], 10);
3515
+ const b = parseInt(rgbMatch[3], 10);
3516
+ return rgbToHex(r, g, b);
3517
+ }
3518
+ const rgbaMatch = color.match(/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)/i);
3519
+ if (rgbaMatch) {
3520
+ const r = parseInt(rgbaMatch[1], 10);
3521
+ const g = parseInt(rgbaMatch[2], 10);
3522
+ const b = parseInt(rgbaMatch[3], 10);
3523
+ return rgbToHex(r, g, b);
3524
+ }
3525
+ const namedColors = {
3526
+ black: "#000000",
3527
+ white: "#FFFFFF",
3528
+ red: "#FF0000",
3529
+ green: "#008000",
3530
+ blue: "#0000FF",
3531
+ yellow: "#FFFF00",
3532
+ cyan: "#00FFFF",
3533
+ magenta: "#FF00FF",
3534
+ gray: "#808080",
3535
+ grey: "#808080",
3536
+ orange: "#FFA500",
3537
+ purple: "#800080",
3538
+ pink: "#FFC0CB",
3539
+ brown: "#A52A2A",
3540
+ navy: "#000080",
3541
+ teal: "#008080",
3542
+ olive: "#808000",
3543
+ maroon: "#800000",
3544
+ aqua: "#00FFFF",
3545
+ lime: "#00FF00",
3546
+ silver: "#C0C0C0",
3547
+ fuchsia: "#FF00FF"
3548
+ };
3549
+ const lowerColor = color.toLowerCase();
3550
+ if (namedColors[lowerColor]) {
3551
+ return namedColors[lowerColor];
3552
+ }
3553
+ return void 0;
3554
+ }
3555
+ function rgbToHex(r, g, b) {
3556
+ const toHex = (n) => {
3557
+ const hex = Math.max(0, Math.min(255, n)).toString(16);
3558
+ return hex.length === 1 ? "0" + hex : hex;
3559
+ };
3560
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
3561
+ }
3562
+ function parseNumber(value) {
3563
+ if (!value) return void 0;
3564
+ const num = parseFloat(value);
3565
+ return isNaN(num) ? void 0 : num;
3566
+ }
3567
+ function parseViewBox(viewBox) {
3568
+ if (!viewBox) return void 0;
3569
+ const parts = viewBox.trim().split(/[\s,]+/).map(parseFloat);
3570
+ if (parts.length === 4 && parts.every((p) => !isNaN(p))) {
3571
+ return { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
3572
+ }
3573
+ return void 0;
3574
+ }
3575
+ function rectToPath(attrs) {
3576
+ const x = parseNumber(extractAttribute(attrs, "x")) || 0;
3577
+ const y = parseNumber(extractAttribute(attrs, "y")) || 0;
3578
+ const width = parseNumber(extractAttribute(attrs, "width")) || 0;
3579
+ const height = parseNumber(extractAttribute(attrs, "height")) || 0;
3580
+ const rx = parseNumber(extractAttribute(attrs, "rx")) || 0;
3581
+ const ry = parseNumber(extractAttribute(attrs, "ry")) || rx;
3582
+ if (rx === 0 && ry === 0) {
3583
+ return `M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z`;
3584
+ }
3585
+ const r = Math.min(rx, ry, width / 2, height / 2);
3586
+ return [
3587
+ `M ${x + r} ${y}`,
3588
+ `L ${x + width - r} ${y}`,
3589
+ `Q ${x + width} ${y} ${x + width} ${y + r}`,
3590
+ `L ${x + width} ${y + height - r}`,
3591
+ `Q ${x + width} ${y + height} ${x + width - r} ${y + height}`,
3592
+ `L ${x + r} ${y + height}`,
3593
+ `Q ${x} ${y + height} ${x} ${y + height - r}`,
3594
+ `L ${x} ${y + r}`,
3595
+ `Q ${x} ${y} ${x + r} ${y}`,
3596
+ "Z"
3597
+ ].join(" ");
3598
+ }
3599
+ function circleToPath(attrs) {
3600
+ const cx = parseNumber(extractAttribute(attrs, "cx")) || 0;
3601
+ const cy = parseNumber(extractAttribute(attrs, "cy")) || 0;
3602
+ const r = parseNumber(extractAttribute(attrs, "r")) || 0;
3603
+ if (r === 0) return "";
3604
+ const KAPPA2 = 0.5522847498307936;
3605
+ const k = r * KAPPA2;
3606
+ return [
3607
+ `M ${cx + r} ${cy}`,
3608
+ `C ${cx + r} ${cy + k} ${cx + k} ${cy + r} ${cx} ${cy + r}`,
3609
+ `C ${cx - k} ${cy + r} ${cx - r} ${cy + k} ${cx - r} ${cy}`,
3610
+ `C ${cx - r} ${cy - k} ${cx - k} ${cy - r} ${cx} ${cy - r}`,
3611
+ `C ${cx + k} ${cy - r} ${cx + r} ${cy - k} ${cx + r} ${cy}`,
3612
+ "Z"
3613
+ ].join(" ");
3614
+ }
3615
+ function ellipseToPath(attrs) {
3616
+ const cx = parseNumber(extractAttribute(attrs, "cx")) || 0;
3617
+ const cy = parseNumber(extractAttribute(attrs, "cy")) || 0;
3618
+ const rx = parseNumber(extractAttribute(attrs, "rx")) || 0;
3619
+ const ry = parseNumber(extractAttribute(attrs, "ry")) || 0;
3620
+ if (rx === 0 || ry === 0) return "";
3621
+ const KAPPA2 = 0.5522847498307936;
3622
+ const kx = rx * KAPPA2;
3623
+ const ky = ry * KAPPA2;
3624
+ return [
3625
+ `M ${cx + rx} ${cy}`,
3626
+ `C ${cx + rx} ${cy + ky} ${cx + kx} ${cy + ry} ${cx} ${cy + ry}`,
3627
+ `C ${cx - kx} ${cy + ry} ${cx - rx} ${cy + ky} ${cx - rx} ${cy}`,
3628
+ `C ${cx - rx} ${cy - ky} ${cx - kx} ${cy - ry} ${cx} ${cy - ry}`,
3629
+ `C ${cx + kx} ${cy - ry} ${cx + rx} ${cy - ky} ${cx + rx} ${cy}`,
3630
+ "Z"
3631
+ ].join(" ");
3632
+ }
3633
+ function lineToPath(attrs) {
3634
+ const x1 = parseNumber(extractAttribute(attrs, "x1")) || 0;
3635
+ const y1 = parseNumber(extractAttribute(attrs, "y1")) || 0;
3636
+ const x2 = parseNumber(extractAttribute(attrs, "x2")) || 0;
3637
+ const y2 = parseNumber(extractAttribute(attrs, "y2")) || 0;
3638
+ return `M ${x1} ${y1} L ${x2} ${y2}`;
3639
+ }
3640
+ function polylineToPath(attrs) {
3641
+ const points = extractAttribute(attrs, "points");
3642
+ if (!points) return "";
3643
+ const coords = points.trim().split(/[\s,]+/).map(parseFloat);
3644
+ if (coords.length < 2) return "";
3645
+ const pathParts = [];
3646
+ for (let i = 0; i < coords.length; i += 2) {
3647
+ if (i + 1 < coords.length) {
3648
+ if (i === 0) {
3649
+ pathParts.push(`M ${coords[i]} ${coords[i + 1]}`);
3650
+ } else {
3651
+ pathParts.push(`L ${coords[i]} ${coords[i + 1]}`);
3652
+ }
3653
+ }
3654
+ }
3655
+ return pathParts.join(" ");
3656
+ }
3657
+ function polygonToPath(attrs) {
3658
+ const polyPath = polylineToPath(attrs);
3659
+ if (!polyPath) return "";
3660
+ return polyPath + " Z";
3661
+ }
3662
+ function extractPathAttributes(attrs) {
3663
+ const d = extractAttribute(attrs, "d") || "";
3664
+ const fill = extractAttribute(attrs, "fill");
3665
+ const fillOpacity = parseNumber(extractAttribute(attrs, "fill-opacity"));
3666
+ const stroke = extractAttribute(attrs, "stroke");
3667
+ const strokeWidth = parseNumber(extractAttribute(attrs, "stroke-width"));
3668
+ const strokeOpacity = parseNumber(extractAttribute(attrs, "stroke-opacity"));
3669
+ const opacity = parseNumber(extractAttribute(attrs, "opacity"));
3670
+ const transform = extractAttribute(attrs, "transform");
3671
+ const style = extractAttribute(attrs, "style");
3672
+ let styleFill = fill;
3673
+ let styleStroke = stroke;
3674
+ let styleStrokeWidth = strokeWidth;
3675
+ let styleOpacity = opacity;
3676
+ if (style) {
3677
+ const fillMatch = style.match(/fill\s*:\s*([^;]+)/i);
3678
+ if (fillMatch) styleFill = fillMatch[1].trim();
3679
+ const strokeMatch = style.match(/stroke\s*:\s*([^;]+)/i);
3680
+ if (strokeMatch) styleStroke = strokeMatch[1].trim();
3681
+ const strokeWidthMatch = style.match(/stroke-width\s*:\s*([^;]+)/i);
3682
+ if (strokeWidthMatch) styleStrokeWidth = parseNumber(strokeWidthMatch[1].trim());
3683
+ const opacityMatch = style.match(/opacity\s*:\s*([^;]+)/i);
3684
+ if (opacityMatch) styleOpacity = parseNumber(opacityMatch[1].trim());
3685
+ }
3686
+ return {
3687
+ d,
3688
+ fill: styleFill,
3689
+ fillOpacity,
3690
+ stroke: styleStroke,
3691
+ strokeWidth: styleStrokeWidth,
3692
+ strokeOpacity,
3693
+ opacity: styleOpacity,
3694
+ transform
3695
+ };
3696
+ }
3697
+ function extractShapeAsPath(tagAttrs, convertFn) {
3698
+ const d = convertFn(tagAttrs);
3699
+ if (!d) return null;
3700
+ const pathAttrs = extractPathAttributes(tagAttrs);
3701
+ pathAttrs.d = d;
3702
+ return pathAttrs;
3703
+ }
3704
+ function parseSvgMarkup(svgString) {
3705
+ const svgMatch = svgString.match(SVG_ATTRS_REGEX);
3706
+ const svgAttrs = svgMatch ? svgMatch[1] : "";
3707
+ const widthAttr = extractAttribute(svgAttrs, "width");
3708
+ const heightAttr = extractAttribute(svgAttrs, "height");
3709
+ const viewBoxAttr = extractAttribute(svgAttrs, "viewBox");
3710
+ let width = parseNumber(widthAttr?.replace(/px$/, "")) || 0;
3711
+ let height = parseNumber(heightAttr?.replace(/px$/, "")) || 0;
3712
+ const viewBox = parseViewBox(viewBoxAttr);
3713
+ if (viewBox && (!width || !height)) {
3714
+ width = width || viewBox.width;
3715
+ height = height || viewBox.height;
3716
+ }
3717
+ const paths = [];
3718
+ let match;
3719
+ PATH_TAG_REGEX.lastIndex = 0;
3720
+ while ((match = PATH_TAG_REGEX.exec(svgString)) !== null) {
3721
+ const pathAttrs = extractPathAttributes(match[1]);
3722
+ if (pathAttrs.d) {
3723
+ paths.push(pathAttrs);
3724
+ }
3725
+ }
3726
+ RECT_TAG_REGEX.lastIndex = 0;
3727
+ while ((match = RECT_TAG_REGEX.exec(svgString)) !== null) {
3728
+ const path = extractShapeAsPath(match[1], rectToPath);
3729
+ if (path) paths.push(path);
3730
+ }
3731
+ CIRCLE_TAG_REGEX.lastIndex = 0;
3732
+ while ((match = CIRCLE_TAG_REGEX.exec(svgString)) !== null) {
3733
+ const path = extractShapeAsPath(match[1], circleToPath);
3734
+ if (path) paths.push(path);
3735
+ }
3736
+ ELLIPSE_TAG_REGEX.lastIndex = 0;
3737
+ while ((match = ELLIPSE_TAG_REGEX.exec(svgString)) !== null) {
3738
+ const path = extractShapeAsPath(match[1], ellipseToPath);
3739
+ if (path) paths.push(path);
3740
+ }
3741
+ LINE_TAG_REGEX.lastIndex = 0;
3742
+ while ((match = LINE_TAG_REGEX.exec(svgString)) !== null) {
3743
+ const path = extractShapeAsPath(match[1], lineToPath);
3744
+ if (path) paths.push(path);
3745
+ }
3746
+ POLYLINE_TAG_REGEX.lastIndex = 0;
3747
+ while ((match = POLYLINE_TAG_REGEX.exec(svgString)) !== null) {
3748
+ const path = extractShapeAsPath(match[1], polylineToPath);
3749
+ if (path) paths.push(path);
3750
+ }
3751
+ POLYGON_TAG_REGEX.lastIndex = 0;
3752
+ while ((match = POLYGON_TAG_REGEX.exec(svgString)) !== null) {
3753
+ const path = extractShapeAsPath(match[1], polygonToPath);
3754
+ if (path) paths.push(path);
3755
+ }
3756
+ return {
3757
+ width,
3758
+ height,
3759
+ viewBox,
3760
+ paths
3761
+ };
3762
+ }
3763
+ function svgToAsset(svgString) {
3764
+ const parsed = parseSvgMarkup(svgString);
3765
+ if (parsed.paths.length === 0) {
3766
+ return {
3767
+ type: "svg",
3768
+ shape: {
3769
+ type: "path",
3770
+ d: ""
3771
+ },
3772
+ width: parsed.width || void 0,
3773
+ height: parsed.height || void 0
3774
+ };
3775
+ }
3776
+ if (parsed.paths.length === 1) {
3777
+ return pathToAsset(parsed.paths[0], parsed.width, parsed.height);
3778
+ }
3779
+ return parsed.paths.map((path) => pathToAsset(path, parsed.width, parsed.height));
3780
+ }
3781
+ function pathToAsset(path, width, height) {
3782
+ const asset = {
3783
+ type: "svg",
3784
+ shape: {
3785
+ type: "path",
3786
+ d: path.d
3787
+ }
3788
+ };
3789
+ const fillColor = parseColor(path.fill);
3790
+ if (fillColor) {
3791
+ asset.fill = {
3792
+ type: "solid",
3793
+ color: fillColor
3794
+ };
3795
+ if (path.fillOpacity !== void 0 && path.fillOpacity !== 1) {
3796
+ asset.fill.opacity = path.fillOpacity;
3797
+ }
3798
+ }
3799
+ const strokeColor = parseColor(path.stroke);
3800
+ if (strokeColor && path.strokeWidth && path.strokeWidth > 0) {
3801
+ asset.stroke = {
3802
+ color: strokeColor,
3803
+ width: path.strokeWidth
3804
+ };
3805
+ if (path.strokeOpacity !== void 0 && path.strokeOpacity !== 1) {
3806
+ asset.stroke.opacity = path.strokeOpacity;
3807
+ }
3808
+ }
3809
+ if (path.opacity !== void 0 && path.opacity !== 1) {
3810
+ asset.opacity = path.opacity;
3811
+ }
3812
+ if (width > 0) {
3813
+ asset.width = width;
3814
+ }
3815
+ if (height > 0) {
3816
+ asset.height = height;
3817
+ }
3818
+ return asset;
3819
+ }
3820
+ function svgToSingleAsset(svgString) {
3821
+ const parsed = parseSvgMarkup(svgString);
3822
+ if (parsed.paths.length === 0) {
3823
+ return {
3824
+ type: "svg",
3825
+ shape: {
3826
+ type: "path",
3827
+ d: ""
3828
+ },
3829
+ width: parsed.width || void 0,
3830
+ height: parsed.height || void 0
3831
+ };
3832
+ }
3833
+ const combinedD = parsed.paths.map((p) => p.d).join(" ");
3834
+ const firstPathWithFill = parsed.paths.find((p) => p.fill && p.fill !== "none");
3835
+ const firstPathWithStroke = parsed.paths.find((p) => p.stroke && p.stroke !== "none");
3836
+ const asset = {
3837
+ type: "svg",
3838
+ shape: {
3839
+ type: "path",
3840
+ d: combinedD
3841
+ }
3842
+ };
3843
+ if (firstPathWithFill) {
3844
+ const fillColor = parseColor(firstPathWithFill.fill);
3845
+ if (fillColor) {
3846
+ asset.fill = {
3847
+ type: "solid",
3848
+ color: fillColor
3849
+ };
3850
+ if (firstPathWithFill.fillOpacity !== void 0 && firstPathWithFill.fillOpacity !== 1) {
3851
+ asset.fill.opacity = firstPathWithFill.fillOpacity;
3852
+ }
3853
+ }
3854
+ }
3855
+ if (firstPathWithStroke) {
3856
+ const strokeColor = parseColor(firstPathWithStroke.stroke);
3857
+ if (strokeColor && firstPathWithStroke.strokeWidth && firstPathWithStroke.strokeWidth > 0) {
3858
+ asset.stroke = {
3859
+ color: strokeColor,
3860
+ width: firstPathWithStroke.strokeWidth
3861
+ };
3862
+ if (firstPathWithStroke.strokeOpacity !== void 0 && firstPathWithStroke.strokeOpacity !== 1) {
3863
+ asset.stroke.opacity = firstPathWithStroke.strokeOpacity;
3864
+ }
3865
+ }
3866
+ }
3867
+ const firstPathWithOpacity = parsed.paths.find((p) => p.opacity !== void 0 && p.opacity !== 1);
3868
+ if (firstPathWithOpacity) {
3869
+ asset.opacity = firstPathWithOpacity.opacity;
3870
+ }
3871
+ if (parsed.width > 0) {
3872
+ asset.width = parsed.width;
3873
+ }
3874
+ if (parsed.height > 0) {
3875
+ asset.height = parsed.height;
3876
+ }
3877
+ return asset;
3878
+ }
3879
+ function importSvg(svgString) {
3880
+ return svgToSingleAsset(svgString);
3881
+ }
3882
+
2454
3883
  // src/env/entry.node.ts
3884
+ var registeredGlobalFonts = /* @__PURE__ */ new Set();
3885
+ async function registerColorEmojiWithCanvas(family, bytes) {
3886
+ if (registeredGlobalFonts.has(family)) {
3887
+ return;
3888
+ }
3889
+ try {
3890
+ const canvasMod = await import("canvas");
3891
+ const GlobalFonts = canvasMod.GlobalFonts;
3892
+ if (GlobalFonts && typeof GlobalFonts.register === "function") {
3893
+ const buffer = Buffer.from(bytes);
3894
+ GlobalFonts.register(buffer, family);
3895
+ registeredGlobalFonts.add(family);
3896
+ console.log(`\u{1F3A8} Registered color emoji font with canvas: ${family}`);
3897
+ }
3898
+ } catch (err) {
3899
+ console.warn(`\u26A0\uFE0F Could not register color emoji font with canvas GlobalFonts: ${err}`);
3900
+ }
3901
+ }
2455
3902
  async function createTextEngine(opts = {}) {
2456
3903
  const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
2457
3904
  const height = opts.height ?? CANVAS_CONFIG.DEFAULTS.height;
@@ -2478,6 +3925,12 @@ async function createTextEngine(opts = {}) {
2478
3925
  family: cf.family,
2479
3926
  weight: cf.weight ?? "400"
2480
3927
  });
3928
+ if (fonts.isColorEmojiFont(cf.family)) {
3929
+ const emojiBytes = fonts.getColorEmojiFontBytes(cf.family);
3930
+ if (emojiBytes) {
3931
+ await registerColorEmojiWithCanvas(cf.family, emojiBytes);
3932
+ }
3933
+ }
2481
3934
  } catch (err) {
2482
3935
  throw new Error(
2483
3936
  `Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
@@ -2535,12 +3988,17 @@ async function createTextEngine(opts = {}) {
2535
3988
  const desc = { family: main.family, weight: `${main.weight}` };
2536
3989
  let lines;
2537
3990
  try {
2538
- const emojiDesc = { family: "NotoEmoji", weight: "400" };
2539
- let emojiAvailable = false;
2540
- try {
2541
- await fonts.getFace(emojiDesc);
2542
- emojiAvailable = true;
2543
- } catch {
3991
+ let emojiFallback = void 0;
3992
+ const colorEmojiFamilies = fonts.getColorEmojiFontFamilies();
3993
+ if (colorEmojiFamilies.length > 0) {
3994
+ emojiFallback = { family: colorEmojiFamilies[0], weight: "400" };
3995
+ } else {
3996
+ const notoEmojiDesc = { family: "NotoEmoji", weight: "400" };
3997
+ try {
3998
+ await fonts.getFace(notoEmojiDesc);
3999
+ emojiFallback = notoEmojiDesc;
4000
+ } catch {
4001
+ }
2544
4002
  }
2545
4003
  const padding2 = asset.padding ? typeof asset.padding === "number" ? {
2546
4004
  top: asset.padding,
@@ -2556,7 +4014,7 @@ async function createTextEngine(opts = {}) {
2556
4014
  lineHeight: asset.style?.lineHeight ?? 1.2,
2557
4015
  desc,
2558
4016
  textTransform: asset.style?.textTransform ?? "none",
2559
- emojiFallback: emojiAvailable ? emojiDesc : void 0
4017
+ emojiFallback
2560
4018
  });
2561
4019
  } catch (err) {
2562
4020
  throw new Error(
@@ -2611,7 +4069,8 @@ async function createTextEngine(opts = {}) {
2611
4069
  border: asset.border,
2612
4070
  padding: asset.padding,
2613
4071
  glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
2614
- getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
4072
+ getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc),
4073
+ isColorEmojiFont: (family) => fonts.isColorEmojiFont(family)
2615
4074
  });
2616
4075
  } catch (err) {
2617
4076
  throw new Error(
@@ -2703,7 +4162,50 @@ async function createTextEngine(opts = {}) {
2703
4162
  // Annotate the CommonJS export names for ESM import in node:
2704
4163
  0 && (module.exports = {
2705
4164
  CanvasRichTextAssetSchema,
4165
+ CanvasSvgAssetSchema,
4166
+ arcToCubicBeziers,
4167
+ centerPath,
4168
+ commandsToPathString,
4169
+ computePathBounds,
4170
+ createNodePainter,
2706
4171
  createTextEngine,
4172
+ generateArrowPath,
4173
+ generateCirclePath,
4174
+ generateCrossPath,
4175
+ generateEllipsePath,
4176
+ generateHeartPath,
4177
+ generateLinePath,
4178
+ generatePolygonPath,
4179
+ generatePolygonPoints,
4180
+ generateRectanglePath,
4181
+ generateRingPath,
4182
+ generateShapePath,
4183
+ generateStarPath,
4184
+ generateStarPoints,
4185
+ generateTrianglePath,
4186
+ importSvg,
2707
4187
  isGlyphFill,
2708
- isShadowFill
4188
+ isShadowFill,
4189
+ normalizePath,
4190
+ normalizePathString,
4191
+ normalizePathToSize,
4192
+ parseSvgMarkup,
4193
+ parseSvgPath,
4194
+ pointsToPath,
4195
+ quadraticToCubic,
4196
+ reverseWindingOrder,
4197
+ rotatePath,
4198
+ scalePath,
4199
+ svgAssetSchema,
4200
+ svgGradientStopSchema,
4201
+ svgLinearGradientFillSchema,
4202
+ svgRadialGradientFillSchema,
4203
+ svgShadowSchema,
4204
+ svgShapeSchema,
4205
+ svgSolidFillSchema,
4206
+ svgStrokeSchema,
4207
+ svgToAsset,
4208
+ svgToSingleAsset,
4209
+ svgTransformSchema,
4210
+ translatePath
2709
4211
  });