@shotstack/shotstack-canvas 1.7.2 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry.node.cjs +1533 -31
- package/dist/entry.node.d.cts +413 -14
- package/dist/entry.node.d.ts +413 -14
- package/dist/entry.node.js +1499 -31
- package/dist/entry.web.d.ts +408 -14
- package/dist/entry.web.js +1452 -15
- package/package.json +2 -2
package/dist/entry.node.js
CHANGED
|
@@ -8,7 +8,16 @@ import {
|
|
|
8
8
|
richTextShadowSchema,
|
|
9
9
|
richTextBackgroundSchema,
|
|
10
10
|
richTextAlignmentSchema,
|
|
11
|
-
richTextAnimationSchema
|
|
11
|
+
richTextAnimationSchema,
|
|
12
|
+
svgAssetSchema,
|
|
13
|
+
svgShapeSchema,
|
|
14
|
+
svgSolidFillSchema,
|
|
15
|
+
svgLinearGradientFillSchema,
|
|
16
|
+
svgRadialGradientFillSchema,
|
|
17
|
+
svgStrokeSchema,
|
|
18
|
+
svgShadowSchema,
|
|
19
|
+
svgTransformSchema,
|
|
20
|
+
svgGradientStopSchema
|
|
12
21
|
} from "@shotstack/schemas/zod";
|
|
13
22
|
|
|
14
23
|
// src/config/canvas-constants.ts
|
|
@@ -164,6 +173,7 @@ var CanvasRichTextAssetSchema = richTextAssetSchema.extend({
|
|
|
164
173
|
animation: canvasAnimationSchema.optional(),
|
|
165
174
|
customFonts: z.array(customFontSchema).optional()
|
|
166
175
|
}).strict();
|
|
176
|
+
var CanvasSvgAssetSchema = svgAssetSchema;
|
|
167
177
|
|
|
168
178
|
// src/wasm/hb-loader.ts
|
|
169
179
|
var hbSingleton = null;
|
|
@@ -399,6 +409,9 @@ var FontRegistry = class _FontRegistry {
|
|
|
399
409
|
fonts = /* @__PURE__ */ new Map();
|
|
400
410
|
blobs = /* @__PURE__ */ new Map();
|
|
401
411
|
fontkitFonts = /* @__PURE__ */ new Map();
|
|
412
|
+
fontkitBaseFonts = /* @__PURE__ */ new Map();
|
|
413
|
+
colorEmojiFonts = /* @__PURE__ */ new Set();
|
|
414
|
+
colorEmojiFontBytes = /* @__PURE__ */ new Map();
|
|
402
415
|
wasmBaseURL;
|
|
403
416
|
initPromise;
|
|
404
417
|
emojiFallbackDesc;
|
|
@@ -462,6 +475,49 @@ var FontRegistry = class _FontRegistry {
|
|
|
462
475
|
setEmojiFallback(desc) {
|
|
463
476
|
this.emojiFallbackDesc = desc;
|
|
464
477
|
}
|
|
478
|
+
isColorEmojiFont(family) {
|
|
479
|
+
return this.colorEmojiFonts.has(family);
|
|
480
|
+
}
|
|
481
|
+
getColorEmojiFontBytes(family) {
|
|
482
|
+
return this.colorEmojiFontBytes.get(family);
|
|
483
|
+
}
|
|
484
|
+
getColorEmojiFontFamilies() {
|
|
485
|
+
return Array.from(this.colorEmojiFonts);
|
|
486
|
+
}
|
|
487
|
+
detectColorEmojiFont(fkFont) {
|
|
488
|
+
try {
|
|
489
|
+
if (fkFont.directory && fkFont.directory.tables) {
|
|
490
|
+
const tables = fkFont.directory.tables;
|
|
491
|
+
if (tables.CBDT || tables.CBLC) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
if (tables.sbix) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
if (tables.COLR && tables.CPAL) {
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
if (tables.SVG) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (fkFont.availableTables) {
|
|
505
|
+
const tables = fkFont.availableTables;
|
|
506
|
+
if (tables.includes("CBDT") || tables.includes("CBLC") || tables.includes("sbix") || tables.includes("COLR") && tables.includes("CPAL") || tables.includes("SVG")) {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (fkFont._tables) {
|
|
511
|
+
const tableNames = Object.keys(fkFont._tables);
|
|
512
|
+
if (tableNames.includes("CBDT") || tableNames.includes("CBLC") || tableNames.includes("sbix") || tableNames.includes("COLR") && tableNames.includes("CPAL") || tableNames.includes("SVG")) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return false;
|
|
517
|
+
} catch {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
465
521
|
constructor(wasmBaseURL) {
|
|
466
522
|
this.wasmBaseURL = wasmBaseURL;
|
|
467
523
|
}
|
|
@@ -530,13 +586,29 @@ var FontRegistry = class _FontRegistry {
|
|
|
530
586
|
try {
|
|
531
587
|
const buffer = typeof Buffer !== "undefined" ? Buffer.from(bytes) : new Uint8Array(bytes);
|
|
532
588
|
const fkFont = fontkit.create(buffer);
|
|
589
|
+
const baseFontKey = desc.family;
|
|
590
|
+
if (!this.fontkitBaseFonts.has(baseFontKey)) {
|
|
591
|
+
this.fontkitBaseFonts.set(baseFontKey, fkFont);
|
|
592
|
+
}
|
|
593
|
+
const isColorEmojiFont = this.detectColorEmojiFont(fkFont);
|
|
594
|
+
if (isColorEmojiFont) {
|
|
595
|
+
this.colorEmojiFonts.add(desc.family);
|
|
596
|
+
if (!this.colorEmojiFontBytes.has(desc.family)) {
|
|
597
|
+
this.colorEmojiFontBytes.set(desc.family, bytes.slice(0));
|
|
598
|
+
}
|
|
599
|
+
console.log(`\u{1F3A8} Registered color emoji font: ${desc.family}`);
|
|
600
|
+
}
|
|
533
601
|
if (fkFont.variationAxes && Object.keys(fkFont.variationAxes).length > 0) {
|
|
534
602
|
const variationFont = fkFont.getVariation({ wght: weightNum });
|
|
535
603
|
this.fontkitFonts.set(k, variationFont);
|
|
536
|
-
|
|
604
|
+
if (!isColorEmojiFont) {
|
|
605
|
+
console.log(`\u2705 Registered variable font: ${desc.family} weight=${weightNum}`);
|
|
606
|
+
}
|
|
537
607
|
} else {
|
|
538
608
|
this.fontkitFonts.set(k, fkFont);
|
|
539
|
-
|
|
609
|
+
if (!isColorEmojiFont) {
|
|
610
|
+
console.log(`\u2705 Registered static font: ${desc.family}`);
|
|
611
|
+
}
|
|
540
612
|
}
|
|
541
613
|
} catch (err) {
|
|
542
614
|
console.warn(`\u26A0\uFE0F Fontkit failed for ${desc.family}:`, err);
|
|
@@ -575,6 +647,9 @@ var FontRegistry = class _FontRegistry {
|
|
|
575
647
|
const installed = await this.tryFallbackInstall(desc);
|
|
576
648
|
f = installed ? this.fonts.get(k) : void 0;
|
|
577
649
|
}
|
|
650
|
+
if (!f) {
|
|
651
|
+
f = await this.tryDeriveFromExistingFont(desc);
|
|
652
|
+
}
|
|
578
653
|
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
579
654
|
return f;
|
|
580
655
|
} catch (err) {
|
|
@@ -586,6 +661,53 @@ var FontRegistry = class _FontRegistry {
|
|
|
586
661
|
);
|
|
587
662
|
}
|
|
588
663
|
}
|
|
664
|
+
async tryDeriveFromExistingFont(desc) {
|
|
665
|
+
const targetFamily = desc.family;
|
|
666
|
+
const targetWeight = normalizeWeight(desc.weight);
|
|
667
|
+
const targetWeightNum = parseInt(targetWeight, 10);
|
|
668
|
+
let existingBlob;
|
|
669
|
+
for (const [key, blob] of this.blobs) {
|
|
670
|
+
if (key.startsWith(`${targetFamily}__`)) {
|
|
671
|
+
existingBlob = blob;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const baseFkFont = this.fontkitBaseFonts.get(targetFamily);
|
|
676
|
+
if (!existingBlob || !baseFkFont) {
|
|
677
|
+
return void 0;
|
|
678
|
+
}
|
|
679
|
+
const hasWeightAxis = baseFkFont.variationAxes && baseFkFont.variationAxes["wght"] !== void 0;
|
|
680
|
+
if (!hasWeightAxis) {
|
|
681
|
+
console.warn(
|
|
682
|
+
`Font ${targetFamily} is not a variable font, cannot derive weight ${targetWeight}`
|
|
683
|
+
);
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
const k = this.key(desc);
|
|
687
|
+
const face = this.hb.createFace(existingBlob, 0);
|
|
688
|
+
const font = this.hb.createFont(face);
|
|
689
|
+
const upem = face.upem || 1e3;
|
|
690
|
+
font.setScale(upem, upem);
|
|
691
|
+
if (typeof font.setVariations === "function") {
|
|
692
|
+
try {
|
|
693
|
+
font.setVariations(`wght=${targetWeightNum}`);
|
|
694
|
+
} catch (e) {
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
this.faces.set(k, face);
|
|
698
|
+
this.fonts.set(k, font);
|
|
699
|
+
try {
|
|
700
|
+
const variationFont = baseFkFont.getVariation({ wght: targetWeightNum });
|
|
701
|
+
this.fontkitFonts.set(k, variationFont);
|
|
702
|
+
console.log(
|
|
703
|
+
`\u2705 Derived variable font: ${targetFamily} weight=${targetWeightNum} from existing registration`
|
|
704
|
+
);
|
|
705
|
+
} catch (err) {
|
|
706
|
+
this.fontkitFonts.set(k, baseFkFont);
|
|
707
|
+
console.warn(`\u26A0\uFE0F Could not apply weight variation, using base font for ${targetFamily}`);
|
|
708
|
+
}
|
|
709
|
+
return font;
|
|
710
|
+
}
|
|
589
711
|
async getFace(desc) {
|
|
590
712
|
try {
|
|
591
713
|
if (!this.hb) await this.init();
|
|
@@ -595,6 +717,10 @@ var FontRegistry = class _FontRegistry {
|
|
|
595
717
|
const installed = await this.tryFallbackInstall(desc);
|
|
596
718
|
face = installed ? this.faces.get(k) : void 0;
|
|
597
719
|
}
|
|
720
|
+
if (!face) {
|
|
721
|
+
await this.tryDeriveFromExistingFont(desc);
|
|
722
|
+
face = this.faces.get(k);
|
|
723
|
+
}
|
|
598
724
|
return face;
|
|
599
725
|
} catch (err) {
|
|
600
726
|
throw new Error(
|
|
@@ -665,6 +791,9 @@ var FontRegistry = class _FontRegistry {
|
|
|
665
791
|
this.faces.clear();
|
|
666
792
|
this.blobs.clear();
|
|
667
793
|
this.fontkitFonts.clear();
|
|
794
|
+
this.fontkitBaseFonts.clear();
|
|
795
|
+
this.colorEmojiFonts.clear();
|
|
796
|
+
this.colorEmojiFontBytes.clear();
|
|
668
797
|
this.hb = void 0;
|
|
669
798
|
this.initPromise = void 0;
|
|
670
799
|
} catch (err) {
|
|
@@ -789,7 +918,10 @@ var LayoutEngine = class {
|
|
|
789
918
|
const charIndex = g.cl;
|
|
790
919
|
let char;
|
|
791
920
|
if (charIndex >= 0 && charIndex < input.length) {
|
|
792
|
-
|
|
921
|
+
const codePoint = input.codePointAt(charIndex);
|
|
922
|
+
if (codePoint !== void 0) {
|
|
923
|
+
char = String.fromCodePoint(codePoint);
|
|
924
|
+
}
|
|
793
925
|
}
|
|
794
926
|
return {
|
|
795
927
|
id: g.g,
|
|
@@ -974,13 +1106,27 @@ async function buildDrawOps(p) {
|
|
|
974
1106
|
});
|
|
975
1107
|
}
|
|
976
1108
|
for (const glyph of line.glyphs) {
|
|
1109
|
+
const glyphX = xCursor + glyph.xOffset;
|
|
1110
|
+
const glyphY = baselineY + glyph.yOffset;
|
|
1111
|
+
const glyphFamily = glyph.fontDesc?.family;
|
|
1112
|
+
const isColorEmoji = glyph.isColorEmoji || glyphFamily && p.isColorEmojiFont && p.isColorEmojiFont(glyphFamily);
|
|
1113
|
+
if (isColorEmoji && glyph.char) {
|
|
1114
|
+
textOps.push({
|
|
1115
|
+
op: "DrawColorEmoji",
|
|
1116
|
+
char: glyph.char,
|
|
1117
|
+
x: glyphX,
|
|
1118
|
+
y: glyphY,
|
|
1119
|
+
fontSize: p.font.size,
|
|
1120
|
+
fontFamily: glyphFamily || "NotoColorEmoji"
|
|
1121
|
+
});
|
|
1122
|
+
xCursor += glyph.xAdvance;
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
977
1125
|
const path = await p.glyphPathProvider(glyph.id, glyph.fontDesc);
|
|
978
1126
|
if (!path || path === "M 0 0") {
|
|
979
1127
|
xCursor += glyph.xAdvance;
|
|
980
1128
|
continue;
|
|
981
1129
|
}
|
|
982
|
-
const glyphX = xCursor + glyph.xOffset;
|
|
983
|
-
const glyphY = baselineY + glyph.yOffset;
|
|
984
1130
|
const pb = computePathBounds(path);
|
|
985
1131
|
const x1 = glyphX + scale * pb.x;
|
|
986
1132
|
const x2 = glyphX + scale * (pb.x + pb.w);
|
|
@@ -1803,14 +1949,14 @@ async function createNodePainter(opts) {
|
|
|
1803
1949
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1804
1950
|
offscreenCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1805
1951
|
const hasBackground = !!(op.bg && op.bg.color);
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1952
|
+
const hasRoundedBackground = hasBackground && op.bg && op.bg.radius && op.bg.radius > 0;
|
|
1953
|
+
needsAlphaExtraction = !!hasRoundedBackground;
|
|
1954
|
+
ctx.clearRect(0, 0, op.width, op.height);
|
|
1955
|
+
offscreenCtx.clearRect(0, 0, op.width, op.height);
|
|
1956
|
+
if (hasBackground && op.bg && op.bg.color) {
|
|
1809
1957
|
const { color, opacity, radius } = op.bg;
|
|
1810
1958
|
const c = parseHex6(color, opacity);
|
|
1811
|
-
|
|
1812
|
-
if (radius && radius > 0) {
|
|
1813
|
-
needsAlphaExtraction = true;
|
|
1959
|
+
if (hasRoundedBackground) {
|
|
1814
1960
|
ctx.save();
|
|
1815
1961
|
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1816
1962
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
@@ -1832,18 +1978,9 @@ async function createNodePainter(opts) {
|
|
|
1832
1978
|
offscreenCtx.fill();
|
|
1833
1979
|
offscreenCtx.restore();
|
|
1834
1980
|
} else {
|
|
1981
|
+
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1835
1982
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
1836
1983
|
}
|
|
1837
|
-
} else {
|
|
1838
|
-
ctx.clearRect(0, 0, op.width, op.height);
|
|
1839
|
-
ctx.save();
|
|
1840
|
-
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1841
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
1842
|
-
ctx.restore();
|
|
1843
|
-
offscreenCtx.save();
|
|
1844
|
-
offscreenCtx.fillStyle = "rgb(0, 0, 0)";
|
|
1845
|
-
offscreenCtx.fillRect(0, 0, op.width, op.height);
|
|
1846
|
-
offscreenCtx.restore();
|
|
1847
1984
|
}
|
|
1848
1985
|
continue;
|
|
1849
1986
|
}
|
|
@@ -1955,6 +2092,97 @@ async function createNodePainter(opts) {
|
|
|
1955
2092
|
});
|
|
1956
2093
|
continue;
|
|
1957
2094
|
}
|
|
2095
|
+
if (op.op === "DrawColorEmoji") {
|
|
2096
|
+
renderToBoth((context) => {
|
|
2097
|
+
context.save();
|
|
2098
|
+
context.font = `${op.fontSize}px "${op.fontFamily}"`;
|
|
2099
|
+
context.textBaseline = "alphabetic";
|
|
2100
|
+
context.fillText(op.char, op.x, op.y);
|
|
2101
|
+
context.restore();
|
|
2102
|
+
});
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
if (op.op === "Ellipse") {
|
|
2106
|
+
renderToBoth((context) => {
|
|
2107
|
+
context.save();
|
|
2108
|
+
const bbox = op.gradientBBox ?? {
|
|
2109
|
+
x: op.cx - op.rx,
|
|
2110
|
+
y: op.cy - op.ry,
|
|
2111
|
+
w: op.rx * 2,
|
|
2112
|
+
h: op.ry * 2
|
|
2113
|
+
};
|
|
2114
|
+
const fill = makeGradientFromBBox(context, op.fill, bbox);
|
|
2115
|
+
context.fillStyle = fill;
|
|
2116
|
+
context.beginPath();
|
|
2117
|
+
const rotation = (op.rotation ?? 0) * Math.PI / 180;
|
|
2118
|
+
context.ellipse(op.cx, op.cy, op.rx, op.ry, rotation, 0, Math.PI * 2);
|
|
2119
|
+
context.fill();
|
|
2120
|
+
context.restore();
|
|
2121
|
+
});
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
if (op.op === "EllipseStroke") {
|
|
2125
|
+
renderToBoth((context) => {
|
|
2126
|
+
context.save();
|
|
2127
|
+
applyStrokeSpec(context, op.stroke);
|
|
2128
|
+
context.beginPath();
|
|
2129
|
+
const rotation = (op.rotation ?? 0) * Math.PI / 180;
|
|
2130
|
+
context.ellipse(op.cx, op.cy, op.rx, op.ry, rotation, 0, Math.PI * 2);
|
|
2131
|
+
context.stroke();
|
|
2132
|
+
context.restore();
|
|
2133
|
+
});
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (op.op === "Polygon") {
|
|
2137
|
+
renderToBoth((context) => {
|
|
2138
|
+
context.save();
|
|
2139
|
+
const bbox = op.gradientBBox ?? computePointsBounds(op.points);
|
|
2140
|
+
const fill = makeGradientFromBBox(context, op.fill, bbox);
|
|
2141
|
+
context.fillStyle = fill;
|
|
2142
|
+
context.beginPath();
|
|
2143
|
+
drawPolygonPath(context, op.points, op.closed ?? true);
|
|
2144
|
+
context.fill();
|
|
2145
|
+
context.restore();
|
|
2146
|
+
});
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (op.op === "PolygonStroke") {
|
|
2150
|
+
renderToBoth((context) => {
|
|
2151
|
+
context.save();
|
|
2152
|
+
applyStrokeSpec(context, op.stroke);
|
|
2153
|
+
context.beginPath();
|
|
2154
|
+
drawPolygonPath(context, op.points, op.closed ?? true);
|
|
2155
|
+
context.stroke();
|
|
2156
|
+
context.restore();
|
|
2157
|
+
});
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
if (op.op === "ShapePath") {
|
|
2161
|
+
renderToBoth((context) => {
|
|
2162
|
+
context.save();
|
|
2163
|
+
context.translate(op.x, op.y);
|
|
2164
|
+
const bbox = op.gradientBBox ?? computePathBounds2(op.path);
|
|
2165
|
+
const fill = makeGradientFromBBox(context, op.fill, bbox);
|
|
2166
|
+
context.fillStyle = fill;
|
|
2167
|
+
context.beginPath();
|
|
2168
|
+
drawSvgPathOnCtx(context, op.path);
|
|
2169
|
+
context.fill();
|
|
2170
|
+
context.restore();
|
|
2171
|
+
});
|
|
2172
|
+
continue;
|
|
2173
|
+
}
|
|
2174
|
+
if (op.op === "ShapePathStroke") {
|
|
2175
|
+
renderToBoth((context) => {
|
|
2176
|
+
context.save();
|
|
2177
|
+
context.translate(op.x, op.y);
|
|
2178
|
+
applyStrokeSpec(context, op.stroke);
|
|
2179
|
+
context.beginPath();
|
|
2180
|
+
drawSvgPathOnCtx(context, op.path);
|
|
2181
|
+
context.stroke();
|
|
2182
|
+
context.restore();
|
|
2183
|
+
});
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
1958
2186
|
}
|
|
1959
2187
|
if (needsAlphaExtraction) {
|
|
1960
2188
|
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -2172,6 +2400,48 @@ function roundRectPath(ctx, x, y, w, h, r) {
|
|
|
2172
2400
|
function tokenizePath2(d) {
|
|
2173
2401
|
return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
|
|
2174
2402
|
}
|
|
2403
|
+
function applyStrokeSpec(ctx, stroke) {
|
|
2404
|
+
const c = parseHex6(stroke.color, stroke.opacity);
|
|
2405
|
+
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
2406
|
+
ctx.lineWidth = stroke.width;
|
|
2407
|
+
ctx.lineCap = stroke.lineCap ?? "butt";
|
|
2408
|
+
ctx.lineJoin = stroke.lineJoin ?? "miter";
|
|
2409
|
+
if (stroke.dashArray && stroke.dashArray.length > 0) {
|
|
2410
|
+
ctx.setLineDash(stroke.dashArray);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
function computePointsBounds(points) {
|
|
2414
|
+
if (points.length === 0) {
|
|
2415
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
2416
|
+
}
|
|
2417
|
+
let minX = points[0].x;
|
|
2418
|
+
let minY = points[0].y;
|
|
2419
|
+
let maxX = points[0].x;
|
|
2420
|
+
let maxY = points[0].y;
|
|
2421
|
+
for (let i = 1; i < points.length; i++) {
|
|
2422
|
+
const p = points[i];
|
|
2423
|
+
if (p.x < minX) minX = p.x;
|
|
2424
|
+
if (p.y < minY) minY = p.y;
|
|
2425
|
+
if (p.x > maxX) maxX = p.x;
|
|
2426
|
+
if (p.y > maxY) maxY = p.y;
|
|
2427
|
+
}
|
|
2428
|
+
return {
|
|
2429
|
+
x: minX,
|
|
2430
|
+
y: minY,
|
|
2431
|
+
w: maxX - minX,
|
|
2432
|
+
h: maxY - minY
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function drawPolygonPath(ctx, points, closed) {
|
|
2436
|
+
if (points.length === 0) return;
|
|
2437
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
2438
|
+
for (let i = 1; i < points.length; i++) {
|
|
2439
|
+
ctx.lineTo(points[i].x, points[i].y);
|
|
2440
|
+
}
|
|
2441
|
+
if (closed) {
|
|
2442
|
+
ctx.closePath();
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2175
2445
|
|
|
2176
2446
|
// src/io/node.ts
|
|
2177
2447
|
import { readFile } from "fs/promises";
|
|
@@ -2420,7 +2690,1150 @@ var isGlyphFill2 = (op) => {
|
|
|
2420
2690
|
return op.op === "FillPath" && op.isShadow !== true;
|
|
2421
2691
|
};
|
|
2422
2692
|
|
|
2693
|
+
// src/core/shape-generators.ts
|
|
2694
|
+
var KAPPA = 0.5522847498307936;
|
|
2695
|
+
var DEG_TO_RAD = Math.PI / 180;
|
|
2696
|
+
function generateShapePath(params) {
|
|
2697
|
+
switch (params.shape) {
|
|
2698
|
+
case "rectangle":
|
|
2699
|
+
return generateRectanglePath(params);
|
|
2700
|
+
case "circle":
|
|
2701
|
+
return generateCirclePath(params);
|
|
2702
|
+
case "ellipse":
|
|
2703
|
+
return generateEllipsePath(params);
|
|
2704
|
+
case "line":
|
|
2705
|
+
return generateLinePath(params);
|
|
2706
|
+
case "triangle":
|
|
2707
|
+
return generateTrianglePath(params);
|
|
2708
|
+
case "polygon":
|
|
2709
|
+
return generatePolygonPath(params);
|
|
2710
|
+
case "star":
|
|
2711
|
+
return generateStarPath(params);
|
|
2712
|
+
case "arrow":
|
|
2713
|
+
return generateArrowPath(params);
|
|
2714
|
+
case "heart":
|
|
2715
|
+
return generateHeartPath(params);
|
|
2716
|
+
case "cross":
|
|
2717
|
+
return generateCrossPath(params);
|
|
2718
|
+
case "ring":
|
|
2719
|
+
return generateRingPath(params);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
function generateRectanglePath(params) {
|
|
2723
|
+
const { width, height, cornerRadius = 0 } = params;
|
|
2724
|
+
if (cornerRadius === 0) {
|
|
2725
|
+
return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
|
|
2726
|
+
}
|
|
2727
|
+
const r = Math.min(cornerRadius, width / 2, height / 2);
|
|
2728
|
+
return [
|
|
2729
|
+
`M ${r} 0`,
|
|
2730
|
+
`L ${width - r} 0`,
|
|
2731
|
+
`Q ${width} 0 ${width} ${r}`,
|
|
2732
|
+
`L ${width} ${height - r}`,
|
|
2733
|
+
`Q ${width} ${height} ${width - r} ${height}`,
|
|
2734
|
+
`L ${r} ${height}`,
|
|
2735
|
+
`Q 0 ${height} 0 ${height - r}`,
|
|
2736
|
+
`L 0 ${r}`,
|
|
2737
|
+
`Q 0 0 ${r} 0`,
|
|
2738
|
+
"Z"
|
|
2739
|
+
].join(" ");
|
|
2740
|
+
}
|
|
2741
|
+
function generateCirclePath(params) {
|
|
2742
|
+
const { radius } = params;
|
|
2743
|
+
return generateEllipsePath({ radiusX: radius, radiusY: radius });
|
|
2744
|
+
}
|
|
2745
|
+
function generateEllipsePath(params) {
|
|
2746
|
+
const { radiusX, radiusY } = params;
|
|
2747
|
+
const kx = radiusX * KAPPA;
|
|
2748
|
+
const ky = radiusY * KAPPA;
|
|
2749
|
+
return [
|
|
2750
|
+
`M ${radiusX} 0`,
|
|
2751
|
+
`C ${radiusX} ${ky} ${kx} ${radiusY} 0 ${radiusY}`,
|
|
2752
|
+
`C ${-kx} ${radiusY} ${-radiusX} ${ky} ${-radiusX} 0`,
|
|
2753
|
+
`C ${-radiusX} ${-ky} ${-kx} ${-radiusY} 0 ${-radiusY}`,
|
|
2754
|
+
`C ${kx} ${-radiusY} ${radiusX} ${-ky} ${radiusX} 0`,
|
|
2755
|
+
"Z"
|
|
2756
|
+
].join(" ");
|
|
2757
|
+
}
|
|
2758
|
+
function generateLinePath(params) {
|
|
2759
|
+
const { length, thickness } = params;
|
|
2760
|
+
const halfThickness = thickness / 2;
|
|
2761
|
+
return [
|
|
2762
|
+
`M 0 ${-halfThickness}`,
|
|
2763
|
+
`L ${length} ${-halfThickness}`,
|
|
2764
|
+
`L ${length} ${halfThickness}`,
|
|
2765
|
+
`L 0 ${halfThickness}`,
|
|
2766
|
+
"Z"
|
|
2767
|
+
].join(" ");
|
|
2768
|
+
}
|
|
2769
|
+
function generateTrianglePath(params) {
|
|
2770
|
+
const { width, height } = params;
|
|
2771
|
+
const halfWidth = width / 2;
|
|
2772
|
+
return `M 0 ${height} L ${halfWidth} 0 L ${width} ${height} Z`;
|
|
2773
|
+
}
|
|
2774
|
+
function generatePolygonPath(params) {
|
|
2775
|
+
const { sides, radius } = params;
|
|
2776
|
+
const points = generatePolygonPoints(sides, radius);
|
|
2777
|
+
return pointsToPath(points, true);
|
|
2778
|
+
}
|
|
2779
|
+
function generatePolygonPoints(sides, radius) {
|
|
2780
|
+
const points = [];
|
|
2781
|
+
const angleStep = 2 * Math.PI / sides;
|
|
2782
|
+
const startAngle = -Math.PI / 2;
|
|
2783
|
+
for (let i = 0; i < sides; i++) {
|
|
2784
|
+
const angle = startAngle + i * angleStep;
|
|
2785
|
+
points.push({
|
|
2786
|
+
x: radius * Math.cos(angle),
|
|
2787
|
+
y: radius * Math.sin(angle)
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
return points;
|
|
2791
|
+
}
|
|
2792
|
+
function generateStarPath(params) {
|
|
2793
|
+
const { points, outerRadius, innerRadius } = params;
|
|
2794
|
+
const starPoints = generateStarPoints(points, outerRadius, innerRadius);
|
|
2795
|
+
return pointsToPath(starPoints, true);
|
|
2796
|
+
}
|
|
2797
|
+
function generateStarPoints(pointCount, outerRadius, innerRadius) {
|
|
2798
|
+
const points = [];
|
|
2799
|
+
const totalPoints = pointCount * 2;
|
|
2800
|
+
const angleStep = Math.PI / pointCount;
|
|
2801
|
+
const startAngle = -Math.PI / 2;
|
|
2802
|
+
for (let i = 0; i < totalPoints; i++) {
|
|
2803
|
+
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
2804
|
+
const angle = startAngle + i * angleStep;
|
|
2805
|
+
points.push({
|
|
2806
|
+
x: radius * Math.cos(angle),
|
|
2807
|
+
y: radius * Math.sin(angle)
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
return points;
|
|
2811
|
+
}
|
|
2812
|
+
function generateArrowPath(params) {
|
|
2813
|
+
const { length, headWidth, headLength, shaftWidth } = params;
|
|
2814
|
+
const halfShaft = shaftWidth / 2;
|
|
2815
|
+
const halfHead = headWidth / 2;
|
|
2816
|
+
const shaftLength = length - headLength;
|
|
2817
|
+
return [
|
|
2818
|
+
`M 0 ${-halfShaft}`,
|
|
2819
|
+
`L ${shaftLength} ${-halfShaft}`,
|
|
2820
|
+
`L ${shaftLength} ${-halfHead}`,
|
|
2821
|
+
`L ${length} 0`,
|
|
2822
|
+
`L ${shaftLength} ${halfHead}`,
|
|
2823
|
+
`L ${shaftLength} ${halfShaft}`,
|
|
2824
|
+
`L 0 ${halfShaft}`,
|
|
2825
|
+
"Z"
|
|
2826
|
+
].join(" ");
|
|
2827
|
+
}
|
|
2828
|
+
function generateHeartPath(params) {
|
|
2829
|
+
const { size } = params;
|
|
2830
|
+
const s = size / 2;
|
|
2831
|
+
return [
|
|
2832
|
+
`M 0 ${s * 0.3}`,
|
|
2833
|
+
`C 0 ${-s * 0.3} ${-s} ${-s * 0.3} ${-s} ${s * 0.3}`,
|
|
2834
|
+
`C ${-s} ${s * 0.7} 0 ${s} 0 ${s * 1.2}`,
|
|
2835
|
+
`C 0 ${s} ${s} ${s * 0.7} ${s} ${s * 0.3}`,
|
|
2836
|
+
`C ${s} ${-s * 0.3} 0 ${-s * 0.3} 0 ${s * 0.3}`,
|
|
2837
|
+
"Z"
|
|
2838
|
+
].join(" ");
|
|
2839
|
+
}
|
|
2840
|
+
function generateCrossPath(params) {
|
|
2841
|
+
const { width, height, thickness } = params;
|
|
2842
|
+
const halfThickness = thickness / 2;
|
|
2843
|
+
const halfWidth = width / 2;
|
|
2844
|
+
const halfHeight = height / 2;
|
|
2845
|
+
return [
|
|
2846
|
+
`M ${-halfThickness} ${-halfHeight}`,
|
|
2847
|
+
`L ${halfThickness} ${-halfHeight}`,
|
|
2848
|
+
`L ${halfThickness} ${-halfThickness}`,
|
|
2849
|
+
`L ${halfWidth} ${-halfThickness}`,
|
|
2850
|
+
`L ${halfWidth} ${halfThickness}`,
|
|
2851
|
+
`L ${halfThickness} ${halfThickness}`,
|
|
2852
|
+
`L ${halfThickness} ${halfHeight}`,
|
|
2853
|
+
`L ${-halfThickness} ${halfHeight}`,
|
|
2854
|
+
`L ${-halfThickness} ${halfThickness}`,
|
|
2855
|
+
`L ${-halfWidth} ${halfThickness}`,
|
|
2856
|
+
`L ${-halfWidth} ${-halfThickness}`,
|
|
2857
|
+
`L ${-halfThickness} ${-halfThickness}`,
|
|
2858
|
+
"Z"
|
|
2859
|
+
].join(" ");
|
|
2860
|
+
}
|
|
2861
|
+
function generateRingPath(params) {
|
|
2862
|
+
const { outerRadius, innerRadius } = params;
|
|
2863
|
+
const outerPath = generateCirclePath({ radius: outerRadius });
|
|
2864
|
+
const innerPath = generateCirclePath({ radius: innerRadius });
|
|
2865
|
+
return `${outerPath} ${reverseWindingOrder(innerPath)}`;
|
|
2866
|
+
}
|
|
2867
|
+
function pointsToPath(points, closed = true) {
|
|
2868
|
+
if (points.length === 0) return "";
|
|
2869
|
+
const commands = [`M ${points[0].x} ${points[0].y}`];
|
|
2870
|
+
for (let i = 1; i < points.length; i++) {
|
|
2871
|
+
commands.push(`L ${points[i].x} ${points[i].y}`);
|
|
2872
|
+
}
|
|
2873
|
+
if (closed) {
|
|
2874
|
+
commands.push("Z");
|
|
2875
|
+
}
|
|
2876
|
+
return commands.join(" ");
|
|
2877
|
+
}
|
|
2878
|
+
function reverseWindingOrder(path) {
|
|
2879
|
+
const commands = parsePathCommands(path);
|
|
2880
|
+
const reversed = [];
|
|
2881
|
+
const points = [];
|
|
2882
|
+
let currentX = 0;
|
|
2883
|
+
let currentY = 0;
|
|
2884
|
+
for (const cmd of commands) {
|
|
2885
|
+
if (cmd.type === "M" || cmd.type === "L") {
|
|
2886
|
+
points.push({ x: cmd.x, y: cmd.y });
|
|
2887
|
+
currentX = cmd.x;
|
|
2888
|
+
currentY = cmd.y;
|
|
2889
|
+
} else if (cmd.type === "C") {
|
|
2890
|
+
points.push({ x: cmd.x, y: cmd.y });
|
|
2891
|
+
currentX = cmd.x;
|
|
2892
|
+
currentY = cmd.y;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
if (points.length === 0) return path;
|
|
2896
|
+
points.reverse();
|
|
2897
|
+
reversed.push(`M ${points[0].x} ${points[0].y}`);
|
|
2898
|
+
for (let i = 1; i < points.length; i++) {
|
|
2899
|
+
reversed.push(`L ${points[i].x} ${points[i].y}`);
|
|
2900
|
+
}
|
|
2901
|
+
reversed.push("Z");
|
|
2902
|
+
return reversed.join(" ");
|
|
2903
|
+
}
|
|
2904
|
+
function parsePathCommands(path) {
|
|
2905
|
+
const commands = [];
|
|
2906
|
+
const regex = /([MLCQZ])\s*([^MLCQZ]*)/gi;
|
|
2907
|
+
let match;
|
|
2908
|
+
while ((match = regex.exec(path)) !== null) {
|
|
2909
|
+
const type = match[1].toUpperCase();
|
|
2910
|
+
const args = match[2].trim().split(/[\s,]+/).filter((s) => s.length > 0).map(parseFloat);
|
|
2911
|
+
if (type === "M" || type === "L") {
|
|
2912
|
+
commands.push({ type, x: args[0], y: args[1] });
|
|
2913
|
+
} else if (type === "C") {
|
|
2914
|
+
commands.push({
|
|
2915
|
+
type,
|
|
2916
|
+
x1: args[0],
|
|
2917
|
+
y1: args[1],
|
|
2918
|
+
x2: args[2],
|
|
2919
|
+
y2: args[3],
|
|
2920
|
+
x: args[4],
|
|
2921
|
+
y: args[5]
|
|
2922
|
+
});
|
|
2923
|
+
} else if (type === "Q") {
|
|
2924
|
+
commands.push({
|
|
2925
|
+
type,
|
|
2926
|
+
x1: args[0],
|
|
2927
|
+
y1: args[1],
|
|
2928
|
+
x: args[2],
|
|
2929
|
+
y: args[3]
|
|
2930
|
+
});
|
|
2931
|
+
} else if (type === "Z") {
|
|
2932
|
+
commands.push({ type });
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
return commands;
|
|
2936
|
+
}
|
|
2937
|
+
function computePathBounds3(path) {
|
|
2938
|
+
const commands = parsePathCommands(path);
|
|
2939
|
+
let minX = Infinity;
|
|
2940
|
+
let minY = Infinity;
|
|
2941
|
+
let maxX = -Infinity;
|
|
2942
|
+
let maxY = -Infinity;
|
|
2943
|
+
const updateBounds = (x, y) => {
|
|
2944
|
+
minX = Math.min(minX, x);
|
|
2945
|
+
minY = Math.min(minY, y);
|
|
2946
|
+
maxX = Math.max(maxX, x);
|
|
2947
|
+
maxY = Math.max(maxY, y);
|
|
2948
|
+
};
|
|
2949
|
+
for (const cmd of commands) {
|
|
2950
|
+
if (cmd.x !== void 0 && cmd.y !== void 0) {
|
|
2951
|
+
updateBounds(cmd.x, cmd.y);
|
|
2952
|
+
}
|
|
2953
|
+
if (cmd.x1 !== void 0 && cmd.y1 !== void 0) {
|
|
2954
|
+
updateBounds(cmd.x1, cmd.y1);
|
|
2955
|
+
}
|
|
2956
|
+
if (cmd.x2 !== void 0 && cmd.y2 !== void 0) {
|
|
2957
|
+
updateBounds(cmd.x2, cmd.y2);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
if (minX === Infinity) {
|
|
2961
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
x: minX,
|
|
2965
|
+
y: minY,
|
|
2966
|
+
w: maxX - minX,
|
|
2967
|
+
h: maxY - minY
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
function translatePath(path, dx, dy) {
|
|
2971
|
+
return path.replace(
|
|
2972
|
+
/(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
|
|
2973
|
+
(match, x, y) => {
|
|
2974
|
+
const newX = parseFloat(x) + dx;
|
|
2975
|
+
const newY = parseFloat(y) + dy;
|
|
2976
|
+
return `${newX} ${newY}`;
|
|
2977
|
+
}
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
function scalePath(path, sx, sy = sx) {
|
|
2981
|
+
return path.replace(
|
|
2982
|
+
/(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
|
|
2983
|
+
(match, x, y) => {
|
|
2984
|
+
const newX = parseFloat(x) * sx;
|
|
2985
|
+
const newY = parseFloat(y) * sy;
|
|
2986
|
+
return `${newX} ${newY}`;
|
|
2987
|
+
}
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
function rotatePath(path, angleDegrees) {
|
|
2991
|
+
const angleRadians = angleDegrees * DEG_TO_RAD;
|
|
2992
|
+
const cos = Math.cos(angleRadians);
|
|
2993
|
+
const sin = Math.sin(angleRadians);
|
|
2994
|
+
return path.replace(
|
|
2995
|
+
/(-?\d*\.?\d+(?:e[-+]?\d+)?)\s+(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi,
|
|
2996
|
+
(match, x, y) => {
|
|
2997
|
+
const px = parseFloat(x);
|
|
2998
|
+
const py = parseFloat(y);
|
|
2999
|
+
const newX = px * cos - py * sin;
|
|
3000
|
+
const newY = px * sin + py * cos;
|
|
3001
|
+
return `${newX} ${newY}`;
|
|
3002
|
+
}
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
function centerPath(path) {
|
|
3006
|
+
const bounds = computePathBounds3(path);
|
|
3007
|
+
const cx = bounds.x + bounds.w / 2;
|
|
3008
|
+
const cy = bounds.y + bounds.h / 2;
|
|
3009
|
+
return translatePath(path, -cx, -cy);
|
|
3010
|
+
}
|
|
3011
|
+
function normalizePathToSize(path, targetWidth, targetHeight) {
|
|
3012
|
+
const bounds = computePathBounds3(path);
|
|
3013
|
+
if (bounds.w === 0 || bounds.h === 0) return path;
|
|
3014
|
+
const scaleX = targetWidth / bounds.w;
|
|
3015
|
+
const scaleY = targetHeight / bounds.h;
|
|
3016
|
+
const scale = Math.min(scaleX, scaleY);
|
|
3017
|
+
let normalized = translatePath(path, -bounds.x, -bounds.y);
|
|
3018
|
+
normalized = scalePath(normalized, scale);
|
|
3019
|
+
return normalized;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
// src/core/svg-path-utils.ts
|
|
3023
|
+
var TAU = Math.PI * 2;
|
|
3024
|
+
var PATH_COMMAND_REGEX = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
|
|
3025
|
+
var NUMBER_REGEX = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g;
|
|
3026
|
+
function parseSvgPath(pathData) {
|
|
3027
|
+
const commands = [];
|
|
3028
|
+
let currentX = 0;
|
|
3029
|
+
let currentY = 0;
|
|
3030
|
+
let startX = 0;
|
|
3031
|
+
let startY = 0;
|
|
3032
|
+
let lastCommand = null;
|
|
3033
|
+
let lastControlX = 0;
|
|
3034
|
+
let lastControlY = 0;
|
|
3035
|
+
const matches = pathData.matchAll(PATH_COMMAND_REGEX);
|
|
3036
|
+
for (const match of matches) {
|
|
3037
|
+
const commandType = match[1];
|
|
3038
|
+
const argsString = match[2];
|
|
3039
|
+
const args = extractNumbers(argsString);
|
|
3040
|
+
const isRelative = commandType === commandType.toLowerCase();
|
|
3041
|
+
switch (commandType.toUpperCase()) {
|
|
3042
|
+
case "M": {
|
|
3043
|
+
const coords = parseCoordinatePairs(args, isRelative, currentX, currentY);
|
|
3044
|
+
for (let i = 0; i < coords.length; i++) {
|
|
3045
|
+
const { x, y } = coords[i];
|
|
3046
|
+
if (i === 0) {
|
|
3047
|
+
commands.push({ type: "M", x, y });
|
|
3048
|
+
startX = x;
|
|
3049
|
+
startY = y;
|
|
3050
|
+
} else {
|
|
3051
|
+
commands.push({ type: "L", x, y });
|
|
3052
|
+
}
|
|
3053
|
+
currentX = x;
|
|
3054
|
+
currentY = y;
|
|
3055
|
+
}
|
|
3056
|
+
break;
|
|
3057
|
+
}
|
|
3058
|
+
case "L": {
|
|
3059
|
+
const coords = parseCoordinatePairs(args, isRelative, currentX, currentY);
|
|
3060
|
+
for (const { x, y } of coords) {
|
|
3061
|
+
commands.push({ type: "L", x, y });
|
|
3062
|
+
currentX = x;
|
|
3063
|
+
currentY = y;
|
|
3064
|
+
}
|
|
3065
|
+
break;
|
|
3066
|
+
}
|
|
3067
|
+
case "H": {
|
|
3068
|
+
for (const arg of args) {
|
|
3069
|
+
const x = isRelative ? currentX + arg : arg;
|
|
3070
|
+
commands.push({ type: "L", x, y: currentY });
|
|
3071
|
+
currentX = x;
|
|
3072
|
+
}
|
|
3073
|
+
break;
|
|
3074
|
+
}
|
|
3075
|
+
case "V": {
|
|
3076
|
+
for (const arg of args) {
|
|
3077
|
+
const y = isRelative ? currentY + arg : arg;
|
|
3078
|
+
commands.push({ type: "L", x: currentX, y });
|
|
3079
|
+
currentY = y;
|
|
3080
|
+
}
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
case "C": {
|
|
3084
|
+
for (let i = 0; i + 5 < args.length + 1; i += 6) {
|
|
3085
|
+
let x1 = args[i];
|
|
3086
|
+
let y1 = args[i + 1];
|
|
3087
|
+
let x2 = args[i + 2];
|
|
3088
|
+
let y2 = args[i + 3];
|
|
3089
|
+
let x = args[i + 4];
|
|
3090
|
+
let y = args[i + 5];
|
|
3091
|
+
if (isRelative) {
|
|
3092
|
+
x1 += currentX;
|
|
3093
|
+
y1 += currentY;
|
|
3094
|
+
x2 += currentX;
|
|
3095
|
+
y2 += currentY;
|
|
3096
|
+
x += currentX;
|
|
3097
|
+
y += currentY;
|
|
3098
|
+
}
|
|
3099
|
+
commands.push({ type: "C", x1, y1, x2, y2, x, y });
|
|
3100
|
+
lastControlX = x2;
|
|
3101
|
+
lastControlY = y2;
|
|
3102
|
+
currentX = x;
|
|
3103
|
+
currentY = y;
|
|
3104
|
+
}
|
|
3105
|
+
break;
|
|
3106
|
+
}
|
|
3107
|
+
case "S": {
|
|
3108
|
+
for (let i = 0; i + 3 < args.length + 1; i += 4) {
|
|
3109
|
+
let x1;
|
|
3110
|
+
let y1;
|
|
3111
|
+
if (lastCommand === "C" || lastCommand === "S") {
|
|
3112
|
+
x1 = 2 * currentX - lastControlX;
|
|
3113
|
+
y1 = 2 * currentY - lastControlY;
|
|
3114
|
+
} else {
|
|
3115
|
+
x1 = currentX;
|
|
3116
|
+
y1 = currentY;
|
|
3117
|
+
}
|
|
3118
|
+
let x2 = args[i];
|
|
3119
|
+
let y2 = args[i + 1];
|
|
3120
|
+
let x = args[i + 2];
|
|
3121
|
+
let y = args[i + 3];
|
|
3122
|
+
if (isRelative) {
|
|
3123
|
+
x2 += currentX;
|
|
3124
|
+
y2 += currentY;
|
|
3125
|
+
x += currentX;
|
|
3126
|
+
y += currentY;
|
|
3127
|
+
}
|
|
3128
|
+
commands.push({ type: "C", x1, y1, x2, y2, x, y });
|
|
3129
|
+
lastControlX = x2;
|
|
3130
|
+
lastControlY = y2;
|
|
3131
|
+
currentX = x;
|
|
3132
|
+
currentY = y;
|
|
3133
|
+
lastCommand = "S";
|
|
3134
|
+
}
|
|
3135
|
+
break;
|
|
3136
|
+
}
|
|
3137
|
+
case "Q": {
|
|
3138
|
+
for (let i = 0; i + 3 < args.length + 1; i += 4) {
|
|
3139
|
+
let x1 = args[i];
|
|
3140
|
+
let y1 = args[i + 1];
|
|
3141
|
+
let x = args[i + 2];
|
|
3142
|
+
let y = args[i + 3];
|
|
3143
|
+
if (isRelative) {
|
|
3144
|
+
x1 += currentX;
|
|
3145
|
+
y1 += currentY;
|
|
3146
|
+
x += currentX;
|
|
3147
|
+
y += currentY;
|
|
3148
|
+
}
|
|
3149
|
+
commands.push({ type: "Q", x1, y1, x, y });
|
|
3150
|
+
lastControlX = x1;
|
|
3151
|
+
lastControlY = y1;
|
|
3152
|
+
currentX = x;
|
|
3153
|
+
currentY = y;
|
|
3154
|
+
}
|
|
3155
|
+
break;
|
|
3156
|
+
}
|
|
3157
|
+
case "T": {
|
|
3158
|
+
for (let i = 0; i + 1 < args.length + 1; i += 2) {
|
|
3159
|
+
let x1;
|
|
3160
|
+
let y1;
|
|
3161
|
+
if (lastCommand === "Q" || lastCommand === "T") {
|
|
3162
|
+
x1 = 2 * currentX - lastControlX;
|
|
3163
|
+
y1 = 2 * currentY - lastControlY;
|
|
3164
|
+
} else {
|
|
3165
|
+
x1 = currentX;
|
|
3166
|
+
y1 = currentY;
|
|
3167
|
+
}
|
|
3168
|
+
let x = args[i];
|
|
3169
|
+
let y = args[i + 1];
|
|
3170
|
+
if (isRelative) {
|
|
3171
|
+
x += currentX;
|
|
3172
|
+
y += currentY;
|
|
3173
|
+
}
|
|
3174
|
+
commands.push({ type: "Q", x1, y1, x, y });
|
|
3175
|
+
lastControlX = x1;
|
|
3176
|
+
lastControlY = y1;
|
|
3177
|
+
currentX = x;
|
|
3178
|
+
currentY = y;
|
|
3179
|
+
lastCommand = "T";
|
|
3180
|
+
}
|
|
3181
|
+
break;
|
|
3182
|
+
}
|
|
3183
|
+
case "A": {
|
|
3184
|
+
for (let i = 0; i + 6 < args.length + 1; i += 7) {
|
|
3185
|
+
const rx = Math.abs(args[i]);
|
|
3186
|
+
const ry = Math.abs(args[i + 1]);
|
|
3187
|
+
const xAxisRotation = args[i + 2];
|
|
3188
|
+
const largeArcFlag = args[i + 3] !== 0;
|
|
3189
|
+
const sweepFlag = args[i + 4] !== 0;
|
|
3190
|
+
let x = args[i + 5];
|
|
3191
|
+
let y = args[i + 6];
|
|
3192
|
+
if (isRelative) {
|
|
3193
|
+
x += currentX;
|
|
3194
|
+
y += currentY;
|
|
3195
|
+
}
|
|
3196
|
+
commands.push({
|
|
3197
|
+
type: "A",
|
|
3198
|
+
rx,
|
|
3199
|
+
ry,
|
|
3200
|
+
xAxisRotation,
|
|
3201
|
+
largeArcFlag,
|
|
3202
|
+
sweepFlag,
|
|
3203
|
+
x,
|
|
3204
|
+
y
|
|
3205
|
+
});
|
|
3206
|
+
currentX = x;
|
|
3207
|
+
currentY = y;
|
|
3208
|
+
}
|
|
3209
|
+
break;
|
|
3210
|
+
}
|
|
3211
|
+
case "Z": {
|
|
3212
|
+
commands.push({ type: "Z" });
|
|
3213
|
+
currentX = startX;
|
|
3214
|
+
currentY = startY;
|
|
3215
|
+
break;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
lastCommand = commandType.toUpperCase();
|
|
3219
|
+
}
|
|
3220
|
+
return commands;
|
|
3221
|
+
}
|
|
3222
|
+
function normalizePath(commands) {
|
|
3223
|
+
const normalized = [];
|
|
3224
|
+
let currentX = 0;
|
|
3225
|
+
let currentY = 0;
|
|
3226
|
+
for (const cmd of commands) {
|
|
3227
|
+
switch (cmd.type) {
|
|
3228
|
+
case "M":
|
|
3229
|
+
case "L":
|
|
3230
|
+
case "C":
|
|
3231
|
+
case "Q":
|
|
3232
|
+
case "Z":
|
|
3233
|
+
normalized.push(cmd);
|
|
3234
|
+
if (cmd.type !== "Z") {
|
|
3235
|
+
currentX = cmd.x;
|
|
3236
|
+
currentY = cmd.y;
|
|
3237
|
+
}
|
|
3238
|
+
break;
|
|
3239
|
+
case "A": {
|
|
3240
|
+
const cubicCurves = arcToCubicBeziers(
|
|
3241
|
+
currentX,
|
|
3242
|
+
currentY,
|
|
3243
|
+
cmd.rx,
|
|
3244
|
+
cmd.ry,
|
|
3245
|
+
cmd.xAxisRotation,
|
|
3246
|
+
cmd.largeArcFlag,
|
|
3247
|
+
cmd.sweepFlag,
|
|
3248
|
+
cmd.x,
|
|
3249
|
+
cmd.y
|
|
3250
|
+
);
|
|
3251
|
+
normalized.push(...cubicCurves);
|
|
3252
|
+
currentX = cmd.x;
|
|
3253
|
+
currentY = cmd.y;
|
|
3254
|
+
break;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
return normalized;
|
|
3259
|
+
}
|
|
3260
|
+
function commandsToPathString(commands) {
|
|
3261
|
+
const parts = [];
|
|
3262
|
+
for (const cmd of commands) {
|
|
3263
|
+
switch (cmd.type) {
|
|
3264
|
+
case "M":
|
|
3265
|
+
parts.push(`M ${cmd.x} ${cmd.y}`);
|
|
3266
|
+
break;
|
|
3267
|
+
case "L":
|
|
3268
|
+
parts.push(`L ${cmd.x} ${cmd.y}`);
|
|
3269
|
+
break;
|
|
3270
|
+
case "C":
|
|
3271
|
+
parts.push(`C ${cmd.x1} ${cmd.y1} ${cmd.x2} ${cmd.y2} ${cmd.x} ${cmd.y}`);
|
|
3272
|
+
break;
|
|
3273
|
+
case "Q":
|
|
3274
|
+
parts.push(`Q ${cmd.x1} ${cmd.y1} ${cmd.x} ${cmd.y}`);
|
|
3275
|
+
break;
|
|
3276
|
+
case "Z":
|
|
3277
|
+
parts.push("Z");
|
|
3278
|
+
break;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
return parts.join(" ");
|
|
3282
|
+
}
|
|
3283
|
+
function normalizePathString(pathData) {
|
|
3284
|
+
const parsed = parseSvgPath(pathData);
|
|
3285
|
+
const normalized = normalizePath(parsed);
|
|
3286
|
+
return commandsToPathString(normalized);
|
|
3287
|
+
}
|
|
3288
|
+
function extractNumbers(str) {
|
|
3289
|
+
const matches = str.match(NUMBER_REGEX);
|
|
3290
|
+
return matches ? matches.map(parseFloat) : [];
|
|
3291
|
+
}
|
|
3292
|
+
function parseCoordinatePairs(args, isRelative, currentX, currentY) {
|
|
3293
|
+
const coords = [];
|
|
3294
|
+
for (let i = 0; i + 1 < args.length + 1; i += 2) {
|
|
3295
|
+
let x = args[i];
|
|
3296
|
+
let y = args[i + 1];
|
|
3297
|
+
if (isRelative && coords.length === 0) {
|
|
3298
|
+
x += currentX;
|
|
3299
|
+
y += currentY;
|
|
3300
|
+
} else if (isRelative) {
|
|
3301
|
+
x += coords[coords.length - 1].x;
|
|
3302
|
+
y += coords[coords.length - 1].y;
|
|
3303
|
+
}
|
|
3304
|
+
coords.push({ x, y });
|
|
3305
|
+
}
|
|
3306
|
+
return coords;
|
|
3307
|
+
}
|
|
3308
|
+
function arcToCubicBeziers(x1, y1, rx, ry, phi, largeArc, sweep, x2, y2) {
|
|
3309
|
+
if (rx === 0 || ry === 0) {
|
|
3310
|
+
return [{ type: "L", x: x2, y: y2 }];
|
|
3311
|
+
}
|
|
3312
|
+
if (x1 === x2 && y1 === y2) {
|
|
3313
|
+
return [];
|
|
3314
|
+
}
|
|
3315
|
+
const sinPhi = Math.sin(phi * Math.PI / 180);
|
|
3316
|
+
const cosPhi = Math.cos(phi * Math.PI / 180);
|
|
3317
|
+
const x1p = cosPhi * (x1 - x2) / 2 + sinPhi * (y1 - y2) / 2;
|
|
3318
|
+
const y1p = -sinPhi * (x1 - x2) / 2 + cosPhi * (y1 - y2) / 2;
|
|
3319
|
+
const x1pSq = x1p * x1p;
|
|
3320
|
+
const y1pSq = y1p * y1p;
|
|
3321
|
+
let rxSq = rx * rx;
|
|
3322
|
+
let rySq = ry * ry;
|
|
3323
|
+
const lambda = x1pSq / rxSq + y1pSq / rySq;
|
|
3324
|
+
if (lambda > 1) {
|
|
3325
|
+
const sqrtLambda = Math.sqrt(lambda);
|
|
3326
|
+
rx *= sqrtLambda;
|
|
3327
|
+
ry *= sqrtLambda;
|
|
3328
|
+
rxSq = rx * rx;
|
|
3329
|
+
rySq = ry * ry;
|
|
3330
|
+
}
|
|
3331
|
+
let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
|
|
3332
|
+
sq = sq < 0 ? 0 : sq;
|
|
3333
|
+
const coef = (largeArc === sweep ? -1 : 1) * Math.sqrt(sq);
|
|
3334
|
+
const cxp = coef * rx * y1p / ry;
|
|
3335
|
+
const cyp = -coef * ry * x1p / rx;
|
|
3336
|
+
const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
|
|
3337
|
+
const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
|
|
3338
|
+
const theta1 = vectorAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
3339
|
+
let dTheta = vectorAngle(
|
|
3340
|
+
(x1p - cxp) / rx,
|
|
3341
|
+
(y1p - cyp) / ry,
|
|
3342
|
+
(-x1p - cxp) / rx,
|
|
3343
|
+
(-y1p - cyp) / ry
|
|
3344
|
+
);
|
|
3345
|
+
if (!sweep && dTheta > 0) {
|
|
3346
|
+
dTheta -= TAU;
|
|
3347
|
+
} else if (sweep && dTheta < 0) {
|
|
3348
|
+
dTheta += TAU;
|
|
3349
|
+
}
|
|
3350
|
+
const segments = Math.max(Math.ceil(Math.abs(dTheta) / (Math.PI / 2)), 1);
|
|
3351
|
+
const segmentAngle = dTheta / segments;
|
|
3352
|
+
const curves = [];
|
|
3353
|
+
let currentTheta = theta1;
|
|
3354
|
+
for (let i = 0; i < segments; i++) {
|
|
3355
|
+
const nextTheta = currentTheta + segmentAngle;
|
|
3356
|
+
const curve = arcSegmentToCubic(cx, cy, rx, ry, phi, currentTheta, nextTheta);
|
|
3357
|
+
curves.push(curve);
|
|
3358
|
+
currentTheta = nextTheta;
|
|
3359
|
+
}
|
|
3360
|
+
return curves;
|
|
3361
|
+
}
|
|
3362
|
+
function vectorAngle(ux, uy, vx, vy) {
|
|
3363
|
+
const sign = ux * vy - uy * vx < 0 ? -1 : 1;
|
|
3364
|
+
const dot = ux * vx + uy * vy;
|
|
3365
|
+
const uLen = Math.sqrt(ux * ux + uy * uy);
|
|
3366
|
+
const vLen = Math.sqrt(vx * vx + vy * vy);
|
|
3367
|
+
let cos = dot / (uLen * vLen);
|
|
3368
|
+
cos = Math.max(-1, Math.min(1, cos));
|
|
3369
|
+
return sign * Math.acos(cos);
|
|
3370
|
+
}
|
|
3371
|
+
function arcSegmentToCubic(cx, cy, rx, ry, phi, theta1, theta2) {
|
|
3372
|
+
const sinPhi = Math.sin(phi * Math.PI / 180);
|
|
3373
|
+
const cosPhi = Math.cos(phi * Math.PI / 180);
|
|
3374
|
+
const dTheta = theta2 - theta1;
|
|
3375
|
+
const t = 4 / 3 * Math.tan(dTheta / 4);
|
|
3376
|
+
const x1 = Math.cos(theta1);
|
|
3377
|
+
const y1 = Math.sin(theta1);
|
|
3378
|
+
const x2 = Math.cos(theta2);
|
|
3379
|
+
const y2 = Math.sin(theta2);
|
|
3380
|
+
const dx1 = -t * y1;
|
|
3381
|
+
const dy1 = t * x1;
|
|
3382
|
+
const dx2 = t * y2;
|
|
3383
|
+
const dy2 = -t * x2;
|
|
3384
|
+
const transform = (px, py) => {
|
|
3385
|
+
const x = rx * px;
|
|
3386
|
+
const y = ry * py;
|
|
3387
|
+
return {
|
|
3388
|
+
x: cosPhi * x - sinPhi * y + cx,
|
|
3389
|
+
y: sinPhi * x + cosPhi * y + cy
|
|
3390
|
+
};
|
|
3391
|
+
};
|
|
3392
|
+
const p1 = transform(x1, y1);
|
|
3393
|
+
const cp1 = transform(x1 + dx1, y1 + dy1);
|
|
3394
|
+
const cp2 = transform(x2 + dx2, y2 + dy2);
|
|
3395
|
+
const p2 = transform(x2, y2);
|
|
3396
|
+
return {
|
|
3397
|
+
type: "C",
|
|
3398
|
+
x1: cp1.x,
|
|
3399
|
+
y1: cp1.y,
|
|
3400
|
+
x2: cp2.x,
|
|
3401
|
+
y2: cp2.y,
|
|
3402
|
+
x: p2.x,
|
|
3403
|
+
y: p2.y
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
function quadraticToCubic(x0, y0, qx, qy, x, y) {
|
|
3407
|
+
return {
|
|
3408
|
+
type: "C",
|
|
3409
|
+
x1: x0 + 2 / 3 * (qx - x0),
|
|
3410
|
+
y1: y0 + 2 / 3 * (qy - y0),
|
|
3411
|
+
x2: x + 2 / 3 * (qx - x),
|
|
3412
|
+
y2: y + 2 / 3 * (qy - y),
|
|
3413
|
+
x,
|
|
3414
|
+
y
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
// src/core/svg-import.ts
|
|
3419
|
+
var SVG_ATTRS_REGEX = /<svg([^>]*)>/i;
|
|
3420
|
+
var PATH_TAG_REGEX = /<path([^/>]*)\/?>/gi;
|
|
3421
|
+
var RECT_TAG_REGEX = /<rect([^/>]*)\/?>/gi;
|
|
3422
|
+
var CIRCLE_TAG_REGEX = /<circle([^/>]*)\/?>/gi;
|
|
3423
|
+
var ELLIPSE_TAG_REGEX = /<ellipse([^/>]*)\/?>/gi;
|
|
3424
|
+
var LINE_TAG_REGEX = /<line([^/>]*)\/?>/gi;
|
|
3425
|
+
var POLYLINE_TAG_REGEX = /<polyline([^/>]*)\/?>/gi;
|
|
3426
|
+
var POLYGON_TAG_REGEX = /<polygon([^/>]*)\/?>/gi;
|
|
3427
|
+
function extractAttribute(attrs, name) {
|
|
3428
|
+
const regex = new RegExp(`${name}\\s*=\\s*["']([^"']*)["']`, "i");
|
|
3429
|
+
const match = attrs.match(regex);
|
|
3430
|
+
return match ? match[1] : void 0;
|
|
3431
|
+
}
|
|
3432
|
+
function parseColor(color) {
|
|
3433
|
+
if (!color || color === "none" || color === "transparent") {
|
|
3434
|
+
return void 0;
|
|
3435
|
+
}
|
|
3436
|
+
color = color.trim();
|
|
3437
|
+
if (color.startsWith("#")) {
|
|
3438
|
+
if (color.length === 4) {
|
|
3439
|
+
const r = color[1];
|
|
3440
|
+
const g = color[2];
|
|
3441
|
+
const b = color[3];
|
|
3442
|
+
return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
|
|
3443
|
+
}
|
|
3444
|
+
return color.toUpperCase();
|
|
3445
|
+
}
|
|
3446
|
+
const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
|
|
3447
|
+
if (rgbMatch) {
|
|
3448
|
+
const r = parseInt(rgbMatch[1], 10);
|
|
3449
|
+
const g = parseInt(rgbMatch[2], 10);
|
|
3450
|
+
const b = parseInt(rgbMatch[3], 10);
|
|
3451
|
+
return rgbToHex(r, g, b);
|
|
3452
|
+
}
|
|
3453
|
+
const rgbaMatch = color.match(/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)/i);
|
|
3454
|
+
if (rgbaMatch) {
|
|
3455
|
+
const r = parseInt(rgbaMatch[1], 10);
|
|
3456
|
+
const g = parseInt(rgbaMatch[2], 10);
|
|
3457
|
+
const b = parseInt(rgbaMatch[3], 10);
|
|
3458
|
+
return rgbToHex(r, g, b);
|
|
3459
|
+
}
|
|
3460
|
+
const namedColors = {
|
|
3461
|
+
black: "#000000",
|
|
3462
|
+
white: "#FFFFFF",
|
|
3463
|
+
red: "#FF0000",
|
|
3464
|
+
green: "#008000",
|
|
3465
|
+
blue: "#0000FF",
|
|
3466
|
+
yellow: "#FFFF00",
|
|
3467
|
+
cyan: "#00FFFF",
|
|
3468
|
+
magenta: "#FF00FF",
|
|
3469
|
+
gray: "#808080",
|
|
3470
|
+
grey: "#808080",
|
|
3471
|
+
orange: "#FFA500",
|
|
3472
|
+
purple: "#800080",
|
|
3473
|
+
pink: "#FFC0CB",
|
|
3474
|
+
brown: "#A52A2A",
|
|
3475
|
+
navy: "#000080",
|
|
3476
|
+
teal: "#008080",
|
|
3477
|
+
olive: "#808000",
|
|
3478
|
+
maroon: "#800000",
|
|
3479
|
+
aqua: "#00FFFF",
|
|
3480
|
+
lime: "#00FF00",
|
|
3481
|
+
silver: "#C0C0C0",
|
|
3482
|
+
fuchsia: "#FF00FF"
|
|
3483
|
+
};
|
|
3484
|
+
const lowerColor = color.toLowerCase();
|
|
3485
|
+
if (namedColors[lowerColor]) {
|
|
3486
|
+
return namedColors[lowerColor];
|
|
3487
|
+
}
|
|
3488
|
+
return void 0;
|
|
3489
|
+
}
|
|
3490
|
+
function rgbToHex(r, g, b) {
|
|
3491
|
+
const toHex = (n) => {
|
|
3492
|
+
const hex = Math.max(0, Math.min(255, n)).toString(16);
|
|
3493
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
3494
|
+
};
|
|
3495
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
|
|
3496
|
+
}
|
|
3497
|
+
function parseNumber(value) {
|
|
3498
|
+
if (!value) return void 0;
|
|
3499
|
+
const num = parseFloat(value);
|
|
3500
|
+
return isNaN(num) ? void 0 : num;
|
|
3501
|
+
}
|
|
3502
|
+
function parseViewBox(viewBox) {
|
|
3503
|
+
if (!viewBox) return void 0;
|
|
3504
|
+
const parts = viewBox.trim().split(/[\s,]+/).map(parseFloat);
|
|
3505
|
+
if (parts.length === 4 && parts.every((p) => !isNaN(p))) {
|
|
3506
|
+
return { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
|
|
3507
|
+
}
|
|
3508
|
+
return void 0;
|
|
3509
|
+
}
|
|
3510
|
+
function rectToPath(attrs) {
|
|
3511
|
+
const x = parseNumber(extractAttribute(attrs, "x")) || 0;
|
|
3512
|
+
const y = parseNumber(extractAttribute(attrs, "y")) || 0;
|
|
3513
|
+
const width = parseNumber(extractAttribute(attrs, "width")) || 0;
|
|
3514
|
+
const height = parseNumber(extractAttribute(attrs, "height")) || 0;
|
|
3515
|
+
const rx = parseNumber(extractAttribute(attrs, "rx")) || 0;
|
|
3516
|
+
const ry = parseNumber(extractAttribute(attrs, "ry")) || rx;
|
|
3517
|
+
if (rx === 0 && ry === 0) {
|
|
3518
|
+
return `M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z`;
|
|
3519
|
+
}
|
|
3520
|
+
const r = Math.min(rx, ry, width / 2, height / 2);
|
|
3521
|
+
return [
|
|
3522
|
+
`M ${x + r} ${y}`,
|
|
3523
|
+
`L ${x + width - r} ${y}`,
|
|
3524
|
+
`Q ${x + width} ${y} ${x + width} ${y + r}`,
|
|
3525
|
+
`L ${x + width} ${y + height - r}`,
|
|
3526
|
+
`Q ${x + width} ${y + height} ${x + width - r} ${y + height}`,
|
|
3527
|
+
`L ${x + r} ${y + height}`,
|
|
3528
|
+
`Q ${x} ${y + height} ${x} ${y + height - r}`,
|
|
3529
|
+
`L ${x} ${y + r}`,
|
|
3530
|
+
`Q ${x} ${y} ${x + r} ${y}`,
|
|
3531
|
+
"Z"
|
|
3532
|
+
].join(" ");
|
|
3533
|
+
}
|
|
3534
|
+
function circleToPath(attrs) {
|
|
3535
|
+
const cx = parseNumber(extractAttribute(attrs, "cx")) || 0;
|
|
3536
|
+
const cy = parseNumber(extractAttribute(attrs, "cy")) || 0;
|
|
3537
|
+
const r = parseNumber(extractAttribute(attrs, "r")) || 0;
|
|
3538
|
+
if (r === 0) return "";
|
|
3539
|
+
const KAPPA2 = 0.5522847498307936;
|
|
3540
|
+
const k = r * KAPPA2;
|
|
3541
|
+
return [
|
|
3542
|
+
`M ${cx + r} ${cy}`,
|
|
3543
|
+
`C ${cx + r} ${cy + k} ${cx + k} ${cy + r} ${cx} ${cy + r}`,
|
|
3544
|
+
`C ${cx - k} ${cy + r} ${cx - r} ${cy + k} ${cx - r} ${cy}`,
|
|
3545
|
+
`C ${cx - r} ${cy - k} ${cx - k} ${cy - r} ${cx} ${cy - r}`,
|
|
3546
|
+
`C ${cx + k} ${cy - r} ${cx + r} ${cy - k} ${cx + r} ${cy}`,
|
|
3547
|
+
"Z"
|
|
3548
|
+
].join(" ");
|
|
3549
|
+
}
|
|
3550
|
+
function ellipseToPath(attrs) {
|
|
3551
|
+
const cx = parseNumber(extractAttribute(attrs, "cx")) || 0;
|
|
3552
|
+
const cy = parseNumber(extractAttribute(attrs, "cy")) || 0;
|
|
3553
|
+
const rx = parseNumber(extractAttribute(attrs, "rx")) || 0;
|
|
3554
|
+
const ry = parseNumber(extractAttribute(attrs, "ry")) || 0;
|
|
3555
|
+
if (rx === 0 || ry === 0) return "";
|
|
3556
|
+
const KAPPA2 = 0.5522847498307936;
|
|
3557
|
+
const kx = rx * KAPPA2;
|
|
3558
|
+
const ky = ry * KAPPA2;
|
|
3559
|
+
return [
|
|
3560
|
+
`M ${cx + rx} ${cy}`,
|
|
3561
|
+
`C ${cx + rx} ${cy + ky} ${cx + kx} ${cy + ry} ${cx} ${cy + ry}`,
|
|
3562
|
+
`C ${cx - kx} ${cy + ry} ${cx - rx} ${cy + ky} ${cx - rx} ${cy}`,
|
|
3563
|
+
`C ${cx - rx} ${cy - ky} ${cx - kx} ${cy - ry} ${cx} ${cy - ry}`,
|
|
3564
|
+
`C ${cx + kx} ${cy - ry} ${cx + rx} ${cy - ky} ${cx + rx} ${cy}`,
|
|
3565
|
+
"Z"
|
|
3566
|
+
].join(" ");
|
|
3567
|
+
}
|
|
3568
|
+
function lineToPath(attrs) {
|
|
3569
|
+
const x1 = parseNumber(extractAttribute(attrs, "x1")) || 0;
|
|
3570
|
+
const y1 = parseNumber(extractAttribute(attrs, "y1")) || 0;
|
|
3571
|
+
const x2 = parseNumber(extractAttribute(attrs, "x2")) || 0;
|
|
3572
|
+
const y2 = parseNumber(extractAttribute(attrs, "y2")) || 0;
|
|
3573
|
+
return `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
3574
|
+
}
|
|
3575
|
+
function polylineToPath(attrs) {
|
|
3576
|
+
const points = extractAttribute(attrs, "points");
|
|
3577
|
+
if (!points) return "";
|
|
3578
|
+
const coords = points.trim().split(/[\s,]+/).map(parseFloat);
|
|
3579
|
+
if (coords.length < 2) return "";
|
|
3580
|
+
const pathParts = [];
|
|
3581
|
+
for (let i = 0; i < coords.length; i += 2) {
|
|
3582
|
+
if (i + 1 < coords.length) {
|
|
3583
|
+
if (i === 0) {
|
|
3584
|
+
pathParts.push(`M ${coords[i]} ${coords[i + 1]}`);
|
|
3585
|
+
} else {
|
|
3586
|
+
pathParts.push(`L ${coords[i]} ${coords[i + 1]}`);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
return pathParts.join(" ");
|
|
3591
|
+
}
|
|
3592
|
+
function polygonToPath(attrs) {
|
|
3593
|
+
const polyPath = polylineToPath(attrs);
|
|
3594
|
+
if (!polyPath) return "";
|
|
3595
|
+
return polyPath + " Z";
|
|
3596
|
+
}
|
|
3597
|
+
function extractPathAttributes(attrs) {
|
|
3598
|
+
const d = extractAttribute(attrs, "d") || "";
|
|
3599
|
+
const fill = extractAttribute(attrs, "fill");
|
|
3600
|
+
const fillOpacity = parseNumber(extractAttribute(attrs, "fill-opacity"));
|
|
3601
|
+
const stroke = extractAttribute(attrs, "stroke");
|
|
3602
|
+
const strokeWidth = parseNumber(extractAttribute(attrs, "stroke-width"));
|
|
3603
|
+
const strokeOpacity = parseNumber(extractAttribute(attrs, "stroke-opacity"));
|
|
3604
|
+
const opacity = parseNumber(extractAttribute(attrs, "opacity"));
|
|
3605
|
+
const transform = extractAttribute(attrs, "transform");
|
|
3606
|
+
const style = extractAttribute(attrs, "style");
|
|
3607
|
+
let styleFill = fill;
|
|
3608
|
+
let styleStroke = stroke;
|
|
3609
|
+
let styleStrokeWidth = strokeWidth;
|
|
3610
|
+
let styleOpacity = opacity;
|
|
3611
|
+
if (style) {
|
|
3612
|
+
const fillMatch = style.match(/fill\s*:\s*([^;]+)/i);
|
|
3613
|
+
if (fillMatch) styleFill = fillMatch[1].trim();
|
|
3614
|
+
const strokeMatch = style.match(/stroke\s*:\s*([^;]+)/i);
|
|
3615
|
+
if (strokeMatch) styleStroke = strokeMatch[1].trim();
|
|
3616
|
+
const strokeWidthMatch = style.match(/stroke-width\s*:\s*([^;]+)/i);
|
|
3617
|
+
if (strokeWidthMatch) styleStrokeWidth = parseNumber(strokeWidthMatch[1].trim());
|
|
3618
|
+
const opacityMatch = style.match(/opacity\s*:\s*([^;]+)/i);
|
|
3619
|
+
if (opacityMatch) styleOpacity = parseNumber(opacityMatch[1].trim());
|
|
3620
|
+
}
|
|
3621
|
+
return {
|
|
3622
|
+
d,
|
|
3623
|
+
fill: styleFill,
|
|
3624
|
+
fillOpacity,
|
|
3625
|
+
stroke: styleStroke,
|
|
3626
|
+
strokeWidth: styleStrokeWidth,
|
|
3627
|
+
strokeOpacity,
|
|
3628
|
+
opacity: styleOpacity,
|
|
3629
|
+
transform
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
function extractShapeAsPath(tagAttrs, convertFn) {
|
|
3633
|
+
const d = convertFn(tagAttrs);
|
|
3634
|
+
if (!d) return null;
|
|
3635
|
+
const pathAttrs = extractPathAttributes(tagAttrs);
|
|
3636
|
+
pathAttrs.d = d;
|
|
3637
|
+
return pathAttrs;
|
|
3638
|
+
}
|
|
3639
|
+
function parseSvgMarkup(svgString) {
|
|
3640
|
+
const svgMatch = svgString.match(SVG_ATTRS_REGEX);
|
|
3641
|
+
const svgAttrs = svgMatch ? svgMatch[1] : "";
|
|
3642
|
+
const widthAttr = extractAttribute(svgAttrs, "width");
|
|
3643
|
+
const heightAttr = extractAttribute(svgAttrs, "height");
|
|
3644
|
+
const viewBoxAttr = extractAttribute(svgAttrs, "viewBox");
|
|
3645
|
+
let width = parseNumber(widthAttr?.replace(/px$/, "")) || 0;
|
|
3646
|
+
let height = parseNumber(heightAttr?.replace(/px$/, "")) || 0;
|
|
3647
|
+
const viewBox = parseViewBox(viewBoxAttr);
|
|
3648
|
+
if (viewBox && (!width || !height)) {
|
|
3649
|
+
width = width || viewBox.width;
|
|
3650
|
+
height = height || viewBox.height;
|
|
3651
|
+
}
|
|
3652
|
+
const paths = [];
|
|
3653
|
+
let match;
|
|
3654
|
+
PATH_TAG_REGEX.lastIndex = 0;
|
|
3655
|
+
while ((match = PATH_TAG_REGEX.exec(svgString)) !== null) {
|
|
3656
|
+
const pathAttrs = extractPathAttributes(match[1]);
|
|
3657
|
+
if (pathAttrs.d) {
|
|
3658
|
+
paths.push(pathAttrs);
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
RECT_TAG_REGEX.lastIndex = 0;
|
|
3662
|
+
while ((match = RECT_TAG_REGEX.exec(svgString)) !== null) {
|
|
3663
|
+
const path = extractShapeAsPath(match[1], rectToPath);
|
|
3664
|
+
if (path) paths.push(path);
|
|
3665
|
+
}
|
|
3666
|
+
CIRCLE_TAG_REGEX.lastIndex = 0;
|
|
3667
|
+
while ((match = CIRCLE_TAG_REGEX.exec(svgString)) !== null) {
|
|
3668
|
+
const path = extractShapeAsPath(match[1], circleToPath);
|
|
3669
|
+
if (path) paths.push(path);
|
|
3670
|
+
}
|
|
3671
|
+
ELLIPSE_TAG_REGEX.lastIndex = 0;
|
|
3672
|
+
while ((match = ELLIPSE_TAG_REGEX.exec(svgString)) !== null) {
|
|
3673
|
+
const path = extractShapeAsPath(match[1], ellipseToPath);
|
|
3674
|
+
if (path) paths.push(path);
|
|
3675
|
+
}
|
|
3676
|
+
LINE_TAG_REGEX.lastIndex = 0;
|
|
3677
|
+
while ((match = LINE_TAG_REGEX.exec(svgString)) !== null) {
|
|
3678
|
+
const path = extractShapeAsPath(match[1], lineToPath);
|
|
3679
|
+
if (path) paths.push(path);
|
|
3680
|
+
}
|
|
3681
|
+
POLYLINE_TAG_REGEX.lastIndex = 0;
|
|
3682
|
+
while ((match = POLYLINE_TAG_REGEX.exec(svgString)) !== null) {
|
|
3683
|
+
const path = extractShapeAsPath(match[1], polylineToPath);
|
|
3684
|
+
if (path) paths.push(path);
|
|
3685
|
+
}
|
|
3686
|
+
POLYGON_TAG_REGEX.lastIndex = 0;
|
|
3687
|
+
while ((match = POLYGON_TAG_REGEX.exec(svgString)) !== null) {
|
|
3688
|
+
const path = extractShapeAsPath(match[1], polygonToPath);
|
|
3689
|
+
if (path) paths.push(path);
|
|
3690
|
+
}
|
|
3691
|
+
return {
|
|
3692
|
+
width,
|
|
3693
|
+
height,
|
|
3694
|
+
viewBox,
|
|
3695
|
+
paths
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
function svgToAsset(svgString) {
|
|
3699
|
+
const parsed = parseSvgMarkup(svgString);
|
|
3700
|
+
if (parsed.paths.length === 0) {
|
|
3701
|
+
return {
|
|
3702
|
+
type: "svg",
|
|
3703
|
+
shape: {
|
|
3704
|
+
type: "path",
|
|
3705
|
+
d: ""
|
|
3706
|
+
},
|
|
3707
|
+
width: parsed.width || void 0,
|
|
3708
|
+
height: parsed.height || void 0
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
if (parsed.paths.length === 1) {
|
|
3712
|
+
return pathToAsset(parsed.paths[0], parsed.width, parsed.height);
|
|
3713
|
+
}
|
|
3714
|
+
return parsed.paths.map((path) => pathToAsset(path, parsed.width, parsed.height));
|
|
3715
|
+
}
|
|
3716
|
+
function pathToAsset(path, width, height) {
|
|
3717
|
+
const asset = {
|
|
3718
|
+
type: "svg",
|
|
3719
|
+
shape: {
|
|
3720
|
+
type: "path",
|
|
3721
|
+
d: path.d
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3724
|
+
const fillColor = parseColor(path.fill);
|
|
3725
|
+
if (fillColor) {
|
|
3726
|
+
asset.fill = {
|
|
3727
|
+
type: "solid",
|
|
3728
|
+
color: fillColor
|
|
3729
|
+
};
|
|
3730
|
+
if (path.fillOpacity !== void 0 && path.fillOpacity !== 1) {
|
|
3731
|
+
asset.fill.opacity = path.fillOpacity;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
const strokeColor = parseColor(path.stroke);
|
|
3735
|
+
if (strokeColor && path.strokeWidth && path.strokeWidth > 0) {
|
|
3736
|
+
asset.stroke = {
|
|
3737
|
+
color: strokeColor,
|
|
3738
|
+
width: path.strokeWidth
|
|
3739
|
+
};
|
|
3740
|
+
if (path.strokeOpacity !== void 0 && path.strokeOpacity !== 1) {
|
|
3741
|
+
asset.stroke.opacity = path.strokeOpacity;
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
if (path.opacity !== void 0 && path.opacity !== 1) {
|
|
3745
|
+
asset.opacity = path.opacity;
|
|
3746
|
+
}
|
|
3747
|
+
if (width > 0) {
|
|
3748
|
+
asset.width = width;
|
|
3749
|
+
}
|
|
3750
|
+
if (height > 0) {
|
|
3751
|
+
asset.height = height;
|
|
3752
|
+
}
|
|
3753
|
+
return asset;
|
|
3754
|
+
}
|
|
3755
|
+
function svgToSingleAsset(svgString) {
|
|
3756
|
+
const parsed = parseSvgMarkup(svgString);
|
|
3757
|
+
if (parsed.paths.length === 0) {
|
|
3758
|
+
return {
|
|
3759
|
+
type: "svg",
|
|
3760
|
+
shape: {
|
|
3761
|
+
type: "path",
|
|
3762
|
+
d: ""
|
|
3763
|
+
},
|
|
3764
|
+
width: parsed.width || void 0,
|
|
3765
|
+
height: parsed.height || void 0
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
const combinedD = parsed.paths.map((p) => p.d).join(" ");
|
|
3769
|
+
const firstPathWithFill = parsed.paths.find((p) => p.fill && p.fill !== "none");
|
|
3770
|
+
const firstPathWithStroke = parsed.paths.find((p) => p.stroke && p.stroke !== "none");
|
|
3771
|
+
const asset = {
|
|
3772
|
+
type: "svg",
|
|
3773
|
+
shape: {
|
|
3774
|
+
type: "path",
|
|
3775
|
+
d: combinedD
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
if (firstPathWithFill) {
|
|
3779
|
+
const fillColor = parseColor(firstPathWithFill.fill);
|
|
3780
|
+
if (fillColor) {
|
|
3781
|
+
asset.fill = {
|
|
3782
|
+
type: "solid",
|
|
3783
|
+
color: fillColor
|
|
3784
|
+
};
|
|
3785
|
+
if (firstPathWithFill.fillOpacity !== void 0 && firstPathWithFill.fillOpacity !== 1) {
|
|
3786
|
+
asset.fill.opacity = firstPathWithFill.fillOpacity;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
if (firstPathWithStroke) {
|
|
3791
|
+
const strokeColor = parseColor(firstPathWithStroke.stroke);
|
|
3792
|
+
if (strokeColor && firstPathWithStroke.strokeWidth && firstPathWithStroke.strokeWidth > 0) {
|
|
3793
|
+
asset.stroke = {
|
|
3794
|
+
color: strokeColor,
|
|
3795
|
+
width: firstPathWithStroke.strokeWidth
|
|
3796
|
+
};
|
|
3797
|
+
if (firstPathWithStroke.strokeOpacity !== void 0 && firstPathWithStroke.strokeOpacity !== 1) {
|
|
3798
|
+
asset.stroke.opacity = firstPathWithStroke.strokeOpacity;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
const firstPathWithOpacity = parsed.paths.find((p) => p.opacity !== void 0 && p.opacity !== 1);
|
|
3803
|
+
if (firstPathWithOpacity) {
|
|
3804
|
+
asset.opacity = firstPathWithOpacity.opacity;
|
|
3805
|
+
}
|
|
3806
|
+
if (parsed.width > 0) {
|
|
3807
|
+
asset.width = parsed.width;
|
|
3808
|
+
}
|
|
3809
|
+
if (parsed.height > 0) {
|
|
3810
|
+
asset.height = parsed.height;
|
|
3811
|
+
}
|
|
3812
|
+
return asset;
|
|
3813
|
+
}
|
|
3814
|
+
function importSvg(svgString) {
|
|
3815
|
+
return svgToSingleAsset(svgString);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
2423
3818
|
// src/env/entry.node.ts
|
|
3819
|
+
var registeredGlobalFonts = /* @__PURE__ */ new Set();
|
|
3820
|
+
async function registerColorEmojiWithCanvas(family, bytes) {
|
|
3821
|
+
if (registeredGlobalFonts.has(family)) {
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
try {
|
|
3825
|
+
const canvasMod = await import("canvas");
|
|
3826
|
+
const GlobalFonts = canvasMod.GlobalFonts;
|
|
3827
|
+
if (GlobalFonts && typeof GlobalFonts.register === "function") {
|
|
3828
|
+
const buffer = Buffer.from(bytes);
|
|
3829
|
+
GlobalFonts.register(buffer, family);
|
|
3830
|
+
registeredGlobalFonts.add(family);
|
|
3831
|
+
console.log(`\u{1F3A8} Registered color emoji font with canvas: ${family}`);
|
|
3832
|
+
}
|
|
3833
|
+
} catch (err) {
|
|
3834
|
+
console.warn(`\u26A0\uFE0F Could not register color emoji font with canvas GlobalFonts: ${err}`);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
2424
3837
|
async function createTextEngine(opts = {}) {
|
|
2425
3838
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
2426
3839
|
const height = opts.height ?? CANVAS_CONFIG.DEFAULTS.height;
|
|
@@ -2447,6 +3860,12 @@ async function createTextEngine(opts = {}) {
|
|
|
2447
3860
|
family: cf.family,
|
|
2448
3861
|
weight: cf.weight ?? "400"
|
|
2449
3862
|
});
|
|
3863
|
+
if (fonts.isColorEmojiFont(cf.family)) {
|
|
3864
|
+
const emojiBytes = fonts.getColorEmojiFontBytes(cf.family);
|
|
3865
|
+
if (emojiBytes) {
|
|
3866
|
+
await registerColorEmojiWithCanvas(cf.family, emojiBytes);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
2450
3869
|
} catch (err) {
|
|
2451
3870
|
throw new Error(
|
|
2452
3871
|
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2504,12 +3923,17 @@ async function createTextEngine(opts = {}) {
|
|
|
2504
3923
|
const desc = { family: main.family, weight: `${main.weight}` };
|
|
2505
3924
|
let lines;
|
|
2506
3925
|
try {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
3926
|
+
let emojiFallback = void 0;
|
|
3927
|
+
const colorEmojiFamilies = fonts.getColorEmojiFontFamilies();
|
|
3928
|
+
if (colorEmojiFamilies.length > 0) {
|
|
3929
|
+
emojiFallback = { family: colorEmojiFamilies[0], weight: "400" };
|
|
3930
|
+
} else {
|
|
3931
|
+
const notoEmojiDesc = { family: "NotoEmoji", weight: "400" };
|
|
3932
|
+
try {
|
|
3933
|
+
await fonts.getFace(notoEmojiDesc);
|
|
3934
|
+
emojiFallback = notoEmojiDesc;
|
|
3935
|
+
} catch {
|
|
3936
|
+
}
|
|
2513
3937
|
}
|
|
2514
3938
|
const padding2 = asset.padding ? typeof asset.padding === "number" ? {
|
|
2515
3939
|
top: asset.padding,
|
|
@@ -2525,7 +3949,7 @@ async function createTextEngine(opts = {}) {
|
|
|
2525
3949
|
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
2526
3950
|
desc,
|
|
2527
3951
|
textTransform: asset.style?.textTransform ?? "none",
|
|
2528
|
-
emojiFallback
|
|
3952
|
+
emojiFallback
|
|
2529
3953
|
});
|
|
2530
3954
|
} catch (err) {
|
|
2531
3955
|
throw new Error(
|
|
@@ -2580,7 +4004,8 @@ async function createTextEngine(opts = {}) {
|
|
|
2580
4004
|
border: asset.border,
|
|
2581
4005
|
padding: asset.padding,
|
|
2582
4006
|
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2583
|
-
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
4007
|
+
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc),
|
|
4008
|
+
isColorEmojiFont: (family) => fonts.isColorEmojiFont(family)
|
|
2584
4009
|
});
|
|
2585
4010
|
} catch (err) {
|
|
2586
4011
|
throw new Error(
|
|
@@ -2671,7 +4096,50 @@ async function createTextEngine(opts = {}) {
|
|
|
2671
4096
|
}
|
|
2672
4097
|
export {
|
|
2673
4098
|
CanvasRichTextAssetSchema,
|
|
4099
|
+
CanvasSvgAssetSchema,
|
|
4100
|
+
arcToCubicBeziers,
|
|
4101
|
+
centerPath,
|
|
4102
|
+
commandsToPathString,
|
|
4103
|
+
computePathBounds3 as computePathBounds,
|
|
4104
|
+
createNodePainter,
|
|
2674
4105
|
createTextEngine,
|
|
4106
|
+
generateArrowPath,
|
|
4107
|
+
generateCirclePath,
|
|
4108
|
+
generateCrossPath,
|
|
4109
|
+
generateEllipsePath,
|
|
4110
|
+
generateHeartPath,
|
|
4111
|
+
generateLinePath,
|
|
4112
|
+
generatePolygonPath,
|
|
4113
|
+
generatePolygonPoints,
|
|
4114
|
+
generateRectanglePath,
|
|
4115
|
+
generateRingPath,
|
|
4116
|
+
generateShapePath,
|
|
4117
|
+
generateStarPath,
|
|
4118
|
+
generateStarPoints,
|
|
4119
|
+
generateTrianglePath,
|
|
4120
|
+
importSvg,
|
|
2675
4121
|
isGlyphFill2 as isGlyphFill,
|
|
2676
|
-
isShadowFill2 as isShadowFill
|
|
4122
|
+
isShadowFill2 as isShadowFill,
|
|
4123
|
+
normalizePath,
|
|
4124
|
+
normalizePathString,
|
|
4125
|
+
normalizePathToSize,
|
|
4126
|
+
parseSvgMarkup,
|
|
4127
|
+
parseSvgPath,
|
|
4128
|
+
pointsToPath,
|
|
4129
|
+
quadraticToCubic,
|
|
4130
|
+
reverseWindingOrder,
|
|
4131
|
+
rotatePath,
|
|
4132
|
+
scalePath,
|
|
4133
|
+
svgAssetSchema,
|
|
4134
|
+
svgGradientStopSchema,
|
|
4135
|
+
svgLinearGradientFillSchema,
|
|
4136
|
+
svgRadialGradientFillSchema,
|
|
4137
|
+
svgShadowSchema,
|
|
4138
|
+
svgShapeSchema,
|
|
4139
|
+
svgSolidFillSchema,
|
|
4140
|
+
svgStrokeSchema,
|
|
4141
|
+
svgToAsset,
|
|
4142
|
+
svgToSingleAsset,
|
|
4143
|
+
svgTransformSchema,
|
|
4144
|
+
translatePath
|
|
2677
4145
|
};
|