@js-draw/math 1.21.3 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. package/build-config.json +1 -1
  2. package/dist/cjs/Color4.js +2 -2
  3. package/dist/cjs/Mat33.d.ts +1 -11
  4. package/dist/cjs/Mat33.js +8 -24
  5. package/dist/cjs/Vec3.js +9 -7
  6. package/dist/cjs/shapes/BezierJSWrapper.js +20 -13
  7. package/dist/cjs/shapes/LineSegment2.js +13 -17
  8. package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
  9. package/dist/cjs/shapes/Path.js +49 -47
  10. package/dist/cjs/shapes/Rect2.js +13 -15
  11. package/dist/cjs/shapes/Triangle.js +4 -5
  12. package/dist/cjs/utils/convexHull2Of.js +3 -3
  13. package/dist/mjs/Color4.mjs +2 -2
  14. package/dist/mjs/Mat33.d.ts +1 -11
  15. package/dist/mjs/Mat33.mjs +8 -24
  16. package/dist/mjs/Vec3.mjs +9 -7
  17. package/dist/mjs/shapes/BezierJSWrapper.mjs +20 -13
  18. package/dist/mjs/shapes/LineSegment2.mjs +13 -17
  19. package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
  20. package/dist/mjs/shapes/Path.mjs +49 -47
  21. package/dist/mjs/shapes/Rect2.mjs +13 -15
  22. package/dist/mjs/shapes/Triangle.mjs +4 -5
  23. package/dist/mjs/utils/convexHull2Of.mjs +3 -3
  24. package/dist-test/test_imports/test-require.cjs +1 -1
  25. package/package.json +3 -3
  26. package/src/Color4.test.ts +16 -21
  27. package/src/Color4.ts +22 -17
  28. package/src/Mat33.fromCSSMatrix.test.ts +31 -45
  29. package/src/Mat33.test.ts +58 -96
  30. package/src/Mat33.ts +61 -104
  31. package/src/Vec2.test.ts +3 -3
  32. package/src/Vec3.test.ts +2 -3
  33. package/src/Vec3.ts +34 -58
  34. package/src/lib.ts +0 -2
  35. package/src/polynomial/solveQuadratic.test.ts +39 -13
  36. package/src/polynomial/solveQuadratic.ts +5 -6
  37. package/src/rounding/cleanUpNumber.test.ts +1 -1
  38. package/src/rounding/constants.ts +1 -3
  39. package/src/rounding/getLenAfterDecimal.ts +1 -2
  40. package/src/rounding/lib.ts +1 -2
  41. package/src/rounding/toRoundedString.test.ts +1 -1
  42. package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
  43. package/src/rounding/toStringOfSamePrecision.ts +1 -1
  44. package/src/shapes/BezierJSWrapper.ts +54 -37
  45. package/src/shapes/CubicBezier.ts +3 -3
  46. package/src/shapes/LineSegment2.test.ts +24 -17
  47. package/src/shapes/LineSegment2.ts +26 -29
  48. package/src/shapes/Parameterized2DShape.ts +5 -4
  49. package/src/shapes/Path.fromString.test.ts +5 -5
  50. package/src/shapes/Path.test.ts +122 -120
  51. package/src/shapes/Path.toString.test.ts +7 -7
  52. package/src/shapes/Path.ts +378 -352
  53. package/src/shapes/PointShape2D.ts +3 -3
  54. package/src/shapes/QuadraticBezier.test.ts +27 -21
  55. package/src/shapes/QuadraticBezier.ts +4 -9
  56. package/src/shapes/Rect2.test.ts +44 -75
  57. package/src/shapes/Rect2.ts +30 -35
  58. package/src/shapes/Triangle.test.ts +31 -29
  59. package/src/shapes/Triangle.ts +17 -18
  60. package/src/utils/convexHull2Of.test.ts +54 -15
  61. package/src/utils/convexHull2Of.ts +9 -7
  62. package/tsconfig.json +1 -3
  63. package/typedoc.json +2 -2
@@ -35,10 +35,15 @@ describe('Path', () => {
35
35
 
36
36
  // Make sure the control points (and start/end points) match what was set
37
37
  expect(firstItem.getPoints()).toMatchObject([
38
- { x: 0, y: 0 }, { x: 1, y: 1 }, { x: -1, y: -1 }, { x: 3, y: 3 }
38
+ { x: 0, y: 0 },
39
+ { x: 1, y: 1 },
40
+ { x: -1, y: -1 },
41
+ { x: 3, y: 3 },
39
42
  ]);
40
43
  expect(secondItem.getPoints()).toMatchObject([
41
- { x: 3, y: 3 }, { x: 1, y: 1 }, { x: 0, y: 0 },
44
+ { x: 3, y: 3 },
45
+ { x: 1, y: 1 },
46
+ { x: 0, y: 0 },
42
47
  ]);
43
48
  });
44
49
 
@@ -55,23 +60,21 @@ describe('Path', () => {
55
60
 
56
61
  expect(path.geometry.length).toBe(1);
57
62
  expect(path.geometry[0]).toBeInstanceOf(LineSegment2);
58
- expect(path.geometry[0]).toMatchObject(
59
- new LineSegment2(lineStart, lineEnd)
60
- );
63
+ expect(path.geometry[0]).toMatchObject(new LineSegment2(lineStart, lineEnd));
61
64
  });
62
65
 
63
66
  it.each([
64
- [ 'm0,0 L1,1', 'M0,0 L1,1', true ],
65
- [ 'm0,0 L1,1', 'M1,1 L0,0', false ],
66
- [ 'm0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0', false ],
67
- [ 'm0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0 Q2,3 4,5', false ],
68
- [ 'm0,0 L1,1 Q2,3 4,5', 'M0,0 L1,1 Q2,3 4,5', true ],
69
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', true ],
70
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', false ],
71
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', false ],
72
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9.01', false ],
73
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7.01 8,9', false ],
74
- [ 'm0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5.01 6,7 8,9', false ],
67
+ ['m0,0 L1,1', 'M0,0 L1,1', true],
68
+ ['m0,0 L1,1', 'M1,1 L0,0', false],
69
+ ['m0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0', false],
70
+ ['m0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0 Q2,3 4,5', false],
71
+ ['m0,0 L1,1 Q2,3 4,5', 'M0,0 L1,1 Q2,3 4,5', true],
72
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', true],
73
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', false],
74
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', false],
75
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9.01', false],
76
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7.01 8,9', false],
77
+ ['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5.01 6,7 8,9', false],
75
78
  ])('.eq should check equality', (path1Str, path2Str, shouldEqual) => {
76
79
  expect(Path.fromString(path1Str)).objEq(Path.fromString(path1Str));
77
80
  expect(Path.fromString(path2Str)).objEq(Path.fromString(path2Str));
@@ -97,7 +100,7 @@ describe('Path', () => {
97
100
  ]);
98
101
 
99
102
  const intersections = path.intersection(
100
- new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200))
103
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)),
101
104
  );
102
105
 
103
106
  // Should only have intersections in quadrants II and III.
@@ -143,7 +146,8 @@ describe('Path', () => {
143
146
 
144
147
  const strokeWidth = 5;
145
148
  let intersections = path.intersection(
146
- new LineSegment2(Vec2.of(2000, 200), Vec2.of(2000, 400)), strokeWidth,
149
+ new LineSegment2(Vec2.of(2000, 200), Vec2.of(2000, 400)),
150
+ strokeWidth,
147
151
  );
148
152
  expect(intersections.length).toBe(0);
149
153
 
@@ -158,7 +162,8 @@ describe('Path', () => {
158
162
  // ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
159
163
 
160
164
  intersections = path.intersection(
161
- new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, 100)), strokeWidth,
165
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, 100)),
166
+ strokeWidth,
162
167
  );
163
168
  expect(intersections.length).toBe(1);
164
169
  expect(intersections[0].point.xy).toMatchObject({
@@ -166,10 +171,10 @@ describe('Path', () => {
166
171
  y: 105,
167
172
  });
168
173
 
169
-
170
174
  // Changing the order of the end points on the line should not change the result
171
175
  intersections = path.intersection(
172
- new LineSegment2(Vec2.of(-50, 100), Vec2.of(-50, 200)), strokeWidth,
176
+ new LineSegment2(Vec2.of(-50, 100), Vec2.of(-50, 200)),
177
+ strokeWidth,
173
178
  );
174
179
  expect(intersections.length).toBe(1);
175
180
  expect(intersections[0].point.xy).toMatchObject({
@@ -181,7 +186,8 @@ describe('Path', () => {
181
186
  // intersections — one entering and one leaving for each intersection with the
182
187
  // centers.
183
188
  intersections = path.intersection(
184
- new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)), strokeWidth,
189
+ new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)),
190
+ strokeWidth,
185
191
  );
186
192
  expect(intersections.length).toBe(4);
187
193
 
@@ -204,7 +210,7 @@ describe('Path', () => {
204
210
  kind: PathCommandType.QuadraticBezierTo,
205
211
  controlPoint: Vec2.unitX,
206
212
  endPoint: Vec2.unitY,
207
- }
213
+ },
208
214
  ]);
209
215
 
210
216
  const strokeWidth = 5;
@@ -212,13 +218,15 @@ describe('Path', () => {
212
218
  // Should be no intersections for a line contained entirely within the stroke
213
219
  // (including stroke width).
214
220
  let intersections = path.intersection(
215
- new LineSegment2(Vec2.of(-1, 0.5), Vec2.of(2, 0.5)), strokeWidth,
221
+ new LineSegment2(Vec2.of(-1, 0.5), Vec2.of(2, 0.5)),
222
+ strokeWidth,
216
223
  );
217
224
  expect(intersections).toHaveLength(0);
218
225
 
219
226
  // Should be an intersection when exiting/entering the edge of the stroke
220
227
  intersections = path.intersection(
221
- new LineSegment2(Vec2.of(0, 0.5), Vec2.of(8, 0.5)), strokeWidth,
228
+ new LineSegment2(Vec2.of(0, 0.5), Vec2.of(8, 0.5)),
229
+ strokeWidth,
222
230
  );
223
231
  expect(intersections).toHaveLength(1);
224
232
  });
@@ -226,10 +234,7 @@ describe('Path', () => {
226
234
  it('should correctly report intersections near the cap of a line-like Bézier', () => {
227
235
  const path = Path.fromString('M0,0Q14,0 27,0');
228
236
  expect(
229
- path.intersection(
230
- new LineSegment2(Vec2.of(0, -100), Vec2.of(0, 100)),
231
- 10,
232
- ),
237
+ path.intersection(new LineSegment2(Vec2.of(0, -100), Vec2.of(0, 100)), 10),
233
238
 
234
239
  // Should have intersections, despite being at the cap of the Bézier
235
240
  // curve.
@@ -237,22 +242,31 @@ describe('Path', () => {
237
242
  });
238
243
 
239
244
  it.each([
240
- [new LineSegment2(Vec2.of(43.5,-12.5), Vec2.of(40.5,24.5)), 0],
241
- [new LineSegment2(Vec2.of(35.5,19.5), Vec2.of(38.5,-17.5)), 0],
242
- ])('should correctly report positive intersections with a line-like Bézier', (line, strokeRadius) => {
243
- const bezier = Path.fromString('M0,0 Q50,0 100,0');
244
- expect(bezier.intersection(line, strokeRadius).length).toBeGreaterThan(0);
245
- });
245
+ [new LineSegment2(Vec2.of(43.5, -12.5), Vec2.of(40.5, 24.5)), 0],
246
+ [new LineSegment2(Vec2.of(35.5, 19.5), Vec2.of(38.5, -17.5)), 0],
247
+ ])(
248
+ 'should correctly report positive intersections with a line-like Bézier',
249
+ (line, strokeRadius) => {
250
+ const bezier = Path.fromString('M0,0 Q50,0 100,0');
251
+ expect(bezier.intersection(line, strokeRadius).length).toBeGreaterThan(0);
252
+ },
253
+ );
246
254
 
247
255
  it('should handle near-vertical lines', () => {
248
- const intersections = Path.fromString('M0,0 Q50,0 100,0').intersection(new LineSegment2(Vec2.of(44, -12), Vec2.of(39, 25)));
256
+ const intersections = Path.fromString('M0,0 Q50,0 100,0').intersection(
257
+ new LineSegment2(Vec2.of(44, -12), Vec2.of(39, 25)),
258
+ );
249
259
  expect(intersections).toHaveLength(1);
250
260
  });
251
261
 
252
262
  it('should handle single-point strokes', () => {
253
263
  const stroke = new Path(Vec2.zero, []);
254
- expect(stroke.intersection(new LineSegment2(Vec2.of(-2, -20), Vec2.of(-2, -1)), 1)).toHaveLength(0);
255
- expect(stroke.intersection(new LineSegment2(Vec2.of(-2, -2), Vec2.of(2, 2)), 1)).toHaveLength(2);
264
+ expect(
265
+ stroke.intersection(new LineSegment2(Vec2.of(-2, -20), Vec2.of(-2, -1)), 1),
266
+ ).toHaveLength(0);
267
+ expect(stroke.intersection(new LineSegment2(Vec2.of(-2, -2), Vec2.of(2, 2)), 1)).toHaveLength(
268
+ 2,
269
+ );
256
270
  });
257
271
  });
258
272
 
@@ -272,18 +286,12 @@ describe('Path', () => {
272
286
  describe('roughlyIntersectsClosed', () => {
273
287
  it('small, line-only path', () => {
274
288
  const path = Path.fromString('m0,0 l10,10 L0,10 z');
275
- expect(
276
- path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(20, 20)))
277
- ).toBe(true);
278
- expect(
279
- path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(2, 2)))
280
- ).toBe(true);
281
- expect(
282
- path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))
283
- ).toBe(false);
284
- expect(
285
- path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))
286
- ).toBe(true);
289
+ expect(path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(20, 20)))).toBe(
290
+ true,
291
+ );
292
+ expect(path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(2, 2)))).toBe(true);
293
+ expect(path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))).toBe(false);
294
+ expect(path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))).toBe(true);
287
295
  });
288
296
 
289
297
  it('path with Bézier curves', () => {
@@ -296,15 +304,9 @@ describe('Path', () => {
296
304
  Q670,470 960,980
297
305
  Q1230,1370 1090,2560
298
306
  `);
299
- expect(
300
- path.closedRoughlyIntersects(new Rect2(0, 0, 500, 500))
301
- ).toBe(true);
302
- expect(
303
- path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))
304
- ).toBe(true);
305
- expect(
306
- path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))
307
- ).toBe(false);
307
+ expect(path.closedRoughlyIntersects(new Rect2(0, 0, 500, 500))).toBe(true);
308
+ expect(path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))).toBe(true);
309
+ expect(path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))).toBe(false);
308
310
  });
309
311
  });
310
312
 
@@ -315,21 +317,11 @@ describe('Path', () => {
315
317
  L20,20
316
318
  L100,21
317
319
  `);
318
- expect(
319
- path.roughlyIntersects(new Rect2(0, 0, 50, 50))
320
- ).toBe(true);
321
- expect(
322
- path.roughlyIntersects(new Rect2(0, 0, 5, 5))
323
- ).toBe(false);
324
- expect(
325
- path.roughlyIntersects(new Rect2(8, 22, 1, 1))
326
- ).toBe(false);
327
- expect(
328
- path.roughlyIntersects(new Rect2(21, 11, 1, 1))
329
- ).toBe(false);
330
- expect(
331
- path.roughlyIntersects(new Rect2(50, 19, 1, 2))
332
- ).toBe(true);
320
+ expect(path.roughlyIntersects(new Rect2(0, 0, 50, 50))).toBe(true);
321
+ expect(path.roughlyIntersects(new Rect2(0, 0, 5, 5))).toBe(false);
322
+ expect(path.roughlyIntersects(new Rect2(8, 22, 1, 1))).toBe(false);
323
+ expect(path.roughlyIntersects(new Rect2(21, 11, 1, 1))).toBe(false);
324
+ expect(path.roughlyIntersects(new Rect2(50, 19, 1, 2))).toBe(true);
333
325
  });
334
326
  });
335
327
 
@@ -358,9 +350,7 @@ describe('Path', () => {
358
350
  });
359
351
 
360
352
  describe('splitAt', () => {
361
- it.each([
362
- 2, 3, 4, 5,
363
- ])('should split a line into %d sections', (numSections) => {
353
+ it.each([2, 3, 4, 5])('should split a line into %d sections', (numSections) => {
364
354
  const path = Path.fromString('m0,0 l1,0');
365
355
 
366
356
  const splitIndices: CurveIndexRecord[] = [];
@@ -371,7 +361,7 @@ describe('Path', () => {
371
361
 
372
362
  expect(split).toHaveLength(numSections + 1);
373
363
  expect(split[numSections].getEndPoint()).objEq(Vec2.unitX);
374
- for (let i = 0; i < numSections; i ++) {
364
+ for (let i = 0; i < numSections; i++) {
375
365
  expect(split[i].geometry).toHaveLength(1);
376
366
  const geom = split[i].geometry[0] as LineSegment2;
377
367
  expect(geom.p1.y).toBeCloseTo(0);
@@ -432,13 +422,13 @@ describe('Path', () => {
432
422
  original: 'm0,0 Q4,0 8,0 Q8,4 8,8',
433
423
  near: Vec2.of(8, 4),
434
424
  map: (p: Point2) => p.plus(Vec2.of(1, 1)),
435
- expected: [ 'M0,0Q4,0 8,0Q9,3 9,5', 'M9,5Q9,7 9,9' ],
425
+ expected: ['M0,0Q4,0 8,0Q9,3 9,5', 'M9,5Q9,7 9,9'],
436
426
  },
437
427
  {
438
428
  original: 'm0,0 L0,10',
439
429
  near: Vec2.of(0, 5),
440
430
  map: (p: Point2) => p.plus(Vec2.of(100, 0)),
441
- expected: [ 'M0,0L100,5', 'M100,5L0,10' ],
431
+ expected: ['M0,0L100,5', 'M100,5L0,10'],
442
432
  },
443
433
  {
444
434
  // Tested using SVG data similar to:
@@ -448,13 +438,16 @@ describe('Path', () => {
448
438
  original: 'm1,1 C1,2 2,10 4,4 C5,0 9,3 7,7',
449
439
  near: Vec2.of(3, 5),
450
440
  map: (p: Point2) => Vec2.of(Math.round(p.x), Math.round(p.y)),
451
- expected: [ 'M1,1C1,2 1,6 2,6', 'M2,6C3,6 3,6 4,4C5,0 9,3 7,7' ],
441
+ expected: ['M1,1C1,2 1,6 2,6', 'M2,6C3,6 3,6 4,4C5,0 9,3 7,7'],
452
442
  },
453
- ])('should support mapping newly-added points while splitting (case %j)', ({ original, near, map, expected }) => {
454
- const path = Path.fromString(original);
455
- const split = path.splitNear(near, { mapNewPoint: map });
456
- expect(split.map(p => p.toString(false))).toMatchObject(expected);
457
- });
443
+ ])(
444
+ 'should support mapping newly-added points while splitting (case %j)',
445
+ ({ original, near, map, expected }) => {
446
+ const path = Path.fromString(original);
447
+ const split = path.splitNear(near, { mapNewPoint: map });
448
+ expect(split.map((p) => p.toString(false))).toMatchObject(expected);
449
+ },
450
+ );
458
451
  });
459
452
 
460
453
  describe('spliced', () => {
@@ -485,23 +478,26 @@ describe('Path', () => {
485
478
  insert: 'M1,0 L1,1 L3,1 L3,0',
486
479
  expected: 'M1,0 L3,0 L3,1 L1,1 L1,0',
487
480
  },
488
- ])('.spliced should support inserting paths inbetween other paths (case %#)', ({ curve, from, to, insert, expected }) => {
489
- const originalCurve = Path.fromString(curve);
490
- expect(
491
- originalCurve.spliced(
492
- { curveIndex: from.i, parameterValue: from.t },
493
- { curveIndex: to.i, parameterValue: to.t },
494
- Path.fromString(insert),
495
- )
496
- ).objEq(Path.fromString(expected));
497
- });
481
+ ])(
482
+ '.spliced should support inserting paths inbetween other paths (case %#)',
483
+ ({ curve, from, to, insert, expected }) => {
484
+ const originalCurve = Path.fromString(curve);
485
+ expect(
486
+ originalCurve.spliced(
487
+ { curveIndex: from.i, parameterValue: from.t },
488
+ { curveIndex: to.i, parameterValue: to.t },
489
+ Path.fromString(insert),
490
+ ),
491
+ ).objEq(Path.fromString(expected));
492
+ },
493
+ );
498
494
  });
499
495
 
500
496
  it.each([
501
- [ 'm0,0 L1,1', 'M1,1 L0,0' ],
502
- [ 'm0,0 L1,1', 'M1,1 L0,0' ],
503
- [ 'M0,0 L1,1 Q2,2 3,3', 'M3,3 Q2,2 1,1 L0,0' ],
504
- [ 'M0,0 L1,1 Q4,2 5,3 C12,13 10,9 8,7', 'M8,7 C 10,9 12,13 5,3 Q 4,2 1,1 L 0,0' ],
497
+ ['m0,0 L1,1', 'M1,1 L0,0'],
498
+ ['m0,0 L1,1', 'M1,1 L0,0'],
499
+ ['M0,0 L1,1 Q2,2 3,3', 'M3,3 Q2,2 1,1 L0,0'],
500
+ ['M0,0 L1,1 Q4,2 5,3 C12,13 10,9 8,7', 'M8,7 C 10,9 12,13 5,3 Q 4,2 1,1 L 0,0'],
505
501
  ])('.reversed should reverse paths', (original, expected) => {
506
502
  expect(Path.fromString(original).reversed()).objEq(Path.fromString(expected));
507
503
  expect(Path.fromString(expected).reversed()).objEq(Path.fromString(original));
@@ -509,29 +505,35 @@ describe('Path', () => {
509
505
  });
510
506
 
511
507
  it.each([
512
- [ 'm0,0 l1,0', Vec2.of(0, 0), Vec2.of(0, 0) ],
513
- [ 'm0,0 l1,0', Vec2.of(0.5, 0), Vec2.of(0.5, 0) ],
514
- [ 'm0,0 Q1,0 1,2', Vec2.of(1, 0), Vec2.of(0.6236, 0.299) ],
515
- ])('.nearestPointTo should return the closest point on a path to the given parameter (case %#)', (path, point, expectedClosest) => {
516
- expect(Path.fromString(path).nearestPointTo(point).point).objEq(expectedClosest, 0.002);
517
- });
508
+ ['m0,0 l1,0', Vec2.of(0, 0), Vec2.of(0, 0)],
509
+ ['m0,0 l1,0', Vec2.of(0.5, 0), Vec2.of(0.5, 0)],
510
+ ['m0,0 Q1,0 1,2', Vec2.of(1, 0), Vec2.of(0.6236, 0.299)],
511
+ ])(
512
+ '.nearestPointTo should return the closest point on a path to the given parameter (case %#)',
513
+ (path, point, expectedClosest) => {
514
+ expect(Path.fromString(path).nearestPointTo(point).point).objEq(expectedClosest, 0.002);
515
+ },
516
+ );
518
517
 
519
518
  it.each([
520
519
  // Polyline
521
- [ 'm0,0 l1,0 l0,1', [ 0, 0.5 ], Vec2.of(1, 0) ],
522
- [ 'm0,0 l1,0 l0,1', [ 0, 0.99 ], Vec2.of(1, 0) ],
523
- [ 'm0,0 l1,0 l0,1', [ 1, 0 ], Vec2.of(0, 1) ],
524
- [ 'm0,0 l1,0 l0,1', [ 1, 0.5 ], Vec2.of(0, 1) ],
525
- [ 'm0,0 l1,0 l0,1', [ 1, 1 ], Vec2.of(0, 1) ],
520
+ ['m0,0 l1,0 l0,1', [0, 0.5], Vec2.of(1, 0)],
521
+ ['m0,0 l1,0 l0,1', [0, 0.99], Vec2.of(1, 0)],
522
+ ['m0,0 l1,0 l0,1', [1, 0], Vec2.of(0, 1)],
523
+ ['m0,0 l1,0 l0,1', [1, 0.5], Vec2.of(0, 1)],
524
+ ['m0,0 l1,0 l0,1', [1, 1], Vec2.of(0, 1)],
526
525
 
527
526
  // Shape with quadratic Bézier curves
528
- [ 'M0,0 Q1,0 0,1', [ 0, 0 ], Vec2.of(1, 0) ],
529
- [ 'M0,0 Q1,1 0,1', [ 0, 1 ], Vec2.of(-1, 0) ],
530
- [ 'M0,0 Q1,0 1,1 Q0,1 0,2', [ 0, 1 ], Vec2.of(0, 1) ],
531
- [ 'M0,0 Q1,0 1,1 Q0,1 0,2', [ 1, 1 ], Vec2.of(0, 1) ],
532
- ])('.tangentAt should point in the direction of increasing parameter values, for curve %s at %j', (pathString, evalAt, expected) => {
533
- const at: CurveIndexRecord = { curveIndex: evalAt[0], parameterValue: evalAt[1] };
534
- const path = Path.fromString(pathString);
535
- expect(path.tangentAt(at)).objEq(expected);
536
- });
527
+ ['M0,0 Q1,0 0,1', [0, 0], Vec2.of(1, 0)],
528
+ ['M0,0 Q1,1 0,1', [0, 1], Vec2.of(-1, 0)],
529
+ ['M0,0 Q1,0 1,1 Q0,1 0,2', [0, 1], Vec2.of(0, 1)],
530
+ ['M0,0 Q1,0 1,1 Q0,1 0,2', [1, 1], Vec2.of(0, 1)],
531
+ ])(
532
+ '.tangentAt should point in the direction of increasing parameter values, for curve %s at %j',
533
+ (pathString, evalAt, expected) => {
534
+ const at: CurveIndexRecord = { curveIndex: evalAt[0], parameterValue: evalAt[1] };
535
+ const path = Path.fromString(pathString);
536
+ expect(path.tangentAt(at)).objEq(expected);
537
+ },
538
+ );
537
539
  });
@@ -1,7 +1,6 @@
1
1
  import Path, { PathCommandType } from './Path';
2
2
  import { Vec2 } from '../Vec2';
3
3
 
4
-
5
4
  describe('Path.toString', () => {
6
5
  it('a single-point path should produce a move-to command', () => {
7
6
  const path = new Path(Vec2.of(0, 0), []);
@@ -27,8 +26,8 @@ describe('Path.toString', () => {
27
26
  },
28
27
  {
29
28
  kind: PathCommandType.LineTo,
30
- point: Vec2.of(184.00482359999998, 1)
31
- }
29
+ point: Vec2.of(184.00482359999998, 1),
30
+ },
32
31
  ]);
33
32
 
34
33
  expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
@@ -49,13 +48,14 @@ describe('Path.toString', () => {
49
48
  const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
50
49
  const ignoreCache = true;
51
50
 
52
- expect(path1.toString(undefined, ignoreCache)).toBe([
53
- 'M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'
54
- ].join(''));
51
+ expect(path1.toString(undefined, ignoreCache)).toBe(
52
+ ['M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'].join(''),
53
+ );
55
54
  });
56
55
 
57
56
  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';
57
+ const pathStr =
58
+ '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
59
 
60
60
  const path1 = Path.fromString(pathStr);
61
61
  const ignoreCache = true;