svg-path-commander 2.0.10 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +1 -0
- package/README.md +64 -8
- package/dist/svg-path-commander.cjs +1 -1
- package/dist/svg-path-commander.cjs.map +1 -1
- package/dist/svg-path-commander.d.ts +236 -43
- package/dist/svg-path-commander.js +1 -1
- package/dist/svg-path-commander.js.map +1 -1
- package/dist/svg-path-commander.mjs +790 -650
- package/dist/svg-path-commander.mjs.map +1 -1
- package/package.json +20 -22
- package/src/convert/pathToAbsolute.ts +16 -70
- package/src/convert/pathToCurve.ts +36 -28
- package/src/convert/pathToRelative.ts +33 -62
- package/src/index.ts +37 -39
- package/src/interface.ts +33 -33
- package/src/math/arcTools.ts +394 -0
- package/src/math/bezier.ts +253 -0
- package/src/math/cubicTools.ts +122 -0
- package/src/math/distanceSquareRoot.ts +3 -1
- package/src/math/lineTools.ts +67 -0
- package/src/math/midPoint.ts +3 -1
- package/src/math/polygonArea.ts +3 -1
- package/src/math/polygonLength.ts +2 -1
- package/src/math/quadTools.ts +98 -0
- package/src/parser/isMoveCommand.ts +17 -0
- package/src/parser/parsePathString.ts +5 -5
- package/src/parser/scanSegment.ts +12 -3
- package/src/process/absolutizeSegment.ts +58 -0
- package/src/process/iterate.ts +33 -0
- package/src/process/normalizePath.ts +34 -28
- package/src/process/normalizeSegment.ts +8 -9
- package/src/process/projection2d.ts +2 -1
- package/src/process/relativizeSegment.ts +61 -0
- package/src/process/reversePath.ts +1 -1
- package/src/process/roundPath.ts +8 -10
- package/src/process/segmentToCubic.ts +1 -1
- package/src/process/shortenSegment.ts +3 -3
- package/src/process/splitCubic.ts +8 -7
- package/src/process/splitPath.ts +39 -5
- package/src/process/transformPath.ts +81 -94
- package/src/types.ts +40 -1
- package/src/util/distanceEpsilon.ts +3 -0
- package/src/util/getClosestPoint.ts +1 -1
- package/src/util/getPathArea.ts +3 -3
- package/src/util/getPathBBox.ts +86 -18
- package/src/util/getPointAtLength.ts +98 -4
- package/src/util/getPropertiesAtLength.ts +4 -3
- package/src/util/getPropertiesAtPoint.ts +4 -1
- package/src/util/getTotalLength.ts +71 -4
- package/src/util/isPointInStroke.ts +2 -1
- package/src/util/shapeToPathArray.ts +8 -4
- package/test/class.test.ts +502 -0
- package/test/fixtures/getMarkup.ts +17 -0
- package/{cypress → test}/fixtures/shapes.js +39 -39
- package/test/fixtures/simpleShapes.js +75 -0
- package/test/static.test.ts +324 -0
- package/tsconfig.json +9 -4
- package/{vite.config.ts → vite.config.mts} +10 -1
- package/vitest.config-ui.mts +26 -0
- package/vitest.config.mts +26 -0
- package/cypress/e2e/svg-path-commander.spec.ts +0 -868
- package/cypress/fixtures/simpleShapes.js +0 -75
- package/cypress/plugins/esbuild-istanbul.ts +0 -50
- package/cypress/plugins/tsCompile.ts +0 -34
- package/cypress/support/commands.ts +0 -37
- package/cypress/support/e2e.ts +0 -21
- package/cypress/test.html +0 -36
- package/cypress.config.ts +0 -29
- package/src/process/fixArc.ts +0 -23
- package/src/util/pathLengthFactory.ts +0 -114
- package/src/util/segmentArcFactory.ts +0 -219
- package/src/util/segmentCubicFactory.ts +0 -114
- package/src/util/segmentLineFactory.ts +0 -45
- package/src/util/segmentQuadFactory.ts +0 -109
- /package/{cypress/fixtures/shapeObjects.js → test/fixtures/shapeObjects.ts} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
|
+
import type {
|
|
3
|
+
AbsoluteSegment,
|
|
4
|
+
AbsoluteCommand,
|
|
5
|
+
ASegment,
|
|
6
|
+
VSegment,
|
|
7
|
+
HSegment,
|
|
8
|
+
QSegment,
|
|
9
|
+
SSegment,
|
|
10
|
+
TSegment,
|
|
11
|
+
CSegment,
|
|
12
|
+
PathSegment,
|
|
13
|
+
MSegment,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns an absolute segment of a `PathArray` object.
|
|
18
|
+
*
|
|
19
|
+
* @param segment the segment object
|
|
20
|
+
* @param params the coordinates of the previous segment
|
|
21
|
+
* @returns the absolute segment
|
|
22
|
+
*/
|
|
23
|
+
const absolutizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
24
|
+
const [pathCommand] = segment;
|
|
25
|
+
const { x, y } = params;
|
|
26
|
+
const values = segment.slice(1).map(Number);
|
|
27
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
28
|
+
const isAbsolute = absCommand === pathCommand;
|
|
29
|
+
|
|
30
|
+
/* istanbul ignore else @preserve */
|
|
31
|
+
if (!isAbsolute) {
|
|
32
|
+
if (absCommand === 'A') {
|
|
33
|
+
return [
|
|
34
|
+
absCommand,
|
|
35
|
+
values[0],
|
|
36
|
+
values[1],
|
|
37
|
+
values[2],
|
|
38
|
+
values[3],
|
|
39
|
+
values[4],
|
|
40
|
+
values[5] + x,
|
|
41
|
+
values[6] + y,
|
|
42
|
+
] as ASegment;
|
|
43
|
+
} else if (absCommand === 'V') {
|
|
44
|
+
return [absCommand, values[0] + y] as VSegment;
|
|
45
|
+
} else if (absCommand === 'H') {
|
|
46
|
+
return [absCommand, values[0] + x] as HSegment;
|
|
47
|
+
} else {
|
|
48
|
+
// use brakets for `eslint: no-case-declaration`
|
|
49
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
50
|
+
const absValues = values.map((n, j) => n + (j % 2 ? y : x));
|
|
51
|
+
// for n, l, c, s, q, t
|
|
52
|
+
return [absCommand, ...absValues] as MSegment | QSegment | TSegment | SSegment | CSegment;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return segment as AbsoluteSegment;
|
|
57
|
+
};
|
|
58
|
+
export default absolutizeSegment;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { PathArray, PathCommand, PathSegment, IteratorCallback } from '../types';
|
|
3
|
+
|
|
4
|
+
const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
|
|
5
|
+
const allPathCommands = [] as PathCommand[];
|
|
6
|
+
const params = { ...paramsParser };
|
|
7
|
+
let pathLen = path.length;
|
|
8
|
+
let segment: PathSegment;
|
|
9
|
+
let pathCommand = 'M' as PathCommand;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < pathLen; i += 1) {
|
|
12
|
+
segment = path[i];
|
|
13
|
+
[pathCommand] = segment;
|
|
14
|
+
allPathCommands[i] = pathCommand;
|
|
15
|
+
const iteratorResult = iterator(segment, params, i);
|
|
16
|
+
path[i] = iteratorResult;
|
|
17
|
+
|
|
18
|
+
if (iteratorResult[0] === 'C') {
|
|
19
|
+
pathLen = path.length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
segment = path[i];
|
|
23
|
+
const seglen = segment.length;
|
|
24
|
+
params.x1 = +segment[seglen - 2];
|
|
25
|
+
params.y1 = +segment[seglen - 1];
|
|
26
|
+
params.x2 = +segment[seglen - 4] || params.x1;
|
|
27
|
+
params.y2 = +segment[seglen - 3] || params.y1;
|
|
28
|
+
}
|
|
29
|
+
// console.log('iteration: ', ...path)
|
|
30
|
+
return path as T;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default iterate;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import pathToAbsolute from '../convert/pathToAbsolute';
|
|
2
1
|
import normalizeSegment from './normalizeSegment';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import type { AbsoluteCommand, NormalArray, PathArray, PointTuple } from '../types';
|
|
3
|
+
import iterate from './iterate';
|
|
4
|
+
import parsePathString from '../parser/parsePathString';
|
|
5
|
+
import absolutizeSegment from './absolutizeSegment';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Normalizes a `path` object for further processing:
|
|
@@ -12,33 +12,39 @@ import type { NormalArray, PathArray } from '../types';
|
|
|
12
12
|
* @param pathInput the string to be parsed or 'pathArray'
|
|
13
13
|
* @returns the normalized `pathArray`
|
|
14
14
|
*/
|
|
15
|
-
const normalizePath = (pathInput: string | PathArray)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const normalizePath = (pathInput: string | PathArray) => {
|
|
16
|
+
let x = 0;
|
|
17
|
+
let y = 0;
|
|
18
|
+
let mx = 0;
|
|
19
|
+
let my = 0;
|
|
20
|
+
let pathCommand = 'M';
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
return iterate<NormalArray>(parsePathString(pathInput), (seg, params) => {
|
|
23
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
24
|
+
const result = normalizeSegment(absoluteSegment, params);
|
|
25
|
+
[pathCommand] = result;
|
|
26
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
if (absCommand === 'Z') {
|
|
29
|
+
x = mx;
|
|
30
|
+
y = my;
|
|
31
|
+
} else {
|
|
32
|
+
[x, y] = result.slice(-2) as PointTuple;
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (absCommand === 'M') {
|
|
35
|
+
mx = x;
|
|
36
|
+
my = y;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
32
39
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
params.
|
|
37
|
-
params.
|
|
38
|
-
params.
|
|
39
|
-
params.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return path as NormalArray;
|
|
40
|
+
// const seglen = result.length;
|
|
41
|
+
// params.x1 = +result[seglen - 2];
|
|
42
|
+
// params.y1 = +result[seglen - 1];
|
|
43
|
+
// params.x2 = +result[seglen - 4] || params.x1;
|
|
44
|
+
// params.y2 = +result[seglen - 3] || params.y1;
|
|
45
|
+
params.x = x;
|
|
46
|
+
params.y = y;
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
43
49
|
};
|
|
44
50
|
export default normalizePath;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ParserParams } from '../interface';
|
|
2
|
-
import type { NormalSegment, PathSegment } from '../types';
|
|
2
|
+
import type { NormalSegment, PointTuple, PathSegment, QSegment, CSegment, LSegment } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Normalizes a single segment of a `pathArray` object.
|
|
@@ -8,11 +8,10 @@ import type { NormalSegment, PathSegment } from '../types';
|
|
|
8
8
|
* @param params the coordinates of the previous segment
|
|
9
9
|
* @returns the normalized segment
|
|
10
10
|
*/
|
|
11
|
-
const normalizeSegment = (segment: PathSegment, params: ParserParams)
|
|
11
|
+
const normalizeSegment = (segment: PathSegment, params: ParserParams) => {
|
|
12
12
|
const [pathCommand] = segment;
|
|
13
13
|
const { x1: px1, y1: py1, x2: px2, y2: py2 } = params;
|
|
14
14
|
const values = segment.slice(1).map(Number);
|
|
15
|
-
let result = segment;
|
|
16
15
|
|
|
17
16
|
if (!'TQ'.includes(pathCommand)) {
|
|
18
17
|
// optional but good to be cautious
|
|
@@ -21,27 +20,27 @@ const normalizeSegment = (segment: PathSegment, params: ParserParams): NormalSeg
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
if (pathCommand === 'H') {
|
|
24
|
-
|
|
23
|
+
return ['L', segment[1], py1] as LSegment;
|
|
25
24
|
} else if (pathCommand === 'V') {
|
|
26
|
-
|
|
25
|
+
return ['L', px1, segment[1]] as LSegment;
|
|
27
26
|
} else if (pathCommand === 'S') {
|
|
28
27
|
const x1 = px1 * 2 - px2;
|
|
29
28
|
const y1 = py1 * 2 - py2;
|
|
30
29
|
params.x1 = x1;
|
|
31
30
|
params.y1 = y1;
|
|
32
|
-
|
|
31
|
+
return ['C', x1, y1, ...values] as CSegment;
|
|
33
32
|
} else if (pathCommand === 'T') {
|
|
34
33
|
const qx = px1 * 2 - (params.qx ? params.qx : /* istanbul ignore next */ 0);
|
|
35
34
|
const qy = py1 * 2 - (params.qy ? params.qy : /* istanbul ignore next */ 0);
|
|
36
35
|
params.qx = qx;
|
|
37
36
|
params.qy = qy;
|
|
38
|
-
|
|
37
|
+
return ['Q', qx, qy, ...values] as QSegment;
|
|
39
38
|
} else if (pathCommand === 'Q') {
|
|
40
|
-
const [nqx, nqy] = values as
|
|
39
|
+
const [nqx, nqy] = values as PointTuple;
|
|
41
40
|
params.qx = nqx;
|
|
42
41
|
params.qy = nqy;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
return
|
|
44
|
+
return segment as NormalSegment;
|
|
46
45
|
};
|
|
47
46
|
export default normalizeSegment;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import CSSMatrix from '@thednp/dommatrix';
|
|
2
|
+
import { type PointTuple } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Transforms a specified point using a matrix, returning a new
|
|
@@ -34,7 +35,7 @@ const translatePoint = (cssm: CSSMatrix, v: [number, number, number, number]): [
|
|
|
34
35
|
* @param origin the [x,y,z] transform origin
|
|
35
36
|
* @returns the projected [x,y] coordinates
|
|
36
37
|
*/
|
|
37
|
-
const projection2d = (m: CSSMatrix, point2D:
|
|
38
|
+
const projection2d = (m: CSSMatrix, point2D: PointTuple, origin: [number, number, number]): PointTuple => {
|
|
38
39
|
const [originX, originY, originZ] = origin;
|
|
39
40
|
const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
|
|
40
41
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
|
+
import type {
|
|
3
|
+
RelativeSegment,
|
|
4
|
+
RelativeCommand,
|
|
5
|
+
PathSegment,
|
|
6
|
+
aSegment,
|
|
7
|
+
vSegment,
|
|
8
|
+
hSegment,
|
|
9
|
+
qSegment,
|
|
10
|
+
tSegment,
|
|
11
|
+
sSegment,
|
|
12
|
+
cSegment,
|
|
13
|
+
} from '../types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns a relative segment of a `PathArray` object.
|
|
17
|
+
*
|
|
18
|
+
* @param segment the segment object
|
|
19
|
+
* @param params the coordinates of the previous segment
|
|
20
|
+
* @param index the segment index
|
|
21
|
+
* @returns the absolute segment
|
|
22
|
+
*/
|
|
23
|
+
const relativizeSegment = (segment: PathSegment, params: ParserParams, index: number) => {
|
|
24
|
+
const [pathCommand] = segment;
|
|
25
|
+
const { x, y } = params;
|
|
26
|
+
const values = segment.slice(1).map(Number);
|
|
27
|
+
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
28
|
+
|
|
29
|
+
if (index === 0 && pathCommand === 'M') {
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* istanbul ignore else @preserve */
|
|
34
|
+
if (pathCommand !== relCommand) {
|
|
35
|
+
if (relCommand === 'a') {
|
|
36
|
+
return [
|
|
37
|
+
relCommand,
|
|
38
|
+
values[0],
|
|
39
|
+
values[1],
|
|
40
|
+
values[2],
|
|
41
|
+
values[3],
|
|
42
|
+
values[4],
|
|
43
|
+
values[5] - x,
|
|
44
|
+
values[6] - y,
|
|
45
|
+
] as aSegment;
|
|
46
|
+
} else if (relCommand === 'v') {
|
|
47
|
+
return [relCommand, values[0] - y] as vSegment;
|
|
48
|
+
} else if (relCommand === 'h') {
|
|
49
|
+
return [relCommand, values[0] - x] as hSegment;
|
|
50
|
+
} else {
|
|
51
|
+
// use brakets for `eslint: no-case-declaration`
|
|
52
|
+
// https://stackoverflow.com/a/50753272/803358
|
|
53
|
+
const relValues = values.map((n, j) => n - (j % 2 ? y : x));
|
|
54
|
+
// for n, l, c, s, q, t
|
|
55
|
+
return [relCommand, ...relValues] as qSegment | tSegment | sSegment | cSegment;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return segment as RelativeSegment;
|
|
60
|
+
};
|
|
61
|
+
export default relativizeSegment;
|
package/src/process/roundPath.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { PathArray } from '
|
|
1
|
+
import type { PathArray, PathSegment } from '../types';
|
|
2
2
|
import defaultOptions from '../options/options';
|
|
3
|
+
import iterate from './iterate';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Rounds the values of a `pathArray` instance to
|
|
@@ -9,21 +10,18 @@ import defaultOptions from '../options/options';
|
|
|
9
10
|
* @param roundOption the amount of decimals to round numbers to
|
|
10
11
|
* @returns the resulted `pathArray` with rounded values
|
|
11
12
|
*/
|
|
12
|
-
const roundPath = (path: PathArray, roundOption?: number | 'off')
|
|
13
|
+
const roundPath = (path: PathArray, roundOption?: number | 'off') => {
|
|
13
14
|
let { round } = defaultOptions;
|
|
14
|
-
if (roundOption === 'off' || round === 'off') return
|
|
15
|
+
if (roundOption === 'off' || round === 'off') return path.slice(0) as PathArray;
|
|
15
16
|
// allow for ZERO decimals
|
|
16
17
|
round = typeof roundOption === 'number' && roundOption >= 0 ? roundOption : round;
|
|
17
18
|
// to round values to the power
|
|
18
19
|
// the `round` value must be integer
|
|
19
20
|
const pow = typeof round === 'number' && round >= 1 ? 10 ** round : 1;
|
|
20
21
|
|
|
21
|
-
return path
|
|
22
|
-
const values =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
|
|
26
|
-
return [pi[0], ...values];
|
|
27
|
-
}) as PathArray;
|
|
22
|
+
return iterate<typeof path>(path, segment => {
|
|
23
|
+
const values = (segment.slice(1) as number[]).map(n => (round ? Math.round(n * pow) / pow : Math.round(n)));
|
|
24
|
+
return [segment[0], ...values] as PathSegment;
|
|
25
|
+
});
|
|
28
26
|
};
|
|
29
27
|
export default roundPath;
|
|
@@ -11,7 +11,7 @@ import type { ParserParams } from '../interface';
|
|
|
11
11
|
* @param params the source segment parameters
|
|
12
12
|
* @returns the cubic-bezier segment
|
|
13
13
|
*/
|
|
14
|
-
const segmentToCubic = (segment: PathSegment, params: ParserParams)
|
|
14
|
+
const segmentToCubic = (segment: PathSegment, params: ParserParams) => {
|
|
15
15
|
const [pathCommand] = segment;
|
|
16
16
|
const values = segment.slice(1).map(Number);
|
|
17
17
|
const [x, y] = values;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ParserParams } from '
|
|
1
|
+
import type { ParserParams } from '../interface';
|
|
2
2
|
import type {
|
|
3
3
|
AbsoluteSegment,
|
|
4
4
|
HSegment,
|
|
@@ -28,8 +28,8 @@ const shortenSegment = (
|
|
|
28
28
|
): ShortSegment => {
|
|
29
29
|
const [pathCommand] = segment;
|
|
30
30
|
const round4 = (n: number) => Math.round(n * 10 ** 4) / 10 ** 4;
|
|
31
|
-
const segmentValues = segment.slice(1)
|
|
32
|
-
const normalValues = normalSegment.slice(1)
|
|
31
|
+
const segmentValues = segment.slice(1) as number[];
|
|
32
|
+
const normalValues = normalSegment.slice(1) as number[];
|
|
33
33
|
const { x1: px1, y1: py1, x2: px2, y2: py2, x: px, y: py } = params;
|
|
34
34
|
let result = segment;
|
|
35
35
|
const [x, y] = normalValues.slice(-2);
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import midPoint from '../math/midPoint';
|
|
2
|
-
import type { CubicSegment } from '../types';
|
|
2
|
+
import type { CubicSegment, PointTuple } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Split a cubic-bezier segment into two.
|
|
6
6
|
*
|
|
7
7
|
* @param pts the cubic-bezier parameters
|
|
8
|
+
* @param ratio the cubic-bezier parameters
|
|
8
9
|
* @return two new cubic-bezier segments
|
|
9
10
|
*/
|
|
10
|
-
const splitCubic = (pts: number[]
|
|
11
|
-
const t =
|
|
12
|
-
const p0 = pts.slice(0, 2) as
|
|
13
|
-
const p1 = pts.slice(2, 4) as
|
|
14
|
-
const p2 = pts.slice(4, 6) as
|
|
15
|
-
const p3 = pts.slice(6, 8) as
|
|
11
|
+
const splitCubic = (pts: number[], ratio = 0.5): [CubicSegment, CubicSegment] => {
|
|
12
|
+
const t = ratio;
|
|
13
|
+
const p0 = pts.slice(0, 2) as PointTuple;
|
|
14
|
+
const p1 = pts.slice(2, 4) as PointTuple;
|
|
15
|
+
const p2 = pts.slice(4, 6) as PointTuple;
|
|
16
|
+
const p3 = pts.slice(6, 8) as PointTuple;
|
|
16
17
|
const p4 = midPoint(p0, p1, t);
|
|
17
18
|
const p5 = midPoint(p1, p2, t);
|
|
18
19
|
const p6 = midPoint(p2, p3, t);
|
package/src/process/splitPath.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import paramsParser from '../parser/paramsParser';
|
|
2
|
+
import type { AbsoluteCommand, HSegment, MSegment, PathArray, PointTuple, RelativeCommand, VSegment } from '../types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Split a path into an `Array` of sub-path strings.
|
|
@@ -7,20 +8,53 @@ import type { PathArray } from '../types';
|
|
|
7
8
|
* for visual consistency.
|
|
8
9
|
*
|
|
9
10
|
* @param pathInput the source `pathArray`
|
|
10
|
-
* @return
|
|
11
|
+
* @return an array with all sub-path strings
|
|
11
12
|
*/
|
|
12
13
|
const splitPath = (pathInput: PathArray): PathArray[] => {
|
|
13
14
|
const composite = [] as PathArray[];
|
|
14
15
|
let path: PathArray;
|
|
15
16
|
let pi = -1;
|
|
17
|
+
let x = 0;
|
|
18
|
+
let y = 0;
|
|
19
|
+
let mx = 0;
|
|
20
|
+
let my = 0;
|
|
21
|
+
const params = { ...paramsParser };
|
|
16
22
|
|
|
17
23
|
pathInput.forEach(seg => {
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
const [pathCommand] = seg;
|
|
25
|
+
const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
|
|
26
|
+
const relCommand = pathCommand.toLowerCase() as RelativeCommand;
|
|
27
|
+
const isRelative = pathCommand === relCommand;
|
|
28
|
+
const values = seg.slice(1) as number[];
|
|
29
|
+
|
|
30
|
+
if (absCommand === 'M') {
|
|
20
31
|
pi += 1;
|
|
32
|
+
[x, y] = values as PointTuple;
|
|
33
|
+
x += isRelative ? params.x : 0;
|
|
34
|
+
y += isRelative ? params.y : 0;
|
|
35
|
+
mx = x;
|
|
36
|
+
my = y;
|
|
37
|
+
path = [(isRelative ? [absCommand, mx, my] : seg) as MSegment];
|
|
21
38
|
} else {
|
|
22
|
-
|
|
39
|
+
if (absCommand === 'Z') {
|
|
40
|
+
x = mx;
|
|
41
|
+
y = my;
|
|
42
|
+
} else if (absCommand === 'H') {
|
|
43
|
+
[, x] = seg as HSegment;
|
|
44
|
+
x += isRelative ? params.x : /* istanbul ignore next @preserve */ 0;
|
|
45
|
+
} else if (absCommand === 'V') {
|
|
46
|
+
[, y] = seg as VSegment;
|
|
47
|
+
y += isRelative ? params.y : /* istanbul ignore next @preserve */ 0;
|
|
48
|
+
} else {
|
|
49
|
+
[x, y] = seg.slice(-2) as PointTuple;
|
|
50
|
+
x += isRelative ? params.x : 0;
|
|
51
|
+
y += isRelative ? params.y : 0;
|
|
52
|
+
}
|
|
53
|
+
path.push(seg);
|
|
23
54
|
}
|
|
55
|
+
|
|
56
|
+
params.x = x;
|
|
57
|
+
params.y = y;
|
|
24
58
|
composite[pi] = path;
|
|
25
59
|
});
|
|
26
60
|
|