@canvas-commons/2d 0.2.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 (183) hide show
  1. package/LICENSE +22 -0
  2. package/editor/index.css +40 -0
  3. package/editor/index.js +525 -0
  4. package/editor/index.js.map +1 -0
  5. package/lib/index-OvpZ-GsA.d.ts +5673 -0
  6. package/lib/index-OvpZ-GsA.d.ts.map +1 -0
  7. package/lib/index.d.ts +2 -0
  8. package/lib/index.js +10326 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/jsx-dev-runtime.d.ts +2 -0
  11. package/lib/jsx-dev-runtime.js +2 -0
  12. package/lib/jsx-runtime.d.ts +2 -0
  13. package/lib/jsx-runtime.js +28 -0
  14. package/lib/jsx-runtime.js.map +1 -0
  15. package/package.json +79 -0
  16. package/src/editor/NodeInspectorConfig.tsx +76 -0
  17. package/src/editor/PreviewOverlayConfig.tsx +65 -0
  18. package/src/editor/Provider.tsx +109 -0
  19. package/src/editor/SceneGraphTabConfig.tsx +87 -0
  20. package/src/editor/icons/CircleIcon.tsx +7 -0
  21. package/src/editor/icons/CodeIcon.tsx +8 -0
  22. package/src/editor/icons/CurveIcon.tsx +7 -0
  23. package/src/editor/icons/GridIcon.tsx +7 -0
  24. package/src/editor/icons/IconMap.ts +35 -0
  25. package/src/editor/icons/ImgIcon.tsx +8 -0
  26. package/src/editor/icons/LayoutIcon.tsx +9 -0
  27. package/src/editor/icons/LineIcon.tsx +7 -0
  28. package/src/editor/icons/NodeIcon.tsx +7 -0
  29. package/src/editor/icons/RayIcon.tsx +7 -0
  30. package/src/editor/icons/RectIcon.tsx +7 -0
  31. package/src/editor/icons/ShapeIcon.tsx +7 -0
  32. package/src/editor/icons/TxtIcon.tsx +8 -0
  33. package/src/editor/icons/VideoIcon.tsx +7 -0
  34. package/src/editor/icons/View2DIcon.tsx +10 -0
  35. package/src/editor/index.css +0 -0
  36. package/src/editor/index.ts +19 -0
  37. package/src/editor/shortcuts.ts +27 -0
  38. package/src/editor/tree/DetachedRoot.tsx +27 -0
  39. package/src/editor/tree/NodeElement.tsx +72 -0
  40. package/src/editor/tree/TreeElement.tsx +70 -0
  41. package/src/editor/tree/TreeRoot.tsx +10 -0
  42. package/src/editor/tree/ViewRoot.tsx +20 -0
  43. package/src/editor/tree/index.module.scss +45 -0
  44. package/src/editor/tree/index.ts +4 -0
  45. package/src/editor/tree/navigation.ts +145 -0
  46. package/src/editor/tsconfig.build.json +5 -0
  47. package/src/editor/tsconfig.json +12 -0
  48. package/src/editor/tsdoc.json +4 -0
  49. package/src/editor/utils/SignalSet.ts +37 -0
  50. package/src/editor/utils/index.ts +1 -0
  51. package/src/editor/vite-env.d.ts +1 -0
  52. package/src/lib/code/CodeCursor.ts +468 -0
  53. package/src/lib/code/CodeDiffer.ts +77 -0
  54. package/src/lib/code/CodeFragment.ts +96 -0
  55. package/src/lib/code/CodeHighlighter.ts +73 -0
  56. package/src/lib/code/CodeMetrics.ts +47 -0
  57. package/src/lib/code/CodeRange.test.ts +113 -0
  58. package/src/lib/code/CodeRange.ts +222 -0
  59. package/src/lib/code/CodeScope.ts +100 -0
  60. package/src/lib/code/CodeSelection.ts +28 -0
  61. package/src/lib/code/CodeSignal.ts +348 -0
  62. package/src/lib/code/CodeTokenizer.ts +54 -0
  63. package/src/lib/code/DefaultHighlightStyle.ts +98 -0
  64. package/src/lib/code/LezerHighlighter.ts +113 -0
  65. package/src/lib/code/diff.test.ts +311 -0
  66. package/src/lib/code/diff.ts +319 -0
  67. package/src/lib/code/extractRange.ts +125 -0
  68. package/src/lib/code/index.ts +13 -0
  69. package/src/lib/components/Bezier.ts +103 -0
  70. package/src/lib/components/Camera.ts +401 -0
  71. package/src/lib/components/Circle.ts +310 -0
  72. package/src/lib/components/Code.ts +532 -0
  73. package/src/lib/components/CubicBezier.ts +115 -0
  74. package/src/lib/components/Curve.ts +460 -0
  75. package/src/lib/components/Grid.ts +134 -0
  76. package/src/lib/components/Icon.ts +153 -0
  77. package/src/lib/components/Img.ts +328 -0
  78. package/src/lib/components/Knot.ts +156 -0
  79. package/src/lib/components/Latex.ts +537 -0
  80. package/src/lib/components/Layout.ts +1101 -0
  81. package/src/lib/components/Line.ts +394 -0
  82. package/src/lib/components/Node.ts +1941 -0
  83. package/src/lib/components/Path.ts +132 -0
  84. package/src/lib/components/Polygon.ts +266 -0
  85. package/src/lib/components/QuadBezier.ts +103 -0
  86. package/src/lib/components/Ray.ts +126 -0
  87. package/src/lib/components/Rect.ts +219 -0
  88. package/src/lib/components/SVG.ts +853 -0
  89. package/src/lib/components/Shape.ts +322 -0
  90. package/src/lib/components/Spline.ts +318 -0
  91. package/src/lib/components/Txt.test.tsx +81 -0
  92. package/src/lib/components/Txt.ts +204 -0
  93. package/src/lib/components/TxtLeaf.ts +210 -0
  94. package/src/lib/components/Video.ts +368 -0
  95. package/src/lib/components/View2D.ts +85 -0
  96. package/src/lib/components/__logs__/image-without-source.ts +18 -0
  97. package/src/lib/components/__logs__/line-without-points.ts +31 -0
  98. package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
  99. package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
  100. package/src/lib/components/__tests__/Camera.test.tsx +73 -0
  101. package/src/lib/components/__tests__/children.test.tsx +142 -0
  102. package/src/lib/components/__tests__/clone.test.tsx +126 -0
  103. package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
  104. package/src/lib/components/__tests__/generatorTest.ts +27 -0
  105. package/src/lib/components/__tests__/mockScene2D.ts +50 -0
  106. package/src/lib/components/__tests__/query.test.tsx +122 -0
  107. package/src/lib/components/__tests__/state.test.tsx +60 -0
  108. package/src/lib/components/index.ts +26 -0
  109. package/src/lib/components/types.ts +35 -0
  110. package/src/lib/curves/ArcSegment.ts +169 -0
  111. package/src/lib/curves/CircleSegment.ts +99 -0
  112. package/src/lib/curves/CubicBezierSegment.ts +80 -0
  113. package/src/lib/curves/CurveDrawingInfo.ts +11 -0
  114. package/src/lib/curves/CurvePoint.ts +15 -0
  115. package/src/lib/curves/CurveProfile.ts +28 -0
  116. package/src/lib/curves/KnotInfo.ts +10 -0
  117. package/src/lib/curves/LineSegment.ts +75 -0
  118. package/src/lib/curves/Polynomial.ts +355 -0
  119. package/src/lib/curves/Polynomial2D.ts +62 -0
  120. package/src/lib/curves/PolynomialSegment.ts +151 -0
  121. package/src/lib/curves/QuadBezierSegment.ts +66 -0
  122. package/src/lib/curves/Segment.ts +31 -0
  123. package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
  124. package/src/lib/curves/createCurveProfileLerp.ts +471 -0
  125. package/src/lib/curves/getBezierSplineProfile.ts +227 -0
  126. package/src/lib/curves/getCircleProfile.ts +86 -0
  127. package/src/lib/curves/getPathProfile.ts +177 -0
  128. package/src/lib/curves/getPointAtDistance.ts +21 -0
  129. package/src/lib/curves/getPolylineProfile.test.ts +21 -0
  130. package/src/lib/curves/getPolylineProfile.ts +88 -0
  131. package/src/lib/curves/getRectProfile.ts +138 -0
  132. package/src/lib/curves/index.ts +16 -0
  133. package/src/lib/decorators/canvasStyleSignal.ts +15 -0
  134. package/src/lib/decorators/colorSignal.ts +9 -0
  135. package/src/lib/decorators/compound.ts +85 -0
  136. package/src/lib/decorators/computed.ts +18 -0
  137. package/src/lib/decorators/defaultStyle.ts +15 -0
  138. package/src/lib/decorators/filtersSignal.ts +133 -0
  139. package/src/lib/decorators/index.ts +10 -0
  140. package/src/lib/decorators/initializers.ts +32 -0
  141. package/src/lib/decorators/nodeName.ts +13 -0
  142. package/src/lib/decorators/signal.test.ts +89 -0
  143. package/src/lib/decorators/signal.ts +348 -0
  144. package/src/lib/decorators/spacingSignal.ts +15 -0
  145. package/src/lib/decorators/transformSignals.test.ts +858 -0
  146. package/src/lib/decorators/transformSignals.ts +1633 -0
  147. package/src/lib/decorators/vector2Signal.ts +35 -0
  148. package/src/lib/globals.d.ts +2 -0
  149. package/src/lib/index.ts +9 -0
  150. package/src/lib/jsx-dev-runtime.ts +2 -0
  151. package/src/lib/jsx-runtime.ts +45 -0
  152. package/src/lib/morphers/PathMorpher.ts +8 -0
  153. package/src/lib/morphers/defaultMorpher.ts +43 -0
  154. package/src/lib/morphers/index.ts +4 -0
  155. package/src/lib/morphers/manimMorpher.ts +658 -0
  156. package/src/lib/parse-svg-path.d.ts +14 -0
  157. package/src/lib/partials/Filter.ts +185 -0
  158. package/src/lib/partials/Gradient.ts +103 -0
  159. package/src/lib/partials/Pattern.ts +35 -0
  160. package/src/lib/partials/RoughConfig.ts +180 -0
  161. package/src/lib/partials/ShaderConfig.ts +122 -0
  162. package/src/lib/partials/index.ts +5 -0
  163. package/src/lib/partials/types.ts +58 -0
  164. package/src/lib/scenes/Scene2D.ts +167 -0
  165. package/src/lib/scenes/index.ts +3 -0
  166. package/src/lib/scenes/makeScene2D.ts +19 -0
  167. package/src/lib/scenes/useScene2D.ts +6 -0
  168. package/src/lib/tsconfig.build.json +5 -0
  169. package/src/lib/tsconfig.json +11 -0
  170. package/src/lib/tsdoc.json +4 -0
  171. package/src/lib/utils/CanvasUtils.ts +304 -0
  172. package/src/lib/utils/PathDataBuilder.ts +320 -0
  173. package/src/lib/utils/diff.test.ts +453 -0
  174. package/src/lib/utils/diff.ts +148 -0
  175. package/src/lib/utils/index.ts +5 -0
  176. package/src/lib/utils/is.ts +11 -0
  177. package/src/lib/utils/makeSignalExtensions.ts +29 -0
  178. package/src/lib/utils/rough.ts +513 -0
  179. package/src/lib/utils/withDefaults.tsx +26 -0
  180. package/src/tsconfig.base.json +18 -0
  181. package/src/tsconfig.build.json +8 -0
  182. package/src/tsconfig.json +5 -0
  183. package/tsconfig.project.json +7 -0
@@ -0,0 +1,658 @@
1
+ import {PathMorpher} from './PathMorpher';
2
+
3
+ export interface ManimMorpherOptions {
4
+ alignPoints?: boolean;
5
+ }
6
+
7
+ interface CubicSegment {
8
+ p0: [number, number];
9
+ p1: [number, number];
10
+ p2: [number, number];
11
+ p3: [number, number];
12
+ }
13
+
14
+ type Subpath = CubicSegment[];
15
+
16
+ function lineToCubic(
17
+ x0: number,
18
+ y0: number,
19
+ x1: number,
20
+ y1: number,
21
+ ): CubicSegment {
22
+ return {
23
+ p0: [x0, y0],
24
+ p1: [x0 + (x1 - x0) / 3, y0 + (y1 - y0) / 3],
25
+ p2: [x0 + (2 * (x1 - x0)) / 3, y0 + (2 * (y1 - y0)) / 3],
26
+ p3: [x1, y1],
27
+ };
28
+ }
29
+
30
+ function quadToCubic(
31
+ x0: number,
32
+ y0: number,
33
+ cpx: number,
34
+ cpy: number,
35
+ x1: number,
36
+ y1: number,
37
+ ): CubicSegment {
38
+ return {
39
+ p0: [x0, y0],
40
+ p1: [x0 + (2 * (cpx - x0)) / 3, y0 + (2 * (cpy - y0)) / 3],
41
+ p2: [x1 + (2 * (cpx - x1)) / 3, y1 + (2 * (cpy - y1)) / 3],
42
+ p3: [x1, y1],
43
+ };
44
+ }
45
+
46
+ function arcToCubic(
47
+ x0: number,
48
+ y0: number,
49
+ rx: number,
50
+ ry: number,
51
+ xRotation: number,
52
+ largeArc: number,
53
+ sweep: number,
54
+ x1: number,
55
+ y1: number,
56
+ ): CubicSegment[] {
57
+ if (rx === 0 || ry === 0) return [lineToCubic(x0, y0, x1, y1)];
58
+
59
+ const sinPhi = Math.sin((xRotation * Math.PI) / 180);
60
+ const cosPhi = Math.cos((xRotation * Math.PI) / 180);
61
+
62
+ const xp = (cosPhi * (x0 - x1)) / 2 + (sinPhi * (y0 - y1)) / 2;
63
+ const yp = (-sinPhi * (x0 - x1)) / 2 + (cosPhi * (y0 - y1)) / 2;
64
+
65
+ let rxSq = rx * rx;
66
+ let rySq = ry * ry;
67
+ const xpSq = xp * xp;
68
+ const ypSq = yp * yp;
69
+
70
+ const lambda = xpSq / rxSq + ypSq / rySq;
71
+ if (lambda > 1) {
72
+ const scale = Math.sqrt(lambda);
73
+ rx *= scale;
74
+ ry *= scale;
75
+ rxSq = rx * rx;
76
+ rySq = ry * ry;
77
+ }
78
+
79
+ const denom = rxSq * ypSq + rySq * xpSq;
80
+ if (denom === 0) return [lineToCubic(x0, y0, x1, y1)];
81
+
82
+ let sq = Math.max(0, (rxSq * rySq - denom) / denom);
83
+ sq = Math.sqrt(sq) * (largeArc === sweep ? -1 : 1);
84
+
85
+ const cxp = (sq * rx * yp) / ry;
86
+ const cyp = (-sq * ry * xp) / rx;
87
+
88
+ const cx = cosPhi * cxp - sinPhi * cyp + (x0 + x1) / 2;
89
+ const cy = sinPhi * cxp + cosPhi * cyp + (y0 + y1) / 2;
90
+
91
+ function angle(ux: number, uy: number, vx: number, vy: number): number {
92
+ const dot = ux * vx + uy * vy;
93
+ const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
94
+ let acos = Math.acos(Math.max(-1, Math.min(1, dot / len)));
95
+ if (ux * vy - uy * vx < 0) acos = -acos;
96
+ return acos;
97
+ }
98
+
99
+ const theta1 = angle(1, 0, (xp - cxp) / rx, (yp - cyp) / ry);
100
+ let dtheta = angle(
101
+ (xp - cxp) / rx,
102
+ (yp - cyp) / ry,
103
+ (-xp - cxp) / rx,
104
+ (-yp - cyp) / ry,
105
+ );
106
+
107
+ if (!sweep && dtheta > 0) dtheta -= 2 * Math.PI;
108
+ if (sweep && dtheta < 0) dtheta += 2 * Math.PI;
109
+
110
+ const segments = Math.max(1, Math.ceil(Math.abs(dtheta) / (Math.PI / 2)));
111
+ const segAngle = dtheta / segments;
112
+ const alpha = (4 / 3) * Math.tan(segAngle / 4);
113
+
114
+ const result: CubicSegment[] = [];
115
+ for (let i = 0; i < segments; i++) {
116
+ const a1 = theta1 + i * segAngle;
117
+ const a2 = theta1 + (i + 1) * segAngle;
118
+
119
+ const cos1 = Math.cos(a1);
120
+ const sin1 = Math.sin(a1);
121
+ const cos2 = Math.cos(a2);
122
+ const sin2 = Math.sin(a2);
123
+
124
+ const ep1x = rx * cos1;
125
+ const ep1y = ry * sin1;
126
+ const ep2x = rx * cos2;
127
+ const ep2y = ry * sin2;
128
+
129
+ const cp1x = ep1x - alpha * rx * sin1;
130
+ const cp1y = ep1y + alpha * ry * cos1;
131
+ const cp2x = ep2x + alpha * rx * sin2;
132
+ const cp2y = ep2y - alpha * ry * cos2;
133
+
134
+ result.push({
135
+ p0: [
136
+ cosPhi * ep1x - sinPhi * ep1y + cx,
137
+ sinPhi * ep1x + cosPhi * ep1y + cy,
138
+ ],
139
+ p1: [
140
+ cosPhi * cp1x - sinPhi * cp1y + cx,
141
+ sinPhi * cp1x + cosPhi * cp1y + cy,
142
+ ],
143
+ p2: [
144
+ cosPhi * cp2x - sinPhi * cp2y + cx,
145
+ sinPhi * cp2x + cosPhi * cp2y + cy,
146
+ ],
147
+ p3: [
148
+ cosPhi * ep2x - sinPhi * ep2y + cx,
149
+ sinPhi * ep2x + cosPhi * ep2y + cy,
150
+ ],
151
+ });
152
+ }
153
+ return result;
154
+ }
155
+
156
+ function parseToCubicSubpaths(d: string): Subpath[] {
157
+ const commands = d.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g);
158
+ if (!commands) return [];
159
+
160
+ const subpaths: Subpath[] = [];
161
+ let currentSubpath: CubicSegment[] = [];
162
+ let currentX = 0;
163
+ let currentY = 0;
164
+ let subpathStartX = 0;
165
+ let subpathStartY = 0;
166
+ let lastControlX = 0;
167
+ let lastControlY = 0;
168
+
169
+ function pushSegment(seg: CubicSegment) {
170
+ currentSubpath.push(seg);
171
+ currentX = seg.p3[0];
172
+ currentY = seg.p3[1];
173
+ }
174
+
175
+ function flushSubpath() {
176
+ if (currentSubpath.length > 0) {
177
+ subpaths.push(currentSubpath);
178
+ currentSubpath = [];
179
+ }
180
+ }
181
+
182
+ for (const cmd of commands) {
183
+ const type = cmd[0];
184
+ const argStr = cmd.slice(1).trim();
185
+ const args = (argStr.match(/-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi) ?? []).map(
186
+ Number,
187
+ );
188
+
189
+ switch (type) {
190
+ case 'M':
191
+ flushSubpath();
192
+ currentX = args[0];
193
+ currentY = args[1];
194
+ subpathStartX = currentX;
195
+ subpathStartY = currentY;
196
+ for (let i = 2; i < args.length; i += 2) {
197
+ pushSegment(lineToCubic(currentX, currentY, args[i], args[i + 1]));
198
+ }
199
+ break;
200
+ case 'm':
201
+ flushSubpath();
202
+ currentX += args[0];
203
+ currentY += args[1];
204
+ subpathStartX = currentX;
205
+ subpathStartY = currentY;
206
+ for (let i = 2; i < args.length; i += 2) {
207
+ pushSegment(
208
+ lineToCubic(
209
+ currentX,
210
+ currentY,
211
+ currentX + args[i],
212
+ currentY + args[i + 1],
213
+ ),
214
+ );
215
+ }
216
+ break;
217
+ case 'L':
218
+ for (let i = 0; i < args.length; i += 2) {
219
+ pushSegment(lineToCubic(currentX, currentY, args[i], args[i + 1]));
220
+ }
221
+ break;
222
+ case 'l':
223
+ for (let i = 0; i < args.length; i += 2) {
224
+ pushSegment(
225
+ lineToCubic(
226
+ currentX,
227
+ currentY,
228
+ currentX + args[i],
229
+ currentY + args[i + 1],
230
+ ),
231
+ );
232
+ }
233
+ break;
234
+ case 'H':
235
+ for (let i = 0; i < args.length; i++) {
236
+ pushSegment(lineToCubic(currentX, currentY, args[i], currentY));
237
+ }
238
+ break;
239
+ case 'h':
240
+ for (let i = 0; i < args.length; i++) {
241
+ pushSegment(
242
+ lineToCubic(currentX, currentY, currentX + args[i], currentY),
243
+ );
244
+ }
245
+ break;
246
+ case 'V':
247
+ for (let i = 0; i < args.length; i++) {
248
+ pushSegment(lineToCubic(currentX, currentY, currentX, args[i]));
249
+ }
250
+ break;
251
+ case 'v':
252
+ for (let i = 0; i < args.length; i++) {
253
+ pushSegment(
254
+ lineToCubic(currentX, currentY, currentX, currentY + args[i]),
255
+ );
256
+ }
257
+ break;
258
+ case 'C':
259
+ for (let i = 0; i < args.length; i += 6) {
260
+ pushSegment({
261
+ p0: [currentX, currentY],
262
+ p1: [args[i], args[i + 1]],
263
+ p2: [args[i + 2], args[i + 3]],
264
+ p3: [args[i + 4], args[i + 5]],
265
+ });
266
+ lastControlX = args[i + 2];
267
+ lastControlY = args[i + 3];
268
+ }
269
+ break;
270
+ case 'c':
271
+ for (let i = 0; i < args.length; i += 6) {
272
+ const cp1x = currentX + args[i];
273
+ const cp1y = currentY + args[i + 1];
274
+ const cp2x = currentX + args[i + 2];
275
+ const cp2y = currentY + args[i + 3];
276
+ const endX = currentX + args[i + 4];
277
+ const endY = currentY + args[i + 5];
278
+ pushSegment({
279
+ p0: [currentX, currentY],
280
+ p1: [cp1x, cp1y],
281
+ p2: [cp2x, cp2y],
282
+ p3: [endX, endY],
283
+ });
284
+ lastControlX = cp2x;
285
+ lastControlY = cp2y;
286
+ }
287
+ break;
288
+ case 'S':
289
+ for (let i = 0; i < args.length; i += 4) {
290
+ const refX = 2 * currentX - lastControlX;
291
+ const refY = 2 * currentY - lastControlY;
292
+ pushSegment({
293
+ p0: [currentX, currentY],
294
+ p1: [refX, refY],
295
+ p2: [args[i], args[i + 1]],
296
+ p3: [args[i + 2], args[i + 3]],
297
+ });
298
+ lastControlX = args[i];
299
+ lastControlY = args[i + 1];
300
+ }
301
+ break;
302
+ case 's':
303
+ for (let i = 0; i < args.length; i += 4) {
304
+ const refX = 2 * currentX - lastControlX;
305
+ const refY = 2 * currentY - lastControlY;
306
+ const cp2x = currentX + args[i];
307
+ const cp2y = currentY + args[i + 1];
308
+ const endX = currentX + args[i + 2];
309
+ const endY = currentY + args[i + 3];
310
+ pushSegment({
311
+ p0: [currentX, currentY],
312
+ p1: [refX, refY],
313
+ p2: [cp2x, cp2y],
314
+ p3: [endX, endY],
315
+ });
316
+ lastControlX = cp2x;
317
+ lastControlY = cp2y;
318
+ }
319
+ break;
320
+ case 'Q':
321
+ for (let i = 0; i < args.length; i += 4) {
322
+ pushSegment(
323
+ quadToCubic(
324
+ currentX,
325
+ currentY,
326
+ args[i],
327
+ args[i + 1],
328
+ args[i + 2],
329
+ args[i + 3],
330
+ ),
331
+ );
332
+ lastControlX = args[i];
333
+ lastControlY = args[i + 1];
334
+ }
335
+ break;
336
+ case 'q':
337
+ for (let i = 0; i < args.length; i += 4) {
338
+ const cpx = currentX + args[i];
339
+ const cpy = currentY + args[i + 1];
340
+ const endX = currentX + args[i + 2];
341
+ const endY = currentY + args[i + 3];
342
+ pushSegment(quadToCubic(currentX, currentY, cpx, cpy, endX, endY));
343
+ lastControlX = cpx;
344
+ lastControlY = cpy;
345
+ }
346
+ break;
347
+ case 'T':
348
+ for (let i = 0; i < args.length; i += 2) {
349
+ const cpx = 2 * currentX - lastControlX;
350
+ const cpy = 2 * currentY - lastControlY;
351
+ pushSegment(
352
+ quadToCubic(currentX, currentY, cpx, cpy, args[i], args[i + 1]),
353
+ );
354
+ lastControlX = cpx;
355
+ lastControlY = cpy;
356
+ }
357
+ break;
358
+ case 't':
359
+ for (let i = 0; i < args.length; i += 2) {
360
+ const cpx = 2 * currentX - lastControlX;
361
+ const cpy = 2 * currentY - lastControlY;
362
+ const endX = currentX + args[i];
363
+ const endY = currentY + args[i + 1];
364
+ pushSegment(quadToCubic(currentX, currentY, cpx, cpy, endX, endY));
365
+ lastControlX = cpx;
366
+ lastControlY = cpy;
367
+ }
368
+ break;
369
+ case 'A':
370
+ for (let i = 0; i < args.length; i += 7) {
371
+ const arcSegs = arcToCubic(
372
+ currentX,
373
+ currentY,
374
+ args[i],
375
+ args[i + 1],
376
+ args[i + 2],
377
+ args[i + 3],
378
+ args[i + 4],
379
+ args[i + 5],
380
+ args[i + 6],
381
+ );
382
+ for (const seg of arcSegs) pushSegment(seg);
383
+ }
384
+ break;
385
+ case 'a':
386
+ for (let i = 0; i < args.length; i += 7) {
387
+ const arcSegs = arcToCubic(
388
+ currentX,
389
+ currentY,
390
+ args[i],
391
+ args[i + 1],
392
+ args[i + 2],
393
+ args[i + 3],
394
+ args[i + 4],
395
+ currentX + args[i + 5],
396
+ currentY + args[i + 6],
397
+ );
398
+ for (const seg of arcSegs) pushSegment(seg);
399
+ }
400
+ break;
401
+ case 'Z':
402
+ case 'z':
403
+ if (currentX !== subpathStartX || currentY !== subpathStartY) {
404
+ pushSegment(
405
+ lineToCubic(currentX, currentY, subpathStartX, subpathStartY),
406
+ );
407
+ }
408
+ flushSubpath();
409
+ currentX = subpathStartX;
410
+ currentY = subpathStartY;
411
+ break;
412
+ }
413
+ }
414
+
415
+ flushSubpath();
416
+ return subpaths;
417
+ }
418
+
419
+ function degenerateSubpath(x: number, y: number): Subpath {
420
+ return [
421
+ {
422
+ p0: [x, y],
423
+ p1: [x, y],
424
+ p2: [x, y],
425
+ p3: [x, y],
426
+ },
427
+ ];
428
+ }
429
+
430
+ function subpathCenter(subpath: Subpath): [number, number] {
431
+ let sx = 0;
432
+ let sy = 0;
433
+ let count = 0;
434
+ for (const seg of subpath) {
435
+ sx += seg.p0[0] + seg.p3[0];
436
+ sy += seg.p0[1] + seg.p3[1];
437
+ count += 2;
438
+ }
439
+ return count > 0 ? [sx / count, sy / count] : [0, 0];
440
+ }
441
+
442
+ function alignSubpathCounts(
443
+ a: Subpath[],
444
+ b: Subpath[],
445
+ ): [Subpath[], Subpath[]] {
446
+ const maxLen = Math.max(a.length, b.length);
447
+ const resultA: Subpath[] = [];
448
+ const resultB: Subpath[] = [];
449
+
450
+ for (let i = 0; i < maxLen; i++) {
451
+ if (i < a.length && i < b.length) {
452
+ resultA.push(a[i]);
453
+ resultB.push(b[i]);
454
+ } else if (i < a.length) {
455
+ resultA.push(a[i]);
456
+ const center = subpathCenter(a[i]);
457
+ resultB.push(degenerateSubpath(center[0], center[1]));
458
+ } else {
459
+ const center = subpathCenter(b[i]);
460
+ resultA.push(degenerateSubpath(center[0], center[1]));
461
+ resultB.push(b[i]);
462
+ }
463
+ }
464
+
465
+ return [resultA, resultB];
466
+ }
467
+
468
+ function splitCubicAt(
469
+ seg: CubicSegment,
470
+ t: number,
471
+ ): [CubicSegment, CubicSegment] {
472
+ const [x0, y0] = seg.p0;
473
+ const [x1, y1] = seg.p1;
474
+ const [x2, y2] = seg.p2;
475
+ const [x3, y3] = seg.p3;
476
+
477
+ const ax = x0 + (x1 - x0) * t;
478
+ const ay = y0 + (y1 - y0) * t;
479
+ const bx = x1 + (x2 - x1) * t;
480
+ const by = y1 + (y2 - y1) * t;
481
+ const cx = x2 + (x3 - x2) * t;
482
+ const cy = y2 + (y3 - y2) * t;
483
+
484
+ const dx = ax + (bx - ax) * t;
485
+ const dy = ay + (by - ay) * t;
486
+ const ex = bx + (cx - bx) * t;
487
+ const ey = by + (cy - by) * t;
488
+
489
+ const fx = dx + (ex - dx) * t;
490
+ const fy = dy + (ey - dy) * t;
491
+
492
+ return [
493
+ {p0: [x0, y0], p1: [ax, ay], p2: [dx, dy], p3: [fx, fy]},
494
+ {p0: [fx, fy], p1: [ex, ey], p2: [cx, cy], p3: [x3, y3]},
495
+ ];
496
+ }
497
+
498
+ function subdivideSegment(seg: CubicSegment, n: number): CubicSegment[] {
499
+ if (n <= 1) return [seg];
500
+
501
+ const result: CubicSegment[] = [];
502
+ let remaining = seg;
503
+ for (let i = 0; i < n - 1; i++) {
504
+ const t = 1 / (n - i);
505
+ const [left, right] = splitCubicAt(remaining, t);
506
+ result.push(left);
507
+ remaining = right;
508
+ }
509
+ result.push(remaining);
510
+ return result;
511
+ }
512
+
513
+ function subdivideSubpath(subpath: Subpath, targetCount: number): Subpath {
514
+ if (subpath.length >= targetCount) return subpath;
515
+
516
+ const ratio = targetCount / subpath.length;
517
+ const result: CubicSegment[] = [];
518
+ let allocated = 0;
519
+
520
+ for (let i = 0; i < subpath.length; i++) {
521
+ const idealEnd = Math.round(ratio * (i + 1));
522
+ const subdivisions = idealEnd - allocated;
523
+ result.push(...subdivideSegment(subpath[i], subdivisions));
524
+ allocated = idealEnd;
525
+ }
526
+
527
+ return result;
528
+ }
529
+
530
+ function alignSegmentCounts(a: Subpath, b: Subpath): [Subpath, Subpath] {
531
+ if (a.length === b.length) return [a, b];
532
+
533
+ const target = Math.max(a.length, b.length);
534
+ return [subdivideSubpath(a, target), subdivideSubpath(b, target)];
535
+ }
536
+
537
+ function dist2(a: [number, number], b: [number, number]): number {
538
+ const dx = a[0] - b[0];
539
+ const dy = a[1] - b[1];
540
+ return dx * dx + dy * dy;
541
+ }
542
+
543
+ function totalControlPointDistance(a: Subpath, b: Subpath): number {
544
+ let total = 0;
545
+ const len = Math.min(a.length, b.length);
546
+ for (let i = 0; i < len; i++) {
547
+ total += dist2(a[i].p0, b[i].p0);
548
+ total += dist2(a[i].p1, b[i].p1);
549
+ total += dist2(a[i].p2, b[i].p2);
550
+ total += dist2(a[i].p3, b[i].p3);
551
+ }
552
+ return total;
553
+ }
554
+
555
+ function rotateCurvesToMinimizeDistance(from: Subpath, to: Subpath): Subpath {
556
+ if (from.length <= 1) return from;
557
+
558
+ const first = from[0];
559
+ const last = from[from.length - 1];
560
+ const isClosed =
561
+ Math.abs(first.p0[0] - last.p3[0]) < 0.5 &&
562
+ Math.abs(first.p0[1] - last.p3[1]) < 0.5;
563
+
564
+ if (!isClosed) return from;
565
+
566
+ let bestRotation = 0;
567
+ let bestDistance = totalControlPointDistance(from, to);
568
+
569
+ for (let r = 1; r < from.length; r++) {
570
+ const rotated = [...from.slice(r), ...from.slice(0, r)];
571
+ const d = totalControlPointDistance(rotated, to);
572
+ if (d < bestDistance) {
573
+ bestDistance = d;
574
+ bestRotation = r;
575
+ }
576
+ }
577
+
578
+ if (bestRotation === 0) return from;
579
+ return [...from.slice(bestRotation), ...from.slice(0, bestRotation)];
580
+ }
581
+
582
+ function subpathsToPathString(subpaths: Subpath[]): string {
583
+ const parts: string[] = [];
584
+ for (const subpath of subpaths) {
585
+ if (subpath.length === 0) continue;
586
+ parts.push(`M${subpath[0].p0[0]},${subpath[0].p0[1]}`);
587
+ for (const seg of subpath) {
588
+ parts.push(
589
+ `C${seg.p1[0]},${seg.p1[1]} ${seg.p2[0]},${seg.p2[1]} ${seg.p3[0]},${seg.p3[1]}`,
590
+ );
591
+ }
592
+ }
593
+ return parts.join('');
594
+ }
595
+
596
+ function lerpPoint(
597
+ a: [number, number],
598
+ b: [number, number],
599
+ t: number,
600
+ ): [number, number] {
601
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
602
+ }
603
+
604
+ /**
605
+ * Create a path morpher that uses the Manim-inspired algorithm for
606
+ * cubic-bezier-based path interpolation with intelligent subpath alignment,
607
+ * segment equalization, and closed-path rotation optimization.
608
+ */
609
+ export function manimMorpher(options: ManimMorpherOptions = {}): PathMorpher {
610
+ const {alignPoints: doAlign = true} = options;
611
+
612
+ return {
613
+ createInterpolator(fromPath: string, toPath: string) {
614
+ let fromSubpaths = parseToCubicSubpaths(fromPath);
615
+ let toSubpaths = parseToCubicSubpaths(toPath);
616
+
617
+ if (fromSubpaths.length === 0 || toSubpaths.length === 0) {
618
+ return (progress: number) => (progress < 0.5 ? fromPath : toPath);
619
+ }
620
+
621
+ [fromSubpaths, toSubpaths] = alignSubpathCounts(fromSubpaths, toSubpaths);
622
+
623
+ const alignedFrom: Subpath[] = [];
624
+ const alignedTo: Subpath[] = [];
625
+
626
+ for (let i = 0; i < fromSubpaths.length; i++) {
627
+ const [af, at] = alignSegmentCounts(fromSubpaths[i], toSubpaths[i]);
628
+ const rotated = doAlign ? rotateCurvesToMinimizeDistance(af, at) : af;
629
+ alignedFrom.push(rotated);
630
+ alignedTo.push(at);
631
+ }
632
+
633
+ return (progress: number): string => {
634
+ if (progress <= 0) return fromPath;
635
+ if (progress >= 1) return toPath;
636
+
637
+ const result: Subpath[] = [];
638
+ for (let i = 0; i < alignedFrom.length; i++) {
639
+ const fromSp = alignedFrom[i];
640
+ const toSp = alignedTo[i];
641
+ const interpolated: CubicSegment[] = [];
642
+
643
+ for (let j = 0; j < fromSp.length; j++) {
644
+ interpolated.push({
645
+ p0: lerpPoint(fromSp[j].p0, toSp[j].p0, progress),
646
+ p1: lerpPoint(fromSp[j].p1, toSp[j].p1, progress),
647
+ p2: lerpPoint(fromSp[j].p2, toSp[j].p2, progress),
648
+ p3: lerpPoint(fromSp[j].p3, toSp[j].p3, progress),
649
+ });
650
+ }
651
+ result.push(interpolated);
652
+ }
653
+
654
+ return subpathsToPathString(result);
655
+ };
656
+ },
657
+ };
658
+ }
@@ -0,0 +1,14 @@
1
+ declare module 'parse-svg-path' {
2
+ export type PathCommand = [
3
+ string,
4
+ number,
5
+ number,
6
+ number,
7
+ number,
8
+ number,
9
+ number,
10
+ number,
11
+ ];
12
+ function parse(path: string): PathCommand[];
13
+ export default parse;
14
+ }