@pixerate/editor 0.117.0 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -0
  2. package/dist/animation-editor/index.cjs +54 -54
  3. package/dist/animation-editor/index.d.cts +3 -3
  4. package/dist/animation-editor/index.d.ts +3 -3
  5. package/dist/animation-editor/index.js +2 -2
  6. package/dist/{chunk-2J3LS7JZ.cjs → chunk-4S2AXNQF.cjs} +221 -186
  7. package/dist/chunk-4S2AXNQF.cjs.map +1 -0
  8. package/dist/{chunk-7LNZAWSJ.js → chunk-CSHEI3YT.js} +625 -451
  9. package/dist/chunk-CSHEI3YT.js.map +1 -0
  10. package/dist/{chunk-M7O4BJRH.cjs → chunk-KGNUFO4T.cjs} +36 -36
  11. package/dist/{chunk-M7O4BJRH.cjs.map → chunk-KGNUFO4T.cjs.map} +1 -1
  12. package/dist/{chunk-R2JE6SA7.js → chunk-KZLQY4CD.js} +42 -7
  13. package/dist/chunk-KZLQY4CD.js.map +1 -0
  14. package/dist/{chunk-L2T6EJ5S.cjs → chunk-L3PXFMOF.cjs} +38 -25
  15. package/dist/chunk-L3PXFMOF.cjs.map +1 -0
  16. package/dist/{chunk-FODKPD44.cjs → chunk-LI3KTY7K.cjs} +724 -550
  17. package/dist/chunk-LI3KTY7K.cjs.map +1 -0
  18. package/dist/{chunk-IVSNUXW7.js → chunk-T7IZDNSL.js} +37 -24
  19. package/dist/chunk-T7IZDNSL.js.map +1 -0
  20. package/dist/{chunk-T2P5TIZ6.js → chunk-TDQDMBUR.js} +2 -2
  21. package/dist/{chunk-BYAWOAST.cjs → chunk-TS5IEUW6.cjs} +26 -25
  22. package/dist/chunk-TS5IEUW6.cjs.map +1 -0
  23. package/dist/{chunk-VD2UDVF7.js → chunk-UMVW2Q66.js} +21 -5
  24. package/dist/chunk-UMVW2Q66.js.map +1 -0
  25. package/dist/{chunk-EBSYZRSW.cjs → chunk-WMRIG4XQ.cjs} +21 -5
  26. package/dist/chunk-WMRIG4XQ.cjs.map +1 -0
  27. package/dist/{chunk-JHXMNOMF.js → chunk-YHW26LS2.js} +14 -13
  28. package/dist/chunk-YHW26LS2.js.map +1 -0
  29. package/dist/{export-DMOuzgZZ.d.cts → export-B_jO2PrH.d.cts} +18 -4
  30. package/dist/{export-Dul1SbPv.d.ts → export-Ds8Ccw0K.d.ts} +18 -4
  31. package/dist/{flow-4-OC-UkQ.d.cts → flow-A2Dj-69w.d.cts} +21 -9
  32. package/dist/{flow-Cb7lkPm8.d.ts → flow-C95atNoQ.d.ts} +21 -9
  33. package/dist/image-editor/index.cjs +1104 -979
  34. package/dist/image-editor/index.cjs.map +1 -1
  35. package/dist/image-editor/index.d.cts +5 -5
  36. package/dist/image-editor/index.d.ts +5 -5
  37. package/dist/image-editor/index.js +157 -32
  38. package/dist/image-editor/index.js.map +1 -1
  39. package/dist/paint/index.cjs +5 -3
  40. package/dist/paint/index.cjs.map +1 -1
  41. package/dist/paint/index.d.cts +2 -2
  42. package/dist/paint/index.d.ts +2 -2
  43. package/dist/paint/index.js +4 -2
  44. package/dist/paint-editor/index.cjs +147 -147
  45. package/dist/paint-editor/index.d.cts +4 -4
  46. package/dist/paint-editor/index.d.ts +4 -4
  47. package/dist/paint-editor/index.js +4 -4
  48. package/dist/raster/index.cjs +2 -2
  49. package/dist/raster/index.js +1 -1
  50. package/dist/{svg-DlEuhwoX.d.ts → svg-CTxrB5xO.d.ts} +5 -1
  51. package/dist/{svg-CVdFc80-.d.cts → svg-hK0WQc7-.d.cts} +5 -1
  52. package/dist/{types-Tfly4Aya.d.cts → types-Dq-8qgoo.d.cts} +45 -3
  53. package/dist/{types-CDLGhAZp.d.ts → types-TrSSh_RB.d.ts} +45 -3
  54. package/dist/vector/index.cjs +1535 -1164
  55. package/dist/vector/index.cjs.map +1 -1
  56. package/dist/vector/index.d.cts +127 -9
  57. package/dist/vector/index.d.ts +127 -9
  58. package/dist/vector/index.js +386 -15
  59. package/dist/vector/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/skills/pixerate-image-editor/references/photoshop-files.md +9 -7
  62. package/skills/pixerate-vector/SKILL.md +13 -1
  63. package/skills/pixerate-vector/references/commands.md +1 -0
  64. package/dist/chunk-2J3LS7JZ.cjs.map +0 -1
  65. package/dist/chunk-7LNZAWSJ.js.map +0 -1
  66. package/dist/chunk-BYAWOAST.cjs.map +0 -1
  67. package/dist/chunk-EBSYZRSW.cjs.map +0 -1
  68. package/dist/chunk-FODKPD44.cjs.map +0 -1
  69. package/dist/chunk-IVSNUXW7.js.map +0 -1
  70. package/dist/chunk-JHXMNOMF.js.map +0 -1
  71. package/dist/chunk-L2T6EJ5S.cjs.map +0 -1
  72. package/dist/chunk-R2JE6SA7.js.map +0 -1
  73. package/dist/chunk-VD2UDVF7.js.map +0 -1
  74. /package/dist/{chunk-T2P5TIZ6.js.map → chunk-TDQDMBUR.js.map} +0 -0
@@ -75,6 +75,7 @@ var VECTOR_CONNECTOR_SIDES = ["auto", "top", "right", "bottom", "left", "center"
75
75
  var VECTOR_CONNECTOR_ROUTES = ["straight", "elbow", "curved"];
76
76
  var VECTOR_BLEND_SPACINGS = ["steps", "distance", "smoothColor"];
77
77
  var VECTOR_MASK_TYPES = ["clip", "alpha", "luminance"];
78
+ var VECTOR_SYMMETRY_MODES = ["vertical", "horizontal", "dual", "diagonal", "radial"];
78
79
  var VECTOR_TEXT_ALIGNS = ["left", "center", "right", "justify"];
79
80
  var VECTOR_PATH_TEXT_BASELINES = ["baseline", "ascender", "descender", "center"];
80
81
  var VECTOR_CONSTRAINT_KINDS = [
@@ -121,7 +122,7 @@ var VECTOR_TOOLS = [
121
122
  ];
122
123
  var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
123
124
  var VECTOR_PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
124
- var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater", "envelope"];
125
+ var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater", "envelope", "contour"];
125
126
  var VECTOR_MERGE_MODES = ["merge", "add", "subtract", "intersect", "exclude"];
126
127
  var VECTOR_FILTER_TYPES = ["dropShadow", "innerShadow", "outerGlow", "innerGlow", "layerBlur", "feather"];
127
128
 
@@ -465,8 +466,8 @@ function vectorGradientParam(type, q, focal) {
465
466
  case "diamond":
466
467
  return Math.abs(q.x) + Math.abs(q.y);
467
468
  case "conic": {
468
- const turn = Math.atan2(q.y, q.x) / (2 * Math.PI);
469
- return turn - Math.floor(turn);
469
+ const turn2 = Math.atan2(q.y, q.x) / (2 * Math.PI);
470
+ return turn2 - Math.floor(turn2);
470
471
  }
471
472
  case "radial":
472
473
  return focal ? vectorGradientFocalParam(q, focal.f, focal.fr) : Math.hypot(q.x, q.y);
@@ -1451,12 +1452,12 @@ function squareTo(a, b) {
1451
1452
  return l > EPS ? mul2(w, 1 / l) : { x: 0, y: 0 };
1452
1453
  }
1453
1454
  function smoothCut(c, radius, smoothing) {
1454
- const turn = Math.PI - Math.acos(Math.max(-1, Math.min(1, c.cos)));
1455
+ const turn2 = Math.PI - Math.acos(Math.max(-1, Math.min(1, c.cos)));
1455
1456
  const t = cornerSetback(c, radius);
1456
1457
  const room = Math.min(c.lu, c.lv) / 2;
1457
1458
  let s = Math.max(0, Math.min(1, smoothing));
1458
1459
  if ((1 + s) * t > room) s = Math.max(0, Math.min(s, room / t - 1));
1459
- const beta = turn * s / 2;
1460
+ const beta = turn2 * s / 2;
1460
1461
  if (!(beta > 1e-6) || !(t > EPS)) return null;
1461
1462
  const p = Math.min((1 + s) * t, room);
1462
1463
  const qa = radius * Math.tan(beta / 2);
@@ -1471,7 +1472,7 @@ function smoothCut(c, radius, smoothing) {
1471
1472
  };
1472
1473
  const U = side(c.u, c.v);
1473
1474
  const V2 = side(c.v, c.u);
1474
- const sweep = turn * (1 - s);
1475
+ const sweep = turn2 * (1 - s);
1475
1476
  const h = radius * (4 / 3) * Math.tan(sweep / 4);
1476
1477
  const tanU = sub2(U.a, U.q);
1477
1478
  const tanV = sub2(V2.a, V2.q);
@@ -1510,9 +1511,9 @@ function cutCorner(c, radius, kind, smoothing = 0) {
1510
1511
  }
1511
1512
  const d1 = mul2(a.dir, -1);
1512
1513
  const d2 = mul2(b.dir, -1);
1513
- const turn = Math.acos(Math.max(-1, Math.min(1, -dot2(d1, d2))));
1514
+ const turn2 = Math.acos(Math.max(-1, Math.min(1, -dot2(d1, d2))));
1514
1515
  const chord = len(sub2(b.p, a.p));
1515
- const k = turn < 1e-9 ? chord / 3 : 4 / 3 * Math.tan(turn / 4) * chord / (2 * Math.sin(turn / 2));
1516
+ const k = turn2 < 1e-9 ? chord / 3 : 4 / 3 * Math.tan(turn2 / 4) * chord / (2 * Math.sin(turn2 / 2));
1516
1517
  if (!Number.isFinite(k)) return null;
1517
1518
  return { tu: a.t, tv: b.t, start: a.p, out: add2(a.p, mul2(d1, k)), in: add2(b.p, mul2(d2, k)), end: b.p };
1518
1519
  }
@@ -2649,6 +2650,380 @@ function withVectorTargetPaint(node, target, layer, paint) {
2649
2650
  return target === "fill" ? withVectorFills(node, next) : withVectorStrokes(node, next);
2650
2651
  }
2651
2652
 
2653
+ // src/vector/pathEffects.ts
2654
+ var EPS2 = 1e-9;
2655
+ var degenerate = (s) => s.every((p) => Math.abs(p[0] - s[0][0]) < EPS2 && Math.abs(p[1] - s[0][1]) < EPS2);
2656
+ function raise(seg) {
2657
+ if (seg.length !== 3) return seg.map((p) => [p[0], p[1]]);
2658
+ const [p0, p1, p2] = seg;
2659
+ return [p0, [p0[0] + 2 / 3 * (p1[0] - p0[0]), p0[1] + 2 / 3 * (p1[1] - p0[1])], [p2[0] + 2 / 3 * (p1[0] - p2[0]), p2[1] + 2 / 3 * (p1[1] - p2[1])], p2];
2660
+ }
2661
+ function subsOf(path) {
2662
+ return pathToContours(path).map((c) => ({ segs: closeContour(c).filter((s) => !degenerate(s)).map(raise), closed: c.closed })).filter((s) => s.segs.length > 0);
2663
+ }
2664
+ var subsToPath = (subs) => contoursToPath(subs.map((s) => ({ segments: s.segs, closed: s.closed })));
2665
+ var unit2 = (x, y) => {
2666
+ const l = Math.hypot(x, y);
2667
+ return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
2668
+ };
2669
+ function startTangent(s) {
2670
+ for (let i = 1; i < s.length; i++) {
2671
+ const u = unit2(s[i][0] - s[0][0], s[i][1] - s[0][1]);
2672
+ if (u) return u;
2673
+ }
2674
+ return [1, 0];
2675
+ }
2676
+ function endTangent(s) {
2677
+ const end = s[s.length - 1];
2678
+ for (let i = s.length - 2; i >= 0; i--) {
2679
+ const u = unit2(end[0] - s[i][0], end[1] - s[i][1]);
2680
+ if (u) return u;
2681
+ }
2682
+ return [1, 0];
2683
+ }
2684
+ function tangentAt(s, t) {
2685
+ if (t <= 1e-9) return startTangent(s);
2686
+ if (t >= 1 - 1e-9) return endTangent(s);
2687
+ const d = segmentDerivative(s, t);
2688
+ return unit2(d[0], d[1]) ?? startTangent(s);
2689
+ }
2690
+ var perp2 = (v2) => [-v2[1], v2[0]];
2691
+ function effectPathLength(path) {
2692
+ let total = 0;
2693
+ for (const s of subsOf(path)) for (const seg of s.segs) total += segmentLength(seg);
2694
+ return total;
2695
+ }
2696
+ function effectPathVertexCount(path) {
2697
+ return subsOf(path).reduce((n, s) => n + s.segs.length + (s.closed ? 0 : 1), 0);
2698
+ }
2699
+ var Measured = class {
2700
+ segs;
2701
+ lens;
2702
+ total;
2703
+ constructor(path) {
2704
+ this.segs = subsOf(path).flatMap((s) => s.segs);
2705
+ this.lens = this.segs.map((s) => segmentLength(s));
2706
+ this.total = this.lens.reduce((a, b) => a + b, 0);
2707
+ }
2708
+ /**
2709
+ * Appends the part between lengths `sa` and `sb`, continuing the last piece when this one
2710
+ * starts where it ends (across the start of a closed path).
2711
+ */
2712
+ append(sa, sb, out) {
2713
+ for (const piece of trimSegments(this.segs, sa, sb, this.lens)) {
2714
+ if (degenerate(piece)) continue;
2715
+ const last = out[out.length - 1]?.segs;
2716
+ const end = last?.[last.length - 1]?.at(-1);
2717
+ if (!end || Math.hypot(end[0] - piece[0][0], end[1] - piece[0][1]) > 1e-6) out.push({ segs: [piece], closed: false });
2718
+ else last.push(piece);
2719
+ }
2720
+ }
2721
+ };
2722
+ function trimIntervals(start, end, offsetDegrees) {
2723
+ let s = start / 100;
2724
+ let e = end / 100;
2725
+ if (!Number.isFinite(s) || !Number.isFinite(e)) return null;
2726
+ if (s > e) [s, e] = [e, s];
2727
+ s = Math.min(1, Math.max(0, s));
2728
+ e = Math.min(1, Math.max(0, e));
2729
+ if (e - s >= 1 - 1e-9) return null;
2730
+ if (e - s <= 1e-9) return [];
2731
+ const off = Number.isFinite(offsetDegrees) ? (offsetDegrees / 360 % 1 + 1) % 1 : 0;
2732
+ s += off;
2733
+ e += off;
2734
+ if (e <= 1) return [[s, e]];
2735
+ if (s >= 1) return [[s - 1, e - 1]];
2736
+ return [
2737
+ [s, 1],
2738
+ [0, e - 1]
2739
+ ];
2740
+ }
2741
+ function trimPaths(paths, start, end, offsetDegrees = 0) {
2742
+ const iv = trimIntervals(start, end, offsetDegrees);
2743
+ if (!iv) return paths.slice();
2744
+ return paths.map((p) => {
2745
+ const m = new Measured(p);
2746
+ const out = [];
2747
+ if (m.total > 0) for (const [a, b] of iv) m.append(a * m.total, b * m.total, out);
2748
+ return subsToPath(out);
2749
+ });
2750
+ }
2751
+ function trimPathsIndividually(paths, start, end, offsetDegrees = 0) {
2752
+ const iv = trimIntervals(start, end, offsetDegrees);
2753
+ if (!iv) return paths.slice();
2754
+ const ms = paths.map((p) => new Measured(p));
2755
+ const total = ms.reduce((n, m) => n + m.total, 0);
2756
+ let base = 0;
2757
+ return ms.map((m) => {
2758
+ const out = [];
2759
+ for (const [a, b] of iv) {
2760
+ const la = Math.max(0, a * total - base);
2761
+ const lb = Math.min(m.total, b * total - base);
2762
+ if (lb > la) m.append(la, lb, out);
2763
+ }
2764
+ base += m.total;
2765
+ return subsToPath(out);
2766
+ });
2767
+ }
2768
+ function puckerBloatPaths(paths, amount) {
2769
+ const a = amount / 100;
2770
+ if (!a || !Number.isFinite(a)) return paths.slice();
2771
+ return paths.map((p) => {
2772
+ const verts = p.filter((c) => c.type !== "Z");
2773
+ if (!verts.length) return p;
2774
+ const cx = verts.reduce((s, c) => s + c.x, 0) / verts.length;
2775
+ const cy = verts.reduce((s, c) => s + c.y, 0) / verts.length;
2776
+ const mv = (x, y) => ({ x: x + (cx - x) * a, y: y + (cy - y) * a });
2777
+ const mvh = (x, y) => ({ x: x + (x - cx) * a, y: y + (y - cy) * a });
2778
+ const out = [];
2779
+ let prev = { x: 0, y: 0 };
2780
+ let start = { x: 0, y: 0 };
2781
+ const curve = (c1, c2, q) => {
2782
+ const h1 = mvh(c1.x, c1.y);
2783
+ const h2 = mvh(c2.x, c2.y);
2784
+ const e = mv(q.x, q.y);
2785
+ out.push({ type: "C", x1: h1.x, y1: h1.y, x2: h2.x, y2: h2.y, x: e.x, y: e.y });
2786
+ };
2787
+ for (const c of p) {
2788
+ switch (c.type) {
2789
+ case "M": {
2790
+ const e = mv(c.x, c.y);
2791
+ out.push({ type: "M", x: e.x, y: e.y });
2792
+ prev = start = { x: c.x, y: c.y };
2793
+ break;
2794
+ }
2795
+ case "L":
2796
+ curve(prev, c, c);
2797
+ prev = { x: c.x, y: c.y };
2798
+ break;
2799
+ case "Q": {
2800
+ const c1 = { x: prev.x + 2 / 3 * (c.x1 - prev.x), y: prev.y + 2 / 3 * (c.y1 - prev.y) };
2801
+ const c2 = { x: c.x + 2 / 3 * (c.x1 - c.x), y: c.y + 2 / 3 * (c.y1 - c.y) };
2802
+ curve(c1, c2, c);
2803
+ prev = { x: c.x, y: c.y };
2804
+ break;
2805
+ }
2806
+ case "C":
2807
+ curve({ x: c.x1, y: c.y1 }, { x: c.x2, y: c.y2 }, c);
2808
+ prev = { x: c.x, y: c.y };
2809
+ break;
2810
+ case "Z":
2811
+ if (Math.hypot(prev.x - start.x, prev.y - start.y) > EPS2) curve(prev, start, start);
2812
+ out.push({ type: "Z" });
2813
+ prev = start;
2814
+ break;
2815
+ }
2816
+ }
2817
+ return out;
2818
+ });
2819
+ }
2820
+ function roundCornerPaths(paths, radius) {
2821
+ if (!(radius > 0) || !Number.isFinite(radius)) return paths.slice();
2822
+ return paths.map((p) => subpathsToBezier(bezierToSubpaths(p).map((sp) => setSubpathCorners(sp, radius))));
2823
+ }
2824
+ function offsetPaths(paths, amount, join = "miter", miterLimit = 4, copies = 1, copyOffset = 0) {
2825
+ const n = Math.min(1e3, Math.max(1, Math.round(Number.isFinite(copies) ? copies : 1)));
2826
+ return paths.map((p) => {
2827
+ const out = [];
2828
+ for (let i = 0; i < n; i++) out.push(...offsetPath(p, amount * (i + 1 + copyOffset), { join, miterLimit }));
2829
+ return out;
2830
+ });
2831
+ }
2832
+ function twistPaths(paths, angle, center) {
2833
+ let rmax = 0;
2834
+ for (const p of paths) for (const c of p) if (c.type !== "Z") rmax = Math.max(rmax, Math.hypot(c.x - center.x, c.y - center.y));
2835
+ const a = angle * Math.PI / 180;
2836
+ if (!a || rmax <= 1e-9 || !Number.isFinite(a)) return paths.slice();
2837
+ const k = a / rmax;
2838
+ const rot = (v2, th) => {
2839
+ const s = Math.sin(th);
2840
+ const co = Math.cos(th);
2841
+ return [v2[0] * co - v2[1] * s, v2[0] * s + v2[1] * co];
2842
+ };
2843
+ const map = (p) => {
2844
+ const v2 = [p[0] - center.x, p[1] - center.y];
2845
+ const r = rot(v2, k * Math.hypot(v2[0], v2[1]));
2846
+ return [center.x + r[0], center.y + r[1]];
2847
+ };
2848
+ const jac = (p, dv) => {
2849
+ const v2 = [p[0] - center.x, p[1] - center.y];
2850
+ const r = Math.hypot(v2[0], v2[1]);
2851
+ let w = dv;
2852
+ if (r > 1e-12) {
2853
+ const f = k * (v2[0] * dv[0] + v2[1] * dv[1]) / r;
2854
+ w = [dv[0] - v2[1] * f, dv[1] + v2[0] * f];
2855
+ }
2856
+ return rot(w, k * r);
2857
+ };
2858
+ return paths.map((p) => {
2859
+ const out = [];
2860
+ for (const sub5 of subsOf(p)) {
2861
+ const segs = [];
2862
+ for (const seg of sub5.segs) {
2863
+ const cubic = seg.length === 2 ? [seg[0], seg[0], seg[1], seg[1]] : seg;
2864
+ const len2 = segmentLength(seg);
2865
+ const m = Math.min(64, Math.max(1, Math.ceil(Math.abs(a) * len2 / rmax / 0.15 + len2 / 50)));
2866
+ for (let i = 0; i < m; i++) {
2867
+ const q = subSegment(cubic, i / m, (i + 1) / m);
2868
+ const p0 = map(q[0]);
2869
+ const p3 = map(q[3]);
2870
+ const h1 = jac(q[0], [q[1][0] - q[0][0], q[1][1] - q[0][1]]);
2871
+ const h2 = jac(q[3], [q[2][0] - q[3][0], q[2][1] - q[3][1]]);
2872
+ segs.push([p0, [p0[0] + h1[0], p0[1] + h1[1]], [p3[0] + h2[0], p3[1] + h2[1]], p3]);
2873
+ }
2874
+ }
2875
+ out.push({ segs, closed: sub5.closed });
2876
+ }
2877
+ return subsToPath(out);
2878
+ });
2879
+ }
2880
+ function resample(sub5, m) {
2881
+ const segs = sub5.segs;
2882
+ const n = segs.length;
2883
+ const lens = segs.map((s) => segmentLength(s));
2884
+ const out = [];
2885
+ for (let j = 0; j < n; j++) {
2886
+ const piece = lens[j] / m;
2887
+ for (let k = 0; k < m; k++) {
2888
+ if (k === 0) {
2889
+ const tout = startTangent(segs[j]);
2890
+ const prev = sub5.closed || j > 0 ? (j + n - 1) % n : null;
2891
+ let tan = tout;
2892
+ if (prev !== null) {
2893
+ const tin = endTangent(segs[prev]);
2894
+ tan = unit2(tin[0] + tout[0], tin[1] + tout[1]) ?? tout;
2895
+ }
2896
+ out.push({ p: segs[j][0], tan, lenPrev: prev === null ? 0 : lens[prev] / m, lenNext: piece });
2897
+ } else {
2898
+ const t = segmentParamAtLength(segs[j], piece * k, lens[j]);
2899
+ out.push({ p: segmentPoint(segs[j], t), tan: tangentAt(segs[j], t), lenPrev: piece, lenNext: piece });
2900
+ }
2901
+ }
2902
+ }
2903
+ if (!sub5.closed) {
2904
+ const last = segs[n - 1];
2905
+ out.push({ p: last[last.length - 1], tan: endTangent(last), lenPrev: lens[n - 1] / m, lenNext: 0 });
2906
+ }
2907
+ return out;
2908
+ }
2909
+ function emit(samples, disp, closed, smooth, handle) {
2910
+ const n = samples.length;
2911
+ if (!n) return null;
2912
+ const pt2 = (i) => [samples[i].p[0] + disp[i][0], samples[i].p[1] + disp[i][1]];
2913
+ const segs = [];
2914
+ const count = closed ? n : n - 1;
2915
+ for (let i = 0; i < count; i++) {
2916
+ const j = (i + 1) % n;
2917
+ const a = samples[i];
2918
+ const b = samples[j];
2919
+ const p = pt2(i);
2920
+ const q = pt2(j);
2921
+ if (smooth) segs.push([p, [p[0] + a.tan[0] * a.lenNext * handle, p[1] + a.tan[1] * a.lenNext * handle], [q[0] - b.tan[0] * b.lenPrev * handle, q[1] - b.tan[1] * b.lenPrev * handle], q]);
2922
+ else segs.push([p, q]);
2923
+ }
2924
+ if (!segs.length) return null;
2925
+ return { segs, closed };
2926
+ }
2927
+ function zigzagPaths(paths, size, ridges, smooth = false) {
2928
+ const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(ridges) ? ridges : 0))) + 1;
2929
+ return paths.map((p) => {
2930
+ const out = [];
2931
+ for (const sub5 of subsOf(p)) {
2932
+ const s = resample(sub5, m);
2933
+ const disp = s.map((x, i) => {
2934
+ const n = perp2(x.tan);
2935
+ const d = i % 2 === 0 ? size : -size;
2936
+ return [n[0] * d, n[1] * d];
2937
+ });
2938
+ const e = emit(s, disp, sub5.closed, smooth, 0.36);
2939
+ if (e) out.push(e);
2940
+ }
2941
+ return subsToPath(out);
2942
+ });
2943
+ }
2944
+ var MASK64 = (1n << 64n) - 1n;
2945
+ function splitmix(x) {
2946
+ x = x + 0x9e3779b97f4a7c15n & MASK64;
2947
+ let z = x;
2948
+ z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK64;
2949
+ z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK64;
2950
+ return z ^ z >> 31n;
2951
+ }
2952
+ function hash(seed, idx, t) {
2953
+ const h = splitmix(splitmix(splitmix(seed) ^ idx) ^ BigInt.asUintN(64, BigInt(t)));
2954
+ return Number(h >> 11n) / 2 ** 53 * 2 - 1;
2955
+ }
2956
+ function noise(seed, idx, t) {
2957
+ const i = Math.floor(t);
2958
+ const f = t - i;
2959
+ const p0 = hash(seed, idx, i - 1);
2960
+ const p1 = hash(seed, idx, i);
2961
+ const p2 = hash(seed, idx, i + 1);
2962
+ const p3 = hash(seed, idx, i + 2);
2963
+ const v2 = 0.5 * (2 * p1 + (-p0 + p2) * f + (2 * p0 - 5 * p1 + 4 * p2 - p3) * f * f + (-p0 + 3 * p1 - 3 * p2 + p3) * f * f * f);
2964
+ return Math.min(1, Math.max(-1, v2));
2965
+ }
2966
+ function wigglePaths(paths, w) {
2967
+ if (!w.size || !Number.isFinite(w.size)) return paths.slice();
2968
+ const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(w.detail) ? w.detail : 0))) + 1;
2969
+ const seed = BigInt.asUintN(64, BigInt(Math.round(Number.isFinite(w.seed) ? w.seed : 0)));
2970
+ const t = (Number.isFinite(w.time) ? w.time : 0) * (Number.isFinite(w.speed) ? w.speed : 0) + (Number.isFinite(w.phase) ? w.phase : 0) / 360;
2971
+ const corr = Math.min(1, Math.max(0, (Number.isFinite(w.correlation) ? w.correlation : 0) / 100));
2972
+ const sx = noise(seed, MASK64, t);
2973
+ const sy = noise(seed, MASK64 - 1n, t);
2974
+ let idx = 0;
2975
+ return paths.map((p) => {
2976
+ const out = [];
2977
+ for (const sub5 of subsOf(p)) {
2978
+ const s = resample(sub5, m);
2979
+ const disp = s.map(() => {
2980
+ const i = BigInt(idx);
2981
+ const ox = noise(seed, i * 2n, t + idx * 0.37);
2982
+ const oy = noise(seed, i * 2n + 1n, t + idx * 0.61);
2983
+ idx++;
2984
+ return [(sx * corr + ox * (1 - corr)) * w.size, (sy * corr + oy * (1 - corr)) * w.size];
2985
+ });
2986
+ const e = emit(s, disp, sub5.closed, w.smooth, 1 / 3);
2987
+ if (e) out.push(e);
2988
+ }
2989
+ return subsToPath(out);
2990
+ });
2991
+ }
2992
+ var MERGE_OPS = { add: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
2993
+ function mergePaths(paths, mode = "merge") {
2994
+ if (mode === "merge" || !(mode in MERGE_OPS)) return paths.flat();
2995
+ const inputs = paths.filter((p) => p.length > 0).map((path) => ({ path, fillRule: "nonzero" }));
2996
+ if (!inputs.length) return [];
2997
+ return pathBoolean(MERGE_OPS[mode], inputs).path;
2998
+ }
2999
+ function vectorRepeaterCopies(r, pivot) {
3000
+ const copies = Math.max(0, Number.isFinite(r.copies) ? Math.min(r.copies, 1e3) : 0);
3001
+ const n = Math.ceil(copies);
3002
+ const offset = Number.isFinite(r.offset) ? r.offset : 0;
3003
+ const so = (r.startOpacity ?? 100) / 100;
3004
+ const eo = (r.endOpacity ?? 100) / 100;
3005
+ const fract = copies - Math.floor(copies);
3006
+ const out = [];
3007
+ for (let i = 0; i < n; i++) {
3008
+ const k = i + offset;
3009
+ let m = translateMatrix(-pivot.x, -pivot.y);
3010
+ m = multiplyMatrices(m, scaleMatrix(Math.pow((r.scale?.x ?? 100) / 100, k), Math.pow((r.scale?.y ?? 100) / 100, k)));
3011
+ m = multiplyMatrices(m, rotateMatrix((r.rotation ?? 0) * k));
3012
+ m = multiplyMatrices(m, translateMatrix(pivot.x + (r.position?.x ?? 0) * k, pivot.y + (r.position?.y ?? 0) * k));
3013
+ const t = n > 1 ? i / (n - 1) : 0;
3014
+ const partial = i + 1 === n && fract > 0 ? fract : 1;
3015
+ out.push({ index: i, matrix: m, opacity: Math.min(1, Math.max(0, (so + (eo - so) * t) * partial)) });
3016
+ }
3017
+ return out;
3018
+ }
3019
+ function repeatPaths(paths, r, pivot) {
3020
+ return vectorRepeaterCopies(r, pivot).map((copy) => ({ copy, paths: paths.map((p) => transformBezierPath(p, copy.matrix)) }));
3021
+ }
3022
+ function pathsCenter(paths) {
3023
+ const b = bezierPathBounds(paths.flat());
3024
+ return b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };
3025
+ }
3026
+
2652
3027
  // src/vector/pathMap.ts
2653
3028
  var MAX_PIECES = 1 << 20;
2654
3029
  var MAX_DEPTH = 6;
@@ -3025,14 +3400,14 @@ function lineHits(p, q, seg) {
3025
3400
  return out;
3026
3401
  }
3027
3402
  var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
3028
- var degenerate = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
3403
+ var degenerate2 = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
3029
3404
  function arc(l, a, b, eps) {
3030
3405
  const n = l.segs.length;
3031
3406
  const segs = [];
3032
3407
  const chord = [];
3033
3408
  const push = (k, t0, t1) => {
3034
3409
  const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : subSegment(l.segs[k], t0, t1);
3035
- if (!degenerate(piece, eps)) {
3410
+ if (!degenerate2(piece, eps)) {
3036
3411
  segs.push(piece);
3037
3412
  chord.push(l.chord[k]);
3038
3413
  }
@@ -4109,8 +4484,8 @@ function createOutlineTextShaper(fonts, options = {}) {
4109
4484
  const fallbackKey = (options.fallback ?? entries[0].family).toLowerCase();
4110
4485
  const pick = (font) => {
4111
4486
  if (font.postScriptName) {
4112
- const exact = entries.find((e) => e.postScript === font.postScriptName);
4113
- if (exact) return { entry: exact, available: true };
4487
+ const exact2 = entries.find((e) => e.postScript === font.postScriptName);
4488
+ if (exact2) return { entry: exact2, available: true };
4114
4489
  }
4115
4490
  const key = font.family.trim().toLowerCase();
4116
4491
  let pool = entries.filter((e) => e.key === key);
@@ -5872,7 +6247,8 @@ var VECTOR_EFFECT_DEFAULTS = {
5872
6247
  endOpacity: 100,
5873
6248
  composite: "below"
5874
6249
  },
5875
- envelope: { style: "arc", bend: 50, horizontal: 0, vertical: 0, orientation: "horizontal", rows: 2, columns: 2, mesh: null }
6250
+ envelope: { style: "arc", bend: 50, horizontal: 0, vertical: 0, orientation: "horizontal", rows: 2, columns: 2, mesh: null },
6251
+ contour: { direction: "inside", steps: 4, offset: 6, join: "round", miterLimit: 4, fillColor: "#ffffff", strokeColor: null, acceleration: 0 }
5876
6252
  };
5877
6253
  var VECTOR_EFFECT_LABELS = {
5878
6254
  trim: "Trim Paths",
@@ -5884,7 +6260,8 @@ var VECTOR_EFFECT_LABELS = {
5884
6260
  offset: "Offset Paths",
5885
6261
  merge: "Merge Paths",
5886
6262
  repeater: "Repeater",
5887
- envelope: "Envelope"
6263
+ envelope: "Envelope",
6264
+ contour: "Contour"
5888
6265
  };
5889
6266
  var VECTOR_EFFECT_PARAM_SPECS = {
5890
6267
  trim: [
@@ -5952,6 +6329,33 @@ var VECTOR_EFFECT_PARAM_SPECS = {
5952
6329
  ]
5953
6330
  }
5954
6331
  ],
6332
+ contour: [
6333
+ {
6334
+ key: "direction",
6335
+ label: "Direction",
6336
+ kind: "select",
6337
+ options: [
6338
+ { value: "inside", label: "Inside" },
6339
+ { value: "outside", label: "Outside" },
6340
+ { value: "center", label: "To centre" }
6341
+ ]
6342
+ },
6343
+ { key: "steps", label: "Steps", kind: "number", min: 1, max: 200, step: 1, slider: true },
6344
+ { key: "offset", label: "Offset", kind: "number", min: 0.1, max: 200, step: 0.5, slider: true },
6345
+ { key: "fillColor", label: "Fill to", kind: "color" },
6346
+ { key: "strokeColor", label: "Stroke to", kind: "color" },
6347
+ { key: "acceleration", label: "Acceleration", kind: "number", min: -100, max: 100, step: 1, slider: true },
6348
+ {
6349
+ key: "join",
6350
+ label: "Join",
6351
+ kind: "select",
6352
+ options: [
6353
+ { value: "miter", label: "Miter" },
6354
+ { value: "round", label: "Round" },
6355
+ { value: "bevel", label: "Bevel" }
6356
+ ]
6357
+ }
6358
+ ],
5955
6359
  repeater: [
5956
6360
  { key: "copies", label: "Copies", kind: "number", min: 0, max: 50, step: 0.1, slider: true },
5957
6361
  { key: "offset", label: "Offset", kind: "number", min: -10, max: 10, step: 0.1, slider: true },
@@ -6041,6 +6445,7 @@ function normalizeVectorEffectParams(type, params, base) {
6041
6445
  else if (spec.kind === "boolean" && typeof v2 === "boolean") out[spec.key] = v2;
6042
6446
  else if (spec.kind === "vec2" && isVec2(v2)) out[spec.key] = { x: v2.x, y: v2.y };
6043
6447
  else if (spec.kind === "select" && spec.options.some((o) => o.value === v2)) out[spec.key] = v2;
6448
+ else if (spec.kind === "color" && (v2 === null || typeof v2 === "string" && parseVectorColor(v2))) out[spec.key] = v2;
6044
6449
  }
6045
6450
  if (type === "envelope") {
6046
6451
  const o = out;
@@ -6066,7 +6471,7 @@ function activeVectorEffects(node) {
6066
6471
  return node.effects?.filter(isEnabled) ?? [];
6067
6472
  }
6068
6473
  function hasOwnVectorAppearance(node) {
6069
- return node.type === "text" || node.type === "instance" || node.type === "group" && !!node.liveBoolean || isVectorFrame(node) || !!node.effects?.some(isEnabled);
6474
+ return node.type === "text" || node.type === "instance" || node.type === "group" && !!node.liveBoolean || isVectorFrame(node) || isVectorSymmetryGroup(node) || !!node.effects?.some(isEnabled);
6070
6475
  }
6071
6476
  var liveCache = /* @__PURE__ */ new WeakMap();
6072
6477
  function hasVectorAppearance(node) {
@@ -6200,6 +6605,37 @@ var mapPaths = (shapes, fn) => {
6200
6605
  const out = fn(shapes.map((s) => s.path));
6201
6606
  return shapes.map((s, i) => ({ ...s, path: out[i] ?? [] }));
6202
6607
  };
6608
+ function blendPaint(from, to, t) {
6609
+ if (to === null || typeof from !== "string") return from;
6610
+ const a = parseVectorColor(from);
6611
+ const b = parseVectorColor(to);
6612
+ if (!a || !b) return from;
6613
+ const h = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255).toString(16).padStart(2, "0");
6614
+ const m = a.map((v2, k) => v2 + (b[k] - v2) * t);
6615
+ return `#${h(m[0])}${h(m[1])}${h(m[2])}`;
6616
+ }
6617
+ function contourShapes(shapes, c) {
6618
+ const out = [];
6619
+ const step = Math.max(0.01, c.offset);
6620
+ const outside = c.direction === "outside";
6621
+ for (const s of shapes) {
6622
+ let n = Math.min(200, Math.max(1, Math.round(c.steps)));
6623
+ if (c.direction === "center") {
6624
+ const b = bezierPathBounds(s.path);
6625
+ n = b ? Math.min(200, Math.max(1, Math.ceil(Math.min(b.maxX - b.minX, b.maxY - b.minY) / 2 / step))) : 0;
6626
+ }
6627
+ out.push({ ...s, order: [outside ? n : 0, ...s.order] });
6628
+ const power = 2 ** (Math.min(100, Math.max(-100, c.acceleration)) / 50);
6629
+ for (let k = 1; k <= n; k++) {
6630
+ const [path] = offsetPaths([s.path], (outside ? 1 : -1) * step * k, c.join, c.miterLimit, 1, 0);
6631
+ if (!path?.length) break;
6632
+ const t = (k / n) ** power;
6633
+ const source = { ...s.source, fill: blendPaint(s.source.fill, c.fillColor, t) ?? null, stroke: blendPaint(s.source.stroke, c.strokeColor, t) ?? null, fills: void 0, strokes: void 0 };
6634
+ out.push({ source, path, opacity: s.opacity, order: [outside ? n - k : k, ...s.order] });
6635
+ }
6636
+ }
6637
+ return out;
6638
+ }
6203
6639
  function applyVectorEffects(shapes, effects) {
6204
6640
  let list = shapes.slice();
6205
6641
  for (const e of effects ?? []) {
@@ -6240,6 +6676,9 @@ function applyVectorEffects(shapes, effects) {
6240
6676
  list = mapPaths(list, (p) => envelopePaths(p, params));
6241
6677
  break;
6242
6678
  }
6679
+ case "contour":
6680
+ list = contourShapes(list, e.params);
6681
+ break;
6243
6682
  case "repeater": {
6244
6683
  const r = e.params;
6245
6684
  const copies = repeatPaths(
@@ -6333,8 +6772,8 @@ function vectorAppearanceShapes(node) {
6333
6772
  shapeCache.set(node, [vectorTextShaperGeneration(), shapes]);
6334
6773
  return shapes;
6335
6774
  }
6336
- if (isVectorFrame(node)) {
6337
- const shapes = vectorAppearanceShapes(vectorFrameDrawn(node));
6775
+ if (isVectorFrame(node) || isVectorSymmetryGroup(node)) {
6776
+ const shapes = vectorAppearanceShapes(isVectorFrame(node) ? vectorFrameDrawn(node) : vectorSymmetryDrawn(node));
6338
6777
  shapeCache.set(node, [vectorTextShaperGeneration(), shapes]);
6339
6778
  return shapes;
6340
6779
  }
@@ -6396,8 +6835,8 @@ function expandVectorAppearance(node) {
6396
6835
  expandCache.set(node, [vectorTextShaperGeneration(), drawn]);
6397
6836
  return drawn;
6398
6837
  }
6399
- if (isVectorFrame(node)) {
6400
- const drawn = vectorFrameDrawn(node);
6838
+ if (isVectorFrame(node) || isVectorSymmetryGroup(node)) {
6839
+ const drawn = isVectorFrame(node) ? vectorFrameDrawn(node) : vectorSymmetryDrawn(node);
6401
6840
  const out2 = hasVectorAppearance(drawn) ? expandVectorAppearance(drawn) : drawn;
6402
6841
  expandCache.set(node, [vectorTextShaperGeneration(), out2]);
6403
6842
  return out2;
@@ -6789,7 +7228,7 @@ var add4 = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
6789
7228
  var sub4 = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
6790
7229
  var scale2 = (a, k) => ({ x: a.x * k, y: a.y * k });
6791
7230
  var left = (d) => ({ x: d.y, y: -d.x });
6792
- var unit2 = (v2) => {
7231
+ var unit3 = (v2) => {
6793
7232
  const l = Math.hypot(v2.x, v2.y);
6794
7233
  return l > 1e-12 ? { x: v2.x / l, y: v2.y / l } : { x: 1, y: 0 };
6795
7234
  };
@@ -6846,7 +7285,7 @@ function vectorStrokeGradientDefs(g, id, node, options = {}) {
6846
7285
  for (let i = 0; i < segCount; i++) {
6847
7286
  const a = pts[i];
6848
7287
  const b = pts[(i + 1) % n];
6849
- dirs.push(unit2(sub4(b, a)));
7288
+ dirs.push(unit3(sub4(b, a)));
6850
7289
  lengths.push(lengths[i] + Math.hypot(b.x - a.x, b.y - a.y));
6851
7290
  }
6852
7291
  const total = lengths[segCount];
@@ -7344,9 +7783,9 @@ function vectorDocumentToSvg(doc, options = {}) {
7344
7783
  };
7345
7784
  const emitInstance = (node, depth) => {
7346
7785
  const data = { symbolId: node.symbolId, ...node.scaleStrokes === false ? { scaleStrokes: false } : {}, ...node.overrides ? { overrides: node.overrides } : {} };
7347
- const exact = !node.overrides && node.scaleStrokes !== false && !node.effects?.length && !node.filters?.length;
7786
+ const exact2 = !node.overrides && node.scaleStrokes !== false && !node.effects?.length && !node.filters?.length;
7348
7787
  const ref = symbolRef(node.symbolId);
7349
- if (exact && ref) {
7788
+ if (exact2 && ref) {
7350
7789
  lines.push(`${indent(depth)}<use${common(node)} href="#${escapeAttr2(ref)}" data-vector-instance="${escapeAttr2(JSON.stringify(data))}"/>`);
7351
7790
  return;
7352
7791
  }
@@ -7397,7 +7836,8 @@ function vectorDocumentToSvg(doc, options = {}) {
7397
7836
  const live2 = {
7398
7837
  kind: node.type,
7399
7838
  ...node.effects?.length ? { effects: node.effects } : {},
7400
- ...node.type === "group" && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {}
7839
+ ...node.type === "group" && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {},
7840
+ ...node.type === "group" && node.symmetry ? { symmetry: node.symmetry } : {}
7401
7841
  };
7402
7842
  lines.push(`${indent(depth)}<g${common(node)} data-vector-live="${escapeAttr2(JSON.stringify(live2))}">`);
7403
7843
  lines.push(`${indent(depth + 1)}<g data-vector-part="appearance">`);
@@ -7960,6 +8400,8 @@ function parseVectorSvg(svg, options = {}) {
7960
8400
  const group = { id, type: "group", ...meta, children: inner };
7961
8401
  if (effects.length) group.effects = transformVectorEffects(effects, m);
7962
8402
  if (liveBoolean) group.liveBoolean = liveBoolean;
8403
+ const symmetry = sanitizeVectorSymmetry(raw.symmetry);
8404
+ if (symmetry) group.symmetry = transformVectorSymmetry(symmetry, m);
7963
8405
  const blend = blendFromAttribute(g.attrs["data-vector-blend"], m);
7964
8406
  if (blend) group.blend = blend;
7965
8407
  return group;
@@ -8424,7 +8866,7 @@ function vectorNodesNear(nodes, rect, pad = 0) {
8424
8866
  }
8425
8867
 
8426
8868
  // src/vector/geometry.ts
8427
- var EPS2 = 1e-9;
8869
+ var EPS3 = 1e-9;
8428
8870
  var cloneAnchor = (a) => ({
8429
8871
  x: a.x,
8430
8872
  y: a.y,
@@ -8450,7 +8892,7 @@ function hasPaint(paint) {
8450
8892
  return true;
8451
8893
  }
8452
8894
  var handleOf = (a, side) => a[side] ?? { x: a.x, y: a.y };
8453
- var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS2 && Math.abs(h.y - a.y) < EPS2;
8895
+ var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS3 && Math.abs(h.y - a.y) < EPS3;
8454
8896
  function subpathSegmentCount(sp) {
8455
8897
  const n = sp.anchors.length;
8456
8898
  if (n < 2) return 0;
@@ -8732,8 +9174,9 @@ function bakeIntoGeometry(node, m) {
8732
9174
  if (node.type === "group") {
8733
9175
  const group = node;
8734
9176
  const children = group.children.map((c) => transformVectorNode(c, m));
9177
+ const sym2 = group.symmetry ? { symmetry: transformVectorSymmetry(group.symmetry, m) } : {};
8735
9178
  const spine = group.blend?.spine;
8736
- if (!spine) return { ...group, children };
9179
+ if (!spine) return { ...group, ...sym2, children };
8737
9180
  const moved = { closed: spine.closed, anchors: spine.anchors.map((a) => mapAnchor(a, m)) };
8738
9181
  return { ...group, children, blend: { ...group.blend, spine: moved } };
8739
9182
  }
@@ -8837,7 +9280,7 @@ function moveAnchorHandle(a, side, pos, breakHandles = false) {
8837
9280
  const dy = pos.y - a.y;
8838
9281
  const len2 = Math.hypot(dx, dy);
8839
9282
  if (type === "symmetric") next[other] = { x: a.x - dx, y: a.y - dy };
8840
- else if (type === "smooth" && len2 > EPS2) {
9283
+ else if (type === "smooth" && len2 > EPS3) {
8841
9284
  const o = a[other];
8842
9285
  const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len2;
8843
9286
  next[other] = { x: a.x - dx / len2 * olen, y: a.y - dy / len2 * olen };
@@ -8867,15 +9310,15 @@ function convertAnchor(sp, index, type) {
8867
9310
  else if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };
8868
9311
  else if (next) dir = { x: next.x - a.x, y: next.y - a.y };
8869
9312
  else if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };
8870
- if (!dir || Math.hypot(dir.x, dir.y) < EPS2) return { anchors, closed: sp.closed };
9313
+ if (!dir || Math.hypot(dir.x, dir.y) < EPS3) return { anchors, closed: sp.closed };
8871
9314
  const dl = Math.hypot(dir.x, dir.y);
8872
9315
  const ux = dir.x / dl;
8873
9316
  const uy = dir.y / dl;
8874
9317
  lenIn = a.in && !isOnAnchor(a, a.in) ? Math.hypot(a.in.x - a.x, a.in.y - a.y) : prev ? Math.hypot(prev.x - a.x, prev.y - a.y) / 3 : 0;
8875
9318
  lenOut = a.out && !isOnAnchor(a, a.out) ? Math.hypot(a.out.x - a.x, a.out.y - a.y) : next ? Math.hypot(next.x - a.x, next.y - a.y) / 3 : 0;
8876
9319
  if (type === "symmetric") lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);
8877
- if (lenIn > EPS2) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
8878
- if (lenOut > EPS2) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };
9320
+ if (lenIn > EPS3) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
9321
+ if (lenOut > EPS3) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };
8879
9322
  a.type = type;
8880
9323
  return { anchors, closed: sp.closed };
8881
9324
  }
@@ -8910,444 +9353,167 @@ function reverseSubpath(sp) {
8910
9353
  const next = { x: a.x, y: a.y };
8911
9354
  if (a.out) next.in = { ...a.out };
8912
9355
  if (a.in) next.out = { ...a.in };
8913
- if (a.type) next.type = a.type;
8914
- if (a.corner) next.corner = { ...a.corner };
8915
- if (a.id) next.id = a.id;
8916
- return next;
8917
- });
8918
- return { anchors, closed: sp.closed };
8919
- }
8920
- var KAPPA2 = 0.5522847498307936;
8921
- function rectSubpath(b, radius = 0, kind) {
8922
- const { x, y, width: w, height: h } = b;
8923
- const sp = { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };
8924
- return radius > 0 ? setSubpathCorners(sp, radius, kind) : sp;
8925
- }
8926
- function ellipseSubpath(b) {
8927
- const rx = b.width / 2;
8928
- const ry = b.height / 2;
8929
- const cx = b.x + rx;
8930
- const cy = b.y + ry;
8931
- const kx = rx * KAPPA2;
8932
- const ky = ry * KAPPA2;
8933
- return {
8934
- closed: true,
8935
- anchors: [
8936
- { x: cx, y: cy - ry, in: { x: cx - kx, y: cy - ry }, out: { x: cx + kx, y: cy - ry }, type: "symmetric" },
8937
- { x: cx + rx, y: cy, in: { x: cx + rx, y: cy - ky }, out: { x: cx + rx, y: cy + ky }, type: "symmetric" },
8938
- { x: cx, y: cy + ry, in: { x: cx + kx, y: cy + ry }, out: { x: cx - kx, y: cy + ry }, type: "symmetric" },
8939
- { x: cx - rx, y: cy, in: { x: cx - rx, y: cy + ky }, out: { x: cx - rx, y: cy - ky }, type: "symmetric" }
8940
- ]
8941
- };
8942
- }
8943
- function polygonSubpath(b, sides = 6) {
8944
- const n = Math.max(3, Math.round(sides));
8945
- const cx = b.x + b.width / 2;
8946
- const cy = b.y + b.height / 2;
8947
- const anchors = [];
8948
- for (let i = 0; i < n; i++) {
8949
- const t = -Math.PI / 2 + i * 2 * Math.PI / n;
8950
- anchors.push({ x: cx + Math.cos(t) * b.width / 2, y: cy + Math.sin(t) * b.height / 2 });
8951
- }
8952
- return { anchors, closed: true };
8953
- }
8954
- function starSubpath(b, points = 5, innerRatio = 0.5) {
8955
- const n = Math.max(3, Math.round(points));
8956
- const cx = b.x + b.width / 2;
8957
- const cy = b.y + b.height / 2;
8958
- const anchors = [];
8959
- for (let i = 0; i < n * 2; i++) {
8960
- const t = -Math.PI / 2 + i * Math.PI / n;
8961
- const r = i % 2 === 0 ? 1 : innerRatio;
8962
- anchors.push({ x: cx + Math.cos(t) * r * b.width / 2, y: cy + Math.sin(t) * r * b.height / 2 });
8963
- }
8964
- return { anchors, closed: true };
8965
- }
8966
- function lineSubpath(a, b) {
8967
- return { anchors: [{ x: a.x, y: a.y }, { x: b.x, y: b.y }], closed: false };
8968
- }
8969
- function roundSubpaths(subpaths, precision = 3) {
8970
- const f = 10 ** precision;
8971
- const r = (n) => Math.round(n * f) / f;
8972
- const rp = (p) => ({ x: r(p.x), y: r(p.y) });
8973
- return subpaths.map((sp) => ({
8974
- closed: sp.closed,
8975
- anchors: sp.anchors.map((a) => ({ ...cloneAnchor(a), x: r(a.x), y: r(a.y), ...a.in ? { in: rp(a.in) } : {}, ...a.out ? { out: rp(a.out) } : {} }))
8976
- }));
8977
- }
8978
-
8979
- // src/vector/pathEffects.ts
8980
- var EPS3 = 1e-9;
8981
- var degenerate2 = (s) => s.every((p) => Math.abs(p[0] - s[0][0]) < EPS3 && Math.abs(p[1] - s[0][1]) < EPS3);
8982
- function raise(seg) {
8983
- if (seg.length !== 3) return seg.map((p) => [p[0], p[1]]);
8984
- const [p0, p1, p2] = seg;
8985
- return [p0, [p0[0] + 2 / 3 * (p1[0] - p0[0]), p0[1] + 2 / 3 * (p1[1] - p0[1])], [p2[0] + 2 / 3 * (p1[0] - p2[0]), p2[1] + 2 / 3 * (p1[1] - p2[1])], p2];
8986
- }
8987
- function subsOf(path) {
8988
- return pathToContours(path).map((c) => ({ segs: closeContour(c).filter((s) => !degenerate2(s)).map(raise), closed: c.closed })).filter((s) => s.segs.length > 0);
8989
- }
8990
- var subsToPath = (subs) => contoursToPath(subs.map((s) => ({ segments: s.segs, closed: s.closed })));
8991
- var unit3 = (x, y) => {
8992
- const l = Math.hypot(x, y);
8993
- return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
8994
- };
8995
- function startTangent(s) {
8996
- for (let i = 1; i < s.length; i++) {
8997
- const u = unit3(s[i][0] - s[0][0], s[i][1] - s[0][1]);
8998
- if (u) return u;
8999
- }
9000
- return [1, 0];
9001
- }
9002
- function endTangent(s) {
9003
- const end = s[s.length - 1];
9004
- for (let i = s.length - 2; i >= 0; i--) {
9005
- const u = unit3(end[0] - s[i][0], end[1] - s[i][1]);
9006
- if (u) return u;
9007
- }
9008
- return [1, 0];
9009
- }
9010
- function tangentAt(s, t) {
9011
- if (t <= 1e-9) return startTangent(s);
9012
- if (t >= 1 - 1e-9) return endTangent(s);
9013
- const d = segmentDerivative(s, t);
9014
- return unit3(d[0], d[1]) ?? startTangent(s);
9015
- }
9016
- var perp2 = (v2) => [-v2[1], v2[0]];
9017
- function effectPathLength(path) {
9018
- let total = 0;
9019
- for (const s of subsOf(path)) for (const seg of s.segs) total += segmentLength(seg);
9020
- return total;
9021
- }
9022
- function effectPathVertexCount(path) {
9023
- return subsOf(path).reduce((n, s) => n + s.segs.length + (s.closed ? 0 : 1), 0);
9024
- }
9025
- var Measured = class {
9026
- segs;
9027
- lens;
9028
- total;
9029
- constructor(path) {
9030
- this.segs = subsOf(path).flatMap((s) => s.segs);
9031
- this.lens = this.segs.map((s) => segmentLength(s));
9032
- this.total = this.lens.reduce((a, b) => a + b, 0);
9033
- }
9034
- /**
9035
- * Appends the part between lengths `sa` and `sb`, continuing the last piece when this one
9036
- * starts where it ends (across the start of a closed path).
9037
- */
9038
- append(sa, sb, out) {
9039
- for (const piece of trimSegments(this.segs, sa, sb, this.lens)) {
9040
- if (degenerate2(piece)) continue;
9041
- const last = out[out.length - 1]?.segs;
9042
- const end = last?.[last.length - 1]?.at(-1);
9043
- if (!end || Math.hypot(end[0] - piece[0][0], end[1] - piece[0][1]) > 1e-6) out.push({ segs: [piece], closed: false });
9044
- else last.push(piece);
9045
- }
9046
- }
9047
- };
9048
- function trimIntervals(start, end, offsetDegrees) {
9049
- let s = start / 100;
9050
- let e = end / 100;
9051
- if (!Number.isFinite(s) || !Number.isFinite(e)) return null;
9052
- if (s > e) [s, e] = [e, s];
9053
- s = Math.min(1, Math.max(0, s));
9054
- e = Math.min(1, Math.max(0, e));
9055
- if (e - s >= 1 - 1e-9) return null;
9056
- if (e - s <= 1e-9) return [];
9057
- const off = Number.isFinite(offsetDegrees) ? (offsetDegrees / 360 % 1 + 1) % 1 : 0;
9058
- s += off;
9059
- e += off;
9060
- if (e <= 1) return [[s, e]];
9061
- if (s >= 1) return [[s - 1, e - 1]];
9062
- return [
9063
- [s, 1],
9064
- [0, e - 1]
9065
- ];
9066
- }
9067
- function trimPaths(paths, start, end, offsetDegrees = 0) {
9068
- const iv = trimIntervals(start, end, offsetDegrees);
9069
- if (!iv) return paths.slice();
9070
- return paths.map((p) => {
9071
- const m = new Measured(p);
9072
- const out = [];
9073
- if (m.total > 0) for (const [a, b] of iv) m.append(a * m.total, b * m.total, out);
9074
- return subsToPath(out);
9075
- });
9076
- }
9077
- function trimPathsIndividually(paths, start, end, offsetDegrees = 0) {
9078
- const iv = trimIntervals(start, end, offsetDegrees);
9079
- if (!iv) return paths.slice();
9080
- const ms = paths.map((p) => new Measured(p));
9081
- const total = ms.reduce((n, m) => n + m.total, 0);
9082
- let base = 0;
9083
- return ms.map((m) => {
9084
- const out = [];
9085
- for (const [a, b] of iv) {
9086
- const la = Math.max(0, a * total - base);
9087
- const lb = Math.min(m.total, b * total - base);
9088
- if (lb > la) m.append(la, lb, out);
9089
- }
9090
- base += m.total;
9091
- return subsToPath(out);
9092
- });
9093
- }
9094
- function puckerBloatPaths(paths, amount) {
9095
- const a = amount / 100;
9096
- if (!a || !Number.isFinite(a)) return paths.slice();
9097
- return paths.map((p) => {
9098
- const verts = p.filter((c) => c.type !== "Z");
9099
- if (!verts.length) return p;
9100
- const cx = verts.reduce((s, c) => s + c.x, 0) / verts.length;
9101
- const cy = verts.reduce((s, c) => s + c.y, 0) / verts.length;
9102
- const mv = (x, y) => ({ x: x + (cx - x) * a, y: y + (cy - y) * a });
9103
- const mvh = (x, y) => ({ x: x + (x - cx) * a, y: y + (y - cy) * a });
9104
- const out = [];
9105
- let prev = { x: 0, y: 0 };
9106
- let start = { x: 0, y: 0 };
9107
- const curve = (c1, c2, q) => {
9108
- const h1 = mvh(c1.x, c1.y);
9109
- const h2 = mvh(c2.x, c2.y);
9110
- const e = mv(q.x, q.y);
9111
- out.push({ type: "C", x1: h1.x, y1: h1.y, x2: h2.x, y2: h2.y, x: e.x, y: e.y });
9112
- };
9113
- for (const c of p) {
9114
- switch (c.type) {
9115
- case "M": {
9116
- const e = mv(c.x, c.y);
9117
- out.push({ type: "M", x: e.x, y: e.y });
9118
- prev = start = { x: c.x, y: c.y };
9119
- break;
9120
- }
9121
- case "L":
9122
- curve(prev, c, c);
9123
- prev = { x: c.x, y: c.y };
9124
- break;
9125
- case "Q": {
9126
- const c1 = { x: prev.x + 2 / 3 * (c.x1 - prev.x), y: prev.y + 2 / 3 * (c.y1 - prev.y) };
9127
- const c2 = { x: c.x + 2 / 3 * (c.x1 - c.x), y: c.y + 2 / 3 * (c.y1 - c.y) };
9128
- curve(c1, c2, c);
9129
- prev = { x: c.x, y: c.y };
9130
- break;
9131
- }
9132
- case "C":
9133
- curve({ x: c.x1, y: c.y1 }, { x: c.x2, y: c.y2 }, c);
9134
- prev = { x: c.x, y: c.y };
9135
- break;
9136
- case "Z":
9137
- if (Math.hypot(prev.x - start.x, prev.y - start.y) > EPS3) curve(prev, start, start);
9138
- out.push({ type: "Z" });
9139
- prev = start;
9140
- break;
9141
- }
9142
- }
9143
- return out;
9356
+ if (a.type) next.type = a.type;
9357
+ if (a.corner) next.corner = { ...a.corner };
9358
+ if (a.id) next.id = a.id;
9359
+ return next;
9144
9360
  });
9361
+ return { anchors, closed: sp.closed };
9145
9362
  }
9146
- function roundCornerPaths(paths, radius) {
9147
- if (!(radius > 0) || !Number.isFinite(radius)) return paths.slice();
9148
- return paths.map((p) => subpathsToBezier(bezierToSubpaths(p).map((sp) => setSubpathCorners(sp, radius))));
9149
- }
9150
- function offsetPaths(paths, amount, join = "miter", miterLimit = 4, copies = 1, copyOffset = 0) {
9151
- const n = Math.min(1e3, Math.max(1, Math.round(Number.isFinite(copies) ? copies : 1)));
9152
- return paths.map((p) => {
9153
- const out = [];
9154
- for (let i = 0; i < n; i++) out.push(...offsetPath(p, amount * (i + 1 + copyOffset), { join, miterLimit }));
9155
- return out;
9156
- });
9363
+ var KAPPA2 = 0.5522847498307936;
9364
+ function rectSubpath(b, radius = 0, kind) {
9365
+ const { x, y, width: w, height: h } = b;
9366
+ const sp = { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };
9367
+ return radius > 0 ? setSubpathCorners(sp, radius, kind) : sp;
9157
9368
  }
9158
- function twistPaths(paths, angle, center) {
9159
- let rmax = 0;
9160
- for (const p of paths) for (const c of p) if (c.type !== "Z") rmax = Math.max(rmax, Math.hypot(c.x - center.x, c.y - center.y));
9161
- const a = angle * Math.PI / 180;
9162
- if (!a || rmax <= 1e-9 || !Number.isFinite(a)) return paths.slice();
9163
- const k = a / rmax;
9164
- const rot = (v2, th) => {
9165
- const s = Math.sin(th);
9166
- const co = Math.cos(th);
9167
- return [v2[0] * co - v2[1] * s, v2[0] * s + v2[1] * co];
9168
- };
9169
- const map = (p) => {
9170
- const v2 = [p[0] - center.x, p[1] - center.y];
9171
- const r = rot(v2, k * Math.hypot(v2[0], v2[1]));
9172
- return [center.x + r[0], center.y + r[1]];
9173
- };
9174
- const jac = (p, dv) => {
9175
- const v2 = [p[0] - center.x, p[1] - center.y];
9176
- const r = Math.hypot(v2[0], v2[1]);
9177
- let w = dv;
9178
- if (r > 1e-12) {
9179
- const f = k * (v2[0] * dv[0] + v2[1] * dv[1]) / r;
9180
- w = [dv[0] - v2[1] * f, dv[1] + v2[0] * f];
9181
- }
9182
- return rot(w, k * r);
9369
+ function ellipseSubpath(b) {
9370
+ const rx = b.width / 2;
9371
+ const ry = b.height / 2;
9372
+ const cx = b.x + rx;
9373
+ const cy = b.y + ry;
9374
+ const kx = rx * KAPPA2;
9375
+ const ky = ry * KAPPA2;
9376
+ return {
9377
+ closed: true,
9378
+ anchors: [
9379
+ { x: cx, y: cy - ry, in: { x: cx - kx, y: cy - ry }, out: { x: cx + kx, y: cy - ry }, type: "symmetric" },
9380
+ { x: cx + rx, y: cy, in: { x: cx + rx, y: cy - ky }, out: { x: cx + rx, y: cy + ky }, type: "symmetric" },
9381
+ { x: cx, y: cy + ry, in: { x: cx + kx, y: cy + ry }, out: { x: cx - kx, y: cy + ry }, type: "symmetric" },
9382
+ { x: cx - rx, y: cy, in: { x: cx - rx, y: cy + ky }, out: { x: cx - rx, y: cy - ky }, type: "symmetric" }
9383
+ ]
9183
9384
  };
9184
- return paths.map((p) => {
9185
- const out = [];
9186
- for (const sub5 of subsOf(p)) {
9187
- const segs = [];
9188
- for (const seg of sub5.segs) {
9189
- const cubic = seg.length === 2 ? [seg[0], seg[0], seg[1], seg[1]] : seg;
9190
- const len2 = segmentLength(seg);
9191
- const m = Math.min(64, Math.max(1, Math.ceil(Math.abs(a) * len2 / rmax / 0.15 + len2 / 50)));
9192
- for (let i = 0; i < m; i++) {
9193
- const q = subSegment(cubic, i / m, (i + 1) / m);
9194
- const p0 = map(q[0]);
9195
- const p3 = map(q[3]);
9196
- const h1 = jac(q[0], [q[1][0] - q[0][0], q[1][1] - q[0][1]]);
9197
- const h2 = jac(q[3], [q[2][0] - q[3][0], q[2][1] - q[3][1]]);
9198
- segs.push([p0, [p0[0] + h1[0], p0[1] + h1[1]], [p3[0] + h2[0], p3[1] + h2[1]], p3]);
9199
- }
9200
- }
9201
- out.push({ segs, closed: sub5.closed });
9202
- }
9203
- return subsToPath(out);
9204
- });
9205
9385
  }
9206
- function resample(sub5, m) {
9207
- const segs = sub5.segs;
9208
- const n = segs.length;
9209
- const lens = segs.map((s) => segmentLength(s));
9210
- const out = [];
9211
- for (let j = 0; j < n; j++) {
9212
- const piece = lens[j] / m;
9213
- for (let k = 0; k < m; k++) {
9214
- if (k === 0) {
9215
- const tout = startTangent(segs[j]);
9216
- const prev = sub5.closed || j > 0 ? (j + n - 1) % n : null;
9217
- let tan = tout;
9218
- if (prev !== null) {
9219
- const tin = endTangent(segs[prev]);
9220
- tan = unit3(tin[0] + tout[0], tin[1] + tout[1]) ?? tout;
9221
- }
9222
- out.push({ p: segs[j][0], tan, lenPrev: prev === null ? 0 : lens[prev] / m, lenNext: piece });
9223
- } else {
9224
- const t = segmentParamAtLength(segs[j], piece * k, lens[j]);
9225
- out.push({ p: segmentPoint(segs[j], t), tan: tangentAt(segs[j], t), lenPrev: piece, lenNext: piece });
9226
- }
9227
- }
9228
- }
9229
- if (!sub5.closed) {
9230
- const last = segs[n - 1];
9231
- out.push({ p: last[last.length - 1], tan: endTangent(last), lenPrev: lens[n - 1] / m, lenNext: 0 });
9386
+ function polygonSubpath(b, sides = 6) {
9387
+ const n = Math.max(3, Math.round(sides));
9388
+ const cx = b.x + b.width / 2;
9389
+ const cy = b.y + b.height / 2;
9390
+ const anchors = [];
9391
+ for (let i = 0; i < n; i++) {
9392
+ const t = -Math.PI / 2 + i * 2 * Math.PI / n;
9393
+ anchors.push({ x: cx + Math.cos(t) * b.width / 2, y: cy + Math.sin(t) * b.height / 2 });
9232
9394
  }
9233
- return out;
9395
+ return { anchors, closed: true };
9234
9396
  }
9235
- function emit(samples, disp, closed, smooth, handle) {
9236
- const n = samples.length;
9237
- if (!n) return null;
9238
- const pt2 = (i) => [samples[i].p[0] + disp[i][0], samples[i].p[1] + disp[i][1]];
9239
- const segs = [];
9240
- const count = closed ? n : n - 1;
9241
- for (let i = 0; i < count; i++) {
9242
- const j = (i + 1) % n;
9243
- const a = samples[i];
9244
- const b = samples[j];
9245
- const p = pt2(i);
9246
- const q = pt2(j);
9247
- if (smooth) segs.push([p, [p[0] + a.tan[0] * a.lenNext * handle, p[1] + a.tan[1] * a.lenNext * handle], [q[0] - b.tan[0] * b.lenPrev * handle, q[1] - b.tan[1] * b.lenPrev * handle], q]);
9248
- else segs.push([p, q]);
9397
+ function starSubpath(b, points = 5, innerRatio = 0.5) {
9398
+ const n = Math.max(3, Math.round(points));
9399
+ const cx = b.x + b.width / 2;
9400
+ const cy = b.y + b.height / 2;
9401
+ const anchors = [];
9402
+ for (let i = 0; i < n * 2; i++) {
9403
+ const t = -Math.PI / 2 + i * Math.PI / n;
9404
+ const r = i % 2 === 0 ? 1 : innerRatio;
9405
+ anchors.push({ x: cx + Math.cos(t) * r * b.width / 2, y: cy + Math.sin(t) * r * b.height / 2 });
9249
9406
  }
9250
- if (!segs.length) return null;
9251
- return { segs, closed };
9252
- }
9253
- function zigzagPaths(paths, size, ridges, smooth = false) {
9254
- const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(ridges) ? ridges : 0))) + 1;
9255
- return paths.map((p) => {
9256
- const out = [];
9257
- for (const sub5 of subsOf(p)) {
9258
- const s = resample(sub5, m);
9259
- const disp = s.map((x, i) => {
9260
- const n = perp2(x.tan);
9261
- const d = i % 2 === 0 ? size : -size;
9262
- return [n[0] * d, n[1] * d];
9263
- });
9264
- const e = emit(s, disp, sub5.closed, smooth, 0.36);
9265
- if (e) out.push(e);
9266
- }
9267
- return subsToPath(out);
9268
- });
9269
- }
9270
- var MASK64 = (1n << 64n) - 1n;
9271
- function splitmix(x) {
9272
- x = x + 0x9e3779b97f4a7c15n & MASK64;
9273
- let z = x;
9274
- z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK64;
9275
- z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK64;
9276
- return z ^ z >> 31n;
9407
+ return { anchors, closed: true };
9277
9408
  }
9278
- function hash(seed, idx, t) {
9279
- const h = splitmix(splitmix(splitmix(seed) ^ idx) ^ BigInt.asUintN(64, BigInt(t)));
9280
- return Number(h >> 11n) / 2 ** 53 * 2 - 1;
9409
+ function lineSubpath(a, b) {
9410
+ return { anchors: [{ x: a.x, y: a.y }, { x: b.x, y: b.y }], closed: false };
9281
9411
  }
9282
- function noise(seed, idx, t) {
9283
- const i = Math.floor(t);
9284
- const f = t - i;
9285
- const p0 = hash(seed, idx, i - 1);
9286
- const p1 = hash(seed, idx, i);
9287
- const p2 = hash(seed, idx, i + 1);
9288
- const p3 = hash(seed, idx, i + 2);
9289
- const v2 = 0.5 * (2 * p1 + (-p0 + p2) * f + (2 * p0 - 5 * p1 + 4 * p2 - p3) * f * f + (-p0 + 3 * p1 - 3 * p2 + p3) * f * f * f);
9290
- return Math.min(1, Math.max(-1, v2));
9412
+ function roundSubpaths(subpaths, precision = 3) {
9413
+ const f = 10 ** precision;
9414
+ const r = (n) => Math.round(n * f) / f;
9415
+ const rp = (p) => ({ x: r(p.x), y: r(p.y) });
9416
+ return subpaths.map((sp) => ({
9417
+ closed: sp.closed,
9418
+ anchors: sp.anchors.map((a) => ({ ...cloneAnchor(a), x: r(a.x), y: r(a.y), ...a.in ? { in: rp(a.in) } : {}, ...a.out ? { out: rp(a.out) } : {} }))
9419
+ }));
9291
9420
  }
9292
- function wigglePaths(paths, w) {
9293
- if (!w.size || !Number.isFinite(w.size)) return paths.slice();
9294
- const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(w.detail) ? w.detail : 0))) + 1;
9295
- const seed = BigInt.asUintN(64, BigInt(Math.round(Number.isFinite(w.seed) ? w.seed : 0)));
9296
- const t = (Number.isFinite(w.time) ? w.time : 0) * (Number.isFinite(w.speed) ? w.speed : 0) + (Number.isFinite(w.phase) ? w.phase : 0) / 360;
9297
- const corr = Math.min(1, Math.max(0, (Number.isFinite(w.correlation) ? w.correlation : 0) / 100));
9298
- const sx = noise(seed, MASK64, t);
9299
- const sy = noise(seed, MASK64 - 1n, t);
9300
- let idx = 0;
9301
- return paths.map((p) => {
9421
+
9422
+ // src/vector/symmetry.ts
9423
+ function isVectorSymmetryGroup(node) {
9424
+ return node?.type === "group" && !!node.symmetry && !node.blend && !node.liveBoolean && !node.mask && !node.frame;
9425
+ }
9426
+ var rad = (deg) => deg * Math.PI / 180;
9427
+ var exact = (v2) => Math.abs(v2) < 1e-12 ? 0 : v2;
9428
+ var around = (c, m) => multiplyMatrices(multiplyMatrices([1, 0, 0, 1, -c.x, -c.y], m), [1, 0, 0, 1, c.x, c.y]);
9429
+ var reflect = (c, deg) => {
9430
+ const a = 2 * rad(deg);
9431
+ return around(c, [exact(Math.cos(a)), exact(Math.sin(a)), exact(Math.sin(a)), exact(-Math.cos(a)), 0, 0]);
9432
+ };
9433
+ var turn = (c, deg) => around(c, [exact(Math.cos(rad(deg))), exact(Math.sin(rad(deg))), exact(-Math.sin(rad(deg))), exact(Math.cos(rad(deg))), 0, 0]);
9434
+ var AXIS = { horizontal: [0], vertical: [90], diagonal: [45], dual: [90, 0] };
9435
+ function vectorSymmetryMatrices(s) {
9436
+ const c = s.center;
9437
+ const r = s.rotation ?? 0;
9438
+ if (s.mode === "radial") {
9439
+ const n = Math.min(64, Math.max(2, Math.round(s.copies ?? 6)));
9302
9440
  const out = [];
9303
- for (const sub5 of subsOf(p)) {
9304
- const s = resample(sub5, m);
9305
- const disp = s.map(() => {
9306
- const i = BigInt(idx);
9307
- const ox = noise(seed, i * 2n, t + idx * 0.37);
9308
- const oy = noise(seed, i * 2n + 1n, t + idx * 0.61);
9309
- idx++;
9310
- return [(sx * corr + ox * (1 - corr)) * w.size, (sy * corr + oy * (1 - corr)) * w.size];
9311
- });
9312
- const e = emit(s, disp, sub5.closed, w.smooth, 1 / 3);
9313
- if (e) out.push(e);
9314
- }
9315
- return subsToPath(out);
9316
- });
9441
+ for (let k = 1; k < n; k++) out.push(turn(c, 360 * k / n));
9442
+ if (s.mirror) for (let k = 0; k < n; k++) out.push(reflect(c, r + 180 * k / n));
9443
+ return out;
9444
+ }
9445
+ const axes = AXIS[s.mode].map((a) => reflect(c, a + r));
9446
+ return s.mode === "dual" ? [...axes, turn(c, 180)] : axes;
9317
9447
  }
9318
- var MERGE_OPS = { add: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
9319
- function mergePaths(paths, mode = "merge") {
9320
- if (mode === "merge" || !(mode in MERGE_OPS)) return paths.flat();
9321
- const inputs = paths.filter((p) => p.length > 0).map((path) => ({ path, fillRule: "nonzero" }));
9322
- if (!inputs.length) return [];
9323
- return pathBoolean(MERGE_OPS[mode], inputs).path;
9448
+ function renamed(node, k) {
9449
+ const id = `${node.id}~sym${k}`;
9450
+ if (node.type === "group") return { ...node, id, children: node.children.map((c) => renamed(c, k)) };
9451
+ return { ...node, id };
9324
9452
  }
9325
- function vectorRepeaterCopies(r, pivot) {
9326
- const copies = Math.max(0, Number.isFinite(r.copies) ? Math.min(r.copies, 1e3) : 0);
9327
- const n = Math.ceil(copies);
9328
- const offset = Number.isFinite(r.offset) ? r.offset : 0;
9329
- const so = (r.startOpacity ?? 100) / 100;
9330
- const eo = (r.endOpacity ?? 100) / 100;
9331
- const fract = copies - Math.floor(copies);
9453
+ var drawnCache2 = /* @__PURE__ */ new WeakMap();
9454
+ function vectorSymmetryDrawn(node) {
9455
+ let hit = drawnCache2.get(node);
9456
+ if (hit) return hit;
9457
+ const { symmetry, ...group } = node;
9458
+ const copies = vectorSymmetryMatrices(symmetry).flatMap((m, k) => node.children.map((c) => ({ ...renamed(transformVectorNode(c, m), k + 1), locked: true })));
9459
+ hit = { ...group, children: [...node.children, ...copies] };
9460
+ drawnCache2.set(node, hit);
9461
+ return hit;
9462
+ }
9463
+ function vectorSymmetryAxes(s, box) {
9464
+ const r = s.rotation ?? 0;
9465
+ const dirs = s.mode === "radial" ? Array.from({ length: Math.min(64, Math.max(2, Math.round(s.copies ?? 6))) }, (_, k) => r - 90 + 360 * k / Math.min(64, Math.max(2, Math.round(s.copies ?? 6)))) : AXIS[s.mode].map((a) => a + r);
9332
9466
  const out = [];
9333
- for (let i = 0; i < n; i++) {
9334
- const k = i + offset;
9335
- let m = translateMatrix(-pivot.x, -pivot.y);
9336
- m = multiplyMatrices(m, scaleMatrix(Math.pow((r.scale?.x ?? 100) / 100, k), Math.pow((r.scale?.y ?? 100) / 100, k)));
9337
- m = multiplyMatrices(m, rotateMatrix((r.rotation ?? 0) * k));
9338
- m = multiplyMatrices(m, translateMatrix(pivot.x + (r.position?.x ?? 0) * k, pivot.y + (r.position?.y ?? 0) * k));
9339
- const t = n > 1 ? i / (n - 1) : 0;
9340
- const partial = i + 1 === n && fract > 0 ? fract : 1;
9341
- out.push({ index: i, matrix: m, opacity: Math.min(1, Math.max(0, (so + (eo - so) * t) * partial)) });
9467
+ for (const deg of dirs) {
9468
+ const d = { x: Math.cos(rad(deg)), y: Math.sin(rad(deg)) };
9469
+ let lo = s.mode === "radial" ? 0 : -Infinity;
9470
+ let hi = Infinity;
9471
+ let inside = true;
9472
+ for (const [o, dv, min, max] of [
9473
+ [s.center.x, d.x, box.x, box.x + box.width],
9474
+ [s.center.y, d.y, box.y, box.y + box.height]
9475
+ ]) {
9476
+ if (Math.abs(dv) < 1e-12) {
9477
+ if (o < min || o > max) inside = false;
9478
+ } else {
9479
+ const a = (min - o) / dv;
9480
+ const b = (max - o) / dv;
9481
+ lo = Math.max(lo, Math.min(a, b));
9482
+ hi = Math.min(hi, Math.max(a, b));
9483
+ }
9484
+ }
9485
+ if (inside && Number.isFinite(lo) && Number.isFinite(hi) && hi > lo) out.push([{ x: s.center.x + lo * d.x, y: s.center.y + lo * d.y }, { x: s.center.x + hi * d.x, y: s.center.y + hi * d.y }]);
9342
9486
  }
9343
9487
  return out;
9344
9488
  }
9345
- function repeatPaths(paths, r, pivot) {
9346
- return vectorRepeaterCopies(r, pivot).map((copy) => ({ copy, paths: paths.map((p) => transformBezierPath(p, copy.matrix)) }));
9489
+ function sanitizeVectorSymmetry(value) {
9490
+ if (!value || typeof value !== "object") return null;
9491
+ const r = value;
9492
+ if (!VECTOR_SYMMETRY_MODES.includes(r.mode)) return null;
9493
+ const c = r.center;
9494
+ if (!c || !Number.isFinite(c.x) || !Number.isFinite(c.y)) return null;
9495
+ const out = { mode: r.mode, center: { x: c.x, y: c.y } };
9496
+ if (typeof r.rotation === "number" && Number.isFinite(r.rotation) && r.rotation % 360 !== 0) out.rotation = (r.rotation % 360 + 360) % 360;
9497
+ if (out.mode === "radial") {
9498
+ if (typeof r.copies === "number" && Number.isFinite(r.copies)) out.copies = Math.min(64, Math.max(2, Math.round(r.copies)));
9499
+ if (r.mirror === true) out.mirror = true;
9500
+ }
9501
+ return out;
9347
9502
  }
9348
- function pathsCenter(paths) {
9349
- const b = bezierPathBounds(paths.flat());
9350
- return b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };
9503
+ var VECTOR_SYMMETRY_MODE_LABELS = {
9504
+ vertical: "Mirror left / right",
9505
+ horizontal: "Mirror top / bottom",
9506
+ dual: "Four quarters",
9507
+ diagonal: "Mirror diagonally",
9508
+ radial: "Radial"
9509
+ };
9510
+ function transformVectorSymmetry(s, m) {
9511
+ const center = { x: m[0] * s.center.x + m[2] * s.center.y + m[4], y: m[1] * s.center.x + m[3] * s.center.y + m[5] };
9512
+ const det = m[0] * m[3] - m[1] * m[2];
9513
+ const angle = Math.atan2(m[1], m[0]) * 180 / Math.PI;
9514
+ const rotation = det < 0 ? -(s.rotation ?? 0) + 2 * angle : (s.rotation ?? 0) + angle;
9515
+ const norm = (rotation % 360 + 360) % 360;
9516
+ return { ...s, center, ...Math.abs(norm) > 1e-9 && Math.abs(norm - 360) > 1e-9 ? { rotation: norm } : { rotation: void 0 } };
9351
9517
  }
9352
9518
 
9353
9519
  export {
@@ -9384,6 +9550,7 @@ export {
9384
9550
  VECTOR_CONNECTOR_ROUTES,
9385
9551
  VECTOR_BLEND_SPACINGS,
9386
9552
  VECTOR_MASK_TYPES,
9553
+ VECTOR_SYMMETRY_MODES,
9387
9554
  VECTOR_TEXT_ALIGNS,
9388
9555
  VECTOR_PATH_TEXT_BASELINES,
9389
9556
  VECTOR_CONSTRAINT_KINDS,
@@ -9455,6 +9622,13 @@ export {
9455
9622
  applyMatrixLinear,
9456
9623
  matrixRotation,
9457
9624
  decomposeRigid,
9625
+ isVectorSymmetryGroup,
9626
+ vectorSymmetryMatrices,
9627
+ vectorSymmetryDrawn,
9628
+ vectorSymmetryAxes,
9629
+ sanitizeVectorSymmetry,
9630
+ VECTOR_SYMMETRY_MODE_LABELS,
9631
+ transformVectorSymmetry,
9458
9632
  vectorCornerAt,
9459
9633
  vectorCorners,
9460
9634
  cornerSetback,
@@ -9794,4 +9968,4 @@ export {
9794
9968
  lineSubpath,
9795
9969
  roundSubpaths
9796
9970
  };
9797
- //# sourceMappingURL=chunk-7LNZAWSJ.js.map
9971
+ //# sourceMappingURL=chunk-CSHEI3YT.js.map