@shotstack/shotstack-canvas 1.8.0 → 1.8.2
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.
- package/dist/entry.node.cjs +1344 -18
- package/dist/entry.node.d.cts +405 -14
- package/dist/entry.node.d.ts +405 -14
- package/dist/entry.node.js +1310 -18
- package/dist/entry.web.d.ts +400 -14
- package/dist/entry.web.js +1289 -2
- package/package.json +2 -2
package/dist/entry.node.cjs
CHANGED
|
@@ -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 = {};
|
|
@@ -1970,14 +2014,14 @@ async function createNodePainter(opts) {
|
|
|
1970
2014
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1971
2015
|
offscreenCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1972
2016
|
const hasBackground = !!(op.bg && op.bg.color);
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
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) {
|
|
1976
2022
|
const { color, opacity, radius } = op.bg;
|
|
1977
2023
|
const c = parseHex6(color, opacity);
|
|
1978
|
-
|
|
1979
|
-
if (radius && radius > 0) {
|
|
1980
|
-
needsAlphaExtraction = true;
|
|
2024
|
+
if (hasRoundedBackground) {
|
|
1981
2025
|
ctx.save();
|
|
1982
2026
|
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1983
2027
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
@@ -1999,18 +2043,9 @@ async function createNodePainter(opts) {
|
|
|
1999
2043
|
offscreenCtx.fill();
|
|
2000
2044
|
offscreenCtx.restore();
|
|
2001
2045
|
} else {
|
|
2046
|
+
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
2002
2047
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
2003
2048
|
}
|
|
2004
|
-
} else {
|
|
2005
|
-
ctx.clearRect(0, 0, op.width, op.height);
|
|
2006
|
-
ctx.save();
|
|
2007
|
-
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
2008
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
2009
|
-
ctx.restore();
|
|
2010
|
-
offscreenCtx.save();
|
|
2011
|
-
offscreenCtx.fillStyle = "rgb(0, 0, 0)";
|
|
2012
|
-
offscreenCtx.fillRect(0, 0, op.width, op.height);
|
|
2013
|
-
offscreenCtx.restore();
|
|
2014
2049
|
}
|
|
2015
2050
|
continue;
|
|
2016
2051
|
}
|
|
@@ -2132,6 +2167,87 @@ async function createNodePainter(opts) {
|
|
|
2132
2167
|
});
|
|
2133
2168
|
continue;
|
|
2134
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
|
+
}
|
|
2135
2251
|
}
|
|
2136
2252
|
if (needsAlphaExtraction) {
|
|
2137
2253
|
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -2349,6 +2465,48 @@ function roundRectPath(ctx, x, y, w, h, r) {
|
|
|
2349
2465
|
function tokenizePath2(d) {
|
|
2350
2466
|
return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
|
|
2351
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
|
+
}
|
|
2352
2510
|
|
|
2353
2511
|
// src/io/node.ts
|
|
2354
2512
|
var import_promises = require("fs/promises");
|
|
@@ -2597,6 +2755,1131 @@ var isGlyphFill2 = (op) => {
|
|
|
2597
2755
|
return op.op === "FillPath" && op.isShadow !== true;
|
|
2598
2756
|
};
|
|
2599
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
|
+
|
|
2600
3883
|
// src/env/entry.node.ts
|
|
2601
3884
|
var registeredGlobalFonts = /* @__PURE__ */ new Set();
|
|
2602
3885
|
async function registerColorEmojiWithCanvas(family, bytes) {
|
|
@@ -2879,7 +4162,50 @@ async function createTextEngine(opts = {}) {
|
|
|
2879
4162
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2880
4163
|
0 && (module.exports = {
|
|
2881
4164
|
CanvasRichTextAssetSchema,
|
|
4165
|
+
CanvasSvgAssetSchema,
|
|
4166
|
+
arcToCubicBeziers,
|
|
4167
|
+
centerPath,
|
|
4168
|
+
commandsToPathString,
|
|
4169
|
+
computePathBounds,
|
|
4170
|
+
createNodePainter,
|
|
2882
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,
|
|
2883
4187
|
isGlyphFill,
|
|
2884
|
-
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
|
|
2885
4211
|
});
|