svg-path-commander 0.1.8 → 0.1.9

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 (154) hide show
  1. package/dist/svg-path-commander.esm.js +963 -351
  2. package/dist/svg-path-commander.esm.min.js +2 -2
  3. package/dist/svg-path-commander.js +968 -344
  4. package/dist/svg-path-commander.min.js +2 -2
  5. package/package.json +4 -4
  6. package/src/class/{SVGPathCommander.js → svg-path-commander.js} +179 -201
  7. package/src/class/version.js +9 -0
  8. package/src/convert/pathToAbsolute.js +99 -92
  9. package/src/convert/pathToCurve.js +45 -41
  10. package/src/convert/pathToRelative.js +96 -89
  11. package/src/convert/pathToString.js +14 -6
  12. package/src/index.js +9 -6
  13. package/src/math/distanceSquareRoot.js +14 -6
  14. package/src/math/epsilon.js +8 -1
  15. package/src/math/midPoint.js +13 -7
  16. package/src/math/polygonArea.js +25 -18
  17. package/src/math/polygonLength.js +19 -13
  18. package/src/math/rotateVector.js +13 -0
  19. package/src/options/options.js +12 -7
  20. package/src/parser/finalizeSegment.js +31 -0
  21. package/src/{util → parser}/invalidPathValue.js +2 -2
  22. package/src/parser/isArcCommand.js +10 -0
  23. package/src/parser/isDigit.js +9 -0
  24. package/src/{util → parser}/isDigitStart.js +13 -6
  25. package/src/{util → parser}/isPathCommand.js +25 -19
  26. package/src/parser/isSpace.js +16 -0
  27. package/src/parser/paramsCount.js +8 -0
  28. package/src/parser/parsePathString.js +40 -0
  29. package/src/parser/scanFlag.js +26 -0
  30. package/src/parser/scanParam.js +97 -0
  31. package/src/parser/scanSegment.js +70 -0
  32. package/src/parser/skipSpaces.js +15 -0
  33. package/src/parser/svgPathArray.js +23 -0
  34. package/src/process/arcToCubic.js +125 -111
  35. package/src/process/clonePath.js +14 -8
  36. package/src/process/fixArc.js +21 -0
  37. package/src/{util → process}/getSVGMatrix.js +57 -55
  38. package/src/process/lineToCubic.js +26 -17
  39. package/src/process/normalizePath.js +49 -41
  40. package/src/process/normalizeSegment.js +43 -35
  41. package/src/process/optimizePath.js +24 -14
  42. package/src/{util → process}/projection2d.js +29 -21
  43. package/src/process/quadToCubic.js +22 -11
  44. package/src/process/reverseCurve.js +19 -13
  45. package/src/process/reversePath.js +90 -83
  46. package/src/process/roundPath.js +26 -21
  47. package/src/process/segmentToCubic.js +38 -31
  48. package/src/process/shorthandToCubic.js +16 -6
  49. package/src/process/shorthandToQuad.js +16 -6
  50. package/src/process/splitCubic.js +26 -20
  51. package/src/process/splitPath.js +19 -10
  52. package/src/{util → process}/transformEllipse.js +73 -65
  53. package/src/process/transformPath.js +135 -125
  54. package/src/util/createPath.js +17 -8
  55. package/src/util/getCubicSize.js +62 -50
  56. package/src/util/getDrawDirection.js +13 -6
  57. package/src/util/getPathArea.js +52 -30
  58. package/src/util/getPathBBox.js +56 -50
  59. package/src/util/getPathLength.js +19 -13
  60. package/src/util/getPointAtLength.js +33 -28
  61. package/src/util/getPointAtSegLength.js +28 -14
  62. package/src/util/getSegArcLength.js +24 -21
  63. package/src/util/getSegCubicLength.js +44 -31
  64. package/src/util/getSegLineLength.js +14 -6
  65. package/src/util/getSegQuadLength.js +31 -19
  66. package/src/util/isAbsoluteArray.js +12 -5
  67. package/src/util/isCurveArray.js +12 -5
  68. package/src/util/isNormalizedArray.js +17 -8
  69. package/src/util/isPathArray.js +14 -8
  70. package/src/util/isRelativeArray.js +13 -6
  71. package/src/util/isValidPath.js +26 -0
  72. package/src/util/shapeToPath.js +169 -0
  73. package/src/util/util.js +65 -60
  74. package/types/index.d.ts +931 -2
  75. package/types/types.d.ts +69 -0
  76. package/src/process/finalizeSegment.js +0 -26
  77. package/src/process/parsePathString.js +0 -35
  78. package/src/process/scanFlag.js +0 -20
  79. package/src/process/scanParam.js +0 -93
  80. package/src/process/scanSegment.js +0 -64
  81. package/src/process/skipSpaces.js +0 -7
  82. package/src/util/DOMMatrix.js +0 -5
  83. package/src/util/fixArc.js +0 -13
  84. package/src/util/isArcCommand.js +0 -4
  85. package/src/util/isDigit.js +0 -3
  86. package/src/util/isSpace.js +0 -9
  87. package/src/util/paramsCount.js +0 -3
  88. package/src/util/rotateVector.js +0 -5
  89. package/src/util/svgPathArray.js +0 -10
  90. package/types/class/SVGPathCommander.d.ts +0 -91
  91. package/types/convert/pathToAbsolute.d.ts +0 -1
  92. package/types/convert/pathToCurve.d.ts +0 -1
  93. package/types/convert/pathToRelative.d.ts +0 -1
  94. package/types/convert/pathToString.d.ts +0 -1
  95. package/types/math/distanceSquareRoot.d.ts +0 -1
  96. package/types/math/epsilon.d.ts +0 -2
  97. package/types/math/midPoint.d.ts +0 -1
  98. package/types/math/polygonArea.d.ts +0 -1
  99. package/types/math/polygonLength.d.ts +0 -1
  100. package/types/options/options.d.ts +0 -6
  101. package/types/process/arcToCubic.d.ts +0 -1
  102. package/types/process/clonePath.d.ts +0 -1
  103. package/types/process/finalizeSegment.d.ts +0 -1
  104. package/types/process/lineToCubic.d.ts +0 -1
  105. package/types/process/normalizePath.d.ts +0 -1
  106. package/types/process/normalizeSegment.d.ts +0 -1
  107. package/types/process/optimizePath.d.ts +0 -1
  108. package/types/process/parsePathString.d.ts +0 -1
  109. package/types/process/quadToCubic.d.ts +0 -1
  110. package/types/process/reverseCurve.d.ts +0 -1
  111. package/types/process/reversePath.d.ts +0 -1
  112. package/types/process/roundPath.d.ts +0 -1
  113. package/types/process/scanFlag.d.ts +0 -1
  114. package/types/process/scanParam.d.ts +0 -1
  115. package/types/process/scanSegment.d.ts +0 -1
  116. package/types/process/segmentToCubic.d.ts +0 -1
  117. package/types/process/shorthandToCubic.d.ts +0 -4
  118. package/types/process/shorthandToQuad.d.ts +0 -4
  119. package/types/process/skipSpaces.d.ts +0 -1
  120. package/types/process/splitCubic.d.ts +0 -1
  121. package/types/process/splitPath.d.ts +0 -1
  122. package/types/process/transformPath.d.ts +0 -1
  123. package/types/util/DOMMatrix.d.ts +0 -2
  124. package/types/util/createPath.d.ts +0 -1
  125. package/types/util/fixArc.d.ts +0 -1
  126. package/types/util/getCubicSize.d.ts +0 -10
  127. package/types/util/getDrawDirection.d.ts +0 -1
  128. package/types/util/getPathArea.d.ts +0 -1
  129. package/types/util/getPathBBox.d.ts +0 -19
  130. package/types/util/getPathLength.d.ts +0 -1
  131. package/types/util/getPointAtLength.d.ts +0 -1
  132. package/types/util/getPointAtSegLength.d.ts +0 -4
  133. package/types/util/getSVGMatrix.d.ts +0 -1
  134. package/types/util/getSegArcLength.d.ts +0 -1
  135. package/types/util/getSegCubicLength.d.ts +0 -1
  136. package/types/util/getSegLineLength.d.ts +0 -1
  137. package/types/util/getSegQuadLength.d.ts +0 -1
  138. package/types/util/invalidPathValue.d.ts +0 -2
  139. package/types/util/isAbsoluteArray.d.ts +0 -1
  140. package/types/util/isArcCommand.d.ts +0 -1
  141. package/types/util/isCurveArray.d.ts +0 -1
  142. package/types/util/isDigit.d.ts +0 -1
  143. package/types/util/isDigitStart.d.ts +0 -1
  144. package/types/util/isNormalizedArray.d.ts +0 -1
  145. package/types/util/isPathArray.d.ts +0 -1
  146. package/types/util/isPathCommand.d.ts +0 -1
  147. package/types/util/isRelativeArray.d.ts +0 -1
  148. package/types/util/isSpace.d.ts +0 -1
  149. package/types/util/paramsCount.d.ts +0 -14
  150. package/types/util/projection2d.d.ts +0 -1
  151. package/types/util/rotateVector.d.ts +0 -4
  152. package/types/util/svgPathArray.d.ts +0 -12
  153. package/types/util/transformEllipse.d.ts +0 -5
  154. package/types/util/util.d.ts +0 -55
@@ -1,14 +1,8 @@
1
1
  /*!
2
- * SVGPathCommander v0.1.8 (http://thednp.github.io/svg-path-commander)
2
+ * SVGPathCommander v0.1.9 (http://thednp.github.io/svg-path-commander)
3
3
  * Copyright 2021 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
5
5
  */
6
- const SVGPCO = {
7
- origin: null,
8
- decimals: 4,
9
- round: 1,
10
- };
11
-
12
6
  // DOMMatrix Static methods
13
7
  // * `fromFloat64Array` and `fromFloat32Array` methods are not supported;
14
8
  // * `fromArray` a more simple implementation, should also accept float[32/64]Array;
@@ -16,15 +10,20 @@ const SVGPCO = {
16
10
  // * `fromString` parses and loads values from any valid CSS transform string.
17
11
 
18
12
  /**
19
- * Creates a new mutable `CSSMatrix` object given an array float values.
13
+ * Creates a new mutable `CSSMatrix` object given an array of floating point values.
14
+ *
15
+ * This static method invalidates arrays that contain non-number elements.
20
16
  *
21
17
  * If the array has six values, the result is a 2D matrix; if the array has 16 values,
22
18
  * the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
23
19
  *
24
- * @param {Number[]} array an `Array` to feed values from.
20
+ * @param {number[]} array an `Array` to feed values from.
25
21
  * @return {CSSMatrix} the resulted matrix.
26
22
  */
27
23
  function fromArray(array) {
24
+ if (!array.every((n) => !Number.isNaN(n))) {
25
+ throw TypeError(`CSSMatrix: "${array}" must only have numbers.`);
26
+ }
28
27
  const m = new CSSMatrix();
29
28
  const a = Array.from(array);
30
29
 
@@ -85,19 +84,22 @@ function fromArray(array) {
85
84
  m.m42 = m42;
86
85
  m.f = m42;
87
86
  } else {
88
- throw new TypeError('CSSMatrix: expecting a 6/16 values Array');
87
+ throw new TypeError('CSSMatrix: expecting an Array of 6/16 values.');
89
88
  }
90
89
  return m;
91
90
  }
92
91
 
93
92
  /**
94
- * Creates a new mutable `CSSMatrix` object given an existing matrix or a
95
- * `DOMMatrix` *Object* which provides the values for its properties.
93
+ * Creates a new mutable `CSSMatrix` instance given an existing matrix or a
94
+ * `DOMMatrix` instance which provides the values for its properties.
96
95
  *
97
- * @param {CSSMatrix | DOMMatrix} m the source matrix to feed values from.
96
+ * @param {CSSMatrix | DOMMatrix | jsonMatrix} m the source matrix to feed values from.
98
97
  * @return {CSSMatrix} the resulted matrix.
99
98
  */
100
99
  function fromMatrix(m) {
100
+ if (![CSSMatrix, DOMMatrix, Object].some((x) => m instanceof x)) {
101
+ throw TypeError(`CSSMatrix: "${m}" is not a DOMMatrix / CSSMatrix compatible object.`);
102
+ }
101
103
  return fromArray(
102
104
  [m.m11, m.m12, m.m13, m.m14,
103
105
  m.m21, m.m22, m.m23, m.m24,
@@ -107,15 +109,21 @@ function fromMatrix(m) {
107
109
  }
108
110
 
109
111
  /**
110
- * Feed a CSSMatrix object with a valid CSS transform value.
111
- * * matrix(a, b, c, d, e, f) - valid matrix() transform function
112
- * * matrix3d(m11, m12, m13, ...m44) - valid matrix3d() transform function
113
- * * translate(tx, ty) rotateX(alpha) - any valid transform function(s)
112
+ * Creates a new mutable `CSSMatrix` instance given any valid CSS transform string.
113
+ *
114
+ * * `matrix(a, b, c, d, e, f)` - valid matrix() transform function
115
+ * * `matrix3d(m11, m12, m13, ...m44)` - valid matrix3d() transform function
116
+ * * `translate(tx, ty) rotateX(alpha)` - any valid transform function(s)
117
+ *
118
+ * @copyright thednp © 2021
114
119
  *
115
120
  * @param {string} source valid CSS transform string syntax.
116
121
  * @return {CSSMatrix} the resulted matrix.
117
122
  */
118
123
  function fromString(source) {
124
+ if (typeof source !== 'string') {
125
+ throw TypeError(`CSSMatrix: "${source}" is not a string.`);
126
+ }
119
127
  const str = String(source).replace(/\s/g, '');
120
128
  let m = new CSSMatrix();
121
129
  let is2D = true;
@@ -123,17 +131,6 @@ function fromString(source) {
123
131
  const [prop, value] = fn.split('(');
124
132
  const components = value.split(',')
125
133
  .map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
126
- const [x, y, z, a] = components;
127
-
128
- // don't add perspective if is2D
129
- if (prop === 'matrix3d'
130
- || (prop === 'rotate3d' && [x, y].every((n) => !Number.isNaN(+n) && n !== 0) && a)
131
- || (['rotateX', 'rotateY'].includes(prop) && x)
132
- || (prop === 'translate3d' && [x, y, z].every((n) => !Number.isNaN(+n)) && z)
133
- || (prop === 'scale3d' && [x, y, z].every((n) => !Number.isNaN(+n) && n !== x))
134
- ) {
135
- is2D = false;
136
- }
137
134
  return { prop, components };
138
135
  });
139
136
 
@@ -195,9 +192,9 @@ function fromString(source) {
195
192
  *
196
193
  * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
197
194
  *
198
- * @param {Number} x the `x-axis` position.
199
- * @param {Number} y the `y-axis` position.
200
- * @param {Number} z the `z-axis` position.
195
+ * @param {number} x the `x-axis` position.
196
+ * @param {number} y the `y-axis` position.
197
+ * @param {number} z the `z-axis` position.
201
198
  * @return {CSSMatrix} the resulted matrix.
202
199
  */
203
200
  function Translate(x, y, z) {
@@ -215,9 +212,9 @@ function Translate(x, y, z) {
215
212
  *
216
213
  * http://en.wikipedia.org/wiki/Rotation_matrix
217
214
  *
218
- * @param {Number} rx the `x-axis` rotation.
219
- * @param {Number} ry the `y-axis` rotation.
220
- * @param {Number} rz the `z-axis` rotation.
215
+ * @param {number} rx the `x-axis` rotation.
216
+ * @param {number} ry the `y-axis` rotation.
217
+ * @param {number} rz the `z-axis` rotation.
221
218
  * @return {CSSMatrix} the resulted matrix.
222
219
  */
223
220
  function Rotate(rx, ry, rz) {
@@ -269,10 +266,10 @@ function Rotate(rx, ry, rz) {
269
266
  *
270
267
  * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
271
268
  *
272
- * @param {Number} x the `x-axis` vector length.
273
- * @param {Number} y the `y-axis` vector length.
274
- * @param {Number} z the `z-axis` vector length.
275
- * @param {Number} alpha the value in degrees of the rotation.
269
+ * @param {number} x the `x-axis` vector length.
270
+ * @param {number} y the `y-axis` vector length.
271
+ * @param {number} z the `z-axis` vector length.
272
+ * @param {number} alpha the value in degrees of the rotation.
276
273
  * @return {CSSMatrix} the resulted matrix.
277
274
  */
278
275
  function RotateAxisAngle(x, y, z, alpha) {
@@ -333,9 +330,9 @@ function RotateAxisAngle(x, y, z, alpha) {
333
330
  *
334
331
  * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
335
332
  *
336
- * @param {Number} x the `x-axis` scale.
337
- * @param {Number} y the `y-axis` scale.
338
- * @param {Number} z the `z-axis` scale.
333
+ * @param {number} x the `x-axis` scale.
334
+ * @param {number} y the `y-axis` scale.
335
+ * @param {number} z the `z-axis` scale.
339
336
  * @return {CSSMatrix} the resulted matrix.
340
337
  */
341
338
  function Scale(x, y, z) {
@@ -356,7 +353,7 @@ function Scale(x, y, z) {
356
353
  *
357
354
  * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
358
355
  *
359
- * @param {Number} angle the angle in degrees.
356
+ * @param {number} angle the angle in degrees.
360
357
  * @return {CSSMatrix} the resulted matrix.
361
358
  */
362
359
  function SkewX(angle) {
@@ -374,7 +371,7 @@ function SkewX(angle) {
374
371
  *
375
372
  * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
376
373
  *
377
- * @param {Number} angle the angle in degrees.
374
+ * @param {number} angle the angle in degrees.
378
375
  * @return {CSSMatrix} the resulted matrix.
379
376
  */
380
377
  function SkewY(angle) {
@@ -434,11 +431,11 @@ function Multiply(m1, m2) {
434
431
  class CSSMatrix {
435
432
  /**
436
433
  * @constructor
437
- * @param {any} args accepts all possible parameter configuration:
438
- * * Types: number[] | string | CSSMatrix | DOMMatrix | undefined
434
+ * @param {any} args accepts all parameter configurations:
435
+ *
439
436
  * * valid CSS transform string,
440
- * * CSSMatrix/DOMMatrix instance
441
- * * a 6/16 elements *Array*
437
+ * * CSSMatrix/DOMMatrix instance,
438
+ * * a 6/16 elements *Array*.
442
439
  */
443
440
  constructor(...args) {
444
441
  const m = this;
@@ -522,12 +519,8 @@ class CSSMatrix {
522
519
  * This method expects valid *matrix()* / *matrix3d()* string values, as well
523
520
  * as other transform functions like *translateX(10px)*.
524
521
  *
525
- * @param {String[] | Number[] | String | CSSMatrix | DOMMatrix} source
526
- * @return {CSSMatrix} a new matrix
527
- * can be one of the following
528
- * * valid CSS matrix string,
529
- * * 6/16 elements *Array*,
530
- * * CSSMatrix | DOMMatrix instance.
522
+ * @param {string | number[] | CSSMatrix | DOMMatrix} source
523
+ * @return {CSSMatrix} the matrix instance
531
524
  */
532
525
  setMatrixValue(source) {
533
526
  const m = this;
@@ -541,7 +534,6 @@ class CSSMatrix {
541
534
  return fromString(source);
542
535
  // [Arguments list | Array] come here
543
536
  } if (Array.isArray(source)) {
544
- // @ts-ignore
545
537
  return fromArray(source);
546
538
  }
547
539
  return m;
@@ -551,12 +543,10 @@ class CSSMatrix {
551
543
  * Creates and returns a string representation of the matrix in `CSS` matrix syntax,
552
544
  * using the appropriate `CSS` matrix notation.
553
545
  *
554
- * The 16 items in the array 3D matrix array are *transposed* in row-major order.
555
- *
556
546
  * matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
557
547
  * matrix *matrix(a, b, c, d, e, f)*
558
548
  *
559
- * @return {String} a string representation of the matrix
549
+ * @return {string} a string representation of the matrix
560
550
  */
561
551
  toString() {
562
552
  const m = this;
@@ -571,7 +561,7 @@ class CSSMatrix {
571
561
  *
572
562
  * Other methods make use of this method to feed their output values from this matrix.
573
563
  *
574
- * @return {Number[]} an *Array* representation of the matrix
564
+ * @return {number[]} an *Array* representation of the matrix
575
565
  */
576
566
  toArray() {
577
567
  const m = this;
@@ -587,19 +577,38 @@ class CSSMatrix {
587
577
  m.m41, m.m42, m.m43, m.m44];
588
578
  }
589
579
  // clean up the numbers
590
- // eslint-disable-next-line
591
- return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
580
+ // eslint-disable-next-line -- no-bitwise
581
+ return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
592
582
  }
583
+ /**
584
+ * @typedef {object} jsonMatrix
585
+ * @property {number} m11
586
+ * @property {number} m12
587
+ * @property {number} m13
588
+ * @property {number} m14
589
+ * @property {number} m21
590
+ * @property {number} m22
591
+ * @property {number} m23
592
+ * @property {number} m24
593
+ * @property {number} m31
594
+ * @property {number} m32
595
+ * @property {number} m33
596
+ * @property {number} m34
597
+ * @property {number} m41
598
+ * @property {number} m42
599
+ * @property {number} m43
600
+ * @property {number} m44
601
+ */
593
602
 
594
603
  /**
595
- * Returns a JSON representation of the `CSSMatrix` object, a standard *Object*
604
+ * Returns a JSON representation of the `CSSMatrix` instance, a standard *Object*
596
605
  * that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties and
597
606
  * excludes `is2D` & `isIdentity` properties.
598
607
  *
599
608
  * The result can also be used as a second parameter for the `fromMatrix` static method
600
609
  * to load values into a matrix instance.
601
610
  *
602
- * @return {Object} an *Object* with all matrix values.
611
+ * @return {jsonMatrix} an *Object* with all matrix values.
603
612
  */
604
613
  toJSON() {
605
614
  return JSON.parse(JSON.stringify(this));
@@ -610,10 +619,11 @@ class CSSMatrix {
610
619
  * matrix multiplied by the passed matrix, with the passed matrix to the right.
611
620
  * This matrix is not modified.
612
621
  *
613
- * @param {CSSMatrix} m2 CSSMatrix
614
- * @return {CSSMatrix} The result matrix.
622
+ * @param {CSSMatrix | DOMMatrix | jsonMatrix} m2 CSSMatrix
623
+ * @return {CSSMatrix} The resulted matrix.
615
624
  */
616
625
  multiply(m2) {
626
+ // @ts-ignore - we only access [m11, m12, ... m44] values
617
627
  return Multiply(this, m2);
618
628
  }
619
629
 
@@ -626,7 +636,7 @@ class CSSMatrix {
626
636
  * @param {number} x X component of the translation value.
627
637
  * @param {number} y Y component of the translation value.
628
638
  * @param {number} z Z component of the translation value.
629
- * @return {CSSMatrix} The result matrix
639
+ * @return {CSSMatrix} The resulted matrix
630
640
  */
631
641
  translate(x, y, z) {
632
642
  const X = x;
@@ -646,7 +656,7 @@ class CSSMatrix {
646
656
  * @param {number} x The X component of the scale value.
647
657
  * @param {number} y The Y component of the scale value.
648
658
  * @param {number} z The Z component of the scale value.
649
- * @return {CSSMatrix} The result matrix
659
+ * @return {CSSMatrix} The resulted matrix
650
660
  */
651
661
  scale(x, y, z) {
652
662
  const X = x;
@@ -668,7 +678,7 @@ class CSSMatrix {
668
678
  * @param {number} rx The X component of the rotation, or Z if Y and Z are null.
669
679
  * @param {number} ry The (optional) Y component of the rotation value.
670
680
  * @param {number} rz The (optional) Z component of the rotation value.
671
- * @return {CSSMatrix} The result matrix
681
+ * @return {CSSMatrix} The resulted matrix
672
682
  */
673
683
  rotate(rx, ry, rz) {
674
684
  let RX = rx;
@@ -689,10 +699,10 @@ class CSSMatrix {
689
699
  * @param {number} y The Y component of the axis vector.
690
700
  * @param {number} z The Z component of the axis vector.
691
701
  * @param {number} angle The angle of rotation about the axis vector, in degrees.
692
- * @return {CSSMatrix} The `CSSMatrix` result
702
+ * @return {CSSMatrix} The resulted matrix
693
703
  */
694
704
  rotateAxisAngle(x, y, z, angle) {
695
- if (arguments.length !== 4) {
705
+ if ([x, y, z, angle].some((n) => Number.isNaN(n))) {
696
706
  throw new TypeError('CSSMatrix: expecting 4 values');
697
707
  }
698
708
  return Multiply(this, RotateAxisAngle(x, y, z, angle));
@@ -703,7 +713,7 @@ class CSSMatrix {
703
713
  * This matrix is not modified.
704
714
  *
705
715
  * @param {number} angle The angle amount in degrees to skew.
706
- * @return {CSSMatrix} The `CSSMatrix` result
716
+ * @return {CSSMatrix} The resulted matrix
707
717
  */
708
718
  skewX(angle) {
709
719
  return Multiply(this, SkewX(angle));
@@ -714,24 +724,32 @@ class CSSMatrix {
714
724
  * This matrix is not modified.
715
725
  *
716
726
  * @param {number} angle The angle amount in degrees to skew.
717
- * @return {CSSMatrix} The `CSSMatrix` result
727
+ * @return {CSSMatrix} The resulted matrix
718
728
  */
719
729
  skewY(angle) {
720
730
  return Multiply(this, SkewY(angle));
721
731
  }
722
732
 
723
733
  /**
724
- * Transforms the specified point using the matrix, returning a new
734
+ * @typedef {Object} Tuple
735
+ * @property {number} x the `x-axis` component
736
+ * @property {number} y the `y-axis` component
737
+ * @property {number} z the `z-axis` component
738
+ * @property {number} w the `w` component
739
+ */
740
+
741
+ /**
742
+ * Transforms a specified point using the matrix, returning a new
725
743
  * Tuple *Object* comprising of the transformed point.
726
744
  * Neither the matrix nor the original point are altered.
727
745
  *
728
746
  * The method is equivalent with `transformPoint()` method
729
747
  * of the `DOMMatrix` constructor.
730
748
  *
731
- * JavaScript implementation by thednp
749
+ * @copyright thednp © 2021
732
750
  *
733
- * @param {{x: number, y: number, z: number, w: number}} v Tuple with `{x,y,z,w}` components
734
- * @return {{x: number, y: number, z: number, w: number}} the resulting Tuple
751
+ * @param {Tuple | DOMPoint} v Tuple or DOMPoint
752
+ * @return {Tuple} the resulting Tuple
735
753
  */
736
754
  transformPoint(v) {
737
755
  const M = this;
@@ -749,12 +767,12 @@ class CSSMatrix {
749
767
  }
750
768
 
751
769
  /**
752
- * Transforms the specified vector using the matrix, returning a new
770
+ * Transforms a specified vector using the matrix, returning a new
753
771
  * {x,y,z,w} Tuple *Object* comprising the transformed vector.
754
772
  * Neither the matrix nor the original vector are altered.
755
773
  *
756
- * @param {{x: number, y: number, z: number, w: number}} t Tuple with `{x,y,z,w}` components
757
- * @return {{x: number, y: number, z: number, w: number}} the resulting Tuple
774
+ * @param {Tuple} t Tuple with `{x,y,z,w}` components
775
+ * @return {Tuple} the resulting Tuple
758
776
  */
759
777
  transform(t) {
760
778
  const m = this;
@@ -784,39 +802,78 @@ CSSMatrix.fromArray = fromArray;
784
802
  CSSMatrix.fromMatrix = fromMatrix;
785
803
  CSSMatrix.fromString = fromString;
786
804
 
787
- const CSS3Matrix = typeof DOMMatrix !== 'undefined' ? DOMMatrix : CSSMatrix;
805
+ /**
806
+ * SVGPathCommander default options
807
+ *
808
+ * @type {Object.<string, (boolean | number | number[])>}
809
+ */
810
+ const SVGPCO = {
811
+ origin: null,
812
+ decimals: 4,
813
+ round: 1,
814
+ };
815
+
816
+ /**
817
+ * Splits an extended A (arc-to) segment into two cubic-bezier segments.
818
+ *
819
+ * @param {SVGPC.pathArray} path the `pathArray` this segment belongs to
820
+ * @param {string[]} allPathCommands all previous path commands
821
+ * @param {Number} i the index of the segment
822
+ */
788
823
 
789
- function fixArc(pathArray, allPathCommands, i) {
790
- if (pathArray[i].length > 7) {
791
- pathArray[i].shift();
792
- const pi = pathArray[i];
824
+ function fixArc(path, allPathCommands, i) {
825
+ if (path[i].length > 7) {
826
+ path[i].shift();
827
+ const segment = path[i];
793
828
  let ni = i; // ESLint
794
- while (pi.length) {
829
+ while (segment.length) {
795
830
  // if created multiple C:s, their original seg is saved
796
831
  allPathCommands[i] = 'A';
797
- pathArray.splice(ni += 1, 0, ['C'].concat(pi.splice(0, 6)));
832
+ path.splice(ni += 1, 0, ['C'].concat(segment.splice(0, 6)));
798
833
  }
799
- pathArray.splice(i, 1);
834
+ path.splice(i, 1);
800
835
  }
801
836
  }
802
837
 
803
- var paramsCount = {
838
+ /**
839
+ * @type {Object.<string, number>}
840
+ */
841
+ const paramsCount = {
804
842
  a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
805
843
  };
806
844
 
807
- function isPathArray(pathArray) {
808
- return Array.isArray(pathArray) && pathArray.every((seg) => {
809
- const pathCommand = seg[0].toLowerCase();
810
- return paramsCount[pathCommand] === seg.length - 1 && /[achlmrqstvz]/gi.test(pathCommand);
845
+ /**
846
+ * Iterates an array to check if it's an actual `pathArray`.
847
+ *
848
+ * @param {SVGPC.pathArray} path the `pathArray` to be checked
849
+ * @returns {boolean} iteration result
850
+ */
851
+ function isPathArray(path) {
852
+ return Array.isArray(path) && path.every((seg) => {
853
+ const lk = seg[0].toLowerCase();
854
+ return paramsCount[lk] === seg.length - 1 && /[achlmqstvz]/gi.test(lk);
811
855
  });
812
856
  }
813
857
 
814
- function isCurveArray(pathArray) {
815
- return isPathArray(pathArray) && pathArray.slice(1).every((seg) => seg[0] === 'C');
858
+ /**
859
+ * Iterates an array to check if it's a `pathArray`
860
+ * with all C (cubic bezier) segments.
861
+ *
862
+ * @param {SVGPC.pathArray} path the `Array` to be checked
863
+ * @returns {boolean} iteration result
864
+ */
865
+ function isCurveArray(path) {
866
+ return isPathArray(path) && path.slice(1).every((seg) => seg[0] === 'C');
816
867
  }
817
868
 
818
- function clonePath(pathArray) {
819
- return pathArray.map((x) => {
869
+ /**
870
+ * Returns a clone of an existing `pathArray`.
871
+ *
872
+ * @param {SVGPC.pathArray} path the original `pathArray`
873
+ * @returns {SVGPC.pathArray} the cloned `pathArray`
874
+ */
875
+ function clonePath(path) {
876
+ return path.map((x) => {
820
877
  if (Array.isArray(x)) {
821
878
  return clonePath(x);
822
879
  }
@@ -824,24 +881,29 @@ function clonePath(pathArray) {
824
881
  });
825
882
  }
826
883
 
827
- function finalizeSegment(state) {
828
- let pathCommand = state.pathValue[state.segmentStart];
884
+ /**
885
+ * Breaks the parsing of a pathString once a segment is finalized.
886
+ *
887
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
888
+ */
889
+ function finalizeSegment(path) {
890
+ let pathCommand = path.pathValue[path.segmentStart];
829
891
  let pathComLK = pathCommand.toLowerCase();
830
- let params = state.data;
892
+ let params = path.data;
831
893
 
832
894
  // Process duplicated commands (without comand name)
833
895
  if (pathComLK === 'm' && params.length > 2) {
834
- state.segments.push([pathCommand, params[0], params[1]]);
896
+ path.segments.push([pathCommand, params[0], params[1]]);
835
897
  params = params.slice(2);
836
898
  pathComLK = 'l';
837
899
  pathCommand = (pathCommand === 'm') ? 'l' : 'L';
838
900
  }
839
901
 
840
902
  if (pathComLK === 'r') {
841
- state.segments.push([pathCommand].concat(params));
903
+ path.segments.push([pathCommand].concat(params));
842
904
  } else {
843
905
  while (params.length >= paramsCount[pathComLK]) {
844
- state.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
906
+ path.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
845
907
  if (!paramsCount[pathComLK]) {
846
908
  break;
847
909
  }
@@ -851,32 +913,49 @@ function finalizeSegment(state) {
851
913
 
852
914
  const invalidPathValue = 'Invalid path value';
853
915
 
854
- function scanFlag(state) {
855
- const ch = state.pathValue.charCodeAt(state.index);
916
+ /**
917
+ * Validates an A (arc-to) specific path command value.
918
+ * Usually a `large-arc-flag` or `sweep-flag`.
919
+ *
920
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
921
+ */
922
+ function scanFlag(path) {
923
+ const { index } = path;
924
+ const ch = path.pathValue.charCodeAt(index);
856
925
 
857
926
  if (ch === 0x30/* 0 */) {
858
- state.param = 0;
859
- state.index += 1;
927
+ path.param = 0;
928
+ path.index += 1;
860
929
  return;
861
930
  }
862
931
 
863
932
  if (ch === 0x31/* 1 */) {
864
- state.param = 1;
865
- state.index += 1;
933
+ path.param = 1;
934
+ path.index += 1;
866
935
  return;
867
936
  }
868
937
 
869
- // state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')';
870
- state.err = `${invalidPathValue}: invalid Arc flag ${ch}`;
938
+ path.err = `${invalidPathValue}: invalid Arc flag ${ch}, expecting 0 or 1 at index ${index}`;
871
939
  }
872
940
 
941
+ /**
942
+ * Checks if a character is a digit.
943
+ *
944
+ * @param {string} code the character to check
945
+ * @returns {boolean} check result
946
+ */
873
947
  function isDigit(code) {
874
948
  return (code >= 48 && code <= 57); // 0..9
875
949
  }
876
950
 
877
- function scanParam(state) {
878
- const start = state.index;
879
- const { max } = state;
951
+ /**
952
+ * Validates every character of the path string,
953
+ * every path command, negative numbers or floating point numbers.
954
+ *
955
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
956
+ */
957
+ function scanParam(path) {
958
+ const { max, pathValue, index: start } = path;
880
959
  let index = start;
881
960
  let zeroFirst = false;
882
961
  let hasCeiling = false;
@@ -885,22 +964,22 @@ function scanParam(state) {
885
964
  let ch;
886
965
 
887
966
  if (index >= max) {
888
- // state.err = 'SvgPath: missed param (at pos ' + index + ')';
889
- state.err = `${invalidPathValue}: missing param ${state.pathValue[index]}`;
967
+ // path.err = 'SvgPath: missed param (at pos ' + index + ')';
968
+ path.err = `${invalidPathValue} at ${index}: missing param ${pathValue[index]}`;
890
969
  return;
891
970
  }
892
- ch = state.pathValue.charCodeAt(index);
971
+ ch = pathValue.charCodeAt(index);
893
972
 
894
973
  if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
895
974
  index += 1;
896
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
975
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
897
976
  }
898
977
 
899
978
  // This logic is shamelessly borrowed from Esprima
900
979
  // https://github.com/ariya/esprimas
901
980
  if (!isDigit(ch) && ch !== 0x2E/* . */) {
902
- // state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
903
- state.err = `${invalidPathValue} at index ${index}: ${state.pathValue[index]} is not a number`;
981
+ // path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
982
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} is not a number`;
904
983
  return;
905
984
  }
906
985
 
@@ -908,79 +987,99 @@ function scanParam(state) {
908
987
  zeroFirst = (ch === 0x30/* 0 */);
909
988
  index += 1;
910
989
 
911
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
990
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
912
991
 
913
992
  if (zeroFirst && index < max) {
914
993
  // decimal number starts with '0' such as '09' is illegal.
915
994
  if (ch && isDigit(ch)) {
916
- // state.err = 'SvgPath: numbers started with `0` such as `09`
995
+ // path.err = 'SvgPath: numbers started with `0` such as `09`
917
996
  // are illegal (at pos ' + start + ')';
918
- state.err = `${invalidPathValue}: ${state.pathValue[start]} illegal number`;
997
+ path.err = `${invalidPathValue} at index ${start}: ${pathValue[start]} illegal number`;
919
998
  return;
920
999
  }
921
1000
  }
922
1001
 
923
- while (index < max && isDigit(state.pathValue.charCodeAt(index))) {
1002
+ while (index < max && isDigit(pathValue.charCodeAt(index))) {
924
1003
  index += 1;
925
1004
  hasCeiling = true;
926
1005
  }
927
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1006
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
928
1007
  }
929
1008
 
930
1009
  if (ch === 0x2E/* . */) {
931
1010
  hasDot = true;
932
1011
  index += 1;
933
- while (isDigit(state.pathValue.charCodeAt(index))) {
1012
+ while (isDigit(pathValue.charCodeAt(index))) {
934
1013
  index += 1;
935
1014
  hasDecimal = true;
936
1015
  }
937
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1016
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
938
1017
  }
939
1018
 
940
1019
  if (ch === 0x65/* e */ || ch === 0x45/* E */) {
941
1020
  if (hasDot && !hasCeiling && !hasDecimal) {
942
- // state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
943
- state.err = `${invalidPathValue}: ${state.pathValue[index]} invalid float exponent`;
1021
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
944
1022
  return;
945
1023
  }
946
1024
 
947
1025
  index += 1;
948
1026
 
949
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1027
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
950
1028
  if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
951
1029
  index += 1;
952
1030
  }
953
- if (index < max && isDigit(state.pathValue.charCodeAt(index))) {
954
- while (index < max && isDigit(state.pathValue.charCodeAt(index))) {
1031
+ if (index < max && isDigit(pathValue.charCodeAt(index))) {
1032
+ while (index < max && isDigit(pathValue.charCodeAt(index))) {
955
1033
  index += 1;
956
1034
  }
957
1035
  } else {
958
- // state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
959
- state.err = `${invalidPathValue}: ${state.pathValue[index]} invalid float exponent`;
1036
+ // path.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
1037
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
960
1038
  return;
961
1039
  }
962
1040
  }
963
1041
 
964
- state.index = index;
965
- state.param = +state.pathValue.slice(start, index);
1042
+ path.index = index;
1043
+ path.param = +path.pathValue.slice(start, index);
966
1044
  }
967
1045
 
968
- function isSpace(ch) {
1046
+ /**
1047
+ * Checks if the character is a space.
1048
+ *
1049
+ * @param {string} code the character to check
1050
+ * @returns {boolean} check result
1051
+ */
1052
+ function isSpace(code) {
969
1053
  const specialSpaces = [
970
1054
  0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
971
1055
  0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
972
- return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
1056
+ // Line terminators
1057
+ return (code === 0x0A) || (code === 0x0D) || (code === 0x2028) || (code === 0x2029)
973
1058
  // White spaces
974
- || (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
975
- || (ch >= 0x1680 && specialSpaces.indexOf(ch) >= 0);
1059
+ || (code === 0x20) || (code === 0x09) || (code === 0x0B) || (code === 0x0C) || (code === 0xA0)
1060
+ || (code >= 0x1680 && specialSpaces.indexOf(code) >= 0);
976
1061
  }
977
1062
 
978
- function skipSpaces(state) {
979
- while (state.index < state.max && isSpace(state.pathValue.charCodeAt(state.index))) {
980
- state.index += 1;
1063
+ /**
1064
+ * Points the parser to the next character in the
1065
+ * path string every time it encounters any kind of
1066
+ * space character.
1067
+ *
1068
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
1069
+ */
1070
+ function skipSpaces(path) {
1071
+ const { pathValue, max } = path;
1072
+ while (path.index < max && isSpace(pathValue.charCodeAt(path.index))) {
1073
+ path.index += 1;
981
1074
  }
982
1075
  }
983
1076
 
1077
+ /**
1078
+ * Checks if the character is a path command.
1079
+ *
1080
+ * @param {string} code the character to check
1081
+ * @returns {boolean} check result
1082
+ */
984
1083
  function isPathCommand(code) {
985
1084
  // eslint-disable-next-line no-bitwise -- Impossible to satisfy
986
1085
  switch (code | 0x20) {
@@ -1001,6 +1100,13 @@ function isPathCommand(code) {
1001
1100
  }
1002
1101
  }
1003
1102
 
1103
+ /**
1104
+ * Checks if the character is or belongs to a number.
1105
+ * [0-9]|+|-|.
1106
+ *
1107
+ * @param {string} code the character to check
1108
+ * @returns {boolean} check result
1109
+ */
1004
1110
  function isDigitStart(code) {
1005
1111
  return (code >= 48 && code <= 57) /* 0..9 */
1006
1112
  || code === 0x2B /* + */
@@ -1008,117 +1114,161 @@ function isDigitStart(code) {
1008
1114
  || code === 0x2E; /* . */
1009
1115
  }
1010
1116
 
1117
+ /**
1118
+ * Checks if the character is an A (arc-to) path command.
1119
+ *
1120
+ * @param {string} code the character to check
1121
+ * @returns {boolean} check result
1122
+ */
1011
1123
  function isArcCommand(code) {
1012
1124
  // eslint-disable-next-line no-bitwise -- Impossible to satisfy
1013
1125
  return (code | 0x20) === 0x61;
1014
1126
  }
1015
1127
 
1016
- function scanSegment(state) {
1017
- const { max } = state;
1018
- const cmdCode = state.pathValue.charCodeAt(state.index);
1019
- const reqParams = paramsCount[state.pathValue[state.index].toLowerCase()];
1128
+ /**
1129
+ * Scans every character in the path string to determine
1130
+ * where a segment starts and where it ends.
1131
+ *
1132
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
1133
+ */
1134
+ function scanSegment(path) {
1135
+ const { max, pathValue, index } = path;
1136
+ const cmdCode = pathValue.charCodeAt(index);
1137
+ const reqParams = paramsCount[pathValue[index].toLowerCase()];
1020
1138
 
1021
- state.segmentStart = state.index;
1139
+ path.segmentStart = index;
1022
1140
 
1023
1141
  if (!isPathCommand(cmdCode)) {
1024
- state.err = `${invalidPathValue}: ${state.pathValue[state.index]} not a path command`;
1142
+ path.err = `${invalidPathValue}: ${pathValue[index]} not a path command`;
1025
1143
  return;
1026
1144
  }
1027
1145
 
1028
- state.index += 1;
1029
- skipSpaces(state);
1146
+ path.index += 1;
1147
+ skipSpaces(path);
1030
1148
 
1031
- state.data = [];
1149
+ path.data = [];
1032
1150
 
1033
1151
  if (!reqParams) {
1034
1152
  // Z
1035
- finalizeSegment(state);
1153
+ finalizeSegment(path);
1036
1154
  return;
1037
1155
  }
1038
1156
 
1039
1157
  for (;;) {
1040
1158
  for (let i = reqParams; i > 0; i -= 1) {
1041
- if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(state);
1042
- else scanParam(state);
1159
+ if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(path);
1160
+ else scanParam(path);
1043
1161
 
1044
- if (state.err.length) {
1162
+ if (path.err.length) {
1045
1163
  return;
1046
1164
  }
1047
- state.data.push(state.param);
1165
+ path.data.push(path.param);
1048
1166
 
1049
- skipSpaces(state);
1167
+ skipSpaces(path);
1050
1168
 
1051
1169
  // after ',' param is mandatory
1052
- if (state.index < max && state.pathValue.charCodeAt(state.index) === 0x2C/* , */) {
1053
- state.index += 1;
1054
- skipSpaces(state);
1170
+ if (path.index < max && pathValue.charCodeAt(path.index) === 0x2C/* , */) {
1171
+ path.index += 1;
1172
+ skipSpaces(path);
1055
1173
  }
1056
1174
  }
1057
1175
 
1058
- if (state.index >= state.max) {
1176
+ if (path.index >= path.max) {
1059
1177
  break;
1060
1178
  }
1061
1179
 
1062
1180
  // Stop on next segment
1063
- if (!isDigitStart(state.pathValue.charCodeAt(state.index))) {
1181
+ if (!isDigitStart(pathValue.charCodeAt(path.index))) {
1064
1182
  break;
1065
1183
  }
1066
1184
  }
1067
1185
 
1068
- finalizeSegment(state);
1186
+ finalizeSegment(path);
1069
1187
  }
1070
1188
 
1189
+ /**
1190
+ * The `parserPathArray` used by the parser.
1191
+ *
1192
+ * @param {string} pathString
1193
+ */
1071
1194
  function SVGPathArray(pathString) {
1195
+ /** @type {[string, ...number[]][]} */
1072
1196
  this.segments = [];
1197
+ /** @type {string} */
1073
1198
  this.pathValue = pathString;
1199
+ /** @type {number} */
1074
1200
  this.max = pathString.length;
1201
+ /** @type {number} */
1075
1202
  this.index = 0;
1203
+ /** @type {number} */
1076
1204
  this.param = 0.0;
1205
+ /** @type {number} */
1077
1206
  this.segmentStart = 0;
1207
+ /** @type {any} */
1078
1208
  this.data = [];
1209
+ /** @type {string} */
1079
1210
  this.err = '';
1080
1211
  }
1081
1212
 
1082
- // Returns array of segments:
1083
- function parsePathString(pathString) {
1084
- if (isPathArray(pathString)) {
1085
- return clonePath(pathString);
1213
+ /**
1214
+ * Parses a path string value and returns an array
1215
+ * of segments we like to call `pathArray`.
1216
+ *
1217
+ * @param {string | SVGPC.pathArray} pathInput the string to be parsed
1218
+ * @returns {SVGPC.pathArray} the resulted `pathArray`
1219
+ */
1220
+ function parsePathString(pathInput) {
1221
+ if (isPathArray(pathInput)) {
1222
+ return clonePath(pathInput);
1086
1223
  }
1087
1224
 
1088
- const state = new SVGPathArray(pathString);
1225
+ const path = new SVGPathArray(pathInput);
1089
1226
 
1090
- skipSpaces(state);
1227
+ skipSpaces(path);
1091
1228
 
1092
- while (state.index < state.max && !state.err.length) {
1093
- scanSegment(state);
1229
+ while (path.index < path.max && !path.err.length) {
1230
+ scanSegment(path);
1094
1231
  }
1095
1232
 
1096
- if (state.err.length) {
1097
- state.segments = [];
1098
- } else if (state.segments.length) {
1099
- if ('mM'.indexOf(state.segments[0][0]) < 0) {
1100
- // state.err = 'Path string should start with `M` or `m`';
1101
- state.err = `${invalidPathValue}: missing M/m`;
1102
- state.segments = [];
1233
+ if (path.err.length) {
1234
+ path.segments = [];
1235
+ } else if (path.segments.length) {
1236
+ if ('mM'.indexOf(path.segments[0][0]) < 0) {
1237
+ path.err = `${invalidPathValue}: missing M/m`;
1238
+ path.segments = [];
1103
1239
  } else {
1104
- state.segments[0][0] = 'M';
1240
+ path.segments[0][0] = 'M';
1105
1241
  }
1106
1242
  }
1107
1243
 
1108
- return state.segments;
1244
+ return path.segments;
1109
1245
  }
1110
1246
 
1111
- function isAbsoluteArray(pathInput) {
1112
- return isPathArray(pathInput) && pathInput.every((x) => x[0] === x[0].toUpperCase());
1247
+ /**
1248
+ * Iterates an array to check if it's a `pathArray`
1249
+ * with all absolute values.
1250
+ *
1251
+ * @param {SVGPC.pathArray} path the `pathArray` to be checked
1252
+ * @returns {boolean} iteration result
1253
+ */
1254
+ function isAbsoluteArray(path) {
1255
+ return isPathArray(path) && path.every((x) => x[0] === x[0].toUpperCase());
1113
1256
  }
1114
1257
 
1258
+ /**
1259
+ * Parses a path string value or object and returns an array
1260
+ * of segments, all converted to absolute values.
1261
+ *
1262
+ * @param {string | SVGPC.pathArray} pathInput the path string | object
1263
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with absolute values
1264
+ */
1115
1265
  function pathToAbsolute(pathInput) {
1116
1266
  if (isAbsoluteArray(pathInput)) {
1117
1267
  return clonePath(pathInput);
1118
1268
  }
1119
1269
 
1120
- const pathArray = parsePathString(pathInput);
1121
- const ii = pathArray.length;
1270
+ const path = parsePathString(pathInput);
1271
+ const ii = path.length;
1122
1272
  const resultArray = [];
1123
1273
  let x = 0;
1124
1274
  let y = 0;
@@ -1126,9 +1276,9 @@ function pathToAbsolute(pathInput) {
1126
1276
  let my = 0;
1127
1277
  let start = 0;
1128
1278
 
1129
- if (pathArray[0][0] === 'M') {
1130
- x = +pathArray[0][1];
1131
- y = +pathArray[0][2];
1279
+ if (path[0][0] === 'M') {
1280
+ x = +path[0][1];
1281
+ y = +path[0][2];
1132
1282
  mx = x;
1133
1283
  my = y;
1134
1284
  start += 1;
@@ -1136,7 +1286,7 @@ function pathToAbsolute(pathInput) {
1136
1286
  }
1137
1287
 
1138
1288
  for (let i = start; i < ii; i += 1) {
1139
- const segment = pathArray[i];
1289
+ const segment = path[i];
1140
1290
  const [pathCommand] = segment;
1141
1291
  const absCommand = pathCommand.toUpperCase();
1142
1292
  const absoluteSegment = [];
@@ -1201,20 +1351,48 @@ function pathToAbsolute(pathInput) {
1201
1351
  return resultArray;
1202
1352
  }
1203
1353
 
1204
- // returns {qx,qy} for shorthand quadratic bezier segments
1354
+ /**
1355
+ * Returns the missing control point from an
1356
+ * T (shorthand quadratic bezier) segment.
1357
+ *
1358
+ * @param {Number} x1 curve start x
1359
+ * @param {Number} y1 curve start y
1360
+ * @param {Number} qx control point x
1361
+ * @param {Number} qy control point y
1362
+ * @param {String} prevCommand the previous path command
1363
+ * @returns {Object} the missing control point
1364
+ */
1205
1365
  function shorthandToQuad(x1, y1, qx, qy, prevCommand) {
1206
1366
  return 'QT'.indexOf(prevCommand) > -1
1207
1367
  ? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
1208
1368
  : { qx: x1, qy: y1 };
1209
1369
  }
1210
1370
 
1211
- // returns {x1,x2} for shorthand cubic bezier segments
1371
+ /**
1372
+ * Returns the missing control point from an
1373
+ * S (shorthand cubic bezier) segment.
1374
+ *
1375
+ * @param {Number} x1 curve start x
1376
+ * @param {Number} y1 curve start y
1377
+ * @param {Number} x2 curve end x
1378
+ * @param {Number} y2 curve end y
1379
+ * @param {String} prevCommand the previous path command
1380
+ * @returns {Object} the missing control point
1381
+ */
1212
1382
  function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
1213
1383
  return 'CS'.indexOf(prevCommand) > -1
1214
1384
  ? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
1215
1385
  : { x1, y1 };
1216
1386
  }
1217
1387
 
1388
+ /**
1389
+ * Normalizes a single segment of a `pathArray` object.
1390
+ *
1391
+ * @param {SVGPC.pathSegment} segment the segment object
1392
+ * @param {Object} params the coordinates of the previous segment
1393
+ * @param {String} prevCommand the path command of the previous segment
1394
+ * @returns {SVGPC.pathSegment} the normalized segment
1395
+ */
1218
1396
  function normalizeSegment(segment, params, prevCommand) {
1219
1397
  const [pathCommand] = segment;
1220
1398
  const xy = segment.slice(1);
@@ -1248,40 +1426,56 @@ function normalizeSegment(segment, params, prevCommand) {
1248
1426
  return result;
1249
1427
  }
1250
1428
 
1251
- function isNormalizedArray(pathArray) {
1252
- return Array.isArray(pathArray) && pathArray.every((seg) => {
1253
- const pathCommand = seg[0].toLowerCase();
1254
- return paramsCount[pathCommand] === seg.length - 1 && /[ACLMQZ]/.test(seg[0]); // achlmrqstvz
1429
+ /**
1430
+ * Iterates an array to check if it's a `pathArray`
1431
+ * with all segments are in non-shorthand notation
1432
+ * with absolute values.
1433
+ *
1434
+ * @param {SVGPC.pathArray} path the `pathArray` to be checked
1435
+ * @returns {boolean} iteration result
1436
+ */
1437
+ function isNormalizedArray(path) {
1438
+ return isPathArray(path) && path.every((seg) => {
1439
+ const lk = seg[0].toLowerCase();
1440
+ return paramsCount[lk] === seg.length - 1 && ('ACLMQZ').includes(seg[0]); // achlmqstvz
1255
1441
  });
1256
1442
  }
1257
1443
 
1258
- function normalizePath(pathInput) { // pathArray|pathString
1444
+ /**
1445
+ * Normalizes a `path` object for further processing:
1446
+ * * convert segments to absolute values
1447
+ * * convert shorthand path commands to their non-shorthand notation
1448
+ *
1449
+ * @param {String | SVGPC.pathArray} pathInput the string to be parsed or 'pathArray'
1450
+ * @returns {SVGPC.pathArray} the normalized `pathArray`
1451
+ */
1452
+ function normalizePath(pathInput) { // path|pathString
1259
1453
  if (isNormalizedArray(pathInput)) {
1260
1454
  return clonePath(pathInput);
1261
1455
  }
1262
1456
 
1263
- const pathArray = pathToAbsolute(pathInput);
1457
+ const path = pathToAbsolute(pathInput);
1264
1458
  const params = {
1265
1459
  x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
1266
1460
  };
1267
1461
  const allPathCommands = [];
1268
- const ii = pathArray.length;
1462
+ const ii = path.length;
1269
1463
  let prevCommand = '';
1270
1464
  let segment;
1271
1465
  let seglen;
1272
1466
 
1273
1467
  for (let i = 0; i < ii; i += 1) {
1274
1468
  // save current path command
1275
- const [pathCommand] = pathArray[i];
1469
+ const [pathCommand] = path[i];
1276
1470
 
1277
1471
  // Save current path command
1278
1472
  allPathCommands[i] = pathCommand;
1279
1473
  // Get previous path command
1280
1474
  if (i) prevCommand = allPathCommands[i - 1];
1281
1475
  // Previous path command is inputted to processSegment
1282
- pathArray[i] = normalizeSegment(pathArray[i], params, prevCommand);
1476
+ path[i] = normalizeSegment(path[i], params, prevCommand);
1283
1477
 
1284
- segment = pathArray[i];
1478
+ segment = path[i];
1285
1479
  seglen = segment.length;
1286
1480
 
1287
1481
  params.x1 = +segment[seglen - 2];
@@ -1289,19 +1483,41 @@ function normalizePath(pathInput) { // pathArray|pathString
1289
1483
  params.x2 = +(segment[seglen - 4]) || params.x1;
1290
1484
  params.y2 = +(segment[seglen - 3]) || params.y1;
1291
1485
  }
1292
- return pathArray;
1486
+ return path;
1293
1487
  }
1294
1488
 
1489
+ /**
1490
+ * Returns an {x,y} vector rotated by a given
1491
+ * angle in radian.
1492
+ *
1493
+ * @param {Number} x the initial vector x
1494
+ * @param {Number} y the initial vector y
1495
+ * @returns {{x: number, y: number}} the rotated vector
1496
+ */
1295
1497
  function rotateVector(x, y, rad) {
1296
1498
  const X = x * Math.cos(rad) - y * Math.sin(rad);
1297
1499
  const Y = x * Math.sin(rad) + y * Math.cos(rad);
1298
1500
  return { x: X, y: Y };
1299
1501
  }
1300
1502
 
1301
- // for more information of where this math came from visit:
1302
- // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1303
- // LAF = largeArcFlag, SF = sweepFlag
1304
-
1503
+ /**
1504
+ * Converts A (arc-to) segments to C (cubic-bezier-to).
1505
+ *
1506
+ * For more information of where this math came from visit:
1507
+ * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1508
+ *
1509
+ * @param {Number} x1 the starting x position
1510
+ * @param {Number} y1 the starting y position
1511
+ * @param {Number} rx x-radius of the arc
1512
+ * @param {Number} ry y-radius of the arc
1513
+ * @param {Number} angle x-axis-rotation of the arc
1514
+ * @param {Number} LAF large-arc-flag of the arc
1515
+ * @param {Number} SF sweep-flag of the arc
1516
+ * @param {Number} x2 the ending x position
1517
+ * @param {Number} y2 the ending y position
1518
+ * @param {Number[] | null} recursive the parameters needed to split arc into 2 segments
1519
+ * @return {Number[] | Number[][]} the resulting cubic-bezier segment(s)
1520
+ */
1305
1521
  function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
1306
1522
  const d120 = (Math.PI * 120) / 180;
1307
1523
  const rad = (Math.PI / 180) * (angle || 0);
@@ -1408,6 +1624,17 @@ function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
1408
1624
  });
1409
1625
  }
1410
1626
 
1627
+ /**
1628
+ * Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
1629
+ *
1630
+ * @param {Number} x1 curve start x
1631
+ * @param {Number} y1 curve start y
1632
+ * @param {Number} qx control point x
1633
+ * @param {Number} qy control point y
1634
+ * @param {Number} x2 curve end x
1635
+ * @param {Number} y2 curve end y
1636
+ * @returns {Number[]} the cubic-bezier segment
1637
+ */
1411
1638
  function quadToCubic(x1, y1, qx, qy, x2, y2) {
1412
1639
  const r13 = 1 / 3;
1413
1640
  const r23 = 2 / 3;
@@ -1420,7 +1647,21 @@ function quadToCubic(x1, y1, qx, qy, x2, y2) {
1420
1647
  ];
1421
1648
  }
1422
1649
 
1423
- // t = [0-1]
1650
+ /**
1651
+ * Returns the {x,y} coordinates of a point at a
1652
+ * given length of a cubic-bezier segment.
1653
+ *
1654
+ * @param {Number} p1x the starting point X
1655
+ * @param {Number} p1y the starting point Y
1656
+ * @param {Number} c1x the first control point X
1657
+ * @param {Number} c1y the first control point Y
1658
+ * @param {Number} c2x the second control point X
1659
+ * @param {Number} c2y the second control point Y
1660
+ * @param {Number} px2 the ending point X
1661
+ * @param {Number} py2 the ending point Y
1662
+ * @param {Number} t a [0-1] ratio
1663
+ * @returns {{x: number, y: number}} the requested {x,y} coordinates
1664
+ */
1424
1665
  function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
1425
1666
  const t1 = 1 - t;
1426
1667
  return {
@@ -1435,14 +1676,29 @@ function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
1435
1676
  };
1436
1677
  }
1437
1678
 
1679
+ /**
1680
+ * Returns the coordinates of a specified distance
1681
+ * ratio between two points.
1682
+ *
1683
+ * @param {Number[]} a the first point coordinates
1684
+ * @param {Number[]} b the second point coordinates
1685
+ * @param {Number} t the ratio
1686
+ * @returns {Number[]} the midpoint coordinates
1687
+ */
1438
1688
  function midPoint(a, b, t) {
1439
- const ax = a[0];
1440
- const ay = a[1];
1441
- const bx = b[0];
1442
- const by = b[1];
1689
+ const [ax, ay] = a; const [bx, by] = b;
1443
1690
  return [ax + (bx - ax) * t, ay + (by - ay) * t];
1444
1691
  }
1445
1692
 
1693
+ /**
1694
+ * Converts an L (line-to) segment to C (cubic-bezier).
1695
+ *
1696
+ * @param {Number} x1 line start x
1697
+ * @param {Number} y1 line start y
1698
+ * @param {Number} x2 line end x
1699
+ * @param {Number} y2 line end y
1700
+ * @returns {Number[]} the cubic-bezier segment
1701
+ */
1446
1702
  function lineToCubic(x1, y1, x2, y2) {
1447
1703
  const t = 0.5;
1448
1704
  const p0 = [x1, y1];
@@ -1458,6 +1714,13 @@ function lineToCubic(x1, y1, x2, y2) {
1458
1714
  return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
1459
1715
  }
1460
1716
 
1717
+ /**
1718
+ * Converts any segment to C (cubic-bezier).
1719
+ *
1720
+ * @param {SVGPC.pathSegment} segment the source segment
1721
+ * @param {Object.<string, number>} params the source segment parameters
1722
+ * @returns {SVGPC.pathSegment} the cubic-bezier segment
1723
+ */
1461
1724
  function segmentToCubic(segment, params) {
1462
1725
  if ('TQ'.indexOf(segment[0]) < 0) {
1463
1726
  params.qx = null;
@@ -1485,32 +1748,36 @@ function segmentToCubic(segment, params) {
1485
1748
  return segment;
1486
1749
  }
1487
1750
 
1488
- function pathToCurve(pathInput) { // pathArray|pathString
1751
+ /**
1752
+ * Parses a path string value or 'pathArray' and returns a new one
1753
+ * in which all segments are converted to cubic-bezier.
1754
+ *
1755
+ * @param {String | SVGPC.pathArray} pathInput the string to be parsed or object
1756
+ * @returns {SVGPC.pathArray} the resulted `pathArray` converted to cubic-bezier
1757
+ */
1758
+ function pathToCurve(pathInput) {
1489
1759
  if (isCurveArray(pathInput)) {
1490
1760
  return clonePath(pathInput);
1491
1761
  }
1492
1762
 
1493
- const pathArray = normalizePath(pathInput);
1763
+ const path = normalizePath(pathInput);
1494
1764
  const params = {
1495
1765
  x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
1496
1766
  };
1497
1767
  const allPathCommands = [];
1498
1768
  let pathCommand = '';
1499
- let ii = pathArray.length;
1500
- let segment;
1501
- let seglen;
1769
+ let ii = path.length;
1502
1770
 
1503
1771
  for (let i = 0; i < ii; i += 1) {
1504
- if (pathArray[i]) [pathCommand] = pathArray[i];
1772
+ const segment = path[i];
1773
+ const seglen = segment.length;
1774
+ if (segment) [pathCommand] = segment;
1505
1775
 
1506
1776
  allPathCommands[i] = pathCommand;
1507
- pathArray[i] = segmentToCubic(pathArray[i], params);
1777
+ path[i] = segmentToCubic(segment, params);
1508
1778
 
1509
- fixArc(pathArray, allPathCommands, i);
1510
- ii = pathArray.length; // solves curveArrays ending in Z
1511
-
1512
- segment = pathArray[i];
1513
- seglen = segment.length;
1779
+ fixArc(path, allPathCommands, i);
1780
+ ii = path.length; // solves curveArrays ending in Z
1514
1781
 
1515
1782
  params.x1 = +segment[seglen - 2];
1516
1783
  params.y1 = +segment[seglen - 1];
@@ -1518,22 +1785,44 @@ function pathToCurve(pathInput) { // pathArray|pathString
1518
1785
  params.y2 = +(segment[seglen - 3]) || params.y1;
1519
1786
  }
1520
1787
 
1521
- return pathArray;
1788
+ return path;
1522
1789
  }
1523
1790
 
1524
- // https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
1525
-
1791
+ /**
1792
+ * Returns the area of a single segment shape.
1793
+ *
1794
+ * http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
1795
+ *
1796
+ * @param {number} x0 the starting point X
1797
+ * @param {number} y0 the starting point Y
1798
+ * @param {number} x1 the first control point X
1799
+ * @param {number} y1 the first control point Y
1800
+ * @param {number} x2 the second control point X
1801
+ * @param {number} y2 the second control point Y
1802
+ * @param {number} x3 the ending point X
1803
+ * @param {number} y3 the ending point Y
1804
+ * @returns {number} the area of the cubic-bezier segment
1805
+ */
1526
1806
  function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
1527
- // http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
1528
1807
  return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
1529
1808
  + (y1 * (x0 - x2)) - (x1 * (y0 - y2))
1530
1809
  + (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
1531
1810
  }
1532
1811
 
1533
- function getPathArea(pathArray) {
1534
- let x = 0; let y = 0; let mx = 0; let my = 0; let
1535
- len = 0;
1536
- return pathToCurve(pathArray).map((seg) => {
1812
+ /**
1813
+ * Returns the area of a shape.
1814
+ * @author Jürg Lehni & Jonathan Puckey
1815
+ *
1816
+ * => https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
1817
+ *
1818
+ * @param {SVGPC.pathArray} path the shape `pathArray`
1819
+ * @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
1820
+ */
1821
+ function getPathArea(path) {
1822
+ let x = 0; let y = 0;
1823
+ let mx = 0; let my = 0;
1824
+ let len = 0;
1825
+ return pathToCurve(path).map((seg) => {
1537
1826
  switch (seg[0]) {
1538
1827
  case 'M':
1539
1828
  case 'Z':
@@ -1556,7 +1845,20 @@ function base3(p1, p2, p3, p4, t) {
1556
1845
  return t * t2 - 3 * p1 + 3 * p2;
1557
1846
  }
1558
1847
 
1559
- // returns the cubic bezier segment length
1848
+ /**
1849
+ * Returns the C (cubic-bezier) segment length.
1850
+ *
1851
+ * @param {Number} x1 the starting point X
1852
+ * @param {Number} y1 the starting point Y
1853
+ * @param {Number} x2 the first control point X
1854
+ * @param {Number} y2 the first control point Y
1855
+ * @param {Number} x3 the second control point X
1856
+ * @param {Number} y3 the second control point Y
1857
+ * @param {Number} x4 the ending point X
1858
+ * @param {Number} y4 the ending point Y
1859
+ * @param {Number} z a [0-1] ratio
1860
+ * @returns {Number} the cubic-bezier segment length
1861
+ */
1560
1862
  function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
1561
1863
  let Z;
1562
1864
  if (z === null || Number.isNaN(+z)) Z = 1;
@@ -1582,30 +1884,48 @@ function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
1582
1884
  return z2 * sum;
1583
1885
  }
1584
1886
 
1585
- // calculates the shape total length
1586
- // equivalent to shape.getTotalLength()
1587
- // pathToCurve version
1588
- function getPathLength(pathArray) {
1887
+ /**
1888
+ * Returns the shape total length,
1889
+ * or the equivalent to `shape.getTotalLength()`
1890
+ * pathToCurve version
1891
+ *
1892
+ * @param {SVGPC.pathArray} path the ending point Y
1893
+ * @returns {Number} the shape total length
1894
+ */
1895
+ function getPathLength(path) {
1589
1896
  let totalLength = 0;
1590
- pathToCurve(pathArray).forEach((s, i, curveArray) => {
1591
- totalLength += s[0] !== 'M' ? getSegCubicLength.apply(0, curveArray[i - 1].slice(-2).concat(s.slice(1))) : 0;
1897
+ pathToCurve(path).forEach((s, i, curveArray) => {
1898
+ totalLength += s[0] === 'M' ? 0
1899
+ : getSegCubicLength.apply(0, curveArray[i - 1].slice(-2).concat(s.slice(1)));
1592
1900
  });
1593
1901
  return totalLength;
1594
1902
  }
1595
1903
 
1596
- function getDrawDirection(pathArray) {
1597
- return getPathArea(pathToCurve(pathArray)) >= 0;
1904
+ /**
1905
+ * Check if a path is drawn clockwise and returns true if so,
1906
+ * false otherwise.
1907
+ *
1908
+ * @param {string | SVGPC.pathArray} path the path string or `pathArray`
1909
+ * @returns {boolean} true when clockwise or false if not
1910
+ */
1911
+ function getDrawDirection(path) {
1912
+ return getPathArea(pathToCurve(path)) >= 0;
1598
1913
  }
1599
1914
 
1600
- // calculates the shape total length
1601
- // almost equivalent to shape.getTotalLength()
1602
- function getPointAtLength(pathArray, length) {
1915
+ /**
1916
+ * Returns [x,y] coordinates of a point at a given length of a shape.
1917
+ *
1918
+ * @param {string | SVGPC.pathArray} path the `pathArray` to look into
1919
+ * @param {Number} length the length of the shape to look at
1920
+ * @returns {Number[]} the requested [x,y] coordinates
1921
+ */
1922
+ function getPointAtLength(path, length) {
1603
1923
  let totalLength = 0;
1604
1924
  let segLen;
1605
1925
  let data;
1606
1926
  let result;
1607
1927
 
1608
- return pathToCurve(pathArray).map((seg, i, curveArray) => { // process data
1928
+ return pathToCurve(path).map((seg, i, curveArray) => {
1609
1929
  data = i ? curveArray[i - 1].slice(-2).concat(seg.slice(1)) : seg.slice(1);
1610
1930
  segLen = i ? getSegCubicLength.apply(0, data) : 0;
1611
1931
  totalLength += segLen;
@@ -1622,7 +1942,19 @@ function getPointAtLength(pathArray, length) {
1622
1942
  }).filter((x) => x).slice(-1)[0]; // isolate last segment
1623
1943
  }
1624
1944
 
1625
- // returns the cubic bezier segment length
1945
+ /**
1946
+ * Returns the cubic-bezier segment length.
1947
+ *
1948
+ * @param {number} p1x the starting point X
1949
+ * @param {number} p1y the starting point Y
1950
+ * @param {number} c1x the first control point X
1951
+ * @param {number} c1y the first control point Y
1952
+ * @param {number} c2x the second control point X
1953
+ * @param {number} c2y the second control point Y
1954
+ * @param {number} p2x the ending point X
1955
+ * @param {number} p2y the ending point Y
1956
+ * @returns {SVGPC.segmentLimits} the length of the cubic-bezier segment
1957
+ */
1626
1958
  function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1627
1959
  let a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
1628
1960
  let b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
@@ -1671,13 +2003,19 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1671
2003
  };
1672
2004
  }
1673
2005
 
1674
- function getPathBBox(pathArray) {
1675
- if (!pathArray) {
2006
+ /**
2007
+ * Returns the bounding box of a shape.
2008
+ *
2009
+ * @param {SVGPC.pathArray} path the shape `pathArray`
2010
+ * @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
2011
+ */
2012
+ function getPathBBox(path) {
2013
+ if (!path) {
1676
2014
  return {
1677
2015
  x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0,
1678
2016
  };
1679
2017
  }
1680
- const pathCurve = pathToCurve(pathArray);
2018
+ const pathCurve = pathToCurve(path);
1681
2019
 
1682
2020
  let x = 0;
1683
2021
  let y = 0;
@@ -1708,28 +2046,67 @@ function getPathBBox(pathArray) {
1708
2046
  const height = yBot - yTop;
1709
2047
 
1710
2048
  return {
2049
+ width,
2050
+ height,
1711
2051
  x: xTop,
1712
2052
  y: yTop,
1713
2053
  x2: xBot,
1714
2054
  y2: yBot,
1715
- width,
1716
- height,
1717
2055
  cx: xTop + width / 2,
1718
2056
  cy: yTop + height / 2,
1719
2057
  };
1720
2058
  }
1721
2059
 
1722
- function isRelativeArray(pathInput) {
1723
- return isPathArray(pathInput)
1724
- && pathInput.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
2060
+ /**
2061
+ * Parses a path string value to determine its validity
2062
+ * then returns true if it's valid or false otherwise.
2063
+ *
2064
+ * @param {string} pathString the path string to be parsed
2065
+ * @returns {boolean} the path string validity
2066
+ */
2067
+ function isValidPath(pathString) {
2068
+ if (typeof pathString !== 'string') {
2069
+ return false;
2070
+ }
2071
+
2072
+ const path = new SVGPathArray(pathString);
2073
+
2074
+ skipSpaces(path);
2075
+
2076
+ while (path.index < path.max && !path.err.length) {
2077
+ scanSegment(path);
2078
+ }
2079
+
2080
+ return !path.err.length && 'mM'.includes(path.segments[0][0]);
1725
2081
  }
1726
2082
 
1727
- function roundPath(pathArray, round) {
1728
- const decimalsOption = !Number.isNaN(+round) ? +round : SVGPCO.round && SVGPCO.decimals;
1729
- let result;
2083
+ /**
2084
+ * Iterates an array to check if it's a `pathArray`
2085
+ * with relative values.
2086
+ *
2087
+ * @param {SVGPC.pathArray} path the `pathArray` to be checked
2088
+ * @returns {boolean} iteration result
2089
+ */
2090
+ function isRelativeArray(path) {
2091
+ return isPathArray(path)
2092
+ && path.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
2093
+ }
2094
+
2095
+ /**
2096
+ * Rounds the values of a `pathArray` instance to
2097
+ * a specified amount of decimals and returns it.
2098
+ *
2099
+ * @param {SVGPC.pathArray} path the source `pathArray`
2100
+ * @param {Number | null} round the amount of decimals to round numbers to
2101
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with rounded values
2102
+ */
2103
+ function roundPath(path, round) {
2104
+ const { round: defaultRound, decimals: defaultDecimals } = SVGPCO;
2105
+ const decimalsOption = !Number.isNaN(+round) ? +round : defaultRound && defaultDecimals;
1730
2106
 
1731
- if (decimalsOption) {
1732
- result = pathArray.map((seg) => seg.map((c) => {
2107
+ return !decimalsOption
2108
+ ? clonePath(path)
2109
+ : path.map((seg) => seg.map((c) => {
1733
2110
  const nr = +c;
1734
2111
  const dc = 10 ** decimalsOption;
1735
2112
  if (nr) {
@@ -1737,17 +2114,198 @@ function roundPath(pathArray, round) {
1737
2114
  }
1738
2115
  return c;
1739
2116
  }));
1740
- } else {
1741
- result = clonePath(pathArray);
1742
- }
1743
- return result;
1744
2117
  }
1745
2118
 
1746
- function pathToString(pathArray, round) {
1747
- return roundPath(pathArray, round)
2119
+ /**
2120
+ * Returns a valid `d` attribute string value created
2121
+ * by rounding values and concatenating the `pathArray` segments.
2122
+ *
2123
+ * @param {SVGPC.pathArray} path the `pathArray` object
2124
+ * @param {Number} round amount of decimals to round values to
2125
+ * @returns {String} the concatenated path string
2126
+ */
2127
+ function pathToString(path, round) {
2128
+ return roundPath(path, round)
1748
2129
  .map((x) => x[0].concat(x.slice(1).join(' '))).join('');
1749
2130
  }
1750
2131
 
2132
+ /**
2133
+ * Supported shapes and their specific parameters.
2134
+ *
2135
+ * @type {object}
2136
+ */
2137
+ const shapeParams = {
2138
+ circle: ['cx', 'cy', 'r'],
2139
+ ellipse: ['cx', 'cy', 'rx', 'ry'],
2140
+ rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
2141
+ polygon: ['points'],
2142
+ polyline: ['points'],
2143
+ glyph: [],
2144
+ };
2145
+
2146
+ /**
2147
+ * Returns a new `pathArray` from line attributes.
2148
+ *
2149
+ * @param {SVGPC.lineAttr} attr shape configuration
2150
+ * @return {SVGPC.pathArray} a new line `pathArray`
2151
+ */
2152
+ function getLinePath(attr) {
2153
+ const {
2154
+ x1, y1, x2, y2,
2155
+ } = attr;
2156
+ return [['M', +x1, +y1], ['L', +x2, +y2]];
2157
+ }
2158
+
2159
+ /**
2160
+ * Returns a new `pathArray` like from polyline/polygon attributes.
2161
+ *
2162
+ * @param {SVGPC.polyAttr} attr shape configuration
2163
+ * @return {SVGPC.pathArray} a new polygon/polyline `pathArray`
2164
+ */
2165
+ function getPolyPath(attr) {
2166
+ const pathArray = [];
2167
+ let { points } = attr;
2168
+
2169
+ points = points.split(/[\s|,]/).map(Number);
2170
+
2171
+ let index = 0;
2172
+ while (index < points.length) {
2173
+ pathArray.push([(index ? 'L' : 'M'), (points[index]), (points[index + 1])]);
2174
+ index += 2;
2175
+ }
2176
+
2177
+ return attr.type === 'polygon' ? pathArray.concat([['z']]) : pathArray;
2178
+ }
2179
+
2180
+ /**
2181
+ * Returns a new `pathArray` from circle/ellipse attributes.
2182
+ *
2183
+ * @param {SVGPC.ellipseAttr | SVGPC.circleAttr} attr shape configuration
2184
+ * @return {SVGPC.pathArray} a circle/ellipse `pathArray`
2185
+ */
2186
+ function getEllipsePath(attr) {
2187
+ const {
2188
+ type, cx, cy, r,
2189
+ } = attr;
2190
+ let { rx, ry } = attr;
2191
+
2192
+ if (type === 'circle' && r > 0) {
2193
+ rx = r;
2194
+ ry = r;
2195
+ }
2196
+
2197
+ return [
2198
+ ['M', (cx - rx), cy],
2199
+ ['a', rx, ry, 0, 1, 0, (2 * rx), 0],
2200
+ ['a', rx, ry, 0, 1, 0, (-2 * rx), 0],
2201
+ ];
2202
+ }
2203
+
2204
+ /**
2205
+ * Returns a new `pathArray` like from rect attributes.
2206
+ *
2207
+ * @param {SVGPC.rectAttr} attr object with properties above
2208
+ * @return {SVGPC.pathArray} a new `pathArray` from `<rect>` attributes
2209
+ */
2210
+ function getRectanglePath(attr) {
2211
+ const x = +attr.x || 0;
2212
+ const y = +attr.y || 0;
2213
+ const w = +attr.width;
2214
+ const h = +attr.height;
2215
+ let rx = +attr.rx;
2216
+ let ry = +attr.ry;
2217
+
2218
+ // Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
2219
+ if (rx || ry) {
2220
+ rx = !rx ? ry : rx;
2221
+ ry = !ry ? rx : ry;
2222
+
2223
+ if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
2224
+ if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
2225
+
2226
+ return [
2227
+ ['M', x + rx, y],
2228
+ ['h', w - rx * 2],
2229
+ ['s', rx, 0, rx, ry],
2230
+ ['v', h - ry * 2],
2231
+ ['s', 0, ry, -rx, ry],
2232
+ ['h', -w + rx * 2],
2233
+ ['s', -rx, 0, -rx, -ry],
2234
+ ['v', -h + ry * 2],
2235
+ ['s', 0, -ry, rx, -ry],
2236
+ ];
2237
+ }
2238
+
2239
+ return [
2240
+ ['M', x, y],
2241
+ ['h', w],
2242
+ ['v', h],
2243
+ ['H', x],
2244
+ ['Z'],
2245
+ ];
2246
+ }
2247
+
2248
+ /**
2249
+ * Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
2250
+ * `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
2251
+ * is `true`, it will replace the target.
2252
+ *
2253
+ * The newly created `<path>` element keeps all non-specific
2254
+ * attributes like `class`, `fill`, etc.
2255
+ *
2256
+ * @param {SVGPC.shapeTypes} element target shape
2257
+ * @param {boolean} replace option to replace target
2258
+ * @return {?SVGPathElement} the newly created `<path>` element
2259
+ */
2260
+ function shapeToPath(element, replace) {
2261
+ const supportedShapes = Object.keys(shapeParams).concat(['glyph']);
2262
+
2263
+ if (!supportedShapes.some((s) => element.tagName === s)) {
2264
+ throw TypeError(`shapeToPath: ${element} is not SVGElement`);
2265
+ }
2266
+
2267
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2268
+ const type = element.tagName;
2269
+ const shapeAttrs = shapeParams[type];
2270
+
2271
+ // set config
2272
+ const config = { type };
2273
+ shapeAttrs.forEach((p) => { config[p] = element.getAttribute(p); });
2274
+
2275
+ // set no-specific shape attributes: fill, stroke, etc
2276
+ Object.values(element.attributes).forEach(({ name, value }) => {
2277
+ if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
2278
+ });
2279
+
2280
+ // set d
2281
+ let description;
2282
+ if (['circle', 'ellipse'].includes(type)) description = pathToString(getEllipsePath(config));
2283
+ else if (['polyline', 'polygon'].includes(type)) description = pathToString(getPolyPath(config));
2284
+ else if (type === 'rect') description = pathToString(getRectanglePath(config));
2285
+ else if (type === 'line') description = pathToString(getLinePath(config));
2286
+ else if (type === 'glyph') description = element.getAttribute('d');
2287
+
2288
+ // replace target element
2289
+ if (description) {
2290
+ path.setAttribute('d', description);
2291
+ if (replace) {
2292
+ element.before(path, element);
2293
+ element.remove();
2294
+ }
2295
+ return path;
2296
+ }
2297
+ return null;
2298
+ }
2299
+
2300
+ /**
2301
+ * Split a path into an `Array` of sub-path strings.
2302
+ *
2303
+ * In the process, values are converted to absolute
2304
+ * for visual consistency.
2305
+ *
2306
+ * @param {Object | String} pathInput the cubic-bezier parameters
2307
+ * @return {Object} an array with all sub-path strings
2308
+ */
1751
2309
  function splitPath(pathInput) {
1752
2310
  return pathToString(pathToAbsolute(pathInput))
1753
2311
  .replace(/(m|M)/g, '|$1')
@@ -1756,13 +2314,20 @@ function splitPath(pathInput) {
1756
2314
  .filter((s) => s);
1757
2315
  }
1758
2316
 
2317
+ /**
2318
+ * Parses a path string value or object and returns an array
2319
+ * of segments, all converted to relative values.
2320
+ *
2321
+ * @param {string | SVGPC.pathArray} pathInput the path string | object
2322
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with relative values
2323
+ */
1759
2324
  function pathToRelative(pathInput) {
1760
2325
  if (isRelativeArray(pathInput)) {
1761
2326
  return clonePath(pathInput);
1762
2327
  }
1763
2328
 
1764
- const pathArray = parsePathString(pathInput);
1765
- const ii = pathArray.length;
2329
+ const path = parsePathString(pathInput);
2330
+ const ii = path.length;
1766
2331
  const resultArray = [];
1767
2332
  let x = 0;
1768
2333
  let y = 0;
@@ -1770,9 +2335,9 @@ function pathToRelative(pathInput) {
1770
2335
  let my = 0;
1771
2336
  let start = 0;
1772
2337
 
1773
- if (pathArray[0][0] === 'M') {
1774
- x = +pathArray[0][1];
1775
- y = +pathArray[0][2];
2338
+ if (path[0][0] === 'M') {
2339
+ x = +path[0][1];
2340
+ y = +path[0][2];
1776
2341
  mx = x;
1777
2342
  my = y;
1778
2343
  start += 1;
@@ -1780,7 +2345,7 @@ function pathToRelative(pathInput) {
1780
2345
  }
1781
2346
 
1782
2347
  for (let i = start; i < ii; i += 1) {
1783
- const segment = pathArray[i];
2348
+ const segment = path[i];
1784
2349
  const [pathCommand] = segment;
1785
2350
  const relativeCommand = pathCommand.toLowerCase();
1786
2351
  const relativeSegment = [];
@@ -1842,9 +2407,19 @@ function pathToRelative(pathInput) {
1842
2407
  return resultArray;
1843
2408
  }
1844
2409
 
1845
- function optimizePath(pathArray, round) {
1846
- const absolutePath = roundPath(pathToAbsolute(pathArray), round);
1847
- const relativePath = roundPath(pathToRelative(pathArray), round);
2410
+ /**
2411
+ * Optimizes a `pathArray` object:
2412
+ * * convert segments to absolute and relative values
2413
+ * * create a new `pathArray` with elements with shortest segments
2414
+ * from absolute and relative `pathArray`s
2415
+ *
2416
+ * @param {string | SVGPC.pathArray} pathInput a string or `pathArray`
2417
+ * @param {number} round the amount of decimals to round values to
2418
+ * @returns {SVGPC.pathArray} the optimized `pathArray`
2419
+ */
2420
+ function optimizePath(pathInput, round) {
2421
+ const absolutePath = roundPath(pathToAbsolute(pathInput), round);
2422
+ const relativePath = roundPath(pathToRelative(pathInput), round);
1848
2423
  return absolutePath.map((x, i) => {
1849
2424
  if (i) {
1850
2425
  return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
@@ -1853,13 +2428,19 @@ function optimizePath(pathArray, round) {
1853
2428
  });
1854
2429
  }
1855
2430
 
1856
- // reverse CURVE based pathArray segments only
1857
- function reverseCurve(pathArray) {
1858
- const rotatedCurve = pathArray.slice(1)
2431
+ /**
2432
+ * Reverses all segments and their values from a `pathArray`
2433
+ * which consists of only C (cubic-bezier) path commands.
2434
+ *
2435
+ * @param {SVGPC.pathArray} path the source `pathArray`
2436
+ * @returns {SVGPC.pathArray} the reversed `pathArray`
2437
+ */
2438
+ function reverseCurve(path) {
2439
+ const rotatedCurve = path.slice(1)
1859
2440
  .map((x, i, curveOnly) => (!i
1860
- ? pathArray[0].slice(1).concat(x.slice(1))
2441
+ ? path[0].slice(1).concat(x.slice(1))
1861
2442
  : curveOnly[i - 1].slice(-2).concat(x.slice(1))))
1862
- .map((x) => x.map((y, i) => x[x.length - i - 2 * (1 - (i % 2))]))
2443
+ .map((x) => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
1863
2444
  .reverse();
1864
2445
 
1865
2446
  return [['M'].concat(rotatedCurve[0]
@@ -1867,8 +2448,15 @@ function reverseCurve(pathArray) {
1867
2448
  .concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
1868
2449
  }
1869
2450
 
1870
- function reversePath(pathString) { // pathArray | pathString
1871
- const absolutePath = pathToAbsolute(pathString);
2451
+ /**
2452
+ * Reverses all segments and their values of a `pathArray`
2453
+ * and returns a new instance.
2454
+ *
2455
+ * @param {SVGPC.pathArray} pathInput the source `pathArray`
2456
+ * @returns {SVGPC.pathArray} the reversed `pathArray`
2457
+ */
2458
+ function reversePath(pathInput) {
2459
+ const absolutePath = pathToAbsolute(pathInput);
1872
2460
  const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
1873
2461
  let reversedPath = [];
1874
2462
  let segLength = 0;
@@ -1882,15 +2470,15 @@ function reversePath(pathString) { // pathArray | pathString
1882
2470
  x: segment[segLength - 2], // x
1883
2471
  y: segment[segLength - 1], // y
1884
2472
  };
1885
- }).map((seg, i, pathArray) => {
2473
+ }).map((seg, i, path) => {
1886
2474
  const segment = seg.seg;
1887
2475
  const data = seg.n;
1888
- const prevSeg = i && pathArray[i - 1];
1889
- const nextSeg = pathArray[i + 1] && pathArray[i + 1];
2476
+ const prevSeg = i && path[i - 1];
2477
+ const nextSeg = path[i + 1] && path[i + 1];
1890
2478
  const pathCommand = seg.c;
1891
- const pLen = pathArray.length;
1892
- const x = i ? pathArray[i - 1].x : pathArray[pLen - 1].x;
1893
- const y = i ? pathArray[i - 1].y : pathArray[pLen - 1].y;
2479
+ const pLen = path.length;
2480
+ const x = i ? path[i - 1].x : path[pLen - 1].x;
2481
+ const y = i ? path[i - 1].y : path[pLen - 1].y;
1894
2482
  let result = [];
1895
2483
 
1896
2484
  switch (pathCommand) {
@@ -1948,21 +2536,28 @@ function reversePath(pathString) { // pathArray | pathString
1948
2536
  : [reversedPath[0]].concat(reversedPath.slice(1).reverse());
1949
2537
  }
1950
2538
 
1951
- var epsilon = 1e-9;
2539
+ /**
2540
+ * A global namespace for epsilon.
2541
+ *
2542
+ * @type {Number}
2543
+ */
2544
+ const epsilon = 1e-9;
1952
2545
 
1953
- function getSVGMatrix(transformObject) {
1954
- let matrix = new CSS3Matrix();
1955
- const { origin } = transformObject;
2546
+ /**
2547
+ * Returns a transformation matrix to apply to `<path>` elements.
2548
+ *
2549
+ * @param {SVGPC.transformObject} transform the `transformObject`
2550
+ * @returns {CSSMatrix} a new transformation matrix
2551
+ */
2552
+ function getSVGMatrix(transform) {
2553
+ let matrix = new CSSMatrix();
2554
+ const { origin } = transform;
1956
2555
  const originX = +origin[0];
1957
2556
  const originY = +origin[1];
1958
- // originZ = +origin[2] || originX, // maybe later. maybe not required
1959
- // perspective = transformObject.perspective,
1960
- const { translate } = transformObject;
1961
- const { rotate } = transformObject;
1962
- const { skew } = transformObject;
1963
- const { scale } = transformObject;
1964
-
1965
- // !isNaN(perspective) && perspective && (matrix.m34 = -1/perspective)
2557
+ const { translate } = transform;
2558
+ const { rotate } = transform;
2559
+ const { skew } = transform;
2560
+ const { scale } = transform;
1966
2561
 
1967
2562
  // set translate
1968
2563
  if (!Number.isNaN(translate) || (Array.isArray(translate) && translate.some((x) => +x !== 0))) {
@@ -2004,6 +2599,14 @@ function getSVGMatrix(transformObject) {
2004
2599
  return matrix;
2005
2600
  }
2006
2601
 
2602
+ /**
2603
+ * Apply a 2D transformation matrix to an ellipse.
2604
+ *
2605
+ * @param {number[]} m the 2D transformation matrix
2606
+ * @param {number} rx ellipse radius X
2607
+ * @param {number} ry ellipse radius Y
2608
+ * @param {number} ax ellipse rotation angle
2609
+ */
2007
2610
  function transformEllipse(m, rx, ry, ax) {
2008
2611
  // We consider the current ellipse as image of the unit circle
2009
2612
  // by first scale(rx,ry) and then rotate(ax) ...
@@ -2068,11 +2671,19 @@ function transformEllipse(m, rx, ry, ax) {
2068
2671
  return { rx: RX, ry: RY, ax: AX };
2069
2672
  }
2070
2673
 
2071
- // Given an xyz point and an xyz perspective origin point,
2072
- // this will return the xy projected location
2073
- // Using the equation found here: http://en.wikipedia.org/wiki/3D_projection#Diagram
2074
- // https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
2075
-
2674
+ /**
2675
+ * Returns the [x,y] projected coordinates for a given an [x,y] point
2676
+ * and an [x,y,z] perspective origin point.
2677
+ *
2678
+ * Equation found here =>
2679
+ * http://en.wikipedia.org/wiki/3D_projection#Diagram
2680
+ * Details =>
2681
+ * https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
2682
+ *
2683
+ * @param {number[]} m the transformation matrix
2684
+ * @param {Number[]} point2D the initial [x,y] coordinates
2685
+ * @returns {Number[]} the projected [x,y] coordinates
2686
+ */
2076
2687
  function projection2d(m, point2D, origin) {
2077
2688
  const point3D = m.transformPoint({
2078
2689
  x: point2D[0], y: point2D[1], z: 0, w: 1,
@@ -2090,13 +2701,23 @@ function projection2d(m, point2D, origin) {
2090
2701
  ];
2091
2702
  }
2092
2703
 
2093
- function transformPath(pathArray, transformObject) {
2704
+ /**
2705
+ * Apply a 2D / 3D transformation to a `pathArray` instance.
2706
+ *
2707
+ * Since *SVGElement* doesn't support 3D transformation, this function
2708
+ * creates a 2D projection of the <path> element.
2709
+ *
2710
+ * @param {SVGPC.pathArray} path the `pathArray` to apply transformation
2711
+ * @param {SVGPC.transformObject} transform the transform functions `Object`
2712
+ * @returns {SVGPC.pathArray} the resulted `pathArray`
2713
+ */
2714
+ function transformPath(path, transform) {
2094
2715
  let x; let y; let i; let j; let ii; let jj; let lx; let ly; let te;
2095
- const absolutePath = pathToAbsolute(pathArray);
2716
+ const absolutePath = pathToAbsolute(path);
2096
2717
  const normalizedPath = normalizePath(absolutePath);
2097
- const matrixInstance = getSVGMatrix(transformObject);
2098
- const transformProps = Object.keys(transformObject);
2099
- const { origin } = transformObject;
2718
+ const matrixInstance = getSVGMatrix(transform);
2719
+ const transformProps = Object.keys(transform);
2720
+ const { origin } = transform;
2100
2721
  const {
2101
2722
  a, b, c, d, e, f,
2102
2723
  } = matrixInstance;
@@ -2206,14 +2827,25 @@ function transformPath(pathArray, transformObject) {
2206
2827
  return clonePath(absolutePath);
2207
2828
  }
2208
2829
 
2830
+ var version = "0.1.9";
2831
+
2832
+ // @ts-ignore
2833
+
2834
+ /**
2835
+ * A global namespace for library version.
2836
+ * @type {string}
2837
+ */
2838
+ const Version = version;
2839
+
2209
2840
  const Util = {
2210
- CSSMatrix: CSS3Matrix,
2841
+ CSSMatrix,
2211
2842
  parsePathString,
2212
2843
  isPathArray,
2213
2844
  isCurveArray,
2214
2845
  isAbsoluteArray,
2215
2846
  isRelativeArray,
2216
2847
  isNormalizedArray,
2848
+ isValidPath,
2217
2849
  pathToAbsolute,
2218
2850
  pathToRelative,
2219
2851
  pathToCurve,
@@ -2232,23 +2864,24 @@ const Util = {
2232
2864
  normalizePath,
2233
2865
  transformPath,
2234
2866
  getSVGMatrix,
2867
+ shapeToPath,
2235
2868
  options: SVGPCO,
2869
+ Version,
2236
2870
  };
2237
2871
 
2238
2872
  /**
2239
2873
  * Creates a new SVGPathCommander instance.
2240
- * @class
2874
+ *
2875
+ * @author thednp <https://github.com/thednp/svg-path-commander>
2241
2876
  */
2242
2877
  class SVGPathCommander {
2243
2878
  /**
2244
- * @constructor
2245
- * @param {String} pathValue the path string
2246
- * @param {Object} config instance options
2879
+ * @param {string} pathValue the path string
2880
+ * @param {object} config instance options
2247
2881
  */
2248
2882
  constructor(pathValue, config) {
2249
2883
  const options = config || {};
2250
- // check for either true or > 0
2251
- // const roundOption = +options.round === 0 || options.round === false ? 0 : SVGPCO.round;
2884
+
2252
2885
  let { round } = SVGPCO;
2253
2886
  const { round: roundOption } = options;
2254
2887
  if (+roundOption === 0 || roundOption === false) {
@@ -2256,33 +2889,16 @@ class SVGPathCommander {
2256
2889
  }
2257
2890
 
2258
2891
  const { decimals } = round && (options || SVGPCO);
2259
- const { origin } = options;
2260
2892
 
2261
2893
  // set instance options
2262
- /**
2263
- * @type {Boolean | Number}
2264
- */
2894
+ /** @type {number | boolean | undefined} */
2265
2895
  this.round = round === 0 ? 0 : decimals;
2266
2896
  // ZERO | FALSE will disable rounding numbers
2267
2897
 
2268
- if (origin) {
2269
- const { x, y } = origin;
2270
- if ([x, y].every((n) => !Number.isNaN(n))) {
2271
- /**
2272
- * @type {Object | null}
2273
- */
2274
- this.origin = origin;
2275
- }
2276
- }
2277
-
2278
- /**
2279
- * @type {Object}
2280
- */
2898
+ /** @type {SVGPC.pathArray} */
2281
2899
  this.segments = parsePathString(pathValue);
2282
2900
 
2283
- /**
2284
- * @type {String}
2285
- */
2901
+ /** * @type {string} */
2286
2902
  this.pathValue = pathValue;
2287
2903
 
2288
2904
  return this;
@@ -2309,8 +2925,8 @@ class SVGPathCommander {
2309
2925
  }
2310
2926
 
2311
2927
  /**
2312
- * Reverse path
2313
- * @param {Boolean | Number} onlySubpath option to reverse all pathArray(s) except first
2928
+ * Reverse the order of the segments and their values.
2929
+ * @param {boolean | number} onlySubpath option to reverse all sub-paths except first
2314
2930
  * @public
2315
2931
  */
2316
2932
  reverse(onlySubpath) {
@@ -2339,7 +2955,7 @@ class SVGPathCommander {
2339
2955
 
2340
2956
  /**
2341
2957
  * Normalize path in 2 steps:
2342
- * * convert pathArray(s) to absolute values
2958
+ * * convert `pathArray`(s) to absolute values
2343
2959
  * * convert shorthand notation to standard notation
2344
2960
  * @public
2345
2961
  */
@@ -2350,7 +2966,7 @@ class SVGPathCommander {
2350
2966
  }
2351
2967
 
2352
2968
  /**
2353
- * Optimize pathArray values:
2969
+ * Optimize `pathArray` values:
2354
2970
  * * convert segments to absolute and/or relative values
2355
2971
  * * select segments with shortest resulted string
2356
2972
  * * round values to the specified `decimals` option value
@@ -2364,31 +2980,27 @@ class SVGPathCommander {
2364
2980
  }
2365
2981
 
2366
2982
  /**
2367
- * Transform path using values from an `Object`
2368
- * with the following structure:
2983
+ * Transform path using values from an `Object` defined as `transformObject`.
2984
+ * @see SVGPC.transformObject for a quick refference
2369
2985
  *
2370
- * {
2371
- * origin: {x, y, z},
2372
- * translate: {x, y, z},
2373
- * rotate: {x, y, z},
2374
- * skew: {x, y, z},
2375
- * scale: {x, y, z}
2376
- * }
2377
- * @param {Object} source a transform `Object`as described above
2986
+ * @param {SVGPC.transformObject} source a `transformObject`as described above
2378
2987
  * @public
2379
2988
  */
2380
2989
  transform(source) {
2381
- const transformObject = source || {};
2990
+ if (!source || typeof source !== 'object' || (typeof source === 'object'
2991
+ && !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
2992
+
2993
+ const transform = source || {};
2382
2994
  const { segments } = this;
2383
2995
 
2384
2996
  // if origin is not specified
2385
2997
  // it's important that we have one
2386
- if (!transformObject.origin) {
2998
+ if (!transform.origin) {
2387
2999
  const BBox = getPathBBox(segments);
2388
- transformObject.origin = [BBox.cx, BBox.cy, BBox.cx];
3000
+ transform.origin = [BBox.cx, BBox.cy, BBox.cx];
2389
3001
  }
2390
3002
 
2391
- this.segments = transformPath(segments, transformObject);
3003
+ this.segments = transformPath(segments, transform);
2392
3004
  return this;
2393
3005
  }
2394
3006