@js-draw/math 1.21.3 → 1.22.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 (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;