@js-draw/math 1.21.3 → 1.23.1

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 (81) hide show
  1. package/build-config.json +1 -1
  2. package/dist/cjs/Color4.d.ts +24 -1
  3. package/dist/cjs/Color4.js +35 -3
  4. package/dist/cjs/Mat33.d.ts +21 -11
  5. package/dist/cjs/Mat33.js +28 -24
  6. package/dist/cjs/Vec3.d.ts +12 -3
  7. package/dist/cjs/Vec3.js +20 -9
  8. package/dist/cjs/lib.d.ts +3 -0
  9. package/dist/cjs/lib.js +3 -0
  10. package/dist/cjs/shapes/BezierJSWrapper.d.ts +2 -0
  11. package/dist/cjs/shapes/BezierJSWrapper.js +22 -13
  12. package/dist/cjs/shapes/LineSegment2.js +13 -17
  13. package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
  14. package/dist/cjs/shapes/Path.d.ts +1 -0
  15. package/dist/cjs/shapes/Path.js +50 -47
  16. package/dist/cjs/shapes/QuadraticBezier.d.ts +19 -2
  17. package/dist/cjs/shapes/QuadraticBezier.js +26 -3
  18. package/dist/cjs/shapes/Rect2.d.ts +13 -0
  19. package/dist/cjs/shapes/Rect2.js +35 -16
  20. package/dist/cjs/shapes/Triangle.js +4 -5
  21. package/dist/cjs/utils/convexHull2Of.js +3 -3
  22. package/dist/mjs/Color4.d.ts +24 -1
  23. package/dist/mjs/Color4.mjs +35 -3
  24. package/dist/mjs/Mat33.d.ts +21 -11
  25. package/dist/mjs/Mat33.mjs +28 -24
  26. package/dist/mjs/Vec3.d.ts +12 -3
  27. package/dist/mjs/Vec3.mjs +20 -9
  28. package/dist/mjs/lib.d.ts +3 -0
  29. package/dist/mjs/lib.mjs +3 -0
  30. package/dist/mjs/shapes/BezierJSWrapper.d.ts +2 -0
  31. package/dist/mjs/shapes/BezierJSWrapper.mjs +22 -13
  32. package/dist/mjs/shapes/LineSegment2.mjs +13 -17
  33. package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
  34. package/dist/mjs/shapes/Path.d.ts +1 -0
  35. package/dist/mjs/shapes/Path.mjs +50 -47
  36. package/dist/mjs/shapes/QuadraticBezier.d.ts +19 -2
  37. package/dist/mjs/shapes/QuadraticBezier.mjs +26 -3
  38. package/dist/mjs/shapes/Rect2.d.ts +13 -0
  39. package/dist/mjs/shapes/Rect2.mjs +35 -16
  40. package/dist/mjs/shapes/Triangle.mjs +4 -5
  41. package/dist/mjs/utils/convexHull2Of.mjs +3 -3
  42. package/dist-test/test_imports/test-require.cjs +1 -1
  43. package/package.json +3 -3
  44. package/src/Color4.test.ts +21 -21
  45. package/src/Color4.ts +61 -18
  46. package/src/Mat33.fromCSSMatrix.test.ts +32 -46
  47. package/src/Mat33.test.ts +64 -102
  48. package/src/Mat33.ts +81 -104
  49. package/src/Vec2.test.ts +3 -3
  50. package/src/Vec3.test.ts +2 -3
  51. package/src/Vec3.ts +46 -61
  52. package/src/lib.ts +3 -2
  53. package/src/polynomial/solveQuadratic.test.ts +39 -13
  54. package/src/polynomial/solveQuadratic.ts +5 -6
  55. package/src/rounding/cleanUpNumber.test.ts +1 -1
  56. package/src/rounding/constants.ts +1 -3
  57. package/src/rounding/getLenAfterDecimal.ts +1 -2
  58. package/src/rounding/lib.ts +1 -2
  59. package/src/rounding/toRoundedString.test.ts +1 -1
  60. package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
  61. package/src/rounding/toStringOfSamePrecision.ts +1 -1
  62. package/src/shapes/BezierJSWrapper.ts +56 -37
  63. package/src/shapes/CubicBezier.ts +3 -3
  64. package/src/shapes/LineSegment2.test.ts +24 -17
  65. package/src/shapes/LineSegment2.ts +26 -29
  66. package/src/shapes/Parameterized2DShape.ts +5 -4
  67. package/src/shapes/Path.fromString.test.ts +5 -5
  68. package/src/shapes/Path.test.ts +122 -120
  69. package/src/shapes/Path.toString.test.ts +7 -7
  70. package/src/shapes/Path.ts +379 -352
  71. package/src/shapes/PointShape2D.ts +3 -3
  72. package/src/shapes/QuadraticBezier.test.ts +27 -21
  73. package/src/shapes/QuadraticBezier.ts +26 -11
  74. package/src/shapes/Rect2.test.ts +44 -75
  75. package/src/shapes/Rect2.ts +47 -35
  76. package/src/shapes/Triangle.test.ts +31 -29
  77. package/src/shapes/Triangle.ts +17 -18
  78. package/src/utils/convexHull2Of.test.ts +54 -15
  79. package/src/utils/convexHull2Of.ts +9 -7
  80. package/tsconfig.json +1 -3
  81. package/typedoc.json +2 -2
@@ -11,6 +11,7 @@ import Parameterized2DShape from './Parameterized2DShape';
11
11
  import BezierJSWrapper from './BezierJSWrapper';
12
12
  import convexHull2Of from '../utils/convexHull2Of';
13
13
 
14
+ /** Identifiers for different path commands. These commands can make up a {@link Path}. */
14
15
  export enum PathCommandType {
15
16
  LineTo,
16
17
  MoveTo,
@@ -41,7 +42,11 @@ export interface MoveToPathCommand {
41
42
  point: Point2;
42
43
  }
43
44
 
44
- export type PathCommand = CubicBezierPathCommand | QuadraticBezierPathCommand | MoveToPathCommand | LinePathCommand;
45
+ export type PathCommand =
46
+ | CubicBezierPathCommand
47
+ | QuadraticBezierPathCommand
48
+ | MoveToPathCommand
49
+ | LinePathCommand;
45
50
 
46
51
  export interface IntersectionResult {
47
52
  // @internal
@@ -62,7 +67,7 @@ export interface PathSplitOptions {
62
67
  * Allows mapping points on newly added segments. This is useful, for example,
63
68
  * to round points to prevent long decimals when later saving.
64
69
  */
65
- mapNewPoint?: (point: Point2)=>Point2;
70
+ mapNewPoint?: (point: Point2) => Point2;
66
71
  }
67
72
 
68
73
  /**
@@ -182,7 +187,7 @@ export class Path {
182
187
  return Rect2.union(...bboxes);
183
188
  }
184
189
 
185
- private cachedGeometry: Parameterized2DShape[]|null = null;
190
+ private cachedGeometry: Parameterized2DShape[] | null = null;
186
191
 
187
192
  // Lazy-loads and returns this path's geometry
188
193
  public get geometry(): Parameterized2DShape[] {
@@ -197,35 +202,27 @@ export class Path {
197
202
  let exhaustivenessCheck: never;
198
203
 
199
204
  switch (part.kind) {
200
- case PathCommandType.CubicBezierTo:
201
- geometry.push(
202
- new CubicBezier(
203
- startPoint, part.controlPoint1, part.controlPoint2, part.endPoint
204
- )
205
- );
206
- startPoint = part.endPoint;
207
- break;
208
- case PathCommandType.QuadraticBezierTo:
209
- geometry.push(
210
- new QuadraticBezier(
211
- startPoint, part.controlPoint, part.endPoint
212
- )
213
- );
214
- startPoint = part.endPoint;
215
- break;
216
- case PathCommandType.LineTo:
217
- geometry.push(
218
- new LineSegment2(startPoint, part.point)
219
- );
220
- startPoint = part.point;
221
- break;
222
- case PathCommandType.MoveTo:
223
- geometry.push(new PointShape2D(part.point));
224
- startPoint = part.point;
225
- break;
226
- default:
227
- exhaustivenessCheck = part;
228
- return exhaustivenessCheck;
205
+ case PathCommandType.CubicBezierTo:
206
+ geometry.push(
207
+ new CubicBezier(startPoint, part.controlPoint1, part.controlPoint2, part.endPoint),
208
+ );
209
+ startPoint = part.endPoint;
210
+ break;
211
+ case PathCommandType.QuadraticBezierTo:
212
+ geometry.push(new QuadraticBezier(startPoint, part.controlPoint, part.endPoint));
213
+ startPoint = part.endPoint;
214
+ break;
215
+ case PathCommandType.LineTo:
216
+ geometry.push(new LineSegment2(startPoint, part.point));
217
+ startPoint = part.point;
218
+ break;
219
+ case PathCommandType.MoveTo:
220
+ geometry.push(new PointShape2D(part.point));
221
+ startPoint = part.point;
222
+ break;
223
+ default:
224
+ exhaustivenessCheck = part;
225
+ return exhaustivenessCheck;
229
226
  }
230
227
  }
231
228
 
@@ -246,26 +243,26 @@ export class Path {
246
243
  let exhaustivenessCheck: never;
247
244
 
248
245
  switch (part.kind) {
249
- case PathCommandType.CubicBezierTo:
250
- yield part.endPoint;
251
- break;
252
- case PathCommandType.QuadraticBezierTo:
253
- yield part.endPoint;
254
- break;
255
- case PathCommandType.LineTo:
256
- yield part.point;
257
- break;
258
- case PathCommandType.MoveTo:
259
- yield part.point;
260
- break;
261
- default:
262
- exhaustivenessCheck = part;
263
- return exhaustivenessCheck;
246
+ case PathCommandType.CubicBezierTo:
247
+ yield part.endPoint;
248
+ break;
249
+ case PathCommandType.QuadraticBezierTo:
250
+ yield part.endPoint;
251
+ break;
252
+ case PathCommandType.LineTo:
253
+ yield part.point;
254
+ break;
255
+ case PathCommandType.MoveTo:
256
+ yield part.point;
257
+ break;
258
+ default:
259
+ exhaustivenessCheck = part;
260
+ return exhaustivenessCheck;
264
261
  }
265
262
  }
266
263
  }
267
264
 
268
- private cachedPolylineApproximation: LineSegment2[]|null = null;
265
+ private cachedPolylineApproximation: LineSegment2[] | null = null;
269
266
 
270
267
  // Approximates this path with a group of line segments.
271
268
  public polylineApproximation(): LineSegment2[] {
@@ -277,16 +274,16 @@ export class Path {
277
274
 
278
275
  for (const part of this.parts) {
279
276
  switch (part.kind) {
280
- case PathCommandType.CubicBezierTo:
281
- points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
282
- break;
283
- case PathCommandType.QuadraticBezierTo:
284
- points.push(part.controlPoint, part.endPoint);
285
- break;
286
- case PathCommandType.MoveTo:
287
- case PathCommandType.LineTo:
288
- points.push(part.point);
289
- break;
277
+ case PathCommandType.CubicBezierTo:
278
+ points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
279
+ break;
280
+ case PathCommandType.QuadraticBezierTo:
281
+ points.push(part.controlPoint, part.endPoint);
282
+ break;
283
+ case PathCommandType.MoveTo:
284
+ case PathCommandType.LineTo:
285
+ points.push(part.point);
286
+ break;
290
287
  }
291
288
  }
292
289
 
@@ -304,19 +301,19 @@ export class Path {
304
301
  const points = [startPoint];
305
302
  let exhaustivenessCheck: never;
306
303
  switch (part.kind) {
307
- case PathCommandType.MoveTo:
308
- case PathCommandType.LineTo:
309
- points.push(part.point);
310
- break;
311
- case PathCommandType.CubicBezierTo:
312
- points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
313
- break;
314
- case PathCommandType.QuadraticBezierTo:
315
- points.push(part.controlPoint, part.endPoint);
316
- break;
317
- default:
318
- exhaustivenessCheck = part;
319
- return exhaustivenessCheck;
304
+ case PathCommandType.MoveTo:
305
+ case PathCommandType.LineTo:
306
+ points.push(part.point);
307
+ break;
308
+ case PathCommandType.CubicBezierTo:
309
+ points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
310
+ break;
311
+ case PathCommandType.QuadraticBezierTo:
312
+ points.push(part.controlPoint, part.endPoint);
313
+ break;
314
+ default:
315
+ exhaustivenessCheck = part;
316
+ return exhaustivenessCheck;
320
317
  }
321
318
 
322
319
  return Rect2.bboxOf(points);
@@ -356,7 +353,9 @@ export class Path {
356
353
  * both end points of `line` and each point in `additionalRaymarchStartPoints`.
357
354
  */
358
355
  private raymarchIntersectionWith(
359
- line: LineSegment2, strokeRadius: number, additionalRaymarchStartPoints: Point2[] = []
356
+ line: LineSegment2,
357
+ strokeRadius: number,
358
+ additionalRaymarchStartPoints: Point2[] = [],
360
359
  ): IntersectionResult[] {
361
360
  // No intersection between bounding boxes: No possible intersection
362
361
  // of the interior.
@@ -368,9 +367,9 @@ export class Path {
368
367
 
369
368
  type DistanceFunction = (point: Point2) => number;
370
369
  type DistanceFunctionRecord = {
371
- part: Parameterized2DShape,
372
- bbox: Rect2,
373
- distFn: DistanceFunction,
370
+ part: Parameterized2DShape;
371
+ bbox: Rect2;
372
+ distFn: DistanceFunction;
374
373
  };
375
374
  const partDistFunctionRecords: DistanceFunctionRecord[] = [];
376
375
 
@@ -407,9 +406,9 @@ export class Path {
407
406
 
408
407
  // Returns the minimum distance to a part in this stroke, where only parts that the given
409
408
  // line could intersect are considered.
410
- const sdf = (point: Point2): [Parameterized2DShape|null, number] => {
409
+ const sdf = (point: Point2): [Parameterized2DShape | null, number] => {
411
410
  let minDist = Infinity;
412
- let minDistPart: Parameterized2DShape|null = null;
411
+ let minDistPart: Parameterized2DShape | null = null;
413
412
 
414
413
  const uncheckedDistFunctions: DistanceFunctionRecord[] = [];
415
414
 
@@ -448,18 +447,15 @@ export class Path {
448
447
  }
449
448
  }
450
449
 
451
- return [ minDistPart, minDist - strokeRadius ];
450
+ return [minDistPart, minDist - strokeRadius];
452
451
  };
453
452
 
454
-
455
453
  // Raymarch:
456
454
  const maxRaymarchSteps = 8;
457
455
 
458
456
  // Start raymarching from each of these points. This allows detection of multiple
459
457
  // intersections.
460
- const startPoints = [
461
- line.p1, ...additionalRaymarchStartPoints, line.p2
462
- ];
458
+ const startPoints = [line.p1, ...additionalRaymarchStartPoints, line.p2];
463
459
 
464
460
  // Converts a point ON THE LINE to a parameter
465
461
  const pointToParameter = (point: Point2) => {
@@ -491,12 +487,12 @@ export class Path {
491
487
  startPoint: Point2,
492
488
 
493
489
  // Direction to march in (multiplies line.direction)
494
- directionMultiplier: -1|1,
490
+ directionMultiplier: -1 | 1,
495
491
 
496
492
  // Terminate if the current point corresponds to a parameter
497
493
  // below this.
498
494
  minimumLineParameter: number,
499
- ): number|null => {
495
+ ): number | null => {
500
496
  let currentPoint = startPoint;
501
497
  let [lastPart, lastDist] = sdf(currentPoint);
502
498
  let lastParameter = pointToParameter(currentPoint);
@@ -594,7 +590,9 @@ export class Path {
594
590
  }
595
591
 
596
592
  if (this.parts.length === 0) {
597
- return new Path(this.startPoint, [{ kind: PathCommandType.MoveTo, point: this.startPoint }]).intersection(line, strokeRadius);
593
+ return new Path(this.startPoint, [
594
+ { kind: PathCommandType.MoveTo, point: this.startPoint },
595
+ ]).intersection(line, strokeRadius);
598
596
  }
599
597
 
600
598
  let index = 0;
@@ -610,7 +608,7 @@ export class Path {
610
608
  });
611
609
  }
612
610
 
613
- index ++;
611
+ index++;
614
612
  }
615
613
 
616
614
  // If given a non-zero strokeWidth, attempt to raymarch.
@@ -619,7 +617,7 @@ export class Path {
619
617
  const doRaymarching = strokeRadius && strokeRadius > 1e-8;
620
618
  if (doRaymarching) {
621
619
  // Starting points for raymarching (in addition to the end points of the line).
622
- const startPoints = result.map(intersection => intersection.point);
620
+ const startPoints = result.map((intersection) => intersection.point);
623
621
  result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
624
622
  }
625
623
 
@@ -678,9 +676,17 @@ export class Path {
678
676
  *
679
677
  * This method is analogous to {@link Array.toSpliced}.
680
678
  */
681
- public spliced(deleteFrom: CurveIndexRecord, deleteTo: CurveIndexRecord, insert: Path|undefined, options?: PathSplitOptions): Path {
679
+ public spliced(
680
+ deleteFrom: CurveIndexRecord,
681
+ deleteTo: CurveIndexRecord,
682
+ insert: Path | undefined,
683
+ options?: PathSplitOptions,
684
+ ): Path {
682
685
  const isBeforeOrEqual = (a: CurveIndexRecord, b: CurveIndexRecord) => {
683
- return a.curveIndex < b.curveIndex || (a.curveIndex === b.curveIndex && a.parameterValue <= b.parameterValue);
686
+ return (
687
+ a.curveIndex < b.curveIndex ||
688
+ (a.curveIndex === b.curveIndex && a.parameterValue <= b.parameterValue)
689
+ );
684
690
  };
685
691
 
686
692
  if (isBeforeOrEqual(deleteFrom, deleteTo)) {
@@ -711,11 +717,14 @@ export class Path {
711
717
  }
712
718
  }
713
719
 
714
- public splitAt(at: CurveIndexRecord, options?: PathSplitOptions): [Path]|[Path, Path];
720
+ public splitAt(at: CurveIndexRecord, options?: PathSplitOptions): [Path] | [Path, Path];
715
721
  public splitAt(at: CurveIndexRecord[], options?: PathSplitOptions): Path[];
716
722
 
717
723
  // @internal
718
- public splitAt(splitAt: CurveIndexRecord[]|CurveIndexRecord, options?: PathSplitOptions): Path[] {
724
+ public splitAt(
725
+ splitAt: CurveIndexRecord[] | CurveIndexRecord,
726
+ options?: PathSplitOptions,
727
+ ): Path[] {
719
728
  if (!Array.isArray(splitAt)) {
720
729
  splitAt = [splitAt];
721
730
  }
@@ -728,9 +737,9 @@ export class Path {
728
737
  //
729
738
 
730
739
  while (
731
- splitAt.length > 0
732
- && splitAt[splitAt.length - 1].curveIndex >= this.parts.length - 1
733
- && splitAt[splitAt.length - 1].parameterValue >= 1
740
+ splitAt.length > 0 &&
741
+ splitAt[splitAt.length - 1].curveIndex >= this.parts.length - 1 &&
742
+ splitAt[splitAt.length - 1].parameterValue >= 1
734
743
  ) {
735
744
  splitAt.pop();
736
745
  }
@@ -738,9 +747,9 @@ export class Path {
738
747
  splitAt.reverse(); // .reverse() <-- We're `.pop`ing from the end
739
748
 
740
749
  while (
741
- splitAt.length > 0
742
- && splitAt[splitAt.length - 1].curveIndex <= 0
743
- && splitAt[splitAt.length - 1].parameterValue <= 0
750
+ splitAt.length > 0 &&
751
+ splitAt[splitAt.length - 1].curveIndex <= 0 &&
752
+ splitAt[splitAt.length - 1].parameterValue <= 0
744
753
  ) {
745
754
  splitAt.pop();
746
755
  }
@@ -750,7 +759,7 @@ export class Path {
750
759
  }
751
760
 
752
761
  const expectedSplitCount = splitAt.length + 1;
753
- const mapNewPoint = options?.mapNewPoint ?? ((p: Point2)=>p);
762
+ const mapNewPoint = options?.mapNewPoint ?? ((p: Point2) => p);
754
763
 
755
764
  const result: Path[] = [];
756
765
  let currentStartPoint = this.startPoint;
@@ -762,7 +771,7 @@ export class Path {
762
771
 
763
772
  let { curveIndex, parameterValue } = splitAt.pop()!;
764
773
 
765
- for (let i = 0; i < this.parts.length; i ++) {
774
+ for (let i = 0; i < this.parts.length; i++) {
766
775
  if (i !== curveIndex) {
767
776
  currentPath.push(this.parts[i]);
768
777
  } else {
@@ -773,71 +782,71 @@ export class Path {
773
782
  const newPath: PathCommand[] = [];
774
783
 
775
784
  switch (part.kind) {
776
- case PathCommandType.MoveTo:
777
- currentPath.push({
778
- kind: part.kind,
779
- point: part.point,
780
- });
781
- newPathStart = part.point;
782
- break;
783
- case PathCommandType.LineTo:
784
- {
785
- const split = (geom as LineSegment2).splitAt(parameterValue);
785
+ case PathCommandType.MoveTo:
786
786
  currentPath.push({
787
787
  kind: part.kind,
788
- point: mapNewPoint(split[0].p2),
788
+ point: part.point,
789
789
  });
790
- newPathStart = split[0].p2;
791
- if (split.length > 1) {
792
- console.assert(split.length === 2);
793
- newPath.push({
790
+ newPathStart = part.point;
791
+ break;
792
+ case PathCommandType.LineTo:
793
+ {
794
+ const split = (geom as LineSegment2).splitAt(parameterValue);
795
+ currentPath.push({
794
796
  kind: part.kind,
795
-
796
- // Don't map: For lines, the end point of the split is
797
- // the same as the end point of the original:
798
- point: split[1]!.p2,
797
+ point: mapNewPoint(split[0].p2),
799
798
  });
800
- geom = split[1]!;
801
- }
802
- }
803
- break;
804
- case PathCommandType.QuadraticBezierTo:
805
- case PathCommandType.CubicBezierTo:
806
- {
807
- const split = (geom as BezierJSWrapper).splitAt(parameterValue);
808
- let isFirstPart = split.length === 2;
809
- for (const segment of split) {
810
- geom = segment;
811
- const targetArray = isFirstPart ? currentPath : newPath;
812
- const controlPoints = segment.getPoints();
813
- if (part.kind === PathCommandType.CubicBezierTo) {
814
- targetArray.push({
815
- kind: part.kind,
816
- controlPoint1: mapNewPoint(controlPoints[1]),
817
- controlPoint2: mapNewPoint(controlPoints[2]),
818
- endPoint: mapNewPoint(controlPoints[3]),
819
- });
820
- } else {
821
- targetArray.push({
799
+ newPathStart = split[0].p2;
800
+ if (split.length > 1) {
801
+ console.assert(split.length === 2);
802
+ newPath.push({
822
803
  kind: part.kind,
823
- controlPoint: mapNewPoint(controlPoints[1]),
824
- endPoint: mapNewPoint(controlPoints[2]),
804
+
805
+ // Don't map: For lines, the end point of the split is
806
+ // the same as the end point of the original:
807
+ point: split[1]!.p2,
825
808
  });
809
+ geom = split[1]!;
826
810
  }
827
-
828
- // We want the start of the new path to match the start of the
829
- // FIRST Bézier in the NEW path.
830
- if (!isFirstPart) {
831
- newPathStart = controlPoints[0];
811
+ }
812
+ break;
813
+ case PathCommandType.QuadraticBezierTo:
814
+ case PathCommandType.CubicBezierTo:
815
+ {
816
+ const split = (geom as BezierJSWrapper).splitAt(parameterValue);
817
+ let isFirstPart = split.length === 2;
818
+ for (const segment of split) {
819
+ geom = segment;
820
+ const targetArray = isFirstPart ? currentPath : newPath;
821
+ const controlPoints = segment.getPoints();
822
+ if (part.kind === PathCommandType.CubicBezierTo) {
823
+ targetArray.push({
824
+ kind: part.kind,
825
+ controlPoint1: mapNewPoint(controlPoints[1]),
826
+ controlPoint2: mapNewPoint(controlPoints[2]),
827
+ endPoint: mapNewPoint(controlPoints[3]),
828
+ });
829
+ } else {
830
+ targetArray.push({
831
+ kind: part.kind,
832
+ controlPoint: mapNewPoint(controlPoints[1]),
833
+ endPoint: mapNewPoint(controlPoints[2]),
834
+ });
835
+ }
836
+
837
+ // We want the start of the new path to match the start of the
838
+ // FIRST Bézier in the NEW path.
839
+ if (!isFirstPart) {
840
+ newPathStart = controlPoints[0];
841
+ }
842
+ isFirstPart = false;
832
843
  }
833
- isFirstPart = false;
834
844
  }
845
+ break;
846
+ default: {
847
+ const exhaustivenessCheck: never = part;
848
+ return exhaustivenessCheck;
835
849
  }
836
- break;
837
- default: {
838
- const exhaustivenessCheck: never = part;
839
- return exhaustivenessCheck;
840
- }
841
850
  }
842
851
 
843
852
  result.push(new Path(currentStartPoint, [...currentPath]));
@@ -867,7 +876,7 @@ export class Path {
867
876
 
868
877
  console.assert(
869
878
  result.length === expectedSplitCount,
870
- `should split into splitAt.length + 1 splits (was ${result.length}, expected ${expectedSplitCount})`
879
+ `should split into splitAt.length + 1 splits (was ${result.length}, expected ${expectedSplitCount})`,
871
880
  );
872
881
  return result;
873
882
  }
@@ -907,37 +916,40 @@ export class Path {
907
916
  return result;
908
917
  }
909
918
 
910
- private static mapPathCommand(part: PathCommand, mapping: (point: Point2)=> Point2): PathCommand {
919
+ private static mapPathCommand(
920
+ part: PathCommand,
921
+ mapping: (point: Point2) => Point2,
922
+ ): PathCommand {
911
923
  switch (part.kind) {
912
- case PathCommandType.MoveTo:
913
- case PathCommandType.LineTo:
914
- return {
915
- kind: part.kind,
916
- point: mapping(part.point),
917
- };
918
- break;
919
- case PathCommandType.CubicBezierTo:
920
- return {
921
- kind: part.kind,
922
- controlPoint1: mapping(part.controlPoint1),
923
- controlPoint2: mapping(part.controlPoint2),
924
- endPoint: mapping(part.endPoint),
925
- };
926
- break;
927
- case PathCommandType.QuadraticBezierTo:
928
- return {
929
- kind: part.kind,
930
- controlPoint: mapping(part.controlPoint),
931
- endPoint: mapping(part.endPoint),
932
- };
933
- break;
924
+ case PathCommandType.MoveTo:
925
+ case PathCommandType.LineTo:
926
+ return {
927
+ kind: part.kind,
928
+ point: mapping(part.point),
929
+ };
930
+ break;
931
+ case PathCommandType.CubicBezierTo:
932
+ return {
933
+ kind: part.kind,
934
+ controlPoint1: mapping(part.controlPoint1),
935
+ controlPoint2: mapping(part.controlPoint2),
936
+ endPoint: mapping(part.endPoint),
937
+ };
938
+ break;
939
+ case PathCommandType.QuadraticBezierTo:
940
+ return {
941
+ kind: part.kind,
942
+ controlPoint: mapping(part.controlPoint),
943
+ endPoint: mapping(part.endPoint),
944
+ };
945
+ break;
934
946
  }
935
947
 
936
948
  const exhaustivenessCheck: never = part;
937
949
  return exhaustivenessCheck;
938
950
  }
939
951
 
940
- public mapPoints(mapping: (point: Point2)=>Point2): Path {
952
+ public mapPoints(mapping: (point: Point2) => Point2): Path {
941
953
  const startPoint = mapping(this.startPoint);
942
954
  const newParts: PathCommand[] = [];
943
955
 
@@ -953,7 +965,7 @@ export class Path {
953
965
  return this;
954
966
  }
955
967
 
956
- return this.mapPoints(point => affineTransfm.transformVec2(point));
968
+ return this.mapPoints((point) => affineTransfm.transformVec2(point));
957
969
  }
958
970
 
959
971
  /**
@@ -974,7 +986,7 @@ export class Path {
974
986
 
975
987
  // Creates a new path by joining [other] to the end of this path
976
988
  public union(
977
- other: Path|PathCommand[]|null,
989
+ other: Path | PathCommand[] | null,
978
990
 
979
991
  // allowReverse: true iff reversing other or this is permitted if it means
980
992
  // no moveTo command is necessary when unioning the paths.
@@ -1024,36 +1036,35 @@ export class Path {
1024
1036
  let lastPoint: Point2 = this.startPoint;
1025
1037
  for (const part of this.parts) {
1026
1038
  switch (part.kind) {
1027
- case PathCommandType.LineTo:
1028
- case PathCommandType.MoveTo:
1029
- newParts.push({
1030
- kind: part.kind,
1031
- point: lastPoint,
1032
- });
1033
- lastPoint = part.point;
1034
- break;
1035
- case PathCommandType.CubicBezierTo:
1036
- newParts.push({
1037
- kind: part.kind,
1038
- controlPoint1: part.controlPoint2,
1039
- controlPoint2: part.controlPoint1,
1040
- endPoint: lastPoint,
1041
- });
1042
- lastPoint = part.endPoint;
1043
- break;
1044
- case PathCommandType.QuadraticBezierTo:
1045
- newParts.push({
1046
- kind: part.kind,
1047
- controlPoint: part.controlPoint,
1048
- endPoint: lastPoint,
1049
- });
1050
- lastPoint = part.endPoint;
1051
- break;
1052
- default:
1053
- {
1054
- const exhaustivenessCheck: never = part;
1055
- return exhaustivenessCheck;
1056
- }
1039
+ case PathCommandType.LineTo:
1040
+ case PathCommandType.MoveTo:
1041
+ newParts.push({
1042
+ kind: part.kind,
1043
+ point: lastPoint,
1044
+ });
1045
+ lastPoint = part.point;
1046
+ break;
1047
+ case PathCommandType.CubicBezierTo:
1048
+ newParts.push({
1049
+ kind: part.kind,
1050
+ controlPoint1: part.controlPoint2,
1051
+ controlPoint2: part.controlPoint1,
1052
+ endPoint: lastPoint,
1053
+ });
1054
+ lastPoint = part.endPoint;
1055
+ break;
1056
+ case PathCommandType.QuadraticBezierTo:
1057
+ newParts.push({
1058
+ kind: part.kind,
1059
+ controlPoint: part.controlPoint,
1060
+ endPoint: lastPoint,
1061
+ });
1062
+ lastPoint = part.endPoint;
1063
+ break;
1064
+ default: {
1065
+ const exhaustivenessCheck: never = part;
1066
+ return exhaustivenessCheck;
1067
+ }
1057
1068
  }
1058
1069
  }
1059
1070
  newParts.reverse();
@@ -1066,7 +1077,10 @@ export class Path {
1066
1077
  return this.startPoint;
1067
1078
  }
1068
1079
  const lastPart = this.parts[this.parts.length - 1];
1069
- if (lastPart.kind === PathCommandType.QuadraticBezierTo || lastPart.kind === PathCommandType.CubicBezierTo) {
1080
+ if (
1081
+ lastPart.kind === PathCommandType.QuadraticBezierTo ||
1082
+ lastPart.kind === PathCommandType.CubicBezierTo
1083
+ ) {
1070
1084
  return lastPart.endPoint;
1071
1085
  } else {
1072
1086
  return lastPart.point;
@@ -1138,7 +1152,7 @@ export class Path {
1138
1152
  let intersectionCount = 0;
1139
1153
  for (const line of polygon) {
1140
1154
  if (line.intersects(testLine)) {
1141
- intersectionCount ++;
1155
+ intersectionCount++;
1142
1156
  }
1143
1157
  }
1144
1158
 
@@ -1178,40 +1192,39 @@ export class Path {
1178
1192
  const part2 = other.parts[i];
1179
1193
 
1180
1194
  switch (part1.kind) {
1181
- case PathCommandType.LineTo:
1182
- case PathCommandType.MoveTo:
1183
- if (part1.kind !== part2.kind) {
1184
- return false;
1185
- } else if(!part1.point.eq(part2.point, tolerance)) {
1186
- return false;
1187
- }
1188
- break;
1189
- case PathCommandType.CubicBezierTo:
1190
- if (part1.kind !== part2.kind) {
1191
- return false;
1192
- } else if (
1193
- !part1.controlPoint1.eq(part2.controlPoint1, tolerance)
1194
- || !part1.controlPoint2.eq(part2.controlPoint2, tolerance)
1195
- || !part1.endPoint.eq(part2.endPoint, tolerance)
1196
- ) {
1197
- return false;
1198
- }
1199
- break;
1200
- case PathCommandType.QuadraticBezierTo:
1201
- if (part1.kind !== part2.kind) {
1202
- return false;
1203
- } else if (
1204
- !part1.controlPoint.eq(part2.controlPoint, tolerance)
1205
- || !part1.endPoint.eq(part2.endPoint, tolerance)
1206
- ) {
1207
- return false;
1195
+ case PathCommandType.LineTo:
1196
+ case PathCommandType.MoveTo:
1197
+ if (part1.kind !== part2.kind) {
1198
+ return false;
1199
+ } else if (!part1.point.eq(part2.point, tolerance)) {
1200
+ return false;
1201
+ }
1202
+ break;
1203
+ case PathCommandType.CubicBezierTo:
1204
+ if (part1.kind !== part2.kind) {
1205
+ return false;
1206
+ } else if (
1207
+ !part1.controlPoint1.eq(part2.controlPoint1, tolerance) ||
1208
+ !part1.controlPoint2.eq(part2.controlPoint2, tolerance) ||
1209
+ !part1.endPoint.eq(part2.endPoint, tolerance)
1210
+ ) {
1211
+ return false;
1212
+ }
1213
+ break;
1214
+ case PathCommandType.QuadraticBezierTo:
1215
+ if (part1.kind !== part2.kind) {
1216
+ return false;
1217
+ } else if (
1218
+ !part1.controlPoint.eq(part2.controlPoint, tolerance) ||
1219
+ !part1.endPoint.eq(part2.endPoint, tolerance)
1220
+ ) {
1221
+ return false;
1222
+ }
1223
+ break;
1224
+ default: {
1225
+ const exhaustivenessCheck: never = part1;
1226
+ return exhaustivenessCheck;
1208
1227
  }
1209
- break;
1210
- default:
1211
- {
1212
- const exhaustivenessCheck: never = part1;
1213
- return exhaustivenessCheck;
1214
- }
1215
1228
  }
1216
1229
  }
1217
1230
 
@@ -1225,7 +1238,7 @@ export class Path {
1225
1238
  * border around `rect`. Otherwise, the resultant path is just the border
1226
1239
  * of `rect`.
1227
1240
  */
1228
- public static fromRect(rect: Rect2, lineWidth: number|null = null): Path {
1241
+ public static fromRect(rect: Rect2, lineWidth: number | null = null): Path {
1229
1242
  const commands: PathCommand[] = [];
1230
1243
 
1231
1244
  let corners;
@@ -1237,18 +1250,14 @@ export class Path {
1237
1250
  const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
1238
1251
  const innerRect = Rect2.fromCorners(
1239
1252
  rect.topLeft.plus(cornerToEdge),
1240
- rect.bottomRight.minus(cornerToEdge)
1253
+ rect.bottomRight.minus(cornerToEdge),
1241
1254
  );
1242
1255
  const outerRect = Rect2.fromCorners(
1243
1256
  rect.topLeft.minus(cornerToEdge),
1244
- rect.bottomRight.plus(cornerToEdge)
1257
+ rect.bottomRight.plus(cornerToEdge),
1245
1258
  );
1246
1259
 
1247
- corners = [
1248
- innerRect.corners[3],
1249
- ...innerRect.corners,
1250
- ...outerRect.corners.reverse(),
1251
- ];
1260
+ corners = [innerRect.corners[3], ...innerRect.corners, ...outerRect.corners.reverse()];
1252
1261
  startPoint = outerRect.corners[3];
1253
1262
  } else {
1254
1263
  corners = rect.corners.slice(1);
@@ -1271,7 +1280,7 @@ export class Path {
1271
1280
  return new Path(startPoint, commands);
1272
1281
  }
1273
1282
 
1274
- private cachedStringVersion: string|null = null;
1283
+ private cachedStringVersion: string | null = null;
1275
1284
 
1276
1285
  /**
1277
1286
  * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
@@ -1302,10 +1311,14 @@ export class Path {
1302
1311
 
1303
1312
  // @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
1304
1313
  // conversions can lead to smaller output strings, but also take time.
1305
- public static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string {
1314
+ public static toString(
1315
+ startPoint: Point2,
1316
+ parts: PathCommand[],
1317
+ onlyAbsCommands?: boolean,
1318
+ ): string {
1306
1319
  const result: string[] = [];
1307
1320
 
1308
- let prevPoint: Point2|undefined;
1321
+ let prevPoint: Point2 | undefined;
1309
1322
  const addCommand = (command: string, ...points: Point2[]) => {
1310
1323
  const absoluteCommandParts: string[] = [];
1311
1324
  const relativeCommandParts: string[] = [];
@@ -1319,8 +1332,18 @@ export class Path {
1319
1332
 
1320
1333
  // Relative commands are often shorter as strings than absolute commands.
1321
1334
  if (!makeAbsCommand) {
1322
- const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint!.x, xComponent, roundedPrevX, roundedPrevY);
1323
- const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint!.y, yComponent, roundedPrevX, roundedPrevY);
1335
+ const xComponentRelative = toStringOfSamePrecision(
1336
+ point.x - prevPoint!.x,
1337
+ xComponent,
1338
+ roundedPrevX,
1339
+ roundedPrevY,
1340
+ );
1341
+ const yComponentRelative = toStringOfSamePrecision(
1342
+ point.y - prevPoint!.y,
1343
+ yComponent,
1344
+ roundedPrevX,
1345
+ roundedPrevY,
1346
+ );
1324
1347
 
1325
1348
  // No need for an additional separator if it starts with a '-'
1326
1349
  if (yComponentRelative.charAt(0) === '-') {
@@ -1363,21 +1386,21 @@ export class Path {
1363
1386
  const part = parts[i];
1364
1387
 
1365
1388
  switch (part.kind) {
1366
- case PathCommandType.MoveTo:
1367
- addCommand('M', part.point);
1368
- break;
1369
- case PathCommandType.LineTo:
1370
- addCommand('L', part.point);
1371
- break;
1372
- case PathCommandType.CubicBezierTo:
1373
- addCommand('C', part.controlPoint1, part.controlPoint2, part.endPoint);
1374
- break;
1375
- case PathCommandType.QuadraticBezierTo:
1376
- addCommand('Q', part.controlPoint, part.endPoint);
1377
- break;
1378
- default:
1379
- exhaustivenessCheck = part;
1380
- return exhaustivenessCheck;
1389
+ case PathCommandType.MoveTo:
1390
+ addCommand('M', part.point);
1391
+ break;
1392
+ case PathCommandType.LineTo:
1393
+ addCommand('L', part.point);
1394
+ break;
1395
+ case PathCommandType.CubicBezierTo:
1396
+ addCommand('C', part.controlPoint1, part.controlPoint2, part.endPoint);
1397
+ break;
1398
+ case PathCommandType.QuadraticBezierTo:
1399
+ addCommand('Q', part.controlPoint, part.endPoint);
1400
+ break;
1401
+ default:
1402
+ exhaustivenessCheck = part;
1403
+ return exhaustivenessCheck;
1381
1404
  }
1382
1405
  }
1383
1406
 
@@ -1410,12 +1433,11 @@ export class Path {
1410
1433
  pathString = pathString.split('\n').join(' ');
1411
1434
 
1412
1435
  let lastPos: Point2 = Vec2.zero;
1413
- let firstPos: Point2|null = null;
1414
- let startPos: Point2|null = null;
1436
+ let firstPos: Point2 | null = null;
1437
+ let startPos: Point2 | null = null;
1415
1438
  let isFirstCommand: boolean = true;
1416
1439
  const commands: PathCommand[] = [];
1417
1440
 
1418
-
1419
1441
  const moveTo = (point: Point2) => {
1420
1442
  // The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
1421
1443
  if (isFirstCommand) {
@@ -1455,36 +1477,38 @@ export class Path {
1455
1477
  });
1456
1478
  };
1457
1479
  const commandArgCounts: Record<string, number> = {
1458
- 'm': 1,
1459
- 'l': 1,
1460
- 'c': 3,
1461
- 'q': 2,
1462
- 'z': 0,
1463
- 'h': 1,
1464
- 'v': 1,
1480
+ m: 1,
1481
+ l: 1,
1482
+ c: 3,
1483
+ q: 2,
1484
+ z: 0,
1485
+ h: 1,
1486
+ v: 1,
1465
1487
  };
1466
1488
 
1467
1489
  // Each command: Command character followed by anything that isn't a command character
1468
- const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/ig;
1490
+ const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/gi;
1469
1491
  let current;
1470
1492
  while ((current = commandExp.exec(pathString)) !== null) {
1471
- const argParts = current[2].trim().split(/[^0-9Ee.-]/).filter(
1472
- part => part.length > 0
1473
- ).reduce((accumualtor: string[], current: string): string[] => {
1474
- // As of 09/2022, iOS Safari doesn't support support lookbehind in regular
1475
- // expressions. As such, we need an alternative.
1476
- // Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
1477
- // we need special cases:
1478
- current = current.replace(/([^eE])[-]/g, '$1 -');
1479
- const parts = current.split(' -');
1480
- if (parts[0] !== '') {
1481
- accumualtor.push(parts[0]);
1482
- }
1483
- accumualtor.push(...parts.slice(1).map(part => `-${part}`));
1484
- return accumualtor;
1485
- }, []);
1493
+ const argParts = current[2]
1494
+ .trim()
1495
+ .split(/[^0-9Ee.-]/)
1496
+ .filter((part) => part.length > 0)
1497
+ .reduce((accumualtor: string[], current: string): string[] => {
1498
+ // As of 09/2022, iOS Safari doesn't support support lookbehind in regular
1499
+ // expressions. As such, we need an alternative.
1500
+ // Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
1501
+ // we need special cases:
1502
+ current = current.replace(/([^eE])[-]/g, '$1 -');
1503
+ const parts = current.split(' -');
1504
+ if (parts[0] !== '') {
1505
+ accumualtor.push(parts[0]);
1506
+ }
1507
+ accumualtor.push(...parts.slice(1).map((part) => `-${part}`));
1508
+ return accumualtor;
1509
+ }, []);
1486
1510
 
1487
- let numericArgs = argParts.map(arg => parseFloat(arg));
1511
+ let numericArgs = argParts.map((arg) => parseFloat(arg));
1488
1512
 
1489
1513
  let commandChar = current[1].toLowerCase();
1490
1514
  let uppercaseCommand = current[1] !== commandChar;
@@ -1501,7 +1525,7 @@ export class Path {
1501
1525
  commandChar = 'l';
1502
1526
  } else if (commandChar === 'z') {
1503
1527
  if (firstPos) {
1504
- numericArgs = [ firstPos.x, firstPos.y ];
1528
+ numericArgs = [firstPos.x, firstPos.y];
1505
1529
  firstPos = lastPos;
1506
1530
  } else {
1507
1531
  continue;
@@ -1512,65 +1536,66 @@ export class Path {
1512
1536
  commandChar = 'l';
1513
1537
  }
1514
1538
 
1515
-
1516
1539
  const commandArgCount: number = commandArgCounts[commandChar] ?? 0;
1517
- const allArgs = numericArgs.reduce((
1518
- accumulator: Point2[], current, index, parts
1519
- ): Point2[] => {
1520
- if (index % 2 !== 0) {
1521
- const currentAsFloat = current;
1522
- const prevAsFloat = parts[index - 1];
1523
- return accumulator.concat(Vec2.of(prevAsFloat, currentAsFloat));
1524
- } else {
1525
- return accumulator;
1526
- }
1527
- }, []).map((coordinate, index): Point2 => {
1528
- // Lowercase commands are relative, uppercase commands use absolute
1529
- // positioning
1530
- let newPos;
1531
- if (uppercaseCommand) {
1532
- newPos = coordinate;
1533
- } else {
1534
- newPos = lastPos.plus(coordinate);
1535
- }
1540
+ const allArgs = numericArgs
1541
+ .reduce((accumulator: Point2[], current, index, parts): Point2[] => {
1542
+ if (index % 2 !== 0) {
1543
+ const currentAsFloat = current;
1544
+ const prevAsFloat = parts[index - 1];
1545
+ return accumulator.concat(Vec2.of(prevAsFloat, currentAsFloat));
1546
+ } else {
1547
+ return accumulator;
1548
+ }
1549
+ }, [])
1550
+ .map((coordinate, index): Point2 => {
1551
+ // Lowercase commands are relative, uppercase commands use absolute
1552
+ // positioning
1553
+ let newPos;
1554
+ if (uppercaseCommand) {
1555
+ newPos = coordinate;
1556
+ } else {
1557
+ newPos = lastPos.plus(coordinate);
1558
+ }
1536
1559
 
1537
- if ((index + 1) % commandArgCount === 0) {
1538
- lastPos = newPos;
1539
- }
1560
+ if ((index + 1) % commandArgCount === 0) {
1561
+ lastPos = newPos;
1562
+ }
1540
1563
 
1541
- return newPos;
1542
- });
1564
+ return newPos;
1565
+ });
1543
1566
 
1544
1567
  if (allArgs.length % commandArgCount !== 0) {
1545
- throw new Error([
1546
- `Incorrect number of arguments: got ${JSON.stringify(allArgs)} with a length of ${allArgs.length} ≠ ${commandArgCount}k, k ∈ ℤ.`,
1547
- `The number of arguments to ${commandChar} must be a multiple of ${commandArgCount}!`,
1548
- `Command: ${current[0]}`,
1549
- ].join('\n'));
1568
+ throw new Error(
1569
+ [
1570
+ `Incorrect number of arguments: got ${JSON.stringify(allArgs)} with a length of ${allArgs.length} ≠ ${commandArgCount}k, k ∈ ℤ.`,
1571
+ `The number of arguments to ${commandChar} must be a multiple of ${commandArgCount}!`,
1572
+ `Command: ${current[0]}`,
1573
+ ].join('\n'),
1574
+ );
1550
1575
  }
1551
1576
 
1552
1577
  for (let argPos = 0; argPos < allArgs.length; argPos += commandArgCount) {
1553
1578
  const args = allArgs.slice(argPos, argPos + commandArgCount);
1554
1579
 
1555
1580
  switch (commandChar.toLowerCase()) {
1556
- case 'm':
1557
- if (argPos === 0) {
1558
- moveTo(args[0]);
1559
- } else {
1581
+ case 'm':
1582
+ if (argPos === 0) {
1583
+ moveTo(args[0]);
1584
+ } else {
1585
+ lineTo(args[0]);
1586
+ }
1587
+ break;
1588
+ case 'l':
1560
1589
  lineTo(args[0]);
1561
- }
1562
- break;
1563
- case 'l':
1564
- lineTo(args[0]);
1565
- break;
1566
- case 'c':
1567
- cubicBezierTo(args[0], args[1], args[2]);
1568
- break;
1569
- case 'q':
1570
- quadraticBeierTo(args[0], args[1]);
1571
- break;
1572
- default:
1573
- throw new Error(`Unknown path command ${commandChar}`);
1590
+ break;
1591
+ case 'c':
1592
+ cubicBezierTo(args[0], args[1], args[2]);
1593
+ break;
1594
+ case 'q':
1595
+ quadraticBeierTo(args[0], args[1]);
1596
+ break;
1597
+ default:
1598
+ throw new Error(`Unknown path command ${commandChar}`);
1574
1599
  }
1575
1600
 
1576
1601
  isFirstCommand = false;
@@ -1595,10 +1620,12 @@ export class Path {
1595
1620
 
1596
1621
  const hull = convexHull2Of(points);
1597
1622
 
1598
- const commands = hull.slice(1).map((p): LinePathCommand => ({
1599
- kind: PathCommandType.LineTo,
1600
- point: p,
1601
- }));
1623
+ const commands = hull.slice(1).map(
1624
+ (p): LinePathCommand => ({
1625
+ kind: PathCommandType.LineTo,
1626
+ point: p,
1627
+ }),
1628
+ );
1602
1629
  // Close -- connect back to the start
1603
1630
  commands.push({
1604
1631
  kind: PathCommandType.LineTo,