svg-path-commander 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +207 -19
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +1052 -969
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +7 -7
- package/src/convert/pathToAbsolute.ts +2 -31
- package/src/convert/pathToCurve.ts +13 -27
- package/src/convert/pathToRelative.ts +2 -47
- package/src/convert/pathToString.ts +40 -7
- package/src/index.ts +132 -39
- package/src/interface.ts +2 -1
- package/src/math/arcTools.ts +53 -51
- package/src/math/bezier.ts +41 -33
- package/src/math/cubicTools.ts +10 -8
- package/src/math/distanceSquareRoot.ts +1 -1
- package/src/math/lineTools.ts +6 -4
- package/src/math/polygonTools.ts +48 -0
- package/src/math/quadTools.ts +8 -6
- package/src/math/rotateVector.ts +3 -2
- package/src/math/roundTo.ts +7 -0
- package/src/parser/finalizeSegment.ts +11 -7
- package/src/parser/parsePathString.ts +2 -3
- package/src/parser/pathParser.ts +1 -1
- package/src/process/absolutizeSegment.ts +35 -30
- package/src/process/arcToCubic.ts +2 -2
- package/src/process/iterate.ts +41 -16
- package/src/process/lineToCubic.ts +1 -1
- package/src/process/normalizePath.ts +14 -31
- package/src/process/normalizeSegment.ts +53 -15
- package/src/process/optimizePath.ts +40 -60
- package/src/process/projection2d.ts +2 -2
- package/src/process/relativizeSegment.ts +33 -35
- package/src/process/reverseCurve.ts +8 -5
- package/src/process/reversePath.ts +87 -74
- package/src/process/roundPath.ts +14 -8
- package/src/process/roundSegment.ts +9 -0
- package/src/process/segmentToCubic.ts +9 -7
- package/src/process/shortenSegment.ts +24 -32
- package/src/process/splitCubic.ts +2 -2
- package/src/process/transformPath.ts +35 -40
- package/src/types.ts +7 -11
- package/src/util/getPathArea.ts +2 -2
- package/src/util/getPathBBox.ts +25 -42
- package/src/util/getPointAtLength.ts +51 -49
- package/src/util/getPropertiesAtLength.ts +4 -5
- package/src/util/getPropertiesAtPoint.ts +5 -5
- package/src/util/getTotalLength.ts +23 -38
- package/test/class.test.ts +2 -0
- package/test/fixtures/shapes.js +5 -5
- package/test/static.test.ts +18 -12
- package/src/math/polygonArea.ts +0 -29
- package/src/math/polygonLength.ts +0 -22
package/src/math/rotateVector.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* @returns the rotated vector
|
|
9
9
|
*/
|
|
10
10
|
const rotateVector = (x: number, y: number, rad: number): { x: number; y: number } => {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const { sin, cos } = Math;
|
|
12
|
+
const X = x * cos(rad) - y * sin(rad);
|
|
13
|
+
const Y = x * sin(rad) + y * cos(rad);
|
|
13
14
|
return { x: X, y: Y };
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -9,21 +9,25 @@ import type { PathCommand, PathSegment, RelativeCommand } from '../types';
|
|
|
9
9
|
*/
|
|
10
10
|
const finalizeSegment = (path: PathParser) => {
|
|
11
11
|
let pathCommand = path.pathValue[path.segmentStart] as PathCommand;
|
|
12
|
-
let
|
|
12
|
+
let relativeCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
13
13
|
const { data } = path;
|
|
14
14
|
|
|
15
|
-
while (data.length >= paramsCount[
|
|
15
|
+
while (data.length >= paramsCount[relativeCommand]) {
|
|
16
16
|
// overloaded `moveTo`
|
|
17
17
|
// https://github.com/rveciana/svg-path-properties/blob/master/src/parse.ts
|
|
18
|
-
if (
|
|
19
|
-
path.segments.push([pathCommand
|
|
20
|
-
|
|
18
|
+
if (relativeCommand === 'm' && data.length > 2) {
|
|
19
|
+
path.segments.push([pathCommand as PathCommand | number].concat(data.splice(0, 2) as number[]) as PathSegment);
|
|
20
|
+
relativeCommand = 'l';
|
|
21
21
|
pathCommand = pathCommand === 'm' ? 'l' : 'L';
|
|
22
22
|
} else {
|
|
23
|
-
path.segments.push(
|
|
23
|
+
path.segments.push(
|
|
24
|
+
[pathCommand as PathCommand | number].concat(
|
|
25
|
+
data.splice(0, paramsCount[relativeCommand]) as number[],
|
|
26
|
+
) as PathSegment,
|
|
27
|
+
);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
if (!paramsCount[
|
|
30
|
+
if (!paramsCount[relativeCommand]) {
|
|
27
31
|
break;
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -10,10 +10,9 @@ import type { PathArray } from '../types';
|
|
|
10
10
|
* @param pathInput the string to be parsed
|
|
11
11
|
* @returns the resulted `pathArray` or error string
|
|
12
12
|
*/
|
|
13
|
-
const parsePathString = (pathInput: string |
|
|
13
|
+
const parsePathString = <T extends PathArray>(pathInput: string | T) => {
|
|
14
14
|
if (typeof pathInput !== 'string') {
|
|
15
|
-
return pathInput.slice(0) as
|
|
16
|
-
// return pathInput;
|
|
15
|
+
return pathInput.slice(0) as typeof pathInput;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const path = new PathParser(pathInput);
|
package/src/parser/pathParser.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ParserParams } from '../interface';
|
|
2
1
|
import type {
|
|
3
2
|
AbsoluteSegment,
|
|
4
3
|
AbsoluteCommand,
|
|
@@ -11,48 +10,54 @@ import type {
|
|
|
11
10
|
CSegment,
|
|
12
11
|
PathSegment,
|
|
13
12
|
MSegment,
|
|
13
|
+
LSegment,
|
|
14
14
|
} from '../types';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Returns an absolute segment of a `PathArray` object.
|
|
18
18
|
*
|
|
19
19
|
* @param segment the segment object
|
|
20
|
-
* @param
|
|
20
|
+
* @param index the segment index
|
|
21
|
+
* @param lastX the last known X value
|
|
22
|
+
* @param lastY the last known Y value
|
|
21
23
|
* @returns the absolute segment
|
|
22
24
|
*/
|
|
23
|
-
const absolutizeSegment = (segment: PathSegment,
|
|
25
|
+
const absolutizeSegment = (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 absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
28
28
|
const isAbsolute = absCommand === pathCommand;
|
|
29
29
|
|
|
30
30
|
/* istanbul ignore else @preserve */
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
31
|
+
if (index === 0 || isAbsolute) return segment as MSegment | AbsoluteSegment;
|
|
32
|
+
// const values = segment.slice(1) as number[];
|
|
33
|
+
if (absCommand === 'A') {
|
|
34
|
+
return [
|
|
35
|
+
absCommand,
|
|
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 (absCommand === 'V') {
|
|
45
|
+
return [absCommand, (segment as VSegment)[1] + lastY] as VSegment;
|
|
46
|
+
} else if (absCommand === 'H') {
|
|
47
|
+
return [absCommand, (segment as HSegment)[1] + lastX] as HSegment;
|
|
48
|
+
} else if (absCommand === 'L') {
|
|
49
|
+
return [absCommand, (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 absValues = (segment.slice(1) as number[]).map((n, j) => n + (j % 2 ? lastY : lastX));
|
|
54
|
+
// for c, s, q, t
|
|
55
|
+
return [absCommand as typeof absCommand | number].concat(absValues) as
|
|
56
|
+
| MSegment
|
|
57
|
+
| QSegment
|
|
58
|
+
| TSegment
|
|
59
|
+
| SSegment
|
|
60
|
+
| CSegment;
|
|
54
61
|
}
|
|
55
|
-
|
|
56
|
-
return segment as AbsoluteSegment;
|
|
57
62
|
};
|
|
58
63
|
export default absolutizeSegment;
|
|
@@ -116,9 +116,9 @@ const arcToCubic = (
|
|
|
116
116
|
m2[0] = 2 * m1[0] - m2[0];
|
|
117
117
|
m2[1] = 2 * m1[1] - m2[1];
|
|
118
118
|
if (recursive) {
|
|
119
|
-
return [
|
|
119
|
+
return [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
|
|
120
120
|
}
|
|
121
|
-
res = [
|
|
121
|
+
res = [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
|
|
122
122
|
const newres = [];
|
|
123
123
|
for (let i = 0, ii = res.length; i < ii; i += 1) {
|
|
124
124
|
newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
|
package/src/process/iterate.ts
CHANGED
|
@@ -1,32 +1,57 @@
|
|
|
1
|
-
import paramsParser from '../parser/paramsParser';
|
|
2
|
-
import type { PathArray, PathCommand, PathSegment, IteratorCallback } from '../types';
|
|
1
|
+
// import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { PathArray, PathCommand, PathSegment, IteratorCallback, AbsoluteCommand } from '../types';
|
|
3
3
|
|
|
4
4
|
const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
|
|
5
|
-
const allPathCommands = [] as PathCommand[];
|
|
6
|
-
const params = { ...paramsParser };
|
|
7
5
|
let pathLen = path.length;
|
|
8
6
|
let segment: PathSegment;
|
|
9
7
|
let pathCommand = 'M' as PathCommand;
|
|
8
|
+
let absCommand = 'M' as AbsoluteCommand;
|
|
9
|
+
let isRelative = false;
|
|
10
|
+
let x = 0;
|
|
11
|
+
let y = 0;
|
|
12
|
+
let mx = 0;
|
|
13
|
+
let my = 0;
|
|
14
|
+
let segLen = 0;
|
|
10
15
|
|
|
11
16
|
for (let i = 0; i < pathLen; i += 1) {
|
|
12
17
|
segment = path[i];
|
|
13
18
|
[pathCommand] = segment;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
segLen = segment.length;
|
|
20
|
+
absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
21
|
+
isRelative = absCommand !== pathCommand;
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const iteratorResult = iterator(segment, i, x, y);
|
|
24
|
+
// some methods like getPointAtLength would like to break
|
|
25
|
+
// when task is complete
|
|
26
|
+
if (iteratorResult === false) {
|
|
27
|
+
break;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
segment = path[i];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
// segment = path[i];
|
|
31
|
+
if (absCommand === 'Z') {
|
|
32
|
+
x = mx;
|
|
33
|
+
y = my;
|
|
34
|
+
} else if (absCommand === 'H') {
|
|
35
|
+
x = (segment[1] as number) + (isRelative ? x : 0);
|
|
36
|
+
} else if (absCommand === 'V') {
|
|
37
|
+
y = (segment[1] as number) + (isRelative ? y : 0);
|
|
38
|
+
} else {
|
|
39
|
+
x = (segment[segLen - 2] as number) + (isRelative ? x : 0);
|
|
40
|
+
y = (segment[segLen - 1] as number) + (isRelative ? y : 0);
|
|
41
|
+
|
|
42
|
+
if (absCommand === 'M') {
|
|
43
|
+
mx = x;
|
|
44
|
+
my = y;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (iteratorResult) {
|
|
49
|
+
path[i] = iteratorResult;
|
|
50
|
+
if (iteratorResult[0] === 'C') {
|
|
51
|
+
pathLen = path.length;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
28
54
|
}
|
|
29
|
-
// console.log('iteration: ', ...path)
|
|
30
55
|
return path as T;
|
|
31
56
|
};
|
|
32
57
|
|
|
@@ -12,6 +12,6 @@ import midPoint from '../math/midPoint';
|
|
|
12
12
|
const lineToCubic = (x1: number, y1: number, x2: number, y2: number) => {
|
|
13
13
|
const c1 = midPoint([x1, y1], [x2, y2], 1.0 / 3.0);
|
|
14
14
|
const c2 = midPoint([x1, y1], [x2, y2], 2.0 / 3.0);
|
|
15
|
-
return [
|
|
15
|
+
return [c1[0], c1[1], c2[0], c2[1], x2, y2];
|
|
16
16
|
};
|
|
17
17
|
export default lineToCubic;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import normalizeSegment from './normalizeSegment';
|
|
2
|
-
import type {
|
|
2
|
+
import type { NormalArray, PathArray } from '../types';
|
|
3
3
|
import iterate from './iterate';
|
|
4
4
|
import parsePathString from '../parser/parsePathString';
|
|
5
|
-
import
|
|
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
|
*
|
|
@@ -13,37 +13,20 @@ import absolutizeSegment from './absolutizeSegment';
|
|
|
13
13
|
* @returns the normalized `pathArray`
|
|
14
14
|
*/
|
|
15
15
|
const normalizePath = (pathInput: string | PathArray) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let mx = 0;
|
|
19
|
-
let my = 0;
|
|
20
|
-
let pathCommand = 'M';
|
|
16
|
+
const path = parsePathString(pathInput);
|
|
17
|
+
const params = { ...paramsParser };
|
|
21
18
|
|
|
22
|
-
return iterate<NormalArray>(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
19
|
+
return iterate<NormalArray>(path, (seg, _, lastX, lastY) => {
|
|
20
|
+
params.x = lastX;
|
|
21
|
+
params.y = lastY;
|
|
22
|
+
const result = normalizeSegment(seg, params);
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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;
|
|
33
29
|
|
|
34
|
-
if (absCommand === 'M') {
|
|
35
|
-
mx = x;
|
|
36
|
-
my = y;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
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
30
|
return result;
|
|
48
31
|
});
|
|
49
32
|
};
|
|
@@ -1,46 +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
23
|
const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
12
24
|
const [pathCommand] = segment;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
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));
|
|
15
30
|
|
|
16
|
-
if (!'TQ'.includes(
|
|
31
|
+
if (!'TQ'.includes(absCommand)) {
|
|
17
32
|
// optional but good to be cautious
|
|
18
33
|
params.qx = null;
|
|
19
34
|
params.qy = null;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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') {
|
|
27
61
|
const x1 = px1 * 2 - px2;
|
|
28
62
|
const y1 = py1 * 2 - py2;
|
|
29
63
|
params.x1 = x1;
|
|
30
64
|
params.y1 = y1;
|
|
31
|
-
return ['C', x1, y1
|
|
32
|
-
} else if (
|
|
65
|
+
return ['C', x1, y1].concat(absValues) as CSegment;
|
|
66
|
+
} else if (absCommand === 'T') {
|
|
33
67
|
const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
|
|
34
68
|
const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
|
|
35
69
|
params.qx = qx;
|
|
36
70
|
params.qy = qy;
|
|
37
|
-
return ['Q', qx, qy
|
|
38
|
-
} else if (
|
|
39
|
-
const [nqx, nqy] =
|
|
71
|
+
return ['Q', qx, qy].concat(absValues) as QSegment;
|
|
72
|
+
} else if (absCommand === 'Q') {
|
|
73
|
+
const [nqx, nqy] = absValues as PointTuple;
|
|
40
74
|
params.qx = nqx;
|
|
41
75
|
params.qy = nqy;
|
|
76
|
+
return ['Q' as PathCommand | number].concat(absValues) as QSegment;
|
|
77
|
+
} else if (absCommand === 'Z') {
|
|
78
|
+
return ['Z'] as NormalSegment;
|
|
42
79
|
}
|
|
43
80
|
|
|
81
|
+
// istanbul ignore next @preserve
|
|
44
82
|
return segment as NormalSegment;
|
|
45
83
|
};
|
|
46
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;
|
|
@@ -13,7 +13,7 @@ import { type PointTuple } from '../types';
|
|
|
13
13
|
* @return the resulting Tuple
|
|
14
14
|
*/
|
|
15
15
|
const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [number, number, number, number] => {
|
|
16
|
-
let m = CSSMatrix.Translate(
|
|
16
|
+
let m = CSSMatrix.Translate(v[0], v[1], v[2]);
|
|
17
17
|
|
|
18
18
|
[, , , m.m44] = v;
|
|
19
19
|
m = cssm.multiply(m);
|
|
@@ -37,7 +37,7 @@ const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [
|
|
|
37
37
|
*/
|
|
38
38
|
const projection2d = (m: CSSMatrix, point2D: PointTuple, origin: [number, number, number]): PointTuple => {
|
|
39
39
|
const [originX, originY, originZ] = origin;
|
|
40
|
-
const [x, y, z] = translatePoint(m, [
|
|
40
|
+
const [x, y, z] = translatePoint(m, [point2D[0], point2D[1], 0, 1]);
|
|
41
41
|
|
|
42
42
|
const relativePositionX = x - originX;
|
|
43
43
|
const relativePositionY = y - originY;
|