svg-path-commander 2.2.0 → 2.2.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * SVGPathCommander v2.2.0 (http://thednp.github.io/svg-path-commander)
2
+ * SVGPathCommander v2.2.1 (http://thednp.github.io/svg-path-commander)
3
3
  * Copyright 2026 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
5
5
  */
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /*!
2
- * SVGPathCommander v2.2.0 (http://thednp.github.io/svg-path-commander)
2
+ * SVGPathCommander v2.2.1 (http://thednp.github.io/svg-path-commander)
3
3
  * Copyright 2026 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
5
5
  */
6
6
  import CSSMatrix from "@thednp/dommatrix";
7
7
  //#region package.json
8
- var version = "2.2.0";
8
+ var version = "2.2.1";
9
9
  //#endregion
10
10
  //#region src/math/midPoint.ts
11
11
  /**
@@ -3423,7 +3423,7 @@ const roundPath = (path, roundOption) => {
3423
3423
  //#region src/morph/fixPath.ts
3424
3424
  /**
3425
3425
  * Checks a `PathArray` for an unnecessary `Z` segment
3426
- * and returns a new `PathArray` without it.
3426
+ * and removes it. The `PathArray` is modified in place.
3427
3427
  * In short, if the segment before `Z` extends to `M`,
3428
3428
  * the `Z` segment must be removed.
3429
3429
  *
@@ -3893,18 +3893,7 @@ const equalizeSegments = (path1, path2, initialCfg = {}) => {
3893
3893
  return [equalP1, equalP2];
3894
3894
  };
3895
3895
  //#endregion
3896
- //#region src/util/pathIntersection.ts
3897
- const intersect = (x1, y1, x2, y2, x3, y3, x4, y4) => {
3898
- if (Math.max(x1, x2) < Math.min(x3, x4) || Math.min(x1, x2) > Math.max(x3, x4) || Math.max(y1, y2) < Math.min(y3, y4) || Math.min(y1, y2) > Math.max(y3, y4)) return;
3899
- const nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
3900
- if (!denominator) return;
3901
- const px = nx / denominator, py = ny / denominator, px2 = roundTo(px, 2), py2 = roundTo(py, 2);
3902
- if (px2 < roundTo(Math.min(x1, x2), 2) || px2 > roundTo(Math.max(x1, x2), 2) || px2 < roundTo(Math.min(x3, x4), 2) || px2 > roundTo(Math.max(x3, x4), 2) || py2 < roundTo(Math.min(y1, y2), 2) || py2 > roundTo(Math.max(y1, y2), 2) || py2 < roundTo(Math.min(y3, y4), 2) || py2 > roundTo(Math.max(y3, y4), 2)) return;
3903
- return {
3904
- x: px,
3905
- y: py
3906
- };
3907
- };
3896
+ //#region src/intersect/isPointInsideBBox.ts
3908
3897
  /**
3909
3898
  * Checks if a point is inside a bounding box.
3910
3899
  *
@@ -3916,6 +3905,8 @@ const isPointInsideBBox = (bbox, [x, y]) => {
3916
3905
  const [minX, minY, maxX, maxY] = bbox;
3917
3906
  return x >= minX && x <= maxX && y >= minY && y <= maxY;
3918
3907
  };
3908
+ //#endregion
3909
+ //#region src/intersect/boundingBoxIntersect.ts
3919
3910
  /**
3920
3911
  * Checks if two bounding boxes intersect.
3921
3912
  *
@@ -3928,154 +3919,6 @@ const boundingBoxIntersect = (a, b) => {
3928
3919
  const [bx1, by1, bx2, by2] = b;
3929
3920
  return isPointInsideBBox(b, [ax1, ay1]) || isPointInsideBBox(b, [ax2, ay1]) || isPointInsideBBox(b, [ax1, ay2]) || isPointInsideBBox(b, [ax2, ay2]) || isPointInsideBBox(a, [bx1, by1]) || isPointInsideBBox(a, [bx2, by1]) || isPointInsideBBox(a, [bx1, by2]) || isPointInsideBBox(a, [bx2, by2]) || (ax1 < bx2 && ax1 > bx1 || bx1 < ax2 && bx1 > ax1) && (ay1 < by2 && ay1 > by1 || by1 < ay2 && by1 > ay1);
3930
3921
  };
3931
- const interHelper = (bez1, bez2, config) => {
3932
- const bbox1 = getCubicBBox(...bez1);
3933
- const bbox2 = getCubicBBox(...bez2);
3934
- const { justCount, epsilon } = Object.assign({
3935
- justCount: true,
3936
- epsilon: DISTANCE_EPSILON
3937
- }, config);
3938
- if (!boundingBoxIntersect(bbox1, bbox2)) return justCount ? 0 : [];
3939
- const l1 = getCubicLength(...bez1), l2 = getCubicLength(...bez2), n1 = Math.max(l1 / 5 >> 0, 1), n2 = Math.max(l2 / 5 >> 0, 1), points1 = [], points2 = [], xy = {};
3940
- let res = justCount ? 0 : [];
3941
- for (let i = 0; i < n1 + 1; i++) {
3942
- const p = getPointAtCubicLength(...bez1, i / n1 * l1);
3943
- points1.push({
3944
- x: p.x,
3945
- y: p.y,
3946
- t: i / n1
3947
- });
3948
- }
3949
- for (let i = 0; i < n2 + 1; i++) {
3950
- const p = getPointAtCubicLength(...bez2, i / n2 * l2);
3951
- points2.push({
3952
- x: p.x,
3953
- y: p.y,
3954
- t: i / n2
3955
- });
3956
- }
3957
- for (let i = 0; i < n1; i++) for (let j = 0; j < n2; j++) {
3958
- const maxLimit = 1 + epsilon, di = points1[i], di1 = points1[i + 1], dj = points2[j], dj1 = points2[j + 1], ci = Math.abs(di1.x - di.x) < .001 ? "y" : "x", cj = Math.abs(dj1.x - dj.x) < .001 ? "y" : "x", is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
3959
- if (is) {
3960
- if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) continue;
3961
- xy[is.x.toFixed(4)] = is.y.toFixed(4);
3962
- const t1 = di.t + Math.abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), t2 = dj.t + Math.abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
3963
- if (t1 >= 0 && t1 <= maxLimit && t2 >= 0 && t2 <= maxLimit) if (justCount) res++;
3964
- else res.push({
3965
- x: is.x,
3966
- y: is.y,
3967
- t1: Math.min(t1, 1),
3968
- t2: Math.min(t2, 1)
3969
- });
3970
- }
3971
- }
3972
- return res;
3973
- };
3974
- /**
3975
- * Finds intersection points between two paths.
3976
- *
3977
- * @param pathInput1 - First path string or PathArray
3978
- * @param pathInput2 - Second path string or PathArray
3979
- * @param justCount - If true, returns the count of intersections; if false, returns the intersection points
3980
- * @returns The number of intersections (when justCount is true) or an array of IntersectionPoint objects
3981
- *
3982
- * @example
3983
- * ```ts
3984
- * pathsIntersection('M0 50C0 0,100 0,100 50', 'M50 0C100 0,100 100,50 100', true)
3985
- * // => 1
3986
- * pathsIntersection('M0 50C0 0,100 0,100 50', 'M50 0C100 0,100 100,50 100', false)
3987
- * // => [{ x: 50, y: 25, t1: 0.5, t2: 0.5 }]
3988
- * ```
3989
- */
3990
- const pathsIntersection = (pathInput1, pathInput2, justCount = true) => {
3991
- const path1 = pathToCurve(pathInput1);
3992
- const path2 = pathToCurve(pathInput2);
3993
- let x1 = 0, y1 = 0, x2 = 0, y2 = 0, x1m = 0, y1m = 0, x2m = 0, y2m = 0, bez1 = [
3994
- x1,
3995
- y1,
3996
- x1,
3997
- y1,
3998
- x1m,
3999
- y1m,
4000
- x1m,
4001
- y1m
4002
- ], bez2 = [
4003
- x2,
4004
- y2,
4005
- x2,
4006
- y2,
4007
- x2m,
4008
- y2m,
4009
- x2m,
4010
- y2m
4011
- ], countResult = 0;
4012
- const pointsResult = [];
4013
- const pathLen1 = path1.length;
4014
- const pathLen2 = path2.length;
4015
- for (let i = 0; i < pathLen1; i++) {
4016
- const seg1 = path1[i];
4017
- if (seg1[0] == "M") {
4018
- x1 = seg1[1];
4019
- y1 = seg1[2];
4020
- x1m = x1;
4021
- y1m = y1;
4022
- } else {
4023
- if (seg1[0] == "C") {
4024
- bez1 = [
4025
- x1,
4026
- y1,
4027
- seg1[1],
4028
- seg1[2],
4029
- seg1[3],
4030
- seg1[4],
4031
- seg1[5],
4032
- seg1[6]
4033
- ];
4034
- x1 = bez1[6];
4035
- y1 = bez1[7];
4036
- } else {
4037
- bez1 = [
4038
- x1,
4039
- y1,
4040
- x1,
4041
- y1,
4042
- x1m,
4043
- y1m,
4044
- x1m,
4045
- y1m
4046
- ];
4047
- x1 = x1m;
4048
- y1 = y1m;
4049
- }
4050
- for (let j = 0; j < pathLen2; j++) {
4051
- const seg2 = path2[j];
4052
- if (seg2[0] == "M") {
4053
- x2 = seg2[1];
4054
- y2 = seg2[2];
4055
- x2m = x2;
4056
- y2m = y2;
4057
- } else if (seg2[0] == "C") {
4058
- bez2 = [
4059
- x2,
4060
- y2,
4061
- seg2[1],
4062
- seg2[2],
4063
- seg2[3],
4064
- seg2[4],
4065
- seg2[5],
4066
- seg2[6]
4067
- ];
4068
- x2 = bez2[6];
4069
- y2 = bez2[7];
4070
- }
4071
- const intr = interHelper(bez1, bez2, { justCount });
4072
- if (justCount) countResult += intr;
4073
- else pointsResult.push(...intr);
4074
- }
4075
- }
4076
- }
4077
- return justCount ? countResult : pointsResult;
4078
- };
4079
3922
  //#endregion
4080
3923
  //#region src/morph/createPlaceholder.ts
4081
3924
  /**
@@ -4275,6 +4118,169 @@ const equalizePaths = (pathInput1, pathInput2, initialCfg = {}) => {
4275
4118
  return [equalizedPairs.map((p) => p[0]).flat(), equalizedPairs.map((p) => p[1]).flat()];
4276
4119
  };
4277
4120
  //#endregion
4121
+ //#region src/intersect/interHelper.ts
4122
+ const intersect = (x1, y1, x2, y2, x3, y3, x4, y4) => {
4123
+ if (Math.max(x1, x2) < Math.min(x3, x4) || Math.min(x1, x2) > Math.max(x3, x4) || Math.max(y1, y2) < Math.min(y3, y4) || Math.min(y1, y2) > Math.max(y3, y4)) return;
4124
+ const nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
4125
+ if (!denominator) return;
4126
+ const px = nx / denominator, py = ny / denominator, px2 = roundTo(px, 2), py2 = roundTo(py, 2);
4127
+ if (px2 < roundTo(Math.min(x1, x2), 2) || px2 > roundTo(Math.max(x1, x2), 2) || px2 < roundTo(Math.min(x3, x4), 2) || px2 > roundTo(Math.max(x3, x4), 2) || py2 < roundTo(Math.min(y1, y2), 2) || py2 > roundTo(Math.max(y1, y2), 2) || py2 < roundTo(Math.min(y3, y4), 2) || py2 > roundTo(Math.max(y3, y4), 2)) return;
4128
+ return {
4129
+ x: px,
4130
+ y: py
4131
+ };
4132
+ };
4133
+ const interHelper = (bez1, bez2, config) => {
4134
+ const bbox1 = getCubicBBox(...bez1);
4135
+ const bbox2 = getCubicBBox(...bez2);
4136
+ const { justCount, epsilon } = Object.assign({
4137
+ justCount: true,
4138
+ epsilon: DISTANCE_EPSILON
4139
+ }, config);
4140
+ if (!boundingBoxIntersect(bbox1, bbox2)) return justCount ? 0 : [];
4141
+ const l1 = getCubicLength(...bez1), l2 = getCubicLength(...bez2), n1 = Math.max(l1 / 5 >> 0, 1), n2 = Math.max(l2 / 5 >> 0, 1), points1 = [], points2 = [], xy = {};
4142
+ let res = justCount ? 0 : [];
4143
+ for (let i = 0; i < n1 + 1; i++) {
4144
+ const p = getPointAtCubicLength(...bez1, i / n1 * l1);
4145
+ points1.push({
4146
+ x: p.x,
4147
+ y: p.y,
4148
+ t: i / n1
4149
+ });
4150
+ }
4151
+ for (let i = 0; i < n2 + 1; i++) {
4152
+ const p = getPointAtCubicLength(...bez2, i / n2 * l2);
4153
+ points2.push({
4154
+ x: p.x,
4155
+ y: p.y,
4156
+ t: i / n2
4157
+ });
4158
+ }
4159
+ for (let i = 0; i < n1; i++) for (let j = 0; j < n2; j++) {
4160
+ const maxLimit = 1 + epsilon, di = points1[i], di1 = points1[i + 1], dj = points2[j], dj1 = points2[j + 1], ci = Math.abs(di1.x - di.x) < .001 ? "y" : "x", cj = Math.abs(dj1.x - dj.x) < .001 ? "y" : "x", is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
4161
+ if (is) {
4162
+ if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) continue;
4163
+ xy[is.x.toFixed(4)] = is.y.toFixed(4);
4164
+ const t1 = di.t + Math.abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), t2 = dj.t + Math.abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
4165
+ if (t1 >= 0 && t1 <= maxLimit && t2 >= 0 && t2 <= maxLimit) if (justCount) res++;
4166
+ else res.push({
4167
+ x: is.x,
4168
+ y: is.y,
4169
+ t1: Math.min(t1, 1),
4170
+ t2: Math.min(t2, 1)
4171
+ });
4172
+ }
4173
+ }
4174
+ return res;
4175
+ };
4176
+ //#endregion
4177
+ //#region src/intersect/pathIntersection.ts
4178
+ /**
4179
+ * Finds intersection points between two paths.
4180
+ *
4181
+ * @param pathInput1 - First path string or PathArray
4182
+ * @param pathInput2 - Second path string or PathArray
4183
+ * @param justCount - If true, returns the count of intersections; if false, returns the intersection points
4184
+ * @returns The number of intersections (when justCount is true) or an array of IntersectionPoint objects
4185
+ *
4186
+ * @example
4187
+ * ```ts
4188
+ * pathsIntersection('M0 50C0 0,100 0,100 50', 'M50 0C100 0,100 100,50 100', true)
4189
+ * // => 1
4190
+ * pathsIntersection('M0 50C0 0,100 0,100 50', 'M50 0C100 0,100 100,50 100', false)
4191
+ * // => [{ x: 50, y: 25, t1: 0.5, t2: 0.5 }]
4192
+ * ```
4193
+ */
4194
+ const pathsIntersection = (pathInput1, pathInput2, justCount = true) => {
4195
+ const path1 = pathToCurve(pathInput1);
4196
+ const path2 = pathToCurve(pathInput2);
4197
+ let x1 = 0, y1 = 0, x2 = 0, y2 = 0, x1m = 0, y1m = 0, x2m = 0, y2m = 0, bez1 = [
4198
+ x1,
4199
+ y1,
4200
+ x1,
4201
+ y1,
4202
+ x1m,
4203
+ y1m,
4204
+ x1m,
4205
+ y1m
4206
+ ], bez2 = [
4207
+ x2,
4208
+ y2,
4209
+ x2,
4210
+ y2,
4211
+ x2m,
4212
+ y2m,
4213
+ x2m,
4214
+ y2m
4215
+ ], countResult = 0;
4216
+ const pointsResult = [];
4217
+ const pathLen1 = path1.length;
4218
+ const pathLen2 = path2.length;
4219
+ for (let i = 0; i < pathLen1; i++) {
4220
+ const seg1 = path1[i];
4221
+ if (seg1[0] == "M") {
4222
+ x1 = seg1[1];
4223
+ y1 = seg1[2];
4224
+ x1m = x1;
4225
+ y1m = y1;
4226
+ } else {
4227
+ if (seg1[0] == "C") {
4228
+ bez1 = [
4229
+ x1,
4230
+ y1,
4231
+ seg1[1],
4232
+ seg1[2],
4233
+ seg1[3],
4234
+ seg1[4],
4235
+ seg1[5],
4236
+ seg1[6]
4237
+ ];
4238
+ x1 = bez1[6];
4239
+ y1 = bez1[7];
4240
+ } else {
4241
+ bez1 = [
4242
+ x1,
4243
+ y1,
4244
+ x1,
4245
+ y1,
4246
+ x1m,
4247
+ y1m,
4248
+ x1m,
4249
+ y1m
4250
+ ];
4251
+ x1 = x1m;
4252
+ y1 = y1m;
4253
+ }
4254
+ for (let j = 0; j < pathLen2; j++) {
4255
+ const seg2 = path2[j];
4256
+ if (seg2[0] == "M") {
4257
+ x2 = seg2[1];
4258
+ y2 = seg2[2];
4259
+ x2m = x2;
4260
+ y2m = y2;
4261
+ } else if (seg2[0] == "C") {
4262
+ bez2 = [
4263
+ x2,
4264
+ y2,
4265
+ seg2[1],
4266
+ seg2[2],
4267
+ seg2[3],
4268
+ seg2[4],
4269
+ seg2[5],
4270
+ seg2[6]
4271
+ ];
4272
+ x2 = bez2[6];
4273
+ y2 = bez2[7];
4274
+ }
4275
+ const intr = interHelper(bez1, bez2, { justCount });
4276
+ if (justCount) countResult += intr;
4277
+ else pointsResult.push(...intr);
4278
+ }
4279
+ }
4280
+ }
4281
+ return justCount ? countResult : pointsResult;
4282
+ };
4283
+ //#endregion
4278
4284
  //#region src/main.ts
4279
4285
  /**
4280
4286
  * Creates a new SVGPathCommander instance with the following properties: