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
|
@@ -11,11 +11,11 @@ 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;
|
|
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): MSegment |
|
|
|
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
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
NormalSegment,
|
|
6
|
-
PathCommand,
|
|
7
|
-
ShortSegment,
|
|
8
|
-
SSegment,
|
|
9
|
-
TSegment,
|
|
10
|
-
VSegment,
|
|
11
|
-
ZSegment,
|
|
12
|
-
} from '../types';
|
|
1
|
+
import defaultOptions from '../options/options';
|
|
2
|
+
import type { ParserParams } from '../interface';
|
|
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
|
|
32
|
-
const normalValues = normalSegment.slice(1)
|
|
33
|
-
const { x1
|
|
34
|
-
|
|
35
|
-
const
|
|
22
|
+
const { round: defaultRound } = defaultOptions;
|
|
23
|
+
const round = typeof defaultRound === 'number' ? defaultRound : /* istanbul ignore next */ 4;
|
|
24
|
+
const normalValues = normalSegment.slice(1) as number[];
|
|
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;
|
|
@@ -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);
|
|
@@ -21,8 +22,8 @@ const splitCubic = (pts: number[] /* , ratio */): [CubicSegment, CubicSegment] =
|
|
|
21
22
|
const p9 = midPoint(p7, p8, t);
|
|
22
23
|
|
|
23
24
|
return [
|
|
24
|
-
['C',
|
|
25
|
-
['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]],
|
|
26
27
|
];
|
|
27
28
|
};
|
|
28
29
|
export default splitCubic;
|
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 {
|
|
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
|
+
}
|
|
22
53
|
path.push(seg);
|
|
23
54
|
}
|
|
55
|
+
|
|
56
|
+
params.x = x;
|
|
57
|
+
params.y = y;
|
|
24
58
|
composite[pi] = path;
|
|
25
59
|
});
|
|
26
60
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import normalizePath from './normalizePath';
|
|
2
|
-
// import pathToAbsolute from '../convert/pathToAbsolute';
|
|
3
|
-
// import segmentToCubic from './segmentToCubic';
|
|
4
|
-
// import fixArc from './fixArc';
|
|
5
1
|
import getSVGMatrix from './getSVGMatrix';
|
|
6
2
|
import projection2d from './projection2d';
|
|
7
|
-
import paramsParser from '../parser/paramsParser';
|
|
8
|
-
import replaceArc from './replaceArc';
|
|
9
3
|
import defaultOptions from '../options/options';
|
|
10
|
-
import type { AbsoluteArray, PathArray, TransformObjectValues } from '../types';
|
|
11
|
-
import type {
|
|
4
|
+
import type { AbsoluteArray, AbsoluteSegment, CSegment, LSegment, PathArray, TransformObjectValues } from '../types';
|
|
5
|
+
import type { TransformObject } from '../interface';
|
|
6
|
+
import iterate from './iterate';
|
|
7
|
+
import parsePathString from '../parser/parsePathString';
|
|
8
|
+
import segmentToCubic from './segmentToCubic';
|
|
9
|
+
import normalizeSegment from './normalizeSegment';
|
|
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.
|
|
@@ -20,89 +20,91 @@ import type { PathTransform, TransformObject } from '../interface';
|
|
|
20
20
|
* @param transform the transform functions `Object`
|
|
21
21
|
* @returns the resulted `pathArray`
|
|
22
22
|
*/
|
|
23
|
-
const transformPath = (
|
|
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
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const
|
|
27
|
+
// new x and y transformed
|
|
28
|
+
let lx = 0;
|
|
29
|
+
let ly = 0;
|
|
30
|
+
// segment params iteration index and length
|
|
31
|
+
let j = 0;
|
|
32
|
+
let jj = 0;
|
|
33
|
+
let pathCommand = 'M';
|
|
34
|
+
// transform uses it's own set of params
|
|
35
|
+
const transformParams = { ...paramsParser };
|
|
36
|
+
const path = parsePathString(pathInput);
|
|
36
37
|
const transformProps = transform && Object.keys(transform);
|
|
37
38
|
|
|
38
39
|
// when used as a static method, invalidate somehow
|
|
39
|
-
if (!transform || (transformProps && !transformProps.length)) return
|
|
40
|
+
if (!transform || (transformProps && !transformProps.length)) return path.slice(0) as typeof path;
|
|
40
41
|
|
|
41
|
-
const normalizedPath = normalizePath(absolutePath);
|
|
42
42
|
// transform origin is extremely important
|
|
43
43
|
if (!transform.origin) {
|
|
44
|
-
|
|
45
|
-
Object.assign(transform, { origin: defaultOrigin });
|
|
44
|
+
Object.assign(transform, { origin: defaultOptions.origin });
|
|
46
45
|
}
|
|
46
|
+
const origin = transform.origin as [number, number, number];
|
|
47
47
|
const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
|
|
48
|
-
const { origin } = transform;
|
|
49
|
-
const params = { ...paramsParser };
|
|
50
|
-
let segment = [];
|
|
51
|
-
let seglen = 0;
|
|
52
|
-
let pathCommand = '';
|
|
53
|
-
const transformedPath = [] as PathTransform[];
|
|
54
48
|
|
|
55
|
-
if (
|
|
56
|
-
for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
|
|
57
|
-
segment = normalizedPath[i];
|
|
58
|
-
seglen = segment.length;
|
|
49
|
+
if (matrixInstance.isIdentity) return path.slice(0) as typeof path;
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
let result =
|
|
62
|
+
absCommand === 'A'
|
|
63
|
+
? segmentToCubic(absoluteSegment, transformParams)
|
|
64
|
+
: ['V', 'H'].includes(absCommand)
|
|
65
|
+
? normalizeSegment(absoluteSegment, transformParams)
|
|
66
|
+
: absoluteSegment;
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
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;
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
pathCommand
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
[lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin as [number, number, number]);
|
|
73
|
+
if (isLongArc) {
|
|
74
|
+
path.splice(index + 1, 0, ['C' as typeof pathCommand | number].concat(result.slice(7)) as CSegment);
|
|
75
|
+
result = tempSegment as CSegment;
|
|
76
|
+
}
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
segment = ['L', lx, ly];
|
|
84
|
-
} else if (y === ly) {
|
|
85
|
-
segment = ['H', lx];
|
|
86
|
-
} else if (x === lx) {
|
|
87
|
-
segment = ['V', ly];
|
|
88
|
-
}
|
|
78
|
+
if (pathCommand === 'L') {
|
|
79
|
+
[lx, ly] = projection2d(matrixInstance, [(result as LSegment)[1], (result as LSegment)[2]], origin);
|
|
89
80
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
/* istanbul ignore else @preserve */
|
|
82
|
+
if (x !== lx && y !== ly) {
|
|
83
|
+
result = ['L', lx, ly];
|
|
84
|
+
} else if (y === ly) {
|
|
85
|
+
result = ['H', lx];
|
|
86
|
+
} else if (x === lx) {
|
|
87
|
+
result = ['V', ly];
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
for (j = 1, jj = result.length; j < jj; j += 2) {
|
|
91
|
+
[lx, ly] = projection2d(matrixInstance, [+result[j], +result[j + 1]], origin);
|
|
92
|
+
result[j] = lx;
|
|
93
|
+
result[j + 1] = ly;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// now update x and y
|
|
97
|
+
x = lx;
|
|
98
|
+
y = ly;
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
segment[j + 1] = y;
|
|
100
|
-
}
|
|
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;
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}) as PathArray;
|
|
105
|
-
}
|
|
106
|
-
return absolutePath.slice(0) as AbsoluteArray;
|
|
106
|
+
return result;
|
|
107
|
+
});
|
|
107
108
|
};
|
|
109
|
+
|
|
108
110
|
export default transformPath;
|
package/src/types.ts
CHANGED
|
@@ -196,3 +196,33 @@ export type Point = {
|
|
|
196
196
|
x: number;
|
|
197
197
|
y: number;
|
|
198
198
|
};
|
|
199
|
+
|
|
200
|
+
export type PointTuple = [number, number];
|
|
201
|
+
|
|
202
|
+
export type DerivedPoint = Point & { t: number };
|
|
203
|
+
export type QuadPoints = [Point, Point, Point, Point, Point, Point];
|
|
204
|
+
export type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
|
|
205
|
+
export type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
206
|
+
export type DerivedCubicPoints = [
|
|
207
|
+
DerivedPoint,
|
|
208
|
+
DerivedPoint,
|
|
209
|
+
DerivedPoint,
|
|
210
|
+
DerivedPoint,
|
|
211
|
+
DerivedPoint,
|
|
212
|
+
DerivedPoint,
|
|
213
|
+
DerivedPoint,
|
|
214
|
+
DerivedPoint,
|
|
215
|
+
];
|
|
216
|
+
export type QuadCoordinates = [number, number, number, number, number, number];
|
|
217
|
+
export type CubicCoordinates = [number, number, number, number, number, number, number, number];
|
|
218
|
+
export type ArcCoordinates = [number, number, number, number, number, number, number, number, number];
|
|
219
|
+
export type LineCoordinates = [number, number, number, number];
|
|
220
|
+
|
|
221
|
+
export type DeriveCallback = (t: number) => Point;
|
|
222
|
+
|
|
223
|
+
export type IteratorCallback = (
|
|
224
|
+
segment: PathSegment,
|
|
225
|
+
index: number,
|
|
226
|
+
lastX: number,
|
|
227
|
+
lastY: number,
|
|
228
|
+
) => PathSegment | false | void | undefined;
|
package/src/util/getPathArea.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pathToCurve from '../convert/pathToCurve';
|
|
2
|
-
import type { PathArray } from '../types';
|
|
2
|
+
import type { PointTuple, PathArray } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns the area of a single cubic-bezier segment.
|
|
@@ -60,8 +60,8 @@ const getPathArea = (path: PathArray) => {
|
|
|
60
60
|
[, x, y] = seg;
|
|
61
61
|
return 0;
|
|
62
62
|
default:
|
|
63
|
-
len = getCubicSegArea(x, y,
|
|
64
|
-
[x, y] = seg.slice(-2) as
|
|
63
|
+
len = getCubicSegArea(x, y, seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
|
|
64
|
+
[x, y] = seg.slice(-2) as PointTuple;
|
|
65
65
|
return len;
|
|
66
66
|
}
|
|
67
67
|
})
|
package/src/util/getPathBBox.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import iterate from '../process/iterate';
|
|
2
|
+
import { PathBBox } from '../interface';
|
|
3
|
+
import { MSegment, PathArray, Point } from '../types';
|
|
4
|
+
import { getLineBBox } from '../math/lineTools';
|
|
5
|
+
import { getArcBBox } from '../math/arcTools';
|
|
6
|
+
import { getCubicBBox } from '../math/cubicTools';
|
|
7
|
+
import { getQuadBBox } from '../math/quadTools';
|
|
8
|
+
import parsePathString from '../parser/parsePathString';
|
|
9
|
+
import paramsParser from '../parser/paramsParser';
|
|
10
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
11
|
+
|
|
12
|
+
const getPathBBox = (pathInput: PathArray | string) => {
|
|
13
|
+
if (!pathInput) {
|
|
13
14
|
return {
|
|
14
15
|
x: 0,
|
|
15
16
|
y: 0,
|
|
@@ -23,12 +24,60 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
|
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const path = parsePathString(pathInput);
|
|
28
|
+
let data = [] as number[];
|
|
29
|
+
let pathCommand = 'M';
|
|
30
|
+
const x = 0;
|
|
31
|
+
const y = 0;
|
|
32
|
+
let mx = 0;
|
|
33
|
+
let my = 0;
|
|
34
|
+
const MIN = [] as Point[];
|
|
35
|
+
const MAX = [] as Point[];
|
|
36
|
+
let min = { x, y };
|
|
37
|
+
let max = { x, y };
|
|
38
|
+
const params = { ...paramsParser };
|
|
39
|
+
|
|
40
|
+
iterate(path, (seg, _, lastX, lastY) => {
|
|
41
|
+
params.x = lastX;
|
|
42
|
+
params.y = lastY;
|
|
43
|
+
const result = normalizeSegment(seg, params);
|
|
44
|
+
[pathCommand] = result;
|
|
45
|
+
data = [lastX, lastY].concat(result.slice(1) as number[]);
|
|
46
|
+
|
|
47
|
+
// this segment is always ZERO
|
|
48
|
+
/* istanbul ignore else @preserve */
|
|
49
|
+
if (pathCommand === 'M') {
|
|
50
|
+
// remember mx, my for Z
|
|
51
|
+
[, mx, my] = result as MSegment;
|
|
52
|
+
min = { x: mx, y: my };
|
|
53
|
+
max = { x: mx, y: my };
|
|
54
|
+
} else if (pathCommand === 'L') {
|
|
55
|
+
({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
|
|
56
|
+
} else if (pathCommand === 'A') {
|
|
57
|
+
({ min, max } = getArcBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]));
|
|
58
|
+
} else if (pathCommand === 'C') {
|
|
59
|
+
({ min, max } = getCubicBBox(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]));
|
|
60
|
+
} else if (pathCommand === 'Q') {
|
|
61
|
+
({ min, max } = getQuadBBox(data[0], data[1], data[2], data[3], data[4], data[5]));
|
|
62
|
+
} else if (pathCommand === 'Z') {
|
|
63
|
+
data = [lastX, lastY, mx, my];
|
|
64
|
+
({ min, max } = getLineBBox(data[0], data[1], data[2], data[3]));
|
|
65
|
+
}
|
|
31
66
|
|
|
67
|
+
MIN.push(min);
|
|
68
|
+
MAX.push(max);
|
|
69
|
+
|
|
70
|
+
const seglen = result.length;
|
|
71
|
+
params.x1 = +result[seglen - 2];
|
|
72
|
+
params.y1 = +result[seglen - 1];
|
|
73
|
+
params.x2 = +result[seglen - 4] || params.x1;
|
|
74
|
+
params.y2 = +result[seglen - 3] || params.y1;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const xMin = Math.min(...MIN.map(n => n.x));
|
|
78
|
+
const xMax = Math.max(...MAX.map(n => n.x));
|
|
79
|
+
const yMin = Math.min(...MIN.map(n => n.y));
|
|
80
|
+
const yMax = Math.max(...MAX.map(n => n.y));
|
|
32
81
|
const width = xMax - xMin;
|
|
33
82
|
const height = yMax - yMin;
|
|
34
83
|
|
|
@@ -41,8 +90,9 @@ const getPathBBox = (path: PathArray | string): PathBBox => {
|
|
|
41
90
|
y2: yMax,
|
|
42
91
|
cx: xMin + width / 2,
|
|
43
92
|
cy: yMin + height / 2,
|
|
44
|
-
// an
|
|
93
|
+
// an estimated guess
|
|
45
94
|
cz: Math.max(width, height) + Math.min(width, height) / 2,
|
|
46
|
-
};
|
|
95
|
+
} satisfies PathBBox;
|
|
47
96
|
};
|
|
97
|
+
|
|
48
98
|
export default getPathBBox;
|