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