@shotstack/shotstack-canvas 1.8.3 → 1.8.4

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.
@@ -33,38 +33,20 @@ __export(entry_node_exports, {
33
33
  CanvasRichTextAssetSchema: () => CanvasRichTextAssetSchema,
34
34
  CanvasSvgAssetSchema: () => CanvasSvgAssetSchema,
35
35
  arcToCubicBeziers: () => arcToCubicBeziers,
36
- centerPath: () => centerPath,
37
36
  commandsToPathString: () => commandsToPathString,
38
- computePathBounds: () => computePathBounds3,
37
+ computeSimplePathBounds: () => computeSimplePathBounds,
39
38
  createNodePainter: () => createNodePainter,
40
39
  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,
40
+ generateShapePathData: () => generateShapePathData,
56
41
  isGlyphFill: () => isGlyphFill2,
57
42
  isShadowFill: () => isShadowFill2,
58
43
  normalizePath: () => normalizePath,
59
44
  normalizePathString: () => normalizePathString,
60
- normalizePathToSize: () => normalizePathToSize,
61
- parseSvgMarkup: () => parseSvgMarkup,
62
45
  parseSvgPath: () => parseSvgPath,
63
- pointsToPath: () => pointsToPath,
64
46
  quadraticToCubic: () => quadraticToCubic,
65
- reverseWindingOrder: () => reverseWindingOrder,
66
- rotatePath: () => rotatePath,
67
- scalePath: () => scalePath,
47
+ renderSvgAssetToPng: () => renderSvgAssetToPng,
48
+ renderSvgToPng: () => renderSvgToPng,
49
+ shapeToSvgString: () => shapeToSvgString,
68
50
  svgAssetSchema: () => import_zod2.svgAssetSchema,
69
51
  svgGradientStopSchema: () => import_zod2.svgGradientStopSchema,
70
52
  svgLinearGradientFillSchema: () => import_zod2.svgLinearGradientFillSchema,
@@ -73,10 +55,7 @@ __export(entry_node_exports, {
73
55
  svgShapeSchema: () => import_zod2.svgShapeSchema,
74
56
  svgSolidFillSchema: () => import_zod2.svgSolidFillSchema,
75
57
  svgStrokeSchema: () => import_zod2.svgStrokeSchema,
76
- svgToAsset: () => svgToAsset,
77
- svgToSingleAsset: () => svgToSingleAsset,
78
- svgTransformSchema: () => import_zod2.svgTransformSchema,
79
- translatePath: () => translatePath
58
+ svgTransformSchema: () => import_zod2.svgTransformSchema
80
59
  });
81
60
  module.exports = __toCommonJS(entry_node_exports);
82
61
 
@@ -2755,335 +2734,6 @@ var isGlyphFill2 = (op) => {
2755
2734
  return op.op === "FillPath" && op.isShadow !== true;
2756
2735
  };
2757
2736
 
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
2737
  // src/core/svg-path-utils.ts
3088
2738
  var TAU = Math.PI * 2;
3089
2739
  var PATH_COMMAND_REGEX = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
@@ -3480,109 +3130,183 @@ function quadraticToCubic(x0, y0, qx, qy, x, y) {
3480
3130
  };
3481
3131
  }
3482
3132
 
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(`(?:^|\\s)${name}\\s*=\\s*["']([^"']*)["']`, "i");
3494
- const match = attrs.match(regex);
3495
- return match ? match[1] : void 0;
3133
+ // src/core/resvg-renderer.ts
3134
+ var resvgModule = null;
3135
+ async function getResvg() {
3136
+ if (!resvgModule) {
3137
+ resvgModule = await import("@resvg/resvg-js");
3138
+ }
3139
+ return resvgModule;
3140
+ }
3141
+ async function renderSvgToPng(svgString, options = {}) {
3142
+ const { Resvg } = await getResvg();
3143
+ const resvgOptions = {};
3144
+ if (options.background) {
3145
+ resvgOptions.background = options.background;
3146
+ }
3147
+ if (options.fitTo) {
3148
+ resvgOptions.fitTo = options.fitTo;
3149
+ } else if (options.width) {
3150
+ resvgOptions.fitTo = { mode: "width", value: options.width };
3151
+ } else if (options.height) {
3152
+ resvgOptions.fitTo = { mode: "height", value: options.height };
3153
+ }
3154
+ const resvg = new Resvg(svgString, resvgOptions);
3155
+ const rendered = resvg.render();
3156
+ const png = rendered.asPng();
3157
+ return {
3158
+ png,
3159
+ width: rendered.width,
3160
+ height: rendered.height
3161
+ };
3496
3162
  }
3497
- function parseColor(color) {
3498
- if (!color || color === "none" || color === "transparent") {
3499
- return void 0;
3163
+ function shapeToSvgString(asset, defaultWidth, defaultHeight) {
3164
+ if (!asset.shape) {
3165
+ throw new Error("Shape is required for shape mode");
3166
+ }
3167
+ const width = asset.width ?? defaultWidth;
3168
+ const height = asset.height ?? defaultHeight;
3169
+ const pathData = generateShapePathData(asset.shape);
3170
+ const bounds = computeSimplePathBounds(pathData);
3171
+ const centerX = width / 2;
3172
+ const centerY = height / 2;
3173
+ const pathCenterX = bounds.x + bounds.w / 2;
3174
+ const pathCenterY = bounds.y + bounds.h / 2;
3175
+ let offsetX = centerX - pathCenterX;
3176
+ let offsetY = centerY - pathCenterY;
3177
+ if (asset.transform) {
3178
+ offsetX += asset.transform.x ?? 0;
3179
+ offsetY += asset.transform.y ?? 0;
3180
+ }
3181
+ const elements = [];
3182
+ if (asset.shadow) {
3183
+ const shadowOffsetX = asset.shadow.offsetX ?? 4;
3184
+ const shadowOffsetY = asset.shadow.offsetY ?? 4;
3185
+ const shadowColor = asset.shadow.color ?? "#000000";
3186
+ const shadowOpacity = asset.shadow.opacity ?? 0.5;
3187
+ elements.push(
3188
+ `<path d="${pathData}" transform="translate(${offsetX + shadowOffsetX}, ${offsetY + shadowOffsetY})" fill="${shadowColor}" fill-opacity="${shadowOpacity}"/>`
3189
+ );
3500
3190
  }
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();
3191
+ const pathAttrs = [`d="${pathData}"`, `transform="translate(${offsetX}, ${offsetY})"`];
3192
+ if (asset.fill) {
3193
+ const fillId = `fill-${Date.now()}`;
3194
+ const fillDef = generateFillDefinition(asset.fill, fillId, bounds);
3195
+ if (fillDef.defs) {
3196
+ elements.unshift(fillDef.defs);
3508
3197
  }
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);
3198
+ pathAttrs.push(`fill="${fillDef.fill}"`);
3199
+ if (asset.fill.opacity !== void 0 && asset.fill.opacity !== 1) {
3200
+ pathAttrs.push(`fill-opacity="${asset.fill.opacity}"`);
3201
+ }
3202
+ } else {
3203
+ pathAttrs.push(`fill="none"`);
3517
3204
  }
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);
3205
+ if (asset.stroke && asset.stroke.width && asset.stroke.width > 0) {
3206
+ pathAttrs.push(`stroke="${asset.stroke.color ?? "#000000"}"`);
3207
+ pathAttrs.push(`stroke-width="${asset.stroke.width}"`);
3208
+ if (asset.stroke.opacity !== void 0 && asset.stroke.opacity !== 1) {
3209
+ pathAttrs.push(`stroke-opacity="${asset.stroke.opacity}"`);
3210
+ }
3211
+ if (asset.stroke.dashArray && asset.stroke.dashArray.length > 0) {
3212
+ pathAttrs.push(`stroke-dasharray="${asset.stroke.dashArray.join(" ")}"`);
3213
+ }
3214
+ if (asset.stroke.lineCap) {
3215
+ pathAttrs.push(`stroke-linecap="${asset.stroke.lineCap}"`);
3216
+ }
3217
+ if (asset.stroke.lineJoin) {
3218
+ pathAttrs.push(`stroke-linejoin="${asset.stroke.lineJoin}"`);
3219
+ }
3524
3220
  }
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];
3221
+ if (asset.opacity !== void 0 && asset.opacity !== 1) {
3222
+ pathAttrs.push(`opacity="${asset.opacity}"`);
3552
3223
  }
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();
3224
+ elements.push(`<path ${pathAttrs.join(" ")}/>`);
3225
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${elements.join(
3226
+ ""
3227
+ )}</svg>`;
3561
3228
  }
3562
- function parseNumber(value) {
3563
- if (!value) return void 0;
3564
- const num = parseFloat(value);
3565
- return isNaN(num) ? void 0 : num;
3229
+ function generateFillDefinition(fill, id, bounds) {
3230
+ if (fill.type === "solid") {
3231
+ return { fill: fill.color };
3232
+ }
3233
+ if (fill.type === "linear") {
3234
+ const angle = fill.angle ?? 0;
3235
+ const rad = angle * Math.PI / 180;
3236
+ const x1 = 50 - Math.cos(rad) * 50;
3237
+ const y1 = 50 - Math.sin(rad) * 50;
3238
+ const x2 = 50 + Math.cos(rad) * 50;
3239
+ const y2 = 50 + Math.sin(rad) * 50;
3240
+ const stops = fill.stops.map((s) => `<stop offset="${s.offset * 100}%" stop-color="${s.color}"/>`).join("");
3241
+ const defs = `<defs><linearGradient id="${id}" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">${stops}</linearGradient></defs>`;
3242
+ return { fill: `url(#${id})`, defs };
3243
+ }
3244
+ if (fill.type === "radial") {
3245
+ const stops = fill.stops.map((s) => `<stop offset="${s.offset * 100}%" stop-color="${s.color}"/>`).join("");
3246
+ const defs = `<defs><radialGradient id="${id}" cx="50%" cy="50%" r="50%">${stops}</radialGradient></defs>`;
3247
+ return { fill: `url(#${id})`, defs };
3248
+ }
3249
+ return { fill: "#000000" };
3566
3250
  }
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] };
3251
+ function generateShapePathData(shape) {
3252
+ const shapeType = shape.type;
3253
+ switch (shapeType) {
3254
+ case "rectangle": {
3255
+ const s = shape;
3256
+ return generateRectanglePath(s.width, s.height, s.cornerRadius ?? 0);
3257
+ }
3258
+ case "circle": {
3259
+ const s = shape;
3260
+ return generateCirclePath(s.radius);
3261
+ }
3262
+ case "ellipse": {
3263
+ const s = shape;
3264
+ return generateEllipsePath(s.radiusX, s.radiusY);
3265
+ }
3266
+ case "line": {
3267
+ const s = shape;
3268
+ return generateLinePath(s.length, s.thickness ?? 2);
3269
+ }
3270
+ case "polygon": {
3271
+ const s = shape;
3272
+ return generatePolygonPath(s.sides, s.radius);
3273
+ }
3274
+ case "star": {
3275
+ const s = shape;
3276
+ return generateStarPath(s.points, s.outerRadius, s.innerRadius);
3277
+ }
3278
+ case "arrow": {
3279
+ const s = shape;
3280
+ return generateArrowPath(s.length, s.headWidth ?? 30, s.headLength ?? 25, s.shaftWidth ?? 10);
3281
+ }
3282
+ case "heart": {
3283
+ const s = shape;
3284
+ return generateHeartPath(s.size);
3285
+ }
3286
+ case "cross": {
3287
+ const s = shape;
3288
+ return generateCrossPath(s.width, s.height, s.thickness);
3289
+ }
3290
+ case "ring": {
3291
+ const s = shape;
3292
+ return generateRingPath(s.outerRadius, s.innerRadius);
3293
+ }
3294
+ case "path": {
3295
+ const s = shape;
3296
+ return s.d;
3297
+ }
3298
+ default:
3299
+ throw new Error(`Unknown shape type: ${shapeType}`);
3572
3300
  }
3573
- return void 0;
3574
3301
  }
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) {
3302
+ var KAPPA = 0.5522847498307936;
3303
+ function generateRectanglePath(width, height, cornerRadius) {
3304
+ const x = -width / 2;
3305
+ const y = -height / 2;
3306
+ const r = Math.min(cornerRadius, width / 2, height / 2);
3307
+ if (r === 0) {
3583
3308
  return `M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z`;
3584
3309
  }
3585
- const r = Math.min(rx, ry, width / 2, height / 2);
3586
3310
  return [
3587
3311
  `M ${x + r} ${y}`,
3588
3312
  `L ${x + width - r} ${y}`,
@@ -3596,288 +3320,358 @@ function rectToPath(attrs) {
3596
3320
  "Z"
3597
3321
  ].join(" ");
3598
3322
  }
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;
3323
+ function generateCirclePath(radius) {
3324
+ const k = radius * KAPPA;
3606
3325
  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}`,
3326
+ `M ${radius} 0`,
3327
+ `C ${radius} ${k} ${k} ${radius} 0 ${radius}`,
3328
+ `C ${-k} ${radius} ${-radius} ${k} ${-radius} 0`,
3329
+ `C ${-radius} ${-k} ${-k} ${-radius} 0 ${-radius}`,
3330
+ `C ${k} ${-radius} ${radius} ${-k} ${radius} 0`,
3612
3331
  "Z"
3613
3332
  ].join(" ");
3614
3333
  }
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;
3334
+ function generateEllipsePath(radiusX, radiusY) {
3335
+ const kx = radiusX * KAPPA;
3336
+ const ky = radiusY * KAPPA;
3624
3337
  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}`,
3338
+ `M ${radiusX} 0`,
3339
+ `C ${radiusX} ${ky} ${kx} ${radiusY} 0 ${radiusY}`,
3340
+ `C ${-kx} ${radiusY} ${-radiusX} ${ky} ${-radiusX} 0`,
3341
+ `C ${-radiusX} ${-ky} ${-kx} ${-radiusY} 0 ${-radiusY}`,
3342
+ `C ${kx} ${-radiusY} ${radiusX} ${-ky} ${radiusX} 0`,
3630
3343
  "Z"
3631
3344
  ].join(" ");
3632
3345
  }
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}`;
3346
+ function generateLinePath(length, thickness) {
3347
+ const halfLen = length / 2;
3348
+ const halfThick = thickness / 2;
3349
+ return `M ${-halfLen} ${-halfThick} L ${halfLen} ${-halfThick} L ${halfLen} ${halfThick} L ${-halfLen} ${halfThick} Z`;
3639
3350
  }
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 "";
3351
+ function generatePolygonPath(sides, radius) {
3352
+ const angleStep = 2 * Math.PI / sides;
3353
+ const startAngle = -Math.PI / 2;
3354
+ const points = [];
3355
+ for (let i = 0; i < sides; i++) {
3356
+ const angle = startAngle + i * angleStep;
3357
+ const x = Math.cos(angle) * radius;
3358
+ const y = Math.sin(angle) * radius;
3359
+ points.push(i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`);
3360
+ }
3361
+ points.push("Z");
3362
+ return points.join(" ");
3363
+ }
3364
+ function generateStarPath(points, outerRadius, innerRadius) {
3365
+ const angleStep = Math.PI / points;
3366
+ const startAngle = -Math.PI / 2;
3645
3367
  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
- }
3368
+ for (let i = 0; i < points * 2; i++) {
3369
+ const angle = startAngle + i * angleStep;
3370
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
3371
+ const x = Math.cos(angle) * radius;
3372
+ const y = Math.sin(angle) * radius;
3373
+ pathParts.push(i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`);
3654
3374
  }
3375
+ pathParts.push("Z");
3655
3376
  return pathParts.join(" ");
3656
3377
  }
3657
- function polygonToPath(attrs) {
3658
- const polyPath = polylineToPath(attrs);
3659
- if (!polyPath) return "";
3660
- return polyPath + " Z";
3378
+ function generateArrowPath(length, headWidth, headLength, shaftWidth) {
3379
+ const halfShaft = shaftWidth / 2;
3380
+ const halfHead = headWidth / 2;
3381
+ const shaftLength = length - headLength;
3382
+ const startX = -length / 2;
3383
+ return [
3384
+ `M ${startX} ${-halfShaft}`,
3385
+ `L ${startX + shaftLength} ${-halfShaft}`,
3386
+ `L ${startX + shaftLength} ${-halfHead}`,
3387
+ `L ${startX + length} 0`,
3388
+ `L ${startX + shaftLength} ${halfHead}`,
3389
+ `L ${startX + shaftLength} ${halfShaft}`,
3390
+ `L ${startX} ${halfShaft}`,
3391
+ "Z"
3392
+ ].join(" ");
3661
3393
  }
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
- };
3394
+ function generateHeartPath(size) {
3395
+ const scale = size / 32;
3396
+ const points = [
3397
+ "M 0 6",
3398
+ "C -0.5 -3 -12 -3 -12 6",
3399
+ "C -12 12 0 18 0 24",
3400
+ "C 0 18 12 12 12 6",
3401
+ "C 12 -3 0.5 -3 0 6",
3402
+ "Z"
3403
+ ];
3404
+ return points.map((cmd) => {
3405
+ return cmd.replace(/-?\d+(\.\d+)?/g, (match) => {
3406
+ return String(parseFloat(match) * scale);
3407
+ });
3408
+ }).join(" ");
3696
3409
  }
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;
3410
+ function generateCrossPath(width, height, thickness) {
3411
+ const hw = width / 2;
3412
+ const hh = height / 2;
3413
+ const ht = thickness / 2;
3414
+ return [
3415
+ `M ${-ht} ${-hh}`,
3416
+ `L ${ht} ${-hh}`,
3417
+ `L ${ht} ${-ht}`,
3418
+ `L ${hw} ${-ht}`,
3419
+ `L ${hw} ${ht}`,
3420
+ `L ${ht} ${ht}`,
3421
+ `L ${ht} ${hh}`,
3422
+ `L ${-ht} ${hh}`,
3423
+ `L ${-ht} ${ht}`,
3424
+ `L ${-hw} ${ht}`,
3425
+ `L ${-hw} ${-ht}`,
3426
+ `L ${-ht} ${-ht}`,
3427
+ "Z"
3428
+ ].join(" ");
3703
3429
  }
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 = [];
3430
+ function generateRingPath(outerRadius, innerRadius) {
3431
+ const outerPath = generateCirclePath(outerRadius);
3432
+ const innerPath = generateCirclePathReversed(innerRadius);
3433
+ return `${outerPath} ${innerPath}`;
3434
+ }
3435
+ function generateCirclePathReversed(radius) {
3436
+ const k = radius * KAPPA;
3437
+ return [
3438
+ `M ${radius} 0`,
3439
+ `C ${radius} ${-k} ${k} ${-radius} 0 ${-radius}`,
3440
+ `C ${-k} ${-radius} ${-radius} ${-k} ${-radius} 0`,
3441
+ `C ${-radius} ${k} ${-k} ${radius} 0 ${radius}`,
3442
+ `C ${k} ${radius} ${radius} ${k} ${radius} 0`,
3443
+ "Z"
3444
+ ].join(" ");
3445
+ }
3446
+ function computeSimplePathBounds(d) {
3447
+ const numberRegex = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
3448
+ const numbers = [];
3718
3449
  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);
3450
+ while ((match = numberRegex.exec(d)) !== null) {
3451
+ const num = parseFloat(match[0]);
3452
+ if (!isNaN(num) && isFinite(num)) {
3453
+ numbers.push(num);
3724
3454
  }
3725
3455
  }
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);
3456
+ if (numbers.length < 2) {
3457
+ return { x: 0, y: 0, w: 0, h: 0 };
3745
3458
  }
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);
3459
+ let minX = Infinity;
3460
+ let minY = Infinity;
3461
+ let maxX = -Infinity;
3462
+ let maxY = -Infinity;
3463
+ let currentX = 0;
3464
+ let currentY = 0;
3465
+ let i = 0;
3466
+ const commands = d.match(/[MmLlHhVvCcSsQqTtAaZz]/g) || [];
3467
+ const commandPositions = [];
3468
+ let searchPos = 0;
3469
+ for (const cmd of commands) {
3470
+ const pos = d.indexOf(cmd, searchPos);
3471
+ commandPositions.push(pos);
3472
+ searchPos = pos + 1;
3473
+ }
3474
+ let cmdIndex = 0;
3475
+ let numIndex = 0;
3476
+ while (cmdIndex < commands.length) {
3477
+ const cmd = commands[cmdIndex];
3478
+ switch (cmd) {
3479
+ case "M":
3480
+ case "L":
3481
+ case "T":
3482
+ if (numIndex + 1 < numbers.length) {
3483
+ currentX = numbers[numIndex++];
3484
+ currentY = numbers[numIndex++];
3485
+ minX = Math.min(minX, currentX);
3486
+ maxX = Math.max(maxX, currentX);
3487
+ minY = Math.min(minY, currentY);
3488
+ maxY = Math.max(maxY, currentY);
3489
+ }
3490
+ break;
3491
+ case "m":
3492
+ case "l":
3493
+ case "t":
3494
+ if (numIndex + 1 < numbers.length) {
3495
+ currentX += numbers[numIndex++];
3496
+ currentY += numbers[numIndex++];
3497
+ minX = Math.min(minX, currentX);
3498
+ maxX = Math.max(maxX, currentX);
3499
+ minY = Math.min(minY, currentY);
3500
+ maxY = Math.max(maxY, currentY);
3501
+ }
3502
+ break;
3503
+ case "H":
3504
+ if (numIndex < numbers.length) {
3505
+ currentX = numbers[numIndex++];
3506
+ minX = Math.min(minX, currentX);
3507
+ maxX = Math.max(maxX, currentX);
3508
+ }
3509
+ break;
3510
+ case "h":
3511
+ if (numIndex < numbers.length) {
3512
+ currentX += numbers[numIndex++];
3513
+ minX = Math.min(minX, currentX);
3514
+ maxX = Math.max(maxX, currentX);
3515
+ }
3516
+ break;
3517
+ case "V":
3518
+ if (numIndex < numbers.length) {
3519
+ currentY = numbers[numIndex++];
3520
+ minY = Math.min(minY, currentY);
3521
+ maxY = Math.max(maxY, currentY);
3522
+ }
3523
+ break;
3524
+ case "v":
3525
+ if (numIndex < numbers.length) {
3526
+ currentY += numbers[numIndex++];
3527
+ minY = Math.min(minY, currentY);
3528
+ maxY = Math.max(maxY, currentY);
3529
+ }
3530
+ break;
3531
+ case "C":
3532
+ if (numIndex + 5 < numbers.length) {
3533
+ for (let j = 0; j < 3; j++) {
3534
+ const x = numbers[numIndex++];
3535
+ const y = numbers[numIndex++];
3536
+ minX = Math.min(minX, x);
3537
+ maxX = Math.max(maxX, x);
3538
+ minY = Math.min(minY, y);
3539
+ maxY = Math.max(maxY, y);
3540
+ if (j === 2) {
3541
+ currentX = x;
3542
+ currentY = y;
3543
+ }
3544
+ }
3545
+ }
3546
+ break;
3547
+ case "c":
3548
+ if (numIndex + 5 < numbers.length) {
3549
+ for (let j = 0; j < 3; j++) {
3550
+ const x = currentX + numbers[numIndex++];
3551
+ const y = currentY + numbers[numIndex++];
3552
+ minX = Math.min(minX, x);
3553
+ maxX = Math.max(maxX, x);
3554
+ minY = Math.min(minY, y);
3555
+ maxY = Math.max(maxY, y);
3556
+ if (j === 2) {
3557
+ currentX = x;
3558
+ currentY = y;
3559
+ }
3560
+ }
3561
+ }
3562
+ break;
3563
+ case "S":
3564
+ case "Q":
3565
+ if (numIndex + 3 < numbers.length) {
3566
+ for (let j = 0; j < 2; j++) {
3567
+ const x = numbers[numIndex++];
3568
+ const y = numbers[numIndex++];
3569
+ minX = Math.min(minX, x);
3570
+ maxX = Math.max(maxX, x);
3571
+ minY = Math.min(minY, y);
3572
+ maxY = Math.max(maxY, y);
3573
+ if (j === 1) {
3574
+ currentX = x;
3575
+ currentY = y;
3576
+ }
3577
+ }
3578
+ }
3579
+ break;
3580
+ case "s":
3581
+ case "q":
3582
+ if (numIndex + 3 < numbers.length) {
3583
+ for (let j = 0; j < 2; j++) {
3584
+ const x = currentX + numbers[numIndex++];
3585
+ const y = currentY + numbers[numIndex++];
3586
+ minX = Math.min(minX, x);
3587
+ maxX = Math.max(maxX, x);
3588
+ minY = Math.min(minY, y);
3589
+ maxY = Math.max(maxY, y);
3590
+ if (j === 1) {
3591
+ currentX = x;
3592
+ currentY = y;
3593
+ }
3594
+ }
3595
+ }
3596
+ break;
3597
+ case "A":
3598
+ if (numIndex + 6 < numbers.length) {
3599
+ numIndex += 5;
3600
+ currentX = numbers[numIndex++];
3601
+ currentY = numbers[numIndex++];
3602
+ minX = Math.min(minX, currentX);
3603
+ maxX = Math.max(maxX, currentX);
3604
+ minY = Math.min(minY, currentY);
3605
+ maxY = Math.max(maxY, currentY);
3606
+ }
3607
+ break;
3608
+ case "a":
3609
+ if (numIndex + 6 < numbers.length) {
3610
+ numIndex += 5;
3611
+ currentX += numbers[numIndex++];
3612
+ currentY += numbers[numIndex++];
3613
+ minX = Math.min(minX, currentX);
3614
+ maxX = Math.max(maxX, currentX);
3615
+ minY = Math.min(minY, currentY);
3616
+ maxY = Math.max(maxY, currentY);
3617
+ }
3618
+ break;
3619
+ case "Z":
3620
+ case "z":
3621
+ break;
3622
+ }
3623
+ cmdIndex++;
3750
3624
  }
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);
3625
+ if (minX === Infinity) {
3626
+ return { x: 0, y: 0, w: 0, h: 0 };
3755
3627
  }
3756
3628
  return {
3757
- width,
3758
- height,
3759
- viewBox,
3760
- paths
3629
+ x: minX,
3630
+ y: minY,
3631
+ w: maxX - minX,
3632
+ h: maxY - minY
3761
3633
  };
3762
3634
  }
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;
3635
+ async function renderSvgAssetToPng(asset, options = {}) {
3636
+ const defaultWidth = options.defaultWidth ?? 1920;
3637
+ const defaultHeight = options.defaultHeight ?? 1080;
3638
+ let svgString;
3639
+ let targetWidth;
3640
+ let targetHeight;
3641
+ if (asset.src) {
3642
+ svgString = asset.src;
3643
+ const dimensions = extractSvgDimensions(svgString);
3644
+ targetWidth = dimensions.width || defaultWidth;
3645
+ targetHeight = dimensions.height || defaultHeight;
3646
+ } else if (asset.shape) {
3647
+ targetWidth = asset.width ?? defaultWidth;
3648
+ targetHeight = asset.height ?? defaultHeight;
3649
+ svgString = shapeToSvgString(asset, targetWidth, targetHeight);
3650
+ } else {
3651
+ throw new Error("Either 'src' or 'shape' must be provided");
3817
3652
  }
3818
- return asset;
3653
+ return renderSvgToPng(svgString, {
3654
+ width: targetWidth,
3655
+ height: targetHeight,
3656
+ background: options.background
3657
+ });
3819
3658
  }
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;
3659
+ function extractSvgDimensions(svgString) {
3660
+ const widthMatch = svgString.match(/width\s*=\s*["']?(\d+(?:\.\d+)?)/i);
3661
+ const heightMatch = svgString.match(/height\s*=\s*["']?(\d+(?:\.\d+)?)/i);
3662
+ let width = widthMatch ? parseFloat(widthMatch[1]) : 0;
3663
+ let height = heightMatch ? parseFloat(heightMatch[1]) : 0;
3664
+ if (!width || !height) {
3665
+ const viewBoxMatch = svgString.match(/viewBox\s*=\s*["']([^"']+)["']/i);
3666
+ if (viewBoxMatch) {
3667
+ const parts = viewBoxMatch[1].trim().split(/[\s,]+/).map(parseFloat);
3668
+ if (parts.length === 4) {
3669
+ width = width || parts[2];
3670
+ height = height || parts[3];
3852
3671
  }
3853
3672
  }
3854
3673
  }
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);
3674
+ return { width, height };
3881
3675
  }
3882
3676
 
3883
3677
  // src/env/entry.node.ts
@@ -4164,38 +3958,20 @@ async function createTextEngine(opts = {}) {
4164
3958
  CanvasRichTextAssetSchema,
4165
3959
  CanvasSvgAssetSchema,
4166
3960
  arcToCubicBeziers,
4167
- centerPath,
4168
3961
  commandsToPathString,
4169
- computePathBounds,
3962
+ computeSimplePathBounds,
4170
3963
  createNodePainter,
4171
3964
  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,
3965
+ generateShapePathData,
4187
3966
  isGlyphFill,
4188
3967
  isShadowFill,
4189
3968
  normalizePath,
4190
3969
  normalizePathString,
4191
- normalizePathToSize,
4192
- parseSvgMarkup,
4193
3970
  parseSvgPath,
4194
- pointsToPath,
4195
3971
  quadraticToCubic,
4196
- reverseWindingOrder,
4197
- rotatePath,
4198
- scalePath,
3972
+ renderSvgAssetToPng,
3973
+ renderSvgToPng,
3974
+ shapeToSvgString,
4199
3975
  svgAssetSchema,
4200
3976
  svgGradientStopSchema,
4201
3977
  svgLinearGradientFillSchema,
@@ -4204,8 +3980,5 @@ async function createTextEngine(opts = {}) {
4204
3980
  svgShapeSchema,
4205
3981
  svgSolidFillSchema,
4206
3982
  svgStrokeSchema,
4207
- svgToAsset,
4208
- svgToSingleAsset,
4209
- svgTransformSchema,
4210
- translatePath
3983
+ svgTransformSchema
4211
3984
  });