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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ParserParams } from '../interface';
|
|
2
1
|
import type {
|
|
3
2
|
RelativeSegment,
|
|
4
3
|
RelativeCommand,
|
|
@@ -10,52 +9,51 @@ import type {
|
|
|
10
9
|
tSegment,
|
|
11
10
|
sSegment,
|
|
12
11
|
cSegment,
|
|
12
|
+
MSegment,
|
|
13
|
+
lSegment,
|
|
13
14
|
} from '../types';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Returns a relative segment of a `PathArray` object.
|
|
17
18
|
*
|
|
18
19
|
* @param segment the segment object
|
|
19
|
-
* @param params the coordinates of the previous segment
|
|
20
20
|
* @param index the segment index
|
|
21
|
-
* @
|
|
21
|
+
* @param lastX the last known X value
|
|
22
|
+
* @param lastY the last known Y value
|
|
23
|
+
* @returns the relative segment
|
|
22
24
|
*/
|
|
23
|
-
const relativizeSegment = (segment: PathSegment,
|
|
25
|
+
const relativizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
|
|
24
26
|
const [pathCommand] = segment;
|
|
25
|
-
const { x, y } = params;
|
|
26
|
-
const values = segment.slice(1).map(Number);
|
|
27
27
|
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
28
|
-
|
|
29
|
-
if (index === 0 && pathCommand === 'M') {
|
|
30
|
-
return segment;
|
|
31
|
-
}
|
|
28
|
+
const isRelative = pathCommand === relCommand;
|
|
32
29
|
|
|
33
30
|
/* istanbul ignore else @preserve */
|
|
34
|
-
if (
|
|
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
|
-
}
|
|
31
|
+
if (index === 0 || isRelative) return segment as MSegment | RelativeSegment;
|
|
58
32
|
|
|
59
|
-
|
|
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
|
+
}
|
|
60
57
|
};
|
|
58
|
+
|
|
61
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,6 +5,7 @@ import type {
|
|
|
5
5
|
MSegment,
|
|
6
6
|
PathArray,
|
|
7
7
|
PathSegment,
|
|
8
|
+
PointTuple,
|
|
8
9
|
QSegment,
|
|
9
10
|
SSegment,
|
|
10
11
|
TSegment,
|
|
@@ -12,90 +13,102 @@ import type {
|
|
|
12
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,6 +1,7 @@
|
|
|
1
|
-
import type { PathArray
|
|
1
|
+
import type { PathArray } from '../types';
|
|
2
2
|
import defaultOptions from '../options/options';
|
|
3
3
|
import iterate from './iterate';
|
|
4
|
+
import roundSegment from './roundSegment';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Rounds the values of a `pathArray` instance to
|
|
@@ -12,16 +13,21 @@ import iterate from './iterate';
|
|
|
12
13
|
*/
|
|
13
14
|
const roundPath = (path: PathArray, roundOption?: number | 'off') => {
|
|
14
15
|
let { round } = defaultOptions;
|
|
15
|
-
if (roundOption === 'off' || round === 'off') return path.slice(0) as PathArray;
|
|
16
16
|
// allow for ZERO decimals
|
|
17
|
-
round =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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';
|
|
25
|
+
|
|
26
|
+
/* istanbul ignore else @preserve */
|
|
27
|
+
if (round === 'off') return path.slice(0) as PathArray;
|
|
21
28
|
|
|
22
29
|
return iterate<typeof path>(path, segment => {
|
|
23
|
-
|
|
24
|
-
return [segment[0], ...values] as PathSegment;
|
|
30
|
+
return roundSegment(segment, round);
|
|
25
31
|
});
|
|
26
32
|
};
|
|
27
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;
|
|
@@ -15,7 +15,7 @@ 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;
|
|
18
|
-
let args;
|
|
18
|
+
// let args;
|
|
19
19
|
const { x1: px1, y1: py1, x: px, y: py } = params;
|
|
20
20
|
|
|
21
21
|
if (!'TQ'.includes(pathCommand)) {
|
|
@@ -28,17 +28,19 @@ const segmentToCubic = (segment: PathSegment, params: ParserParams) => {
|
|
|
28
28
|
params.y = y;
|
|
29
29
|
return segment;
|
|
30
30
|
} else if (pathCommand === 'A') {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
return ['C' as string | number].concat(
|
|
32
|
+
arcToCubic(px1, py1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]),
|
|
33
|
+
) as CSegment;
|
|
33
34
|
} else if (pathCommand === 'Q') {
|
|
34
35
|
params.qx = x;
|
|
35
36
|
params.qy = y;
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
return ['C' as string | number].concat(
|
|
38
|
+
quadToCubic(px1, py1, values[0], values[1], values[2], values[3]),
|
|
39
|
+
) as CSegment;
|
|
38
40
|
} else if (pathCommand === 'L') {
|
|
39
|
-
return ['C'
|
|
41
|
+
return ['C' as string | number].concat(lineToCubic(px1, py1, x, y)) as CSegment;
|
|
40
42
|
} else if (pathCommand === 'Z') {
|
|
41
|
-
return ['C'
|
|
43
|
+
return ['C' as string | number].concat(lineToCubic(px1, py1, px, py)) as CSegment;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
return segment as MSegment | CSegment;
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
+
import defaultOptions from '../options/options';
|
|
1
2
|
import type { ParserParams } from '../interface';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
HSegment,
|
|
5
|
-
NormalSegment,
|
|
6
|
-
PathCommand,
|
|
7
|
-
ShortSegment,
|
|
8
|
-
SSegment,
|
|
9
|
-
TSegment,
|
|
10
|
-
VSegment,
|
|
11
|
-
ZSegment,
|
|
12
|
-
} from '../types';
|
|
3
|
+
import roundTo from '../math/roundTo';
|
|
4
|
+
import type { AbsoluteSegment, NormalSegment, PathCommand, ShortSegment, SSegment, TSegment } from '../types';
|
|
13
5
|
|
|
14
6
|
/**
|
|
15
7
|
* Shorten a single segment of a `pathArray` object.
|
|
@@ -27,12 +19,12 @@ const shortenSegment = (
|
|
|
27
19
|
prevCommand: PathCommand,
|
|
28
20
|
): ShortSegment => {
|
|
29
21
|
const [pathCommand] = segment;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
22
|
+
const { round: defaultRound } = defaultOptions;
|
|
23
|
+
const round = typeof defaultRound === 'number' ? defaultRound : /* istanbul ignore next */ 4;
|
|
32
24
|
const normalValues = normalSegment.slice(1) as number[];
|
|
33
|
-
const { x1
|
|
34
|
-
|
|
35
|
-
const
|
|
25
|
+
const { x1, y1, x2, y2, x, y } = params;
|
|
26
|
+
const [nx, ny] = normalValues.slice(-2);
|
|
27
|
+
const result = segment;
|
|
36
28
|
|
|
37
29
|
if (!'TQ'.includes(pathCommand)) {
|
|
38
30
|
// optional but good to be cautious
|
|
@@ -40,26 +32,24 @@ const shortenSegment = (
|
|
|
40
32
|
params.qy = null;
|
|
41
33
|
}
|
|
42
34
|
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
} else if (round4(py) === round4(y)) {
|
|
49
|
-
result = ['H', x];
|
|
35
|
+
if (pathCommand === 'L') {
|
|
36
|
+
if (roundTo(x, round) === roundTo(nx, round)) {
|
|
37
|
+
return ['V', ny];
|
|
38
|
+
} else if (roundTo(y, round) === roundTo(ny, round)) {
|
|
39
|
+
return ['H', nx];
|
|
50
40
|
}
|
|
51
41
|
} else if (pathCommand === 'C') {
|
|
52
|
-
const [
|
|
42
|
+
const [nx1, ny1] = normalValues;
|
|
43
|
+
params.x1 = nx1;
|
|
44
|
+
params.y1 = ny1;
|
|
53
45
|
|
|
54
46
|
if (
|
|
55
47
|
'CS'.includes(prevCommand) &&
|
|
56
|
-
((
|
|
57
|
-
(
|
|
48
|
+
((roundTo(nx1, round) === roundTo(x1 * 2 - x2, round) && roundTo(ny1, round) === roundTo(y1 * 2 - y2, round)) ||
|
|
49
|
+
(roundTo(x1, round) === roundTo(x2 * 2 - x, round) && roundTo(y1, round) === roundTo(y2 * 2 - y, round)))
|
|
58
50
|
) {
|
|
59
|
-
|
|
51
|
+
return ['S', normalValues[2], normalValues[3], normalValues[4], normalValues[5]] as SSegment;
|
|
60
52
|
}
|
|
61
|
-
params.x1 = x1;
|
|
62
|
-
params.y1 = y1;
|
|
63
53
|
} else if (pathCommand === 'Q') {
|
|
64
54
|
const [qx, qy] = normalValues;
|
|
65
55
|
params.qx = qx;
|
|
@@ -67,13 +57,15 @@ const shortenSegment = (
|
|
|
67
57
|
|
|
68
58
|
if (
|
|
69
59
|
'QT'.includes(prevCommand) &&
|
|
70
|
-
(
|
|
71
|
-
|
|
60
|
+
roundTo(qx, round) === roundTo(x1 * 2 - x2, round) &&
|
|
61
|
+
roundTo(qy, round) === roundTo(y1 * 2 - y2, round)
|
|
72
62
|
) {
|
|
73
|
-
|
|
63
|
+
return ['T', normalValues[2], normalValues[3]] as TSegment;
|
|
74
64
|
}
|
|
75
65
|
}
|
|
76
66
|
|
|
67
|
+
// ['V', 'H', 'S', 'T', 'Z'].includes(pathCommand)
|
|
77
68
|
return result as ShortSegment;
|
|
78
69
|
};
|
|
70
|
+
|
|
79
71
|
export default shortenSegment;
|
|
@@ -22,8 +22,8 @@ const splitCubic = (pts: number[], ratio = 0.5): [CubicSegment, CubicSegment] =>
|
|
|
22
22
|
const p9 = midPoint(p7, p8, t);
|
|
23
23
|
|
|
24
24
|
return [
|
|
25
|
-
['C',
|
|
26
|
-
['C',
|
|
25
|
+
['C', p4[0], p4[1], p7[0], p7[1], p9[0], p9[1]],
|
|
26
|
+
['C', p8[0], p8[1], p6[0], p6[1], p3[0], p3[1]],
|
|
27
27
|
];
|
|
28
28
|
};
|
|
29
29
|
export default splitCubic;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import getSVGMatrix from './getSVGMatrix';
|
|
2
2
|
import projection2d from './projection2d';
|
|
3
3
|
import defaultOptions from '../options/options';
|
|
4
|
-
import type { AbsoluteArray, CSegment,
|
|
4
|
+
import type { AbsoluteArray, AbsoluteSegment, CSegment, LSegment, PathArray, TransformObjectValues } from '../types';
|
|
5
5
|
import type { TransformObject } from '../interface';
|
|
6
6
|
import iterate from './iterate';
|
|
7
7
|
import parsePathString from '../parser/parsePathString';
|
|
8
|
-
import absolutizeSegment from './absolutizeSegment';
|
|
9
8
|
import segmentToCubic from './segmentToCubic';
|
|
10
9
|
import normalizeSegment from './normalizeSegment';
|
|
11
10
|
import paramsParser from '../parser/paramsParser';
|
|
11
|
+
import absolutizeSegment from './absolutizeSegment';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Apply a 2D / 3D transformation to a `pathArray` instance.
|
|
@@ -21,16 +21,15 @@ import paramsParser from '../parser/paramsParser';
|
|
|
21
21
|
* @returns the resulted `pathArray`
|
|
22
22
|
*/
|
|
23
23
|
const transformPath = (pathInput: PathArray | string, transform?: Partial<TransformObject>) => {
|
|
24
|
+
// last x and y transformed values
|
|
24
25
|
let x = 0;
|
|
25
26
|
let y = 0;
|
|
26
|
-
|
|
27
|
-
let my = 0;
|
|
27
|
+
// new x and y transformed
|
|
28
28
|
let lx = 0;
|
|
29
29
|
let ly = 0;
|
|
30
|
+
// segment params iteration index and length
|
|
30
31
|
let j = 0;
|
|
31
32
|
let jj = 0;
|
|
32
|
-
let nx = 0;
|
|
33
|
-
let ny = 0;
|
|
34
33
|
let pathCommand = 'M';
|
|
35
34
|
// transform uses it's own set of params
|
|
36
35
|
const transformParams = { ...paramsParser };
|
|
@@ -38,7 +37,7 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
|
|
|
38
37
|
const transformProps = transform && Object.keys(transform);
|
|
39
38
|
|
|
40
39
|
// when used as a static method, invalidate somehow
|
|
41
|
-
if (!transform || (transformProps && !transformProps.length)) return path;
|
|
40
|
+
if (!transform || (transformProps && !transformProps.length)) return path.slice(0) as typeof path;
|
|
42
41
|
|
|
43
42
|
// transform origin is extremely important
|
|
44
43
|
if (!transform.origin) {
|
|
@@ -47,29 +46,37 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
|
|
|
47
46
|
const origin = transform.origin as [number, number, number];
|
|
48
47
|
const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
|
|
49
48
|
|
|
50
|
-
if (matrixInstance.isIdentity) return path;
|
|
49
|
+
if (matrixInstance.isIdentity) return path.slice(0) as typeof path;
|
|
51
50
|
|
|
52
|
-
return iterate<AbsoluteArray>(path, (seg,
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
return iterate<AbsoluteArray>(path, (seg, index, lastX, lastY) => {
|
|
52
|
+
transformParams.x = lastX;
|
|
53
|
+
transformParams.y = lastY;
|
|
54
|
+
[pathCommand] = seg;
|
|
55
|
+
const absCommand = pathCommand.toUpperCase();
|
|
56
|
+
const isRelative = absCommand !== pathCommand;
|
|
57
|
+
const absoluteSegment = isRelative
|
|
58
|
+
? absolutizeSegment(seg, index, lastX, lastY)
|
|
59
|
+
: (seg.slice(0) as AbsoluteSegment);
|
|
55
60
|
|
|
56
61
|
let result =
|
|
57
|
-
|
|
58
|
-
? segmentToCubic(
|
|
59
|
-
: ['V', 'H'].includes(
|
|
60
|
-
? normalizeSegment(
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
absCommand === 'A'
|
|
63
|
+
? segmentToCubic(absoluteSegment, transformParams)
|
|
64
|
+
: ['V', 'H'].includes(absCommand)
|
|
65
|
+
? normalizeSegment(absoluteSegment, transformParams)
|
|
66
|
+
: absoluteSegment;
|
|
67
|
+
|
|
68
|
+
// update pathCommand
|
|
69
|
+
pathCommand = result[0];
|
|
70
|
+
const isLongArc = pathCommand === 'C' && result.length > 7;
|
|
71
|
+
const tempSegment = (isLongArc ? result.slice(0, 7) : result.slice(0)) as AbsoluteSegment;
|
|
64
72
|
|
|
65
73
|
if (isLongArc) {
|
|
66
|
-
path.splice(
|
|
67
|
-
result =
|
|
74
|
+
path.splice(index + 1, 0, ['C' as typeof pathCommand | number].concat(result.slice(7)) as CSegment);
|
|
75
|
+
result = tempSegment as CSegment;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
[lx, ly] = projection2d(matrixInstance, values, origin);
|
|
78
|
+
if (pathCommand === 'L') {
|
|
79
|
+
[lx, ly] = projection2d(matrixInstance, [(result as LSegment)[1], (result as LSegment)[2]], origin);
|
|
73
80
|
|
|
74
81
|
/* istanbul ignore else @preserve */
|
|
75
82
|
if (x !== lx && y !== ly) {
|
|
@@ -90,24 +97,12 @@ const transformPath = (pathInput: PathArray | string, transform?: Partial<Transf
|
|
|
90
97
|
x = lx;
|
|
91
98
|
y = ly;
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (pathCommand === 'M') {
|
|
99
|
-
mx = nx;
|
|
100
|
-
my = ny;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
100
|
+
const seglen = tempSegment.length;
|
|
101
|
+
transformParams.x1 = +tempSegment[seglen - 2];
|
|
102
|
+
transformParams.y1 = +tempSegment[seglen - 1];
|
|
103
|
+
transformParams.x2 = +tempSegment[seglen - 4] || transformParams.x1;
|
|
104
|
+
transformParams.y2 = +tempSegment[seglen - 3] || transformParams.y1;
|
|
103
105
|
|
|
104
|
-
const seglen = normalizedSegment.length;
|
|
105
|
-
transformParams.x1 = +normalizedSegment[seglen - 2];
|
|
106
|
-
transformParams.y1 = +normalizedSegment[seglen - 1];
|
|
107
|
-
transformParams.x2 = +normalizedSegment[seglen - 4] || transformParams.x1;
|
|
108
|
-
transformParams.y2 = +normalizedSegment[seglen - 3] || transformParams.y1;
|
|
109
|
-
transformParams.x = nx;
|
|
110
|
-
transformParams.y = ny;
|
|
111
106
|
return result;
|
|
112
107
|
});
|
|
113
108
|
};
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
LineAttr,
|
|
3
|
-
CircleAttr,
|
|
4
|
-
PolyAttr,
|
|
5
|
-
RectAttr,
|
|
6
|
-
EllipseAttr,
|
|
7
|
-
GlyphAttr,
|
|
8
|
-
TransformObject,
|
|
9
|
-
ParserParams,
|
|
10
|
-
} from './interface';
|
|
1
|
+
import type { LineAttr, CircleAttr, PolyAttr, RectAttr, EllipseAttr, GlyphAttr, TransformObject } from './interface';
|
|
11
2
|
|
|
12
3
|
export type SpaceNumber =
|
|
13
4
|
| 0x1680
|
|
@@ -229,4 +220,9 @@ export type LineCoordinates = [number, number, number, number];
|
|
|
229
220
|
|
|
230
221
|
export type DeriveCallback = (t: number) => Point;
|
|
231
222
|
|
|
232
|
-
export type IteratorCallback = (
|
|
223
|
+
export type IteratorCallback = (
|
|
224
|
+
segment: PathSegment,
|
|
225
|
+
index: number,
|
|
226
|
+
lastX: number,
|
|
227
|
+
lastY: number,
|
|
228
|
+
) => PathSegment | false | void | undefined;
|