modern-path2d 1.7.0 → 1.8.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/dist/index.cjs +316 -11
- package/dist/index.d.cts +122 -2
- package/dist/index.d.mts +122 -2
- package/dist/index.d.ts +122 -2
- package/dist/index.js +3 -2
- package/dist/index.mjs +313 -12
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const earcut = require('earcut');
|
|
4
|
+
const polygonClipping = require('polygon-clipping');
|
|
4
5
|
|
|
5
6
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
7
|
|
|
7
8
|
const earcut__default = /*#__PURE__*/_interopDefaultCompat(earcut);
|
|
9
|
+
const polygonClipping__default = /*#__PURE__*/_interopDefaultCompat(polygonClipping);
|
|
8
10
|
|
|
9
11
|
function drawPoint(ctx, x, y, options = {}) {
|
|
10
12
|
const { radius = 1 } = options;
|
|
@@ -303,6 +305,63 @@ class BoundingBox {
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
|
|
308
|
+
function flatRingToPairs(r) {
|
|
309
|
+
const ring = [];
|
|
310
|
+
for (let i = 0; i < r.length; i += 2) {
|
|
311
|
+
ring.push([r[i], r[i + 1]]);
|
|
312
|
+
}
|
|
313
|
+
const first = ring[0];
|
|
314
|
+
const last = ring[ring.length - 1];
|
|
315
|
+
if (first && last && (first[0] !== last[0] || first[1] !== last[1])) {
|
|
316
|
+
ring.push([first[0], first[1]]);
|
|
317
|
+
}
|
|
318
|
+
return ring;
|
|
319
|
+
}
|
|
320
|
+
function ringsToGeom(rings) {
|
|
321
|
+
const valid = rings.filter((r) => r.length >= 6).map(flatRingToPairs);
|
|
322
|
+
if (!valid.length) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
let geom = [[valid[0]]];
|
|
326
|
+
for (let i = 1; i < valid.length; i++) {
|
|
327
|
+
geom = polygonClipping__default.xor(geom, [[valid[i]]]);
|
|
328
|
+
}
|
|
329
|
+
return geom;
|
|
330
|
+
}
|
|
331
|
+
function geomToRings(geom) {
|
|
332
|
+
const out = [];
|
|
333
|
+
for (const poly of geom) {
|
|
334
|
+
for (const ring of poly) {
|
|
335
|
+
const flat = [];
|
|
336
|
+
for (const [x, y] of ring) {
|
|
337
|
+
flat.push(x, y);
|
|
338
|
+
}
|
|
339
|
+
out.push(flat);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
function polygonBoolean(op, ringsA, ringsB) {
|
|
345
|
+
const a = ringsToGeom(ringsA);
|
|
346
|
+
const b = ringsToGeom(ringsB);
|
|
347
|
+
let res;
|
|
348
|
+
switch (op) {
|
|
349
|
+
case "union":
|
|
350
|
+
res = polygonClipping__default.union(a, b);
|
|
351
|
+
break;
|
|
352
|
+
case "intersection":
|
|
353
|
+
res = polygonClipping__default.intersection(a, b);
|
|
354
|
+
break;
|
|
355
|
+
case "difference":
|
|
356
|
+
res = polygonClipping__default.difference(a, b);
|
|
357
|
+
break;
|
|
358
|
+
case "xor":
|
|
359
|
+
res = polygonClipping__default.xor(a, b);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
return geomToRings(res);
|
|
363
|
+
}
|
|
364
|
+
|
|
306
365
|
function catmullRom(t, p0, p1, p2, p3) {
|
|
307
366
|
const v0 = (p2 - p0) * 0.5;
|
|
308
367
|
const v1 = (p3 - p1) * 0.5;
|
|
@@ -867,22 +926,35 @@ function quadraticBezier(t, p0, p1, p2) {
|
|
|
867
926
|
return quadraticBezierP0(t, p0) + quadraticBezierP1(t, p1) + quadraticBezierP2(t, p2);
|
|
868
927
|
}
|
|
869
928
|
|
|
929
|
+
function resolveLineJoin(join) {
|
|
930
|
+
switch (join) {
|
|
931
|
+
case "round":
|
|
932
|
+
case "bevel":
|
|
933
|
+
case "miter":
|
|
934
|
+
return join;
|
|
935
|
+
default:
|
|
936
|
+
return "miter";
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function resolveLineStyle(style) {
|
|
940
|
+
return {
|
|
941
|
+
width: style?.strokeWidth ?? 1,
|
|
942
|
+
alignment: 0.5,
|
|
943
|
+
join: resolveLineJoin(style?.strokeLinejoin),
|
|
944
|
+
cap: style?.strokeLinecap ?? "butt",
|
|
945
|
+
miterLimit: style?.strokeMiterlimit ?? 10
|
|
946
|
+
};
|
|
947
|
+
}
|
|
870
948
|
const closePointEps = 1e-4;
|
|
871
949
|
const curveEps = 1e-4;
|
|
872
950
|
function strokeTriangulate(points, options = {}) {
|
|
873
951
|
const {
|
|
874
952
|
vertices = [],
|
|
875
953
|
indices = [],
|
|
876
|
-
lineStyle = {
|
|
877
|
-
alignment: 0.5,
|
|
878
|
-
cap: "butt",
|
|
879
|
-
join: "miter",
|
|
880
|
-
width: 1,
|
|
881
|
-
miterLimit: 10
|
|
882
|
-
},
|
|
883
954
|
flipAlignment = false,
|
|
884
955
|
closed = true
|
|
885
956
|
} = options;
|
|
957
|
+
const lineStyle = options.lineStyle ?? resolveLineStyle(options.style);
|
|
886
958
|
const eps = closePointEps;
|
|
887
959
|
if (points.length === 0) {
|
|
888
960
|
return { vertices, indices };
|
|
@@ -2710,6 +2782,22 @@ class Curve {
|
|
|
2710
2782
|
getControlPointRefs() {
|
|
2711
2783
|
return [];
|
|
2712
2784
|
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Reverse the traversal direction in place (start ↔ end, same geometry). The base
|
|
2787
|
+
* implementation reverses the order of the control-point *values*, which is correct for
|
|
2788
|
+
* line / Bézier / spline primitives whose {@link getControlPointRefs} order matches their
|
|
2789
|
+
* parametric order. {@link RoundCurve} (angle-based) and composites (child order) override it.
|
|
2790
|
+
*/
|
|
2791
|
+
reverse() {
|
|
2792
|
+
const refs = this.getControlPointRefs();
|
|
2793
|
+
const n = refs.length;
|
|
2794
|
+
const snapshot = refs.map((p) => p.clone());
|
|
2795
|
+
for (let i = 0; i < n; i++) {
|
|
2796
|
+
refs[i].copyFrom(snapshot[n - 1 - i]);
|
|
2797
|
+
}
|
|
2798
|
+
this.invalidate();
|
|
2799
|
+
return this;
|
|
2800
|
+
}
|
|
2713
2801
|
applyTransform(transform) {
|
|
2714
2802
|
const isFunction = typeof transform === "function";
|
|
2715
2803
|
this.getControlPointRefs().forEach((p) => {
|
|
@@ -2837,6 +2925,22 @@ class Curve {
|
|
|
2837
2925
|
getTangentAt(u, output) {
|
|
2838
2926
|
return this.getTangent(this.getUToTMapping(u), output);
|
|
2839
2927
|
}
|
|
2928
|
+
/**
|
|
2929
|
+
* PathKit-style sample at an absolute arc-length `distance` along the curve: the point, the unit
|
|
2930
|
+
* tangent, and the tangent `angle` in radians. `distance` is clamped to `[0, getLength()]`, so
|
|
2931
|
+
* passing `0`/`getLength()` always yields the endpoints. See {@link PathMeasure} for a wrapper.
|
|
2932
|
+
*/
|
|
2933
|
+
getPosTan(distance) {
|
|
2934
|
+
const length = this.getLength();
|
|
2935
|
+
const u = length > 0 ? Math.min(Math.max(distance / length, 0), 1) : 0;
|
|
2936
|
+
const t = this.getUToTMapping(u);
|
|
2937
|
+
const tangent = this.getTangent(t);
|
|
2938
|
+
return {
|
|
2939
|
+
position: this.getPoint(t),
|
|
2940
|
+
tangent,
|
|
2941
|
+
angle: Math.atan2(tangent.y, tangent.x)
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2840
2944
|
getNormal(t, output = new Vector2()) {
|
|
2841
2945
|
this.getTangent(t, output);
|
|
2842
2946
|
return output.set(-output.y, output.x).normalize();
|
|
@@ -2932,10 +3036,28 @@ class Curve {
|
|
|
2932
3036
|
options
|
|
2933
3037
|
);
|
|
2934
3038
|
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Whether this curve forms a closed loop (its outline should be stroked without end caps,
|
|
3041
|
+
* stitching the last vertex back to the first). The base test is purely geometric — the first
|
|
3042
|
+
* sampled vertex coincides with the last. Curves that close without a duplicated endpoint
|
|
3043
|
+
* (a full-revolution {@link RoundCurve}, rectangles, polygons) override this.
|
|
3044
|
+
*/
|
|
3045
|
+
isClosed() {
|
|
3046
|
+
const v = this._getCachedAdaptiveVertices();
|
|
3047
|
+
const len = v.length;
|
|
3048
|
+
if (len < 6) {
|
|
3049
|
+
return false;
|
|
3050
|
+
}
|
|
3051
|
+
const eps = 1e-4;
|
|
3052
|
+
return Math.abs(v[0] - v[len - 2]) < eps && Math.abs(v[1] - v[len - 1]) < eps;
|
|
3053
|
+
}
|
|
2935
3054
|
strokeTriangulate(options) {
|
|
2936
3055
|
return strokeTriangulate(
|
|
2937
3056
|
this.getAdaptiveVertices(),
|
|
2938
|
-
|
|
3057
|
+
{
|
|
3058
|
+
...options,
|
|
3059
|
+
closed: options?.closed ?? this.isClosed()
|
|
3060
|
+
}
|
|
2939
3061
|
);
|
|
2940
3062
|
}
|
|
2941
3063
|
toCommands() {
|
|
@@ -3030,6 +3152,22 @@ class RoundCurve extends Curve {
|
|
|
3030
3152
|
isClockwise() {
|
|
3031
3153
|
return this.clockwise;
|
|
3032
3154
|
}
|
|
3155
|
+
/**
|
|
3156
|
+
* A circle/ellipse arc is closed when it sweeps (at least) a full revolution — the sampled
|
|
3157
|
+
* outline does not duplicate the start vertex, so the geometric first==last test in the base
|
|
3158
|
+
* class would wrongly report a full circle as open and leave a seam gap in the stroke.
|
|
3159
|
+
*/
|
|
3160
|
+
isClosed() {
|
|
3161
|
+
return Math.abs(this.endAngle - this.startAngle) >= Math.PI * 2 - 1e-9 || super.isClosed();
|
|
3162
|
+
}
|
|
3163
|
+
reverse() {
|
|
3164
|
+
const { startAngle, endAngle } = this;
|
|
3165
|
+
this.startAngle = endAngle;
|
|
3166
|
+
this.endAngle = startAngle;
|
|
3167
|
+
this.clockwise = !this.clockwise;
|
|
3168
|
+
this.invalidate();
|
|
3169
|
+
return this;
|
|
3170
|
+
}
|
|
3033
3171
|
_getDeltaAngle() {
|
|
3034
3172
|
const PI_2 = Math.PI * 2;
|
|
3035
3173
|
let deltaAngle = this.endAngle - this.startAngle;
|
|
@@ -3286,9 +3424,25 @@ class RoundCurve extends Curve {
|
|
|
3286
3424
|
return output;
|
|
3287
3425
|
}
|
|
3288
3426
|
getAdaptiveVertices(output = []) {
|
|
3289
|
-
|
|
3427
|
+
const PI2 = Math.PI * 2;
|
|
3428
|
+
if (this.startAngle === 0 && this.endAngle === PI2) {
|
|
3290
3429
|
return this._getAdaptiveVerticesByCircle(output);
|
|
3291
3430
|
}
|
|
3431
|
+
if (Math.abs(this.endAngle - this.startAngle) >= PI2 - 1e-9) {
|
|
3432
|
+
const tmp = this._getAdaptiveVerticesByCircle([]);
|
|
3433
|
+
const n = tmp.length / 2;
|
|
3434
|
+
if (this.endAngle > this.startAngle) {
|
|
3435
|
+
for (let i = 0; i < tmp.length; i++) {
|
|
3436
|
+
output.push(tmp[i]);
|
|
3437
|
+
}
|
|
3438
|
+
} else {
|
|
3439
|
+
output.push(tmp[0], tmp[1]);
|
|
3440
|
+
for (let i = n - 1; i >= 1; i--) {
|
|
3441
|
+
output.push(tmp[i * 2], tmp[i * 2 + 1]);
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return output;
|
|
3445
|
+
}
|
|
3292
3446
|
return this._getAdaptiveVerticesByArc(output);
|
|
3293
3447
|
}
|
|
3294
3448
|
copyFrom(source) {
|
|
@@ -3498,6 +3652,15 @@ class LineCurve extends Curve {
|
|
|
3498
3652
|
getControlPointRefs() {
|
|
3499
3653
|
return [this.p1, this.p2];
|
|
3500
3654
|
}
|
|
3655
|
+
// Swap endpoint *references* (not values) so corner Vector2s shared with adjacent
|
|
3656
|
+
// segments stay intact and simply re-associate with the reversed segment.
|
|
3657
|
+
reverse() {
|
|
3658
|
+
const { p1, p2 } = this;
|
|
3659
|
+
this.p1 = p2;
|
|
3660
|
+
this.p2 = p1;
|
|
3661
|
+
this.invalidate();
|
|
3662
|
+
return this;
|
|
3663
|
+
}
|
|
3501
3664
|
getAdaptiveVertices(output = []) {
|
|
3502
3665
|
output.push(
|
|
3503
3666
|
this.p1.x,
|
|
@@ -3661,6 +3824,16 @@ class CompositeCurve extends Curve {
|
|
|
3661
3824
|
});
|
|
3662
3825
|
return output;
|
|
3663
3826
|
}
|
|
3827
|
+
/**
|
|
3828
|
+
* A composite is closed when its single child is closed (e.g. a lone full-circle arc), or when
|
|
3829
|
+
* its assembled outline returns to its start (rectangles, polygons, multi-segment loops).
|
|
3830
|
+
*/
|
|
3831
|
+
isClosed() {
|
|
3832
|
+
if (this.curves.length === 1) {
|
|
3833
|
+
return this.curves[0].isClosed();
|
|
3834
|
+
}
|
|
3835
|
+
return super.isClosed();
|
|
3836
|
+
}
|
|
3664
3837
|
strokeTriangulate(options) {
|
|
3665
3838
|
if (this.curves.length === 1) {
|
|
3666
3839
|
return this.curves[0].strokeTriangulate(options);
|
|
@@ -3668,6 +3841,13 @@ class CompositeCurve extends Curve {
|
|
|
3668
3841
|
return super.strokeTriangulate(options);
|
|
3669
3842
|
}
|
|
3670
3843
|
}
|
|
3844
|
+
/** Reverse the sub-curve order and reverse each sub-curve, so the whole outline runs backwards. */
|
|
3845
|
+
reverse() {
|
|
3846
|
+
this.curves.reverse();
|
|
3847
|
+
this.curves.forEach((curve) => curve.reverse());
|
|
3848
|
+
this.invalidate();
|
|
3849
|
+
return this;
|
|
3850
|
+
}
|
|
3671
3851
|
getFillVertices(options) {
|
|
3672
3852
|
if (this.curves.length === 1) {
|
|
3673
3853
|
return this.curves[0].getFillVertices(options);
|
|
@@ -3763,6 +3943,16 @@ class CubicBezierCurve extends Curve {
|
|
|
3763
3943
|
getControlPointRefs() {
|
|
3764
3944
|
return [this.p1, this.cp1, this.cp2, this.p2];
|
|
3765
3945
|
}
|
|
3946
|
+
// Swap endpoint and control-point references; keeps shared corner Vector2s intact.
|
|
3947
|
+
reverse() {
|
|
3948
|
+
const { p1, cp1, cp2, p2 } = this;
|
|
3949
|
+
this.p1 = p2;
|
|
3950
|
+
this.cp1 = cp2;
|
|
3951
|
+
this.cp2 = cp1;
|
|
3952
|
+
this.p2 = p1;
|
|
3953
|
+
this.invalidate();
|
|
3954
|
+
return this;
|
|
3955
|
+
}
|
|
3766
3956
|
_solveQuadratic(a, b, c) {
|
|
3767
3957
|
if (Math.abs(a) < 1e-12) {
|
|
3768
3958
|
if (Math.abs(b) < 1e-12)
|
|
@@ -3923,6 +4113,14 @@ class QuadraticBezierCurve extends Curve {
|
|
|
3923
4113
|
getControlPointRefs() {
|
|
3924
4114
|
return [this.p1, this.cp, this.p2];
|
|
3925
4115
|
}
|
|
4116
|
+
// Swap endpoint references (cp is symmetric); keeps shared corner Vector2s intact.
|
|
4117
|
+
reverse() {
|
|
4118
|
+
const { p1, p2 } = this;
|
|
4119
|
+
this.p1 = p2;
|
|
4120
|
+
this.p2 = p1;
|
|
4121
|
+
this.invalidate();
|
|
4122
|
+
return this;
|
|
4123
|
+
}
|
|
3926
4124
|
getAdaptiveVertices(output = []) {
|
|
3927
4125
|
return getAdaptiveQuadraticBezierCurvePoints(
|
|
3928
4126
|
this.p1.x,
|
|
@@ -4124,6 +4322,11 @@ class SplineCurve extends Curve {
|
|
|
4124
4322
|
getControlPointRefs() {
|
|
4125
4323
|
return this.points;
|
|
4126
4324
|
}
|
|
4325
|
+
reverse() {
|
|
4326
|
+
this.points.reverse();
|
|
4327
|
+
this.invalidate();
|
|
4328
|
+
return this;
|
|
4329
|
+
}
|
|
4127
4330
|
copyFrom(source) {
|
|
4128
4331
|
super.copyFrom(source);
|
|
4129
4332
|
this.points = [];
|
|
@@ -4160,6 +4363,22 @@ class CurvePath extends CompositeCurve {
|
|
|
4160
4363
|
this.addCommands(svgPathDataToCommands(data));
|
|
4161
4364
|
return this;
|
|
4162
4365
|
}
|
|
4366
|
+
/**
|
|
4367
|
+
* A sub-path is closed if it was explicitly closed (`autoClose`, i.e. a `Z`/`closePath`), or if
|
|
4368
|
+
* it forms a geometric loop / wraps a single closed primitive (handled by the base class).
|
|
4369
|
+
*/
|
|
4370
|
+
isClosed() {
|
|
4371
|
+
return this.autoClose || super.isClosed();
|
|
4372
|
+
}
|
|
4373
|
+
/** Reverse direction, then refresh the {@link startPoint}/{@link currentPoint} cursors. */
|
|
4374
|
+
reverse() {
|
|
4375
|
+
super.reverse();
|
|
4376
|
+
if (this.curves.length) {
|
|
4377
|
+
this.startPoint = this.getPoint(0);
|
|
4378
|
+
this.currentPoint = this.getPoint(1);
|
|
4379
|
+
}
|
|
4380
|
+
return this;
|
|
4381
|
+
}
|
|
4163
4382
|
_closeVertices(output) {
|
|
4164
4383
|
if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
|
|
4165
4384
|
output.push(output[0], output[1]);
|
|
@@ -4596,6 +4815,51 @@ class Path2D extends CompositeCurve {
|
|
|
4596
4815
|
const fillRule = options.fillRule ?? this.style.fillRule ?? "nonzero";
|
|
4597
4816
|
return pointInPolygons(point, this._getRings(), fillRule);
|
|
4598
4817
|
}
|
|
4818
|
+
/** Build a `Path2D` from flat rings (`[x0,y0,…]` per sub-path); closed-and-filled as sub-paths. */
|
|
4819
|
+
static fromRings(rings, style = {}) {
|
|
4820
|
+
const path = new Path2D(void 0, style);
|
|
4821
|
+
for (const ring of rings) {
|
|
4822
|
+
if (ring.length < 6) {
|
|
4823
|
+
continue;
|
|
4824
|
+
}
|
|
4825
|
+
let end = ring.length;
|
|
4826
|
+
if (ring[0] === ring[end - 2] && ring[1] === ring[end - 1]) {
|
|
4827
|
+
end -= 2;
|
|
4828
|
+
}
|
|
4829
|
+
path.moveTo(ring[0], ring[1]);
|
|
4830
|
+
for (let i = 2; i < end; i += 2) {
|
|
4831
|
+
path.lineTo(ring[i], ring[i + 1]);
|
|
4832
|
+
}
|
|
4833
|
+
path.closePath();
|
|
4834
|
+
}
|
|
4835
|
+
return path;
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* Boolean (path) operation against another path, returning a NEW `Path2D` whose outline is the
|
|
4839
|
+
* polygonal result. Curves are sampled before clipping, so the result is a polygonal
|
|
4840
|
+
* approximation (see {@link polygonBoolean}). The result inherits this path's `style` unless
|
|
4841
|
+
* overridden via `style`. Holes are emitted as oppositely-wound sub-paths (nonzero fill).
|
|
4842
|
+
*/
|
|
4843
|
+
booleanOp(op, other, style) {
|
|
4844
|
+
const rings = polygonBoolean(op, this._getRings(), other._getRings());
|
|
4845
|
+
return Path2D.fromRings(rings, { ...this.style, ...style });
|
|
4846
|
+
}
|
|
4847
|
+
/** `this ∪ other` — the combined filled area. */
|
|
4848
|
+
union(other, style) {
|
|
4849
|
+
return this.booleanOp("union", other, style);
|
|
4850
|
+
}
|
|
4851
|
+
/** `this ∩ other` — only the overlapping area. */
|
|
4852
|
+
intersection(other, style) {
|
|
4853
|
+
return this.booleanOp("intersection", other, style);
|
|
4854
|
+
}
|
|
4855
|
+
/** `this − other` — this path with `other` cut away. */
|
|
4856
|
+
difference(other, style) {
|
|
4857
|
+
return this.booleanOp("difference", other, style);
|
|
4858
|
+
}
|
|
4859
|
+
/** `this ⊕ other` — areas covered by exactly one of the two paths. */
|
|
4860
|
+
xor(other, style) {
|
|
4861
|
+
return this.booleanOp("xor", other, style);
|
|
4862
|
+
}
|
|
4599
4863
|
/**
|
|
4600
4864
|
* Test whether a point lies on this path's stroke. A hit on any sub-path counts.
|
|
4601
4865
|
*
|
|
@@ -4695,7 +4959,7 @@ class Path2D extends CompositeCurve {
|
|
|
4695
4959
|
}
|
|
4696
4960
|
drawTo(ctx, style = {}) {
|
|
4697
4961
|
style = { ...this.style, ...style };
|
|
4698
|
-
const { fill = "#000", stroke = "none" } = style;
|
|
4962
|
+
const { fill = "#000", stroke = "none", fillRule = "nonzero" } = style;
|
|
4699
4963
|
ctx.beginPath();
|
|
4700
4964
|
ctx.save();
|
|
4701
4965
|
setCanvasContext(ctx, style);
|
|
@@ -4703,7 +4967,7 @@ class Path2D extends CompositeCurve {
|
|
|
4703
4967
|
path.drawTo(ctx);
|
|
4704
4968
|
});
|
|
4705
4969
|
if (fill !== "none") {
|
|
4706
|
-
ctx.fill();
|
|
4970
|
+
ctx.fill(fillRule);
|
|
4707
4971
|
}
|
|
4708
4972
|
if (stroke !== "none") {
|
|
4709
4973
|
ctx.stroke();
|
|
@@ -4909,6 +5173,44 @@ ${content}
|
|
|
4909
5173
|
}
|
|
4910
5174
|
}
|
|
4911
5175
|
|
|
5176
|
+
class PathMeasure {
|
|
5177
|
+
constructor(curve) {
|
|
5178
|
+
this.curve = curve;
|
|
5179
|
+
}
|
|
5180
|
+
/** Total arc length of the path. */
|
|
5181
|
+
getLength() {
|
|
5182
|
+
return this.curve.getLength();
|
|
5183
|
+
}
|
|
5184
|
+
/** Whether the path forms a closed loop (see {@link Curve.isClosed}). */
|
|
5185
|
+
isClosed() {
|
|
5186
|
+
return this.curve.isClosed();
|
|
5187
|
+
}
|
|
5188
|
+
/** Point + unit tangent + tangent angle at an absolute arc-length `distance` (clamped). */
|
|
5189
|
+
getPosTan(distance) {
|
|
5190
|
+
return this.curve.getPosTan(distance);
|
|
5191
|
+
}
|
|
5192
|
+
/** Point at an absolute arc-length `distance` (clamped to `[0, getLength()]`). */
|
|
5193
|
+
getPosition(distance) {
|
|
5194
|
+
return this.curve.getPosTan(distance).position;
|
|
5195
|
+
}
|
|
5196
|
+
/** Point + tangent at a normalized progress `t ∈ [0, 1]` along the path. */
|
|
5197
|
+
getPosTanAtProgress(t) {
|
|
5198
|
+
return this.curve.getPosTan(this.getLength() * t);
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Evenly sample the path into `count + 1` {@link PosTan} entries (arc-length spaced), e.g. to
|
|
5202
|
+
* lay glyphs along a path or drive an `animate(progress)`-style traversal.
|
|
5203
|
+
*/
|
|
5204
|
+
sample(count = 100) {
|
|
5205
|
+
const length = this.getLength();
|
|
5206
|
+
const out = [];
|
|
5207
|
+
for (let i = 0; i <= count; i++) {
|
|
5208
|
+
out.push(this.curve.getPosTan(length * i / count));
|
|
5209
|
+
}
|
|
5210
|
+
return out;
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
|
|
4912
5214
|
class FFDControlGrid {
|
|
4913
5215
|
constructor(rows, cols, width = 1, height = 1) {
|
|
4914
5216
|
this.rows = rows;
|
|
@@ -4978,6 +5280,7 @@ exports.PI = PI;
|
|
|
4978
5280
|
exports.PI_2 = PI_2;
|
|
4979
5281
|
exports.Path2D = Path2D;
|
|
4980
5282
|
exports.Path2DSet = Path2DSet;
|
|
5283
|
+
exports.PathMeasure = PathMeasure;
|
|
4981
5284
|
exports.PolygonCurve = PolygonCurve;
|
|
4982
5285
|
exports.QuadraticBezierCurve = QuadraticBezierCurve;
|
|
4983
5286
|
exports.RectangleCurve = RectangleCurve;
|
|
@@ -5004,7 +5307,9 @@ exports.pointInPolygon = pointInPolygon;
|
|
|
5004
5307
|
exports.pointInPolygons = pointInPolygons;
|
|
5005
5308
|
exports.pointToPolylineDistance = pointToPolylineDistance;
|
|
5006
5309
|
exports.pointToSegmentDistance = pointToSegmentDistance;
|
|
5310
|
+
exports.polygonBoolean = polygonBoolean;
|
|
5007
5311
|
exports.quadraticBezier = quadraticBezier;
|
|
5312
|
+
exports.resolveLineStyle = resolveLineStyle;
|
|
5008
5313
|
exports.setCanvasContext = setCanvasContext;
|
|
5009
5314
|
exports.strokeTriangulate = strokeTriangulate;
|
|
5010
5315
|
exports.svgPathCommandsAddToPath2D = svgPathCommandsAddToPath2D;
|