svg-path-commander 2.0.10 → 2.1.0

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.
Files changed (59) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/README.md +4 -4
  3. package/dist/svg-path-commander.cjs +1 -1
  4. package/dist/svg-path-commander.cjs.map +1 -1
  5. package/dist/svg-path-commander.d.ts +137 -30
  6. package/dist/svg-path-commander.js +1 -1
  7. package/dist/svg-path-commander.js.map +1 -1
  8. package/dist/svg-path-commander.mjs +868 -698
  9. package/dist/svg-path-commander.mjs.map +1 -1
  10. package/package.json +20 -22
  11. package/src/convert/pathToAbsolute.ts +1 -1
  12. package/src/convert/pathToCurve.ts +1 -1
  13. package/src/convert/pathToRelative.ts +1 -1
  14. package/src/index.ts +30 -26
  15. package/src/interface.ts +32 -32
  16. package/src/math/arcTools.ts +217 -0
  17. package/src/math/bezier.ts +261 -0
  18. package/src/math/cubicTools.ts +81 -0
  19. package/src/math/lineTools.ts +52 -0
  20. package/src/math/quadTools.ts +79 -0
  21. package/src/parser/isMoveCommand.ts +17 -0
  22. package/src/parser/parsePathString.ts +1 -1
  23. package/src/parser/scanSegment.ts +12 -3
  24. package/src/process/normalizePath.ts +1 -1
  25. package/src/process/replaceArc.ts +52 -0
  26. package/src/process/splitPath.ts +1 -1
  27. package/src/process/transformPath.ts +14 -34
  28. package/src/types.ts +5 -0
  29. package/src/util/distanceEpsilon.ts +3 -0
  30. package/src/util/getClosestPoint.ts +1 -1
  31. package/src/util/getPathBBox.ts +4 -3
  32. package/src/util/getPointAtLength.ts +3 -3
  33. package/src/util/getPropertiesAtLength.ts +2 -1
  34. package/src/util/getPropertiesAtPoint.ts +4 -1
  35. package/src/util/getTotalLength.ts +2 -2
  36. package/src/util/isPointInStroke.ts +2 -1
  37. package/src/util/pathFactory.ts +130 -0
  38. package/src/util/shapeToPathArray.ts +8 -4
  39. package/test/class.test.ts +501 -0
  40. package/test/fixtures/getMarkup.ts +17 -0
  41. package/{cypress → test}/fixtures/shapes.js +18 -18
  42. package/{cypress → test}/fixtures/simpleShapes.js +6 -6
  43. package/test/static.test.ts +304 -0
  44. package/tsconfig.json +9 -4
  45. package/{vite.config.ts → vite.config.mts} +10 -1
  46. package/vitest.config-ui.mts +26 -0
  47. package/vitest.config.mts +26 -0
  48. package/cypress/e2e/svg-path-commander.spec.ts +0 -868
  49. package/cypress/plugins/esbuild-istanbul.ts +0 -50
  50. package/cypress/plugins/tsCompile.ts +0 -34
  51. package/cypress/support/commands.ts +0 -37
  52. package/cypress/support/e2e.ts +0 -21
  53. package/cypress/test.html +0 -36
  54. package/src/util/pathLengthFactory.ts +0 -114
  55. package/src/util/segmentArcFactory.ts +0 -219
  56. package/src/util/segmentCubicFactory.ts +0 -114
  57. package/src/util/segmentLineFactory.ts +0 -45
  58. package/src/util/segmentQuadFactory.ts +0 -109
  59. /package/{cypress/fixtures/shapeObjects.js → test/fixtures/shapeObjects.ts} +0 -0
@@ -0,0 +1,261 @@
1
+ import { type Point } from '../types';
2
+
3
+ /**
4
+ * Tools from bezier.js by Mike 'Pomax' Kamermans
5
+ * @see https://github.com/Pomax/bezierjs
6
+ */
7
+
8
+ const ZERO = { x: 0, y: 0 };
9
+
10
+ const Tvalues = [
11
+ -0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909,
12
+ -0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404,
13
+ -0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864,
14
+ -0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524,
15
+ -0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107,
16
+ -0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696,
17
+ -0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232,
18
+ -0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761,
19
+ -0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873,
20
+ -0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548,
21
+ -0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411,
22
+ -0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745,
23
+ ];
24
+
25
+ const Cvalues = [
26
+ 0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517,
27
+ 0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264,
28
+ 0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607,
29
+ 0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622,
30
+ 0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946,
31
+ 0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279,
32
+ 0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185,
33
+ 0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193,
34
+ 0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412,
35
+ 0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593,
36
+ 0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491,
37
+ 0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759,
38
+ ];
39
+
40
+ type DerivedPoint = Point & { t: number };
41
+ type QuadPoints = [Point, Point, Point, Point, Point, Point];
42
+ type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
43
+ type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
44
+ type DerivedCubicPoints = [
45
+ DerivedPoint,
46
+ DerivedPoint,
47
+ DerivedPoint,
48
+ DerivedPoint,
49
+ DerivedPoint,
50
+ DerivedPoint,
51
+ DerivedPoint,
52
+ DerivedPoint,
53
+ ];
54
+ export type QuadCoordinates = [number, number, number, number, number, number];
55
+ export type CubicCoordinates = [number, number, number, number, number, number, number, number];
56
+
57
+ type DeriveCallback = (t: number) => Point;
58
+
59
+ /**
60
+ *
61
+ * @param points
62
+ * @returns
63
+ */
64
+ const derive = (points: QuadPoints | CubicPoints) => {
65
+ const dpoints = [] as (DerivedCubicPoints | DerivedQuadPoints)[];
66
+ for (let p = points, d = p.length, c = d - 1; d > 1; d -= 1, c -= 1) {
67
+ const list = [] as unknown as DerivedCubicPoints | DerivedQuadPoints;
68
+ for (let j = 0; j < c; j += 1) {
69
+ list.push({
70
+ x: c * (p[j + 1].x - p[j].x),
71
+ y: c * (p[j + 1].y - p[j].y),
72
+ t: 0,
73
+ });
74
+ }
75
+ dpoints.push(list);
76
+ p = list;
77
+ }
78
+ return dpoints;
79
+ };
80
+
81
+ /**
82
+ *
83
+ * @param points
84
+ * @param t
85
+ */
86
+ const compute = (points: DerivedQuadPoints | DerivedCubicPoints, t: number) => {
87
+ // shortcuts
88
+ /* istanbul ignore next @preserve */
89
+ if (t === 0) {
90
+ points[0].t = 0;
91
+ return points[0];
92
+ }
93
+
94
+ const order = points.length - 1;
95
+
96
+ /* istanbul ignore next @preserve */
97
+ if (t === 1) {
98
+ points[order].t = 1;
99
+ return points[order];
100
+ }
101
+
102
+ const mt = 1 - t;
103
+ let p = points as typeof points | [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
104
+
105
+ // constant?
106
+ /* istanbul ignore next @preserve */
107
+ if (order === 0) {
108
+ points[0].t = t;
109
+ return points[0];
110
+ }
111
+
112
+ // linear?
113
+ /* istanbul ignore else @preserve */
114
+ if (order === 1) {
115
+ return {
116
+ x: mt * p[0].x + t * p[1].x,
117
+ y: mt * p[0].y + t * p[1].y,
118
+ t,
119
+ };
120
+ }
121
+
122
+ // quadratic/cubic curve?
123
+ const mt2 = mt * mt;
124
+ const t2 = t * t;
125
+ let a = 0;
126
+ let b = 0;
127
+ let c = 0;
128
+ let d = 0;
129
+ /* istanbul ignore else @preserve */
130
+ if (order === 2) {
131
+ p = [p[0], p[1], p[2], ZERO as DerivedPoint];
132
+ a = mt2;
133
+ b = mt * t * 2;
134
+ c = t2;
135
+ } else if (order === 3) {
136
+ a = mt2 * mt;
137
+ b = mt2 * t * 3;
138
+ c = mt * t2 * 3;
139
+ d = t * t2;
140
+ }
141
+ return {
142
+ x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
143
+ y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
144
+ t,
145
+ };
146
+ };
147
+
148
+ const arcfn = (derivativeFn: DeriveCallback, t: number) => {
149
+ const d = derivativeFn(t);
150
+ const l = d.x * d.x + d.y * d.y;
151
+
152
+ return Math.sqrt(l);
153
+ };
154
+
155
+ const lengthFn = (derivativeFn: DeriveCallback) => {
156
+ const z = 0.5;
157
+ const len = Tvalues.length;
158
+
159
+ let sum = 0;
160
+
161
+ for (let i = 0, t; i < len; i++) {
162
+ t = z * Tvalues[i] + z;
163
+ sum += Cvalues[i] * arcfn(derivativeFn, t);
164
+ }
165
+ return z * sum;
166
+ };
167
+
168
+ /**
169
+ * Returns the length of CubicBezier / Quad segment.
170
+ * @param curve cubic / quad bezier segment
171
+ */
172
+ export const length = (curve: CubicCoordinates | QuadCoordinates) => {
173
+ const points = [] as unknown as CubicPoints | QuadPoints;
174
+ for (let idx = 0, len = curve.length, step = 2; idx < len; idx += step) {
175
+ points.push({
176
+ x: curve[idx],
177
+ y: curve[idx + 1],
178
+ });
179
+ }
180
+ const dpoints = derive(points);
181
+ return lengthFn((t: number) => {
182
+ return compute(dpoints[0], t);
183
+ });
184
+ };
185
+
186
+ // Precision for consider cubic polynom as quadratic one
187
+ const CBEZIER_MINMAX_EPSILON = 0.00000001;
188
+
189
+ /**
190
+ * Returns the most extreme points in a Quad Bezier segment.
191
+ * @param A
192
+ */
193
+ // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
194
+ export const minmaxQ = (A: [number, number, number]) => {
195
+ const min = Math.min(A[0], A[2]);
196
+ const max = Math.max(A[0], A[2]);
197
+
198
+ /* istanbul ignore else @preserve */
199
+ if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
200
+ // if no extremum in ]0,1[
201
+ return [min, max] as [number, number];
202
+ }
203
+
204
+ // check if the extremum E is min or max
205
+ const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
206
+ return (E < min ? [E, max] : [min, E]) as [number, number];
207
+ };
208
+
209
+ /**
210
+ * Returns the most extreme points in a Cubic Bezier segment.
211
+ * @param A
212
+ * @see https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
213
+ */
214
+ export const minmaxC = (A: [number, number, number, number]) => {
215
+ const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
216
+
217
+ // if the polynomial is (almost) quadratic and not cubic
218
+ /* istanbul ignore else @preserve */
219
+ if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
220
+ if (A[0] === A[3] && A[0] === A[1]) {
221
+ // no curve, point targeting same location
222
+ return [A[0], A[3]] as [number, number];
223
+ }
224
+
225
+ return minmaxQ([A[0], -0.5 * A[0] + 1.5 * A[1], A[0] - 3 * A[1] + 3 * A[2]]);
226
+ }
227
+
228
+ // the reduced discriminant of the derivative
229
+ const T = -A[0] * A[2] + A[0] * A[3] - A[1] * A[2] - A[1] * A[3] + A[1] * A[1] + A[2] * A[2];
230
+
231
+ // if the polynomial is monotone in [0,1]
232
+ if (T <= 0) {
233
+ return [Math.min(A[0], A[3]), Math.max(A[0], A[3])] as [number, number];
234
+ }
235
+ const S = Math.sqrt(T);
236
+
237
+ // potential extrema
238
+ let min = Math.min(A[0], A[3]);
239
+ let max = Math.max(A[0], A[3]);
240
+
241
+ const L = A[0] - 2 * A[1] + A[2];
242
+ // check local extrema
243
+ for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
244
+ if (R > 0 && R < 1) {
245
+ // if the extrema is for R in [0,1]
246
+ const Q =
247
+ A[0] * (1 - R) * (1 - R) * (1 - R) +
248
+ A[1] * 3 * (1 - R) * (1 - R) * R +
249
+ A[2] * 3 * (1 - R) * R * R +
250
+ A[3] * R * R * R;
251
+ if (Q < min) {
252
+ min = Q;
253
+ }
254
+ if (Q > max) {
255
+ max = Q;
256
+ }
257
+ }
258
+ }
259
+
260
+ return [min, max] as [number, number];
261
+ };
@@ -0,0 +1,81 @@
1
+ import { length, minmaxC, type CubicCoordinates } from './bezier';
2
+
3
+ /**
4
+ * Returns a {x,y} point at a given length of a CubicBezier segment.
5
+ *
6
+ * @param x1 the starting point X
7
+ * @param y1 the starting point Y
8
+ * @param c1x the first control point X
9
+ * @param c1y the first control point Y
10
+ * @param c2x the second control point X
11
+ * @param c2y the second control point Y
12
+ * @param x2 the ending point X
13
+ * @param y2 the ending point Y
14
+ * @param t a [0-1] ratio
15
+ * @returns the point at cubic-bezier segment length
16
+ */
17
+ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
18
+ const t1 = 1 - t;
19
+ return {
20
+ x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
21
+ y: t1 ** 3 * y1 + 3 * t1 ** 2 * t * c1y + 3 * t1 * t ** 2 * c2y + t ** 3 * y2,
22
+ };
23
+ };
24
+
25
+ /**
26
+ * Returns the properties of a CubicBezier segment.
27
+ *
28
+ * @param x1 the starting point X
29
+ * @param y1 the starting point Y
30
+ * @param c1x the first control point X
31
+ * @param c1y the first control point Y
32
+ * @param c2x the second control point X
33
+ * @param c2y the second control point Y
34
+ * @param x2 the ending point X
35
+ * @param y2 the ending point Y
36
+ * @param distance the point distance
37
+ * @returns the segment length, point at length and the bounding box
38
+ */
39
+ const getSegmentProperties = (
40
+ x1: number,
41
+ y1: number,
42
+ c1x: number,
43
+ c1y: number,
44
+ c2x: number,
45
+ c2y: number,
46
+ x2: number,
47
+ y2: number,
48
+ distance?: number,
49
+ ) => {
50
+ const distanceIsNumber = typeof distance === 'number';
51
+ let POINT = { x: x1, y: y1 };
52
+ const getLength = () => length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
53
+
54
+ if (distanceIsNumber) {
55
+ const currentLength = getLength();
56
+ if (distance <= 0) {
57
+ // first point already defined
58
+ } else if (distance >= currentLength) {
59
+ POINT = { x: x2, y: y2 };
60
+ } else {
61
+ POINT = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
62
+ }
63
+ }
64
+
65
+ return {
66
+ point: POINT,
67
+ get length() {
68
+ return getLength();
69
+ },
70
+ get bbox() {
71
+ const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
72
+ const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
73
+ return {
74
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
75
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
76
+ };
77
+ },
78
+ };
79
+ };
80
+
81
+ export default getSegmentProperties;
@@ -0,0 +1,52 @@
1
+ import midPoint from './midPoint';
2
+ import distanceSquareRoot from './distanceSquareRoot';
3
+
4
+ /**
5
+ * Returns properties for line segments (MoveTo, LineTo).
6
+ *
7
+ * @param x1 the starting point X
8
+ * @param y1 the starting point Y
9
+ * @param x2 the ending point X
10
+ * @param y2 the ending point Y
11
+ * @param distance the distance to point
12
+ * @returns the segment length, point at length and the bounding box
13
+ */
14
+ const getSegmentProperties = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
15
+ const { min, max } = Math;
16
+ let point = { x: 0, y: 0 };
17
+ const length = () => distanceSquareRoot([x1, y1], [x2, y2]);
18
+
19
+ /* istanbul ignore else @preserve */
20
+ if (typeof distance === 'number') {
21
+ const currentLength = length();
22
+ if (distance <= 0) {
23
+ point = { x: x1, y: y1 };
24
+ } else if (distance >= currentLength) {
25
+ point = { x: x2, y: y2 };
26
+ } else {
27
+ const [x, y] = midPoint([x1, y1], [x2, y2], distance / currentLength);
28
+ point = { x, y };
29
+ }
30
+ }
31
+
32
+ return {
33
+ point,
34
+ get length() {
35
+ return length();
36
+ },
37
+ get bbox() {
38
+ return {
39
+ min: {
40
+ x: min(x1, x2),
41
+ y: min(y1, y2),
42
+ },
43
+ max: {
44
+ x: max(x1, x2),
45
+ y: max(y1, y2),
46
+ },
47
+ };
48
+ },
49
+ };
50
+ };
51
+
52
+ export default getSegmentProperties;
@@ -0,0 +1,79 @@
1
+ import { length, minmaxQ, type QuadCoordinates } from './bezier';
2
+
3
+ /**
4
+ * Returns the {x,y} coordinates of a point at a
5
+ * given length of a quadratic-bezier segment.
6
+ *
7
+ * @see https://github.com/substack/point-at-length
8
+ *
9
+ * @param x1 the starting point X
10
+ * @param y1 the starting point Y
11
+ * @param cx the control point X
12
+ * @param cy the control point Y
13
+ * @param x2 the ending point X
14
+ * @param y2 the ending point Y
15
+ * @param t a [0-1] ratio
16
+ * @returns the requested {x,y} coordinates
17
+ */
18
+ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates, t: number) => {
19
+ const t1 = 1 - t;
20
+ return {
21
+ x: t1 ** 2 * x1 + 2 * t1 * t * cx + t ** 2 * x2,
22
+ y: t1 ** 2 * y1 + 2 * t1 * t * cy + t ** 2 * y2,
23
+ };
24
+ };
25
+
26
+ /**
27
+ * Returns properties of a QuadraticBezier segment.
28
+ *
29
+ * @param x1 the starting point X
30
+ * @param y1 the starting point Y
31
+ * @param cx the control point X
32
+ * @param cy the control point Y
33
+ * @param x2 the ending point X
34
+ * @param y2 the ending point Y
35
+ * @param distance the point distance
36
+ * @returns the segment length, point at length and the bounding box
37
+ */
38
+ const getSegmentProperties = (
39
+ x1: number,
40
+ y1: number,
41
+ cx: number,
42
+ cy: number,
43
+ x2: number,
44
+ y2: number,
45
+ distance: number | undefined,
46
+ ) => {
47
+ const distanceIsNumber = typeof distance === 'number';
48
+ let POINT = { x: x1, y: y1 };
49
+ const getLength = () => length([x1, y1, cx, cy, x2, y2]);
50
+
51
+ if (distanceIsNumber) {
52
+ const currentLength = getLength();
53
+
54
+ if (distance <= 0) {
55
+ // first point already defined
56
+ } else if (distance >= currentLength) {
57
+ POINT = { x: x2, y: y2 };
58
+ } else {
59
+ POINT = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
60
+ }
61
+ }
62
+
63
+ return {
64
+ point: POINT,
65
+ get length() {
66
+ return getLength();
67
+ },
68
+ get bbox() {
69
+ const cxMinMax = minmaxQ([x1, cx, x2]);
70
+ const cyMinMax = minmaxQ([y1, cy, y2]);
71
+ return {
72
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
73
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
74
+ };
75
+ },
76
+ };
77
+ };
78
+
79
+ export default getSegmentProperties;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Checks if the character is a MoveTo command.
3
+ *
4
+ * @param code the character to check
5
+ * @returns check result
6
+ */
7
+ const isMoveCommand = (code: number): code is 0x6d | 0x4d => {
8
+ // eslint-disable-next-line no-bitwise -- Impossible to satisfy
9
+ switch (code | 0x20) {
10
+ case 0x6d /* m */:
11
+ case 0x4d /* M */:
12
+ return true;
13
+ default:
14
+ return false;
15
+ }
16
+ };
17
+ export default isMoveCommand;
@@ -13,7 +13,7 @@ import type { PathArray } from '../types';
13
13
  */
14
14
  const parsePathString = (pathInput: string | PathArray): PathArray => {
15
15
  if (isPathArray(pathInput)) {
16
- return [...pathInput];
16
+ return pathInput.slice(0) as PathArray;
17
17
  }
18
18
 
19
19
  const path = new PathParser(pathInput);
@@ -6,11 +6,12 @@ import skipSpaces from './skipSpaces';
6
6
  import isPathCommand from './isPathCommand';
7
7
  import isDigitStart from './isDigitStart';
8
8
  import isArcCommand from './isArcCommand';
9
+ import isMoveCommand from './isMoveCommand';
9
10
  import invalidPathValue from './invalidPathValue';
10
11
  import error from './error';
11
12
 
12
13
  import type PathParser from './pathParser';
13
- import { RelativeCommand } from '../types';
14
+ import type { PathSegment, RelativeCommand } from '../types';
14
15
 
15
16
  /**
16
17
  * Scans every character in the path string to determine
@@ -19,14 +20,22 @@ import { RelativeCommand } from '../types';
19
20
  * @param path the `PathParser` instance
20
21
  */
21
22
  const scanSegment = (path: PathParser) => {
22
- const { max, pathValue, index } = path;
23
+ const { max, pathValue, index, segments } = path;
23
24
  const cmdCode = pathValue.charCodeAt(index);
24
25
  const reqParams = paramCounts[pathValue[index].toLowerCase() as RelativeCommand];
25
26
 
26
27
  path.segmentStart = index;
27
28
 
29
+ // segments always start with a path command
28
30
  if (!isPathCommand(cmdCode)) {
29
- path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command`;
31
+ path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command at index ${index}`;
32
+ return;
33
+ }
34
+
35
+ // after a Z segment, we only expect a MoveTo path command
36
+ const lastSegment = segments[segments.length - 1] as PathSegment | undefined;
37
+ if (!isMoveCommand(cmdCode) && lastSegment?.[0]?.toLocaleLowerCase() === 'z') {
38
+ path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a MoveTo path command at index ${index}`;
30
39
  return;
31
40
  }
32
41
 
@@ -14,7 +14,7 @@ import type { NormalArray, PathArray } from '../types';
14
14
  */
15
15
  const normalizePath = (pathInput: string | PathArray): NormalArray => {
16
16
  if (isNormalizedArray(pathInput)) {
17
- return [...pathInput];
17
+ return pathInput.slice(0) as NormalArray;
18
18
  }
19
19
 
20
20
  const path = pathToAbsolute(pathInput);
@@ -0,0 +1,52 @@
1
+ import type { PathArray, PathCommand, PathSegment } from '../types';
2
+ import isNormalizedArray from '../util/isNormalizedArray';
3
+ import segmentToCubic from './segmentToCubic';
4
+ import paramsParser from '../parser/paramsParser';
5
+ import normalizePath from './normalizePath';
6
+ import fixArc from './fixArc';
7
+ import isAbsoluteArray from '../util/isAbsoluteArray';
8
+ import pathToAbsolute from '../convert/pathToAbsolute';
9
+
10
+ const replaceArc = (pathInput: PathArray | string): PathArray => {
11
+ const absolutePath = isAbsoluteArray(pathInput) ? pathInput : pathToAbsolute(pathInput);
12
+ const normalizedPath = isNormalizedArray(absolutePath) ? absolutePath : normalizePath(absolutePath);
13
+ const params = { ...paramsParser };
14
+ const allPathCommands = [] as PathCommand[]; // needed for arc to curve transformation
15
+ let segment = [] as unknown as PathSegment;
16
+ let seglen = 0;
17
+ let pathCommand = '';
18
+ const resultedPath = [] as unknown as PathArray;
19
+ let i = 0;
20
+ let ii = absolutePath.length;
21
+
22
+ for (i = 0; i < ii; i += 1) {
23
+ /* istanbul ignore else @preserve */
24
+ if (absolutePath[i]) [pathCommand] = absolutePath[i];
25
+ allPathCommands[i] = pathCommand as PathCommand;
26
+
27
+ /* istanbul ignore else @preserve */
28
+ if (pathCommand === 'A') {
29
+ segment = segmentToCubic(normalizedPath[i], params);
30
+
31
+ absolutePath[i] = segmentToCubic(normalizedPath[i], params);
32
+ fixArc(absolutePath, allPathCommands, i);
33
+
34
+ normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
35
+ fixArc(normalizedPath, allPathCommands, i);
36
+ ii = Math.max(absolutePath.length, normalizedPath.length);
37
+ }
38
+
39
+ segment = normalizedPath[i];
40
+ seglen = segment.length;
41
+
42
+ params.x1 = +segment[seglen - 2];
43
+ params.y1 = +segment[seglen - 1];
44
+ params.x2 = +segment[seglen - 4] || params.x1;
45
+ params.y2 = +segment[seglen - 3] || params.y1;
46
+
47
+ resultedPath.push(absolutePath[i]);
48
+ }
49
+ return resultedPath;
50
+ };
51
+
52
+ export default replaceArc;
@@ -19,7 +19,7 @@ const splitPath = (pathInput: PathArray): PathArray[] => {
19
19
  path = [seg];
20
20
  pi += 1;
21
21
  } else {
22
- path = [...path, seg];
22
+ path.push(seg);
23
23
  }
24
24
  composite[pi] = path;
25
25
  });