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
|
@@ -1,13 +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
3
|
import defaultOptions from '../options/options';
|
|
9
|
-
import type { PathArray,
|
|
10
|
-
import type {
|
|
4
|
+
import type { AbsoluteArray, CSegment, PathArray, PointTuple, TransformObjectValues } from '../types';
|
|
5
|
+
import type { TransformObject } from '../interface';
|
|
6
|
+
import iterate from './iterate';
|
|
7
|
+
import parsePathString from '../parser/parsePathString';
|
|
8
|
+
import absolutizeSegment from './absolutizeSegment';
|
|
9
|
+
import segmentToCubic from './segmentToCubic';
|
|
10
|
+
import normalizeSegment from './normalizeSegment';
|
|
11
|
+
import paramsParser from '../parser/paramsParser';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Apply a 2D / 3D transformation to a `pathArray` instance.
|
|
@@ -19,110 +20,96 @@ import type { PathTransform, TransformObject } from '../interface';
|
|
|
19
20
|
* @param transform the transform functions `Object`
|
|
20
21
|
* @returns the resulted `pathArray`
|
|
21
22
|
*/
|
|
22
|
-
const transformPath = (
|
|
23
|
+
const transformPath = (pathInput: PathArray | string, transform?: Partial<TransformObject>) => {
|
|
23
24
|
let x = 0;
|
|
24
25
|
let y = 0;
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
|
|
26
|
+
let mx = 0;
|
|
27
|
+
let my = 0;
|
|
28
|
+
let lx = 0;
|
|
29
|
+
let ly = 0;
|
|
30
|
+
let j = 0;
|
|
31
|
+
let jj = 0;
|
|
32
|
+
let nx = 0;
|
|
33
|
+
let ny = 0;
|
|
34
|
+
let pathCommand = 'M';
|
|
35
|
+
// transform uses it's own set of params
|
|
36
|
+
const transformParams = { ...paramsParser };
|
|
37
|
+
const path = parsePathString(pathInput);
|
|
32
38
|
const transformProps = transform && Object.keys(transform);
|
|
33
39
|
|
|
34
40
|
// when used as a static method, invalidate somehow
|
|
35
|
-
if (!transform || (transformProps && !transformProps.length)) return
|
|
41
|
+
if (!transform || (transformProps && !transformProps.length)) return path;
|
|
36
42
|
|
|
37
|
-
const normalizedPath = normalizePath(absolutePath);
|
|
38
43
|
// transform origin is extremely important
|
|
39
44
|
if (!transform.origin) {
|
|
40
|
-
|
|
41
|
-
Object.assign(transform, { origin: defaultOrigin });
|
|
45
|
+
Object.assign(transform, { origin: defaultOptions.origin });
|
|
42
46
|
}
|
|
47
|
+
const origin = transform.origin as [number, number, number];
|
|
43
48
|
const matrixInstance = getSVGMatrix(transform as TransformObjectValues);
|
|
44
|
-
const { origin } = transform;
|
|
45
|
-
const params = { ...paramsParser };
|
|
46
|
-
let segment = [];
|
|
47
|
-
let seglen = 0;
|
|
48
|
-
let pathCommand = '';
|
|
49
|
-
let transformedPath = [] as PathTransform[];
|
|
50
|
-
const allPathCommands = [] as PathCommand[]; // needed for arc to curve transformation
|
|
51
|
-
|
|
52
|
-
if (!matrixInstance.isIdentity) {
|
|
53
|
-
for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
|
|
54
|
-
segment = absolutePath[i];
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
if (absolutePath[i]) [pathCommand] = segment;
|
|
50
|
+
if (matrixInstance.isIdentity) return path;
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/// ////////////////////////////////////////
|
|
63
|
-
allPathCommands[i] = pathCommand as PathCommand;
|
|
52
|
+
return iterate<AbsoluteArray>(path, (seg, _, i) => {
|
|
53
|
+
const absSegment = absolutizeSegment(seg, transformParams);
|
|
54
|
+
[pathCommand] = absSegment;
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
let result =
|
|
57
|
+
pathCommand === 'A'
|
|
58
|
+
? segmentToCubic(absSegment, transformParams)
|
|
59
|
+
: ['V', 'H'].includes(pathCommand)
|
|
60
|
+
? normalizeSegment(absSegment, transformParams)
|
|
61
|
+
: absSegment;
|
|
62
|
+
const isLongArc = result[0] === 'C' && result.length > 7;
|
|
63
|
+
const normalizedSegment = (isLongArc ? result.slice(0, 7) : result.slice(0)) as typeof result;
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
if (isLongArc) {
|
|
66
|
+
path.splice(i + 1, 0, ['C', ...result.slice(7)] as CSegment);
|
|
67
|
+
result = result.slice(0, 7) as CSegment;
|
|
68
|
+
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
if (result[0] === 'L') {
|
|
71
|
+
const values = result.slice(-2) as PointTuple;
|
|
72
|
+
[lx, ly] = projection2d(matrixInstance, values, origin);
|
|
73
|
+
|
|
74
|
+
/* istanbul ignore else @preserve */
|
|
75
|
+
if (x !== lx && y !== ly) {
|
|
76
|
+
result = ['L', lx, ly];
|
|
77
|
+
} else if (y === ly) {
|
|
78
|
+
result = ['H', lx];
|
|
79
|
+
} else if (x === lx) {
|
|
80
|
+
result = ['V', ly];
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
for (j = 1, jj = result.length; j < jj; j += 2) {
|
|
84
|
+
[lx, ly] = projection2d(matrixInstance, [+result[j], +result[j + 1]], origin);
|
|
85
|
+
result[j] = lx;
|
|
86
|
+
result[j + 1] = ly;
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
/// ////////////////////////////////////////
|
|
77
|
-
segment = normalizedPath[i];
|
|
78
|
-
seglen = segment.length;
|
|
79
|
-
|
|
80
|
-
params.x1 = +segment[seglen - 2];
|
|
81
|
-
params.y1 = +segment[seglen - 1];
|
|
82
|
-
params.x2 = +segment[seglen - 4] || params.x1;
|
|
83
|
-
params.y2 = +segment[seglen - 3] || params.y1;
|
|
84
|
-
|
|
85
|
-
const result = {
|
|
86
|
-
s: absolutePath[i],
|
|
87
|
-
c: absolutePath[i][0],
|
|
88
|
-
x: params.x1,
|
|
89
|
-
y: params.y1,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
transformedPath = [...transformedPath, ...[result]];
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else if (x === lx) {
|
|
107
|
-
segment = ['V', ly];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// now update x and y
|
|
111
|
-
x = lx;
|
|
112
|
-
y = ly;
|
|
113
|
-
|
|
114
|
-
return segment;
|
|
115
|
-
} else {
|
|
116
|
-
for (j = 1, jj = segment.length; j < jj; j += 2) {
|
|
117
|
-
[x, y] = projection2d(matrixInstance, [+segment[j], +segment[j + 1]], origin as [number, number, number]);
|
|
118
|
-
segment[j] = x;
|
|
119
|
-
segment[j + 1] = y;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return segment;
|
|
89
|
+
// now update x and y
|
|
90
|
+
x = lx;
|
|
91
|
+
y = ly;
|
|
92
|
+
|
|
93
|
+
if (pathCommand === 'Z') {
|
|
94
|
+
nx = mx;
|
|
95
|
+
ny = my;
|
|
96
|
+
} else {
|
|
97
|
+
[nx, ny] = normalizedSegment.slice(-2) as PointTuple;
|
|
98
|
+
if (pathCommand === 'M') {
|
|
99
|
+
mx = nx;
|
|
100
|
+
my = ny;
|
|
123
101
|
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
102
|
+
}
|
|
103
|
+
|
|
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
|
+
return result;
|
|
112
|
+
});
|
|
127
113
|
};
|
|
114
|
+
|
|
128
115
|
export default transformPath;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
LineAttr,
|
|
3
|
+
CircleAttr,
|
|
4
|
+
PolyAttr,
|
|
5
|
+
RectAttr,
|
|
6
|
+
EllipseAttr,
|
|
7
|
+
GlyphAttr,
|
|
8
|
+
TransformObject,
|
|
9
|
+
ParserParams,
|
|
10
|
+
} from './interface';
|
|
2
11
|
|
|
3
12
|
export type SpaceNumber =
|
|
4
13
|
| 0x1680
|
|
@@ -191,3 +200,33 @@ export type ShapeTags = 'line' | 'polyline' | 'polygon' | 'ellipse' | 'circle' |
|
|
|
191
200
|
export type ShapeOps = LineAttr | PolyAttr | PolyAttr | EllipseAttr | CircleAttr | RectAttr | GlyphAttr;
|
|
192
201
|
|
|
193
202
|
export type TransformObjectValues = Partial<TransformObject> & { origin: [number, number, number] };
|
|
203
|
+
|
|
204
|
+
export type Point = {
|
|
205
|
+
x: number;
|
|
206
|
+
y: number;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export type PointTuple = [number, number];
|
|
210
|
+
|
|
211
|
+
export type DerivedPoint = Point & { t: number };
|
|
212
|
+
export type QuadPoints = [Point, Point, Point, Point, Point, Point];
|
|
213
|
+
export type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
|
|
214
|
+
export type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
|
|
215
|
+
export type DerivedCubicPoints = [
|
|
216
|
+
DerivedPoint,
|
|
217
|
+
DerivedPoint,
|
|
218
|
+
DerivedPoint,
|
|
219
|
+
DerivedPoint,
|
|
220
|
+
DerivedPoint,
|
|
221
|
+
DerivedPoint,
|
|
222
|
+
DerivedPoint,
|
|
223
|
+
DerivedPoint,
|
|
224
|
+
];
|
|
225
|
+
export type QuadCoordinates = [number, number, number, number, number, number];
|
|
226
|
+
export type CubicCoordinates = [number, number, number, number, number, number, number, number];
|
|
227
|
+
export type ArcCoordinates = [number, number, number, number, number, number, number, number, number];
|
|
228
|
+
export type LineCoordinates = [number, number, number, number];
|
|
229
|
+
|
|
230
|
+
export type DeriveCallback = (t: number) => Point;
|
|
231
|
+
|
|
232
|
+
export type IteratorCallback = (segment: PathSegment, params: ParserParams, index: number) => PathSegment;
|
|
@@ -8,7 +8,7 @@ import getPropertiesAtPoint from './getPropertiesAtPoint';
|
|
|
8
8
|
* @param point the given point
|
|
9
9
|
* @returns the best match
|
|
10
10
|
*/
|
|
11
|
-
const getClosestPoint = (pathInput: string | PathArray, point: { x: number; y: number })
|
|
11
|
+
const getClosestPoint = (pathInput: string | PathArray, point: { x: number; y: number }) => {
|
|
12
12
|
return getPropertiesAtPoint(pathInput, point).closest;
|
|
13
13
|
};
|
|
14
14
|
|
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, QuadCoordinates } 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, ...(seg.slice(1) as
|
|
64
|
-
[x, y] = seg.slice(-2) as
|
|
63
|
+
len = getCubicSegArea(x, y, ...(seg.slice(1) as QuadCoordinates));
|
|
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,26 @@
|
|
|
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 {
|
|
4
|
+
ArcCoordinates,
|
|
5
|
+
CubicCoordinates,
|
|
6
|
+
LineCoordinates,
|
|
7
|
+
MSegment,
|
|
8
|
+
PathArray,
|
|
9
|
+
Point,
|
|
10
|
+
PointTuple,
|
|
11
|
+
QuadCoordinates,
|
|
12
|
+
} from '../types';
|
|
13
|
+
// import pathFactory from './pathFactory';
|
|
14
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
15
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
16
|
+
import parsePathString from '../parser/parsePathString';
|
|
17
|
+
import { getLineBBox } from '../math/lineTools';
|
|
18
|
+
import { getArcBBox } from '../math/arcTools';
|
|
19
|
+
import { getCubicBBox } from '../math/cubicTools';
|
|
20
|
+
import { getQuadBBox } from '../math/quadTools';
|
|
21
|
+
|
|
22
|
+
const getPathBBox = (pathInput: PathArray | string) => {
|
|
23
|
+
if (!pathInput) {
|
|
13
24
|
return {
|
|
14
25
|
x: 0,
|
|
15
26
|
y: 0,
|
|
@@ -23,11 +34,67 @@ const getPathBBox = (path?: PathArray | string): PathBBox => {
|
|
|
23
34
|
};
|
|
24
35
|
}
|
|
25
36
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
const path = parsePathString(pathInput);
|
|
38
|
+
let data = [] as number[];
|
|
39
|
+
let pathCommand = 'M';
|
|
40
|
+
let x = 0;
|
|
41
|
+
let y = 0;
|
|
42
|
+
let mx = 0;
|
|
43
|
+
let my = 0;
|
|
44
|
+
const MIN = [] as Point[];
|
|
45
|
+
const MAX = [] as Point[];
|
|
46
|
+
let min = { x, y };
|
|
47
|
+
let max = { x, y };
|
|
48
|
+
|
|
49
|
+
iterate(path, (seg, params) => {
|
|
50
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
51
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
52
|
+
[pathCommand] = normalSegment;
|
|
53
|
+
data = [x, y, ...normalSegment.slice(1)] as number[];
|
|
54
|
+
|
|
55
|
+
// this segment is always ZERO
|
|
56
|
+
/* istanbul ignore else @preserve */
|
|
57
|
+
if (pathCommand === 'M') {
|
|
58
|
+
// remember mx, my for Z
|
|
59
|
+
[, mx, my] = normalSegment as MSegment;
|
|
60
|
+
min = { x: mx, y: my };
|
|
61
|
+
max = { x: mx, y: my };
|
|
62
|
+
} else if (pathCommand === 'L') {
|
|
63
|
+
({ min, max } = getLineBBox(...(data as LineCoordinates)));
|
|
64
|
+
} else if (pathCommand === 'A') {
|
|
65
|
+
({ min, max } = getArcBBox(...(data as ArcCoordinates)));
|
|
66
|
+
} else if (pathCommand === 'C') {
|
|
67
|
+
({ min, max } = getCubicBBox(...(data as CubicCoordinates)));
|
|
68
|
+
} else if (pathCommand === 'Q') {
|
|
69
|
+
({ min, max } = getQuadBBox(...(data as QuadCoordinates)));
|
|
70
|
+
} else if (pathCommand === 'Z') {
|
|
71
|
+
data = [x, y, mx, my];
|
|
72
|
+
({ min, max } = getLineBBox(...(data as LineCoordinates)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
MIN.push(min);
|
|
76
|
+
MAX.push(max);
|
|
30
77
|
|
|
78
|
+
if (pathCommand === 'Z') {
|
|
79
|
+
x = mx;
|
|
80
|
+
y = my;
|
|
81
|
+
} else {
|
|
82
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
83
|
+
|
|
84
|
+
if (pathCommand === 'M') {
|
|
85
|
+
mx = x;
|
|
86
|
+
my = y;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
params.x = x;
|
|
90
|
+
params.y = y;
|
|
91
|
+
return normalSegment;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const xMin = Math.min(...MIN.map(n => n.x));
|
|
95
|
+
const xMax = Math.max(...MAX.map(n => n.x));
|
|
96
|
+
const yMin = Math.min(...MIN.map(n => n.y));
|
|
97
|
+
const yMax = Math.max(...MAX.map(n => n.y));
|
|
31
98
|
const width = xMax - xMin;
|
|
32
99
|
const height = yMax - yMin;
|
|
33
100
|
|
|
@@ -40,8 +107,9 @@ const getPathBBox = (path?: PathArray | string): PathBBox => {
|
|
|
40
107
|
y2: yMax,
|
|
41
108
|
cx: xMin + width / 2,
|
|
42
109
|
cy: yMin + height / 2,
|
|
43
|
-
// an
|
|
110
|
+
// an estimated guess
|
|
44
111
|
cz: Math.max(width, height) + Math.min(width, height) / 2,
|
|
45
|
-
};
|
|
112
|
+
} satisfies PathBBox;
|
|
46
113
|
};
|
|
114
|
+
|
|
47
115
|
export default getPathBBox;
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import DISTANCE_EPSILON from './distanceEpsilon';
|
|
2
|
+
import parsePathString from '../parser/parsePathString';
|
|
3
|
+
import type {
|
|
4
|
+
ArcCoordinates,
|
|
5
|
+
CubicCoordinates,
|
|
6
|
+
LineCoordinates,
|
|
7
|
+
MSegment,
|
|
8
|
+
PathArray,
|
|
9
|
+
PointTuple,
|
|
10
|
+
QuadCoordinates,
|
|
11
|
+
} from '../types';
|
|
12
|
+
import iterate from '../process/iterate';
|
|
13
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
14
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
15
|
+
import { getLineLength, getPointAtLineLength } from '../math/lineTools';
|
|
16
|
+
import { getArcLength, getPointAtArcLength } from '../math/arcTools';
|
|
17
|
+
import { getCubicLength, getPointAtCubicLength } from '../math/cubicTools';
|
|
18
|
+
import { getQuadLength, getPointAtQuadLength } from '../math/quadTools';
|
|
3
19
|
|
|
4
20
|
/**
|
|
5
21
|
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
@@ -8,7 +24,85 @@ import pathLengthFactory from './pathLengthFactory';
|
|
|
8
24
|
* @param distance the length of the shape to look at
|
|
9
25
|
* @returns the requested {x, y} point coordinates
|
|
10
26
|
*/
|
|
11
|
-
const getPointAtLength = (pathInput: string | PathArray, distance
|
|
12
|
-
|
|
27
|
+
const getPointAtLength = (pathInput: string | PathArray, distance?: number) => {
|
|
28
|
+
const path = parsePathString(pathInput);
|
|
29
|
+
let isM = false;
|
|
30
|
+
let data = [] as number[];
|
|
31
|
+
let pathCommand = 'M';
|
|
32
|
+
let x = 0;
|
|
33
|
+
let y = 0;
|
|
34
|
+
let [mx, my] = path[0].slice(1) as PointTuple;
|
|
35
|
+
const distanceIsNumber = typeof distance === 'number';
|
|
36
|
+
let point = { x: mx, y: my };
|
|
37
|
+
let length = 0;
|
|
38
|
+
let POINT = point;
|
|
39
|
+
let totalLength = 0;
|
|
40
|
+
|
|
41
|
+
if (!distanceIsNumber) return point;
|
|
42
|
+
|
|
43
|
+
if (distance < DISTANCE_EPSILON) {
|
|
44
|
+
POINT = point;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
iterate(path, (seg, params) => {
|
|
48
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
49
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
50
|
+
[pathCommand] = normalSegment;
|
|
51
|
+
isM = pathCommand === 'M';
|
|
52
|
+
data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
|
|
53
|
+
|
|
54
|
+
// this segment is always ZERO
|
|
55
|
+
/* istanbul ignore else @preserve */
|
|
56
|
+
if (isM) {
|
|
57
|
+
// remember mx, my for Z
|
|
58
|
+
[, mx, my] = seg as MSegment;
|
|
59
|
+
point = { x: mx, y: my };
|
|
60
|
+
length = 0;
|
|
61
|
+
} else if (pathCommand === 'L') {
|
|
62
|
+
point = getPointAtLineLength(...(data as LineCoordinates), distance - totalLength);
|
|
63
|
+
length = getLineLength(...(data as LineCoordinates));
|
|
64
|
+
} else if (pathCommand === 'A') {
|
|
65
|
+
point = getPointAtArcLength(...(data as ArcCoordinates), distance - totalLength);
|
|
66
|
+
length = getArcLength(...(data as ArcCoordinates));
|
|
67
|
+
} else if (pathCommand === 'C') {
|
|
68
|
+
point = getPointAtCubicLength(...(data as CubicCoordinates), distance - totalLength);
|
|
69
|
+
length = getCubicLength(...(data as CubicCoordinates));
|
|
70
|
+
} else if (pathCommand === 'Q') {
|
|
71
|
+
point = getPointAtQuadLength(...(data as QuadCoordinates), distance - totalLength);
|
|
72
|
+
length = getQuadLength(...(data as QuadCoordinates));
|
|
73
|
+
} else if (pathCommand === 'Z') {
|
|
74
|
+
data = [x, y, mx, my];
|
|
75
|
+
point = { x: mx, y: my };
|
|
76
|
+
length = getLineLength(...(data as LineCoordinates));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (totalLength < distance && totalLength + length >= distance) {
|
|
80
|
+
POINT = point;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
totalLength += length;
|
|
84
|
+
if (pathCommand === 'Z') {
|
|
85
|
+
x = mx;
|
|
86
|
+
y = my;
|
|
87
|
+
} else {
|
|
88
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
89
|
+
|
|
90
|
+
if (pathCommand === 'M') {
|
|
91
|
+
mx = x;
|
|
92
|
+
my = y;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
params.x = x;
|
|
96
|
+
params.y = y;
|
|
97
|
+
return normalSegment;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// native `getPointAtLength` behavior when the given distance
|
|
101
|
+
// is higher than total length
|
|
102
|
+
if (distance > totalLength - DISTANCE_EPSILON) {
|
|
103
|
+
POINT = { x, y };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return POINT;
|
|
13
107
|
};
|
|
14
108
|
export default getPointAtLength;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PathArray, PathSegment } from '../types';
|
|
1
|
+
import type { PointTuple, PathArray, PathSegment } from '../types';
|
|
2
2
|
import type { SegmentProperties } from '../interface';
|
|
3
3
|
import parsePathString from '../parser/parsePathString';
|
|
4
4
|
import getTotalLength from './getTotalLength';
|
|
@@ -14,13 +14,13 @@ import getTotalLength from './getTotalLength';
|
|
|
14
14
|
const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number): SegmentProperties => {
|
|
15
15
|
const pathArray = parsePathString(pathInput);
|
|
16
16
|
|
|
17
|
-
let pathTemp =
|
|
17
|
+
let pathTemp = pathArray.slice(0) as PathArray;
|
|
18
18
|
let pathLength = getTotalLength(pathTemp);
|
|
19
19
|
let index = pathTemp.length - 1;
|
|
20
20
|
let lengthAtSegment = 0;
|
|
21
21
|
let length = 0;
|
|
22
22
|
let segment = pathArray[0] as PathSegment;
|
|
23
|
-
const [x, y] = segment.slice(-2) as
|
|
23
|
+
const [x, y] = segment.slice(-2) as PointTuple;
|
|
24
24
|
const point = { x, y };
|
|
25
25
|
|
|
26
26
|
// If the path is empty, return 0.
|
|
@@ -64,4 +64,5 @@ const getPropertiesAtLength = (pathInput: string | PathArray, distance?: number)
|
|
|
64
64
|
|
|
65
65
|
return segments.find(({ lengthAtSegment: l }) => l <= distance) as SegmentProperties;
|
|
66
66
|
};
|
|
67
|
+
|
|
67
68
|
export default getPropertiesAtLength;
|
|
@@ -36,6 +36,7 @@ const getPropertiesAtPoint = (pathInput: string | PathArray, point: { x: number;
|
|
|
36
36
|
for (let scanLength = 0; scanLength <= pathLength; scanLength += precision) {
|
|
37
37
|
scan = getPointAtLength(normalPath, scanLength);
|
|
38
38
|
scanDistance = distanceTo(scan);
|
|
39
|
+
|
|
39
40
|
if (scanDistance < bestDistance) {
|
|
40
41
|
closest = scan;
|
|
41
42
|
bestLength = scanLength;
|
|
@@ -52,13 +53,14 @@ const getPropertiesAtPoint = (pathInput: string | PathArray, point: { x: number;
|
|
|
52
53
|
let beforeDistance = 0;
|
|
53
54
|
let afterDistance = 0;
|
|
54
55
|
|
|
55
|
-
while (precision > 0.
|
|
56
|
+
while (precision > 0.000001) {
|
|
56
57
|
beforeLength = bestLength - precision;
|
|
57
58
|
before = getPointAtLength(normalPath, beforeLength);
|
|
58
59
|
beforeDistance = distanceTo(before);
|
|
59
60
|
afterLength = bestLength + precision;
|
|
60
61
|
after = getPointAtLength(normalPath, afterLength);
|
|
61
62
|
afterDistance = distanceTo(after);
|
|
63
|
+
|
|
62
64
|
if (beforeLength >= 0 && beforeDistance < bestDistance) {
|
|
63
65
|
closest = before;
|
|
64
66
|
bestLength = beforeLength;
|
|
@@ -70,6 +72,7 @@ const getPropertiesAtPoint = (pathInput: string | PathArray, point: { x: number;
|
|
|
70
72
|
} else {
|
|
71
73
|
precision /= 2;
|
|
72
74
|
}
|
|
75
|
+
if (precision < 0.00001) break;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const segment = getPropertiesAtLength(path, bestLength);
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import parsePathString from '../parser/parsePathString';
|
|
2
|
+
import type {
|
|
3
|
+
ArcCoordinates,
|
|
4
|
+
CubicCoordinates,
|
|
5
|
+
LineCoordinates,
|
|
6
|
+
MSegment,
|
|
7
|
+
PathArray,
|
|
8
|
+
PointTuple,
|
|
9
|
+
QuadCoordinates,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { getLineLength } from '../math/lineTools';
|
|
12
|
+
import { getArcLength } from '../math/arcTools';
|
|
13
|
+
import { getCubicLength } from '../math/cubicTools';
|
|
14
|
+
import { getQuadLength } from '../math/quadTools';
|
|
15
|
+
import iterate from '../process/iterate';
|
|
16
|
+
import absolutizeSegment from '../process/absolutizeSegment';
|
|
17
|
+
import normalizeSegment from '../process/normalizeSegment';
|
|
18
|
+
// import pathFactory from './pathFactory';
|
|
3
19
|
|
|
4
20
|
/**
|
|
5
21
|
* Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
|
|
@@ -10,7 +26,58 @@ import pathLengthFactory from './pathLengthFactory';
|
|
|
10
26
|
* @param pathInput the target `pathArray`
|
|
11
27
|
* @returns the shape total length
|
|
12
28
|
*/
|
|
13
|
-
const getTotalLength = (pathInput: string | PathArray)
|
|
14
|
-
|
|
29
|
+
const getTotalLength = (pathInput: string | PathArray) => {
|
|
30
|
+
const path = parsePathString(pathInput);
|
|
31
|
+
let isM = false;
|
|
32
|
+
let data = [] as number[];
|
|
33
|
+
let pathCommand = 'M';
|
|
34
|
+
let x = 0;
|
|
35
|
+
let y = 0;
|
|
36
|
+
let mx = 0;
|
|
37
|
+
let my = 0;
|
|
38
|
+
let totalLength = 0;
|
|
39
|
+
|
|
40
|
+
iterate(path, (seg, params) => {
|
|
41
|
+
const absoluteSegment = absolutizeSegment(seg, params);
|
|
42
|
+
const normalSegment = normalizeSegment(absoluteSegment, params);
|
|
43
|
+
[pathCommand] = normalSegment;
|
|
44
|
+
isM = pathCommand === 'M';
|
|
45
|
+
data = !isM ? [x, y, ...(normalSegment.slice(1) as number[])] : data;
|
|
46
|
+
|
|
47
|
+
// this segment is always ZERO
|
|
48
|
+
/* istanbul ignore else @preserve */
|
|
49
|
+
if (isM) {
|
|
50
|
+
// remember mx, my for Z
|
|
51
|
+
[, mx, my] = seg as MSegment;
|
|
52
|
+
} else if (pathCommand === 'L') {
|
|
53
|
+
totalLength += getLineLength(...(data as LineCoordinates));
|
|
54
|
+
} else if (pathCommand === 'A') {
|
|
55
|
+
totalLength += getArcLength(...(data as ArcCoordinates));
|
|
56
|
+
} else if (pathCommand === 'C') {
|
|
57
|
+
totalLength += getCubicLength(...(data as CubicCoordinates));
|
|
58
|
+
} else if (pathCommand === 'Q') {
|
|
59
|
+
totalLength += getQuadLength(...(data as QuadCoordinates));
|
|
60
|
+
} else if (pathCommand === 'Z') {
|
|
61
|
+
data = [x, y, mx, my];
|
|
62
|
+
totalLength += getLineLength(...(data as LineCoordinates));
|
|
63
|
+
}
|
|
64
|
+
if (pathCommand === 'Z') {
|
|
65
|
+
x = mx;
|
|
66
|
+
y = my;
|
|
67
|
+
} else {
|
|
68
|
+
[x, y] = normalSegment.slice(-2) as PointTuple;
|
|
69
|
+
|
|
70
|
+
if (isM) {
|
|
71
|
+
mx = x;
|
|
72
|
+
my = y;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
params.x = x;
|
|
76
|
+
params.y = y;
|
|
77
|
+
return normalSegment;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return totalLength;
|
|
15
81
|
};
|
|
82
|
+
|
|
16
83
|
export default getTotalLength;
|