svg-path-commander 2.1.1 → 2.1.2
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/README.md +2 -2
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +207 -19
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +1052 -969
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +7 -7
- package/src/convert/pathToAbsolute.ts +2 -31
- package/src/convert/pathToCurve.ts +13 -27
- package/src/convert/pathToRelative.ts +2 -47
- package/src/convert/pathToString.ts +40 -7
- package/src/index.ts +132 -39
- package/src/interface.ts +2 -1
- package/src/math/arcTools.ts +53 -51
- package/src/math/bezier.ts +41 -33
- package/src/math/cubicTools.ts +10 -8
- package/src/math/distanceSquareRoot.ts +1 -1
- package/src/math/lineTools.ts +6 -4
- package/src/math/polygonTools.ts +48 -0
- package/src/math/quadTools.ts +8 -6
- package/src/math/rotateVector.ts +3 -2
- package/src/math/roundTo.ts +7 -0
- package/src/parser/finalizeSegment.ts +11 -7
- package/src/parser/parsePathString.ts +2 -3
- package/src/parser/pathParser.ts +1 -1
- package/src/process/absolutizeSegment.ts +35 -30
- package/src/process/arcToCubic.ts +2 -2
- package/src/process/iterate.ts +41 -16
- package/src/process/lineToCubic.ts +1 -1
- package/src/process/normalizePath.ts +14 -31
- package/src/process/normalizeSegment.ts +53 -15
- package/src/process/optimizePath.ts +40 -60
- package/src/process/projection2d.ts +2 -2
- package/src/process/relativizeSegment.ts +33 -35
- package/src/process/reverseCurve.ts +8 -5
- package/src/process/reversePath.ts +87 -74
- package/src/process/roundPath.ts +14 -8
- package/src/process/roundSegment.ts +9 -0
- package/src/process/segmentToCubic.ts +9 -7
- package/src/process/shortenSegment.ts +24 -32
- package/src/process/splitCubic.ts +2 -2
- package/src/process/transformPath.ts +35 -40
- package/src/types.ts +7 -11
- package/src/util/getPathArea.ts +2 -2
- package/src/util/getPathBBox.ts +25 -42
- package/src/util/getPointAtLength.ts +51 -49
- package/src/util/getPropertiesAtLength.ts +4 -5
- package/src/util/getPropertiesAtPoint.ts +5 -5
- package/src/util/getTotalLength.ts +23 -38
- package/test/class.test.ts +2 -0
- package/test/fixtures/shapes.js +5 -5
- package/test/static.test.ts +18 -12
- package/src/math/polygonArea.ts +0 -29
- package/src/math/polygonLength.ts +0 -22
package/src/util/getPathArea.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pathToCurve from '../convert/pathToCurve';
|
|
2
|
-
import type { PointTuple, PathArray
|
|
2
|
+
import type { PointTuple, PathArray } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns the area of a single cubic-bezier segment.
|
|
@@ -60,7 +60,7 @@ const getPathArea = (path: PathArray) => {
|
|
|
60
60
|
[, x, y] = seg;
|
|
61
61
|
return 0;
|
|
62
62
|
default:
|
|
63
|
-
len = getCubicSegArea(x, y,
|
|
63
|
+
len = getCubicSegArea(x, y, seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
|
|
64
64
|
[x, y] = seg.slice(-2) as PointTuple;
|
|
65
65
|
return len;
|
|
66
66
|
}
|
package/src/util/getPathBBox.ts
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import iterate from '../process/iterate';
|
|
2
2
|
import { PathBBox } from '../interface';
|
|
3
|
-
import {
|
|
4
|
-
ArcCoordinates,
|
|
5
|
-
CubicCoordinates,
|
|
6
|
-
LineCoordinates,
|
|
7
|
-
MSegment,
|
|
8
|
-
PathArray,
|
|
9
|
-
Point,
|
|
10
|
-
PointTuple,
|
|
11
|
-
QuadCoordinates,
|
|
12
|
-
} from '../types';
|
|
13
|
-
// import pathFactory from './pathFactory';
|
|
14
|
-
import absolutizeSegment from '../process/absolutizeSegment';
|
|
15
|
-
import normalizeSegment from '../process/normalizeSegment';
|
|
16
|
-
import parsePathString from '../parser/parsePathString';
|
|
3
|
+
import { MSegment, PathArray, Point } from '../types';
|
|
17
4
|
import { getLineBBox } from '../math/lineTools';
|
|
18
5
|
import { getArcBBox } from '../math/arcTools';
|
|
19
6
|
import { getCubicBBox } from '../math/cubicTools';
|
|
20
7
|
import { getQuadBBox } from '../math/quadTools';
|
|
8
|
+
import parsePathString from '../parser/parsePathString';
|
|
9
|
+
import paramsParser from '../parser/paramsParser';
|
|
10
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
21
11
|
|
|
22
12
|
const getPathBBox = (pathInput: PathArray | string) => {
|
|
23
13
|
if (!pathInput) {
|
|
@@ -37,58 +27,51 @@ const getPathBBox = (pathInput: PathArray | string) => {
|
|
|
37
27
|
const path = parsePathString(pathInput);
|
|
38
28
|
let data = [] as number[];
|
|
39
29
|
let pathCommand = 'M';
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
const x = 0;
|
|
31
|
+
const y = 0;
|
|
42
32
|
let mx = 0;
|
|
43
33
|
let my = 0;
|
|
44
34
|
const MIN = [] as Point[];
|
|
45
35
|
const MAX = [] as Point[];
|
|
46
36
|
let min = { x, y };
|
|
47
37
|
let max = { x, y };
|
|
38
|
+
const params = { ...paramsParser };
|
|
48
39
|
|
|
49
|
-
iterate(path, (seg,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
iterate(path, (seg, _, lastX, lastY) => {
|
|
41
|
+
params.x = lastX;
|
|
42
|
+
params.y = lastY;
|
|
43
|
+
const result = normalizeSegment(seg, params);
|
|
44
|
+
[pathCommand] = result;
|
|
45
|
+
data = [lastX, lastY].concat(result.slice(1) as number[]);
|
|
54
46
|
|
|
55
47
|
// this segment is always ZERO
|
|
56
48
|
/* istanbul ignore else @preserve */
|
|
57
49
|
if (pathCommand === 'M') {
|
|
58
50
|
// remember mx, my for Z
|
|
59
|
-
[, mx, my] =
|
|
51
|
+
[, mx, my] = result as MSegment;
|
|
60
52
|
min = { x: mx, y: my };
|
|
61
53
|
max = { x: mx, y: my };
|
|
62
54
|
} else if (pathCommand === 'L') {
|
|
63
|
-
({ min, max } = getLineBBox(
|
|
55
|
+
({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
|
|
64
56
|
} else if (pathCommand === 'A') {
|
|
65
|
-
({ min, max } = getArcBBox(
|
|
57
|
+
({ min, max } = getArcBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]));
|
|
66
58
|
} else if (pathCommand === 'C') {
|
|
67
|
-
({ min, max } = getCubicBBox(
|
|
59
|
+
({ min, max } = getCubicBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]));
|
|
68
60
|
} else if (pathCommand === 'Q') {
|
|
69
|
-
({ min, max } = getQuadBBox(
|
|
61
|
+
({ min, max } = getQuadBBox(data[0], data[1], data[2], data[3], data[4], data[5]));
|
|
70
62
|
} else if (pathCommand === 'Z') {
|
|
71
|
-
data = [
|
|
72
|
-
({ min, max } = getLineBBox(
|
|
63
|
+
data = [lastX, lastY, mx, my];
|
|
64
|
+
({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
MIN.push(min);
|
|
76
68
|
MAX.push(max);
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (pathCommand === 'M') {
|
|
85
|
-
mx = x;
|
|
86
|
-
my = y;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
params.x = x;
|
|
90
|
-
params.y = y;
|
|
91
|
-
return normalSegment;
|
|
70
|
+
const seglen = result.length;
|
|
71
|
+
params.x1 = +result[seglen - 2];
|
|
72
|
+
params.y1 = +result[seglen - 1];
|
|
73
|
+
params.x2 = +result[seglen - 4] || params.x1;
|
|
74
|
+
params.y2 = +result[seglen - 3] || params.y1;
|
|
92
75
|
});
|
|
93
76
|
|
|
94
77
|
const xMin = Math.min(...MIN.map(n => n.x));
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import DISTANCE_EPSILON from './distanceEpsilon';
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
ArcCoordinates,
|
|
5
|
-
CubicCoordinates,
|
|
6
|
-
LineCoordinates,
|
|
7
|
-
MSegment,
|
|
8
|
-
PathArray,
|
|
9
|
-
PointTuple,
|
|
10
|
-
QuadCoordinates,
|
|
11
|
-
} from '../types';
|
|
2
|
+
import type { MSegment, PathArray, PointTuple } from '../types';
|
|
12
3
|
import iterate from '../process/iterate';
|
|
13
|
-
import absolutizeSegment from '../process/absolutizeSegment';
|
|
14
|
-
import normalizeSegment from '../process/normalizeSegment';
|
|
15
4
|
import { getLineLength, getPointAtLineLength } from '../math/lineTools';
|
|
16
5
|
import { getArcLength, getPointAtArcLength } from '../math/arcTools';
|
|
17
6
|
import { getCubicLength, getPointAtCubicLength } from '../math/cubicTools';
|
|
18
7
|
import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
|
|
8
|
+
import normalizePath from '../process/normalizePath';
|
|
19
9
|
|
|
20
10
|
/**
|
|
21
11
|
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
@@ -25,7 +15,7 @@ import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
|
|
|
25
15
|
* @returns the requested {x, y} point coordinates
|
|
26
16
|
*/
|
|
27
17
|
const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
|
|
28
|
-
const path =
|
|
18
|
+
const path = normalizePath(pathInput);
|
|
29
19
|
let isM = false;
|
|
30
20
|
let data = [] as number[];
|
|
31
21
|
let pathCommand = 'M';
|
|
@@ -38,18 +28,13 @@ const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
|
|
|
38
28
|
let POINT = point;
|
|
39
29
|
let totalLength = 0;
|
|
40
30
|
|
|
41
|
-
if (!distanceIsNumber) return point;
|
|
31
|
+
if (!distanceIsNumber || distance < DISTANCE_EPSILON) return point;
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
iterate(path, (seg, params) => {
|
|
48
|
-
const absoluteSegment = absolutizeSegment(seg, params);
|
|
49
|
-
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
50
|
-
[pathCommand] = normalSegment;
|
|
33
|
+
// for (let i = 0; i < pathLen; i += 1) {
|
|
34
|
+
iterate(path, (seg, _, lastX, lastY) => {
|
|
35
|
+
[pathCommand] = seg;
|
|
51
36
|
isM = pathCommand === 'M';
|
|
52
|
-
data = !isM ? [
|
|
37
|
+
data = !isM ? [lastX, lastY].concat(seg.slice(1) as number[]) : data;
|
|
53
38
|
|
|
54
39
|
// this segment is always ZERO
|
|
55
40
|
/* istanbul ignore else @preserve */
|
|
@@ -59,50 +44,67 @@ const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
|
|
|
59
44
|
point = { x: mx, y: my };
|
|
60
45
|
length = 0;
|
|
61
46
|
} else if (pathCommand === 'L') {
|
|
62
|
-
point = getPointAtLineLength(
|
|
63
|
-
length = getLineLength(
|
|
47
|
+
point = getPointAtLineLength(data[0], data[1], data[2], data[3], distance - totalLength);
|
|
48
|
+
length = getLineLength(data[0], data[1], data[2], data[3]);
|
|
64
49
|
} else if (pathCommand === 'A') {
|
|
65
|
-
point = getPointAtArcLength(
|
|
66
|
-
|
|
50
|
+
point = getPointAtArcLength(
|
|
51
|
+
data[0],
|
|
52
|
+
data[1],
|
|
53
|
+
data[2],
|
|
54
|
+
data[3],
|
|
55
|
+
data[4],
|
|
56
|
+
data[5],
|
|
57
|
+
data[6],
|
|
58
|
+
data[7],
|
|
59
|
+
data[8],
|
|
60
|
+
distance - totalLength,
|
|
61
|
+
);
|
|
62
|
+
length = getArcLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
|
|
67
63
|
} else if (pathCommand === 'C') {
|
|
68
|
-
point = getPointAtCubicLength(
|
|
69
|
-
|
|
64
|
+
point = getPointAtCubicLength(
|
|
65
|
+
data[0],
|
|
66
|
+
data[1],
|
|
67
|
+
data[2],
|
|
68
|
+
data[3],
|
|
69
|
+
data[4],
|
|
70
|
+
data[5],
|
|
71
|
+
data[6],
|
|
72
|
+
data[7],
|
|
73
|
+
distance - totalLength,
|
|
74
|
+
);
|
|
75
|
+
length = getCubicLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
|
70
76
|
} else if (pathCommand === 'Q') {
|
|
71
|
-
point = getPointAtQuadLength(
|
|
72
|
-
length = getQuadLength(
|
|
77
|
+
point = getPointAtQuadLength(data[0], data[1], data[2], data[3], data[4], data[5], distance - totalLength);
|
|
78
|
+
length = getQuadLength(data[0], data[1], data[2], data[3], data[4], data[5]);
|
|
73
79
|
} else if (pathCommand === 'Z') {
|
|
74
|
-
data = [
|
|
80
|
+
data = [lastX, lastY, mx, my];
|
|
75
81
|
point = { x: mx, y: my };
|
|
76
|
-
|
|
82
|
+
|
|
83
|
+
length = getLineLength(data[0], data[1], data[2], data[3]);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
[x, y] = data.slice(-2);
|
|
87
|
+
|
|
88
|
+
if (totalLength < distance) {
|
|
80
89
|
POINT = point;
|
|
90
|
+
} else {
|
|
91
|
+
// totalLength >= distance
|
|
92
|
+
// stop right here
|
|
93
|
+
// stop iterator now!
|
|
94
|
+
return false;
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
totalLength += length;
|
|
84
|
-
|
|
85
|
-
x = mx;
|
|
86
|
-
y = my;
|
|
87
|
-
} else {
|
|
88
|
-
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
89
|
-
|
|
90
|
-
if (pathCommand === 'M') {
|
|
91
|
-
mx = x;
|
|
92
|
-
my = y;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
params.x = x;
|
|
96
|
-
params.y = y;
|
|
97
|
-
return normalSegment;
|
|
98
|
+
return;
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
// native `getPointAtLength` behavior when the given distance
|
|
101
102
|
// is higher than total length
|
|
102
103
|
if (distance > totalLength - DISTANCE_EPSILON) {
|
|
103
|
-
|
|
104
|
+
return { x, y };
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
return POINT;
|
|
107
108
|
};
|
|
109
|
+
|
|
108
110
|
export default getPointAtLength;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PathArray, PathSegment } from '../types';
|
|
2
2
|
import type { SegmentProperties } from '../interface';
|
|
3
3
|
import parsePathString from '../parser/parsePathString';
|
|
4
4
|
import getTotalLength from './getTotalLength';
|
|
@@ -20,8 +20,6 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
20
20
|
let lengthAtSegment = 0;
|
|
21
21
|
let length = 0;
|
|
22
22
|
let segment = pathArray[0] as PathSegment;
|
|
23
|
-
const [x, y] = segment.slice(-2) as PointTuple;
|
|
24
|
-
const point = { x, y };
|
|
25
23
|
|
|
26
24
|
// If the path is empty, return 0.
|
|
27
25
|
if (index <= 0 || !distance || !Number.isFinite(distance)) {
|
|
@@ -29,7 +27,6 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
29
27
|
segment,
|
|
30
28
|
index: 0,
|
|
31
29
|
length,
|
|
32
|
-
point,
|
|
33
30
|
lengthAtSegment,
|
|
34
31
|
};
|
|
35
32
|
}
|
|
@@ -38,8 +35,9 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
38
35
|
pathTemp = pathArray.slice(0, -1) as PathArray;
|
|
39
36
|
lengthAtSegment = getTotalLength(pathTemp);
|
|
40
37
|
length = pathLength - lengthAtSegment;
|
|
38
|
+
segment = pathArray[index];
|
|
41
39
|
return {
|
|
42
|
-
segment
|
|
40
|
+
segment,
|
|
43
41
|
index,
|
|
44
42
|
length,
|
|
45
43
|
lengthAtSegment,
|
|
@@ -53,6 +51,7 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
53
51
|
lengthAtSegment = getTotalLength(pathTemp);
|
|
54
52
|
length = pathLength - lengthAtSegment;
|
|
55
53
|
pathLength = lengthAtSegment;
|
|
54
|
+
|
|
56
55
|
segments.push({
|
|
57
56
|
segment,
|
|
58
57
|
index,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PathArray } from '../types';
|
|
1
|
+
import type { PathArray, Point } from '../types';
|
|
2
2
|
import type { PointProperties } from '../interface';
|
|
3
3
|
import getPointAtLength from './getPointAtLength';
|
|
4
4
|
import getPropertiesAtLength from './getPropertiesAtLength';
|
|
@@ -16,17 +16,17 @@ import normalizePath from '../process/normalizePath';
|
|
|
16
16
|
* @param point the given point
|
|
17
17
|
* @returns the requested properties
|
|
18
18
|
*/
|
|
19
|
-
const getPropertiesAtPoint = (pathInput: string | PathArray, point:
|
|
19
|
+
const getPropertiesAtPoint = (pathInput: string | PathArray, point: Point): PointProperties => {
|
|
20
20
|
const path = parsePathString(pathInput);
|
|
21
21
|
const normalPath = normalizePath(path);
|
|
22
|
-
const pathLength = getTotalLength(
|
|
23
|
-
const distanceTo = (p:
|
|
22
|
+
const pathLength = getTotalLength(normalPath);
|
|
23
|
+
const distanceTo = (p: Point) => {
|
|
24
24
|
const dx = p.x - point.x;
|
|
25
25
|
const dy = p.y - point.y;
|
|
26
26
|
return dx * dx + dy * dy;
|
|
27
27
|
};
|
|
28
28
|
let precision = 8;
|
|
29
|
-
let scan:
|
|
29
|
+
let scan: Point;
|
|
30
30
|
let closest = { x: 0, y: 0 }; // make TS happy
|
|
31
31
|
let scanDistance = 0;
|
|
32
32
|
let bestLength = 0;
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
ArcCoordinates,
|
|
4
|
-
CubicCoordinates,
|
|
5
|
-
LineCoordinates,
|
|
6
|
-
MSegment,
|
|
7
|
-
PathArray,
|
|
8
|
-
PointTuple,
|
|
9
|
-
QuadCoordinates,
|
|
10
|
-
} from '../types';
|
|
1
|
+
import type { MSegment, PathArray } from '../types';
|
|
11
2
|
import { getLineLength } from '../math/lineTools';
|
|
12
3
|
import { getArcLength } from '../math/arcTools';
|
|
13
4
|
import { getCubicLength } from '../math/cubicTools';
|
|
14
5
|
import { getQuadLength } from '../math/quadTools';
|
|
15
6
|
import iterate from '../process/iterate';
|
|
16
|
-
import
|
|
7
|
+
// import normalizePath from '../process/normalizePath';
|
|
8
|
+
import parsePathString from '../parser/parsePathString';
|
|
9
|
+
import paramsParser from '../parser/paramsParser';
|
|
17
10
|
import normalizeSegment from '../process/normalizeSegment';
|
|
18
|
-
// import pathFactory from './pathFactory';
|
|
19
11
|
|
|
20
12
|
/**
|
|
21
13
|
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
@@ -28,53 +20,46 @@ import normalizeSegment from '../process/normalizeSegment';
|
|
|
28
20
|
*/
|
|
29
21
|
const getTotalLength = (pathInput: string | PathArray) => {
|
|
30
22
|
const path = parsePathString(pathInput);
|
|
23
|
+
const params = { ...paramsParser };
|
|
24
|
+
|
|
31
25
|
let isM = false;
|
|
32
26
|
let data = [] as number[];
|
|
33
27
|
let pathCommand = 'M';
|
|
34
|
-
let x = 0;
|
|
35
|
-
let y = 0;
|
|
36
28
|
let mx = 0;
|
|
37
29
|
let my = 0;
|
|
38
30
|
let totalLength = 0;
|
|
39
31
|
|
|
40
|
-
iterate(path, (seg,
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
iterate(path, (seg, _, lastX, lastY) => {
|
|
33
|
+
params.x = lastX;
|
|
34
|
+
params.y = lastY;
|
|
35
|
+
const normalSegment = normalizeSegment(seg, params);
|
|
43
36
|
[pathCommand] = normalSegment;
|
|
44
37
|
isM = pathCommand === 'M';
|
|
45
|
-
data = !isM ? [
|
|
38
|
+
data = !isM ? [lastX, lastY].concat(normalSegment.slice(1) as number[]) : data;
|
|
46
39
|
|
|
47
40
|
// this segment is always ZERO
|
|
48
41
|
/* istanbul ignore else @preserve */
|
|
49
42
|
if (isM) {
|
|
50
43
|
// remember mx, my for Z
|
|
51
|
-
[, mx, my] =
|
|
44
|
+
[, mx, my] = normalSegment as MSegment;
|
|
52
45
|
} else if (pathCommand === 'L') {
|
|
53
|
-
totalLength += getLineLength(
|
|
46
|
+
totalLength += getLineLength(data[0], data[1], data[2], data[3]);
|
|
54
47
|
} else if (pathCommand === 'A') {
|
|
55
|
-
totalLength += getArcLength(
|
|
48
|
+
totalLength += getArcLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
|
|
56
49
|
} else if (pathCommand === 'C') {
|
|
57
|
-
totalLength += getCubicLength(
|
|
50
|
+
totalLength += getCubicLength(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
|
58
51
|
} else if (pathCommand === 'Q') {
|
|
59
|
-
totalLength += getQuadLength(
|
|
52
|
+
totalLength += getQuadLength(data[0], data[1], data[2], data[3], data[4], data[5]);
|
|
60
53
|
} else if (pathCommand === 'Z') {
|
|
61
|
-
data = [
|
|
62
|
-
totalLength += getLineLength(
|
|
54
|
+
data = [lastX, lastY, mx, my];
|
|
55
|
+
totalLength += getLineLength(data[0], data[1], data[2], data[3]);
|
|
63
56
|
}
|
|
64
|
-
if (pathCommand === 'Z') {
|
|
65
|
-
x = mx;
|
|
66
|
-
y = my;
|
|
67
|
-
} else {
|
|
68
|
-
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
params.x = x;
|
|
76
|
-
params.y = y;
|
|
77
|
-
return normalSegment;
|
|
58
|
+
const seglen = normalSegment.length;
|
|
59
|
+
params.x1 = +normalSegment[seglen - 2];
|
|
60
|
+
params.y1 = +normalSegment[seglen - 1];
|
|
61
|
+
params.x2 = +normalSegment[seglen - 4] || params.x1;
|
|
62
|
+
params.y2 = +normalSegment[seglen - 3] || params.y1;
|
|
78
63
|
});
|
|
79
64
|
|
|
80
65
|
return totalLength;
|
package/test/class.test.ts
CHANGED
|
@@ -283,6 +283,8 @@ a1.63 1.63 0 0 0 -0.906 0.274a1.63 1.63 0 0 0 -0.601 0.73a1.63 1.63 0 0 0 -0.094
|
|
|
283
283
|
|
|
284
284
|
path.setAttribute('d', rect.toString());
|
|
285
285
|
expect(path.getAttribute('d')).to.equal('M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2z');
|
|
286
|
+
// add at least one round: 'off' test
|
|
287
|
+
expect(new SVGPathCommander(simpleShapes.normalized[2], { round: 'off' }).optimize().toString()).to.equal(simpleShapes.initial[2]);
|
|
286
288
|
});
|
|
287
289
|
|
|
288
290
|
it('Test reverse single path', async () => {
|
package/test/fixtures/shapes.js
CHANGED
|
@@ -20,12 +20,12 @@ const shapes = {
|
|
|
20
20
|
"M8.66 8.66L8.66 4.66L7.34 4.66L7.34 8.66L8.65 8.66ZM8.66 11.34L8.66 10L7.34 10L7.34 11.34L8.65 11.34ZM8 1.34Q10.75 1.34 12.7 3.29Q14.65 5.24 14.66 8Q14.67 10.76 12.71 12.7Q10.75 14.64 8 14.66Q5.25 14.68 3.3 12.7Q1.35 10.72 1.34 8Q1.33 5.28 3.3 3.3Q5.27 1.32 8 1.34Z"
|
|
21
21
|
],
|
|
22
22
|
optimized: [
|
|
23
|
-
"M16 8C16 4.13 12.42 1 8 1S0 4.13 0 8c0 1.76 0.74 3.37 1.97 4.6c-0.1 1.02 -0.42 2.13 -0.77 2.97c-0.08 0.
|
|
24
|
-
"M16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2zM2.56 12.33H1.85L3.75 7h0.
|
|
25
|
-
"M5.5 0.5A0.5 0.5 0 0 1 6 0h4a0.5 0.5 0 0 1 0 1H9v1.07a7 7 0 0 1 3.54 12.26l0.
|
|
26
|
-
"M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0zm3.35 4.
|
|
23
|
+
"M16 8C16 4.13 12.42 1 8 1S0 4.13 0 8c0 1.76 0.74 3.37 1.97 4.6c-0.1 1.02 -0.42 2.13 -0.77 2.97c-0.08 0.19 0.07 0.39 0.27 0.36c2.26 -0.37 3.6 -0.94 4.18 -1.23A9.06 9.06 0 0 0 8 15c4.42 0 8 -3.13 8 -7zM7.19 6.77c0.09 0.12 0.16 0.26 0.23 0.4c0.43 0.95 0.39 2.38 -0.94 3.71a0.45 0.45 0 0 1 -0.61 0.01A0.41 0.41 0 0 1 5.86 10.29C6.27 9.88 6.53 9.46 6.67 9.07C6.4 9.24 6.08 9.33 5.74 9.33C4.78 9.33 4 8.59 4 7.67S4.78 6 5.73 6c0.27 0 0.53 0.06 0.76 0.17l0.01 0c0.17 0.07 0.33 0.18 0.47 0.32c0.08 0.08 0.16 0.17 0.23 0.27zM11 9.07c-0.27 0.16 -0.59 0.26 -0.93 0.26C9.11 9.33 8.34 8.59 8.34 7.67S9.11 6 10.07 6c0.27 0 0.53 0.06 0.76 0.17l0.01 0C11 6.24 11.16 6.35 11.3 6.49c0.09 0.08 0.16 0.17 0.23 0.27c0.09 0.12 0.16 0.26 0.23 0.4c0.43 0.95 0.39 2.38 -0.94 3.71a0.45 0.45 0 0 1 -0.61 0.01a0.41 0.41 0 0 1 -0.01 -0.59C10.61 9.87 10.86 9.46 11 9.07z",
|
|
24
|
+
"M16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2zM2.56 12.33H1.85L3.75 7h0.7l1.9 5.33H5.62l-0.54 -1.6H3.1l-0.54 1.6zM4.1 7.81H4.08l-0.8 2.38H4.9L4.1 7.81zm5.75 0.42v4.11H9.18v-0.54H9.16C9.01 12.12 8.62 12.4 7.97 12.4c-0.85 0 -1.45 -0.48 -1.45 -1.43V8.23h0.68v2.55c0 0.77 0.44 1.01 0.98 1.01c0.59 0 1 -0.37 1 -1.02V8.23h0.68zm1.27 4.41c0.07 0.33 0.42 0.64 0.98 0.64c0.65 0 1.07 -0.38 1.07 -1.02v-0.61h-0.02C13 12 12.55 12.29 11.99 12.29c-0.96 0 -1.64 -0.67 -1.64 -1.9v-0.34c0 -1.21 0.68 -1.89 1.64 -1.89c0.56 0 1 0.29 1.2 0.64h0.02V8.23h0.65v4.03c0 1.05 -0.82 1.58 -1.75 1.58c-1.04 0 -1.57 -0.52 -1.67 -1.2h0.69zm2.06 -2.53c0 -0.83 -0.41 -1.36 -1.06 -1.36c-0.69 0 -1.1 0.49 -1.1 1.36v0.25c0 0.85 0.41 1.36 1.1 1.36c0.67 0 1.06 -0.52 1.06 -1.36V10.1zM4 0.5a0.5 0.5 0 0 0 -1 0V1H2A2 2 0 0 0 0 3v1h16V3A2 2 0 0 0 14 1h-1V0.5a0.5 0.5 0 0 0 -1 0V1H4V0.5z",
|
|
25
|
+
"M5.5 0.5A0.5 0.5 0 0 1 6 0h4a0.5 0.5 0 0 1 0 1H9v1.07a7 7 0 0 1 3.54 12.26l0.82 0.82a0.5 0.5 0 0 1 -0.71 0.71l-0.92 -0.93A6.97 6.97 0 0 1 8 16A6.97 6.97 0 0 1 4.28 14.93l-0.92 0.92A0.5 0.5 0 0 1 2.65 15.15l0.82 -0.82A7 7 0 0 1 7 2.07V1H6A0.5 0.5 0 0 1 5.5 0.5zM0.86 5.39A2.5 2.5 0 1 1 4.39 1.86A8.04 8.04 0 0 0 0.86 5.39zM13.5 1c-0.75 0 -1.43 0.33 -1.89 0.86a8.04 8.04 0 0 1 3.53 3.53A2.5 2.5 0 0 0 13.5 1zm-5 4a0.5 0.5 0 0 0 -1 0v3.88l-1.45 2.89a0.5 0.5 0 1 0 0.89 0.45l1.5 -3A0.5 0.5 0 0 0 8.5 9V5z",
|
|
26
|
+
"M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2H2A2 2 0 0 1 0 14V2A2 2 0 0 1 2 0zm3.35 4.65A0.5 0.5 0 1 0 4.65 5.35L7.29 8l-2.65 2.65a0.5 0.5 0 0 0 0.71 0.71L8 8.71l2.65 2.65a0.5 0.5 0 0 0 0.71 -0.71L8.71 8l2.65 -2.65A0.5 0.5 0 0 0 10.65 4.65L8 7.29L5.35 4.65z",
|
|
27
27
|
"M2 4A2 2 0 0 0 0 6v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V6A2 2 0 0 0 14 4h-1.17A2 2 0 0 1 11.41 3.41L10.59 2.59A2 2 0 0 0 9.17 2H6.83A2 2 0 0 0 5.41 2.59L4.59 3.41A2 2 0 0 1 3.17 4H2zm8.5 4.5a2.5 2.5 0 0 0 -5 0a2.5 2.5 0 1 0 5 0zM2.5 6a0.5 0.5 0 0 1 0 -1a0.5 0.5 0 1 1 0 1zm9 2.5a3.5 3.5 0 1 1 -7 0a3.5 3.5 0 0 1 7 0z",
|
|
28
|
-
"M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zm7.5 11h-4A0.5 0.5 0 0 1 5 10.5v-4a0.5 0.5 0 0 1 1 0v2.79l4.15 -4.
|
|
28
|
+
"M2 0A2 2 0 0 0 0 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2V2A2 2 0 0 0 14 0H2zm7.5 11h-4A0.5 0.5 0 0 1 5 10.5v-4a0.5 0.5 0 0 1 1 0v2.79l4.15 -4.15a0.5 0.5 0 0 1 0.71 0.71L6.71 10H9.5a0.5 0.5 0 0 1 0 1z",
|
|
29
29
|
"M15.98 8.51c-0.38 6.15 -7.27 9.57 -12.4 6.17C3.27 14.47 2.98 14.25 2.71 14.01l5.5 -5.5h7.78zM2 13.3L7.5 7.8V0.03C1.35 0.41 -2.07 7.3 1.33 12.43c0.2 0.3 0.42 0.59 0.67 0.87zM8.5 0.03c4.02 0.25 7.23 3.46 7.49 7.49H8.5V0.03z",
|
|
30
30
|
"M8.66 8.66v-4H7.34v4h1.31zm0 2.68V10H7.34v1.34h1.31zM8 1.34q2.75 0 4.7 1.95T14.66 8t-1.95 4.7T8 14.66T3.3 12.7T1.34 8T3.3 3.3T8 1.34z"
|
|
31
31
|
],
|
package/test/static.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, it, describe, beforeEach, vi } from 'vitest';
|
|
2
|
-
import SVGPathCommander, { type CurveArray, type ShapeTypes } from '~/index';
|
|
2
|
+
import SVGPathCommander, { PathArray, type CurveArray, type ShapeTypes } from '~/index';
|
|
3
3
|
import invalidPathValue from '../src/parser/invalidPathValue';
|
|
4
4
|
import error from '../src/parser/error';
|
|
5
5
|
|
|
@@ -115,11 +115,12 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
it(`Can revert back to default round option`, () => {
|
|
119
|
-
const sample = [["M", 0, 0], ["L", 181.99955, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]];
|
|
120
|
-
const rounded = [["M", 0, 0], ["L", 181.9996, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]];
|
|
118
|
+
it(`Can disable round, use a given decimal amount or revert back to default round option`, () => {
|
|
119
|
+
const sample = [["M", 0, 0], ["L", 181.99955, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]] as PathArray;
|
|
120
|
+
const rounded = [["M", 0, 0], ["L", 181.9996, 0], ["L", 91, 72], ["L", 0, 0], ["Z"]] as PathArray;
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
expect(SVGPathCommander.roundPath(sample, 4), `can use number setting`).to.deep.equal(rounded);
|
|
123
|
+
expect(SVGPathCommander.roundPath(sample, 'off'), `can use "off" setting`).to.deep.equal(sample);
|
|
123
124
|
expect(SVGPathCommander.roundPath(sample, -1), `use 4 decimals when negative number is provided`).to.deep.equal(rounded);
|
|
124
125
|
// @ts-expect-error
|
|
125
126
|
expect(SVGPathCommander.roundPath(sample, 'wombat'), `use 4 decimals when string is provided`).to.deep.equal(rounded);
|
|
@@ -198,7 +199,7 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
198
199
|
const propsPoint0 = getPropertiesAtPoint(simpleShapes.initial[1], { "x": 10, "y": 90 });
|
|
199
200
|
expect(propsPoint0.closest).to.deep.equal({ x: 10, y: 90 });
|
|
200
201
|
expect(propsPoint0.distance).to.equal(0);
|
|
201
|
-
expect(propsPoint0.segment).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0,
|
|
202
|
+
expect(propsPoint0.segment).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, lengthAtSegment: 0 })
|
|
202
203
|
|
|
203
204
|
// getPropertiesAtPoint mid point
|
|
204
205
|
const propsPoint50 = getPropertiesAtPoint(simpleShapes.initial[1], { x: 30.072453006153214, y: 41.42818552481854 });
|
|
@@ -224,7 +225,7 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
224
225
|
it(`Can do getSegmentOfPoint`, () => {
|
|
225
226
|
const { getSegmentOfPoint } = SVGPathCommander;
|
|
226
227
|
// first point
|
|
227
|
-
expect(getSegmentOfPoint(simpleShapes.initial[1], { x: 10, y: 90 })).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0,
|
|
228
|
+
expect(getSegmentOfPoint(simpleShapes.initial[1], { x: 10, y: 90 })).to.deep.equal({ segment: ["M", 10, 90], index: 0, length: 0, lengthAtSegment: 0 });
|
|
228
229
|
// mid point
|
|
229
230
|
expect(getSegmentOfPoint(simpleShapes.initial[3], { x: 9, y: 9 })).to.deep.equal({ segment: ["a", 6, 4, 10, 0, 1, 8, 0], index: 5, length: 7.498916687913066,/* point: { x: 6, y: 10 },*/ lengthAtSegment: 48.11479095890485 });
|
|
230
231
|
});
|
|
@@ -267,13 +268,13 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
267
268
|
});
|
|
268
269
|
|
|
269
270
|
it(`Can do polygonLength`, () => {
|
|
270
|
-
const {
|
|
271
|
-
expect(polygonLength([[100, 100], [150, 25], [150, 75], [200, 0]])).to.equal(230.27756377319946);
|
|
271
|
+
const { polygonTools } = SVGPathCommander;
|
|
272
|
+
expect(polygonTools.polygonLength([[100, 100], [150, 25], [150, 75], [200, 0]])).to.equal(230.27756377319946);
|
|
272
273
|
});
|
|
273
274
|
|
|
274
275
|
it(`Can do polygonArea`, () => {
|
|
275
|
-
const {
|
|
276
|
-
expect(polygonArea([[107.4, 13], [113.7, 28.8], [127.9, 31.3], [117.6, 43.5], [120.1, 60.8], [107.4, 52.6], [94.6, 60.8], [97.1, 43.5], [86.8, 31.3], [101, 28.8]])).to.equal(-836.69);
|
|
276
|
+
const { polygonTools } = SVGPathCommander;
|
|
277
|
+
expect(polygonTools.polygonArea([[107.4, 13], [113.7, 28.8], [127.9, 31.3], [117.6, 43.5], [120.1, 60.8], [107.4, 52.6], [94.6, 60.8], [97.1, 43.5], [86.8, 31.3], [101, 28.8]])).to.equal(-836.69);
|
|
277
278
|
});
|
|
278
279
|
|
|
279
280
|
it(`Can do transformPath with empty object`, () => {
|
|
@@ -304,7 +305,7 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
304
305
|
});
|
|
305
306
|
|
|
306
307
|
it(`Can cover all remaining branches`, () => {
|
|
307
|
-
const { splitPath, parsePathString, getPathBBox, getPointAtLength, getTotalLength } = SVGPathCommander;
|
|
308
|
+
const { splitPath, pathToString, optimizePath, parsePathString, getPathBBox, getPointAtLength, getTotalLength } = SVGPathCommander;
|
|
308
309
|
expect(getPointAtLength(simpleShapes.normalized[3], 24.057395479452424)).to.deep.equal({ x: 14, y: 10 });
|
|
309
310
|
expect(getPointAtLength(simpleShapes.normalized[0], 0)).to.deep.equal({ x: 10, y: 10 });
|
|
310
311
|
expect(getPointAtLength(simpleShapes.normalized[3], undefined)).to.deep.equal({ x: 6, y: 10 });
|
|
@@ -320,5 +321,10 @@ describe('SVGPathCommander Static Methods', () => {
|
|
|
320
321
|
"x": 10, "y": 10, "x2": 170,"y2": 90,
|
|
321
322
|
});
|
|
322
323
|
expect(splitPath(parsePathString(shapes.relative[1])).length).to.equal(7);
|
|
324
|
+
expect(pathToString(optimizePath(parsePathString(
|
|
325
|
+
// 'M10 50q15 -25 30 0Q55 75 70 50Q85 25 100 50T130 50Q145 25 160 50t30 0'
|
|
326
|
+
simpleShapes.normalized[2]
|
|
327
|
+
), 2))).to.equal(simpleShapes.initial[2]);
|
|
328
|
+
// M10 50q15 -25 30 0t30 0t30 0t30 0t30 0t30 0
|
|
323
329
|
});
|
|
324
330
|
});
|
package/src/math/polygonArea.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { type PointTuple } from '../types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* d3-polygon-area
|
|
5
|
-
* https://github.com/d3/d3-polygon
|
|
6
|
-
*
|
|
7
|
-
* Returns the area of a polygon.
|
|
8
|
-
*
|
|
9
|
-
* @param polygon an array of coordinates
|
|
10
|
-
* @returns the polygon area
|
|
11
|
-
*/
|
|
12
|
-
const polygonArea = (polygon: PointTuple[]): number => {
|
|
13
|
-
const n = polygon.length;
|
|
14
|
-
let i = -1;
|
|
15
|
-
let a;
|
|
16
|
-
let b = polygon[n - 1];
|
|
17
|
-
let area = 0;
|
|
18
|
-
|
|
19
|
-
/* eslint-disable-next-line */
|
|
20
|
-
while (++i < n) {
|
|
21
|
-
a = b;
|
|
22
|
-
b = polygon[i];
|
|
23
|
-
area += a[1] * b[0] - a[0] * b[1];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return area / 2;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export default polygonArea;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type PointTuple } from '../types';
|
|
2
|
-
import distanceSquareRoot from './distanceSquareRoot';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* d3-polygon-length
|
|
6
|
-
* https://github.com/d3/d3-polygon
|
|
7
|
-
*
|
|
8
|
-
* Returns the perimeter of a polygon.
|
|
9
|
-
*
|
|
10
|
-
* @param polygon an array of coordinates
|
|
11
|
-
* @returns {number} the polygon length
|
|
12
|
-
*/
|
|
13
|
-
const polygonLength = (polygon: PointTuple[]): number => {
|
|
14
|
-
return polygon.reduce((length, point, i) => {
|
|
15
|
-
if (i) {
|
|
16
|
-
return length + distanceSquareRoot(polygon[i - 1], point);
|
|
17
|
-
}
|
|
18
|
-
return 0;
|
|
19
|
-
}, 0);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default polygonLength;
|