svg-path-commander 1.0.5 → 2.0.0-alpha2

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 (131) hide show
  1. package/README.md +26 -4
  2. package/dist/svg-path-commander.cjs +2 -0
  3. package/dist/svg-path-commander.cjs.map +1 -0
  4. package/dist/svg-path-commander.d.ts +454 -0
  5. package/dist/svg-path-commander.js +2 -3762
  6. package/dist/svg-path-commander.js.map +1 -0
  7. package/dist/svg-path-commander.mjs +1194 -0
  8. package/dist/svg-path-commander.mjs.map +1 -0
  9. package/package.json +39 -42
  10. package/src/convert/pathToAbsolute.ts +101 -0
  11. package/src/convert/{pathToCurve.js → pathToCurve.ts} +12 -14
  12. package/src/convert/pathToRelative.ts +92 -0
  13. package/src/convert/pathToString.ts +17 -0
  14. package/src/{svg-path-commander.js → index.ts} +143 -66
  15. package/src/interface.ts +129 -0
  16. package/src/math/distanceSquareRoot.ts +13 -0
  17. package/src/math/midPoint.ts +16 -0
  18. package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
  19. package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
  20. package/src/math/rotateVector.ts +16 -0
  21. package/src/options/options.ts +9 -0
  22. package/src/parser/error.ts +2 -0
  23. package/src/parser/finalizeSegment.ts +31 -0
  24. package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
  25. package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
  26. package/src/parser/isDigit.ts +12 -0
  27. package/src/parser/isDigitStart.ts +14 -0
  28. package/src/parser/isPathCommand.ts +28 -0
  29. package/src/parser/isSpace.ts +23 -0
  30. package/src/parser/paramsCount.ts +16 -0
  31. package/src/parser/paramsParser.ts +14 -0
  32. package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
  33. package/src/parser/pathParser.ts +29 -0
  34. package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
  35. package/src/parser/{scanParam.js → scanParam.ts} +12 -11
  36. package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
  37. package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
  38. package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
  39. package/src/process/fixArc.ts +23 -0
  40. package/src/process/getSVGMatrix.ts +70 -0
  41. package/src/process/lineToCubic.ts +17 -0
  42. package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
  43. package/src/process/normalizeSegment.ts +47 -0
  44. package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
  45. package/src/process/{projection2d.js → projection2d.ts} +16 -15
  46. package/src/process/quadToCubic.ts +31 -0
  47. package/src/process/reverseCurve.ts +21 -0
  48. package/src/process/reversePath.ts +101 -0
  49. package/src/process/roundPath.ts +29 -0
  50. package/src/process/segmentToCubic.ts +46 -0
  51. package/src/process/shortenSegment.ts +79 -0
  52. package/src/process/splitCubic.ts +28 -0
  53. package/src/process/{splitPath.js → splitPath.ts} +9 -8
  54. package/src/process/{transformPath.js → transformPath.ts} +55 -48
  55. package/src/types.ts +193 -0
  56. package/src/util/getClosestPoint.ts +15 -0
  57. package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
  58. package/src/util/getPathArea.ts +70 -0
  59. package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
  60. package/src/util/getPointAtLength.ts +14 -0
  61. package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
  62. package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
  63. package/src/util/getSegmentAtLength.ts +15 -0
  64. package/src/util/getSegmentOfPoint.ts +18 -0
  65. package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
  66. package/src/util/isAbsoluteArray.ts +18 -0
  67. package/src/util/{isCurveArray.js → isCurveArray.ts} +6 -4
  68. package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +4 -2
  69. package/src/util/isPathArray.ts +19 -0
  70. package/src/util/isPointInStroke.ts +15 -0
  71. package/src/util/isRelativeArray.ts +18 -0
  72. package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
  73. package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
  74. package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +71 -55
  75. package/src/util/segmentCubicFactory.ts +114 -0
  76. package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +10 -8
  77. package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +47 -32
  78. package/src/util/shapeToPath.ts +214 -0
  79. package/dist/svg-path-commander.es5.js +0 -3875
  80. package/dist/svg-path-commander.es5.min.js +0 -2
  81. package/dist/svg-path-commander.esm.js +0 -3754
  82. package/dist/svg-path-commander.esm.min.js +0 -2
  83. package/dist/svg-path-commander.min.js +0 -2
  84. package/src/convert/pathToAbsolute.js +0 -86
  85. package/src/convert/pathToRelative.js +0 -84
  86. package/src/convert/pathToString.js +0 -14
  87. package/src/index.js +0 -10
  88. package/src/math/distanceSquareRoot.js +0 -14
  89. package/src/math/epsilon.js +0 -8
  90. package/src/math/midPoint.js +0 -13
  91. package/src/math/rotateVector.js +0 -14
  92. package/src/options/options.js +0 -10
  93. package/src/parser/error.js +0 -2
  94. package/src/parser/finalizeSegment.js +0 -28
  95. package/src/parser/isDigit.js +0 -9
  96. package/src/parser/isDigitStart.js +0 -13
  97. package/src/parser/isPathCommand.js +0 -25
  98. package/src/parser/isSpace.js +0 -16
  99. package/src/parser/paramsCount.js +0 -9
  100. package/src/parser/paramsParser.js +0 -8
  101. package/src/parser/pathParser.js +0 -24
  102. package/src/process/clonePath.js +0 -9
  103. package/src/process/fixArc.js +0 -21
  104. package/src/process/fixPath.js +0 -31
  105. package/src/process/getSVGMatrix.js +0 -61
  106. package/src/process/lineToCubic.js +0 -30
  107. package/src/process/normalizeSegment.js +0 -45
  108. package/src/process/quadToCubic.js +0 -22
  109. package/src/process/reverseCurve.js +0 -18
  110. package/src/process/reversePath.js +0 -89
  111. package/src/process/roundPath.js +0 -26
  112. package/src/process/segmentToCubic.js +0 -46
  113. package/src/process/shortenSegment.js +0 -58
  114. package/src/process/splitCubic.js +0 -26
  115. package/src/util/getClosestPoint.js +0 -12
  116. package/src/util/getPathArea.js +0 -47
  117. package/src/util/getPointAtLength.js +0 -12
  118. package/src/util/getSegmentAtLength.js +0 -11
  119. package/src/util/getSegmentOfPoint.js +0 -12
  120. package/src/util/isAbsoluteArray.js +0 -14
  121. package/src/util/isPathArray.js +0 -14
  122. package/src/util/isPointInStroke.js +0 -13
  123. package/src/util/isRelativeArray.js +0 -14
  124. package/src/util/segmentCubicFactory.js +0 -97
  125. package/src/util/shapeToPath.js +0 -204
  126. package/src/util/util.js +0 -82
  127. package/src/version.js +0 -8
  128. package/types/index.d.ts +0 -120
  129. package/types/more/modules.ts +0 -82
  130. package/types/more/svg.d.ts +0 -211
  131. package/types/svg-path-commander.d.ts +0 -1089
@@ -1,13 +1,15 @@
1
+ import type { LengthFactory } from '../interface';
1
2
  import segmentLineFactory from './segmentLineFactory';
2
3
  import distanceSquareRoot from '../math/distanceSquareRoot';
3
4
 
4
5
  /**
6
+ * Returns an angle value between two points.
5
7
  *
6
- * @param {{x: number, y: number}} v0
7
- * @param {{x: number, y: number}} v1
8
- * @returns {{x: number, y: number}}
8
+ * @param v0
9
+ * @param v1
10
+ * @returns a number representing an angle
9
11
  */
10
- function angleBetween(v0, v1) {
12
+ const angleBetween = (v0: { x: number; y: number }, v1: { x: number; y: number }): number => {
11
13
  const { x: v0x, y: v0y } = v0;
12
14
  const { x: v1x, y: v1y } = v1;
13
15
  const p = v0x * v1x + v0y * v1y;
@@ -16,29 +18,39 @@ function angleBetween(v0, v1) {
16
18
  const angle = sign * Math.acos(p / n);
17
19
 
18
20
  return angle;
19
- }
21
+ };
20
22
 
21
23
  /**
22
24
  * Returns a {x,y} point at a given length, the total length and
23
25
  * the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
26
+ *
24
27
  * @see https://github.com/MadLittleMods/svg-curve-lib/blob/master/src/js/svg-curve-lib.js
25
28
  *
26
- * @param {number} x1 the starting x position
27
- * @param {number} y1 the starting y position
28
- * @param {number} RX x-radius of the arc
29
- * @param {number} RY y-radius of the arc
30
- * @param {number} angle x-axis-rotation of the arc
31
- * @param {number} LAF large-arc-flag of the arc
32
- * @param {number} SF sweep-flag of the arc
33
- * @param {number} x the ending x position
34
- * @param {number} y the ending y position
35
- * @param {number} t the point distance
36
- * @returns {{x: number, y: number}} the requested point
29
+ * @param x1 the starting x position
30
+ * @param y1 the starting y position
31
+ * @param RX x-radius of the arc
32
+ * @param RY y-radius of the arc
33
+ * @param angle x-axis-rotation of the arc
34
+ * @param LAF large-arc-flag of the arc
35
+ * @param SF sweep-flag of the arc
36
+ * @param x the ending x position
37
+ * @param y the ending y position
38
+ * @param t the point distance
39
+ * @returns the requested point
37
40
  */
38
- function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
39
- const {
40
- abs, sin, cos, sqrt, PI,
41
- } = Math;
41
+ const getPointAtArcSegmentLength = (
42
+ x1: number,
43
+ y1: number,
44
+ RX: number,
45
+ RY: number,
46
+ angle: number,
47
+ LAF: number,
48
+ SF: number,
49
+ x: number,
50
+ y: number,
51
+ t: number,
52
+ ): { x: number; y: number } => {
53
+ const { abs, sin, cos, sqrt, PI } = Math;
42
54
  let rx = abs(RX);
43
55
  let ry = abs(RY);
44
56
  const xRot = ((angle % 360) + 360) % 360;
@@ -67,12 +79,9 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
67
79
  ry *= sqrt(radiiCheck);
68
80
  }
69
81
 
70
- const cSquareNumerator = rx ** 2 * ry ** 2
71
- - rx ** 2 * transformedPoint.y ** 2
72
- - ry ** 2 * transformedPoint.x ** 2;
82
+ const cSquareNumerator = rx ** 2 * ry ** 2 - rx ** 2 * transformedPoint.y ** 2 - ry ** 2 * transformedPoint.x ** 2;
73
83
 
74
- const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2
75
- + ry ** 2 * transformedPoint.x ** 2;
84
+ const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2 + ry ** 2 * transformedPoint.x ** 2;
76
85
 
77
86
  let cRadicand = cSquareNumerator / cSquareRootDenom;
78
87
  cRadicand = cRadicand < 0 ? 0 : cRadicand;
@@ -83,10 +92,8 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
83
92
  };
84
93
 
85
94
  const center = {
86
- x: cos(xRotRad) * transformedCenter.x
87
- - sin(xRotRad) * transformedCenter.y + (x1 + x) / 2,
88
- y: sin(xRotRad) * transformedCenter.x
89
- + cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
95
+ x: cos(xRotRad) * transformedCenter.x - sin(xRotRad) * transformedCenter.y + (x1 + x) / 2,
96
+ y: sin(xRotRad) * transformedCenter.x + cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
90
97
  };
91
98
 
92
99
  const startVector = {
@@ -114,12 +121,8 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
114
121
  const ellipseComponentY = ry * sin(alpha);
115
122
 
116
123
  const point = {
117
- x: cos(xRotRad) * ellipseComponentX
118
- - sin(xRotRad) * ellipseComponentY
119
- + center.x,
120
- y: sin(xRotRad) * ellipseComponentX
121
- + cos(xRotRad) * ellipseComponentY
122
- + center.y,
124
+ x: cos(xRotRad) * ellipseComponentX - sin(xRotRad) * ellipseComponentY + center.x,
125
+ y: sin(xRotRad) * ellipseComponentX + cos(xRotRad) * ellipseComponentY + center.y,
123
126
  };
124
127
 
125
128
  // to be used later
@@ -132,30 +135,42 @@ function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
132
135
  // point.resultantRy = ry;
133
136
 
134
137
  return point;
135
- }
138
+ };
136
139
 
137
140
  /**
138
141
  * Returns a {x,y} point at a given length, the total length and
139
142
  * the shape minimum and maximum {x,y} coordinates of an A (arc-to) segment.
140
143
  *
141
- * @param {number} X1 the starting x position
142
- * @param {number} Y1 the starting y position
143
- * @param {number} RX x-radius of the arc
144
- * @param {number} RY y-radius of the arc
145
- * @param {number} angle x-axis-rotation of the arc
146
- * @param {number} LAF large-arc-flag of the arc
147
- * @param {number} SF sweep-flag of the arc
148
- * @param {number} X2 the ending x position
149
- * @param {number} Y2 the ending y position
150
- * @param {number} distance the point distance
151
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
144
+ * @param X1 the starting x position
145
+ * @param Y1 the starting y position
146
+ * @param RX x-radius of the arc
147
+ * @param RY y-radius of the arc
148
+ * @param angle x-axis-rotation of the arc
149
+ * @param LAF large-arc-flag of the arc
150
+ * @param SF sweep-flag of the arc
151
+ * @param X2 the ending x position
152
+ * @param Y2 the ending y position
153
+ * @param distance the point distance
154
+ * @returns the segment length, point, min & max
152
155
  */
153
- export default function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
156
+ const segmentArcFactory = (
157
+ X1: number,
158
+ Y1: number,
159
+ RX: number,
160
+ RY: number,
161
+ angle: number,
162
+ LAF: number,
163
+ SF: number,
164
+ X2: number,
165
+ Y2: number,
166
+ distance: number,
167
+ ): LengthFactory => {
154
168
  const distanceIsNumber = typeof distance === 'number';
155
- let x = X1; let y = Y1;
169
+ let x = X1;
170
+ let y = Y1;
156
171
  let LENGTH = 0;
157
172
  let prev = [x, y, LENGTH];
158
- let cur = [x, y];
173
+ let cur = [x, y] as [number, number];
159
174
  let t = 0;
160
175
  let POINT = { x: 0, y: 0 };
161
176
  let POINTS = [{ x, y }];
@@ -192,12 +207,13 @@ export default function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2
192
207
  length: LENGTH,
193
208
  point: POINT,
194
209
  min: {
195
- x: Math.min(...POINTS.map((n) => n.x)),
196
- y: Math.min(...POINTS.map((n) => n.y)),
210
+ x: Math.min(...POINTS.map(n => n.x)),
211
+ y: Math.min(...POINTS.map(n => n.y)),
197
212
  },
198
213
  max: {
199
- x: Math.max(...POINTS.map((n) => n.x)),
200
- y: Math.max(...POINTS.map((n) => n.y)),
214
+ x: Math.max(...POINTS.map(n => n.x)),
215
+ y: Math.max(...POINTS.map(n => n.y)),
201
216
  },
202
217
  };
203
- }
218
+ };
219
+ export default segmentArcFactory;
@@ -0,0 +1,114 @@
1
+ import { LengthFactory } from 'src/interface';
2
+ import distanceSquareRoot from '../math/distanceSquareRoot';
3
+
4
+ /**
5
+ * Returns a {x,y} point at a given length, the total length and
6
+ * the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
7
+ *
8
+ * @param x1 the starting point X
9
+ * @param y1 the starting point Y
10
+ * @param c1x the first control point X
11
+ * @param c1y the first control point Y
12
+ * @param c2x the second control point X
13
+ * @param c2y the second control point Y
14
+ * @param x2 the ending point X
15
+ * @param y2 the ending point Y
16
+ * @param t a [0-1] ratio
17
+ * @returns the point at cubic-bezier segment length
18
+ */
19
+ const getPointAtCubicSegmentLength = (
20
+ x1: number,
21
+ y1: number,
22
+ c1x: number,
23
+ c1y: number,
24
+ c2x: number,
25
+ c2y: number,
26
+ x2: number,
27
+ y2: number,
28
+ t: number,
29
+ ): { x: number; y: number } => {
30
+ const t1 = 1 - t;
31
+ return {
32
+ x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
33
+ y: t1 ** 3 * y1 + 3 * t1 ** 2 * t * c1y + 3 * t1 * t ** 2 * c2y + t ** 3 * y2,
34
+ };
35
+ };
36
+
37
+ /**
38
+ * Returns the length of a C (cubic-bezier) segment
39
+ * or an {x,y} point at a given length.
40
+ *
41
+ * @param x1 the starting point X
42
+ * @param y1 the starting point Y
43
+ * @param c1x the first control point X
44
+ * @param c1y the first control point Y
45
+ * @param c2x the second control point X
46
+ * @param c2y the second control point Y
47
+ * @param x2 the ending point X
48
+ * @param y2 the ending point Y
49
+ * @param distance the point distance
50
+ * @returns the segment length, point, min & max
51
+ */
52
+ const segmentCubicFactory = (
53
+ x1: number,
54
+ y1: number,
55
+ c1x: number,
56
+ c1y: number,
57
+ c2x: number,
58
+ c2y: number,
59
+ x2: number,
60
+ y2: number,
61
+ distance?: number,
62
+ ): LengthFactory => {
63
+ const distanceIsNumber = typeof distance === 'number';
64
+ let x = x1;
65
+ let y = y1;
66
+ let LENGTH = 0;
67
+ let prev = [x, y, LENGTH];
68
+ let cur = [x, y] as [number, number];
69
+ let t = 0;
70
+ let POINT = { x: 0, y: 0 };
71
+ let POINTS = [{ x, y }];
72
+
73
+ if (distanceIsNumber && distance <= 0) {
74
+ POINT = { x, y };
75
+ }
76
+
77
+ const sampleSize = 300;
78
+ for (let j = 0; j <= sampleSize; j += 1) {
79
+ t = j / sampleSize;
80
+
81
+ ({ x, y } = getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t));
82
+ POINTS = [...POINTS, { x, y }];
83
+ LENGTH += distanceSquareRoot(cur, [x, y]);
84
+ cur = [x, y];
85
+
86
+ if (distanceIsNumber && LENGTH > distance && distance > prev[2]) {
87
+ const dv = (LENGTH - distance) / (LENGTH - prev[2]);
88
+
89
+ POINT = {
90
+ x: cur[0] * (1 - dv) + prev[0] * dv,
91
+ y: cur[1] * (1 - dv) + prev[1] * dv,
92
+ };
93
+ }
94
+ prev = [x, y, LENGTH];
95
+ }
96
+
97
+ if (distanceIsNumber && distance >= LENGTH) {
98
+ POINT = { x: x2, y: y2 };
99
+ }
100
+
101
+ return {
102
+ length: LENGTH,
103
+ point: POINT,
104
+ min: {
105
+ x: Math.min(...POINTS.map(n => n.x)),
106
+ y: Math.min(...POINTS.map(n => n.y)),
107
+ },
108
+ max: {
109
+ x: Math.max(...POINTS.map(n => n.x)),
110
+ y: Math.max(...POINTS.map(n => n.y)),
111
+ },
112
+ };
113
+ };
114
+ export default segmentCubicFactory;
@@ -1,18 +1,19 @@
1
1
  import midPoint from '../math/midPoint';
2
2
  import distanceSquareRoot from '../math/distanceSquareRoot';
3
+ import type { LengthFactory } from '../interface';
3
4
 
4
5
  /**
5
6
  * Returns a {x,y} point at a given length, the total length and
6
7
  * the minimum and maximum {x,y} coordinates of a line (L,V,H,Z) segment.
7
8
  *
8
- * @param {number} x1 the starting point X
9
- * @param {number} y1 the starting point Y
10
- * @param {number} x2 the ending point X
11
- * @param {number} y2 the ending point Y
12
- * @param {number=} distance the distance to point
13
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
9
+ * @param x1 the starting point X
10
+ * @param y1 the starting point Y
11
+ * @param x2 the ending point X
12
+ * @param y2 the ending point Y
13
+ * @param distance the distance to point
14
+ * @returns the segment length, point, min & max
14
15
  */
15
- export default function segmentLineFactory(x1, y1, x2, y2, distance) {
16
+ const segmentLineFactory = (x1: number, y1: number, x2: number, y2: number, distance?: number): LengthFactory => {
16
17
  const length = distanceSquareRoot([x1, y1], [x2, y2]);
17
18
  let point = { x: 0, y: 0 };
18
19
 
@@ -40,4 +41,5 @@ export default function segmentLineFactory(x1, y1, x2, y2, distance) {
40
41
  y: Math.max(y1, y2),
41
42
  },
42
43
  };
43
- }
44
+ };
45
+ export default segmentLineFactory;
@@ -1,3 +1,4 @@
1
+ import { LengthFactory } from 'src/interface';
1
2
  import distanceSquareRoot from '../math/distanceSquareRoot';
2
3
 
3
4
  /**
@@ -6,46 +7,59 @@ import distanceSquareRoot from '../math/distanceSquareRoot';
6
7
  *
7
8
  * @see https://github.com/substack/point-at-length
8
9
  *
9
- * @param {number} x1 the starting point X
10
- * @param {number} y1 the starting point Y
11
- * @param {number} cx the control point X
12
- * @param {number} cy the control point Y
13
- * @param {number} x2 the ending point X
14
- * @param {number} y2 the ending point Y
15
- * @param {number} t a [0-1] ratio
16
- * @returns {{x: number, y: number}} the requested {x,y} coordinates
10
+ * @param x1 the starting point X
11
+ * @param y1 the starting point Y
12
+ * @param cx the control point X
13
+ * @param cy the control point Y
14
+ * @param x2 the ending point X
15
+ * @param y2 the ending point Y
16
+ * @param t a [0-1] ratio
17
+ * @returns the requested {x,y} coordinates
17
18
  */
18
- function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
19
+ const getPointAtQuadSegmentLength = (
20
+ x1: number,
21
+ y1: number,
22
+ cx: number,
23
+ cy: number,
24
+ x2: number,
25
+ y2: number,
26
+ t: number,
27
+ ): { x: number; y: number } => {
19
28
  const t1 = 1 - t;
20
29
  return {
21
- x: (t1 ** 2) * x1
22
- + 2 * t1 * t * cx
23
- + (t ** 2) * x2,
24
- y: (t1 ** 2) * y1
25
- + 2 * t1 * t * cy
26
- + (t ** 2) * y2,
30
+ x: t1 ** 2 * x1 + 2 * t1 * t * cx + t ** 2 * x2,
31
+ y: t1 ** 2 * y1 + 2 * t1 * t * cy + t ** 2 * y2,
27
32
  };
28
- }
33
+ };
29
34
 
30
35
  /**
31
36
  * Returns a {x,y} point at a given length, the total length and
32
37
  * the minimum and maximum {x,y} coordinates of a Q (quadratic-bezier) segment.
33
38
  *
34
- * @param {number} x1 the starting point X
35
- * @param {number} y1 the starting point Y
36
- * @param {number} qx the control point X
37
- * @param {number} qy the control point Y
38
- * @param {number} x2 the ending point X
39
- * @param {number} y2 the ending point Y
40
- * @param {number=} distance the distance to point
41
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
39
+ * @param x1 the starting point X
40
+ * @param y1 the starting point Y
41
+ * @param qx the control point X
42
+ * @param qy the control point Y
43
+ * @param x2 the ending point X
44
+ * @param y2 the ending point Y
45
+ * @param distance the distance to point
46
+ * @returns the segment length, point, min & max
42
47
  */
43
- export default function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
48
+ const segmentQuadFactory = (
49
+ x1: number,
50
+ y1: number,
51
+ qx: number,
52
+ qy: number,
53
+ x2: number,
54
+ y2: number,
55
+ distance?: number,
56
+ ): LengthFactory => {
44
57
  const distanceIsNumber = typeof distance === 'number';
45
- let x = x1; let y = y1;
58
+ let x = x1;
59
+ let y = y1;
46
60
  let LENGTH = 0;
47
61
  let prev = [x, y, LENGTH];
48
- let cur = [x, y];
62
+ let cur = [x, y] as [number, number];
49
63
  let t = 0;
50
64
  let POINT = { x: 0, y: 0 };
51
65
  let POINTS = [{ x, y }];
@@ -83,12 +97,13 @@ export default function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
83
97
  length: LENGTH,
84
98
  point: POINT,
85
99
  min: {
86
- x: Math.min(...POINTS.map((n) => n.x)),
87
- y: Math.min(...POINTS.map((n) => n.y)),
100
+ x: Math.min(...POINTS.map(n => n.x)),
101
+ y: Math.min(...POINTS.map(n => n.y)),
88
102
  },
89
103
  max: {
90
- x: Math.max(...POINTS.map((n) => n.x)),
91
- y: Math.max(...POINTS.map((n) => n.y)),
104
+ x: Math.max(...POINTS.map(n => n.x)),
105
+ y: Math.max(...POINTS.map(n => n.y)),
92
106
  },
93
107
  };
94
- }
108
+ };
109
+ export default segmentQuadFactory;
@@ -0,0 +1,214 @@
1
+ import type { CircleAttr, EllipseAttr, GlyphAttr, LineAttr, PolyAttr, RectAttr, ShapeParams } from '../interface';
2
+ import type { PathArray, PathSegment, ShapeOps, ShapeTypes } from '../types';
3
+ import pathToString from '../convert/pathToString';
4
+ import defaultOptions from '../options/options';
5
+ import error from '../parser/error';
6
+ import isValidPath from './isValidPath';
7
+
8
+ /**
9
+ * Supported shapes and their specific parameters.
10
+ */
11
+ const shapeParams: ShapeParams = {
12
+ line: ['x1', 'y1', 'x2', 'y2'],
13
+ circle: ['cx', 'cy', 'r'],
14
+ ellipse: ['cx', 'cy', 'rx', 'ry'],
15
+ rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
16
+ polygon: ['points'],
17
+ polyline: ['points'],
18
+ glyph: ['d'],
19
+ };
20
+
21
+ /**
22
+ * Returns a new `pathArray` from line attributes.
23
+ *
24
+ * @param attr shape configuration
25
+ * @returns a new line `pathArray`
26
+ */
27
+ export const getLinePath = (attr: LineAttr): PathArray => {
28
+ const { x1, y1, x2, y2 } = attr;
29
+ return [
30
+ ['M', x1, y1],
31
+ ['L', x2, y2],
32
+ ];
33
+ };
34
+
35
+ /**
36
+ * Returns a new `pathArray` like from polyline/polygon attributes.
37
+ *
38
+ * @param attr shape configuration
39
+ * @return a new polygon/polyline `pathArray`
40
+ */
41
+ export const getPolyPath = (attr: PolyAttr): PathArray => {
42
+ const pathArray = [] as PathSegment[];
43
+ const points = (attr.points || '')
44
+ .trim()
45
+ .split(/[\s|,]/)
46
+ .map(Number);
47
+
48
+ let index = 0;
49
+ while (index < points.length) {
50
+ pathArray.push([index ? 'L' : 'M', points[index], points[index + 1]]);
51
+ index += 2;
52
+ }
53
+
54
+ return (attr.type === 'polygon' ? [...pathArray, ['z']] : pathArray) as PathArray;
55
+ };
56
+
57
+ /**
58
+ * Returns a new `pathArray` from circle attributes.
59
+ *
60
+ * @param attr shape configuration
61
+ * @return a circle `pathArray`
62
+ */
63
+ export const getCirclePath = (attr: CircleAttr): PathArray => {
64
+ const { cx, cy, r } = attr;
65
+
66
+ return [
67
+ ['M', cx - r, cy],
68
+ ['a', r, r, 0, 1, 0, 2 * r, 0],
69
+ ['a', r, r, 0, 1, 0, -2 * r, 0],
70
+ ];
71
+ };
72
+
73
+ /**
74
+ * Returns a new `pathArray` from ellipse attributes.
75
+ *
76
+ * @param attr shape configuration
77
+ * @return an ellipse `pathArray`
78
+ */
79
+ export const getEllipsePath = (attr: EllipseAttr): PathArray => {
80
+ const { cx, cy, rx, ry } = attr;
81
+
82
+ return [
83
+ ['M', cx - rx, cy],
84
+ ['a', rx, ry, 0, 1, 0, 2 * rx, 0],
85
+ ['a', rx, ry, 0, 1, 0, -2 * rx, 0],
86
+ ];
87
+ };
88
+
89
+ /**
90
+ * Returns a new `pathArray` like from rect attributes.
91
+ *
92
+ * @param attr object with properties above
93
+ * @return a new `pathArray` from `<rect>` attributes
94
+ */
95
+ export const getRectanglePath = (attr: RectAttr): PathArray => {
96
+ const x = +attr.x || 0;
97
+ const y = +attr.y || 0;
98
+ const w = +attr.width;
99
+ const h = +attr.height;
100
+ let rx = +attr.rx;
101
+ let ry = +attr.ry;
102
+
103
+ // Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
104
+ if (rx || ry) {
105
+ rx = !rx ? ry : rx;
106
+ ry = !ry ? rx : ry;
107
+
108
+ /* istanbul ignore else */
109
+ if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
110
+ /* istanbul ignore else */
111
+ if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
112
+
113
+ return [
114
+ ['M', x + rx, y],
115
+ ['h', w - rx * 2],
116
+ ['s', rx, 0, rx, ry],
117
+ ['v', h - ry * 2],
118
+ ['s', 0, ry, -rx, ry],
119
+ ['h', -w + rx * 2],
120
+ ['s', -rx, 0, -rx, -ry],
121
+ ['v', -h + ry * 2],
122
+ ['s', 0, -ry, rx, -ry],
123
+ ];
124
+ }
125
+
126
+ return [['M', x, y], ['h', w], ['v', h], ['H', x], ['Z']];
127
+ };
128
+
129
+ /**
130
+ * Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
131
+ * `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
132
+ * is `true`, it will replace the target. The default `ownerDocument` is your current
133
+ * `document` browser page, if you want to use in server-side using `jsdom`, you can
134
+ * pass the `jsdom` `document` to `ownDocument`.
135
+ *
136
+ * It can also work with an options object,
137
+ *
138
+ * @see ShapeOps
139
+ *
140
+ * The newly created `<path>` element keeps all non-specific
141
+ * attributes like `class`, `fill`, etc.
142
+ *
143
+ * @param element target shape
144
+ * @param replace option to replace target
145
+ * @param ownerDocument document for create element
146
+ * @return the newly created `<path>` element
147
+ */
148
+ const shapeToPath = (
149
+ element: ShapeTypes | ShapeOps,
150
+ replace?: boolean,
151
+ ownerDocument?: Document,
152
+ ): SVGPathElement | false => {
153
+ const doc = ownerDocument || document;
154
+ const win = doc.defaultView || /* istanbul ignore next */ window;
155
+ const supportedShapes = Object.keys(shapeParams) as (keyof ShapeParams)[];
156
+ const elementIsElement = element instanceof win.SVGElement;
157
+ const tagName = elementIsElement ? element.tagName : null;
158
+
159
+ if (tagName && supportedShapes.every(s => tagName !== s)) {
160
+ throw TypeError(`${error}: "${tagName}" is not SVGElement`);
161
+ }
162
+
163
+ const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
164
+ const type = (elementIsElement ? tagName : element.type) as ShapeOps['type'];
165
+ const shapeAttrs = shapeParams[type] as string[];
166
+ const config = { type } as Record<string, string>;
167
+
168
+ if (elementIsElement) {
169
+ shapeAttrs.forEach(p => {
170
+ if (shapeAttrs.includes(p)) config[p] = element.getAttribute(p) as string;
171
+ });
172
+ // set no-specific shape attributes: fill, stroke, etc
173
+ Object.values(element.attributes).forEach(({ name, value }) => {
174
+ if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
175
+ });
176
+ } else {
177
+ Object.assign(config, element);
178
+ // set no-specific shape attributes: fill, stroke, etc
179
+ Object.keys(config).forEach(k => {
180
+ if (!shapeAttrs.includes(k) && k !== 'type') {
181
+ path.setAttribute(
182
+ k.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`),
183
+ config[k],
184
+ );
185
+ }
186
+ });
187
+ }
188
+
189
+ // set d
190
+ let description = '';
191
+ const round = defaultOptions.round as number;
192
+
193
+ /* istanbul ignore else */
194
+ if (type === 'circle') description = pathToString(getCirclePath(config as unknown as CircleAttr), round);
195
+ else if (type === 'ellipse') description = pathToString(getEllipsePath(config as unknown as EllipseAttr), round);
196
+ else if (['polyline', 'polygon'].includes(type))
197
+ description = pathToString(getPolyPath(config as unknown as PolyAttr), round);
198
+ else if (type === 'rect') description = pathToString(getRectanglePath(config as unknown as RectAttr), round);
199
+ else if (type === 'line') description = pathToString(getLinePath(config as unknown as LineAttr), round);
200
+ else if (type === 'glyph')
201
+ description = elementIsElement ? (element.getAttribute('d') as string) : (element as GlyphAttr).d;
202
+
203
+ // replace target element
204
+ if (isValidPath(description)) {
205
+ path.setAttribute('d', description);
206
+ if (replace && elementIsElement) {
207
+ element.before(path, element);
208
+ element.remove();
209
+ }
210
+ return path;
211
+ }
212
+ return false;
213
+ };
214
+ export default shapeToPath;