svg-path-commander 2.1.0 → 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 +63 -7
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +314 -40
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +1085 -1032
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +8 -8
- package/src/convert/pathToAbsolute.ts +5 -88
- package/src/convert/pathToCurve.ts +22 -28
- package/src/convert/pathToRelative.ts +4 -78
- package/src/convert/pathToString.ts +40 -7
- package/src/index.ts +145 -58
- package/src/interface.ts +3 -2
- package/src/math/arcTools.ts +259 -80
- package/src/math/bezier.ts +58 -58
- package/src/math/cubicTools.ts +68 -25
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +42 -25
- package/src/math/midPoint.ts +3 -1
- package/src/math/polygonTools.ts +48 -0
- package/src/math/quadTools.ts +46 -25
- 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 +4 -5
- package/src/parser/pathParser.ts +1 -1
- package/src/process/absolutizeSegment.ts +63 -0
- package/src/process/arcToCubic.ts +2 -2
- package/src/process/iterate.ts +58 -0
- package/src/process/lineToCubic.ts +1 -1
- package/src/process/normalizePath.ts +17 -28
- package/src/process/normalizeSegment.ts +55 -18
- package/src/process/optimizePath.ts +40 -60
- package/src/process/projection2d.ts +4 -3
- package/src/process/relativizeSegment.ts +59 -0
- package/src/process/reverseCurve.ts +8 -5
- package/src/process/reversePath.ts +88 -75
- package/src/process/roundPath.ts +18 -14
- package/src/process/roundSegment.ts +9 -0
- package/src/process/segmentToCubic.ts +10 -8
- package/src/process/shortenSegment.ts +26 -34
- package/src/process/splitCubic.ts +10 -9
- package/src/process/splitPath.ts +38 -4
- package/src/process/transformPath.ts +75 -73
- package/src/types.ts +30 -0
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +69 -19
- package/src/util/getPointAtLength.ts +100 -4
- package/src/util/getPropertiesAtLength.ts +3 -4
- package/src/util/getPropertiesAtPoint.ts +5 -5
- package/src/util/getTotalLength.ts +56 -4
- package/test/class.test.ts +17 -14
- package/test/fixtures/shapes.js +26 -26
- package/test/fixtures/simpleShapes.js +18 -18
- package/test/static.test.ts +54 -28
- package/cypress.config.ts +0 -29
- package/src/math/polygonArea.ts +0 -27
- package/src/math/polygonLength.ts +0 -21
- package/src/process/fixArc.ts +0 -23
- package/src/process/replaceArc.ts +0 -52
- package/src/util/pathFactory.ts +0 -130
|
@@ -1,44 +1,33 @@
|
|
|
1
|
-
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
2
1
|
import normalizeSegment from './normalizeSegment';
|
|
3
|
-
import isNormalizedArray from '../util/isNormalizedArray';
|
|
4
|
-
import paramsParser from '../parser/paramsParser';
|
|
5
2
|
import type { NormalArray, PathArray } from '../types';
|
|
3
|
+
import iterate from './iterate';
|
|
4
|
+
import parsePathString from '../parser/parsePathString';
|
|
5
|
+
import paramsParser from '../parser/paramsParser';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Normalizes a `
|
|
8
|
+
* Normalizes a `pathArray` 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
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
|
-
return pathInput.slice(0) as NormalArray;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const path = pathToAbsolute(pathInput);
|
|
15
|
+
const normalizePath = (pathInput: string | PathArray) => {
|
|
16
|
+
const path = parsePathString(pathInput);
|
|
21
17
|
const params = { ...paramsParser };
|
|
22
|
-
const allPathCommands = [];
|
|
23
|
-
const ii = path.length;
|
|
24
|
-
let pathCommand = '';
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < ii; i += 1) {
|
|
27
|
-
[pathCommand] = path[i];
|
|
28
|
-
|
|
29
|
-
// Save current path command
|
|
30
|
-
allPathCommands[i] = pathCommand;
|
|
31
|
-
path[i] = normalizeSegment(path[i], params);
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
return iterate<NormalArray>(path, (seg, _, lastX, lastY) => {
|
|
20
|
+
params.x = lastX;
|
|
21
|
+
params.y = lastY;
|
|
22
|
+
const result = normalizeSegment(seg, params);
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
params.
|
|
38
|
-
params.
|
|
39
|
-
params.
|
|
40
|
-
|
|
24
|
+
const seglen = result.length;
|
|
25
|
+
params.x1 = +result[seglen - 2];
|
|
26
|
+
params.y1 = +result[seglen - 1];
|
|
27
|
+
params.x2 = +result[seglen - 4] || params.x1;
|
|
28
|
+
params.y2 = +result[seglen - 3] || params.y1;
|
|
41
29
|
|
|
42
|
-
|
|
30
|
+
return result;
|
|
31
|
+
});
|
|
43
32
|
};
|
|
44
33
|
export default normalizePath;
|
|
@@ -1,47 +1,84 @@
|
|
|
1
1
|
import type { ParserParams } from '../interface';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
NormalSegment,
|
|
4
|
+
PointTuple,
|
|
5
|
+
PathSegment,
|
|
6
|
+
QSegment,
|
|
7
|
+
CSegment,
|
|
8
|
+
LSegment,
|
|
9
|
+
MSegment,
|
|
10
|
+
HSegment,
|
|
11
|
+
VSegment,
|
|
12
|
+
ASegment,
|
|
13
|
+
PathCommand,
|
|
14
|
+
} from '../types';
|
|
3
15
|
|
|
4
16
|
/**
|
|
5
17
|
* Normalizes a single segment of a `pathArray` object.
|
|
6
18
|
*
|
|
7
19
|
* @param segment the segment object
|
|
8
|
-
* @param params the
|
|
20
|
+
* @param params the normalization parameters
|
|
9
21
|
* @returns the normalized segment
|
|
10
22
|
*/
|
|
11
|
-
const normalizeSegment = (segment: PathSegment, params: ParserParams)
|
|
23
|
+
const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
12
24
|
const [pathCommand] = segment;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
25
|
+
const absCommand = pathCommand.toUpperCase();
|
|
26
|
+
const isRelative = pathCommand !== absCommand;
|
|
27
|
+
const { x1: px1, y1: py1, x2: px2, y2: py2, x, y } = params;
|
|
28
|
+
const values = segment.slice(1) as number[];
|
|
29
|
+
let absValues = values.map((n, j) => n + (isRelative ? (j % 2 ? y : x) : 0));
|
|
16
30
|
|
|
17
|
-
if (!'TQ'.includes(
|
|
31
|
+
if (!'TQ'.includes(absCommand)) {
|
|
18
32
|
// optional but good to be cautious
|
|
19
33
|
params.qx = null;
|
|
20
34
|
params.qy = null;
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
// istanbul ignore else @preserve
|
|
38
|
+
if (absCommand === 'A') {
|
|
39
|
+
absValues = values.slice(0, -2).concat(values[5] + (isRelative ? x : 0), values[6] + (isRelative ? y : 0));
|
|
40
|
+
|
|
41
|
+
return ['A' as PathCommand | number].concat(absValues) as ASegment;
|
|
42
|
+
} else if (absCommand === 'H') {
|
|
43
|
+
return ['L', (segment as HSegment)[1] + (isRelative ? x : 0), py1] as LSegment;
|
|
44
|
+
} else if (absCommand === 'V') {
|
|
45
|
+
return ['L', px1, (segment as VSegment)[1] + (isRelative ? y : 0)] as LSegment;
|
|
46
|
+
} else if (absCommand === 'L') {
|
|
47
|
+
return [
|
|
48
|
+
'L',
|
|
49
|
+
(segment as LSegment)[1] + (isRelative ? x : 0),
|
|
50
|
+
(segment as LSegment)[2] + (isRelative ? y : 0),
|
|
51
|
+
] as LSegment;
|
|
52
|
+
} else if (absCommand === 'M') {
|
|
53
|
+
return [
|
|
54
|
+
'M',
|
|
55
|
+
(segment as MSegment)[1] + (isRelative ? x : 0),
|
|
56
|
+
(segment as MSegment)[2] + (isRelative ? y : 0),
|
|
57
|
+
] as MSegment;
|
|
58
|
+
} else if (absCommand === 'C') {
|
|
59
|
+
return ['C' as PathCommand | number].concat(absValues) as CSegment;
|
|
60
|
+
} else if (absCommand === 'S') {
|
|
28
61
|
const x1 = px1 * 2 - px2;
|
|
29
62
|
const y1 = py1 * 2 - py2;
|
|
30
63
|
params.x1 = x1;
|
|
31
64
|
params.y1 = y1;
|
|
32
|
-
|
|
33
|
-
} else if (
|
|
65
|
+
return ['C', x1, y1].concat(absValues) as CSegment;
|
|
66
|
+
} else if (absCommand === 'T') {
|
|
34
67
|
const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
|
|
35
68
|
const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
|
|
36
69
|
params.qx = qx;
|
|
37
70
|
params.qy = qy;
|
|
38
|
-
|
|
39
|
-
} else if (
|
|
40
|
-
const [nqx, nqy] =
|
|
71
|
+
return ['Q', qx, qy].concat(absValues) as QSegment;
|
|
72
|
+
} else if (absCommand === 'Q') {
|
|
73
|
+
const [nqx, nqy] = absValues as PointTuple;
|
|
41
74
|
params.qx = nqx;
|
|
42
75
|
params.qy = nqy;
|
|
76
|
+
return ['Q' as PathCommand | number].concat(absValues) as QSegment;
|
|
77
|
+
} else if (absCommand === 'Z') {
|
|
78
|
+
return ['Z'] as NormalSegment;
|
|
43
79
|
}
|
|
44
80
|
|
|
45
|
-
|
|
81
|
+
// istanbul ignore next @preserve
|
|
82
|
+
return segment as NormalSegment;
|
|
46
83
|
};
|
|
47
84
|
export default normalizeSegment;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import roundPath from './roundPath';
|
|
2
1
|
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
3
|
-
import pathToRelative from '../convert/pathToRelative';
|
|
4
2
|
import shortenSegment from './shortenSegment';
|
|
5
3
|
import paramsParser from '../parser/paramsParser';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import type { AbsoluteSegment, PathArray, PathCommand } from '../types';
|
|
5
|
+
import iterate from './iterate';
|
|
6
|
+
import normalizeSegment from './normalizeSegment';
|
|
7
|
+
import relativizeSegment from './relativizeSegment';
|
|
8
|
+
import roundSegment from './roundSegment';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Optimizes a `pathArray` object:
|
|
@@ -12,72 +13,51 @@ import type { PathSegment, HSegment, PathArray, VSegment, PathCommand, AbsoluteS
|
|
|
12
13
|
* * select shortest segments from absolute and relative `pathArray`s
|
|
13
14
|
*
|
|
14
15
|
* @param pathInput a string or `pathArray`
|
|
15
|
-
* @param
|
|
16
|
+
* @param roundOption the amount of decimals to round values to
|
|
16
17
|
* @returns the optimized `pathArray`
|
|
17
18
|
*/
|
|
18
|
-
const optimizePath = (pathInput: PathArray,
|
|
19
|
+
const optimizePath = (pathInput: PathArray, roundOption: number) => {
|
|
19
20
|
const path = pathToAbsolute(pathInput);
|
|
20
|
-
|
|
21
|
-
const
|
|
21
|
+
// allow for ZERO decimals or use an aggressive value of 2
|
|
22
|
+
const round =
|
|
23
|
+
typeof roundOption === 'number' && roundOption >= 0 ? roundOption : /* istanbul ignore next @preserve */ 2;
|
|
24
|
+
// this utility overrides the iterator params
|
|
25
|
+
const optimParams = { ...paramsParser };
|
|
26
|
+
|
|
22
27
|
const allPathCommands = [] as PathCommand[];
|
|
23
|
-
|
|
24
|
-
let
|
|
25
|
-
let prevCommand = '' as PathCommand;
|
|
26
|
-
let x = 0;
|
|
27
|
-
let y = 0;
|
|
28
|
-
let mx = 0;
|
|
29
|
-
let my = 0;
|
|
28
|
+
let pathCommand = 'M' as PathCommand;
|
|
29
|
+
let prevCommand = 'Z' as PathCommand;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
return iterate(path, (seg, i, lastX, lastY) => {
|
|
32
|
+
optimParams.x = lastX;
|
|
33
|
+
optimParams.y = lastY;
|
|
34
|
+
// const absoluteSegment = absolutizeSegment(seg, optimParams);
|
|
35
|
+
const normalizedSegment = normalizeSegment(seg, optimParams);
|
|
36
|
+
let result = seg;
|
|
37
|
+
[pathCommand] = seg;
|
|
33
38
|
|
|
34
39
|
// Save current path command
|
|
35
40
|
allPathCommands[i] = pathCommand;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
params.x2 = +segment[seglen - 4] || params.x1;
|
|
47
|
-
params.y2 = +segment[seglen - 3] || params.y1;
|
|
48
|
-
|
|
49
|
-
// update x, y params
|
|
50
|
-
switch (pathCommand) {
|
|
51
|
-
case 'Z':
|
|
52
|
-
x = mx;
|
|
53
|
-
y = my;
|
|
54
|
-
break;
|
|
55
|
-
case 'H':
|
|
56
|
-
[, x] = segment as HSegment;
|
|
57
|
-
break;
|
|
58
|
-
case 'V':
|
|
59
|
-
[, y] = segment as VSegment;
|
|
60
|
-
break;
|
|
61
|
-
default:
|
|
62
|
-
[x, y] = segment.slice(-2).map(Number);
|
|
63
|
-
|
|
64
|
-
if (pathCommand === 'M') {
|
|
65
|
-
mx = x;
|
|
66
|
-
my = y;
|
|
67
|
-
}
|
|
41
|
+
if (i) {
|
|
42
|
+
// Get previous path command for `shortenSegment`
|
|
43
|
+
prevCommand = allPathCommands[i - 1];
|
|
44
|
+
const shortSegment = shortenSegment(seg as AbsoluteSegment, normalizedSegment, optimParams, prevCommand);
|
|
45
|
+
const absSegment = roundSegment(shortSegment, round);
|
|
46
|
+
const absString = absSegment.join('');
|
|
47
|
+
const relativeSegment = relativizeSegment(shortSegment, i, lastX, lastY);
|
|
48
|
+
const relSegment = roundSegment(relativeSegment, round);
|
|
49
|
+
const relString = relSegment.join('');
|
|
50
|
+
result = absString.length < relString.length ? absSegment : relSegment;
|
|
68
51
|
}
|
|
69
|
-
params.x = x;
|
|
70
|
-
params.y = y;
|
|
71
|
-
}
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
const seglen = normalizedSegment.length;
|
|
54
|
+
optimParams.x1 = +normalizedSegment[seglen - 2];
|
|
55
|
+
optimParams.y1 = +normalizedSegment[seglen - 1];
|
|
56
|
+
optimParams.x2 = +normalizedSegment[seglen - 4] || optimParams.x1;
|
|
57
|
+
optimParams.y2 = +normalizedSegment[seglen - 3] || optimParams.y1;
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return a.join('').length < relativePath[i].join('').length ? a : relativePath[i];
|
|
79
|
-
}
|
|
80
|
-
return a;
|
|
81
|
-
}) as PathArray;
|
|
59
|
+
return result;
|
|
60
|
+
});
|
|
82
61
|
};
|
|
62
|
+
|
|
83
63
|
export default optimizePath;
|
|
@@ -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
|
|
@@ -12,7 +13,7 @@ import CSSMatrix from '@thednp/dommatrix';
|
|
|
12
13
|
* @return the resulting Tuple
|
|
13
14
|
*/
|
|
14
15
|
const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [number, number, number, number] => {
|
|
15
|
-
let m = CSSMatrix.Translate(
|
|
16
|
+
let m = CSSMatrix.Translate(v[0], v[1], v[2]);
|
|
16
17
|
|
|
17
18
|
[, , , m.m44] = v;
|
|
18
19
|
m = cssm.multiply(m);
|
|
@@ -34,9 +35,9 @@ 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
|
-
const [x, y, z] = translatePoint(m, [
|
|
40
|
+
const [x, y, z] = translatePoint(m, [point2D[0], point2D[1], 0, 1]);
|
|
40
41
|
|
|
41
42
|
const relativePositionX = x - originX;
|
|
42
43
|
const relativePositionY = y - originY;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RelativeSegment,
|
|
3
|
+
RelativeCommand,
|
|
4
|
+
PathSegment,
|
|
5
|
+
aSegment,
|
|
6
|
+
vSegment,
|
|
7
|
+
hSegment,
|
|
8
|
+
qSegment,
|
|
9
|
+
tSegment,
|
|
10
|
+
sSegment,
|
|
11
|
+
cSegment,
|
|
12
|
+
MSegment,
|
|
13
|
+
lSegment,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns a relative segment of a `PathArray` object.
|
|
18
|
+
*
|
|
19
|
+
* @param segment the segment object
|
|
20
|
+
* @param index the segment index
|
|
21
|
+
* @param lastX the last known X value
|
|
22
|
+
* @param lastY the last known Y value
|
|
23
|
+
* @returns the relative segment
|
|
24
|
+
*/
|
|
25
|
+
const relativizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
|
|
26
|
+
const [pathCommand] = segment;
|
|
27
|
+
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
28
|
+
const isRelative = pathCommand === relCommand;
|
|
29
|
+
|
|
30
|
+
/* istanbul ignore else @preserve */
|
|
31
|
+
if (index === 0 || isRelative) return segment as MSegment | RelativeSegment;
|
|
32
|
+
|
|
33
|
+
if (relCommand === 'a') {
|
|
34
|
+
return [
|
|
35
|
+
relCommand,
|
|
36
|
+
segment[1],
|
|
37
|
+
segment[2],
|
|
38
|
+
segment[3],
|
|
39
|
+
segment[4],
|
|
40
|
+
segment[5],
|
|
41
|
+
(segment as aSegment)[6] - lastX,
|
|
42
|
+
(segment as aSegment)[7] - lastY,
|
|
43
|
+
] as aSegment;
|
|
44
|
+
} else if (relCommand === 'v') {
|
|
45
|
+
return [relCommand, (segment as vSegment)[1] - lastY] as vSegment;
|
|
46
|
+
} else if (relCommand === 'h') {
|
|
47
|
+
return [relCommand, (segment as hSegment)[1] - lastX] as hSegment;
|
|
48
|
+
} else if (relCommand === 'l') {
|
|
49
|
+
return [relCommand, (segment as lSegment)[1] - lastX, (segment as lSegment)[2] - lastY] as lSegment;
|
|
50
|
+
} else {
|
|
51
|
+
// use brakets for `eslint: no-case-declaration`
|
|
52
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
53
|
+
const relValues = (segment.slice(1) as number[]).map((n, j) => n - (j % 2 ? lastY : lastX));
|
|
54
|
+
// for c, s, q, t
|
|
55
|
+
return [relCommand as RelativeCommand | number].concat(relValues) as qSegment | tSegment | sSegment | cSegment;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default relativizeSegment;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CurveArray } from '../types';
|
|
1
|
+
import type { CSegment, CurveArray, MSegment, PathCommand } from '../types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Reverses all segments of a `pathArray`
|
|
@@ -7,15 +7,18 @@ import type { CurveArray } from '../types';
|
|
|
7
7
|
* @param path the source `pathArray`
|
|
8
8
|
* @returns the reversed `pathArray`
|
|
9
9
|
*/
|
|
10
|
-
const reverseCurve = (path: CurveArray)
|
|
10
|
+
const reverseCurve = (path: CurveArray) => {
|
|
11
11
|
const rotatedCurve = path
|
|
12
12
|
.slice(1)
|
|
13
13
|
.map((x, i, curveOnly) =>
|
|
14
|
-
!i ?
|
|
14
|
+
!i ? path[0].slice(1).concat(x.slice(1) as number[]) : curveOnly[i - 1].slice(-2).concat(x.slice(1)),
|
|
15
15
|
)
|
|
16
16
|
.map(x => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
|
|
17
|
-
.reverse();
|
|
17
|
+
.reverse() as (MSegment | CSegment)[];
|
|
18
18
|
|
|
19
|
-
return [['M'
|
|
19
|
+
return [['M' as PathCommand | number].concat(rotatedCurve[0].slice(0, 2))].concat(
|
|
20
|
+
rotatedCurve.map(x => ['C' as PathCommand | number].concat(x.slice(2))),
|
|
21
|
+
) as CurveArray;
|
|
20
22
|
};
|
|
23
|
+
|
|
21
24
|
export default reverseCurve;
|
|
@@ -5,97 +5,110 @@ import type {
|
|
|
5
5
|
MSegment,
|
|
6
6
|
PathArray,
|
|
7
7
|
PathSegment,
|
|
8
|
+
PointTuple,
|
|
8
9
|
QSegment,
|
|
9
10
|
SSegment,
|
|
10
11
|
TSegment,
|
|
11
12
|
VSegment,
|
|
12
|
-
} from '
|
|
13
|
+
} from '../types';
|
|
13
14
|
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
14
15
|
import normalizePath from './normalizePath';
|
|
16
|
+
import iterate from './iterate';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
* Reverses all segments of a `pathArray` and returns a new `pathArray` instance
|
|
19
|
+
* Reverses all segments of a `pathArray` and returns a new `pathArray` instance
|
|
20
|
+
* with absolute values.
|
|
18
21
|
*
|
|
19
22
|
* @param pathInput the source `pathArray`
|
|
20
23
|
* @returns the reversed `pathArray`
|
|
21
24
|
*/
|
|
22
|
-
const reversePath = (pathInput: PathArray)
|
|
25
|
+
const reversePath = (pathInput: PathArray) => {
|
|
23
26
|
const absolutePath = pathToAbsolute(pathInput);
|
|
24
|
-
const
|
|
27
|
+
const normalizedPath = normalizePath(absolutePath);
|
|
28
|
+
const pLen = absolutePath.length;
|
|
29
|
+
const isClosed = absolutePath[pLen - 1][0] === 'Z';
|
|
25
30
|
|
|
26
|
-
const reversedPath =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 = [];
|
|
31
|
+
const reversedPath = iterate(absolutePath, (segment, i) => {
|
|
32
|
+
const normalizedSegment = normalizedPath[i];
|
|
33
|
+
const prevSeg = i && absolutePath[i - 1];
|
|
34
|
+
const prevCommand = prevSeg && prevSeg[0];
|
|
35
|
+
const nextSeg = absolutePath[i + 1];
|
|
36
|
+
const nextCommand = nextSeg && nextSeg[0];
|
|
37
|
+
const [pathCommand] = segment;
|
|
38
|
+
const [x, y] = normalizedPath[i ? i - 1 : pLen - 1].slice(-2) as PointTuple;
|
|
39
|
+
let result = segment;
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
result = [pathCommand, y] as
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
41
|
+
switch (pathCommand) {
|
|
42
|
+
case 'M':
|
|
43
|
+
result = (isClosed ? ['Z'] : [pathCommand, x, y]) as PathSegment;
|
|
44
|
+
break;
|
|
45
|
+
case 'A':
|
|
46
|
+
result = [
|
|
47
|
+
pathCommand,
|
|
48
|
+
segment[1],
|
|
49
|
+
segment[2],
|
|
50
|
+
segment[3],
|
|
51
|
+
segment[4],
|
|
52
|
+
segment[5] === 1 ? 0 : 1,
|
|
53
|
+
x,
|
|
54
|
+
y,
|
|
55
|
+
] as ASegment;
|
|
56
|
+
break;
|
|
57
|
+
case 'C':
|
|
58
|
+
if (nextSeg && nextCommand === 'S') {
|
|
59
|
+
result = ['S', segment[1], segment[2], x, y] as SSegment;
|
|
60
|
+
} else {
|
|
61
|
+
result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y] as CSegment;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
case 'S':
|
|
65
|
+
if (prevCommand && 'CS'.includes(prevCommand) && (!nextSeg || nextCommand !== 'S')) {
|
|
66
|
+
result = [
|
|
67
|
+
'C',
|
|
68
|
+
normalizedSegment[3],
|
|
69
|
+
normalizedSegment[4],
|
|
70
|
+
normalizedSegment[1],
|
|
71
|
+
normalizedSegment[2],
|
|
72
|
+
x,
|
|
73
|
+
y,
|
|
74
|
+
] as CSegment;
|
|
75
|
+
} else {
|
|
76
|
+
result = [pathCommand, normalizedSegment[1], normalizedSegment[2], x, y] as SSegment;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case 'Q':
|
|
80
|
+
if (nextSeg && nextCommand === 'T') {
|
|
81
|
+
result = ['T', x, y] as TSegment;
|
|
82
|
+
} else {
|
|
83
|
+
result = [pathCommand, segment[1], segment[2], x, y] as QSegment;
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case 'T':
|
|
87
|
+
if (prevCommand && 'QT'.includes(prevCommand) && (!nextSeg || nextCommand !== 'T')) {
|
|
88
|
+
result = ['Q', normalizedSegment[1], normalizedSegment[2], x, y] as QSegment;
|
|
89
|
+
} else {
|
|
90
|
+
result = [pathCommand, x, y] as TSegment;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case 'Z':
|
|
94
|
+
result = ['M', x, y] as MSegment;
|
|
95
|
+
break;
|
|
96
|
+
case 'H':
|
|
97
|
+
result = [pathCommand, x] as HSegment;
|
|
98
|
+
break;
|
|
99
|
+
case 'V':
|
|
100
|
+
result = [pathCommand, y] as VSegment;
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
result = [pathCommand as typeof pathCommand | number].concat(segment.slice(1, -2), x, y) as PathSegment;
|
|
104
|
+
}
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
return result;
|
|
107
|
+
});
|
|
98
108
|
|
|
99
|
-
return (
|
|
109
|
+
return (
|
|
110
|
+
isClosed ? reversedPath.reverse() : [reversedPath[0] as PathSegment].concat(reversedPath.slice(1).reverse())
|
|
111
|
+
) as PathArray;
|
|
100
112
|
};
|
|
113
|
+
|
|
101
114
|
export default reversePath;
|
package/src/process/roundPath.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { PathArray } from '
|
|
1
|
+
import type { PathArray } from '../types';
|
|
2
2
|
import defaultOptions from '../options/options';
|
|
3
|
+
import iterate from './iterate';
|
|
4
|
+
import roundSegment from './roundSegment';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Rounds the values of a `pathArray` instance to
|
|
@@ -9,21 +11,23 @@ import defaultOptions from '../options/options';
|
|
|
9
11
|
* @param roundOption the amount of decimals to round numbers to
|
|
10
12
|
* @returns the resulted `pathArray` with rounded values
|
|
11
13
|
*/
|
|
12
|
-
const roundPath = (path: PathArray, roundOption?: number | 'off')
|
|
14
|
+
const roundPath = (path: PathArray, roundOption?: number | 'off') => {
|
|
13
15
|
let { round } = defaultOptions;
|
|
14
|
-
if (roundOption === 'off' || round === 'off') return [...path];
|
|
15
16
|
// allow for ZERO decimals
|
|
16
|
-
round =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
round =
|
|
18
|
+
roundOption === 'off'
|
|
19
|
+
? roundOption
|
|
20
|
+
: typeof roundOption === 'number' && roundOption >= 0
|
|
21
|
+
? roundOption
|
|
22
|
+
: typeof round === 'number' && round >= 0
|
|
23
|
+
? round
|
|
24
|
+
: /* istanbul ignore next @preserve */ 'off';
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}) as PathArray;
|
|
26
|
+
/* istanbul ignore else @preserve */
|
|
27
|
+
if (round === 'off') return path.slice(0) as PathArray;
|
|
28
|
+
|
|
29
|
+
return iterate<typeof path>(path, segment => {
|
|
30
|
+
return roundSegment(segment, round);
|
|
31
|
+
});
|
|
28
32
|
};
|
|
29
33
|
export default roundPath;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PathCommand, PathSegment } from '../types';
|
|
2
|
+
import roundTo from '../math/roundTo';
|
|
3
|
+
|
|
4
|
+
const roundSegment = <T extends PathSegment>(segment: T, roundOption: number) => {
|
|
5
|
+
const values = (segment.slice(1) as number[]).map(n => roundTo(n, roundOption));
|
|
6
|
+
return [segment[0] as PathCommand | number].concat(values) as T;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default roundSegment;
|