@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.
- package/build-config.json +1 -1
- package/dist/cjs/Color4.d.ts +24 -1
- package/dist/cjs/Color4.js +35 -3
- package/dist/cjs/Mat33.d.ts +21 -11
- package/dist/cjs/Mat33.js +28 -24
- package/dist/cjs/Vec3.d.ts +12 -3
- package/dist/cjs/Vec3.js +20 -9
- package/dist/cjs/lib.d.ts +3 -0
- package/dist/cjs/lib.js +3 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +2 -0
- package/dist/cjs/shapes/BezierJSWrapper.js +22 -13
- package/dist/cjs/shapes/LineSegment2.js +13 -17
- package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
- package/dist/cjs/shapes/Path.d.ts +1 -0
- package/dist/cjs/shapes/Path.js +50 -47
- package/dist/cjs/shapes/QuadraticBezier.d.ts +19 -2
- package/dist/cjs/shapes/QuadraticBezier.js +26 -3
- package/dist/cjs/shapes/Rect2.d.ts +13 -0
- package/dist/cjs/shapes/Rect2.js +35 -16
- package/dist/cjs/shapes/Triangle.js +4 -5
- package/dist/cjs/utils/convexHull2Of.js +3 -3
- package/dist/mjs/Color4.d.ts +24 -1
- package/dist/mjs/Color4.mjs +35 -3
- package/dist/mjs/Mat33.d.ts +21 -11
- package/dist/mjs/Mat33.mjs +28 -24
- package/dist/mjs/Vec3.d.ts +12 -3
- package/dist/mjs/Vec3.mjs +20 -9
- package/dist/mjs/lib.d.ts +3 -0
- package/dist/mjs/lib.mjs +3 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +2 -0
- package/dist/mjs/shapes/BezierJSWrapper.mjs +22 -13
- package/dist/mjs/shapes/LineSegment2.mjs +13 -17
- package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
- package/dist/mjs/shapes/Path.d.ts +1 -0
- package/dist/mjs/shapes/Path.mjs +50 -47
- package/dist/mjs/shapes/QuadraticBezier.d.ts +19 -2
- package/dist/mjs/shapes/QuadraticBezier.mjs +26 -3
- package/dist/mjs/shapes/Rect2.d.ts +13 -0
- package/dist/mjs/shapes/Rect2.mjs +35 -16
- 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 +21 -21
- package/src/Color4.ts +61 -18
- package/src/Mat33.fromCSSMatrix.test.ts +32 -46
- package/src/Mat33.test.ts +64 -102
- package/src/Mat33.ts +81 -104
- package/src/Vec2.test.ts +3 -3
- package/src/Vec3.test.ts +2 -3
- package/src/Vec3.ts +46 -61
- package/src/lib.ts +3 -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 +56 -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 +379 -352
- package/src/shapes/PointShape2D.ts +3 -3
- package/src/shapes/QuadraticBezier.test.ts +27 -21
- package/src/shapes/QuadraticBezier.ts +26 -11
- package/src/shapes/Rect2.test.ts +44 -75
- package/src/shapes/Rect2.ts +47 -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
@@ -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 =
|
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
|
-
|
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;
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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,
|
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 [
|
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, [
|
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(
|
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
|
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(
|
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
|
-
|
733
|
-
|
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
|
-
|
743
|
-
|
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
|
-
|
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:
|
788
|
+
point: part.point,
|
789
789
|
});
|
790
|
-
newPathStart =
|
791
|
-
|
792
|
-
|
793
|
-
|
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
|
-
|
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({
|
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
|
-
|
824
|
-
|
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
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
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(
|
919
|
+
private static mapPathCommand(
|
920
|
+
part: PathCommand,
|
921
|
+
mapping: (point: Point2) => Point2,
|
922
|
+
): PathCommand {
|
911
923
|
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
|
-
|
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
|
-
|
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
|
-
}
|
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 (
|
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
|
-
|
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
|
-
|
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(
|
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(
|
1323
|
-
|
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
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
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
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
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]*)/
|
1490
|
+
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/gi;
|
1469
1491
|
let current;
|
1470
1492
|
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
|
-
|
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 = [
|
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
|
1518
|
-
accumulator: Point2[], current, index, parts
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
}
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
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
|
-
|
1538
|
-
|
1539
|
-
|
1560
|
+
if ((index + 1) % commandArgCount === 0) {
|
1561
|
+
lastPos = newPos;
|
1562
|
+
}
|
1540
1563
|
|
1541
|
-
|
1542
|
-
|
1564
|
+
return newPos;
|
1565
|
+
});
|
1543
1566
|
|
1544
1567
|
if (allArgs.length % commandArgCount !== 0) {
|
1545
|
-
throw new Error(
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
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
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
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
|
-
|
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}`);
|
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(
|
1599
|
-
|
1600
|
-
|
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,
|