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.
Files changed (63) hide show
  1. package/README.md +63 -7
  2. package/dist/svg-path-commander.cjs +1 -1
  3. package/dist/svg-path-commander.cjs.map +1 -1
  4. package/dist/svg-path-commander.d.ts +314 -40
  5. package/dist/svg-path-commander.js +1 -1
  6. package/dist/svg-path-commander.js.map +1 -1
  7. package/dist/svg-path-commander.mjs +1085 -1032
  8. package/dist/svg-path-commander.mjs.map +1 -1
  9. package/package.json +8 -8
  10. package/src/convert/pathToAbsolute.ts +5 -88
  11. package/src/convert/pathToCurve.ts +22 -28
  12. package/src/convert/pathToRelative.ts +4 -78
  13. package/src/convert/pathToString.ts +40 -7
  14. package/src/index.ts +145 -58
  15. package/src/interface.ts +3 -2
  16. package/src/math/arcTools.ts +259 -80
  17. package/src/math/bezier.ts +58 -58
  18. package/src/math/cubicTools.ts +68 -25
  19. package/src/math/distanceSquareRoot.ts +3 -1
  20. package/src/math/lineTools.ts +42 -25
  21. package/src/math/midPoint.ts +3 -1
  22. package/src/math/polygonTools.ts +48 -0
  23. package/src/math/quadTools.ts +46 -25
  24. package/src/math/rotateVector.ts +3 -2
  25. package/src/math/roundTo.ts +7 -0
  26. package/src/parser/finalizeSegment.ts +11 -7
  27. package/src/parser/parsePathString.ts +4 -5
  28. package/src/parser/pathParser.ts +1 -1
  29. package/src/process/absolutizeSegment.ts +63 -0
  30. package/src/process/arcToCubic.ts +2 -2
  31. package/src/process/iterate.ts +58 -0
  32. package/src/process/lineToCubic.ts +1 -1
  33. package/src/process/normalizePath.ts +17 -28
  34. package/src/process/normalizeSegment.ts +55 -18
  35. package/src/process/optimizePath.ts +40 -60
  36. package/src/process/projection2d.ts +4 -3
  37. package/src/process/relativizeSegment.ts +59 -0
  38. package/src/process/reverseCurve.ts +8 -5
  39. package/src/process/reversePath.ts +88 -75
  40. package/src/process/roundPath.ts +18 -14
  41. package/src/process/roundSegment.ts +9 -0
  42. package/src/process/segmentToCubic.ts +10 -8
  43. package/src/process/shortenSegment.ts +26 -34
  44. package/src/process/splitCubic.ts +10 -9
  45. package/src/process/splitPath.ts +38 -4
  46. package/src/process/transformPath.ts +75 -73
  47. package/src/types.ts +30 -0
  48. package/src/util/getPathArea.ts +3 -3
  49. package/src/util/getPathBBox.ts +69 -19
  50. package/src/util/getPointAtLength.ts +100 -4
  51. package/src/util/getPropertiesAtLength.ts +3 -4
  52. package/src/util/getPropertiesAtPoint.ts +5 -5
  53. package/src/util/getTotalLength.ts +56 -4
  54. package/test/class.test.ts +17 -14
  55. package/test/fixtures/shapes.js +26 -26
  56. package/test/fixtures/simpleShapes.js +18 -18
  57. package/test/static.test.ts +54 -28
  58. package/cypress.config.ts +0 -29
  59. package/src/math/polygonArea.ts +0 -27
  60. package/src/math/polygonLength.ts +0 -21
  61. package/src/process/fixArc.ts +0 -23
  62. package/src/process/replaceArc.ts +0 -52
  63. package/src/util/pathFactory.ts +0 -130
@@ -1,7 +1,8 @@
1
- import { length, minmaxC, type CubicCoordinates } from './bezier';
1
+ import { getBezierLength, minmaxC } from './bezier';
2
+ import { type CubicCoordinates } from '../types';
2
3
 
3
4
  /**
4
- * Returns a {x,y} point at a given length of a CubicBezier segment.
5
+ * Returns a point at a given length of a CubicBezier segment.
5
6
  *
6
7
  * @param x1 the starting point X
7
8
  * @param y1 the starting point Y
@@ -23,7 +24,7 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
23
24
  };
24
25
 
25
26
  /**
26
- * Returns the properties of a CubicBezier segment.
27
+ * Returns the length of a CubicBezier segment.
27
28
  *
28
29
  * @param x1 the starting point X
29
30
  * @param y1 the starting point Y
@@ -33,10 +34,36 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
33
34
  * @param c2y the second control point Y
34
35
  * @param x2 the ending point X
35
36
  * @param y2 the ending point Y
36
- * @param distance the point distance
37
- * @returns the segment length, point at length and the bounding box
37
+ * @returns the CubicBezier segment length
38
38
  */
39
- const getSegmentProperties = (
39
+ const getCubicLength = (
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
+ ) => {
49
+ return getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
50
+ };
51
+
52
+ /**
53
+ * Returns the point along a CubicBezier segment at a given distance.
54
+ *
55
+ * @param x1 the starting point X
56
+ * @param y1 the starting point Y
57
+ * @param c1x the first control point X
58
+ * @param c1y the first control point Y
59
+ * @param c2x the second control point X
60
+ * @param c2y the second control point Y
61
+ * @param x2 the ending point X
62
+ * @param y2 the ending point Y
63
+ * @param distance the distance to look at
64
+ * @returns the point at CubicBezier length
65
+ */
66
+ const getPointAtCubicLength = (
40
67
  x1: number,
41
68
  y1: number,
42
69
  c1x: number,
@@ -48,34 +75,50 @@ const getSegmentProperties = (
48
75
  distance?: number,
49
76
  ) => {
50
77
  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
-
78
+ let point = { x: x1, y: y1 };
79
+ /* istanbul ignore else @preserve */
54
80
  if (distanceIsNumber) {
55
- const currentLength = getLength();
81
+ const currentLength = getBezierLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
56
82
  if (distance <= 0) {
57
83
  // first point already defined
58
84
  } else if (distance >= currentLength) {
59
- POINT = { x: x2, y: y2 };
85
+ point = { x: x2, y: y2 };
60
86
  } else {
61
- POINT = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
87
+ point = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
62
88
  }
63
89
  }
90
+ return point;
91
+ };
64
92
 
93
+ /**
94
+ * Returns the boundig box of a CubicBezier segment.
95
+ *
96
+ * @param x1 the starting point X
97
+ * @param y1 the starting point Y
98
+ * @param c1x the first control point X
99
+ * @param c1y the first control point Y
100
+ * @param c2x the second control point X
101
+ * @param c2y the second control point Y
102
+ * @param x2 the ending point X
103
+ * @param y2 the ending point Y
104
+ * @returns the point at CubicBezier length
105
+ */
106
+ const getCubicBBox = (
107
+ x1: number,
108
+ y1: number,
109
+ c1x: number,
110
+ c1y: number,
111
+ c2x: number,
112
+ c2y: number,
113
+ x2: number,
114
+ y2: number,
115
+ ) => {
116
+ const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
117
+ const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
65
118
  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
- },
119
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
120
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
78
121
  };
79
122
  };
80
123
 
81
- export default getSegmentProperties;
124
+ export { getCubicLength, getCubicBBox, getPointAtCubicLength, getPointAtCubicSegmentLength };
@@ -1,3 +1,5 @@
1
+ import { type PointTuple } from '../types';
2
+
1
3
  /**
2
4
  * Returns the square root of the distance
3
5
  * between two given points.
@@ -6,7 +8,7 @@
6
8
  * @param b the second point coordinates
7
9
  * @returns the distance value
8
10
  */
9
- const distanceSquareRoot = (a: [number, number], b: [number, number]): number => {
11
+ const distanceSquareRoot = (a: PointTuple, b: PointTuple) => {
10
12
  return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
11
13
  };
12
14
 
@@ -2,51 +2,68 @@ import midPoint from './midPoint';
2
2
  import distanceSquareRoot from './distanceSquareRoot';
3
3
 
4
4
  /**
5
- * Returns properties for line segments (MoveTo, LineTo).
5
+ * Returns length for line segments (MoveTo, LineTo).
6
6
  *
7
7
  * @param x1 the starting point X
8
8
  * @param y1 the starting point Y
9
9
  * @param x2 the ending point X
10
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
11
+ * @returns the line segment length
13
12
  */
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]);
13
+ const getLineLength = (x1: number, y1: number, x2: number, y2: number) => {
14
+ return distanceSquareRoot([x1, y1], [x2, y2]);
15
+ };
16
+
17
+ /**
18
+ * Returns a point along the line segments (MoveTo, LineTo).
19
+ *
20
+ * @param x1 the starting point X
21
+ * @param y1 the starting point Y
22
+ * @param x2 the ending point X
23
+ * @param y2 the ending point Y
24
+ * @param distance the distance to point in [0-1] range
25
+ * @returns the point at length
26
+ */
27
+ const getPointAtLineLength = (x1: number, y1: number, x2: number, y2: number, distance?: number) => {
28
+ let point = { x: x1, y: y1 };
18
29
 
19
30
  /* istanbul ignore else @preserve */
20
31
  if (typeof distance === 'number') {
21
- const currentLength = length();
32
+ const length = distanceSquareRoot([x1, y1], [x2, y2]);
22
33
  if (distance <= 0) {
23
34
  point = { x: x1, y: y1 };
24
- } else if (distance >= currentLength) {
35
+ } else if (distance >= length) {
25
36
  point = { x: x2, y: y2 };
26
37
  } else {
27
- const [x, y] = midPoint([x1, y1], [x2, y2], distance / currentLength);
38
+ const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
28
39
  point = { x, y };
29
40
  }
30
41
  }
42
+ return point;
43
+ };
31
44
 
45
+ /**
46
+ * Returns bounding box for line segments (MoveTo, LineTo).
47
+ *
48
+ * @param x1 the starting point X
49
+ * @param y1 the starting point Y
50
+ * @param x2 the ending point X
51
+ * @param y2 the ending point Y
52
+ * @param distance the distance to point in [0-1] range
53
+ * @returns the extrema for line segments
54
+ */
55
+ const getLineBBox = (x1: number, y1: number, x2: number, y2: number) => {
56
+ const { min, max } = Math;
32
57
  return {
33
- point,
34
- get length() {
35
- return length();
58
+ min: {
59
+ x: min(x1, x2),
60
+ y: min(y1, y2),
36
61
  },
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
- };
62
+ max: {
63
+ x: max(x1, x2),
64
+ y: max(y1, y2),
48
65
  },
49
66
  };
50
67
  };
51
68
 
52
- export default getSegmentProperties;
69
+ export { getPointAtLineLength, getLineBBox, getLineLength };
@@ -1,3 +1,5 @@
1
+ import { PointTuple } from '../types';
2
+
1
3
  /**
2
4
  * Returns the coordinates of a specified distance
3
5
  * ratio between two points.
@@ -7,7 +9,7 @@
7
9
  * @param t the ratio
8
10
  * @returns the midpoint coordinates
9
11
  */
10
- const midPoint = (a: [number, number], b: [number, number], t: number): [number, number] => {
12
+ const midPoint = (a: PointTuple, b: PointTuple, t: number): PointTuple => {
11
13
  const [ax, ay] = a;
12
14
  const [bx, by] = b;
13
15
  return [ax + (bx - ax) * t, ay + (by - ay) * t];
@@ -0,0 +1,48 @@
1
+ import distanceSquareRoot from './distanceSquareRoot';
2
+ import { type PointTuple } from '../types';
3
+
4
+ /**
5
+ * d3-polygon-area
6
+ * https://github.com/d3/d3-polygon
7
+ *
8
+ * Returns the area of a polygon.
9
+ *
10
+ * @param polygon an array of coordinates
11
+ * @returns the polygon area
12
+ */
13
+ const polygonArea = (polygon: PointTuple[]) => {
14
+ const n = polygon.length;
15
+ let i = -1;
16
+ let a: PointTuple;
17
+ let b = polygon[n - 1];
18
+ let area = 0;
19
+
20
+ /* eslint-disable-next-line */
21
+ while (++i < n) {
22
+ a = b;
23
+ b = polygon[i];
24
+ area += a[1] * b[0] - a[0] * b[1];
25
+ }
26
+
27
+ return area / 2;
28
+ };
29
+
30
+ /**
31
+ * d3-polygon-length
32
+ * https://github.com/d3/d3-polygon
33
+ *
34
+ * Returns the perimeter of a polygon.
35
+ *
36
+ * @param polygon an array of coordinates
37
+ * @returns the polygon length
38
+ */
39
+ const polygonLength = (polygon: PointTuple[]) => {
40
+ return polygon.reduce((length, point, i) => {
41
+ if (i) {
42
+ return length + distanceSquareRoot(polygon[i - 1], point);
43
+ }
44
+ return 0;
45
+ }, 0);
46
+ };
47
+
48
+ export { polygonArea, polygonLength };
@@ -1,4 +1,5 @@
1
- import { length, minmaxQ, type QuadCoordinates } from './bezier';
1
+ import { getBezierLength, minmaxQ } from './bezier';
2
+ import { type QuadCoordinates } from '../types';
2
3
 
3
4
  /**
4
5
  * Returns the {x,y} coordinates of a point at a
@@ -24,7 +25,7 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
24
25
  };
25
26
 
26
27
  /**
27
- * Returns properties of a QuadraticBezier segment.
28
+ * Returns the length of a QuadraticBezier segment.
28
29
  *
29
30
  * @param x1 the starting point X
30
31
  * @param y1 the starting point Y
@@ -32,48 +33,68 @@ const getPointAtQuadSegmentLength = ([x1, y1, cx, cy, x2, y2]: QuadCoordinates,
32
33
  * @param cy the control point Y
33
34
  * @param x2 the ending point X
34
35
  * @param y2 the ending point Y
35
- * @param distance the point distance
36
- * @returns the segment length, point at length and the bounding box
36
+ * @returns the QuadraticBezier segment length
37
37
  */
38
- const getSegmentProperties = (
38
+ const getQuadLength = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
39
+ return getBezierLength([x1, y1, cx, cy, x2, y2]);
40
+ };
41
+
42
+ /**
43
+ * Returns the point along a QuadraticBezier segment at a given distance.
44
+ *
45
+ * @param x1 the starting point X
46
+ * @param y1 the starting point Y
47
+ * @param cx the control point X
48
+ * @param cy the control point Y
49
+ * @param x2 the ending point X
50
+ * @param y2 the ending point Y
51
+ * @param distance the distance to look at
52
+ * @returns the point at QuadraticBezier length
53
+ */
54
+ const getPointAtQuadLength = (
39
55
  x1: number,
40
56
  y1: number,
41
57
  cx: number,
42
58
  cy: number,
43
59
  x2: number,
44
60
  y2: number,
45
- distance: number | undefined,
61
+ distance?: number,
46
62
  ) => {
47
63
  const distanceIsNumber = typeof distance === 'number';
48
- let POINT = { x: x1, y: y1 };
49
- const getLength = () => length([x1, y1, cx, cy, x2, y2]);
64
+ let point = { x: x1, y: y1 };
50
65
 
66
+ /* istanbul ignore else @preserve */
51
67
  if (distanceIsNumber) {
52
- const currentLength = getLength();
53
-
68
+ const currentLength = getBezierLength([x1, y1, cx, cy, x2, y2]);
54
69
  if (distance <= 0) {
55
70
  // first point already defined
56
71
  } else if (distance >= currentLength) {
57
- POINT = { x: x2, y: y2 };
72
+ point = { x: x2, y: y2 };
58
73
  } else {
59
- POINT = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
74
+ point = getPointAtQuadSegmentLength([x1, y1, cx, cy, x2, y2], distance / currentLength);
60
75
  }
61
76
  }
77
+ return point;
78
+ };
62
79
 
80
+ /**
81
+ * Returns the boundig box of a QuadraticBezier segment.
82
+ *
83
+ * @param x1 the starting point X
84
+ * @param y1 the starting point Y
85
+ * @param cx the control point X
86
+ * @param cy the control point Y
87
+ * @param x2 the ending point X
88
+ * @param y2 the ending point Y
89
+ * @returns the point at CubicBezier length
90
+ */
91
+ const getQuadBBox = (x1: number, y1: number, cx: number, cy: number, x2: number, y2: number) => {
92
+ const cxMinMax = minmaxQ([x1, cx, x2]);
93
+ const cyMinMax = minmaxQ([y1, cy, y2]);
63
94
  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
- },
95
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
96
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
76
97
  };
77
98
  };
78
99
 
79
- export default getSegmentProperties;
100
+ export { getPointAtQuadSegmentLength, getQuadLength, getQuadBBox, getPointAtQuadLength };
@@ -8,8 +8,9 @@
8
8
  * @returns the rotated vector
9
9
  */
10
10
  const rotateVector = (x: number, y: number, rad: number): { x: number; y: number } => {
11
- const X = x * Math.cos(rad) - y * Math.sin(rad);
12
- const Y = x * Math.sin(rad) + y * Math.cos(rad);
11
+ const { sin, cos } = Math;
12
+ const X = x * cos(rad) - y * sin(rad);
13
+ const Y = x * sin(rad) + y * cos(rad);
13
14
  return { x: X, y: Y };
14
15
  };
15
16
 
@@ -0,0 +1,7 @@
1
+ const roundTo = (n: number, round: number) => {
2
+ const pow = round >= 1 ? 10 ** round : 1;
3
+
4
+ return round > 0 ? Math.round(n * pow) / pow : Math.round(n);
5
+ };
6
+
7
+ export default roundTo;
@@ -9,21 +9,25 @@ import type { PathCommand, PathSegment, RelativeCommand } from '../types';
9
9
  */
10
10
  const finalizeSegment = (path: PathParser) => {
11
11
  let pathCommand = path.pathValue[path.segmentStart] as PathCommand;
12
- let LK = pathCommand.toLowerCase() as RelativeCommand;
12
+ let relativeCommand = pathCommand.toLowerCase() as RelativeCommand;
13
13
  const { data } = path;
14
14
 
15
- while (data.length >= paramsCount[LK]) {
15
+ while (data.length >= paramsCount[relativeCommand]) {
16
16
  // overloaded `moveTo`
17
17
  // https://github.com/rveciana/svg-path-properties/blob/master/src/parse.ts
18
- if (LK === 'm' && data.length > 2) {
19
- path.segments.push([pathCommand, ...(data.splice(0, 2) as number[])] as PathSegment);
20
- LK = 'l';
18
+ if (relativeCommand === 'm' && data.length > 2) {
19
+ path.segments.push([pathCommand as PathCommand | number].concat(data.splice(0, 2) as number[]) as PathSegment);
20
+ relativeCommand = 'l';
21
21
  pathCommand = pathCommand === 'm' ? 'l' : 'L';
22
22
  } else {
23
- path.segments.push([pathCommand, ...(data.splice(0, paramsCount[LK]) as number[])] as PathSegment);
23
+ path.segments.push(
24
+ [pathCommand as PathCommand | number].concat(
25
+ data.splice(0, paramsCount[relativeCommand]) as number[],
26
+ ) as PathSegment,
27
+ );
24
28
  }
25
29
 
26
- if (!paramsCount[LK]) {
30
+ if (!paramsCount[relativeCommand]) {
27
31
  break;
28
32
  }
29
33
  }
@@ -1,7 +1,6 @@
1
1
  import scanSegment from './scanSegment';
2
2
  import skipSpaces from './skipSpaces';
3
3
  import PathParser from './pathParser';
4
- import isPathArray from '../util/isPathArray';
5
4
  import type { PathArray } from '../types';
6
5
 
7
6
  /**
@@ -11,9 +10,9 @@ import type { PathArray } from '../types';
11
10
  * @param pathInput the string to be parsed
12
11
  * @returns the resulted `pathArray` or error string
13
12
  */
14
- const parsePathString = (pathInput: string | PathArray): PathArray => {
15
- if (isPathArray(pathInput)) {
16
- return pathInput.slice(0) as PathArray;
13
+ const parsePathString = <T extends PathArray>(pathInput: string | T) => {
14
+ if (typeof pathInput !== 'string') {
15
+ return pathInput.slice(0) as typeof pathInput;
17
16
  }
18
17
 
19
18
  const path = new PathParser(pathInput);
@@ -24,7 +23,7 @@ const parsePathString = (pathInput: string | PathArray): PathArray => {
24
23
  scanSegment(path);
25
24
  }
26
25
 
27
- if (path.err && path.err.length) {
26
+ if (path?.err.length) {
28
27
  throw TypeError(path.err);
29
28
  }
30
29
 
@@ -13,7 +13,7 @@ export default class PathParser {
13
13
  declare index: number;
14
14
  declare param: number;
15
15
  declare segmentStart: number;
16
- declare data: any[];
16
+ declare data: (string | number)[];
17
17
  declare err: string;
18
18
 
19
19
  constructor(pathString: string) {
@@ -0,0 +1,63 @@
1
+ import type {
2
+ AbsoluteSegment,
3
+ AbsoluteCommand,
4
+ ASegment,
5
+ VSegment,
6
+ HSegment,
7
+ QSegment,
8
+ SSegment,
9
+ TSegment,
10
+ CSegment,
11
+ PathSegment,
12
+ MSegment,
13
+ LSegment,
14
+ } from '../types';
15
+
16
+ /**
17
+ * Returns an absolute segment of a `PathArray` object.
18
+ *
19
+ * @param segment the segment object
20
+ * @param index the segment index
21
+ * @param lastX the last known X value
22
+ * @param lastY the last known Y value
23
+ * @returns the absolute segment
24
+ */
25
+ const absolutizeSegment = (segment: PathSegment, index: number, lastX: number, lastY: number) => {
26
+ const [pathCommand] = segment;
27
+ const absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
28
+ const isAbsolute = absCommand === pathCommand;
29
+
30
+ /* istanbul ignore else @preserve */
31
+ if (index === 0 || isAbsolute) return segment as MSegment | AbsoluteSegment;
32
+ // const values = segment.slice(1) as number[];
33
+ if (absCommand === 'A') {
34
+ return [
35
+ absCommand,
36
+ segment[1],
37
+ segment[2],
38
+ segment[3],
39
+ segment[4],
40
+ segment[5],
41
+ (segment as ASegment)[6] + lastX,
42
+ (segment as ASegment)[7] + lastY,
43
+ ] as ASegment;
44
+ } else if (absCommand === 'V') {
45
+ return [absCommand, (segment as VSegment)[1] + lastY] as VSegment;
46
+ } else if (absCommand === 'H') {
47
+ return [absCommand, (segment as HSegment)[1] + lastX] as HSegment;
48
+ } else if (absCommand === 'L') {
49
+ return [absCommand, (segment as LSegment)[1] + lastX, (segment as LSegment)[2] + lastY] as LSegment;
50
+ } else {
51
+ // use brakets for `eslint: no-case-declaration`
52
+ // https://stackoverflow.com/a/50753272/803358
53
+ const absValues = (segment.slice(1) as number[]).map((n, j) => n + (j % 2 ? lastY : lastX));
54
+ // for c, s, q, t
55
+ return [absCommand as typeof absCommand | number].concat(absValues) as
56
+ | MSegment
57
+ | QSegment
58
+ | TSegment
59
+ | SSegment
60
+ | CSegment;
61
+ }
62
+ };
63
+ export default absolutizeSegment;
@@ -116,9 +116,9 @@ const arcToCubic = (
116
116
  m2[0] = 2 * m1[0] - m2[0];
117
117
  m2[1] = 2 * m1[1] - m2[1];
118
118
  if (recursive) {
119
- return [...m2, ...m3, ...m4, ...res];
119
+ return [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
120
120
  }
121
- res = [...m2, ...m3, ...m4, ...res];
121
+ res = [m2[0], m2[1], m3[0], m3[1], m4[0], m4[1]].concat(res);
122
122
  const newres = [];
123
123
  for (let i = 0, ii = res.length; i < ii; i += 1) {
124
124
  newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
@@ -0,0 +1,58 @@
1
+ // import paramsParser from '../parser/paramsParser';
2
+ import type { PathArray, PathCommand, PathSegment, IteratorCallback, AbsoluteCommand } from '../types';
3
+
4
+ const iterate = <T extends PathArray>(path: PathArray, iterator: IteratorCallback) => {
5
+ let pathLen = path.length;
6
+ let segment: PathSegment;
7
+ let pathCommand = 'M' as PathCommand;
8
+ let absCommand = 'M' as AbsoluteCommand;
9
+ let isRelative = false;
10
+ let x = 0;
11
+ let y = 0;
12
+ let mx = 0;
13
+ let my = 0;
14
+ let segLen = 0;
15
+
16
+ for (let i = 0; i < pathLen; i += 1) {
17
+ segment = path[i];
18
+ [pathCommand] = segment;
19
+ segLen = segment.length;
20
+ absCommand = pathCommand.toUpperCase() as AbsoluteCommand;
21
+ isRelative = absCommand !== pathCommand;
22
+
23
+ const iteratorResult = iterator(segment, i, x, y);
24
+ // some methods like getPointAtLength would like to break
25
+ // when task is complete
26
+ if (iteratorResult === false) {
27
+ break;
28
+ }
29
+
30
+ // segment = path[i];
31
+ if (absCommand === 'Z') {
32
+ x = mx;
33
+ y = my;
34
+ } else if (absCommand === 'H') {
35
+ x = (segment[1] as number) + (isRelative ? x : 0);
36
+ } else if (absCommand === 'V') {
37
+ y = (segment[1] as number) + (isRelative ? y : 0);
38
+ } else {
39
+ x = (segment[segLen - 2] as number) + (isRelative ? x : 0);
40
+ y = (segment[segLen - 1] as number) + (isRelative ? y : 0);
41
+
42
+ if (absCommand === 'M') {
43
+ mx = x;
44
+ my = y;
45
+ }
46
+ }
47
+
48
+ if (iteratorResult) {
49
+ path[i] = iteratorResult;
50
+ if (iteratorResult[0] === 'C') {
51
+ pathLen = path.length;
52
+ }
53
+ }
54
+ }
55
+ return path as T;
56
+ };
57
+
58
+ export default iterate;
@@ -12,6 +12,6 @@ import midPoint from '../math/midPoint';
12
12
  const lineToCubic = (x1: number, y1: number, x2: number, y2: number) => {
13
13
  const c1 = midPoint([x1, y1], [x2, y2], 1.0 / 3.0);
14
14
  const c2 = midPoint([x1, y1], [x2, y2], 2.0 / 3.0);
15
- return [...c1, ...c2, x2, y2];
15
+ return [c1[0], c1[1], c2[0], c2[1], x2, y2];
16
16
  };
17
17
  export default lineToCubic;