svg-path-commander 1.0.5 → 2.0.0-alpha2
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 +26 -4
- package/dist/svg-path-commander.cjs +2 -0
- package/dist/svg-path-commander.cjs.map +1 -0
- package/dist/svg-path-commander.d.ts +454 -0
- package/dist/svg-path-commander.js +2 -3762
- package/dist/svg-path-commander.js.map +1 -0
- package/dist/svg-path-commander.mjs +1194 -0
- package/dist/svg-path-commander.mjs.map +1 -0
- package/package.json +39 -42
- package/src/convert/pathToAbsolute.ts +101 -0
- package/src/convert/{pathToCurve.js → pathToCurve.ts} +12 -14
- package/src/convert/pathToRelative.ts +92 -0
- package/src/convert/pathToString.ts +17 -0
- package/src/{svg-path-commander.js → index.ts} +143 -66
- package/src/interface.ts +129 -0
- package/src/math/distanceSquareRoot.ts +13 -0
- package/src/math/midPoint.ts +16 -0
- package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
- package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
- package/src/math/rotateVector.ts +16 -0
- package/src/options/options.ts +9 -0
- package/src/parser/error.ts +2 -0
- package/src/parser/finalizeSegment.ts +31 -0
- package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
- package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
- package/src/parser/isDigit.ts +12 -0
- package/src/parser/isDigitStart.ts +14 -0
- package/src/parser/isPathCommand.ts +28 -0
- package/src/parser/isSpace.ts +23 -0
- package/src/parser/paramsCount.ts +16 -0
- package/src/parser/paramsParser.ts +14 -0
- package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
- package/src/parser/pathParser.ts +29 -0
- package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
- package/src/parser/{scanParam.js → scanParam.ts} +12 -11
- package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
- package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
- package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
- package/src/process/fixArc.ts +23 -0
- package/src/process/getSVGMatrix.ts +70 -0
- package/src/process/lineToCubic.ts +17 -0
- package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
- package/src/process/normalizeSegment.ts +47 -0
- package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
- package/src/process/{projection2d.js → projection2d.ts} +16 -15
- package/src/process/quadToCubic.ts +31 -0
- package/src/process/reverseCurve.ts +21 -0
- package/src/process/reversePath.ts +101 -0
- package/src/process/roundPath.ts +29 -0
- package/src/process/segmentToCubic.ts +46 -0
- package/src/process/shortenSegment.ts +79 -0
- package/src/process/splitCubic.ts +28 -0
- package/src/process/{splitPath.js → splitPath.ts} +9 -8
- package/src/process/{transformPath.js → transformPath.ts} +55 -48
- package/src/types.ts +193 -0
- package/src/util/getClosestPoint.ts +15 -0
- package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
- package/src/util/getPathArea.ts +70 -0
- package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
- package/src/util/getPointAtLength.ts +14 -0
- package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
- package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
- package/src/util/getSegmentAtLength.ts +15 -0
- package/src/util/getSegmentOfPoint.ts +18 -0
- package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
- package/src/util/isAbsoluteArray.ts +18 -0
- package/src/util/{isCurveArray.js → isCurveArray.ts} +6 -4
- package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +4 -2
- package/src/util/isPathArray.ts +19 -0
- package/src/util/isPointInStroke.ts +15 -0
- package/src/util/isRelativeArray.ts +18 -0
- package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
- package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
- package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +71 -55
- package/src/util/segmentCubicFactory.ts +114 -0
- package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +10 -8
- package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +47 -32
- package/src/util/shapeToPath.ts +214 -0
- package/dist/svg-path-commander.es5.js +0 -3875
- package/dist/svg-path-commander.es5.min.js +0 -2
- package/dist/svg-path-commander.esm.js +0 -3754
- package/dist/svg-path-commander.esm.min.js +0 -2
- package/dist/svg-path-commander.min.js +0 -2
- package/src/convert/pathToAbsolute.js +0 -86
- package/src/convert/pathToRelative.js +0 -84
- package/src/convert/pathToString.js +0 -14
- package/src/index.js +0 -10
- package/src/math/distanceSquareRoot.js +0 -14
- package/src/math/epsilon.js +0 -8
- package/src/math/midPoint.js +0 -13
- package/src/math/rotateVector.js +0 -14
- package/src/options/options.js +0 -10
- package/src/parser/error.js +0 -2
- package/src/parser/finalizeSegment.js +0 -28
- package/src/parser/isDigit.js +0 -9
- package/src/parser/isDigitStart.js +0 -13
- package/src/parser/isPathCommand.js +0 -25
- package/src/parser/isSpace.js +0 -16
- package/src/parser/paramsCount.js +0 -9
- package/src/parser/paramsParser.js +0 -8
- package/src/parser/pathParser.js +0 -24
- package/src/process/clonePath.js +0 -9
- package/src/process/fixArc.js +0 -21
- package/src/process/fixPath.js +0 -31
- package/src/process/getSVGMatrix.js +0 -61
- package/src/process/lineToCubic.js +0 -30
- package/src/process/normalizeSegment.js +0 -45
- package/src/process/quadToCubic.js +0 -22
- package/src/process/reverseCurve.js +0 -18
- package/src/process/reversePath.js +0 -89
- package/src/process/roundPath.js +0 -26
- package/src/process/segmentToCubic.js +0 -46
- package/src/process/shortenSegment.js +0 -58
- package/src/process/splitCubic.js +0 -26
- package/src/util/getClosestPoint.js +0 -12
- package/src/util/getPathArea.js +0 -47
- package/src/util/getPointAtLength.js +0 -12
- package/src/util/getSegmentAtLength.js +0 -11
- package/src/util/getSegmentOfPoint.js +0 -12
- package/src/util/isAbsoluteArray.js +0 -14
- package/src/util/isPathArray.js +0 -14
- package/src/util/isPointInStroke.js +0 -13
- package/src/util/isRelativeArray.js +0 -14
- package/src/util/segmentCubicFactory.js +0 -97
- package/src/util/shapeToPath.js +0 -204
- package/src/util/util.js +0 -82
- package/src/version.js +0 -8
- package/types/index.d.ts +0 -120
- package/types/more/modules.ts +0 -82
- package/types/more/svg.d.ts +0 -211
- package/types/svg-path-commander.d.ts +0 -1089
|
@@ -6,27 +6,42 @@ import rotateVector from '../math/rotateVector';
|
|
|
6
6
|
* For more information of where this math came from visit:
|
|
7
7
|
* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
|
8
8
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @param
|
|
17
|
-
* @param
|
|
18
|
-
* @param
|
|
19
|
-
* @return
|
|
9
|
+
* @param X1 the starting x position
|
|
10
|
+
* @param Y1 the starting y position
|
|
11
|
+
* @param RX x-radius of the arc
|
|
12
|
+
* @param RY y-radius of the arc
|
|
13
|
+
* @param angle x-axis-rotation of the arc
|
|
14
|
+
* @param LAF large-arc-flag of the arc
|
|
15
|
+
* @param SF sweep-flag of the arc
|
|
16
|
+
* @param X2 the ending x position
|
|
17
|
+
* @param Y2 the ending y position
|
|
18
|
+
* @param recursive the parameters needed to split arc into 2 segments
|
|
19
|
+
* @return the resulting cubic-bezier segment(s)
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const arcToCubic = (
|
|
22
|
+
X1: number,
|
|
23
|
+
Y1: number,
|
|
24
|
+
RX: number,
|
|
25
|
+
RY: number,
|
|
26
|
+
angle: number,
|
|
27
|
+
LAF: number,
|
|
28
|
+
SF: number,
|
|
29
|
+
X2: number,
|
|
30
|
+
Y2: number,
|
|
31
|
+
recursive?: [number, number, number, number],
|
|
32
|
+
): number[] => {
|
|
33
|
+
let x1 = X1;
|
|
34
|
+
let y1 = Y1;
|
|
35
|
+
let rx = RX;
|
|
36
|
+
let ry = RY;
|
|
37
|
+
let x2 = X2;
|
|
38
|
+
let y2 = Y2;
|
|
23
39
|
// for more information of where this Math came from visit:
|
|
24
40
|
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
|
25
41
|
const d120 = (Math.PI * 120) / 180;
|
|
26
42
|
|
|
27
43
|
const rad = (Math.PI / 180) * (+angle || 0);
|
|
28
|
-
|
|
29
|
-
let res = [];
|
|
44
|
+
let res = [] as number[];
|
|
30
45
|
let xy;
|
|
31
46
|
let f1;
|
|
32
47
|
let f2;
|
|
@@ -52,21 +67,21 @@ export default function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recur
|
|
|
52
67
|
const rx2 = rx * rx;
|
|
53
68
|
const ry2 = ry * ry;
|
|
54
69
|
|
|
55
|
-
const k =
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
const k =
|
|
71
|
+
(LAF === SF ? -1 : 1) *
|
|
72
|
+
Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
|
|
58
73
|
|
|
59
|
-
cx = (
|
|
60
|
-
cy = (
|
|
74
|
+
cx = (k * rx * y) / ry + (x1 + x2) / 2;
|
|
75
|
+
cy = (k * -ry * x) / rx + (y1 + y2) / 2;
|
|
61
76
|
// eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
|
|
62
|
-
f1 = Math.asin((((y1 - cy) / ry) *
|
|
77
|
+
f1 = Math.asin(((((y1 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);
|
|
63
78
|
// eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
|
|
64
|
-
f2 = Math.asin((((y2 - cy) / ry) *
|
|
79
|
+
f2 = Math.asin(((((y2 - cy) / ry) * 10 ** 9) >> 0) / 10 ** 9);
|
|
65
80
|
|
|
66
81
|
f1 = x1 < cx ? Math.PI - f1 : f1;
|
|
67
82
|
f2 = x2 < cx ? Math.PI - f2 : f2;
|
|
68
|
-
if (f1 < 0)
|
|
69
|
-
if (f2 < 0)
|
|
83
|
+
if (f1 < 0) f1 = Math.PI * 2 + f1;
|
|
84
|
+
if (f2 < 0) f2 = Math.PI * 2 + f2;
|
|
70
85
|
if (SF && f1 > f2) {
|
|
71
86
|
f1 -= Math.PI * 2;
|
|
72
87
|
}
|
|
@@ -106,9 +121,8 @@ export default function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recur
|
|
|
106
121
|
res = [...m2, ...m3, ...m4, ...res];
|
|
107
122
|
const newres = [];
|
|
108
123
|
for (let i = 0, ii = res.length; i < ii; i += 1) {
|
|
109
|
-
newres[i] = i % 2
|
|
110
|
-
? rotateVector(res[i - 1], res[i], rad).y
|
|
111
|
-
: rotateVector(res[i], res[i + 1], rad).x;
|
|
124
|
+
newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
|
|
112
125
|
}
|
|
113
126
|
return newres;
|
|
114
|
-
}
|
|
127
|
+
};
|
|
128
|
+
export default arcToCubic;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CSegment, PathArray, PathCommand } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Splits an extended A (arc-to) segment into two cubic-bezier segments.
|
|
5
|
+
*
|
|
6
|
+
* @param path the `pathArray` this segment belongs to
|
|
7
|
+
* @param allPathCommands all previous path commands
|
|
8
|
+
* @param i the segment index
|
|
9
|
+
*/
|
|
10
|
+
const fixArc = (path: PathArray, allPathCommands: PathCommand[], i: number) => {
|
|
11
|
+
if (path[i].length > 7) {
|
|
12
|
+
path[i].shift();
|
|
13
|
+
const segment = path[i];
|
|
14
|
+
let ni = i; // ESLint
|
|
15
|
+
while (segment.length) {
|
|
16
|
+
// if created multiple C:s, their original seg is saved
|
|
17
|
+
allPathCommands[i] = 'A';
|
|
18
|
+
path.splice((ni += 1), 0, ['C', ...segment.splice(0, 6)] as CSegment);
|
|
19
|
+
}
|
|
20
|
+
path.splice(i, 1);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export default fixArc;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import CSSMatrix from '@thednp/dommatrix';
|
|
2
|
+
// import type { TransformObject } from '../interface';
|
|
3
|
+
import type { TransformObjectValues } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a transformation matrix to apply to `<path>` elements.
|
|
7
|
+
*
|
|
8
|
+
* @see TransformObjectValues
|
|
9
|
+
*
|
|
10
|
+
* @param transform the `transformObject`
|
|
11
|
+
* @returns a new transformation matrix
|
|
12
|
+
*/
|
|
13
|
+
const getSVGMatrix = (transform: TransformObjectValues): CSSMatrix => {
|
|
14
|
+
let matrix = new CSSMatrix();
|
|
15
|
+
const { origin } = transform;
|
|
16
|
+
const [originX, originY] = origin as [number, number, number];
|
|
17
|
+
const { translate } = transform;
|
|
18
|
+
const { rotate } = transform;
|
|
19
|
+
const { skew } = transform;
|
|
20
|
+
const { scale } = transform;
|
|
21
|
+
|
|
22
|
+
// set translate
|
|
23
|
+
if (
|
|
24
|
+
Array.isArray(translate) &&
|
|
25
|
+
translate.length >= 2 &&
|
|
26
|
+
translate.every(x => !Number.isNaN(+x)) &&
|
|
27
|
+
translate.some(x => x !== 0)
|
|
28
|
+
) {
|
|
29
|
+
matrix = matrix.translate(...(translate as [number, number, number?]));
|
|
30
|
+
} else if (typeof translate === 'number' && !Number.isNaN(translate)) {
|
|
31
|
+
matrix = matrix.translate(translate);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (rotate || skew || scale) {
|
|
35
|
+
// set SVG transform-origin, always defined
|
|
36
|
+
matrix = matrix.translate(originX, originY);
|
|
37
|
+
|
|
38
|
+
// set rotation
|
|
39
|
+
if (
|
|
40
|
+
Array.isArray(rotate) &&
|
|
41
|
+
rotate.length >= 2 &&
|
|
42
|
+
rotate.every(x => !Number.isNaN(+x)) &&
|
|
43
|
+
rotate.some(x => x !== 0)
|
|
44
|
+
) {
|
|
45
|
+
matrix = matrix.rotate(...(rotate as [number, number, number?]));
|
|
46
|
+
} else if (typeof rotate === 'number' && !Number.isNaN(rotate)) {
|
|
47
|
+
matrix = matrix.rotate(rotate);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// set skew(s)
|
|
51
|
+
if (Array.isArray(skew) && skew.length === 2 && skew.every(x => !Number.isNaN(+x)) && skew.some(x => x !== 0)) {
|
|
52
|
+
matrix = skew[0] ? matrix.skewX(skew[0]) : matrix;
|
|
53
|
+
matrix = skew[1] ? matrix.skewY(skew[1]) : matrix;
|
|
54
|
+
} else if (typeof skew === 'number' && !Number.isNaN(skew)) {
|
|
55
|
+
matrix = matrix.skewX(skew);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// set scale
|
|
59
|
+
if (Array.isArray(scale) && scale.length >= 2 && scale.every(x => !Number.isNaN(+x)) && scale.some(x => x !== 1)) {
|
|
60
|
+
matrix = matrix.scale(...(scale as [number, number, number?]));
|
|
61
|
+
} else if (typeof scale === 'number' && !Number.isNaN(scale)) {
|
|
62
|
+
matrix = matrix.scale(scale);
|
|
63
|
+
}
|
|
64
|
+
// set SVG transform-origin
|
|
65
|
+
matrix = matrix.translate(-originX, -originY);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return matrix;
|
|
69
|
+
};
|
|
70
|
+
export default getSVGMatrix;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import midPoint from '../math/midPoint';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts an L (line-to) segment to C (cubic-bezier).
|
|
5
|
+
*
|
|
6
|
+
* @param x1 line start x
|
|
7
|
+
* @param y1 line start y
|
|
8
|
+
* @param x2 line end x
|
|
9
|
+
* @param y2 line end y
|
|
10
|
+
* @returns the cubic-bezier segment
|
|
11
|
+
*/
|
|
12
|
+
const lineToCubic = (x1: number, y1: number, x2: number, y2: number) => {
|
|
13
|
+
const t = 0.5;
|
|
14
|
+
const mid = midPoint([x1, y1], [x2, y2], t);
|
|
15
|
+
return [...mid, x2, y2, x2, y2];
|
|
16
|
+
};
|
|
17
|
+
export default lineToCubic;
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
2
2
|
import normalizeSegment from './normalizeSegment';
|
|
3
|
-
import clonePath from './clonePath';
|
|
4
3
|
import isNormalizedArray from '../util/isNormalizedArray';
|
|
5
4
|
import paramsParser from '../parser/paramsParser';
|
|
5
|
+
import type { NormalArray, PathArray } from '../types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Normalizes a `path` object for further processing:
|
|
9
9
|
* * convert segments to absolute values
|
|
10
10
|
* * convert shorthand path commands to their non-shorthand notation
|
|
11
11
|
*
|
|
12
|
-
* @param
|
|
13
|
-
* @returns
|
|
12
|
+
* @param pathInput the string to be parsed or 'pathArray'
|
|
13
|
+
* @returns the normalized `pathArray`
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
const normalizePath = (pathInput: string | PathArray): NormalArray => {
|
|
16
16
|
if (isNormalizedArray(pathInput)) {
|
|
17
|
-
return
|
|
17
|
+
return [...pathInput];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** @type {SVGPath.normalArray} */
|
|
21
20
|
const path = pathToAbsolute(pathInput);
|
|
22
21
|
const params = { ...paramsParser };
|
|
23
22
|
const allPathCommands = [];
|
|
@@ -36,9 +35,10 @@ export default function normalizePath(pathInput) {
|
|
|
36
35
|
|
|
37
36
|
params.x1 = +segment[seglen - 2];
|
|
38
37
|
params.y1 = +segment[seglen - 1];
|
|
39
|
-
params.x2 = +
|
|
40
|
-
params.y2 = +
|
|
38
|
+
params.x2 = +segment[seglen - 4] || params.x1;
|
|
39
|
+
params.y2 = +segment[seglen - 3] || params.y1;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
return path;
|
|
44
|
-
}
|
|
42
|
+
return path as NormalArray;
|
|
43
|
+
};
|
|
44
|
+
export default normalizePath;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
|
+
import type { NormalSegment, PathSegment } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes a single segment of a `pathArray` object.
|
|
6
|
+
*
|
|
7
|
+
* @param segment the segment object
|
|
8
|
+
* @param params the coordinates of the previous segment
|
|
9
|
+
* @returns the normalized segment
|
|
10
|
+
*/
|
|
11
|
+
const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSegment => {
|
|
12
|
+
const [pathCommand] = segment;
|
|
13
|
+
const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
|
|
14
|
+
const values = segment.slice(1).map(Number);
|
|
15
|
+
let result = segment;
|
|
16
|
+
|
|
17
|
+
if (!'TQ'.includes(pathCommand)) {
|
|
18
|
+
// optional but good to be cautious
|
|
19
|
+
params.qx = null;
|
|
20
|
+
params.qy = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (pathCommand === 'H') {
|
|
24
|
+
result = ['L', segment[1], py1];
|
|
25
|
+
} else if (pathCommand === 'V') {
|
|
26
|
+
result = ['L', px1, segment[1]];
|
|
27
|
+
} else if (pathCommand === 'S') {
|
|
28
|
+
const x1 = px1 * 2 - px2;
|
|
29
|
+
const y1 = py1 * 2 - py2;
|
|
30
|
+
params.x1 = x1;
|
|
31
|
+
params.y1 = y1;
|
|
32
|
+
result = ['C', x1, y1, ...(values as [number, number, number, number])];
|
|
33
|
+
} else if (pathCommand === 'T') {
|
|
34
|
+
const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
|
|
35
|
+
const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
|
|
36
|
+
params.qx = qx;
|
|
37
|
+
params.qy = qy;
|
|
38
|
+
result = ['Q', qx, qy, ...(values as [number, number])];
|
|
39
|
+
} else if (pathCommand === 'Q') {
|
|
40
|
+
const [nqx, nqy] = values as [number, number];
|
|
41
|
+
params.qx = nqx;
|
|
42
|
+
params.qy = nqy;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result as NormalSegment;
|
|
46
|
+
};
|
|
47
|
+
export default normalizeSegment;
|
|
@@ -4,28 +4,25 @@ import pathToRelative from '../convert/pathToRelative';
|
|
|
4
4
|
import shortenSegment from './shortenSegment';
|
|
5
5
|
import paramsParser from '../parser/paramsParser';
|
|
6
6
|
import normalizePath from './normalizePath';
|
|
7
|
+
import type { PathSegment, HSegment, PathArray, VSegment, PathCommand, AbsoluteSegment } from '../types';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Optimizes a `pathArray` object:
|
|
10
11
|
* * convert segments to shorthand if possible
|
|
11
12
|
* * select shortest segments from absolute and relative `pathArray`s
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param {SVGPath.pathArray} pathInput a string or `pathArray`
|
|
18
|
-
* @param {number | 'off'} round the amount of decimals to round values to
|
|
19
|
-
* @returns {SVGPath.pathArray} the optimized `pathArray`
|
|
14
|
+
* @param pathInput a string or `pathArray`
|
|
15
|
+
* @param round the amount of decimals to round values to
|
|
16
|
+
* @returns the optimized `pathArray`
|
|
20
17
|
*/
|
|
21
|
-
|
|
18
|
+
const optimizePath = (pathInput: PathArray, round: 'off' | number): PathArray => {
|
|
22
19
|
const path = pathToAbsolute(pathInput);
|
|
23
20
|
const normalPath = normalizePath(path);
|
|
24
21
|
const params = { ...paramsParser };
|
|
25
|
-
const allPathCommands = [];
|
|
22
|
+
const allPathCommands = [] as PathCommand[];
|
|
26
23
|
const ii = path.length;
|
|
27
|
-
let pathCommand = '';
|
|
28
|
-
let prevCommand = '';
|
|
24
|
+
let pathCommand = '' as PathCommand;
|
|
25
|
+
let prevCommand = '' as PathCommand;
|
|
29
26
|
let x = 0;
|
|
30
27
|
let y = 0;
|
|
31
28
|
let mx = 0;
|
|
@@ -38,7 +35,7 @@ export default function optimizePath(pathInput, round) {
|
|
|
38
35
|
allPathCommands[i] = pathCommand;
|
|
39
36
|
// Get previous path command for `shortenSegment`
|
|
40
37
|
if (i) prevCommand = allPathCommands[i - 1];
|
|
41
|
-
path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand);
|
|
38
|
+
path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand) as AbsoluteSegment;
|
|
42
39
|
|
|
43
40
|
const segment = path[i];
|
|
44
41
|
const seglen = segment.length;
|
|
@@ -46,8 +43,8 @@ export default function optimizePath(pathInput, round) {
|
|
|
46
43
|
// update C, S, Q, T specific params
|
|
47
44
|
params.x1 = +segment[seglen - 2];
|
|
48
45
|
params.y1 = +segment[seglen - 1];
|
|
49
|
-
params.x2 = +
|
|
50
|
-
params.y2 = +
|
|
46
|
+
params.x2 = +segment[seglen - 4] || params.x1;
|
|
47
|
+
params.y2 = +segment[seglen - 3] || params.y1;
|
|
51
48
|
|
|
52
49
|
// update x, y params
|
|
53
50
|
switch (pathCommand) {
|
|
@@ -56,10 +53,10 @@ export default function optimizePath(pathInput, round) {
|
|
|
56
53
|
y = my;
|
|
57
54
|
break;
|
|
58
55
|
case 'H':
|
|
59
|
-
[, x] = segment;
|
|
56
|
+
[, x] = segment as HSegment;
|
|
60
57
|
break;
|
|
61
58
|
case 'V':
|
|
62
|
-
[, y] = segment;
|
|
59
|
+
[, y] = segment as VSegment;
|
|
63
60
|
break;
|
|
64
61
|
default:
|
|
65
62
|
[x, y] = segment.slice(-2).map(Number);
|
|
@@ -76,11 +73,11 @@ export default function optimizePath(pathInput, round) {
|
|
|
76
73
|
const absolutePath = roundPath(path, round);
|
|
77
74
|
const relativePath = roundPath(pathToRelative(path), round);
|
|
78
75
|
|
|
79
|
-
return absolutePath.map((a, i) => {
|
|
76
|
+
return absolutePath.map((a: PathSegment, i: number) => {
|
|
80
77
|
if (i) {
|
|
81
|
-
return a.join('').length < relativePath[i].join('').length
|
|
82
|
-
? a : relativePath[i];
|
|
78
|
+
return a.join('').length < relativePath[i].join('').length ? a : relativePath[i];
|
|
83
79
|
}
|
|
84
80
|
return a;
|
|
85
|
-
});
|
|
86
|
-
}
|
|
81
|
+
}) as PathArray;
|
|
82
|
+
};
|
|
83
|
+
export default optimizePath;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CSSMatrix from '@thednp/dommatrix';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Transforms a specified point using a matrix, returning a new
|
|
@@ -7,18 +7,18 @@ import { Translate } from 'dommatrix/src/dommatrix';
|
|
|
7
7
|
*
|
|
8
8
|
* @copyright thednp © 2021
|
|
9
9
|
*
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @return
|
|
10
|
+
* @param cssm CSSMatrix instance
|
|
11
|
+
* @param v Tuple
|
|
12
|
+
* @return the resulting Tuple
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
let m = Translate(...v);
|
|
14
|
+
const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [number, number, number, number] => {
|
|
15
|
+
let m = CSSMatrix.Translate(...(v.slice(0, -1) as [number, number, number]));
|
|
16
16
|
|
|
17
|
-
[
|
|
18
|
-
m =
|
|
17
|
+
[, , , m.m44] = v;
|
|
18
|
+
m = cssm.multiply(m);
|
|
19
19
|
|
|
20
20
|
return [m.m41, m.m42, m.m43, m.m44];
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Returns the [x,y] projected coordinates for a given an [x,y] point
|
|
@@ -29,12 +29,12 @@ function translatePoint(M, v) {
|
|
|
29
29
|
* Details =>
|
|
30
30
|
* https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
|
|
31
31
|
*
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
34
|
-
* @param
|
|
35
|
-
* @returns
|
|
32
|
+
* @param m the transformation matrix
|
|
33
|
+
* @param point2D the initial [x,y] coordinates
|
|
34
|
+
* @param origin the [x,y,z] transform origin
|
|
35
|
+
* @returns the projected [x,y] coordinates
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
const projection2d = (m: CSSMatrix, point2D: [number, number], origin: [number, number, number]): [number, number] => {
|
|
38
38
|
const [originX, originY, originZ] = origin;
|
|
39
39
|
const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
|
|
40
40
|
|
|
@@ -47,4 +47,5 @@ export default function projection2d(m, point2D, origin) {
|
|
|
47
47
|
relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originX,
|
|
48
48
|
relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originY,
|
|
49
49
|
];
|
|
50
|
-
}
|
|
50
|
+
};
|
|
51
|
+
export default projection2d;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
|
|
3
|
+
*
|
|
4
|
+
* @param x1 curve start x
|
|
5
|
+
* @param y1 curve start y
|
|
6
|
+
* @param qx control point x
|
|
7
|
+
* @param qy control point y
|
|
8
|
+
* @param x2 curve end x
|
|
9
|
+
* @param y2 curve end y
|
|
10
|
+
* @returns the cubic-bezier segment
|
|
11
|
+
*/
|
|
12
|
+
const quadToCubic = (
|
|
13
|
+
x1: number,
|
|
14
|
+
y1: number,
|
|
15
|
+
qx: number,
|
|
16
|
+
qy: number,
|
|
17
|
+
x2: number,
|
|
18
|
+
y2: number,
|
|
19
|
+
): [number, number, number, number, number, number] => {
|
|
20
|
+
const r13 = 1 / 3;
|
|
21
|
+
const r23 = 2 / 3;
|
|
22
|
+
return [
|
|
23
|
+
r13 * x1 + r23 * qx, // cpx1
|
|
24
|
+
r13 * y1 + r23 * qy, // cpy1
|
|
25
|
+
r13 * x2 + r23 * qx, // cpx2
|
|
26
|
+
r13 * y2 + r23 * qy, // cpy2
|
|
27
|
+
x2,
|
|
28
|
+
y2, // x,y
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
export default quadToCubic;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CurveArray } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reverses all segments of a `pathArray`
|
|
5
|
+
* which consists of only C (cubic-bezier) path commands.
|
|
6
|
+
*
|
|
7
|
+
* @param path the source `pathArray`
|
|
8
|
+
* @returns the reversed `pathArray`
|
|
9
|
+
*/
|
|
10
|
+
const reverseCurve = (path: CurveArray): CurveArray => {
|
|
11
|
+
const rotatedCurve = path
|
|
12
|
+
.slice(1)
|
|
13
|
+
.map((x, i, curveOnly) =>
|
|
14
|
+
!i ? [...path[0].slice(1), ...x.slice(1)] : [...curveOnly[i - 1].slice(-2), ...x.slice(1)],
|
|
15
|
+
)
|
|
16
|
+
.map(x => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
|
|
17
|
+
.reverse();
|
|
18
|
+
|
|
19
|
+
return [['M', ...rotatedCurve[0].slice(0, 2)], ...rotatedCurve.map(x => ['C', ...x.slice(2)])] as CurveArray;
|
|
20
|
+
};
|
|
21
|
+
export default reverseCurve;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ASegment,
|
|
3
|
+
CSegment,
|
|
4
|
+
HSegment,
|
|
5
|
+
MSegment,
|
|
6
|
+
PathArray,
|
|
7
|
+
PathSegment,
|
|
8
|
+
QSegment,
|
|
9
|
+
SSegment,
|
|
10
|
+
TSegment,
|
|
11
|
+
VSegment,
|
|
12
|
+
} from 'src/types';
|
|
13
|
+
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
14
|
+
import normalizePath from './normalizePath';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Reverses all segments of a `pathArray` and returns a new `pathArray` instance.
|
|
18
|
+
*
|
|
19
|
+
* @param pathInput the source `pathArray`
|
|
20
|
+
* @returns the reversed `pathArray`
|
|
21
|
+
*/
|
|
22
|
+
const reversePath = (pathInput: PathArray): PathArray => {
|
|
23
|
+
const absolutePath = pathToAbsolute(pathInput);
|
|
24
|
+
const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
|
|
25
|
+
|
|
26
|
+
const reversedPath = normalizePath(absolutePath)
|
|
27
|
+
.map((segment, i) => {
|
|
28
|
+
const [x, y] = segment.slice(-2).map(Number);
|
|
29
|
+
return {
|
|
30
|
+
seg: absolutePath[i], // absolute
|
|
31
|
+
n: segment, // normalized
|
|
32
|
+
c: absolutePath[i][0], // pathCommand
|
|
33
|
+
x, // x
|
|
34
|
+
y, // y
|
|
35
|
+
};
|
|
36
|
+
})
|
|
37
|
+
.map((seg, i, path) => {
|
|
38
|
+
const segment = seg.seg;
|
|
39
|
+
const data = seg.n;
|
|
40
|
+
const prevSeg = i && path[i - 1];
|
|
41
|
+
const nextSeg = path[i + 1];
|
|
42
|
+
const pathCommand = seg.c;
|
|
43
|
+
const pLen = path.length;
|
|
44
|
+
const x = i ? path[i - 1].x : path[pLen - 1].x;
|
|
45
|
+
const y = i ? path[i - 1].y : path[pLen - 1].y;
|
|
46
|
+
let result = [];
|
|
47
|
+
|
|
48
|
+
switch (pathCommand) {
|
|
49
|
+
case 'M':
|
|
50
|
+
result = (isClosed ? ['Z'] : [pathCommand, x, y]) as PathSegment;
|
|
51
|
+
break;
|
|
52
|
+
case 'A':
|
|
53
|
+
result = [pathCommand, ...segment.slice(1, -3), segment[5] === 1 ? 0 : 1, x, y] as ASegment;
|
|
54
|
+
break;
|
|
55
|
+
case 'C':
|
|
56
|
+
if (nextSeg && nextSeg.c === 'S') {
|
|
57
|
+
result = ['S', segment[1], segment[2], x, y] as SSegment;
|
|
58
|
+
} else {
|
|
59
|
+
result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y] as CSegment;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case 'S':
|
|
63
|
+
if (prevSeg && 'CS'.includes(prevSeg.c) && (!nextSeg || nextSeg.c !== 'S')) {
|
|
64
|
+
result = ['C', data[3], data[4], data[1], data[2], x, y] as CSegment;
|
|
65
|
+
} else {
|
|
66
|
+
result = [pathCommand, data[1], data[2], x, y] as SSegment;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
case 'Q':
|
|
70
|
+
if (nextSeg && nextSeg.c === 'T') {
|
|
71
|
+
result = ['T', x, y] as TSegment;
|
|
72
|
+
} else {
|
|
73
|
+
result = [pathCommand, ...segment.slice(1, -2), x, y] as QSegment;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case 'T':
|
|
77
|
+
if (prevSeg && 'QT'.includes(prevSeg.c) && (!nextSeg || nextSeg.c !== 'T')) {
|
|
78
|
+
result = ['Q', data[1], data[2], x, y] as QSegment;
|
|
79
|
+
} else {
|
|
80
|
+
result = [pathCommand, x, y] as TSegment;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'Z':
|
|
84
|
+
result = ['M', x, y] as MSegment;
|
|
85
|
+
break;
|
|
86
|
+
case 'H':
|
|
87
|
+
result = [pathCommand, x] as HSegment;
|
|
88
|
+
break;
|
|
89
|
+
case 'V':
|
|
90
|
+
result = [pathCommand, y] as VSegment;
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
result = [pathCommand, ...segment.slice(1, -2), x, y] as PathSegment;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return (isClosed ? reversedPath.reverse() : [reversedPath[0], ...reversedPath.slice(1).reverse()]) as PathArray;
|
|
100
|
+
};
|
|
101
|
+
export default reversePath;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PathArray } from 'src/types';
|
|
2
|
+
import defaultOptions from '../options/options';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rounds the values of a `pathArray` instance to
|
|
6
|
+
* a specified amount of decimals and returns it.
|
|
7
|
+
*
|
|
8
|
+
* @param path the source `pathArray`
|
|
9
|
+
* @param roundOption the amount of decimals to round numbers to
|
|
10
|
+
* @returns the resulted `pathArray` with rounded values
|
|
11
|
+
*/
|
|
12
|
+
const roundPath = (path: PathArray, roundOption?: number | 'off'): PathArray => {
|
|
13
|
+
let { round } = defaultOptions;
|
|
14
|
+
if (roundOption === 'off' || round === 'off') return [...path];
|
|
15
|
+
// allow for ZERO decimals
|
|
16
|
+
round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
|
|
17
|
+
// to round values to the power
|
|
18
|
+
// the `round` value must be integer
|
|
19
|
+
const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
|
|
20
|
+
|
|
21
|
+
return path.map(pi => {
|
|
22
|
+
const values = pi
|
|
23
|
+
.slice(1)
|
|
24
|
+
.map(Number)
|
|
25
|
+
.map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
|
|
26
|
+
return [pi[0], ...values];
|
|
27
|
+
}) as PathArray;
|
|
28
|
+
};
|
|
29
|
+
export default roundPath;
|