@js-draw/math 1.0.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/Color4.test.d.ts +1 -0
  3. package/dist/cjs/Mat33.test.d.ts +1 -0
  4. package/dist/cjs/Vec2.test.d.ts +1 -0
  5. package/dist/cjs/Vec3.test.d.ts +1 -0
  6. package/dist/cjs/polynomial/solveQuadratic.test.d.ts +1 -0
  7. package/dist/cjs/rounding.test.d.ts +1 -0
  8. package/dist/cjs/shapes/LineSegment2.test.d.ts +1 -0
  9. package/dist/cjs/shapes/Path.fromString.test.d.ts +1 -0
  10. package/dist/cjs/shapes/Path.test.d.ts +1 -0
  11. package/dist/cjs/shapes/Path.toString.test.d.ts +1 -0
  12. package/dist/cjs/shapes/QuadraticBezier.test.d.ts +1 -0
  13. package/dist/cjs/shapes/Rect2.test.d.ts +1 -0
  14. package/dist/cjs/shapes/Triangle.test.d.ts +1 -0
  15. package/dist/mjs/Color4.test.d.ts +1 -0
  16. package/dist/mjs/Mat33.test.d.ts +1 -0
  17. package/dist/mjs/Vec2.test.d.ts +1 -0
  18. package/dist/mjs/Vec3.test.d.ts +1 -0
  19. package/dist/mjs/polynomial/solveQuadratic.test.d.ts +1 -0
  20. package/dist/mjs/rounding.test.d.ts +1 -0
  21. package/dist/mjs/shapes/LineSegment2.test.d.ts +1 -0
  22. package/dist/mjs/shapes/Path.fromString.test.d.ts +1 -0
  23. package/dist/mjs/shapes/Path.test.d.ts +1 -0
  24. package/dist/mjs/shapes/Path.toString.test.d.ts +1 -0
  25. package/dist/mjs/shapes/QuadraticBezier.test.d.ts +1 -0
  26. package/dist/mjs/shapes/Rect2.test.d.ts +1 -0
  27. package/dist/mjs/shapes/Triangle.test.d.ts +1 -0
  28. package/dist-test/test_imports/package-lock.json +13 -0
  29. package/dist-test/test_imports/package.json +12 -0
  30. package/dist-test/test_imports/test-imports.js +15 -0
  31. package/dist-test/test_imports/test-require.cjs +15 -0
  32. package/package.json +4 -3
  33. package/src/Color4.test.ts +52 -0
  34. package/src/Color4.ts +318 -0
  35. package/src/Mat33.test.ts +244 -0
  36. package/src/Mat33.ts +450 -0
  37. package/src/Vec2.test.ts +30 -0
  38. package/src/Vec2.ts +49 -0
  39. package/src/Vec3.test.ts +51 -0
  40. package/src/Vec3.ts +245 -0
  41. package/src/lib.ts +42 -0
  42. package/src/polynomial/solveQuadratic.test.ts +39 -0
  43. package/src/polynomial/solveQuadratic.ts +43 -0
  44. package/src/rounding.test.ts +65 -0
  45. package/src/rounding.ts +167 -0
  46. package/src/shapes/Abstract2DShape.ts +63 -0
  47. package/src/shapes/BezierJSWrapper.ts +93 -0
  48. package/src/shapes/CubicBezier.ts +35 -0
  49. package/src/shapes/LineSegment2.test.ts +99 -0
  50. package/src/shapes/LineSegment2.ts +232 -0
  51. package/src/shapes/Path.fromString.test.ts +223 -0
  52. package/src/shapes/Path.test.ts +309 -0
  53. package/src/shapes/Path.toString.test.ts +77 -0
  54. package/src/shapes/Path.ts +963 -0
  55. package/src/shapes/PointShape2D.ts +33 -0
  56. package/src/shapes/QuadraticBezier.test.ts +31 -0
  57. package/src/shapes/QuadraticBezier.ts +142 -0
  58. package/src/shapes/Rect2.test.ts +209 -0
  59. package/src/shapes/Rect2.ts +346 -0
  60. package/src/shapes/Triangle.test.ts +61 -0
  61. package/src/shapes/Triangle.ts +139 -0
@@ -0,0 +1,223 @@
1
+ // Tests to ensure that Paths can be deserialized
2
+
3
+ import Path, { PathCommandType } from './Path';
4
+ import { Vec2 } from '../Vec2';
5
+
6
+ describe('Path.fromString', () => {
7
+ it('should handle an empty path', () => {
8
+ const path = Path.fromString('');
9
+ expect(path.geometry.length).toBe(0);
10
+ });
11
+
12
+ it('should properly handle absolute moveTo commands', () => {
13
+ const path1 = Path.fromString('M0,0');
14
+ const path2 = Path.fromString('M 0 0');
15
+ const path3 = Path.fromString('M 1,1M 2,2 M 3,3');
16
+
17
+ expect(path1.parts.length).toBe(0);
18
+ expect(path1.startPoint).toMatchObject(Vec2.zero);
19
+
20
+ expect(path2.parts.length).toBe(0);
21
+ expect(path2.startPoint).toMatchObject(Vec2.zero);
22
+
23
+ expect(path3.parts).toMatchObject([
24
+ {
25
+ kind: PathCommandType.MoveTo,
26
+ point: Vec2.of(2, 2),
27
+ },
28
+ {
29
+ kind: PathCommandType.MoveTo,
30
+ point: Vec2.of(3,3),
31
+ },
32
+ ]);
33
+ expect(path3.startPoint).toMatchObject(Vec2.of(1, 1));
34
+ });
35
+
36
+ it('should properly handle relative moveTo commands', () => {
37
+ const path = Path.fromString('m1,1 m0,0 m 3,3');
38
+ expect(path.parts).toMatchObject([
39
+ {
40
+ kind: PathCommandType.MoveTo,
41
+ point: Vec2.of(1, 1),
42
+ },
43
+ {
44
+ kind: PathCommandType.MoveTo,
45
+ point: Vec2.of(4, 4),
46
+ },
47
+ ]);
48
+ expect(path.startPoint).toMatchObject(Vec2.of(1, 1));
49
+ });
50
+
51
+ it('should handle lineTo commands', () => {
52
+ const path = Path.fromString('l1,2L-1,0l0.1,-1.0');
53
+ // l is a relative lineTo, but because there
54
+ // is no previous command, it should act like an
55
+ // absolute moveTo.
56
+ expect(path.startPoint).toMatchObject(Vec2.of(1, 2));
57
+
58
+ expect(path.parts).toMatchObject([
59
+ {
60
+ kind: PathCommandType.LineTo,
61
+ point: Vec2.of(-1, 0),
62
+ },
63
+ {
64
+ kind: PathCommandType.LineTo,
65
+ point: Vec2.of(-0.9, -1.0),
66
+ },
67
+ ]);
68
+ });
69
+
70
+ it('"z" should close strokes', () => {
71
+ const path1 = Path.fromString('m3,3 l1,2 l1,1 z');
72
+ const path2 = Path.fromString('m3,3 l1,2 l1,1 Z');
73
+
74
+ expect(path1.startPoint).toMatchObject(Vec2.of(3,3));
75
+ expect(path2.startPoint).toMatchObject(path1.startPoint);
76
+ expect(path1.parts).toMatchObject(path2.parts);
77
+ expect(path1.parts).toMatchObject([
78
+ {
79
+ kind: PathCommandType.LineTo,
80
+ point: Vec2.of(4, 5),
81
+ },
82
+ {
83
+ kind: PathCommandType.LineTo,
84
+ point: Vec2.of(5, 6),
85
+ },
86
+ {
87
+ kind: PathCommandType.LineTo,
88
+ point: path1.startPoint,
89
+ }
90
+ ]);
91
+ });
92
+
93
+ it('should break compoents at -s', () => {
94
+ const path = Path.fromString('m1-1 L-1-1-3-4-5-6,5-1');
95
+ expect(path.parts.length).toBe(4);
96
+ expect(path.parts).toMatchObject([
97
+ {
98
+ kind: PathCommandType.LineTo,
99
+ point: Vec2.of(-1, -1),
100
+ },
101
+ {
102
+ kind: PathCommandType.LineTo,
103
+ point: Vec2.of(-3, -4),
104
+ },
105
+ {
106
+ kind: PathCommandType.LineTo,
107
+ point: Vec2.of(-5, -6),
108
+ },
109
+ {
110
+ kind: PathCommandType.LineTo,
111
+ point: Vec2.of(5, -1),
112
+ },
113
+ ]);
114
+ });
115
+
116
+ it('should properly handle cubic Bézier curves', () => {
117
+ const path = Path.fromString('m1,1 c1,1 0-3 4 5 C1,1 0.1, 0.1 0, 0');
118
+ expect(path.parts.length).toBe(2);
119
+ expect(path.parts).toMatchObject([
120
+ {
121
+ kind: PathCommandType.CubicBezierTo,
122
+ controlPoint1: Vec2.of(2, 2),
123
+ controlPoint2: Vec2.of(1, -2),
124
+ endPoint: Vec2.of(5, 6),
125
+ },
126
+ {
127
+ kind: PathCommandType.CubicBezierTo,
128
+ controlPoint1: Vec2.of(1, 1),
129
+ controlPoint2: Vec2.of(0.1, 0.1),
130
+ endPoint: Vec2.zero,
131
+ }
132
+ ]);
133
+ });
134
+
135
+ it('should handle quadratic Bézier curves', () => {
136
+ const path = Path.fromString(' Q1 1,2 3 q-1 -2,-3 -4 Q 1 2,3 4');
137
+ expect(path.parts).toMatchObject([
138
+ {
139
+ kind: PathCommandType.QuadraticBezierTo,
140
+ controlPoint: Vec2.of(1, 1),
141
+ endPoint: Vec2.of(2, 3),
142
+ },
143
+ {
144
+ kind: PathCommandType.QuadraticBezierTo,
145
+ controlPoint: Vec2.of(1, 1),
146
+ endPoint: Vec2.of(-1, -1),
147
+ },
148
+ {
149
+ kind: PathCommandType.QuadraticBezierTo,
150
+ controlPoint: Vec2.of(1, 2),
151
+ endPoint: Vec2.of(3, 4),
152
+ },
153
+ ]);
154
+ expect(path.startPoint).toMatchObject(Vec2.of(1, 1));
155
+ });
156
+
157
+ it('should correctly handle a command followed by multiple sets of arguments', () => {
158
+ // Commands followed by multiple sets of arguments, for example,
159
+ // l 5,10 5,4 3,2,
160
+ // should be interpreted as multiple commands. Our example, is therefore equivalent to,
161
+ // l 5,10 l 5,4 l 3,2
162
+
163
+ const path = Path.fromString(`
164
+ L5,10 1,1
165
+ 2,2 -3,-1
166
+ q 1,2 1,1
167
+ -1,-1 -3,-4
168
+ h -4 -1
169
+ V 3 5 1
170
+ `);
171
+ expect(path.parts).toMatchObject([
172
+ {
173
+ kind: PathCommandType.LineTo,
174
+ point: Vec2.of(1, 1),
175
+ },
176
+ {
177
+ kind: PathCommandType.LineTo,
178
+ point: Vec2.of(2, 2),
179
+ },
180
+ {
181
+ kind: PathCommandType.LineTo,
182
+ point: Vec2.of(-3, -1),
183
+ },
184
+
185
+ // q 1,2 1,1 -1,-1 -3,-4
186
+ {
187
+ kind: PathCommandType.QuadraticBezierTo,
188
+ controlPoint: Vec2.of(-2, 1),
189
+ endPoint: Vec2.of(-2, 0),
190
+ },
191
+ {
192
+ kind: PathCommandType.QuadraticBezierTo,
193
+ controlPoint: Vec2.of(-3, -1),
194
+ endPoint: Vec2.of(-5, -4),
195
+ },
196
+
197
+ // h -4 -1
198
+ {
199
+ kind: PathCommandType.LineTo,
200
+ point: Vec2.of(-9, -4),
201
+ },
202
+ {
203
+ kind: PathCommandType.LineTo,
204
+ point: Vec2.of(-10, -4),
205
+ },
206
+
207
+ // V 3 5 1
208
+ {
209
+ kind: PathCommandType.LineTo,
210
+ point: Vec2.of(-10, 3),
211
+ },
212
+ {
213
+ kind: PathCommandType.LineTo,
214
+ point: Vec2.of(-10, 5),
215
+ },
216
+ {
217
+ kind: PathCommandType.LineTo,
218
+ point: Vec2.of(-10, 1),
219
+ },
220
+ ]);
221
+ expect(path.startPoint).toMatchObject(Vec2.of(5, 10));
222
+ });
223
+ });
@@ -0,0 +1,309 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import Path, { PathCommandType } from './Path';
3
+ import Rect2 from './Rect2';
4
+ import { Vec2 } from '../Vec2';
5
+ import CubicBezier from './CubicBezier';
6
+ import QuadraticBezier from './QuadraticBezier';
7
+
8
+ describe('Path', () => {
9
+ it('should instantiate Beziers from cubic and quatratic commands', () => {
10
+ const path = new Path(Vec2.zero, [
11
+ {
12
+ kind: PathCommandType.CubicBezierTo,
13
+ controlPoint1: Vec2.of(1, 1),
14
+ controlPoint2: Vec2.of(-1, -1),
15
+ endPoint: Vec2.of(3, 3),
16
+ },
17
+ {
18
+ kind: PathCommandType.QuadraticBezierTo,
19
+ controlPoint: Vec2.of(1, 1),
20
+ endPoint: Vec2.of(0, 0),
21
+ },
22
+ ]);
23
+
24
+ expect(path.geometry.length).toBe(2);
25
+
26
+ const firstItem = path.geometry[0];
27
+ const secondItem = path.geometry[1];
28
+ expect(firstItem).toBeInstanceOf(CubicBezier);
29
+ expect(secondItem).toBeInstanceOf(QuadraticBezier);
30
+
31
+ // Force TypeScript to do type narrowing.
32
+ if (!(firstItem instanceof CubicBezier) || !(secondItem instanceof QuadraticBezier)) {
33
+ throw new Error('Invalid state! .toBeInstanceOf should have caused test to fail!');
34
+ }
35
+
36
+ // Make sure the control points (and start/end points) match what was set
37
+ expect(firstItem.getPoints()).toMatchObject([
38
+ { x: 0, y: 0 }, { x: 1, y: 1 }, { x: -1, y: -1 }, { x: 3, y: 3 }
39
+ ]);
40
+ expect(secondItem.getPoints()).toMatchObject([
41
+ { x: 3, y: 3 }, { x: 1, y: 1 }, { x: 0, y: 0 },
42
+ ]);
43
+ });
44
+
45
+ it('should create LineSegments from line commands', () => {
46
+ const lineStart = Vec2.zero;
47
+ const lineEnd = Vec2.of(100, 100);
48
+
49
+ const path = new Path(lineStart, [
50
+ {
51
+ kind: PathCommandType.LineTo,
52
+ point: lineEnd,
53
+ },
54
+ ]);
55
+
56
+ expect(path.geometry.length).toBe(1);
57
+ expect(path.geometry[0]).toBeInstanceOf(LineSegment2);
58
+ expect(path.geometry[0]).toMatchObject(
59
+ new LineSegment2(lineStart, lineEnd)
60
+ );
61
+ });
62
+
63
+ describe('intersection', () => {
64
+ it('should give all intersections for a path made up of lines', () => {
65
+ const lineStart = Vec2.of(100, 100);
66
+ const path = new Path(lineStart, [
67
+ {
68
+ kind: PathCommandType.LineTo,
69
+ point: Vec2.of(-100, 100),
70
+ },
71
+ {
72
+ kind: PathCommandType.LineTo,
73
+ point: Vec2.of(0, 0),
74
+ },
75
+ {
76
+ kind: PathCommandType.LineTo,
77
+ point: Vec2.of(100, -100),
78
+ },
79
+ ]);
80
+
81
+ const intersections = path.intersection(
82
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200))
83
+ );
84
+
85
+ // Should only have intersections in quadrants II and III.
86
+ expect(intersections.length).toBe(2);
87
+
88
+ // First intersection should be with the first curve
89
+ const firstIntersection = intersections[0];
90
+ expect(firstIntersection.point.xy).toMatchObject({
91
+ x: -50,
92
+ y: 100,
93
+ });
94
+ });
95
+
96
+ it('should give all intersections for a stroked path', () => {
97
+ const lineStart = Vec2.of(100, 100);
98
+
99
+ // Create a path in this shape:
100
+ // + - - - -|- - - - +
101
+ // \ |
102
+ // \ |
103
+ // \ |
104
+ // \ |
105
+ // ---------------------
106
+ // | \
107
+ // | \
108
+ // | \
109
+ // | \
110
+ // | +
111
+ const path = new Path(lineStart, [
112
+ {
113
+ kind: PathCommandType.LineTo,
114
+ point: Vec2.of(-100, 100),
115
+ },
116
+ {
117
+ kind: PathCommandType.LineTo,
118
+ point: Vec2.of(0, 0),
119
+ },
120
+ {
121
+ kind: PathCommandType.LineTo,
122
+ point: Vec2.of(100, -100),
123
+ },
124
+ ]);
125
+
126
+ const strokeWidth = 5;
127
+ let intersections = path.intersection(
128
+ new LineSegment2(Vec2.of(2000, 200), Vec2.of(2000, 400)), strokeWidth,
129
+ );
130
+ expect(intersections.length).toBe(0);
131
+
132
+ // Test a line that only enters, but does not exit one of the strokes:
133
+ //
134
+ // * <- Line to test
135
+ // |
136
+ // _____|_____________
137
+ // |
138
+ // * Stroke
139
+ //
140
+ // ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
141
+
142
+ intersections = path.intersection(
143
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, 100)), strokeWidth,
144
+ );
145
+ expect(intersections.length).toBe(1);
146
+ expect(intersections[0].point.xy).toMatchObject({
147
+ x: -50,
148
+ y: 105,
149
+ });
150
+
151
+
152
+ // Changing the order of the end points on the line should not change the result
153
+ intersections = path.intersection(
154
+ new LineSegment2(Vec2.of(-50, 100), Vec2.of(-50, 200)), strokeWidth,
155
+ );
156
+ expect(intersections.length).toBe(1);
157
+ expect(intersections[0].point.xy).toMatchObject({
158
+ x: -50,
159
+ y: 105,
160
+ });
161
+
162
+ // This line should intersect two of the strokes. Thus, there should be four
163
+ // intersections — one entering and one leaving for each intersection with the
164
+ // centers.
165
+ intersections = path.intersection(
166
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)), strokeWidth,
167
+ );
168
+ expect(intersections.length).toBe(4);
169
+
170
+ // Intersections should be in increasing order away from the
171
+ // first point on the line.
172
+ expect(intersections[0].point.xy).toMatchObject({
173
+ x: -50,
174
+ y: 105,
175
+ });
176
+ expect(intersections[1].point.xy).toMatchObject({
177
+ x: -50,
178
+ y: 95,
179
+ });
180
+ });
181
+
182
+ it('should give all intersections for a Bézier stroked path', () => {
183
+ const lineStart = Vec2.zero;
184
+ const path = new Path(lineStart, [
185
+ {
186
+ kind: PathCommandType.QuadraticBezierTo,
187
+ controlPoint: Vec2.unitX,
188
+ endPoint: Vec2.unitY,
189
+ }
190
+ ]);
191
+
192
+ const strokeWidth = 5;
193
+
194
+ // Should be no intersections for a line contained entirely within the stroke
195
+ // (including stroke width).
196
+ let intersections = path.intersection(
197
+ new LineSegment2(Vec2.of(-1, 0.5), Vec2.of(2, 0.5)), strokeWidth,
198
+ );
199
+ expect(intersections.length).toBe(0);
200
+
201
+ // Should be an intersection when exiting/entering the edge of the stroke
202
+ intersections = path.intersection(
203
+ new LineSegment2(Vec2.of(0, 0.5), Vec2.of(8, 0.5)), strokeWidth,
204
+ );
205
+ expect(intersections.length).toBe(1);
206
+ });
207
+ });
208
+
209
+ describe('polylineApproximation', () => {
210
+ it('should approximate Bézier curves with polylines', () => {
211
+ const path = Path.fromString('m0,0 l4,4 Q 1,4 4,1z');
212
+
213
+ expect(path.polylineApproximation()).toMatchObject([
214
+ new LineSegment2(Vec2.of(0, 0), Vec2.of(4, 4)),
215
+ new LineSegment2(Vec2.of(4, 4), Vec2.of(1, 4)),
216
+ new LineSegment2(Vec2.of(1, 4), Vec2.of(4, 1)),
217
+ new LineSegment2(Vec2.of(4, 1), Vec2.of(0, 0)),
218
+ ]);
219
+ });
220
+ });
221
+
222
+ describe('roughlyIntersectsClosed', () => {
223
+ it('small, line-only path', () => {
224
+ const path = Path.fromString('m0,0 l10,10 L0,10 z');
225
+ expect(
226
+ path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(20, 20)))
227
+ ).toBe(true);
228
+ expect(
229
+ path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(2, 2)))
230
+ ).toBe(true);
231
+ expect(
232
+ path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))
233
+ ).toBe(false);
234
+ expect(
235
+ path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))
236
+ ).toBe(true);
237
+ });
238
+
239
+ it('path with Bézier curves', () => {
240
+ const path = Path.fromString(`
241
+ M1090,2560
242
+ L1570,2620
243
+ Q1710,1300 1380,720
244
+ Q980,100 -460,-640
245
+ L-680,-200
246
+ Q670,470 960,980
247
+ Q1230,1370 1090,2560
248
+ `);
249
+ expect(
250
+ path.closedRoughlyIntersects(new Rect2(0, 0, 500, 500))
251
+ ).toBe(true);
252
+ expect(
253
+ path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))
254
+ ).toBe(true);
255
+ expect(
256
+ path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))
257
+ ).toBe(false);
258
+ });
259
+ });
260
+
261
+ describe('roughlyIntersects', () => {
262
+ it('should consider parts outside bbox of individual parts of a line as not intersecting', () => {
263
+ const path = Path.fromString(`
264
+ M10,10
265
+ L20,20
266
+ L100,21
267
+ `);
268
+ expect(
269
+ path.roughlyIntersects(new Rect2(0, 0, 50, 50))
270
+ ).toBe(true);
271
+ expect(
272
+ path.roughlyIntersects(new Rect2(0, 0, 5, 5))
273
+ ).toBe(false);
274
+ expect(
275
+ path.roughlyIntersects(new Rect2(8, 22, 1, 1))
276
+ ).toBe(false);
277
+ expect(
278
+ path.roughlyIntersects(new Rect2(21, 11, 1, 1))
279
+ ).toBe(false);
280
+ expect(
281
+ path.roughlyIntersects(new Rect2(50, 19, 1, 2))
282
+ ).toBe(true);
283
+ });
284
+ });
285
+
286
+ describe('fromRect', () => {
287
+ const filledRect = Path.fromRect(Rect2.unitSquare);
288
+ const strokedRect = Path.fromRect(Rect2.unitSquare, 0.1);
289
+
290
+ it('filled should be closed shape', () => {
291
+ const lastSegment = filledRect.parts[filledRect.parts.length - 1];
292
+
293
+ if (lastSegment.kind !== PathCommandType.LineTo) {
294
+ throw new Error('Rectangles should only be made up of lines');
295
+ }
296
+
297
+ expect(filledRect.startPoint).objEq(lastSegment.point);
298
+ });
299
+
300
+ it('stroked should be closed shape', () => {
301
+ const lastSegment = strokedRect.parts[strokedRect.parts.length - 1];
302
+ if (lastSegment.kind !== PathCommandType.LineTo) {
303
+ throw new Error('Rectangles should only be made up of lines');
304
+ }
305
+
306
+ expect(strokedRect.startPoint).objEq(lastSegment.point);
307
+ });
308
+ });
309
+ });
@@ -0,0 +1,77 @@
1
+ import Path, { PathCommandType } from './Path';
2
+ import { Vec2 } from '../Vec2';
3
+
4
+
5
+ describe('Path.toString', () => {
6
+ it('a single-point path should produce a move-to command', () => {
7
+ const path = new Path(Vec2.of(0, 0), []);
8
+ expect(path.toString()).toBe('M0,0');
9
+ });
10
+
11
+ it('should convert lineTo commands to L SVG commands', () => {
12
+ const path = new Path(Vec2.of(0.1, 0.2), [
13
+ {
14
+ kind: PathCommandType.LineTo,
15
+ point: Vec2.of(0.3, 0.4),
16
+ },
17
+ ]);
18
+ expect(path.toString()).toBe('M.1,.2L.3,.4');
19
+ });
20
+
21
+ it('should fix rounding errors', () => {
22
+ const path = new Path(Vec2.of(0.100000001, 0.199999999), [
23
+ {
24
+ kind: PathCommandType.QuadraticBezierTo,
25
+ controlPoint: Vec2.of(9999, -10.999999995),
26
+ endPoint: Vec2.of(0.000300001, 1.400000002),
27
+ },
28
+ {
29
+ kind: PathCommandType.LineTo,
30
+ point: Vec2.of(184.00482359999998, 1)
31
+ }
32
+ ]);
33
+
34
+ expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
35
+ });
36
+
37
+ it('should not remove trailing zeroes before decimal points', () => {
38
+ const path = new Path(Vec2.of(1000, 2_000_000), [
39
+ {
40
+ kind: PathCommandType.LineTo,
41
+ point: Vec2.of(30.00000001, 40.000000001),
42
+ },
43
+ ]);
44
+
45
+ expect(path.toString()).toBe('M1000,2000000l-970-1999960');
46
+ });
47
+
48
+ it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
49
+ const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
50
+ path1['cachedStringVersion'] = null; // Clear the cache.
51
+
52
+ expect(path1.toString()).toBe([
53
+ 'M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'
54
+ ].join(''));
55
+ });
56
+
57
+ it('should not lose precision when saving', () => {
58
+ const pathStr = 'M184.2,52.3l-.2-.2q-2.7,2.4 -3.2,3.5q-2.8,7 -.9,6.1q4.3-2.6 4.8-6.1q1.2-8.8 .4-8.3q-4.2,5.2 -3.9,3.9q.2-1.6 .3-2.1q.2-1.3 -.2-1q-3.8,6.5 -3.2,3.3q.6-4.1 1.1-5.3q4.1-10 3.3-8.3q-5.3,13.1 -6.6,14.1q-3.3,2.8 -1.8-1.5q2.8-9.7 2.7-8.4q0,.3 0,.4q-1.4,7.1 -2.7,8.5q-2.6,3.2 -2.5,2.9q-.3-1.9 -.7-1.9q-4.1,4.4 -2.9,1.9q1.1-3 .3-2.6q-1.8,2 -2.5,2.4q-4.5,2.8 -4.2,1.9q.3-1.6 .2-1.4q1.5,2.2 1.3,2.9q-.8,3.9 -.5,3.3q.8-7.6 2.5-13.3q2.6-9.2 2.9-6.9q.3,1.4 .3,1.2q-.7-.4 -.9,0q-2.2,11.6 -7.6,13.6q-3.9,1.6 -2.1-1.3q3-5.5 2.6-3.4q-.2,1.8 -.5,1.8q-3.2,.5 -4.1,1.2q-2.6,2.6 -1.9,2.5q4.7-4.4 3.7-5.5q-1.1-.9 -1.6-.6q-7.2,7.5 -3.9,6.5q.3-.1 .4-.4q.6-5.3 -.2-4.9q-2.8,2.3 -3.1,2.4q-3.7,1.5 -3.5,.5q.3-3.6 1.4-3.3q3.5,.7 1.9,2.4q-1.7,2.3 -1.6,.8q0-3.5 -.9-3.1q-5.1,3.3 -4.9,2.8q.1-4 -.8-3.5q-4.3,3.4 -4.6,2.5q-1-2.1 .5-8.7l-.2,0q-1.6,6.6 -.7,8.9q.7,1.2 5.2-2.3q.4-.5 .2,3.1q.1,1 5.5-2.4q.4-.4 .3,2.7q.1,2 2.4-.4q1.7-2.3 -2.1-3.2q-1.7-.3 -2,3.7q0,1.4 4.1-.1q.3-.1 3.1-2.4q.3-.5 -.4,4.5q0-.1 -.2,0q-2.6,1.2 4.5-5.7q0-.2 .8,.6q.9,.6 -3.7,4.7q-.5,1 2.7-1.7q.6-.7 3.7-1.2q.7-.2 .9-2.2q.1-2.7 -3.4,3.2q-1.8,3.4 2.7,1.9q5.6-2.1 7.8-14q-.1,.1 .3,.4q.6,.1 .3-1.6q-.7-2.8 -3.7,6.7q-1.8,5.8 -2.5,13.5q.1,1.1 1.3-3.1q.2-1 -1.3-3.3q-.5-.5 -1,1.6q-.1,1.3 4.8-1.5q1-1 3-2q.1-.4 -1.1,2q-1.1,3.1 3.7-1.3q-.4,0 -.1,1.5q.3,.8 3.3-2.5q1.3-1.6 2.7-8.9q0-.1 0-.4q-.3-1.9 -3.5,8.2q-1.3,4.9 2.4,2.1q1.4-1.2 6.6-14.3q.8-2.4 -3.9,7.9q-.6,1.3 -1.1,5.5q-.3,3.7 4-3.1q-.2,0 -.6,.6q-.2,.6 -.3,2.3q0,1.8 4.7-3.5q.1-.5 -1.2,7.9q-.5,3.2 -4.6,5.7q-1.3,1 1.5-5.5q.4-1.1 3.01-3.5';
59
+
60
+ const path1 = Path.fromString(pathStr);
61
+ path1['cachedStringVersion'] = null; // Clear the cache.
62
+ const path = Path.fromString(path1.toString(true));
63
+ path1['cachedStringVersion'] = null; // Clear the cache.
64
+
65
+ expect(path.toString(true)).toBe(path1.toString(true));
66
+ });
67
+
68
+ it('should remove no-op move-tos', () => {
69
+ const path1 = Path.fromString('M50,75m0,0q0,12.5 0,50q0,6.3 25,0');
70
+ path1['cachedStringVersion'] = null;
71
+ const path2 = Path.fromString('M150,175M150,175q0,12.5 0,50q0,6.3 25,0');
72
+ path2['cachedStringVersion'] = null;
73
+
74
+ expect(path1.toString()).toBe('M50,75q0,12.5 0,50q0,6.3 25,0');
75
+ expect(path2.toString()).toBe('M150,175q0,12.5 0,50q0,6.3 25,0');
76
+ });
77
+ });