svg-path-commander 2.1.0 → 2.1.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/README.md +61 -5
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +119 -33
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +798 -828
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +3 -3
- package/src/convert/pathToAbsolute.ts +16 -70
- package/src/convert/pathToCurve.ts +36 -28
- package/src/convert/pathToRelative.ts +33 -62
- package/src/index.ts +13 -19
- package/src/interface.ts +1 -1
- package/src/math/arcTools.ts +248 -71
- package/src/math/bezier.ts +19 -27
- package/src/math/cubicTools.ts +67 -26
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +41 -26
- package/src/math/midPoint.ts +3 -1
- package/src/math/polygonArea.ts +3 -1
- package/src/math/polygonLength.ts +2 -1
- package/src/math/quadTools.ts +45 -26
- package/src/parser/parsePathString.ts +4 -4
- package/src/process/absolutizeSegment.ts +58 -0
- package/src/process/iterate.ts +33 -0
- package/src/process/normalizePath.ts +34 -28
- package/src/process/normalizeSegment.ts +8 -9
- package/src/process/projection2d.ts +2 -1
- package/src/process/relativizeSegment.ts +61 -0
- package/src/process/reversePath.ts +1 -1
- package/src/process/roundPath.ts +8 -10
- package/src/process/segmentToCubic.ts +1 -1
- package/src/process/shortenSegment.ts +3 -3
- package/src/process/splitCubic.ts +8 -7
- package/src/process/splitPath.ts +38 -4
- package/src/process/transformPath.ts +80 -73
- package/src/types.ts +35 -1
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +86 -19
- package/src/util/getPointAtLength.ts +98 -4
- package/src/util/getPropertiesAtLength.ts +2 -2
- package/src/util/getTotalLength.ts +71 -4
- package/test/class.test.ts +15 -14
- package/test/fixtures/shapes.js +27 -27
- package/test/fixtures/simpleShapes.js +18 -18
- package/test/static.test.ts +37 -17
- package/cypress.config.ts +0 -29
- package/src/process/fixArc.ts +0 -23
- package/src/process/replaceArc.ts +0 -52
- package/src/util/pathFactory.ts +0 -130
package/src/math/lineTools.ts
CHANGED
|
@@ -2,51 +2,66 @@ import midPoint from './midPoint';
|
|
|
2
2
|
import distanceSquareRoot from './distanceSquareRoot';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Returns
|
|
5
|
+
* Returns length for line segments (MoveTo, LineTo).
|
|
6
6
|
*
|
|
7
7
|
* @param x1 the starting point X
|
|
8
8
|
* @param y1 the starting point Y
|
|
9
9
|
* @param x2 the ending point X
|
|
10
10
|
* @param y2 the ending point Y
|
|
11
|
-
* @
|
|
12
|
-
* @returns the segment length, point at length and the bounding box
|
|
11
|
+
* @returns the line segment length
|
|
13
12
|
*/
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
export const getLineLength = (x1: number, y1: number, x2: number, y2: number) => {
|
|
14
|
+
return distanceSquareRoot([x1, y1], [x2, y2]);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns a point along the line segments (MoveTo, LineTo).
|
|
19
|
+
*
|
|
20
|
+
* @param x1 the starting point X
|
|
21
|
+
* @param y1 the starting point Y
|
|
22
|
+
* @param x2 the ending point X
|
|
23
|
+
* @param y2 the ending point Y
|
|
24
|
+
* @param distance the distance to point in [0-1] range
|
|
25
|
+
* @returns the point at length
|
|
26
|
+
*/
|
|
27
|
+
export const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
|
|
28
|
+
const length = distanceSquareRoot([x1, y1], [x2, y2]);
|
|
29
|
+
let point = { x: x1, y: y1 };
|
|
18
30
|
|
|
19
31
|
/* istanbul ignore else @preserve */
|
|
20
32
|
if (typeof distance === 'number') {
|
|
21
|
-
const currentLength = length();
|
|
22
33
|
if (distance <= 0) {
|
|
23
34
|
point = { x: x1, y: y1 };
|
|
24
|
-
} else if (distance >=
|
|
35
|
+
} else if (distance >= length) {
|
|
25
36
|
point = { x: x2, y: y2 };
|
|
26
37
|
} else {
|
|
27
|
-
const [x, y] = midPoint([x1, y1], [x2, y2], distance /
|
|
38
|
+
const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
|
|
28
39
|
point = { x, y };
|
|
29
40
|
}
|
|
30
41
|
}
|
|
42
|
+
return point;
|
|
43
|
+
};
|
|
31
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Returns bounding box for line segments (MoveTo, LineTo).
|
|
47
|
+
*
|
|
48
|
+
* @param x1 the starting point X
|
|
49
|
+
* @param y1 the starting point Y
|
|
50
|
+
* @param x2 the ending point X
|
|
51
|
+
* @param y2 the ending point Y
|
|
52
|
+
* @param distance the distance to point in [0-1] range
|
|
53
|
+
* @returns the extrema for line segments
|
|
54
|
+
*/
|
|
55
|
+
export const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
|
|
56
|
+
const { min, max } = Math;
|
|
32
57
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
min: {
|
|
59
|
+
x: min(x1, x2),
|
|
60
|
+
y: min(y1, y2),
|
|
36
61
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
x: min(x1, x2),
|
|
41
|
-
y: min(y1, y2),
|
|
42
|
-
},
|
|
43
|
-
max: {
|
|
44
|
-
x: max(x1, x2),
|
|
45
|
-
y: max(y1, y2),
|
|
46
|
-
},
|
|
47
|
-
};
|
|
62
|
+
max: {
|
|
63
|
+
x: max(x1, x2),
|
|
64
|
+
y: max(y1, y2),
|
|
48
65
|
},
|
|
49
66
|
};
|
|
50
67
|
};
|
|
51
|
-
|
|
52
|
-
export default getSegmentProperties;
|
package/src/math/midPoint.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PointTuple } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Returns the coordinates of a specified distance
|
|
3
5
|
* ratio between two points.
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
* @param t the ratio
|
|
8
10
|
* @returns the midpoint coordinates
|
|
9
11
|
*/
|
|
10
|
-
const midPoint = (a:
|
|
12
|
+
const midPoint = (a: PointTuple, b: PointTuple, t: number): PointTuple => {
|
|
11
13
|
const [ax, ay] = a;
|
|
12
14
|
const [bx, by] = b;
|
|
13
15
|
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
package/src/math/polygonArea.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type PointTuple } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* d3-polygon-area
|
|
3
5
|
* https://github.com/d3/d3-polygon
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
* @param polygon an array of coordinates
|
|
8
10
|
* @returns the polygon area
|
|
9
11
|
*/
|
|
10
|
-
const polygonArea = (polygon: [
|
|
12
|
+
const polygonArea = (polygon: PointTuple[]): number => {
|
|
11
13
|
const n = polygon.length;
|
|
12
14
|
let i = -1;
|
|
13
15
|
let a;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PointTuple } from '../types';
|
|
1
2
|
import distanceSquareRoot from './distanceSquareRoot';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -9,7 +10,7 @@ import distanceSquareRoot from './distanceSquareRoot';
|
|
|
9
10
|
* @param polygon an array of coordinates
|
|
10
11
|
* @returns {number} the polygon length
|
|
11
12
|
*/
|
|
12
|
-
const polygonLength = (polygon: [
|
|
13
|
+
const polygonLength = (polygon: PointTuple[]): number => {
|
|
13
14
|
return polygon.reduce((length, point, i) => {
|
|
14
15
|
if (i) {
|
|
15
16
|
return length + distanceSquareRoot(polygon[i - 1], point);
|
package/src/math/quadTools.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { length, minmaxQ
|
|
1
|
+
import { length, minmaxQ } from './bezier';
|
|
2
|
+
import { type QuadCoordinates } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Returns the {x,y} coordinates of a point at a
|
|
@@ -24,7 +25,7 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
|
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* Returns
|
|
28
|
+
* Returns the length of a QuadraticBezier segment.
|
|
28
29
|
*
|
|
29
30
|
* @param x1 the starting point X
|
|
30
31
|
* @param y1 the starting point Y
|
|
@@ -32,48 +33,66 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
|
|
|
32
33
|
* @param cy the control point Y
|
|
33
34
|
* @param x2 the ending point X
|
|
34
35
|
* @param y2 the ending point Y
|
|
35
|
-
* @
|
|
36
|
-
* @returns the segment length, point at length and the bounding box
|
|
36
|
+
* @returns the QuadraticBezier segment length
|
|
37
37
|
*/
|
|
38
|
-
const
|
|
38
|
+
export const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
39
|
+
return length([x1, y1, cx, cy, x2, y2]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the point along a QuadraticBezier segment at a given distance.
|
|
44
|
+
*
|
|
45
|
+
* @param x1 the starting point X
|
|
46
|
+
* @param y1 the starting point Y
|
|
47
|
+
* @param cx the control point X
|
|
48
|
+
* @param cy the control point Y
|
|
49
|
+
* @param x2 the ending point X
|
|
50
|
+
* @param y2 the ending point Y
|
|
51
|
+
* @param distance the distance to look at
|
|
52
|
+
* @returns the point at QuadraticBezier length
|
|
53
|
+
*/
|
|
54
|
+
export const getPointAtQuadLength = (
|
|
39
55
|
x1: number,
|
|
40
56
|
y1: number,
|
|
41
57
|
cx: number,
|
|
42
58
|
cy: number,
|
|
43
59
|
x2: number,
|
|
44
60
|
y2: number,
|
|
45
|
-
distance
|
|
61
|
+
distance?: number,
|
|
46
62
|
) => {
|
|
47
63
|
const distanceIsNumber = typeof distance === 'number';
|
|
48
|
-
let
|
|
49
|
-
const getLength = () => length([x1, y1, cx, cy, x2, y2]);
|
|
64
|
+
let point = { x: x1, y: y1 };
|
|
50
65
|
|
|
66
|
+
/* istanbul ignore else @preserve */
|
|
51
67
|
if (distanceIsNumber) {
|
|
52
|
-
const currentLength =
|
|
53
|
-
|
|
68
|
+
const currentLength = length([x1, y1, cx, cy, x2, y2]);
|
|
54
69
|
if (distance <= 0) {
|
|
55
70
|
// first point already defined
|
|
56
71
|
} else if (distance >= currentLength) {
|
|
57
|
-
|
|
72
|
+
point = { x: x2, y: y2 };
|
|
58
73
|
} else {
|
|
59
|
-
|
|
74
|
+
point = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
|
|
60
75
|
}
|
|
61
76
|
}
|
|
77
|
+
return point;
|
|
78
|
+
};
|
|
62
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Returns the boundig box of a QuadraticBezier segment.
|
|
82
|
+
*
|
|
83
|
+
* @param x1 the starting point X
|
|
84
|
+
* @param y1 the starting point Y
|
|
85
|
+
* @param cx the control point X
|
|
86
|
+
* @param cy the control point Y
|
|
87
|
+
* @param x2 the ending point X
|
|
88
|
+
* @param y2 the ending point Y
|
|
89
|
+
* @returns the point at CubicBezier length
|
|
90
|
+
*/
|
|
91
|
+
export const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
|
|
92
|
+
const cxMinMax = minmaxQ([x1, cx, x2]);
|
|
93
|
+
const cyMinMax = minmaxQ([y1, cy, y2]);
|
|
63
94
|
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return getLength();
|
|
67
|
-
},
|
|
68
|
-
get bbox() {
|
|
69
|
-
const cxMinMax = minmaxQ([x1, cx, x2]);
|
|
70
|
-
const cyMinMax = minmaxQ([y1, cy, y2]);
|
|
71
|
-
return {
|
|
72
|
-
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
73
|
-
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
74
|
-
};
|
|
75
|
-
},
|
|
95
|
+
min: { x: cxMinMax[0], y: cyMinMax[0] },
|
|
96
|
+
max: { x: cxMinMax[1], y: cyMinMax[1] },
|
|
76
97
|
};
|
|
77
98
|
};
|
|
78
|
-
|
|
79
|
-
export default getSegmentProperties;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import scanSegment from './scanSegment';
|
|
2
2
|
import skipSpaces from './skipSpaces';
|
|
3
3
|
import PathParser from './pathParser';
|
|
4
|
-
import isPathArray from '../util/isPathArray';
|
|
5
4
|
import type { PathArray } from '../types';
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -11,9 +10,10 @@ import type { PathArray } from '../types';
|
|
|
11
10
|
* @param pathInput the string to be parsed
|
|
12
11
|
* @returns the resulted `pathArray` or error string
|
|
13
12
|
*/
|
|
14
|
-
const parsePathString = (pathInput: string | PathArray)
|
|
15
|
-
if (
|
|
13
|
+
const parsePathString = (pathInput: string | PathArray) => {
|
|
14
|
+
if (typeof pathInput !== 'string') {
|
|
16
15
|
return pathInput.slice(0) as PathArray;
|
|
16
|
+
// return pathInput;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const path = new PathParser(pathInput);
|
|
@@ -24,7 +24,7 @@ const parsePathString = (pathInput: string | PathArray): PathArray => {
|
|
|
24
24
|
scanSegment(path);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
if (path
|
|
27
|
+
if (path?.err.length) {
|
|
28
28
|
throw TypeError(path.err);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
|
+
import type {
|
|
3
|
+
AbsoluteSegment,
|
|
4
|
+
AbsoluteCommand,
|
|
5
|
+
ASegment,
|
|
6
|
+
VSegment,
|
|
7
|
+
HSegment,
|
|
8
|
+
QSegment,
|
|
9
|
+
SSegment,
|
|
10
|
+
TSegment,
|
|
11
|
+
CSegment,
|
|
12
|
+
PathSegment,
|
|
13
|
+
MSegment,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns an absolute segment of a `PathArray` object.
|
|
18
|
+
*
|
|
19
|
+
* @param segment the segment object
|
|
20
|
+
* @param params the coordinates of the previous segment
|
|
21
|
+
* @returns the absolute segment
|
|
22
|
+
*/
|
|
23
|
+
const absolutizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
24
|
+
const [pathCommand] = segment;
|
|
25
|
+
const { x, y } = params;
|
|
26
|
+
const values = segment.slice(1).map(Number);
|
|
27
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
28
|
+
const isAbsolute = absCommand === pathCommand;
|
|
29
|
+
|
|
30
|
+
/* istanbul ignore else @preserve */
|
|
31
|
+
if (!isAbsolute) {
|
|
32
|
+
if (absCommand === 'A') {
|
|
33
|
+
return [
|
|
34
|
+
absCommand,
|
|
35
|
+
values[0],
|
|
36
|
+
values[1],
|
|
37
|
+
values[2],
|
|
38
|
+
values[3],
|
|
39
|
+
values[4],
|
|
40
|
+
values[5] + x,
|
|
41
|
+
values[6] + y,
|
|
42
|
+
] as ASegment;
|
|
43
|
+
} else if (absCommand === 'V') {
|
|
44
|
+
return [absCommand, values[0] + y] as VSegment;
|
|
45
|
+
} else if (absCommand === 'H') {
|
|
46
|
+
return [absCommand, values[0] + x] as HSegment;
|
|
47
|
+
} else {
|
|
48
|
+
// use brakets for `eslint: no-case-declaration`
|
|
49
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
50
|
+
const absValues = values.map((n, j) => n + (j % 2 ? y : x));
|
|
51
|
+
// for n, l, c, s, q, t
|
|
52
|
+
return [absCommand, ...absValues] as MSegment | QSegment | TSegment | SSegment | CSegment;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return segment as AbsoluteSegment;
|
|
57
|
+
};
|
|
58
|
+
export default absolutizeSegment;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { PathArray, PathCommand, PathSegment, IteratorCallback } from '../types';
|
|
3
|
+
|
|
4
|
+
const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
|
|
5
|
+
const allPathCommands = [] as PathCommand[];
|
|
6
|
+
const params = { ...paramsParser };
|
|
7
|
+
let pathLen = path.length;
|
|
8
|
+
let segment: PathSegment;
|
|
9
|
+
let pathCommand = 'M' as PathCommand;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < pathLen; i += 1) {
|
|
12
|
+
segment = path[i];
|
|
13
|
+
[pathCommand] = segment;
|
|
14
|
+
allPathCommands[i] = pathCommand;
|
|
15
|
+
const iteratorResult = iterator(segment, params, i);
|
|
16
|
+
path[i] = iteratorResult;
|
|
17
|
+
|
|
18
|
+
if (iteratorResult[0] === 'C') {
|
|
19
|
+
pathLen = path.length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
segment = path[i];
|
|
23
|
+
const seglen = segment.length;
|
|
24
|
+
params.x1 = +segment[seglen - 2];
|
|
25
|
+
params.y1 = +segment[seglen - 1];
|
|
26
|
+
params.x2 = +segment[seglen - 4] || params.x1;
|
|
27
|
+
params.y2 = +segment[seglen - 3] || params.y1;
|
|
28
|
+
}
|
|
29
|
+
// console.log('iteration: ', ...path)
|
|
30
|
+
return path as T;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default iterate;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
2
1
|
import normalizeSegment from './normalizeSegment';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import type { AbsoluteCommand, NormalArray, PathArray, PointTuple } from '../types';
|
|
3
|
+
import iterate from './iterate';
|
|
4
|
+
import parsePathString from '../parser/parsePathString';
|
|
5
|
+
import absolutizeSegment from './absolutizeSegment';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Normalizes a `path` object for further processing:
|
|
@@ -12,33 +12,39 @@ import type { NormalArray, PathArray } from '../types';
|
|
|
12
12
|
* @param pathInput the string to be parsed or 'pathArray'
|
|
13
13
|
* @returns the normalized `pathArray`
|
|
14
14
|
*/
|
|
15
|
-
const normalizePath = (pathInput: string | PathArray)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const normalizePath = (pathInput: string | PathArray) => {
|
|
16
|
+
let x = 0;
|
|
17
|
+
let y = 0;
|
|
18
|
+
let mx = 0;
|
|
19
|
+
let my = 0;
|
|
20
|
+
let pathCommand = 'M';
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
return iterate<NormalArray>(parsePathString(pathInput), (seg, params) => {
|
|
23
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
24
|
+
const result = normalizeSegment(absoluteSegment, params);
|
|
25
|
+
[pathCommand] = result;
|
|
26
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
if (absCommand === 'Z') {
|
|
29
|
+
x = mx;
|
|
30
|
+
y = my;
|
|
31
|
+
} else {
|
|
32
|
+
[x, y] = result.slice(-2) as PointTuple;
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (absCommand === 'M') {
|
|
35
|
+
mx = x;
|
|
36
|
+
my = y;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
32
39
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
params.
|
|
37
|
-
params.
|
|
38
|
-
params.
|
|
39
|
-
params.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return path as NormalArray;
|
|
40
|
+
// const seglen = result.length;
|
|
41
|
+
// params.x1 = +result[seglen - 2];
|
|
42
|
+
// params.y1 = +result[seglen - 1];
|
|
43
|
+
// params.x2 = +result[seglen - 4] || params.x1;
|
|
44
|
+
// params.y2 = +result[seglen - 3] || params.y1;
|
|
45
|
+
params.x = x;
|
|
46
|
+
params.y = y;
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
43
49
|
};
|
|
44
50
|
export default normalizePath;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ParserParams } from '../interface';
|
|
2
|
-
import type { NormalSegment, PathSegment } from '../types';
|
|
2
|
+
import type { NormalSegment, PointTuple, PathSegment, QSegment, CSegment, LSegment } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalizes a single segment of a `pathArray` object.
|
|
@@ -8,11 +8,10 @@ import type { NormalSegment, PathSegment } from '../types';
|
|
|
8
8
|
* @param params the coordinates of the previous segment
|
|
9
9
|
* @returns the normalized segment
|
|
10
10
|
*/
|
|
11
|
-
const normalizeSegment = (segment: PathSegment, params: ParserParams)
|
|
11
|
+
const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
12
12
|
const [pathCommand] = segment;
|
|
13
13
|
const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
|
|
14
14
|
const values = segment.slice(1).map(Number);
|
|
15
|
-
let result = segment;
|
|
16
15
|
|
|
17
16
|
if (!'TQ'.includes(pathCommand)) {
|
|
18
17
|
// optional but good to be cautious
|
|
@@ -21,27 +20,27 @@ const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSeg
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
if (pathCommand === 'H') {
|
|
24
|
-
|
|
23
|
+
return ['L', segment[1], py1] as LSegment;
|
|
25
24
|
} else if (pathCommand === 'V') {
|
|
26
|
-
|
|
25
|
+
return ['L', px1, segment[1]] as LSegment;
|
|
27
26
|
} else if (pathCommand === 'S') {
|
|
28
27
|
const x1 = px1 * 2 - px2;
|
|
29
28
|
const y1 = py1 * 2 - py2;
|
|
30
29
|
params.x1 = x1;
|
|
31
30
|
params.y1 = y1;
|
|
32
|
-
|
|
31
|
+
return ['C', x1, y1, ...values] as CSegment;
|
|
33
32
|
} else if (pathCommand === 'T') {
|
|
34
33
|
const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
|
|
35
34
|
const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
|
|
36
35
|
params.qx = qx;
|
|
37
36
|
params.qy = qy;
|
|
38
|
-
|
|
37
|
+
return ['Q', qx, qy, ...values] as QSegment;
|
|
39
38
|
} else if (pathCommand === 'Q') {
|
|
40
|
-
const [nqx, nqy] = values as
|
|
39
|
+
const [nqx, nqy] = values as PointTuple;
|
|
41
40
|
params.qx = nqx;
|
|
42
41
|
params.qy = nqy;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
return
|
|
44
|
+
return segment as NormalSegment;
|
|
46
45
|
};
|
|
47
46
|
export default normalizeSegment;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import CSSMatrix from '@thednp/dommatrix';
|
|
2
|
+
import { type PointTuple } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Transforms a specified point using a matrix, returning a new
|
|
@@ -34,7 +35,7 @@ const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [
|
|
|
34
35
|
* @param origin the [x,y,z] transform origin
|
|
35
36
|
* @returns the projected [x,y] coordinates
|
|
36
37
|
*/
|
|
37
|
-
const projection2d = (m: CSSMatrix, point2D:
|
|
38
|
+
const projection2d = (m: CSSMatrix, point2D: PointTuple, origin: [number, number, number]): PointTuple => {
|
|
38
39
|
const [originX, originY, originZ] = origin;
|
|
39
40
|
const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
|
|
40
41
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
|
+
import type {
|
|
3
|
+
RelativeSegment,
|
|
4
|
+
RelativeCommand,
|
|
5
|
+
PathSegment,
|
|
6
|
+
aSegment,
|
|
7
|
+
vSegment,
|
|
8
|
+
hSegment,
|
|
9
|
+
qSegment,
|
|
10
|
+
tSegment,
|
|
11
|
+
sSegment,
|
|
12
|
+
cSegment,
|
|
13
|
+
} from '../types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns a relative segment of a `PathArray` object.
|
|
17
|
+
*
|
|
18
|
+
* @param segment the segment object
|
|
19
|
+
* @param params the coordinates of the previous segment
|
|
20
|
+
* @param index the segment index
|
|
21
|
+
* @returns the absolute segment
|
|
22
|
+
*/
|
|
23
|
+
const relativizeSegment = (segment: PathSegment, params: ParserParams, index: number) => {
|
|
24
|
+
const [pathCommand] = segment;
|
|
25
|
+
const { x, y } = params;
|
|
26
|
+
const values = segment.slice(1).map(Number);
|
|
27
|
+
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
28
|
+
|
|
29
|
+
if (index === 0 && pathCommand === 'M') {
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* istanbul ignore else @preserve */
|
|
34
|
+
if (pathCommand !== relCommand) {
|
|
35
|
+
if (relCommand === 'a') {
|
|
36
|
+
return [
|
|
37
|
+
relCommand,
|
|
38
|
+
values[0],
|
|
39
|
+
values[1],
|
|
40
|
+
values[2],
|
|
41
|
+
values[3],
|
|
42
|
+
values[4],
|
|
43
|
+
values[5] - x,
|
|
44
|
+
values[6] - y,
|
|
45
|
+
] as aSegment;
|
|
46
|
+
} else if (relCommand === 'v') {
|
|
47
|
+
return [relCommand, values[0] - y] as vSegment;
|
|
48
|
+
} else if (relCommand === 'h') {
|
|
49
|
+
return [relCommand, values[0] - x] as hSegment;
|
|
50
|
+
} else {
|
|
51
|
+
// use brakets for `eslint: no-case-declaration`
|
|
52
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
53
|
+
const relValues = values.map((n, j) => n - (j % 2 ? y : x));
|
|
54
|
+
// for n, l, c, s, q, t
|
|
55
|
+
return [relCommand, ...relValues] as qSegment | tSegment | sSegment | cSegment;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return segment as RelativeSegment;
|
|
60
|
+
};
|
|
61
|
+
export default relativizeSegment;
|
package/src/process/roundPath.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { PathArray } from '
|
|
1
|
+
import type { PathArray, PathSegment } from '../types';
|
|
2
2
|
import defaultOptions from '../options/options';
|
|
3
|
+
import iterate from './iterate';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Rounds the values of a `pathArray` instance to
|
|
@@ -9,21 +10,18 @@ import defaultOptions from '../options/options';
|
|
|
9
10
|
* @param roundOption the amount of decimals to round numbers to
|
|
10
11
|
* @returns the resulted `pathArray` with rounded values
|
|
11
12
|
*/
|
|
12
|
-
const roundPath = (path: PathArray, roundOption?: number | 'off')
|
|
13
|
+
const roundPath = (path: PathArray, roundOption?: number | 'off') => {
|
|
13
14
|
let { round } = defaultOptions;
|
|
14
|
-
if (roundOption === 'off' || round === 'off') return
|
|
15
|
+
if (roundOption === 'off' || round === 'off') return path.slice(0) as PathArray;
|
|
15
16
|
// allow for ZERO decimals
|
|
16
17
|
round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
|
|
17
18
|
// to round values to the power
|
|
18
19
|
// the `round` value must be integer
|
|
19
20
|
const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
|
|
20
21
|
|
|
21
|
-
return path
|
|
22
|
-
const values =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
|
|
26
|
-
return [pi[0], ...values];
|
|
27
|
-
}) as PathArray;
|
|
22
|
+
return iterate<typeof path>(path, segment => {
|
|
23
|
+
const values = (segment.slice(1) as number[]).map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
|
|
24
|
+
return [segment[0], ...values] as PathSegment;
|
|
25
|
+
});
|
|
28
26
|
};
|
|
29
27
|
export default roundPath;
|
|
@@ -11,7 +11,7 @@ import type { ParserParams } from '../interface';
|
|
|
11
11
|
* @param params the source segment parameters
|
|
12
12
|
* @returns the cubic-bezier segment
|
|
13
13
|
*/
|
|
14
|
-
const segmentToCubic = (segment: PathSegment, params: ParserParams)
|
|
14
|
+
const segmentToCubic = (segment: PathSegment, params: ParserParams) => {
|
|
15
15
|
const [pathCommand] = segment;
|
|
16
16
|
const values = segment.slice(1).map(Number);
|
|
17
17
|
const [x, y] = values;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ParserParams } from '
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
2
|
import type {
|
|
3
3
|
AbsoluteSegment,
|
|
4
4
|
HSegment,
|
|
@@ -28,8 +28,8 @@ const shortenSegment = (
|
|
|
28
28
|
): ShortSegment => {
|
|
29
29
|
const [pathCommand] = segment;
|
|
30
30
|
const round4 = (n: number) => Math.round(n * 10 ** 4) / 10 ** 4;
|
|
31
|
-
const segmentValues = segment.slice(1)
|
|
32
|
-
const normalValues = normalSegment.slice(1)
|
|
31
|
+
const segmentValues = segment.slice(1) as number[];
|
|
32
|
+
const normalValues = normalSegment.slice(1) as number[];
|
|
33
33
|
const { x1: px1, y1: py1, x2: px2, y2: py2, x: px, y: py } = params;
|
|
34
34
|
let result = segment;
|
|
35
35
|
const [x, y] = normalValues.slice(-2);
|