svg-path-commander 0.1.8 → 0.1.10-alpha3

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 (153) hide show
  1. package/dist/svg-path-commander.esm.js +2538 -1861
  2. package/dist/svg-path-commander.esm.min.js +2 -2
  3. package/dist/svg-path-commander.js +2564 -1880
  4. package/dist/svg-path-commander.min.js +2 -2
  5. package/package.json +4 -4
  6. package/src/convert/pathToAbsolute.js +101 -92
  7. package/src/convert/pathToCurve.js +45 -41
  8. package/src/convert/pathToRelative.js +98 -89
  9. package/src/convert/pathToString.js +14 -6
  10. package/src/index.js +3 -6
  11. package/src/math/distanceSquareRoot.js +14 -6
  12. package/src/math/epsilon.js +8 -1
  13. package/src/math/midPoint.js +13 -7
  14. package/src/math/polygonArea.js +25 -18
  15. package/src/math/polygonLength.js +19 -13
  16. package/src/math/rotateVector.js +14 -0
  17. package/src/options/options.js +11 -7
  18. package/src/parser/finalizeSegment.js +33 -0
  19. package/src/{util → parser}/invalidPathValue.js +2 -2
  20. package/src/parser/isArcCommand.js +10 -0
  21. package/src/parser/isDigit.js +9 -0
  22. package/src/{util → parser}/isDigitStart.js +13 -6
  23. package/src/{util → parser}/isPathCommand.js +25 -19
  24. package/src/{util → parser}/isSpace.js +15 -9
  25. package/src/parser/paramsCount.js +8 -0
  26. package/src/parser/parsePathString.js +41 -0
  27. package/src/parser/scanFlag.js +26 -0
  28. package/src/parser/scanParam.js +97 -0
  29. package/src/parser/scanSegment.js +70 -0
  30. package/src/parser/skipSpaces.js +15 -0
  31. package/src/parser/svgPathArray.js +23 -0
  32. package/src/process/arcToCubic.js +114 -111
  33. package/src/process/clonePath.js +15 -8
  34. package/src/process/fixArc.js +23 -0
  35. package/src/{util → process}/getSVGMatrix.js +62 -55
  36. package/src/process/lineToCubic.js +28 -17
  37. package/src/process/normalizePath.js +49 -41
  38. package/src/process/normalizeSegment.js +43 -35
  39. package/src/process/optimizePath.js +24 -14
  40. package/src/{util → process}/projection2d.js +30 -21
  41. package/src/process/quadToCubic.js +22 -11
  42. package/src/process/reverseCurve.js +20 -13
  43. package/src/process/reversePath.js +90 -83
  44. package/src/process/roundPath.js +27 -21
  45. package/src/process/segmentToCubic.js +42 -31
  46. package/src/process/shorthandToCubic.js +16 -6
  47. package/src/process/shorthandToQuad.js +16 -6
  48. package/src/process/splitCubic.js +26 -20
  49. package/src/process/splitPath.js +19 -10
  50. package/src/{util → process}/transformEllipse.js +73 -65
  51. package/src/process/transformPath.js +138 -125
  52. package/src/{class/SVGPathCommander.js → svg-path-commander.js} +193 -201
  53. package/src/util/createPath.js +17 -8
  54. package/src/util/getCubicSize.js +64 -50
  55. package/src/util/getDrawDirection.js +13 -6
  56. package/src/util/getPathArea.js +54 -30
  57. package/src/util/getPathBBox.js +60 -50
  58. package/src/util/getPathLength.js +20 -13
  59. package/src/util/getPointAtLength.js +35 -28
  60. package/src/util/getPointAtSegLength.js +28 -14
  61. package/src/util/getSegArcLength.js +24 -21
  62. package/src/util/getSegCubicLength.js +52 -31
  63. package/src/util/getSegLineLength.js +14 -6
  64. package/src/util/getSegQuadLength.js +31 -19
  65. package/src/util/isAbsoluteArray.js +13 -5
  66. package/src/util/isCurveArray.js +13 -5
  67. package/src/util/isNormalizedArray.js +17 -8
  68. package/src/util/isPathArray.js +14 -8
  69. package/src/util/isRelativeArray.js +13 -6
  70. package/src/util/isValidPath.js +26 -0
  71. package/src/util/shapeToPath.js +187 -0
  72. package/src/util/util.js +65 -60
  73. package/src/util/version.js +9 -0
  74. package/types/index.d.ts +841 -2
  75. package/types/types.d.ts +83 -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/paramsCount.js +0 -3
  87. package/src/util/rotateVector.js +0 -5
  88. package/src/util/svgPathArray.js +0 -10
  89. package/types/class/SVGPathCommander.d.ts +0 -91
  90. package/types/convert/pathToAbsolute.d.ts +0 -1
  91. package/types/convert/pathToCurve.d.ts +0 -1
  92. package/types/convert/pathToRelative.d.ts +0 -1
  93. package/types/convert/pathToString.d.ts +0 -1
  94. package/types/math/distanceSquareRoot.d.ts +0 -1
  95. package/types/math/epsilon.d.ts +0 -2
  96. package/types/math/midPoint.d.ts +0 -1
  97. package/types/math/polygonArea.d.ts +0 -1
  98. package/types/math/polygonLength.d.ts +0 -1
  99. package/types/options/options.d.ts +0 -6
  100. package/types/process/arcToCubic.d.ts +0 -1
  101. package/types/process/clonePath.d.ts +0 -1
  102. package/types/process/finalizeSegment.d.ts +0 -1
  103. package/types/process/lineToCubic.d.ts +0 -1
  104. package/types/process/normalizePath.d.ts +0 -1
  105. package/types/process/normalizeSegment.d.ts +0 -1
  106. package/types/process/optimizePath.d.ts +0 -1
  107. package/types/process/parsePathString.d.ts +0 -1
  108. package/types/process/quadToCubic.d.ts +0 -1
  109. package/types/process/reverseCurve.d.ts +0 -1
  110. package/types/process/reversePath.d.ts +0 -1
  111. package/types/process/roundPath.d.ts +0 -1
  112. package/types/process/scanFlag.d.ts +0 -1
  113. package/types/process/scanParam.d.ts +0 -1
  114. package/types/process/scanSegment.d.ts +0 -1
  115. package/types/process/segmentToCubic.d.ts +0 -1
  116. package/types/process/shorthandToCubic.d.ts +0 -4
  117. package/types/process/shorthandToQuad.d.ts +0 -4
  118. package/types/process/skipSpaces.d.ts +0 -1
  119. package/types/process/splitCubic.d.ts +0 -1
  120. package/types/process/splitPath.d.ts +0 -1
  121. package/types/process/transformPath.d.ts +0 -1
  122. package/types/util/DOMMatrix.d.ts +0 -2
  123. package/types/util/createPath.d.ts +0 -1
  124. package/types/util/fixArc.d.ts +0 -1
  125. package/types/util/getCubicSize.d.ts +0 -10
  126. package/types/util/getDrawDirection.d.ts +0 -1
  127. package/types/util/getPathArea.d.ts +0 -1
  128. package/types/util/getPathBBox.d.ts +0 -19
  129. package/types/util/getPathLength.d.ts +0 -1
  130. package/types/util/getPointAtLength.d.ts +0 -1
  131. package/types/util/getPointAtSegLength.d.ts +0 -4
  132. package/types/util/getSVGMatrix.d.ts +0 -1
  133. package/types/util/getSegArcLength.d.ts +0 -1
  134. package/types/util/getSegCubicLength.d.ts +0 -1
  135. package/types/util/getSegLineLength.d.ts +0 -1
  136. package/types/util/getSegQuadLength.d.ts +0 -1
  137. package/types/util/invalidPathValue.d.ts +0 -2
  138. package/types/util/isAbsoluteArray.d.ts +0 -1
  139. package/types/util/isArcCommand.d.ts +0 -1
  140. package/types/util/isCurveArray.d.ts +0 -1
  141. package/types/util/isDigit.d.ts +0 -1
  142. package/types/util/isDigitStart.d.ts +0 -1
  143. package/types/util/isNormalizedArray.d.ts +0 -1
  144. package/types/util/isPathArray.d.ts +0 -1
  145. package/types/util/isPathCommand.d.ts +0 -1
  146. package/types/util/isRelativeArray.d.ts +0 -1
  147. package/types/util/isSpace.d.ts +0 -1
  148. package/types/util/paramsCount.d.ts +0 -14
  149. package/types/util/projection2d.d.ts +0 -1
  150. package/types/util/rotateVector.d.ts +0 -4
  151. package/types/util/svgPathArray.d.ts +0 -12
  152. package/types/util/transformEllipse.d.ts +0 -5
  153. package/types/util/util.d.ts +0 -55
@@ -1,1628 +1,2331 @@
1
1
  /*!
2
- * SVGPathCommander v0.1.8 (http://thednp.github.io/svg-path-commander)
2
+ * SVGPathCommander v0.1.10alpha3 (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
+ /**
7
+ * SVGPathCommander default options
8
+ *
9
+ */
6
10
  const SVGPCO = {
7
- origin: null,
11
+ origin: [0, 0],
8
12
  decimals: 4,
9
13
  round: 1,
10
14
  };
11
15
 
12
- // DOMMatrix Static methods
13
- // * `fromFloat64Array` and `fromFloat32Array` methods are not supported;
14
- // * `fromArray` a more simple implementation, should also accept float[32/64]Array;
15
- // * `fromMatrix` load values from another CSSMatrix/DOMMatrix instance;
16
- // * `fromString` parses and loads values from any valid CSS transform string.
16
+ /**
17
+ * @type {Object.<string, number>}
18
+ */
19
+ const paramsCount = {
20
+ a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
21
+ };
17
22
 
18
23
  /**
19
- * Creates a new mutable `CSSMatrix` object given an array float values.
24
+ * Breaks the parsing of a pathString once a segment is finalized.
20
25
  *
21
- * If the array has six values, the result is a 2D matrix; if the array has 16 values,
22
- * the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
23
- *
24
- * @param {Number[]} array an `Array` to feed values from.
25
- * @return {CSSMatrix} the resulted matrix.
26
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
26
27
  */
27
- function fromArray(array) {
28
- const m = new CSSMatrix();
29
- const a = Array.from(array);
28
+ function finalizeSegment(path) {
29
+ let pathCommand = path.pathValue[path.segmentStart];
30
+ let pathComLK = pathCommand.toLowerCase();
31
+ let params = path.data;
30
32
 
31
- if (a.length === 16) {
32
- const [m11, m12, m13, m14,
33
- m21, m22, m23, m24,
34
- m31, m32, m33, m34,
35
- m41, m42, m43, m44] = a;
33
+ // Process duplicated commands (without comand name)
34
+ if (pathComLK === 'm' && params.length > 2) {
35
+ path.segments.push([pathCommand, params[0], params[1]]);
36
+ params = params.slice(2);
37
+ pathComLK = 'l';
38
+ pathCommand = (pathCommand === 'm') ? 'l' : 'L';
39
+ }
36
40
 
37
- m.m11 = m11;
38
- m.a = m11;
41
+ if (pathComLK === 'r') {
42
+ // @ts-ignore
43
+ path.segments.push([pathCommand].concat(params));
44
+ } else {
45
+ while (params.length >= paramsCount[pathComLK]) {
46
+ // @ts-ignore
47
+ path.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
48
+ if (!paramsCount[pathComLK]) {
49
+ break;
50
+ }
51
+ }
52
+ }
53
+ }
39
54
 
40
- m.m21 = m21;
41
- m.c = m21;
55
+ const invalidPathValue = 'Invalid path value';
42
56
 
43
- m.m31 = m31;
57
+ /**
58
+ * Validates an A (arc-to) specific path command value.
59
+ * Usually a `large-arc-flag` or `sweep-flag`.
60
+ *
61
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
62
+ */
63
+ function scanFlag(path) {
64
+ const { index } = path;
65
+ const ch = path.pathValue.charCodeAt(index);
44
66
 
45
- m.m41 = m41;
46
- m.e = m41;
67
+ if (ch === 0x30/* 0 */) {
68
+ path.param = 0;
69
+ path.index += 1;
70
+ return;
71
+ }
47
72
 
48
- m.m12 = m12;
49
- m.b = m12;
73
+ if (ch === 0x31/* 1 */) {
74
+ path.param = 1;
75
+ path.index += 1;
76
+ return;
77
+ }
50
78
 
51
- m.m22 = m22;
52
- m.d = m22;
79
+ path.err = `${invalidPathValue}: invalid Arc flag ${ch}, expecting 0 or 1 at index ${index}`;
80
+ }
53
81
 
54
- m.m32 = m32;
82
+ /**
83
+ * Checks if a character is a digit.
84
+ *
85
+ * @param {number} code the character to check
86
+ * @returns {boolean} check result
87
+ */
88
+ function isDigit(code) {
89
+ return (code >= 48 && code <= 57); // 0..9
90
+ }
55
91
 
56
- m.m42 = m42;
57
- m.f = m42;
92
+ /**
93
+ * Validates every character of the path string,
94
+ * every path command, negative numbers or floating point numbers.
95
+ *
96
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
97
+ */
98
+ function scanParam(path) {
99
+ const { max, pathValue, index: start } = path;
100
+ let index = start;
101
+ let zeroFirst = false;
102
+ let hasCeiling = false;
103
+ let hasDecimal = false;
104
+ let hasDot = false;
105
+ let ch;
58
106
 
59
- m.m13 = m13;
60
- m.m23 = m23;
61
- m.m33 = m33;
62
- m.m43 = m43;
63
- m.m14 = m14;
64
- m.m24 = m24;
65
- m.m34 = m34;
66
- m.m44 = m44;
67
- } else if (a.length === 6) {
68
- const [m11, m12, m21, m22, m41, m42] = a;
107
+ if (index >= max) {
108
+ // path.err = 'SvgPath: missed param (at pos ' + index + ')';
109
+ path.err = `${invalidPathValue} at ${index}: missing param ${pathValue[index]}`;
110
+ return;
111
+ }
112
+ ch = pathValue.charCodeAt(index);
69
113
 
70
- m.m11 = m11;
71
- m.a = m11;
114
+ if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
115
+ index += 1;
116
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
117
+ }
72
118
 
73
- m.m12 = m12;
74
- m.b = m12;
119
+ // This logic is shamelessly borrowed from Esprima
120
+ // https://github.com/ariya/esprimas
121
+ if (!isDigit(ch) && ch !== 0x2E/* . */) {
122
+ // path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
123
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} is not a number`;
124
+ return;
125
+ }
75
126
 
76
- m.m21 = m21;
77
- m.c = m21;
127
+ if (ch !== 0x2E/* . */) {
128
+ zeroFirst = (ch === 0x30/* 0 */);
129
+ index += 1;
78
130
 
79
- m.m22 = m22;
80
- m.d = m22;
131
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
81
132
 
82
- m.m41 = m41;
83
- m.e = m41;
133
+ if (zeroFirst && index < max) {
134
+ // decimal number starts with '0' such as '09' is illegal.
135
+ if (ch && isDigit(ch)) {
136
+ // path.err = 'SvgPath: numbers started with `0` such as `09`
137
+ // are illegal (at pos ' + start + ')';
138
+ path.err = `${invalidPathValue} at index ${start}: ${pathValue[start]} illegal number`;
139
+ return;
140
+ }
141
+ }
84
142
 
85
- m.m42 = m42;
86
- m.f = m42;
87
- } else {
88
- throw new TypeError('CSSMatrix: expecting a 6/16 values Array');
143
+ while (index < max && isDigit(pathValue.charCodeAt(index))) {
144
+ index += 1;
145
+ hasCeiling = true;
146
+ }
147
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
89
148
  }
90
- return m;
91
- }
92
-
93
- /**
94
- * Creates a new mutable `CSSMatrix` object given an existing matrix or a
95
- * `DOMMatrix` *Object* which provides the values for its properties.
96
- *
97
- * @param {CSSMatrix | DOMMatrix} m the source matrix to feed values from.
98
- * @return {CSSMatrix} the resulted matrix.
99
- */
100
- function fromMatrix(m) {
101
- return fromArray(
102
- [m.m11, m.m12, m.m13, m.m14,
103
- m.m21, m.m22, m.m23, m.m24,
104
- m.m31, m.m32, m.m33, m.m34,
105
- m.m41, m.m42, m.m43, m.m44],
106
- );
107
- }
108
149
 
109
- /**
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)
114
- *
115
- * @param {string} source valid CSS transform string syntax.
116
- * @return {CSSMatrix} the resulted matrix.
117
- */
118
- function fromString(source) {
119
- const str = String(source).replace(/\s/g, '');
120
- let m = new CSSMatrix();
121
- let is2D = true;
122
- const tramsformObject = str.split(')').filter((f) => f).map((fn) => {
123
- const [prop, value] = fn.split('(');
124
- const components = value.split(',')
125
- .map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
126
- const [x, y, z, a] = components;
150
+ if (ch === 0x2E/* . */) {
151
+ hasDot = true;
152
+ index += 1;
153
+ while (isDigit(pathValue.charCodeAt(index))) {
154
+ index += 1;
155
+ hasDecimal = true;
156
+ }
157
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
158
+ }
127
159
 
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;
160
+ if (ch === 0x65/* e */ || ch === 0x45/* E */) {
161
+ if (hasDot && !hasCeiling && !hasDecimal) {
162
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
163
+ return;
136
164
  }
137
- return { prop, components };
138
- });
139
165
 
140
- tramsformObject.forEach((tf) => {
141
- const { prop, components } = tf;
142
- const [x, y, z, a] = components;
143
- const xyz = [x, y, z];
144
- const xyza = [x, y, z, a];
166
+ index += 1;
145
167
 
146
- if (prop === 'perspective' && !is2D) {
147
- m.m34 = -1 / x;
148
- } else if (prop.includes('matrix')) {
149
- const values = components.map((n) => (Math.abs(n) < 1e-6 ? 0 : n));
150
- if ([6, 16].includes(values.length)) {
151
- m = m.multiply(fromArray(values));
152
- }
153
- } else if (['translate', 'translate3d'].some((p) => prop === p) && x) {
154
- m = m.translate(x, y || 0, z || 0);
155
- } else if (prop === 'rotate3d' && xyza.every((n) => !Number.isNaN(+n)) && a) {
156
- m = m.rotateAxisAngle(x, y, z, a);
157
- } else if (prop === 'scale3d' && xyz.every((n) => !Number.isNaN(+n)) && xyz.some((n) => n !== 1)) {
158
- m = m.scale(x, y, z);
159
- } else if (prop === 'rotate' && x) {
160
- m = m.rotate(0, 0, x);
161
- } else if (prop === 'scale' && !Number.isNaN(x) && x !== 1) {
162
- const nosy = Number.isNaN(+y);
163
- const sy = nosy ? x : y;
164
- m = m.scale(x, sy, 1);
165
- } else if (prop === 'skew' && (x || y)) {
166
- m = x ? m.skewX(x) : m;
167
- m = y ? m.skewY(y) : m;
168
- } else if (/[XYZ]/.test(prop) && x) {
169
- if (prop.includes('skew')) {
170
- // @ts-ignore unfortunately
171
- m = m[prop](x);
172
- } else {
173
- const fn = prop.replace(/[XYZ]/, '');
174
- const axis = prop.replace(fn, '');
175
- const idx = ['X', 'Y', 'Z'].indexOf(axis);
176
- const axeValues = [
177
- idx === 0 ? x : 0,
178
- idx === 1 ? x : 0,
179
- idx === 2 ? x : 0];
180
- // @ts-ignore unfortunately
181
- m = m[fn](...axeValues);
168
+ ch = (index < max) ? pathValue.charCodeAt(index) : 0;
169
+ if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
170
+ index += 1;
171
+ }
172
+ if (index < max && isDigit(pathValue.charCodeAt(index))) {
173
+ while (index < max && isDigit(pathValue.charCodeAt(index))) {
174
+ index += 1;
182
175
  }
176
+ } else {
177
+ // path.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
178
+ path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
179
+ return;
183
180
  }
184
- });
181
+ }
185
182
 
186
- return m;
183
+ path.index = index;
184
+ path.param = +path.pathValue.slice(start, index);
187
185
  }
188
186
 
189
- // Transform Functions
190
- // https://www.w3.org/TR/css-transforms-1/#transform-functions
191
-
192
187
  /**
193
- * Creates a new `CSSMatrix` for the translation matrix and returns it.
194
- * This method is equivalent to the CSS `translate3d()` function.
195
- *
196
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
188
+ * Checks if the character is a space.
197
189
  *
198
- * @param {Number} x the `x-axis` position.
199
- * @param {Number} y the `y-axis` position.
200
- * @param {Number} z the `z-axis` position.
201
- * @return {CSSMatrix} the resulted matrix.
190
+ * @param {number} ch the character to check
191
+ * @returns {boolean} check result
202
192
  */
203
- function Translate(x, y, z) {
204
- const m = new CSSMatrix();
205
- m.m41 = x;
206
- m.e = x;
207
- m.m42 = y;
208
- m.f = y;
209
- m.m43 = z;
210
- return m;
193
+ function isSpace(ch) {
194
+ const specialSpaces = [
195
+ 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
196
+ 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
197
+ return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
198
+ // White spaces
199
+ || (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
200
+ || (ch >= 0x1680 && specialSpaces.indexOf(ch) >= 0);
211
201
  }
212
202
 
213
203
  /**
214
- * Creates a new `CSSMatrix` for the rotation matrix and returns it.
204
+ * Points the parser to the next character in the
205
+ * path string every time it encounters any kind of
206
+ * space character.
215
207
  *
216
- * http://en.wikipedia.org/wiki/Rotation_matrix
208
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
209
+ */
210
+ function skipSpaces(path) {
211
+ const { pathValue, max } = path;
212
+ while (path.index < max && isSpace(pathValue.charCodeAt(path.index))) {
213
+ path.index += 1;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Checks if the character is a path command.
217
219
  *
218
- * @param {Number} rx the `x-axis` rotation.
219
- * @param {Number} ry the `y-axis` rotation.
220
- * @param {Number} rz the `z-axis` rotation.
221
- * @return {CSSMatrix} the resulted matrix.
220
+ * @param {any} code the character to check
221
+ * @returns {boolean} check result
222
222
  */
223
- function Rotate(rx, ry, rz) {
224
- const m = new CSSMatrix();
225
- const degToRad = Math.PI / 180;
226
- const radX = rx * degToRad;
227
- const radY = ry * degToRad;
228
- const radZ = rz * degToRad;
229
-
230
- // minus sin() because of right-handed system
231
- const cosx = Math.cos(radX);
232
- const sinx = -Math.sin(radX);
233
- const cosy = Math.cos(radY);
234
- const siny = -Math.sin(radY);
235
- const cosz = Math.cos(radZ);
236
- const sinz = -Math.sin(radZ);
237
-
238
- const m11 = cosy * cosz;
239
- const m12 = -cosy * sinz;
240
-
241
- m.m11 = m11;
242
- m.a = m11;
243
-
244
- m.m12 = m12;
245
- m.b = m12;
246
-
247
- m.m13 = siny;
248
-
249
- const m21 = sinx * siny * cosz + cosx * sinz;
250
- m.m21 = m21;
251
- m.c = m21;
252
-
253
- const m22 = cosx * cosz - sinx * siny * sinz;
254
- m.m22 = m22;
255
- m.d = m22;
256
-
257
- m.m23 = -sinx * cosy;
258
-
259
- m.m31 = sinx * sinz - cosx * siny * cosz;
260
- m.m32 = sinx * cosz + cosx * siny * sinz;
261
- m.m33 = cosx * cosy;
223
+ function isPathCommand(code) {
224
+ // eslint-disable-next-line no-bitwise -- Impossible to satisfy
225
+ switch (code | 0x20) {
226
+ case 0x6D/* m */:
227
+ case 0x7A/* z */:
228
+ case 0x6C/* l */:
229
+ case 0x68/* h */:
230
+ case 0x76/* v */:
231
+ case 0x63/* c */:
232
+ case 0x73/* s */:
233
+ case 0x71/* q */:
234
+ case 0x74/* t */:
235
+ case 0x61/* a */:
236
+ case 0x72/* r */:
237
+ return true;
238
+ default:
239
+ return false;
240
+ }
241
+ }
262
242
 
263
- return m;
243
+ /**
244
+ * Checks if the character is or belongs to a number.
245
+ * [0-9]|+|-|.
246
+ *
247
+ * @param {number} code the character to check
248
+ * @returns {boolean} check result
249
+ */
250
+ function isDigitStart(code) {
251
+ return (code >= 48 && code <= 57) /* 0..9 */
252
+ || code === 0x2B /* + */
253
+ || code === 0x2D /* - */
254
+ || code === 0x2E; /* . */
264
255
  }
265
256
 
266
257
  /**
267
- * Creates a new `CSSMatrix` for the rotation matrix and returns it.
268
- * This method is equivalent to the CSS `rotate3d()` function.
258
+ * Checks if the character is an A (arc-to) path command.
269
259
  *
270
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
260
+ * @param {number} code the character to check
261
+ * @returns {boolean} check result
262
+ */
263
+ function isArcCommand(code) {
264
+ // eslint-disable-next-line no-bitwise -- Impossible to satisfy
265
+ return (code | 0x20) === 0x61;
266
+ }
267
+
268
+ /**
269
+ * Scans every character in the path string to determine
270
+ * where a segment starts and where it ends.
271
271
  *
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.
276
- * @return {CSSMatrix} the resulted matrix.
272
+ * @param {SVGPC.parserPathArray} path the `parserPathArray` instance
277
273
  */
278
- function RotateAxisAngle(x, y, z, alpha) {
279
- const m = new CSSMatrix();
280
- const angle = alpha * (Math.PI / 360);
281
- const sinA = Math.sin(angle);
282
- const cosA = Math.cos(angle);
283
- const sinA2 = sinA * sinA;
284
- const length = Math.sqrt(x * x + y * y + z * z);
285
- let X = x;
286
- let Y = y;
287
- let Z = z;
274
+ function scanSegment(path) {
275
+ const { max, pathValue, index } = path;
276
+ const cmdCode = pathValue.charCodeAt(index);
277
+ const reqParams = paramsCount[pathValue[index].toLowerCase()];
288
278
 
289
- if (length === 0) {
290
- // bad vector length, use something reasonable
291
- X = 0;
292
- Y = 0;
293
- Z = 1;
294
- } else {
295
- X /= length;
296
- Y /= length;
297
- Z /= length;
279
+ path.segmentStart = index;
280
+
281
+ if (!isPathCommand(cmdCode)) {
282
+ path.err = `${invalidPathValue}: ${pathValue[index]} not a path command`;
283
+ return;
298
284
  }
299
285
 
300
- const x2 = X * X;
301
- const y2 = Y * Y;
302
- const z2 = Z * Z;
286
+ path.index += 1;
287
+ skipSpaces(path);
303
288
 
304
- const m11 = 1 - 2 * (y2 + z2) * sinA2;
305
- m.m11 = m11;
306
- m.a = m11;
289
+ path.data = [];
307
290
 
308
- const m12 = 2 * (X * Y * sinA2 + Z * sinA * cosA);
309
- m.m12 = m12;
310
- m.b = m12;
291
+ if (!reqParams) {
292
+ // Z
293
+ finalizeSegment(path);
294
+ return;
295
+ }
311
296
 
312
- m.m13 = 2 * (X * Z * sinA2 - Y * sinA * cosA);
297
+ for (;;) {
298
+ for (let i = reqParams; i > 0; i -= 1) {
299
+ if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(path);
300
+ else scanParam(path);
313
301
 
314
- const m21 = 2 * (Y * X * sinA2 - Z * sinA * cosA);
315
- m.m21 = m21;
316
- m.c = m21;
302
+ if (path.err.length) {
303
+ return;
304
+ }
305
+ path.data.push(path.param);
317
306
 
318
- const m22 = 1 - 2 * (z2 + x2) * sinA2;
319
- m.m22 = m22;
320
- m.d = m22;
307
+ skipSpaces(path);
321
308
 
322
- m.m23 = 2 * (Y * Z * sinA2 + X * sinA * cosA);
323
- m.m31 = 2 * (Z * X * sinA2 + Y * sinA * cosA);
324
- m.m32 = 2 * (Z * Y * sinA2 - X * sinA * cosA);
325
- m.m33 = 1 - 2 * (x2 + y2) * sinA2;
309
+ // after ',' param is mandatory
310
+ if (path.index < max && pathValue.charCodeAt(path.index) === 0x2C/* , */) {
311
+ path.index += 1;
312
+ skipSpaces(path);
313
+ }
314
+ }
326
315
 
327
- return m;
316
+ if (path.index >= path.max) {
317
+ break;
318
+ }
319
+
320
+ // Stop on next segment
321
+ if (!isDigitStart(pathValue.charCodeAt(path.index))) {
322
+ break;
323
+ }
324
+ }
325
+
326
+ finalizeSegment(path);
328
327
  }
329
328
 
329
+ // @ts-nocheck
330
330
  /**
331
- * Creates a new `CSSMatrix` for the scale matrix and returns it.
332
- * This method is equivalent to the CSS `scale3d()` function.
331
+ * Returns a clone of an existing `pathArray`.
333
332
  *
334
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
335
- *
336
- * @param {Number} x the `x-axis` scale.
337
- * @param {Number} y the `y-axis` scale.
338
- * @param {Number} z the `z-axis` scale.
339
- * @return {CSSMatrix} the resulted matrix.
333
+ * @param {SVGPC.pathArray | string[]} path the source `pathArray`
334
+ * @returns {SVGPC.pathArray} the cloned `pathArray`
340
335
  */
341
- function Scale(x, y, z) {
342
- const m = new CSSMatrix();
343
- m.m11 = x;
344
- m.a = x;
345
-
346
- m.m22 = y;
347
- m.d = y;
348
-
349
- m.m33 = z;
350
- return m;
336
+ function clonePath(path) {
337
+ return path.map((x) => {
338
+ if (Array.isArray(x)) {
339
+ return clonePath(x);
340
+ }
341
+ return !Number.isNaN(+x) ? +x : x;
342
+ });
351
343
  }
352
344
 
353
345
  /**
354
- * Creates a new `CSSMatrix` for the shear of the `x-axis` rotation matrix and
355
- * returns it. This method is equivalent to the CSS `skewX()` function.
346
+ * The `parserPathArray` used by the parser.
356
347
  *
357
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
358
- *
359
- * @param {Number} angle the angle in degrees.
360
- * @return {CSSMatrix} the resulted matrix.
348
+ * @param {string} pathString
361
349
  */
362
- function SkewX(angle) {
363
- const m = new CSSMatrix();
364
- const radA = (angle * Math.PI) / 180;
365
- const t = Math.tan(radA);
366
- m.m21 = t;
367
- m.c = t;
368
- return m;
350
+ function SVGPathArray(pathString) {
351
+ /** @type {[string, ...number[]][]} */
352
+ this.segments = [];
353
+ /** @type {string} */
354
+ this.pathValue = pathString;
355
+ /** @type {number} */
356
+ this.max = pathString.length;
357
+ /** @type {number} */
358
+ this.index = 0;
359
+ /** @type {number} */
360
+ this.param = 0.0;
361
+ /** @type {number} */
362
+ this.segmentStart = 0;
363
+ /** @type {any} */
364
+ this.data = [];
365
+ /** @type {string} */
366
+ this.err = '';
369
367
  }
370
368
 
371
369
  /**
372
- * Creates a new `CSSMatrix` for the shear of the `y-axis` rotation matrix and
373
- * returns it. This method is equivalent to the CSS `skewY()` function.
374
- *
375
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
370
+ * Iterates an array to check if it's an actual `pathArray`.
376
371
  *
377
- * @param {Number} angle the angle in degrees.
378
- * @return {CSSMatrix} the resulted matrix.
372
+ * @param {string | SVGPC.pathArray} path the `pathArray` to be checked
373
+ * @returns {boolean} iteration result
379
374
  */
380
- function SkewY(angle) {
381
- const m = new CSSMatrix();
382
- const radA = (angle * Math.PI) / 180;
383
- const t = Math.tan(radA);
384
- m.m12 = t;
385
- m.b = t;
386
- return m;
375
+ function isPathArray(path) {
376
+ return Array.isArray(path) && path.every((seg) => {
377
+ const lk = seg[0].toLowerCase();
378
+ return paramsCount[lk] === seg.length - 1 && /[achlmqstvz]/gi.test(lk);
379
+ });
387
380
  }
388
381
 
389
382
  /**
390
- * Creates a new `CSSMatrix` resulted from the multiplication of two matrixes
391
- * and returns it. Both matrixes are not changed.
383
+ * Parses a path string value and returns an array
384
+ * of segments we like to call `pathArray`.
392
385
  *
393
- * @param {CSSMatrix} m1 the first matrix.
394
- * @param {CSSMatrix} m2 the second matrix.
395
- * @return {CSSMatrix} the resulted matrix.
386
+ * @param {string | SVGPC.pathArray} pathInput the string to be parsed
387
+ * @returns {SVGPC.pathArray} the resulted `pathArray`
396
388
  */
397
- function Multiply(m1, m2) {
398
- const m11 = m2.m11 * m1.m11 + m2.m12 * m1.m21 + m2.m13 * m1.m31 + m2.m14 * m1.m41;
399
- const m12 = m2.m11 * m1.m12 + m2.m12 * m1.m22 + m2.m13 * m1.m32 + m2.m14 * m1.m42;
400
- const m13 = m2.m11 * m1.m13 + m2.m12 * m1.m23 + m2.m13 * m1.m33 + m2.m14 * m1.m43;
401
- const m14 = m2.m11 * m1.m14 + m2.m12 * m1.m24 + m2.m13 * m1.m34 + m2.m14 * m1.m44;
389
+ function parsePathString(pathInput) {
390
+ if (isPathArray(pathInput)) {
391
+ // @ts-ignore
392
+ return clonePath(pathInput);
393
+ }
394
+ // @ts-ignore
395
+ const path = new SVGPathArray(pathInput);
402
396
 
403
- const m21 = m2.m21 * m1.m11 + m2.m22 * m1.m21 + m2.m23 * m1.m31 + m2.m24 * m1.m41;
404
- const m22 = m2.m21 * m1.m12 + m2.m22 * m1.m22 + m2.m23 * m1.m32 + m2.m24 * m1.m42;
405
- const m23 = m2.m21 * m1.m13 + m2.m22 * m1.m23 + m2.m23 * m1.m33 + m2.m24 * m1.m43;
406
- const m24 = m2.m21 * m1.m14 + m2.m22 * m1.m24 + m2.m23 * m1.m34 + m2.m24 * m1.m44;
397
+ skipSpaces(path);
407
398
 
408
- const m31 = m2.m31 * m1.m11 + m2.m32 * m1.m21 + m2.m33 * m1.m31 + m2.m34 * m1.m41;
409
- const m32 = m2.m31 * m1.m12 + m2.m32 * m1.m22 + m2.m33 * m1.m32 + m2.m34 * m1.m42;
410
- const m33 = m2.m31 * m1.m13 + m2.m32 * m1.m23 + m2.m33 * m1.m33 + m2.m34 * m1.m43;
411
- const m34 = m2.m31 * m1.m14 + m2.m32 * m1.m24 + m2.m33 * m1.m34 + m2.m34 * m1.m44;
399
+ while (path.index < path.max && !path.err.length) {
400
+ scanSegment(path);
401
+ }
412
402
 
413
- const m41 = m2.m41 * m1.m11 + m2.m42 * m1.m21 + m2.m43 * m1.m31 + m2.m44 * m1.m41;
414
- const m42 = m2.m41 * m1.m12 + m2.m42 * m1.m22 + m2.m43 * m1.m32 + m2.m44 * m1.m42;
415
- const m43 = m2.m41 * m1.m13 + m2.m42 * m1.m23 + m2.m43 * m1.m33 + m2.m44 * m1.m43;
416
- const m44 = m2.m41 * m1.m14 + m2.m42 * m1.m24 + m2.m43 * m1.m34 + m2.m44 * m1.m44;
403
+ if (path.err.length) {
404
+ path.segments = [];
405
+ } else if (path.segments.length) {
406
+ if ('mM'.indexOf(path.segments[0][0]) < 0) {
407
+ path.err = `${invalidPathValue}: missing M/m`;
408
+ path.segments = [];
409
+ } else {
410
+ path.segments[0][0] = 'M';
411
+ }
412
+ }
417
413
 
418
- return fromArray(
419
- [m11, m12, m13, m14,
420
- m21, m22, m23, m24,
421
- m31, m32, m33, m34,
422
- m41, m42, m43, m44],
423
- );
414
+ return path.segments;
424
415
  }
425
416
 
426
417
  /**
427
- * Creates and returns a new `DOMMatrix` compatible *Object*
428
- * with equivalent instance methods.
418
+ * Iterates an array to check if it's a `pathArray`
419
+ * with all absolute values.
429
420
  *
430
- * https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
431
- * https://github.com/thednp/DOMMatrix/
421
+ * @param {string | SVGPC.pathArray} path the `pathArray` to be checked
422
+ * @returns {boolean} iteration result
432
423
  */
424
+ function isAbsoluteArray(path) {
425
+ return Array.isArray(path) && isPathArray(path)
426
+ && path.every((x) => x[0] === x[0].toUpperCase());
427
+ }
433
428
 
434
- class CSSMatrix {
435
- /**
436
- * @constructor
437
- * @param {any} args accepts all possible parameter configuration:
438
- * * Types: number[] | string | CSSMatrix | DOMMatrix | undefined
439
- * * valid CSS transform string,
440
- * * CSSMatrix/DOMMatrix instance
441
- * * a 6/16 elements *Array*
442
- */
443
- constructor(...args) {
444
- const m = this;
445
- // array 6
446
- m.a = 1; m.b = 0;
447
- m.c = 0; m.d = 1;
448
- m.e = 0; m.f = 0;
449
- // array 16
450
- m.m11 = 1; m.m12 = 0; m.m13 = 0; m.m14 = 0;
451
- m.m21 = 0; m.m22 = 1; m.m23 = 0; m.m24 = 0;
452
- m.m31 = 0; m.m32 = 0; m.m33 = 1; m.m34 = 0;
453
- m.m41 = 0; m.m42 = 0; m.m43 = 0; m.m44 = 1;
454
-
455
- if (args && args.length) {
456
- let ARGS = args;
429
+ /**
430
+ * Parses a path string value or object and returns an array
431
+ * of segments, all converted to absolute values.
432
+ *
433
+ * @param {string | SVGPC.pathArray} pathInput the path string | object
434
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with absolute values
435
+ */
436
+ function pathToAbsolute(pathInput) {
437
+ if (Array.isArray(pathInput) && isAbsoluteArray(pathInput)) {
438
+ return clonePath(pathInput);
439
+ }
457
440
 
458
- if (args instanceof Array) {
459
- if ((args[0] instanceof Array && [16, 6].includes(args[0].length))
460
- || typeof args[0] === 'string'
461
- || [CSSMatrix, DOMMatrix].some((x) => args[0] instanceof x)) {
462
- [ARGS] = args;
463
- }
464
- }
465
- return m.setMatrixValue(ARGS);
466
- }
467
- return m;
468
- }
441
+ const path = parsePathString(pathInput);
442
+ const ii = path.length;
443
+ /** @type {SVGPC.pathArray} */
444
+ const resultArray = [];
445
+ let x = 0;
446
+ let y = 0;
447
+ let mx = 0;
448
+ let my = 0;
449
+ let start = 0;
469
450
 
470
- /**
471
- * Sets a new `Boolean` flag value for `this.isIdentity` matrix property.
472
- *
473
- * @param {Boolean} value sets a new flag for this property
474
- */
475
- set isIdentity(value) {
476
- this.isIdentity = value;
451
+ if (path[0][0] === 'M') {
452
+ x = +path[0][1];
453
+ y = +path[0][2];
454
+ mx = x;
455
+ my = y;
456
+ start += 1;
457
+ resultArray.push(['M', x, y]);
477
458
  }
478
459
 
479
- /**
480
- * A `Boolean` whose value is `true` if the matrix is the identity matrix. The identity
481
- * matrix is one in which every value is 0 except those on the main diagonal from top-left
482
- * to bottom-right corner (in other words, where the offsets in each direction are equal).
483
- *
484
- * @return {Boolean} the current property value
485
- */
486
- get isIdentity() {
487
- const m = this;
488
- return (m.m11 === 1 && m.m12 === 0 && m.m13 === 0 && m.m14 === 0
489
- && m.m21 === 0 && m.m22 === 1 && m.m23 === 0 && m.m24 === 0
490
- && m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0
491
- && m.m41 === 0 && m.m42 === 0 && m.m43 === 0 && m.m44 === 1);
492
- }
460
+ for (let i = start; i < ii; i += 1) {
461
+ const segment = path[i];
462
+ const [pathCommand] = segment;
463
+ const absCommand = pathCommand.toUpperCase();
464
+ const absoluteSegment = [];
465
+ let newSeg = [];
493
466
 
494
- /**
495
- * A `Boolean` flag whose value is `true` if the matrix was initialized as a 2D matrix
496
- * and `false` if the matrix is 3D.
497
- *
498
- * @return {Boolean} the current property value
499
- */
500
- get is2D() {
501
- const m = this;
502
- return (m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0 && m.m43 === 0 && m.m44 === 1);
503
- }
467
+ if (pathCommand !== absCommand) {
468
+ absoluteSegment[0] = absCommand;
504
469
 
505
- /**
506
- * Sets a new `Boolean` flag value for `this.is2D` matrix property.
507
- *
508
- * @param {Boolean} value sets a new flag for this property
509
- */
510
- set is2D(value) {
511
- this.is2D = value;
512
- }
470
+ switch (absCommand) {
471
+ case 'A':
472
+ newSeg = segment.slice(1, -2).concat([+segment[6] + x, +segment[7] + y]);
473
+ for (let j = 0; j < newSeg.length; j += 1) {
474
+ absoluteSegment.push(newSeg[j]);
475
+ }
476
+ break;
477
+ case 'V':
478
+ absoluteSegment[1] = +segment[1] + y;
479
+ break;
480
+ case 'H':
481
+ absoluteSegment[1] = +segment[1] + x;
482
+ break;
483
+ default:
484
+ if (absCommand === 'M') {
485
+ mx = +segment[1] + x;
486
+ my = +segment[2] + y;
487
+ }
488
+ // for is here to stay for eslint
489
+ for (let j = 1; j < segment.length; j += 1) {
490
+ absoluteSegment.push(+segment[j] + (j % 2 ? x : y));
491
+ }
492
+ }
493
+ } else {
494
+ for (let j = 0; j < segment.length; j += 1) {
495
+ absoluteSegment.push(segment[j]);
496
+ }
497
+ }
498
+ // @ts-ignore
499
+ resultArray.push(absoluteSegment);
513
500
 
514
- /**
515
- * The `setMatrixValue` method replaces the existing matrix with one computed
516
- * in the browser. EG: `matrix(1,0.25,-0.25,1,0,0)`
517
- *
518
- * The method accepts any *Array* values, the result of
519
- * `DOMMatrix` instance method `toFloat64Array()` / `toFloat32Array()` calls
520
- * or `CSSMatrix` instance method `toArray()`.
521
- *
522
- * This method expects valid *matrix()* / *matrix3d()* string values, as well
523
- * as other transform functions like *translateX(10px)*.
524
- *
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.
531
- */
532
- setMatrixValue(source) {
533
- const m = this;
501
+ const segLength = absoluteSegment.length;
502
+ switch (absCommand) {
503
+ case 'Z':
504
+ x = mx;
505
+ y = my;
506
+ break;
507
+ case 'H':
508
+ x = +absoluteSegment[1];
509
+ break;
510
+ case 'V':
511
+ y = +absoluteSegment[1];
512
+ break;
513
+ default:
514
+ x = +absoluteSegment[segLength - 2];
515
+ y = +absoluteSegment[segLength - 1];
534
516
 
535
- // new CSSMatrix(CSSMatrix | DOMMatrix)
536
- if ([DOMMatrix, CSSMatrix].some((x) => source instanceof x)) {
537
- // @ts-ignore
538
- return fromMatrix(source);
539
- // CSS transform string source
540
- } if (typeof source === 'string' && source.length && source !== 'none') {
541
- return fromString(source);
542
- // [Arguments list | Array] come here
543
- } if (Array.isArray(source)) {
544
- // @ts-ignore
545
- return fromArray(source);
517
+ if (absCommand === 'M') {
518
+ mx = x;
519
+ my = y;
520
+ }
546
521
  }
547
- return m;
548
522
  }
549
523
 
550
- /**
551
- * Creates and returns a string representation of the matrix in `CSS` matrix syntax,
552
- * using the appropriate `CSS` matrix notation.
553
- *
554
- * The 16 items in the array 3D matrix array are *transposed* in row-major order.
555
- *
556
- * matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
557
- * matrix *matrix(a, b, c, d, e, f)*
558
- *
559
- * @return {String} a string representation of the matrix
560
- */
561
- toString() {
562
- const m = this;
563
- const values = m.toArray().join(',');
564
- const type = m.is2D ? 'matrix' : 'matrix3d';
565
- return `${type}(${values})`;
566
- }
524
+ return resultArray;
525
+ }
567
526
 
568
- /**
569
- * Returns an *Array* containing all 16 elements which comprise the matrix.
570
- * The method can return either the elements.
571
- *
572
- * Other methods make use of this method to feed their output values from this matrix.
573
- *
574
- * @return {Number[]} an *Array* representation of the matrix
575
- */
576
- toArray() {
577
- const m = this;
578
- const pow6 = (10 ** 6);
579
- let result;
527
+ /**
528
+ * Iterates an array to check if it's a `pathArray`
529
+ * with relative values.
530
+ *
531
+ * @param {string | SVGPC.pathArray} path the `pathArray` to be checked
532
+ * @returns {boolean} iteration result
533
+ */
534
+ function isRelativeArray(path) {
535
+ return Array.isArray(path) && isPathArray(path)
536
+ && path.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
537
+ }
580
538
 
581
- if (m.is2D) {
582
- result = [m.a, m.b, m.c, m.d, m.e, m.f];
583
- } else {
584
- result = [m.m11, m.m12, m.m13, m.m14,
585
- m.m21, m.m22, m.m23, m.m24,
586
- m.m31, m.m32, m.m33, m.m34,
587
- m.m41, m.m42, m.m43, m.m44];
588
- }
589
- // clean up the numbers
590
- // eslint-disable-next-line
591
- return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
539
+ /**
540
+ * Parses a path string value or object and returns an array
541
+ * of segments, all converted to relative values.
542
+ *
543
+ * @param {string | SVGPC.pathArray} pathInput the path string | object
544
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with relative values
545
+ */
546
+ function pathToRelative(pathInput) {
547
+ if (Array.isArray(pathInput) && isRelativeArray(pathInput)) {
548
+ return clonePath(pathInput);
592
549
  }
593
550
 
594
- /**
595
- * Returns a JSON representation of the `CSSMatrix` object, a standard *Object*
596
- * that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties and
597
- * excludes `is2D` & `isIdentity` properties.
598
- *
599
- * The result can also be used as a second parameter for the `fromMatrix` static method
600
- * to load values into a matrix instance.
601
- *
602
- * @return {Object} an *Object* with all matrix values.
603
- */
604
- toJSON() {
605
- return JSON.parse(JSON.stringify(this));
606
- }
551
+ const path = parsePathString(pathInput);
552
+ const ii = path.length;
553
+ /** @type {SVGPC.pathArray} */
554
+ const resultArray = [];
555
+ let x = 0;
556
+ let y = 0;
557
+ let mx = 0;
558
+ let my = 0;
559
+ let start = 0;
607
560
 
608
- /**
609
- * The Multiply method returns a new CSSMatrix which is the result of this
610
- * matrix multiplied by the passed matrix, with the passed matrix to the right.
611
- * This matrix is not modified.
612
- *
613
- * @param {CSSMatrix} m2 CSSMatrix
614
- * @return {CSSMatrix} The result matrix.
615
- */
616
- multiply(m2) {
617
- return Multiply(this, m2);
561
+ if (path[0][0] === 'M') {
562
+ x = +path[0][1];
563
+ y = +path[0][2];
564
+ mx = x;
565
+ my = y;
566
+ start += 1;
567
+ resultArray.push(['M', x, y]);
618
568
  }
619
569
 
620
- /**
621
- * The translate method returns a new matrix which is this matrix post
622
- * multiplied by a translation matrix containing the passed values. If the z
623
- * component is undefined, a 0 value is used in its place. This matrix is not
624
- * modified.
625
- *
626
- * @param {number} x X component of the translation value.
627
- * @param {number} y Y component of the translation value.
628
- * @param {number} z Z component of the translation value.
629
- * @return {CSSMatrix} The result matrix
630
- */
631
- translate(x, y, z) {
632
- const X = x;
633
- let Y = y;
634
- let Z = z;
635
- if (Z == null) Z = 0;
636
- if (Y == null) Y = 0;
637
- return Multiply(this, Translate(X, Y, Z));
638
- }
570
+ for (let i = start; i < ii; i += 1) {
571
+ const segment = path[i];
572
+ const [pathCommand] = segment;
573
+ const relativeCommand = pathCommand.toLowerCase();
574
+ const relativeSegment = []; // this a test to please TS
575
+ let newSeg = [];
639
576
 
640
- /**
641
- * The scale method returns a new matrix which is this matrix post multiplied by
642
- * a scale matrix containing the passed values. If the z component is undefined,
643
- * a 1 value is used in its place. If the y component is undefined, the x
644
- * component value is used in its place. This matrix is not modified.
645
- *
646
- * @param {number} x The X component of the scale value.
647
- * @param {number} y The Y component of the scale value.
648
- * @param {number} z The Z component of the scale value.
649
- * @return {CSSMatrix} The result matrix
650
- */
651
- scale(x, y, z) {
652
- const X = x;
653
- let Y = y;
654
- let Z = z;
655
- if (Y == null) Y = x;
656
- if (Z == null) Z = x;
577
+ if (pathCommand !== relativeCommand) {
578
+ relativeSegment[0] = relativeCommand;
579
+ switch (relativeCommand) {
580
+ case 'a':
581
+ newSeg = segment.slice(1, -2).concat([+segment[6] - x, +segment[7] - y]);
657
582
 
658
- return Multiply(this, Scale(X, Y, Z));
583
+ for (let j = 0; j < newSeg.length; j += 1) {
584
+ relativeSegment.push(newSeg[j]);
585
+ }
586
+ break;
587
+ case 'v':
588
+ relativeSegment[1] = +segment[1] - y;
589
+ break;
590
+ default:
591
+ // for is here to stay for eslint
592
+ for (let j = 1; j < segment.length; j += 1) {
593
+ relativeSegment.push(+segment[j] - (j % 2 ? x : y));
594
+ }
595
+
596
+ if (relativeCommand === 'm') {
597
+ mx = +segment[1];
598
+ my = +segment[2];
599
+ }
600
+ }
601
+ } else {
602
+ if (pathCommand === 'm') {
603
+ mx = +segment[1] + x;
604
+ my = +segment[2] + y;
605
+ }
606
+ for (let j = 0; j < segment.length; j += 1) {
607
+ relativeSegment.push(segment[j]);
608
+ }
609
+ }
610
+ // @ts-ignore
611
+ resultArray.push(relativeSegment);
612
+
613
+ const segLength = relativeSegment.length;
614
+ switch (relativeSegment[0]) {
615
+ case 'z':
616
+ x = mx;
617
+ y = my;
618
+ break;
619
+ case 'h':
620
+ x += +relativeSegment[segLength - 1];
621
+ break;
622
+ case 'v':
623
+ y += +relativeSegment[segLength - 1];
624
+ break;
625
+ default:
626
+ x += +resultArray[i][segLength - 2];
627
+ y += +resultArray[i][segLength - 1];
628
+ }
659
629
  }
660
630
 
661
- /**
662
- * The rotate method returns a new matrix which is this matrix post multiplied
663
- * by each of 3 rotation matrices about the major axes, first X, then Y, then Z.
664
- * If the y and z components are undefined, the x value is used to rotate the
665
- * object about the z axis, as though the vector (0,0,x) were passed. All
666
- * rotation values are in degrees. This matrix is not modified.
667
- *
668
- * @param {number} rx The X component of the rotation, or Z if Y and Z are null.
669
- * @param {number} ry The (optional) Y component of the rotation value.
670
- * @param {number} rz The (optional) Z component of the rotation value.
671
- * @return {CSSMatrix} The result matrix
672
- */
673
- rotate(rx, ry, rz) {
674
- let RX = rx;
675
- let RY = ry;
676
- let RZ = rz;
677
- if (RY == null) RY = 0;
678
- if (RZ == null) { RZ = RX; RX = 0; }
679
- return Multiply(this, Rotate(RX, RY, RZ));
631
+ return resultArray;
632
+ }
633
+
634
+ /**
635
+ * Rounds the values of a `pathArray` instance to
636
+ * a specified amount of decimals and returns it.
637
+ *
638
+ * @param {SVGPC.pathArray} path the source `pathArray`
639
+ * @param {null | number} round the amount of decimals to round numbers to
640
+ * @returns {SVGPC.pathArray} the resulted `pathArray` with rounded values
641
+ */
642
+ function roundPath(path, round) {
643
+ const { round: defaultRound, decimals: defaultDecimals } = SVGPCO;
644
+ const decimalsOption = round && !Number.isNaN(+round) ? +round
645
+ : defaultRound && defaultDecimals;
646
+
647
+ if (!decimalsOption) return clonePath(path);
648
+
649
+ // @ts-ignore
650
+ return path.map((seg) => seg.map((c) => {
651
+ const nr = +c;
652
+ const dc = 10 ** decimalsOption;
653
+ if (!Number.isNaN(nr)) {
654
+ return nr % 1 === 0 ? nr : Math.round(nr * dc) / dc;
655
+ }
656
+ return c;
657
+ }));
658
+ }
659
+
660
+ /**
661
+ * Returns a valid `d` attribute string value created
662
+ * by rounding values and concatenating the `pathArray` segments.
663
+ *
664
+ * @param {SVGPC.pathArray} path the `pathArray` object
665
+ * @param {number | null} round amount of decimals to round values to
666
+ * @returns {string} the concatenated path string
667
+ */
668
+ function pathToString(path, round) {
669
+ return roundPath(path, round)
670
+ .map((x) => x[0].concat(x.slice(1).join(' '))).join('');
671
+ }
672
+
673
+ /**
674
+ * Returns the missing control point from an
675
+ * T (shorthand quadratic bezier) segment.
676
+ *
677
+ * @param {number} x1 curve start x
678
+ * @param {number} y1 curve start y
679
+ * @param {number} qx control point x
680
+ * @param {number} qy control point y
681
+ * @param {string} prevCommand the previous path command
682
+ * @returns {{qx: number, qy: number}}} the missing control point
683
+ */
684
+ function shorthandToQuad(x1, y1, qx, qy, prevCommand) {
685
+ return 'QT'.indexOf(prevCommand) > -1
686
+ ? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
687
+ : { qx: x1, qy: y1 };
688
+ }
689
+
690
+ /**
691
+ * Returns the missing control point from an
692
+ * S (shorthand cubic bezier) segment.
693
+ *
694
+ * @param {number} x1 curve start x
695
+ * @param {number} y1 curve start y
696
+ * @param {number} x2 curve end x
697
+ * @param {number} y2 curve end y
698
+ * @param {string} prevCommand the previous path command
699
+ * @returns {{x1: number, y1: number}}} the missing control point
700
+ */
701
+ function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
702
+ return 'CS'.indexOf(prevCommand) > -1
703
+ ? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
704
+ : { x1, y1 };
705
+ }
706
+
707
+ /**
708
+ * Normalizes a single segment of a `pathArray` object.
709
+ *
710
+ * @param {SVGPC.pathSegment} segment the segment object
711
+ * @param {any} params the coordinates of the previous segment
712
+ * @param {String} prevCommand the path command of the previous segment
713
+ * @returns {any} the normalized segment
714
+ */
715
+ function normalizeSegment(segment, params, prevCommand) {
716
+ const [pathCommand] = segment;
717
+ const xy = segment.slice(1);
718
+ let result = segment.slice();
719
+
720
+ if ('TQ'.indexOf(segment[0]) < 0) {
721
+ // optional but good to be cautious
722
+ params.qx = null;
723
+ params.qy = null;
680
724
  }
681
725
 
682
- /**
683
- * The rotateAxisAngle method returns a new matrix which is this matrix post
684
- * multiplied by a rotation matrix with the given axis and `angle`. The right-hand
685
- * rule is used to determine the direction of rotation. All rotation values are
686
- * in degrees. This matrix is not modified.
687
- *
688
- * @param {number} x The X component of the axis vector.
689
- * @param {number} y The Y component of the axis vector.
690
- * @param {number} z The Z component of the axis vector.
691
- * @param {number} angle The angle of rotation about the axis vector, in degrees.
692
- * @return {CSSMatrix} The `CSSMatrix` result
693
- */
694
- rotateAxisAngle(x, y, z, angle) {
695
- if (arguments.length !== 4) {
696
- throw new TypeError('CSSMatrix: expecting 4 values');
726
+ if (pathCommand === 'H') {
727
+ result = ['L', segment[1], params.y1];
728
+ } else if (pathCommand === 'V') {
729
+ result = ['L', params.x1, segment[1]];
730
+ } else if (pathCommand === 'S') {
731
+ const { x1, y1 } = shorthandToCubic(params.x1, params.y1, params.x2, params.y2, prevCommand);
732
+ params.x1 = x1;
733
+ params.y1 = y1;
734
+ result = ['C', x1, y1].concat(xy);
735
+ } else if (pathCommand === 'T') {
736
+ const { qx, qy } = shorthandToQuad(params.x1, params.y1, params.qx, params.qy, prevCommand);
737
+ params.qx = qx;
738
+ params.qy = qy;
739
+ result = ['Q', qx, qy].concat(xy);
740
+ } else if (pathCommand === 'Q') {
741
+ const [nqx, nqy] = xy;
742
+ params.qx = nqx;
743
+ params.qy = nqy;
744
+ }
745
+ return result;
746
+ }
747
+
748
+ /**
749
+ * Iterates an array to check if it's a `pathArray`
750
+ * with all segments are in non-shorthand notation
751
+ * with absolute values.
752
+ *
753
+ * @param {string | SVGPC.pathArray} path the `pathArray` to be checked
754
+ * @returns {boolean} iteration result
755
+ */
756
+ function isNormalizedArray(path) {
757
+ return Array.isArray(path) && isPathArray(path) && path.every((seg) => {
758
+ const lk = seg[0].toLowerCase();
759
+ return paramsCount[lk] === seg.length - 1 && ('ACLMQZ').includes(seg[0]); // achlmqstvz
760
+ });
761
+ }
762
+
763
+ /**
764
+ * Normalizes a `path` object for further processing:
765
+ * * convert segments to absolute values
766
+ * * convert shorthand path commands to their non-shorthand notation
767
+ *
768
+ * @param {string | SVGPC.pathArray} pathInput the string to be parsed or 'pathArray'
769
+ * @returns {SVGPC.pathArray} the normalized `pathArray`
770
+ */
771
+ function normalizePath(pathInput) { // path|pathString
772
+ if (Array.isArray(pathInput) && isNormalizedArray(pathInput)) {
773
+ return clonePath(pathInput);
774
+ }
775
+
776
+ const path = pathToAbsolute(pathInput);
777
+ const params = {
778
+ x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
779
+ };
780
+ const allPathCommands = [];
781
+ const ii = path.length;
782
+ let prevCommand = '';
783
+ let segment;
784
+ let seglen;
785
+
786
+ for (let i = 0; i < ii; i += 1) {
787
+ // save current path command
788
+ const [pathCommand] = path[i];
789
+
790
+ // Save current path command
791
+ allPathCommands[i] = pathCommand;
792
+ // Get previous path command
793
+ if (i) prevCommand = allPathCommands[i - 1];
794
+ // Previous path command is inputted to processSegment
795
+ path[i] = normalizeSegment(path[i], params, prevCommand);
796
+
797
+ segment = path[i];
798
+ seglen = segment.length;
799
+
800
+ params.x1 = +segment[seglen - 2];
801
+ params.y1 = +segment[seglen - 1];
802
+ params.x2 = +(segment[seglen - 4]) || params.x1;
803
+ params.y2 = +(segment[seglen - 3]) || params.y1;
804
+ }
805
+ return path;
806
+ }
807
+
808
+ /**
809
+ * Reverses all segments and their values of a `pathArray`
810
+ * and returns a new instance.
811
+ *
812
+ * @param {SVGPC.pathArray} pathInput the source `pathArray`
813
+ * @returns {SVGPC.pathArray} the reversed `pathArray`
814
+ */
815
+ function reversePath(pathInput) {
816
+ const absolutePath = pathToAbsolute(pathInput);
817
+ const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
818
+ let reversedPath = [];
819
+ let segLength = 0;
820
+
821
+ reversedPath = normalizePath(absolutePath).map((segment, i) => {
822
+ segLength = segment.length;
823
+ return {
824
+ seg: absolutePath[i], // absolute
825
+ n: segment, // normalized
826
+ c: absolutePath[i][0], // pathCommand
827
+ x: segment[segLength - 2], // x
828
+ y: segment[segLength - 1], // y
829
+ };
830
+ }).map((seg, i, path) => {
831
+ const segment = seg.seg;
832
+ const data = seg.n;
833
+ const prevSeg = i && path[i - 1];
834
+ const nextSeg = path[i + 1] && path[i + 1];
835
+ const pathCommand = seg.c;
836
+ const pLen = path.length;
837
+ const x = i ? path[i - 1].x : path[pLen - 1].x;
838
+ const y = i ? path[i - 1].y : path[pLen - 1].y;
839
+ let result = [];
840
+
841
+ switch (pathCommand) {
842
+ case 'M':
843
+ result = isClosed ? ['Z'] : [pathCommand, x, y];
844
+ break;
845
+ case 'A':
846
+ result = segment.slice(0, -3).concat([(segment[5] === 1 ? 0 : 1), x, y]);
847
+ break;
848
+ case 'C':
849
+ if (nextSeg && nextSeg.c === 'S') {
850
+ result = ['S', segment[1], segment[2], x, y];
851
+ } else {
852
+ result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y];
853
+ }
854
+ break;
855
+ case 'S':
856
+ if ((prevSeg && 'CS'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'S'))) {
857
+ result = ['C', data[3], data[4], data[1], data[2], x, y];
858
+ } else {
859
+ result = [pathCommand, data[1], data[2], x, y];
860
+ }
861
+ break;
862
+ case 'Q':
863
+ if (nextSeg && nextSeg.c === 'T') {
864
+ result = ['T', x, y];
865
+ } else {
866
+ result = segment.slice(0, -2).concat([x, y]);
867
+ }
868
+ break;
869
+ case 'T':
870
+ if ((prevSeg && 'QT'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'T'))) {
871
+ result = ['Q', data[1], data[2], x, y];
872
+ } else {
873
+ result = [pathCommand, x, y];
874
+ }
875
+ break;
876
+ case 'Z':
877
+ result = ['M', x, y];
878
+ break;
879
+ case 'H':
880
+ result = [pathCommand, x];
881
+ break;
882
+ case 'V':
883
+ result = [pathCommand, y];
884
+ break;
885
+ default:
886
+ result = segment.slice(0, -2).concat([x, y]);
697
887
  }
698
- return Multiply(this, RotateAxisAngle(x, y, z, angle));
888
+
889
+ return result;
890
+ });
891
+ // @ts-ignore
892
+ return isClosed ? reversedPath.reverse()
893
+ : [reversedPath[0]].concat(reversedPath.slice(1).reverse());
894
+ }
895
+
896
+ /**
897
+ * Split a path into an `Array` of sub-path strings.
898
+ *
899
+ * In the process, values are converted to absolute
900
+ * for visual consistency.
901
+ *
902
+ * @param {SVGPC.pathArray | string} pathInput the cubic-bezier parameters
903
+ * @return {string[]} an array with all sub-path strings
904
+ */
905
+ function splitPath(pathInput) {
906
+ return pathToString(pathToAbsolute(pathInput), 0)
907
+ .replace(/(m|M)/g, '|$1')
908
+ .split('|')
909
+ .map((s) => s.trim())
910
+ .filter((s) => s);
911
+ }
912
+
913
+ /**
914
+ * Optimizes a `pathArray` object:
915
+ * * convert segments to absolute and relative values
916
+ * * create a new `pathArray` with elements with shortest segments
917
+ * from absolute and relative `pathArray`s
918
+ *
919
+ * @param {string | SVGPC.pathArray} pathInput a string or `pathArray`
920
+ * @param {number | null} round the amount of decimals to round values to
921
+ * @returns {SVGPC.pathArray} the optimized `pathArray`
922
+ */
923
+ function optimizePath(pathInput, round) {
924
+ const absolutePath = roundPath(pathToAbsolute(pathInput), round);
925
+ const relativePath = roundPath(pathToRelative(pathInput), round);
926
+ return absolutePath.map((x, i) => {
927
+ if (i) {
928
+ return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
929
+ }
930
+ return x;
931
+ });
932
+ }
933
+
934
+ /**
935
+ * A global namespace for epsilon.
936
+ *
937
+ * @type {Number}
938
+ */
939
+ const epsilon = 1e-9;
940
+
941
+ /**
942
+ * Returns an {x,y} vector rotated by a given
943
+ * angle in radian.
944
+ *
945
+ * @param {number} x the initial vector x
946
+ * @param {number} y the initial vector y
947
+ * @param {number} rad the radian vector angle
948
+ * @returns {{x: number, y: number}} the rotated vector
949
+ */
950
+ function rotateVector(x, y, rad) {
951
+ const X = x * Math.cos(rad) - y * Math.sin(rad);
952
+ const Y = x * Math.sin(rad) + y * Math.cos(rad);
953
+ return { x: X, y: Y };
954
+ }
955
+
956
+ /**
957
+ * Converts A (arc-to) segments to C (cubic-bezier-to).
958
+ *
959
+ * For more information of where this math came from visit:
960
+ * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
961
+ *
962
+ * @param {number} X1 the starting x position
963
+ * @param {number} Y1 the starting y position
964
+ * @param {number} RX x-radius of the arc
965
+ * @param {number} RY y-radius of the arc
966
+ * @param {number} angle x-axis-rotation of the arc
967
+ * @param {number} LAF large-arc-flag of the arc
968
+ * @param {number} SF sweep-flag of the arc
969
+ * @param {number} X2 the ending x position
970
+ * @param {number} Y2 the ending y position
971
+ * @param {number[] | null} recursive the parameters needed to split arc into 2 segments
972
+ * @return {any} the resulting cubic-bezier segment(s)
973
+ */
974
+ // export default function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
975
+ function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recursive) {
976
+ let x1 = X1; let y1 = Y1; let rx = RX; let ry = RY; let x2 = X2; let y2 = Y2;
977
+ // for more information of where this Math came from visit:
978
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
979
+ const d120 = (Math.PI * 120) / 180;
980
+
981
+ const rad = (Math.PI / 180) * (+angle || 0);
982
+ let res = [];
983
+ let xy;
984
+ let f1;
985
+ let f2;
986
+ let cx;
987
+ let cy;
988
+
989
+ if (!recursive) {
990
+ xy = rotateVector(x1, y1, -rad);
991
+ x1 = xy.x;
992
+ y1 = xy.y;
993
+ xy = rotateVector(x2, y2, -rad);
994
+ x2 = xy.x;
995
+ y2 = xy.y;
996
+
997
+ const x = (x1 - x2) / 2;
998
+ const y = (y1 - y2) / 2;
999
+ let h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
1000
+ if (h > 1) {
1001
+ h = Math.sqrt(h);
1002
+ rx *= h;
1003
+ ry *= h;
1004
+ }
1005
+ const rx2 = rx * rx;
1006
+ const ry2 = ry * ry;
1007
+
1008
+ const k = (LAF === SF ? -1 : 1)
1009
+ * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
1010
+ / (rx2 * y * y + ry2 * x * x)));
1011
+
1012
+ cx = ((k * rx * y) / ry) + ((x1 + x2) / 2);
1013
+ cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2);
1014
+ // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
1015
+ f1 = (Math.asin((((y1 - cy) / ry))) * (10 ** 9) >> 0) / (10 ** 9);
1016
+ // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
1017
+ f2 = (Math.asin((((y2 - cy) / ry))) * (10 ** 9) >> 0) / (10 ** 9);
1018
+
1019
+ f1 = x1 < cx ? Math.PI - f1 : f1;
1020
+ f2 = x2 < cx ? Math.PI - f2 : f2;
1021
+ if (f1 < 0) (f1 = Math.PI * 2 + f1);
1022
+ if (f2 < 0) (f2 = Math.PI * 2 + f2);
1023
+ if (SF && f1 > f2) {
1024
+ f1 -= Math.PI * 2;
1025
+ }
1026
+ if (!SF && f2 > f1) {
1027
+ f2 -= Math.PI * 2;
1028
+ }
1029
+ } else {
1030
+ [f1, f2, cx, cy] = recursive;
1031
+ }
1032
+ let df = f2 - f1;
1033
+ if (Math.abs(df) > d120) {
1034
+ const f2old = f2;
1035
+ const x2old = x2;
1036
+ const y2old = y2;
1037
+ f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
1038
+ x2 = cx + rx * Math.cos(f2);
1039
+ y2 = cy + ry * Math.sin(f2);
1040
+ res = arcToCubic(x2, y2, rx, ry, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
1041
+ }
1042
+ df = f2 - f1;
1043
+ const c1 = Math.cos(f1);
1044
+ const s1 = Math.sin(f1);
1045
+ const c2 = Math.cos(f2);
1046
+ const s2 = Math.sin(f2);
1047
+ const t = Math.tan(df / 4);
1048
+ const hx = (4 / 3) * rx * t;
1049
+ const hy = (4 / 3) * ry * t;
1050
+ const m1 = [x1, y1];
1051
+ const m2 = [x1 + hx * s1, y1 - hy * c1];
1052
+ const m3 = [x2 + hx * s2, y2 - hy * c2];
1053
+ const m4 = [x2, y2];
1054
+ m2[0] = 2 * m1[0] - m2[0];
1055
+ m2[1] = 2 * m1[1] - m2[1];
1056
+ if (recursive) {
1057
+ return [m2, m3, m4].concat(res);
699
1058
  }
700
-
701
- /**
702
- * Specifies a skew transformation along the `x-axis` by the given angle.
703
- * This matrix is not modified.
704
- *
705
- * @param {number} angle The angle amount in degrees to skew.
706
- * @return {CSSMatrix} The `CSSMatrix` result
707
- */
708
- skewX(angle) {
709
- return Multiply(this, SkewX(angle));
1059
+ res = [m2, m3, m4].concat(res).join().split(',');
1060
+ const newres = [];
1061
+ for (let i = 0, ii = res.length; i < ii; i += 1) {
1062
+ newres[i] = i % 2
1063
+ // @ts-ignore
1064
+ ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
710
1065
  }
1066
+ return newres;
1067
+ }
711
1068
 
712
- /**
713
- * Specifies a skew transformation along the `y-axis` by the given angle.
714
- * This matrix is not modified.
715
- *
716
- * @param {number} angle The angle amount in degrees to skew.
717
- * @return {CSSMatrix} The `CSSMatrix` result
718
- */
719
- skewY(angle) {
720
- return Multiply(this, SkewY(angle));
721
- }
1069
+ /**
1070
+ * Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
1071
+ *
1072
+ * @param {Number} x1 curve start x
1073
+ * @param {Number} y1 curve start y
1074
+ * @param {Number} qx control point x
1075
+ * @param {Number} qy control point y
1076
+ * @param {Number} x2 curve end x
1077
+ * @param {Number} y2 curve end y
1078
+ * @returns {Number[]} the cubic-bezier segment
1079
+ */
1080
+ function quadToCubic(x1, y1, qx, qy, x2, y2) {
1081
+ const r13 = 1 / 3;
1082
+ const r23 = 2 / 3;
1083
+ return [
1084
+ r13 * x1 + r23 * qx, // cpx1
1085
+ r13 * y1 + r23 * qy, // cpy1
1086
+ r13 * x2 + r23 * qx, // cpx2
1087
+ r13 * y2 + r23 * qy, // cpy2
1088
+ x2, y2, // x,y
1089
+ ];
1090
+ }
722
1091
 
723
- /**
724
- * Transforms the specified point using the matrix, returning a new
725
- * Tuple *Object* comprising of the transformed point.
726
- * Neither the matrix nor the original point are altered.
727
- *
728
- * The method is equivalent with `transformPoint()` method
729
- * of the `DOMMatrix` constructor.
730
- *
731
- * JavaScript implementation by thednp
732
- *
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
735
- */
736
- transformPoint(v) {
737
- const M = this;
738
- let m = Translate(v.x, v.y, v.z);
1092
+ /**
1093
+ * Returns the {x,y} coordinates of a point at a
1094
+ * given length of a cubic-bezier segment.
1095
+ *
1096
+ * @param {number} p1x the starting point X
1097
+ * @param {number} p1y the starting point Y
1098
+ * @param {number} c1x the first control point X
1099
+ * @param {number} c1y the first control point Y
1100
+ * @param {number} c2x the second control point X
1101
+ * @param {number} c2y the second control point Y
1102
+ * @param {number} p2x the ending point X
1103
+ * @param {number} p2y the ending point Y
1104
+ * @param {number} t a [0-1] ratio
1105
+ * @returns {{x: number, y: number}} the requested {x,y} coordinates
1106
+ */
1107
+ function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
1108
+ const t1 = 1 - t;
1109
+ return {
1110
+ x: (t1 ** 3) * p1x
1111
+ + t1 * t1 * 3 * t * c1x
1112
+ + t1 * 3 * t * t * c2x
1113
+ + (t ** 3) * p2x,
1114
+ y: (t1 ** 3) * p1y
1115
+ + t1 * t1 * 3 * t * c1y
1116
+ + t1 * 3 * t * t * c2y
1117
+ + (t ** 3) * p2y,
1118
+ };
1119
+ }
739
1120
 
740
- m.m44 = v.w || 1;
741
- m = M.multiply(m);
1121
+ /**
1122
+ * Returns the coordinates of a specified distance
1123
+ * ratio between two points.
1124
+ *
1125
+ * @param {Number[]} a the first point coordinates
1126
+ * @param {Number[]} b the second point coordinates
1127
+ * @param {Number} t the ratio
1128
+ * @returns {Number[]} the midpoint coordinates
1129
+ */
1130
+ function midPoint(a, b, t) {
1131
+ const [ax, ay] = a; const [bx, by] = b;
1132
+ return [ax + (bx - ax) * t, ay + (by - ay) * t];
1133
+ }
742
1134
 
743
- return {
744
- x: m.m41,
745
- y: m.m42,
746
- z: m.m43,
747
- w: m.m44,
748
- };
1135
+ /**
1136
+ * Converts an L (line-to) segment to C (cubic-bezier).
1137
+ *
1138
+ * @param {number} x1 line start x
1139
+ * @param {number} y1 line start y
1140
+ * @param {number} x2 line end x
1141
+ * @param {number} y2 line end y
1142
+ * @returns {number[]} the cubic-bezier segment
1143
+ */
1144
+ function lineToCubic(x1, y1, x2, y2) {
1145
+ const t = 0.5;
1146
+ const p0 = [x1, y1];
1147
+ const p1 = [x2, y2];
1148
+ const p2 = midPoint(p0, p1, t);
1149
+ const p3 = midPoint(p1, p2, t);
1150
+ const p4 = midPoint(p2, p3, t);
1151
+ const p5 = midPoint(p3, p4, t);
1152
+ const p6 = midPoint(p4, p5, t);
1153
+ // @ts-ignore
1154
+ const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
1155
+ // @ts-ignore
1156
+ const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
1157
+
1158
+ return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
1159
+ }
1160
+
1161
+ /**
1162
+ * Converts any segment to C (cubic-bezier).
1163
+ *
1164
+ * @param {SVGPC.pathSegment} segment the source segment
1165
+ * @param {SVGPC.parserParams} params the source segment parameters
1166
+ * @returns {SVGPC.pathSegment} the cubic-bezier segment
1167
+ */
1168
+ function segmentToCubic(segment, params) {
1169
+ if ('TQ'.indexOf(segment[0]) < 0) {
1170
+ params.qx = null;
1171
+ params.qy = null;
749
1172
  }
750
1173
 
751
- /**
752
- * Transforms the specified vector using the matrix, returning a new
753
- * {x,y,z,w} Tuple *Object* comprising the transformed vector.
754
- * Neither the matrix nor the original vector are altered.
755
- *
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
758
- */
759
- transform(t) {
760
- const m = this;
761
- const x = m.m11 * t.x + m.m12 * t.y + m.m13 * t.z + m.m14 * t.w;
762
- const y = m.m21 * t.x + m.m22 * t.y + m.m23 * t.z + m.m24 * t.w;
763
- const z = m.m31 * t.x + m.m32 * t.y + m.m33 * t.z + m.m34 * t.w;
764
- const w = m.m41 * t.x + m.m42 * t.y + m.m43 * t.z + m.m44 * t.w;
1174
+ const [s1, s2] = segment.slice(1);
765
1175
 
766
- return {
767
- x: x / w,
768
- y: y / w,
769
- z: z / w,
770
- w,
771
- };
1176
+ switch (segment[0]) {
1177
+ case 'M':
1178
+ params.x = +s1;
1179
+ params.y = +s2;
1180
+ return segment;
1181
+ case 'A':
1182
+ // @ts-ignore
1183
+ return ['C'].concat(arcToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
1184
+ case 'Q':
1185
+ params.qx = +s1;
1186
+ params.qy = +s2;
1187
+ // @ts-ignore
1188
+ return ['C'].concat(quadToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
1189
+ case 'L':
1190
+ // @ts-ignore
1191
+ return ['C'].concat(lineToCubic(params.x1, params.y1, segment[1], segment[2]));
1192
+ case 'Z':
1193
+ // @ts-ignore
1194
+ return ['C'].concat(lineToCubic(params.x1, params.y1, params.x, params.y));
772
1195
  }
1196
+ return segment;
773
1197
  }
774
1198
 
775
- // Add Transform Functions to CSSMatrix object
776
- CSSMatrix.Translate = Translate;
777
- CSSMatrix.Rotate = Rotate;
778
- CSSMatrix.RotateAxisAngle = RotateAxisAngle;
779
- CSSMatrix.Scale = Scale;
780
- CSSMatrix.SkewX = SkewX;
781
- CSSMatrix.SkewY = SkewY;
782
- CSSMatrix.Multiply = Multiply;
783
- CSSMatrix.fromArray = fromArray;
784
- CSSMatrix.fromMatrix = fromMatrix;
785
- CSSMatrix.fromString = fromString;
786
-
787
- const CSS3Matrix = typeof DOMMatrix !== 'undefined' ? DOMMatrix : CSSMatrix;
1199
+ /**
1200
+ * Splits an extended A (arc-to) segment into two cubic-bezier segments.
1201
+ *
1202
+ * @param {SVGPC.pathArray} path the `pathArray` this segment belongs to
1203
+ * @param {string[]} allPathCommands all previous path commands
1204
+ * @param {Number} i the index of the segment
1205
+ */
788
1206
 
789
- function fixArc(pathArray, allPathCommands, i) {
790
- if (pathArray[i].length > 7) {
791
- pathArray[i].shift();
792
- const pi = pathArray[i];
1207
+ function fixArc(path, allPathCommands, i) {
1208
+ if (path[i].length > 7) {
1209
+ path[i].shift();
1210
+ const segment = path[i];
793
1211
  let ni = i; // ESLint
794
- while (pi.length) {
1212
+ while (segment.length) {
795
1213
  // if created multiple C:s, their original seg is saved
796
1214
  allPathCommands[i] = 'A';
797
- pathArray.splice(ni += 1, 0, ['C'].concat(pi.splice(0, 6)));
1215
+ // path.splice(i++, 0, ['C'].concat(segment.splice(0, 6)));
1216
+ // @ts-ignore
1217
+ path.splice(ni += 1, 0, ['C'].concat(segment.splice(0, 6)));
798
1218
  }
799
- pathArray.splice(i, 1);
1219
+ path.splice(i, 1);
800
1220
  }
801
1221
  }
802
1222
 
803
- var paramsCount = {
804
- a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
805
- };
806
-
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);
811
- });
812
- }
1223
+ var version$1 = "0.0.16alpha4";
813
1224
 
814
- function isCurveArray(pathArray) {
815
- return isPathArray(pathArray) && pathArray.slice(1).every((seg) => seg[0] === 'C');
816
- }
1225
+ // @ts-ignore
817
1226
 
818
- function clonePath(pathArray) {
819
- return pathArray.map((x) => {
820
- if (Array.isArray(x)) {
821
- return clonePath(x);
822
- }
823
- return !Number.isNaN(+x) ? +x : x;
824
- });
825
- }
1227
+ /**
1228
+ * A global namespace for library version.
1229
+ * @type {string}
1230
+ */
1231
+ const DMVersion = version$1;
826
1232
 
827
- function finalizeSegment(state) {
828
- let pathCommand = state.pathValue[state.segmentStart];
829
- let pathComLK = pathCommand.toLowerCase();
830
- let params = state.data;
1233
+ // DOMMatrix Static methods
1234
+ // * `fromFloat64Array` and `fromFloat32Array` methods are not supported;
1235
+ // * `fromArray` a more simple implementation, should also accept float[32/64]Array;
1236
+ // * `fromMatrix` load values from another CSSMatrix/DOMMatrix instance;
1237
+ // * `fromString` parses and loads values from any valid CSS transform string.
831
1238
 
832
- // Process duplicated commands (without comand name)
833
- if (pathComLK === 'm' && params.length > 2) {
834
- state.segments.push([pathCommand, params[0], params[1]]);
835
- params = params.slice(2);
836
- pathComLK = 'l';
837
- pathCommand = (pathCommand === 'm') ? 'l' : 'L';
1239
+ /**
1240
+ * Creates a new mutable `CSSMatrix` object given an array of floating point values.
1241
+ *
1242
+ * This static method invalidates arrays that contain non-number elements.
1243
+ *
1244
+ * If the array has six values, the result is a 2D matrix; if the array has 16 values,
1245
+ * the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
1246
+ *
1247
+ * @param {number[]} array an `Array` to feed values from.
1248
+ * @return {CSSMatrix} the resulted matrix.
1249
+ */
1250
+ function fromArray(array) {
1251
+ if (!array.every((n) => !Number.isNaN(n))) {
1252
+ throw TypeError(`CSSMatrix: "${array}" must only have numbers.`);
838
1253
  }
1254
+ const m = new CSSMatrix();
1255
+ const a = Array.from(array);
839
1256
 
840
- if (pathComLK === 'r') {
841
- state.segments.push([pathCommand].concat(params));
842
- } else {
843
- while (params.length >= paramsCount[pathComLK]) {
844
- state.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
845
- if (!paramsCount[pathComLK]) {
846
- break;
847
- }
848
- }
849
- }
850
- }
1257
+ if (a.length === 16) {
1258
+ const [m11, m12, m13, m14,
1259
+ m21, m22, m23, m24,
1260
+ m31, m32, m33, m34,
1261
+ m41, m42, m43, m44] = a;
851
1262
 
852
- const invalidPathValue = 'Invalid path value';
1263
+ m.m11 = m11;
1264
+ m.a = m11;
853
1265
 
854
- function scanFlag(state) {
855
- const ch = state.pathValue.charCodeAt(state.index);
1266
+ m.m21 = m21;
1267
+ m.c = m21;
856
1268
 
857
- if (ch === 0x30/* 0 */) {
858
- state.param = 0;
859
- state.index += 1;
860
- return;
861
- }
1269
+ m.m31 = m31;
862
1270
 
863
- if (ch === 0x31/* 1 */) {
864
- state.param = 1;
865
- state.index += 1;
866
- return;
867
- }
1271
+ m.m41 = m41;
1272
+ m.e = m41;
868
1273
 
869
- // state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')';
870
- state.err = `${invalidPathValue}: invalid Arc flag ${ch}`;
871
- }
1274
+ m.m12 = m12;
1275
+ m.b = m12;
872
1276
 
873
- function isDigit(code) {
874
- return (code >= 48 && code <= 57); // 0..9
875
- }
1277
+ m.m22 = m22;
1278
+ m.d = m22;
876
1279
 
877
- function scanParam(state) {
878
- const start = state.index;
879
- const { max } = state;
880
- let index = start;
881
- let zeroFirst = false;
882
- let hasCeiling = false;
883
- let hasDecimal = false;
884
- let hasDot = false;
885
- let ch;
1280
+ m.m32 = m32;
886
1281
 
887
- if (index >= max) {
888
- // state.err = 'SvgPath: missed param (at pos ' + index + ')';
889
- state.err = `${invalidPathValue}: missing param ${state.pathValue[index]}`;
890
- return;
891
- }
892
- ch = state.pathValue.charCodeAt(index);
1282
+ m.m42 = m42;
1283
+ m.f = m42;
893
1284
 
894
- if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
895
- index += 1;
896
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
897
- }
1285
+ m.m13 = m13;
1286
+ m.m23 = m23;
1287
+ m.m33 = m33;
1288
+ m.m43 = m43;
1289
+ m.m14 = m14;
1290
+ m.m24 = m24;
1291
+ m.m34 = m34;
1292
+ m.m44 = m44;
1293
+ } else if (a.length === 6) {
1294
+ const [m11, m12, m21, m22, m41, m42] = a;
1295
+
1296
+ m.m11 = m11;
1297
+ m.a = m11;
1298
+
1299
+ m.m12 = m12;
1300
+ m.b = m12;
898
1301
 
899
- // This logic is shamelessly borrowed from Esprima
900
- // https://github.com/ariya/esprimas
901
- 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`;
904
- return;
905
- }
1302
+ m.m21 = m21;
1303
+ m.c = m21;
906
1304
 
907
- if (ch !== 0x2E/* . */) {
908
- zeroFirst = (ch === 0x30/* 0 */);
909
- index += 1;
1305
+ m.m22 = m22;
1306
+ m.d = m22;
910
1307
 
911
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1308
+ m.m41 = m41;
1309
+ m.e = m41;
912
1310
 
913
- if (zeroFirst && index < max) {
914
- // decimal number starts with '0' such as '09' is illegal.
915
- if (ch && isDigit(ch)) {
916
- // state.err = 'SvgPath: numbers started with `0` such as `09`
917
- // are illegal (at pos ' + start + ')';
918
- state.err = `${invalidPathValue}: ${state.pathValue[start]} illegal number`;
919
- return;
920
- }
921
- }
1311
+ m.m42 = m42;
1312
+ m.f = m42;
1313
+ } else {
1314
+ throw new TypeError('CSSMatrix: expecting an Array of 6/16 values.');
1315
+ }
1316
+ return m;
1317
+ }
922
1318
 
923
- while (index < max && isDigit(state.pathValue.charCodeAt(index))) {
924
- index += 1;
925
- hasCeiling = true;
926
- }
927
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1319
+ /**
1320
+ * Creates a new mutable `CSSMatrix` instance given an existing matrix or a
1321
+ * `DOMMatrix` instance which provides the values for its properties.
1322
+ *
1323
+ * @param {CSSMatrix | DOMMatrix | DMNS.jsonMatrix} m the source matrix to feed values from.
1324
+ * @return {CSSMatrix} the resulted matrix.
1325
+ */
1326
+ function fromMatrix(m) {
1327
+ const keys = [
1328
+ 'm11', 'm12', 'm13', 'm14',
1329
+ 'm21', 'm22', 'm23', 'm24',
1330
+ 'm31', 'm32', 'm33', 'm34',
1331
+ 'm41', 'm42', 'm43', 'm44'];
1332
+ if ([CSSMatrix, DOMMatrix].some((x) => m instanceof x)
1333
+ || (typeof m === 'object' && keys.every((k) => k in m))) {
1334
+ return fromArray(
1335
+ [m.m11, m.m12, m.m13, m.m14,
1336
+ m.m21, m.m22, m.m23, m.m24,
1337
+ m.m31, m.m32, m.m33, m.m34,
1338
+ m.m41, m.m42, m.m43, m.m44],
1339
+ );
928
1340
  }
1341
+ throw TypeError(`CSSMatrix: "${m}" is not a DOMMatrix / CSSMatrix compatible object.`);
1342
+ }
929
1343
 
930
- if (ch === 0x2E/* . */) {
931
- hasDot = true;
932
- index += 1;
933
- while (isDigit(state.pathValue.charCodeAt(index))) {
934
- index += 1;
935
- hasDecimal = true;
936
- }
937
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
1344
+ /**
1345
+ * Creates a new mutable `CSSMatrix` instance given any valid CSS transform string.
1346
+ *
1347
+ * * `matrix(a, b, c, d, e, f)` - valid matrix() transform function
1348
+ * * `matrix3d(m11, m12, m13, ...m44)` - valid matrix3d() transform function
1349
+ * * `translate(tx, ty) rotateX(alpha)` - any valid transform function(s)
1350
+ *
1351
+ * @copyright thednp © 2021
1352
+ *
1353
+ * @param {string} source valid CSS transform string syntax.
1354
+ * @return {CSSMatrix} the resulted matrix.
1355
+ */
1356
+ function fromString(source) {
1357
+ if (typeof source !== 'string') {
1358
+ throw TypeError(`CSSMatrix: "${source}" is not a string.`);
938
1359
  }
1360
+ const str = String(source).replace(/\s/g, '');
1361
+ let m = new CSSMatrix();
1362
+ let is2D = true;
1363
+ const tramsformObject = str.split(')').filter((f) => f).map((fn) => {
1364
+ const [prop, value] = fn.split('(');
1365
+ const components = value.split(',')
1366
+ .map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
1367
+ const [x, y, z, a] = components;
939
1368
 
940
- if (ch === 0x65/* e */ || ch === 0x45/* E */) {
941
- if (hasDot && !hasCeiling && !hasDecimal) {
942
- // state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
943
- state.err = `${invalidPathValue}: ${state.pathValue[index]} invalid float exponent`;
944
- return;
1369
+ // don't add perspective if is2D
1370
+ if (is2D && (prop === 'matrix3d' // only modify is2D once
1371
+ || (prop === 'rotate3d' && [x, y].every((n) => !Number.isNaN(+n) && n !== 0) && a)
1372
+ || (['rotateX', 'rotateY'].includes(prop) && x)
1373
+ || (prop === 'translate3d' && [x, y, z].every((n) => !Number.isNaN(+n)) && z)
1374
+ || (prop === 'scale3d' && [x, y, z].every((n) => !Number.isNaN(+n) && n !== x))
1375
+ )) {
1376
+ is2D = false;
945
1377
  }
1378
+ return { prop, components };
1379
+ });
946
1380
 
947
- index += 1;
1381
+ tramsformObject.forEach((tf) => {
1382
+ const { prop, components } = tf;
1383
+ const [x, y, z, a] = components;
1384
+ const xyz = [x, y, z];
1385
+ const xyza = [x, y, z, a];
948
1386
 
949
- ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
950
- if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
951
- index += 1;
952
- }
953
- if (index < max && isDigit(state.pathValue.charCodeAt(index))) {
954
- while (index < max && isDigit(state.pathValue.charCodeAt(index))) {
955
- index += 1;
1387
+ if (prop === 'perspective' && !is2D) {
1388
+ m.m34 = -1 / x;
1389
+ } else if (prop.includes('matrix')) {
1390
+ const values = components.map((n) => (Math.abs(n) < 1e-6 ? 0 : n));
1391
+ if ([6, 16].includes(values.length)) {
1392
+ m = m.multiply(fromArray(values));
1393
+ }
1394
+ } else if (['translate', 'translate3d'].some((p) => prop === p) && x) {
1395
+ m = m.translate(x, y || 0, z || 0);
1396
+ } else if (prop === 'rotate3d' && xyza.every((n) => !Number.isNaN(+n)) && a) {
1397
+ m = m.rotateAxisAngle(x, y, z, a);
1398
+ } else if (prop === 'scale3d' && xyz.every((n) => !Number.isNaN(+n)) && xyz.some((n) => n !== 1)) {
1399
+ m = m.scale(x, y, z);
1400
+ } else if (prop === 'rotate' && x) {
1401
+ m = m.rotate(0, 0, x);
1402
+ } else if (prop === 'scale' && !Number.isNaN(x) && x !== 1) {
1403
+ const nosy = Number.isNaN(+y);
1404
+ const sy = nosy ? x : y;
1405
+ m = m.scale(x, sy, 1);
1406
+ } else if (prop === 'skew' && (x || y)) {
1407
+ m = x ? m.skewX(x) : m;
1408
+ m = y ? m.skewY(y) : m;
1409
+ } else if (/[XYZ]/.test(prop) && x) {
1410
+ if (prop.includes('skew')) {
1411
+ // @ts-ignore unfortunately
1412
+ m = m[prop](x);
1413
+ } else {
1414
+ const fn = prop.replace(/[XYZ]/, '');
1415
+ const axis = prop.replace(fn, '');
1416
+ const idx = ['X', 'Y', 'Z'].indexOf(axis);
1417
+ const axeValues = [
1418
+ idx === 0 ? x : 0,
1419
+ idx === 1 ? x : 0,
1420
+ idx === 2 ? x : 0];
1421
+ // @ts-ignore unfortunately
1422
+ m = m[fn](...axeValues);
956
1423
  }
957
- } else {
958
- // state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
959
- state.err = `${invalidPathValue}: ${state.pathValue[index]} invalid float exponent`;
960
- return;
961
1424
  }
962
- }
1425
+ });
963
1426
 
964
- state.index = index;
965
- state.param = +state.pathValue.slice(start, index);
1427
+ return m;
966
1428
  }
967
1429
 
968
- function isSpace(ch) {
969
- const specialSpaces = [
970
- 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
971
- 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
972
- return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
973
- // White spaces
974
- || (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
975
- || (ch >= 0x1680 && specialSpaces.indexOf(ch) >= 0);
976
- }
1430
+ // Transform Functions
1431
+ // https://www.w3.org/TR/css-transforms-1/#transform-functions
977
1432
 
978
- function skipSpaces(state) {
979
- while (state.index < state.max && isSpace(state.pathValue.charCodeAt(state.index))) {
980
- state.index += 1;
981
- }
1433
+ /**
1434
+ * Creates a new `CSSMatrix` for the translation matrix and returns it.
1435
+ * This method is equivalent to the CSS `translate3d()` function.
1436
+ *
1437
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
1438
+ *
1439
+ * @param {number} x the `x-axis` position.
1440
+ * @param {number} y the `y-axis` position.
1441
+ * @param {number} z the `z-axis` position.
1442
+ * @return {CSSMatrix} the resulted matrix.
1443
+ */
1444
+ function Translate(x, y, z) {
1445
+ const m = new CSSMatrix();
1446
+ m.m41 = x;
1447
+ m.e = x;
1448
+ m.m42 = y;
1449
+ m.f = y;
1450
+ m.m43 = z;
1451
+ return m;
982
1452
  }
983
1453
 
984
- function isPathCommand(code) {
985
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy
986
- switch (code | 0x20) {
987
- case 0x6D/* m */:
988
- case 0x7A/* z */:
989
- case 0x6C/* l */:
990
- case 0x68/* h */:
991
- case 0x76/* v */:
992
- case 0x63/* c */:
993
- case 0x73/* s */:
994
- case 0x71/* q */:
995
- case 0x74/* t */:
996
- case 0x61/* a */:
997
- case 0x72/* r */:
998
- return true;
999
- default:
1000
- return false;
1001
- }
1002
- }
1454
+ /**
1455
+ * Creates a new `CSSMatrix` for the rotation matrix and returns it.
1456
+ *
1457
+ * http://en.wikipedia.org/wiki/Rotation_matrix
1458
+ *
1459
+ * @param {number} rx the `x-axis` rotation.
1460
+ * @param {number} ry the `y-axis` rotation.
1461
+ * @param {number} rz the `z-axis` rotation.
1462
+ * @return {CSSMatrix} the resulted matrix.
1463
+ */
1464
+ function Rotate(rx, ry, rz) {
1465
+ const m = new CSSMatrix();
1466
+ const degToRad = Math.PI / 180;
1467
+ const radX = rx * degToRad;
1468
+ const radY = ry * degToRad;
1469
+ const radZ = rz * degToRad;
1003
1470
 
1004
- function isDigitStart(code) {
1005
- return (code >= 48 && code <= 57) /* 0..9 */
1006
- || code === 0x2B /* + */
1007
- || code === 0x2D /* - */
1008
- || code === 0x2E; /* . */
1009
- }
1471
+ // minus sin() because of right-handed system
1472
+ const cosx = Math.cos(radX);
1473
+ const sinx = -Math.sin(radX);
1474
+ const cosy = Math.cos(radY);
1475
+ const siny = -Math.sin(radY);
1476
+ const cosz = Math.cos(radZ);
1477
+ const sinz = -Math.sin(radZ);
1010
1478
 
1011
- function isArcCommand(code) {
1012
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy
1013
- return (code | 0x20) === 0x61;
1014
- }
1479
+ const m11 = cosy * cosz;
1480
+ const m12 = -cosy * sinz;
1015
1481
 
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()];
1482
+ m.m11 = m11;
1483
+ m.a = m11;
1020
1484
 
1021
- state.segmentStart = state.index;
1485
+ m.m12 = m12;
1486
+ m.b = m12;
1022
1487
 
1023
- if (!isPathCommand(cmdCode)) {
1024
- state.err = `${invalidPathValue}: ${state.pathValue[state.index]} not a path command`;
1025
- return;
1488
+ m.m13 = siny;
1489
+
1490
+ const m21 = sinx * siny * cosz + cosx * sinz;
1491
+ m.m21 = m21;
1492
+ m.c = m21;
1493
+
1494
+ const m22 = cosx * cosz - sinx * siny * sinz;
1495
+ m.m22 = m22;
1496
+ m.d = m22;
1497
+
1498
+ m.m23 = -sinx * cosy;
1499
+
1500
+ m.m31 = sinx * sinz - cosx * siny * cosz;
1501
+ m.m32 = sinx * cosz + cosx * siny * sinz;
1502
+ m.m33 = cosx * cosy;
1503
+
1504
+ return m;
1505
+ }
1506
+
1507
+ /**
1508
+ * Creates a new `CSSMatrix` for the rotation matrix and returns it.
1509
+ * This method is equivalent to the CSS `rotate3d()` function.
1510
+ *
1511
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
1512
+ *
1513
+ * @param {number} x the `x-axis` vector length.
1514
+ * @param {number} y the `y-axis` vector length.
1515
+ * @param {number} z the `z-axis` vector length.
1516
+ * @param {number} alpha the value in degrees of the rotation.
1517
+ * @return {CSSMatrix} the resulted matrix.
1518
+ */
1519
+ function RotateAxisAngle(x, y, z, alpha) {
1520
+ const m = new CSSMatrix();
1521
+ const angle = alpha * (Math.PI / 360);
1522
+ const sinA = Math.sin(angle);
1523
+ const cosA = Math.cos(angle);
1524
+ const sinA2 = sinA * sinA;
1525
+ const length = Math.sqrt(x * x + y * y + z * z);
1526
+ let X = x;
1527
+ let Y = y;
1528
+ let Z = z;
1529
+
1530
+ if (length === 0) {
1531
+ // bad vector length, use something reasonable
1532
+ X = 0;
1533
+ Y = 0;
1534
+ Z = 1;
1535
+ } else {
1536
+ X /= length;
1537
+ Y /= length;
1538
+ Z /= length;
1026
1539
  }
1027
1540
 
1028
- state.index += 1;
1029
- skipSpaces(state);
1541
+ const x2 = X * X;
1542
+ const y2 = Y * Y;
1543
+ const z2 = Z * Z;
1030
1544
 
1031
- state.data = [];
1545
+ const m11 = 1 - 2 * (y2 + z2) * sinA2;
1546
+ m.m11 = m11;
1547
+ m.a = m11;
1032
1548
 
1033
- if (!reqParams) {
1034
- // Z
1035
- finalizeSegment(state);
1036
- return;
1037
- }
1549
+ const m12 = 2 * (X * Y * sinA2 + Z * sinA * cosA);
1550
+ m.m12 = m12;
1551
+ m.b = m12;
1038
1552
 
1039
- for (;;) {
1040
- for (let i = reqParams; i > 0; i -= 1) {
1041
- if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(state);
1042
- else scanParam(state);
1553
+ m.m13 = 2 * (X * Z * sinA2 - Y * sinA * cosA);
1043
1554
 
1044
- if (state.err.length) {
1045
- return;
1046
- }
1047
- state.data.push(state.param);
1555
+ const m21 = 2 * (Y * X * sinA2 - Z * sinA * cosA);
1556
+ m.m21 = m21;
1557
+ m.c = m21;
1048
1558
 
1049
- skipSpaces(state);
1559
+ const m22 = 1 - 2 * (z2 + x2) * sinA2;
1560
+ m.m22 = m22;
1561
+ m.d = m22;
1050
1562
 
1051
- // after ',' param is mandatory
1052
- if (state.index < max && state.pathValue.charCodeAt(state.index) === 0x2C/* , */) {
1053
- state.index += 1;
1054
- skipSpaces(state);
1055
- }
1056
- }
1563
+ m.m23 = 2 * (Y * Z * sinA2 + X * sinA * cosA);
1564
+ m.m31 = 2 * (Z * X * sinA2 + Y * sinA * cosA);
1565
+ m.m32 = 2 * (Z * Y * sinA2 - X * sinA * cosA);
1566
+ m.m33 = 1 - 2 * (x2 + y2) * sinA2;
1057
1567
 
1058
- if (state.index >= state.max) {
1059
- break;
1060
- }
1568
+ return m;
1569
+ }
1061
1570
 
1062
- // Stop on next segment
1063
- if (!isDigitStart(state.pathValue.charCodeAt(state.index))) {
1064
- break;
1065
- }
1066
- }
1571
+ /**
1572
+ * Creates a new `CSSMatrix` for the scale matrix and returns it.
1573
+ * This method is equivalent to the CSS `scale3d()` function.
1574
+ *
1575
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
1576
+ *
1577
+ * @param {number} x the `x-axis` scale.
1578
+ * @param {number} y the `y-axis` scale.
1579
+ * @param {number} z the `z-axis` scale.
1580
+ * @return {CSSMatrix} the resulted matrix.
1581
+ */
1582
+ function Scale(x, y, z) {
1583
+ const m = new CSSMatrix();
1584
+ m.m11 = x;
1585
+ m.a = x;
1067
1586
 
1068
- finalizeSegment(state);
1069
- }
1587
+ m.m22 = y;
1588
+ m.d = y;
1070
1589
 
1071
- function SVGPathArray(pathString) {
1072
- this.segments = [];
1073
- this.pathValue = pathString;
1074
- this.max = pathString.length;
1075
- this.index = 0;
1076
- this.param = 0.0;
1077
- this.segmentStart = 0;
1078
- this.data = [];
1079
- this.err = '';
1590
+ m.m33 = z;
1591
+ return m;
1080
1592
  }
1081
1593
 
1082
- // Returns array of segments:
1083
- function parsePathString(pathString) {
1084
- if (isPathArray(pathString)) {
1085
- return clonePath(pathString);
1086
- }
1594
+ /**
1595
+ * Creates a new `CSSMatrix` for the shear of the `x-axis` rotation matrix and
1596
+ * returns it. This method is equivalent to the CSS `skewX()` function.
1597
+ *
1598
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
1599
+ *
1600
+ * @param {number} angle the angle in degrees.
1601
+ * @return {CSSMatrix} the resulted matrix.
1602
+ */
1603
+ function SkewX(angle) {
1604
+ const m = new CSSMatrix();
1605
+ const radA = (angle * Math.PI) / 180;
1606
+ const t = Math.tan(radA);
1607
+ m.m21 = t;
1608
+ m.c = t;
1609
+ return m;
1610
+ }
1087
1611
 
1088
- const state = new SVGPathArray(pathString);
1612
+ /**
1613
+ * Creates a new `CSSMatrix` for the shear of the `y-axis` rotation matrix and
1614
+ * returns it. This method is equivalent to the CSS `skewY()` function.
1615
+ *
1616
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
1617
+ *
1618
+ * @param {number} angle the angle in degrees.
1619
+ * @return {CSSMatrix} the resulted matrix.
1620
+ */
1621
+ function SkewY(angle) {
1622
+ const m = new CSSMatrix();
1623
+ const radA = (angle * Math.PI) / 180;
1624
+ const t = Math.tan(radA);
1625
+ m.m12 = t;
1626
+ m.b = t;
1627
+ return m;
1628
+ }
1089
1629
 
1090
- skipSpaces(state);
1630
+ /**
1631
+ * Creates a new `CSSMatrix` resulted from the multiplication of two matrixes
1632
+ * and returns it. Both matrixes are not changed.
1633
+ *
1634
+ * @param {CSSMatrix} m1 the first matrix.
1635
+ * @param {CSSMatrix} m2 the second matrix.
1636
+ * @return {CSSMatrix} the resulted matrix.
1637
+ */
1638
+ function Multiply(m1, m2) {
1639
+ const m11 = m2.m11 * m1.m11 + m2.m12 * m1.m21 + m2.m13 * m1.m31 + m2.m14 * m1.m41;
1640
+ const m12 = m2.m11 * m1.m12 + m2.m12 * m1.m22 + m2.m13 * m1.m32 + m2.m14 * m1.m42;
1641
+ const m13 = m2.m11 * m1.m13 + m2.m12 * m1.m23 + m2.m13 * m1.m33 + m2.m14 * m1.m43;
1642
+ const m14 = m2.m11 * m1.m14 + m2.m12 * m1.m24 + m2.m13 * m1.m34 + m2.m14 * m1.m44;
1091
1643
 
1092
- while (state.index < state.max && !state.err.length) {
1093
- scanSegment(state);
1094
- }
1644
+ const m21 = m2.m21 * m1.m11 + m2.m22 * m1.m21 + m2.m23 * m1.m31 + m2.m24 * m1.m41;
1645
+ const m22 = m2.m21 * m1.m12 + m2.m22 * m1.m22 + m2.m23 * m1.m32 + m2.m24 * m1.m42;
1646
+ const m23 = m2.m21 * m1.m13 + m2.m22 * m1.m23 + m2.m23 * m1.m33 + m2.m24 * m1.m43;
1647
+ const m24 = m2.m21 * m1.m14 + m2.m22 * m1.m24 + m2.m23 * m1.m34 + m2.m24 * m1.m44;
1095
1648
 
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 = [];
1103
- } else {
1104
- state.segments[0][0] = 'M';
1105
- }
1106
- }
1649
+ const m31 = m2.m31 * m1.m11 + m2.m32 * m1.m21 + m2.m33 * m1.m31 + m2.m34 * m1.m41;
1650
+ const m32 = m2.m31 * m1.m12 + m2.m32 * m1.m22 + m2.m33 * m1.m32 + m2.m34 * m1.m42;
1651
+ const m33 = m2.m31 * m1.m13 + m2.m32 * m1.m23 + m2.m33 * m1.m33 + m2.m34 * m1.m43;
1652
+ const m34 = m2.m31 * m1.m14 + m2.m32 * m1.m24 + m2.m33 * m1.m34 + m2.m34 * m1.m44;
1107
1653
 
1108
- return state.segments;
1109
- }
1654
+ const m41 = m2.m41 * m1.m11 + m2.m42 * m1.m21 + m2.m43 * m1.m31 + m2.m44 * m1.m41;
1655
+ const m42 = m2.m41 * m1.m12 + m2.m42 * m1.m22 + m2.m43 * m1.m32 + m2.m44 * m1.m42;
1656
+ const m43 = m2.m41 * m1.m13 + m2.m42 * m1.m23 + m2.m43 * m1.m33 + m2.m44 * m1.m43;
1657
+ const m44 = m2.m41 * m1.m14 + m2.m42 * m1.m24 + m2.m43 * m1.m34 + m2.m44 * m1.m44;
1110
1658
 
1111
- function isAbsoluteArray(pathInput) {
1112
- return isPathArray(pathInput) && pathInput.every((x) => x[0] === x[0].toUpperCase());
1659
+ return fromArray(
1660
+ [m11, m12, m13, m14,
1661
+ m21, m22, m23, m24,
1662
+ m31, m32, m33, m34,
1663
+ m41, m42, m43, m44],
1664
+ );
1113
1665
  }
1114
1666
 
1115
- function pathToAbsolute(pathInput) {
1116
- if (isAbsoluteArray(pathInput)) {
1117
- return clonePath(pathInput);
1118
- }
1119
-
1120
- const pathArray = parsePathString(pathInput);
1121
- const ii = pathArray.length;
1122
- const resultArray = [];
1123
- let x = 0;
1124
- let y = 0;
1125
- let mx = 0;
1126
- let my = 0;
1127
- let start = 0;
1128
-
1129
- if (pathArray[0][0] === 'M') {
1130
- x = +pathArray[0][1];
1131
- y = +pathArray[0][2];
1132
- mx = x;
1133
- my = y;
1134
- start += 1;
1135
- resultArray.push(['M', x, y]);
1136
- }
1667
+ /**
1668
+ * Creates and returns a new `DOMMatrix` compatible *Object*
1669
+ * with equivalent instance methods.
1670
+ *
1671
+ * https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
1672
+ * https://github.com/thednp/DOMMatrix/
1673
+ */
1137
1674
 
1138
- for (let i = start; i < ii; i += 1) {
1139
- const segment = pathArray[i];
1140
- const [pathCommand] = segment;
1141
- const absCommand = pathCommand.toUpperCase();
1142
- const absoluteSegment = [];
1143
- let newSeg = [];
1144
- resultArray.push(absoluteSegment);
1675
+ class CSSMatrix {
1676
+ /**
1677
+ * @constructor
1678
+ * @param {any} args accepts all parameter configurations:
1679
+ *
1680
+ * * valid CSS transform string,
1681
+ * * CSSMatrix/DOMMatrix instance,
1682
+ * * a 6/16 elements *Array*.
1683
+ */
1684
+ constructor(...args) {
1685
+ const m = this;
1686
+ // array 6
1687
+ m.a = 1; m.b = 0;
1688
+ m.c = 0; m.d = 1;
1689
+ m.e = 0; m.f = 0;
1690
+ // array 16
1691
+ m.m11 = 1; m.m12 = 0; m.m13 = 0; m.m14 = 0;
1692
+ m.m21 = 0; m.m22 = 1; m.m23 = 0; m.m24 = 0;
1693
+ m.m31 = 0; m.m32 = 0; m.m33 = 1; m.m34 = 0;
1694
+ m.m41 = 0; m.m42 = 0; m.m43 = 0; m.m44 = 1;
1145
1695
 
1146
- if (pathCommand !== absCommand) {
1147
- absoluteSegment[0] = absCommand;
1696
+ if (args && args.length) {
1697
+ let ARGS = args;
1148
1698
 
1149
- switch (absCommand) {
1150
- case 'A':
1151
- newSeg = segment.slice(1, -2).concat([+segment[6] + x, +segment[7] + y]);
1152
- for (let j = 0; j < newSeg.length; j += 1) {
1153
- absoluteSegment.push(newSeg[j]);
1154
- }
1155
- break;
1156
- case 'V':
1157
- absoluteSegment[1] = +segment[1] + y;
1158
- break;
1159
- case 'H':
1160
- absoluteSegment[1] = +segment[1] + x;
1161
- break;
1162
- default:
1163
- if (absCommand === 'M') {
1164
- mx = +segment[1] + x;
1165
- my = +segment[2] + y;
1166
- }
1167
- // for is here to stay for eslint
1168
- for (let j = 1; j < segment.length; j += 1) {
1169
- absoluteSegment.push(+segment[j] + (j % 2 ? x : y));
1170
- }
1171
- }
1172
- } else {
1173
- for (let j = 0; j < segment.length; j += 1) {
1174
- absoluteSegment.push(segment[j]);
1699
+ if (args instanceof Array) {
1700
+ if ((args[0] instanceof Array && [16, 6].includes(args[0].length))
1701
+ || typeof args[0] === 'string'
1702
+ || [CSSMatrix, DOMMatrix].some((x) => args[0] instanceof x)) {
1703
+ [ARGS] = args;
1704
+ }
1175
1705
  }
1706
+ return m.setMatrixValue(ARGS);
1176
1707
  }
1708
+ return m;
1709
+ }
1177
1710
 
1178
- const segLength = absoluteSegment.length;
1179
- switch (absCommand) {
1180
- case 'Z':
1181
- x = mx;
1182
- y = my;
1183
- break;
1184
- case 'H':
1185
- x = +absoluteSegment[1];
1186
- break;
1187
- case 'V':
1188
- y = +absoluteSegment[1];
1189
- break;
1190
- default:
1191
- x = +absoluteSegment[segLength - 2];
1192
- y = +absoluteSegment[segLength - 1];
1711
+ /**
1712
+ * Sets a new `Boolean` flag value for `this.isIdentity` matrix property.
1713
+ *
1714
+ * @param {Boolean} value sets a new flag for this property
1715
+ */
1716
+ set isIdentity(value) {
1717
+ this.isIdentity = value;
1718
+ }
1193
1719
 
1194
- if (absCommand === 'M') {
1195
- mx = x;
1196
- my = y;
1197
- }
1198
- }
1720
+ /**
1721
+ * A `Boolean` whose value is `true` if the matrix is the identity matrix. The identity
1722
+ * matrix is one in which every value is 0 except those on the main diagonal from top-left
1723
+ * to bottom-right corner (in other words, where the offsets in each direction are equal).
1724
+ *
1725
+ * @return {Boolean} the current property value
1726
+ */
1727
+ get isIdentity() {
1728
+ const m = this;
1729
+ return (m.m11 === 1 && m.m12 === 0 && m.m13 === 0 && m.m14 === 0
1730
+ && m.m21 === 0 && m.m22 === 1 && m.m23 === 0 && m.m24 === 0
1731
+ && m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0
1732
+ && m.m41 === 0 && m.m42 === 0 && m.m43 === 0 && m.m44 === 1);
1199
1733
  }
1200
1734
 
1201
- return resultArray;
1202
- }
1203
-
1204
- // returns {qx,qy} for shorthand quadratic bezier segments
1205
- function shorthandToQuad(x1, y1, qx, qy, prevCommand) {
1206
- return 'QT'.indexOf(prevCommand) > -1
1207
- ? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
1208
- : { qx: x1, qy: y1 };
1209
- }
1735
+ /**
1736
+ * A `Boolean` flag whose value is `true` if the matrix was initialized as a 2D matrix
1737
+ * and `false` if the matrix is 3D.
1738
+ *
1739
+ * @return {Boolean} the current property value
1740
+ */
1741
+ get is2D() {
1742
+ const m = this;
1743
+ return (m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0 && m.m43 === 0 && m.m44 === 1);
1744
+ }
1210
1745
 
1211
- // returns {x1,x2} for shorthand cubic bezier segments
1212
- function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
1213
- return 'CS'.indexOf(prevCommand) > -1
1214
- ? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
1215
- : { x1, y1 };
1216
- }
1746
+ /**
1747
+ * Sets a new `Boolean` flag value for `this.is2D` matrix property.
1748
+ *
1749
+ * @param {Boolean} value sets a new flag for this property
1750
+ */
1751
+ set is2D(value) {
1752
+ this.is2D = value;
1753
+ }
1217
1754
 
1218
- function normalizeSegment(segment, params, prevCommand) {
1219
- const [pathCommand] = segment;
1220
- const xy = segment.slice(1);
1221
- let result = segment;
1755
+ /**
1756
+ * The `setMatrixValue` method replaces the existing matrix with one computed
1757
+ * in the browser. EG: `matrix(1,0.25,-0.25,1,0,0)`
1758
+ *
1759
+ * The method accepts any *Array* values, the result of
1760
+ * `DOMMatrix` instance method `toFloat64Array()` / `toFloat32Array()` calls
1761
+ * or `CSSMatrix` instance method `toArray()`.
1762
+ *
1763
+ * This method expects valid *matrix()* / *matrix3d()* string values, as well
1764
+ * as other transform functions like *translateX(10px)*.
1765
+ *
1766
+ * @param {string | number[] | CSSMatrix | DOMMatrix} source
1767
+ * @return {CSSMatrix} the matrix instance
1768
+ */
1769
+ setMatrixValue(source) {
1770
+ const m = this;
1222
1771
 
1223
- if ('TQ'.indexOf(segment[0]) < 0) {
1224
- // optional but good to be cautious
1225
- params.qx = null;
1226
- params.qy = null;
1772
+ // new CSSMatrix(CSSMatrix | DOMMatrix)
1773
+ if ([DOMMatrix, CSSMatrix].some((x) => source instanceof x)) {
1774
+ // @ts-ignore
1775
+ return fromMatrix(source);
1776
+ // CSS transform string source
1777
+ } if (typeof source === 'string' && source.length && source !== 'none') {
1778
+ return fromString(source);
1779
+ // [Arguments list | Array] come here
1780
+ } if (Array.isArray(source)) {
1781
+ return fromArray(source);
1782
+ }
1783
+ return m;
1227
1784
  }
1228
1785
 
1229
- if (pathCommand === 'H') {
1230
- result = ['L', segment[1], params.y1];
1231
- } else if (pathCommand === 'V') {
1232
- result = ['L', params.x1, segment[1]];
1233
- } else if (pathCommand === 'S') {
1234
- const { x1, y1 } = shorthandToCubic(params.x1, params.y1, params.x2, params.y2, prevCommand);
1235
- params.x1 = x1;
1236
- params.y1 = y1;
1237
- result = ['C', x1, y1].concat(xy);
1238
- } else if (pathCommand === 'T') {
1239
- const { qx, qy } = shorthandToQuad(params.x1, params.y1, params.qx, params.qy, prevCommand);
1240
- params.qx = qx;
1241
- params.qy = qy;
1242
- result = ['Q', qx, qy].concat(xy);
1243
- } else if (pathCommand === 'Q') {
1244
- const [nqx, nqy] = xy;
1245
- params.qx = nqx;
1246
- params.qy = nqy;
1786
+ /**
1787
+ * Creates and returns a string representation of the matrix in `CSS` matrix syntax,
1788
+ * using the appropriate `CSS` matrix notation.
1789
+ *
1790
+ * matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
1791
+ * matrix *matrix(a, b, c, d, e, f)*
1792
+ *
1793
+ * @return {string} a string representation of the matrix
1794
+ */
1795
+ toString() {
1796
+ const m = this;
1797
+ const values = m.toArray().join(',');
1798
+ const type = m.is2D ? 'matrix' : 'matrix3d';
1799
+ return `${type}(${values})`;
1247
1800
  }
1248
- return result;
1249
- }
1250
1801
 
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
1255
- });
1256
- }
1802
+ /**
1803
+ * Returns an *Array* containing all 16 elements which comprise the matrix.
1804
+ * The method can return either the elements.
1805
+ *
1806
+ * Other methods make use of this method to feed their output values from this matrix.
1807
+ *
1808
+ * @return {number[]} an *Array* representation of the matrix
1809
+ */
1810
+ toArray() {
1811
+ const m = this;
1812
+ const pow6 = (10 ** 6);
1813
+ let result;
1257
1814
 
1258
- function normalizePath(pathInput) { // pathArray|pathString
1259
- if (isNormalizedArray(pathInput)) {
1260
- return clonePath(pathInput);
1815
+ if (m.is2D) {
1816
+ result = [m.a, m.b, m.c, m.d, m.e, m.f];
1817
+ } else {
1818
+ result = [m.m11, m.m12, m.m13, m.m14,
1819
+ m.m21, m.m22, m.m23, m.m24,
1820
+ m.m31, m.m32, m.m33, m.m34,
1821
+ m.m41, m.m42, m.m43, m.m44];
1822
+ }
1823
+ // clean up the numbers
1824
+ // eslint-disable-next-line -- no-bitwise
1825
+ return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
1261
1826
  }
1262
1827
 
1263
- const pathArray = pathToAbsolute(pathInput);
1264
- const params = {
1265
- x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
1266
- };
1267
- const allPathCommands = [];
1268
- const ii = pathArray.length;
1269
- let prevCommand = '';
1270
- let segment;
1271
- let seglen;
1272
-
1273
- for (let i = 0; i < ii; i += 1) {
1274
- // save current path command
1275
- const [pathCommand] = pathArray[i];
1276
-
1277
- // Save current path command
1278
- allPathCommands[i] = pathCommand;
1279
- // Get previous path command
1280
- if (i) prevCommand = allPathCommands[i - 1];
1281
- // Previous path command is inputted to processSegment
1282
- pathArray[i] = normalizeSegment(pathArray[i], params, prevCommand);
1828
+ /**
1829
+ * Returns a JSON representation of the `CSSMatrix` instance, a standard *Object*
1830
+ * that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties and
1831
+ * excludes `is2D` & `isIdentity` properties.
1832
+ *
1833
+ * The result can also be used as a second parameter for the `fromMatrix` static method
1834
+ * to load values into a matrix instance.
1835
+ *
1836
+ * @return {DMNS.jsonMatrix} an *Object* with all matrix values.
1837
+ */
1838
+ toJSON() {
1839
+ return JSON.parse(JSON.stringify(this));
1840
+ }
1283
1841
 
1284
- segment = pathArray[i];
1285
- seglen = segment.length;
1842
+ /**
1843
+ * The Multiply method returns a new CSSMatrix which is the result of this
1844
+ * matrix multiplied by the passed matrix, with the passed matrix to the right.
1845
+ * This matrix is not modified.
1846
+ *
1847
+ * @param {CSSMatrix | DOMMatrix | DMNS.jsonMatrix} m2 CSSMatrix
1848
+ * @return {CSSMatrix} The resulted matrix.
1849
+ */
1850
+ multiply(m2) {
1851
+ // @ts-ignore - we only access [m11, m12, ... m44] values
1852
+ return Multiply(this, m2);
1853
+ }
1286
1854
 
1287
- params.x1 = +segment[seglen - 2];
1288
- params.y1 = +segment[seglen - 1];
1289
- params.x2 = +(segment[seglen - 4]) || params.x1;
1290
- params.y2 = +(segment[seglen - 3]) || params.y1;
1855
+ /**
1856
+ * The translate method returns a new matrix which is this matrix post
1857
+ * multiplied by a translation matrix containing the passed values. If the z
1858
+ * component is undefined, a 0 value is used in its place. This matrix is not
1859
+ * modified.
1860
+ *
1861
+ * @param {number} x X component of the translation value.
1862
+ * @param {number | null} y Y component of the translation value.
1863
+ * @param {number | null} z Z component of the translation value.
1864
+ * @return {CSSMatrix} The resulted matrix
1865
+ */
1866
+ translate(x, y, z) {
1867
+ const X = x;
1868
+ let Y = y;
1869
+ let Z = z;
1870
+ if (Z == null) Z = 0;
1871
+ if (Y == null) Y = 0;
1872
+ return Multiply(this, Translate(X, Y, Z));
1291
1873
  }
1292
- return pathArray;
1293
- }
1294
1874
 
1295
- function rotateVector(x, y, rad) {
1296
- const X = x * Math.cos(rad) - y * Math.sin(rad);
1297
- const Y = x * Math.sin(rad) + y * Math.cos(rad);
1298
- return { x: X, y: Y };
1299
- }
1875
+ /**
1876
+ * The scale method returns a new matrix which is this matrix post multiplied by
1877
+ * a scale matrix containing the passed values. If the z component is undefined,
1878
+ * a 1 value is used in its place. If the y component is undefined, the x
1879
+ * component value is used in its place. This matrix is not modified.
1880
+ *
1881
+ * @param {number} x The X component of the scale value.
1882
+ * @param {number | null} y The Y component of the scale value.
1883
+ * @param {number | null} z The Z component of the scale value.
1884
+ * @return {CSSMatrix} The resulted matrix
1885
+ */
1886
+ scale(x, y, z) {
1887
+ const X = x;
1888
+ let Y = y;
1889
+ let Z = z;
1890
+ if (Y == null) Y = x;
1891
+ if (Z == null) Z = x;
1300
1892
 
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
1893
+ return Multiply(this, Scale(X, Y, Z));
1894
+ }
1304
1895
 
1305
- function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
1306
- const d120 = (Math.PI * 120) / 180;
1307
- const rad = (Math.PI / 180) * (angle || 0);
1308
- let res = [];
1309
- let X1 = x1;
1310
- let X2 = x2;
1311
- let Y1 = y1;
1312
- let Y2 = y2;
1313
- let RX = rx;
1314
- let RY = ry;
1315
- let xy;
1316
- let f1;
1317
- let f2;
1318
- let cx;
1319
- let cy;
1896
+ /**
1897
+ * The rotate method returns a new matrix which is this matrix post multiplied
1898
+ * by each of 3 rotation matrices about the major axes, first X, then Y, then Z.
1899
+ * If the y and z components are undefined, the x value is used to rotate the
1900
+ * object about the z axis, as though the vector (0,0,x) were passed. All
1901
+ * rotation values are in degrees. This matrix is not modified.
1902
+ *
1903
+ * @param {number} rx The X component of the rotation, or Z if Y and Z are null.
1904
+ * @param {number | null} ry The (optional) Y component of the rotation value.
1905
+ * @param {number | null} rz The (optional) Z component of the rotation value.
1906
+ * @return {CSSMatrix} The resulted matrix
1907
+ */
1908
+ rotate(rx, ry, rz) {
1909
+ let RX = rx;
1910
+ let RY = ry;
1911
+ let RZ = rz;
1912
+ if (RY == null) RY = 0;
1913
+ if (RZ == null) { RZ = RX; RX = 0; }
1914
+ return Multiply(this, Rotate(RX, RY, RZ));
1915
+ }
1320
1916
 
1321
- if (!recursive) {
1322
- xy = rotateVector(X1, Y1, -rad);
1323
- X1 = xy.x;
1324
- Y1 = xy.y;
1325
- xy = rotateVector(X2, Y2, -rad);
1326
- X2 = xy.x;
1327
- Y2 = xy.y;
1328
-
1329
- const x = (X1 - X2) / 2;
1330
- const y = (Y1 - Y2) / 2;
1331
- let h = (x ** 2) / (RX ** 2) + (y ** 2) / (RY ** 2);
1332
- if (h > 1) {
1333
- h = Math.sqrt(h);
1334
- RX *= h;
1335
- RY *= h;
1917
+ /**
1918
+ * The rotateAxisAngle method returns a new matrix which is this matrix post
1919
+ * multiplied by a rotation matrix with the given axis and `angle`. The right-hand
1920
+ * rule is used to determine the direction of rotation. All rotation values are
1921
+ * in degrees. This matrix is not modified.
1922
+ *
1923
+ * @param {number} x The X component of the axis vector.
1924
+ * @param {number} y The Y component of the axis vector.
1925
+ * @param {number} z The Z component of the axis vector.
1926
+ * @param {number} angle The angle of rotation about the axis vector, in degrees.
1927
+ * @return {CSSMatrix} The resulted matrix
1928
+ */
1929
+ rotateAxisAngle(x, y, z, angle) {
1930
+ if ([x, y, z, angle].some((n) => Number.isNaN(n))) {
1931
+ throw new TypeError('CSSMatrix: expecting 4 values');
1336
1932
  }
1337
- const rx2 = RX ** 2;
1338
- const ry2 = RY ** 2;
1339
- const k = (LAF === SF ? -1 : 1)
1340
- * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
1341
- / (rx2 * y * y + ry2 * x * x)));
1933
+ return Multiply(this, RotateAxisAngle(x, y, z, angle));
1934
+ }
1935
+
1936
+ /**
1937
+ * Specifies a skew transformation along the `x-axis` by the given angle.
1938
+ * This matrix is not modified.
1939
+ *
1940
+ * @param {number} angle The angle amount in degrees to skew.
1941
+ * @return {CSSMatrix} The resulted matrix
1942
+ */
1943
+ skewX(angle) {
1944
+ return Multiply(this, SkewX(angle));
1945
+ }
1342
1946
 
1343
- cx = ((k * RX * y) / RY) + ((X1 + X2) / 2);
1344
- cy = ((k * -RY * x) / RX) + ((Y1 + Y2) / 2);
1947
+ /**
1948
+ * Specifies a skew transformation along the `y-axis` by the given angle.
1949
+ * This matrix is not modified.
1950
+ *
1951
+ * @param {number} angle The angle amount in degrees to skew.
1952
+ * @return {CSSMatrix} The resulted matrix
1953
+ */
1954
+ skewY(angle) {
1955
+ return Multiply(this, SkewY(angle));
1956
+ }
1345
1957
 
1346
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
1347
- f1 = Math.asin((((Y1 - cy) / RY) * 10 ** 9 >> 0) / (10 ** 9));
1348
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
1349
- f2 = Math.asin((((Y2 - cy) / RY) * 10 ** 9 >> 0) / (10 ** 9));
1958
+ /**
1959
+ * Transforms a specified point using the matrix, returning a new
1960
+ * Tuple *Object* comprising of the transformed point.
1961
+ * Neither the matrix nor the original point are altered.
1962
+ *
1963
+ * The method is equivalent with `transformPoint()` method
1964
+ * of the `DOMMatrix` constructor.
1965
+ *
1966
+ * @copyright thednp © 2021
1967
+ *
1968
+ * @param {DMNS.PointTuple | DOMPoint} v Tuple or DOMPoint
1969
+ * @return {DMNS.PointTuple} the resulting Tuple
1970
+ */
1971
+ transformPoint(v) {
1972
+ const M = this;
1973
+ let m = Translate(v.x, v.y, v.z);
1974
+
1975
+ m.m44 = v.w || 1;
1976
+ m = M.multiply(m);
1350
1977
 
1351
- f1 = X1 < cx ? Math.PI - f1 : f1;
1352
- f2 = X2 < cx ? Math.PI - f2 : f2;
1978
+ return {
1979
+ x: m.m41,
1980
+ y: m.m42,
1981
+ z: m.m43,
1982
+ w: m.m44,
1983
+ };
1984
+ }
1353
1985
 
1354
- if (f1 < 0) { f1 = Math.PI * 2 + f1; }
1355
- if (f2 < 0) { f2 = Math.PI * 2 + f2; }
1986
+ /**
1987
+ * Transforms a specified vector using the matrix, returning a new
1988
+ * {x,y,z,w} Tuple *Object* comprising the transformed vector.
1989
+ * Neither the matrix nor the original vector are altered.
1990
+ *
1991
+ * @param {DMNS.PointTuple} t Tuple with `{x,y,z,w}` components
1992
+ * @return {DMNS.PointTuple} the resulting Tuple
1993
+ */
1994
+ transform(t) {
1995
+ const m = this;
1996
+ const x = m.m11 * t.x + m.m12 * t.y + m.m13 * t.z + m.m14 * t.w;
1997
+ const y = m.m21 * t.x + m.m22 * t.y + m.m23 * t.z + m.m24 * t.w;
1998
+ const z = m.m31 * t.x + m.m32 * t.y + m.m33 * t.z + m.m34 * t.w;
1999
+ const w = m.m41 * t.x + m.m42 * t.y + m.m43 * t.z + m.m44 * t.w;
1356
2000
 
1357
- if (SF && f1 > f2) {
1358
- f1 -= Math.PI * 2;
1359
- }
1360
- if (!SF && f2 > f1) {
1361
- f2 -= Math.PI * 2;
1362
- }
1363
- } else {
1364
- const [r1, r2, r3, r4] = recursive;
1365
- f1 = r1;
1366
- f2 = r2;
1367
- cx = r3;
1368
- cy = r4;
2001
+ return {
2002
+ x: x / w,
2003
+ y: y / w,
2004
+ z: z / w,
2005
+ w,
2006
+ };
1369
2007
  }
2008
+ }
1370
2009
 
1371
- let df = f2 - f1;
2010
+ // Add Transform Functions to CSSMatrix object
2011
+ CSSMatrix.Translate = Translate;
2012
+ CSSMatrix.Rotate = Rotate;
2013
+ CSSMatrix.RotateAxisAngle = RotateAxisAngle;
2014
+ CSSMatrix.Scale = Scale;
2015
+ CSSMatrix.SkewX = SkewX;
2016
+ CSSMatrix.SkewY = SkewY;
2017
+ CSSMatrix.Multiply = Multiply;
2018
+ CSSMatrix.fromArray = fromArray;
2019
+ CSSMatrix.fromMatrix = fromMatrix;
2020
+ CSSMatrix.fromString = fromString;
2021
+ CSSMatrix.Version = DMVersion;
1372
2022
 
1373
- if (Math.abs(df) > d120) {
1374
- const f2old = f2;
1375
- const x2old = X2;
1376
- const y2old = Y2;
2023
+ /**
2024
+ * Returns a transformation matrix to apply to `<path>` elements.
2025
+ *
2026
+ * @param {SVGPC.transformObject} transform the `transformObject`
2027
+ * @returns {CSSMatrix} a new transformation matrix
2028
+ */
2029
+ function getSVGMatrix(transform) {
2030
+ let matrix = new CSSMatrix();
2031
+ const { origin } = transform;
2032
+ const originX = +origin[0];
2033
+ const originY = +origin[1];
2034
+ const { translate } = transform;
2035
+ const { rotate } = transform;
2036
+ const { skew } = transform;
2037
+ const { scale } = transform;
1377
2038
 
1378
- f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
1379
- X2 = cx + RX * Math.cos(f2);
1380
- Y2 = cy + RY * Math.sin(f2);
1381
- res = arcToCubic(X2, Y2, RX, RY, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
2039
+ // set translate
2040
+ if ((Array.isArray(translate) && translate.some((x) => +x !== 0)) || !Number.isNaN(translate)) {
2041
+ matrix = Array.isArray(translate)
2042
+ ? matrix.translate(+translate[0] || 0, +translate[1] || 0, +translate[2] || 0)
2043
+ // @ts-ignore
2044
+ : matrix.translate(+translate || 0);
1382
2045
  }
1383
2046
 
1384
- df = f2 - f1;
1385
- const c1 = Math.cos(f1);
1386
- const s1 = Math.sin(f1);
1387
- const c2 = Math.cos(f2);
1388
- const s2 = Math.sin(f2);
1389
- const t = Math.tan(df / 4);
1390
- const hx = (4 / 3) * RX * t;
1391
- const hy = (4 / 3) * RY * t;
1392
- const m1 = [X1, Y1];
1393
- const m2 = [X1 + hx * s1, Y1 - hy * c1];
1394
- const m3 = [X2 + hx * s2, Y2 - hy * c2];
1395
- const m4 = [X2, Y2];
1396
- m2[0] = 2 * m1[0] - m2[0];
1397
- m2[1] = 2 * m1[1] - m2[1];
2047
+ if (rotate || skew || scale) {
2048
+ // set SVG transform-origin, always defined
2049
+ // matrix = matrix.translate(+originX,+originY,+originZ)
2050
+ // @ts-ignore
2051
+ matrix = matrix.translate(+originX, +originY);
1398
2052
 
1399
- if (recursive) {
1400
- return [m2, m3, m4].concat(res);
1401
- }
1402
- res = [m2, m3, m4].concat(res).join().split(',');
1403
- return res.map((rz, i) => {
1404
- if (i % 2) {
1405
- return rotateVector(res[i - 1], rz, rad).y;
2053
+ // set rotation
2054
+ if (rotate) {
2055
+ matrix = Array.isArray(rotate) && rotate.some((x) => +x !== 0)
2056
+ ? matrix.rotate(+rotate[0] || 0, +rotate[1] || 0, +rotate[2] || 0)
2057
+ // @ts-ignore
2058
+ : matrix.rotate(+rotate || 0);
1406
2059
  }
1407
- return rotateVector(rz, res[i + 1], rad).x;
1408
- });
2060
+ // set skew(s)
2061
+ if (Array.isArray(skew) && skew.some((x) => +x !== 0)) {
2062
+ if (Array.isArray(skew)) {
2063
+ matrix = skew[0] ? matrix.skewX(+skew[0] || 0) : matrix;
2064
+ matrix = skew[1] ? matrix.skewY(+skew[1] || 0) : matrix;
2065
+ } else {
2066
+ matrix = matrix.skewX(+skew || 0);
2067
+ }
2068
+ }
2069
+ // set scale
2070
+ if (!Number.isNaN(scale) || (Array.isArray(scale) && scale.some((x) => +x !== 1))) {
2071
+ matrix = Array.isArray(scale)
2072
+ ? (matrix.scale(+scale[0] || 1, +scale[1] || 1, +scale[2] || 1))
2073
+ // @ts-ignore
2074
+ : matrix.scale(+scale || 1);
2075
+ }
2076
+ // set SVG transform-origin
2077
+ // matrix = matrix.translate(-originX,-originY,-originZ)
2078
+ // @ts-ignore
2079
+ matrix = matrix.translate(-originX, -originY);
2080
+ }
2081
+ return matrix;
1409
2082
  }
1410
2083
 
1411
- function quadToCubic(x1, y1, qx, qy, x2, y2) {
1412
- const r13 = 1 / 3;
1413
- const r23 = 2 / 3;
1414
- return [
1415
- r13 * x1 + r23 * qx, // cpx1
1416
- r13 * y1 + r23 * qy, // cpy1
1417
- r13 * x2 + r23 * qx, // cpx2
1418
- r13 * y2 + r23 * qy, // cpy2
1419
- x2, y2, // x,y
2084
+ /**
2085
+ * Apply a 2D transformation matrix to an ellipse.
2086
+ *
2087
+ * @param {number[]} m the 2D transformation matrix
2088
+ * @param {number} rx ellipse radius X
2089
+ * @param {number} ry ellipse radius Y
2090
+ * @param {number} ax ellipse rotation angle
2091
+ */
2092
+ function transformEllipse(m, rx, ry, ax) {
2093
+ // We consider the current ellipse as image of the unit circle
2094
+ // by first scale(rx,ry) and then rotate(ax) ...
2095
+ // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle.
2096
+ const c = Math.cos((ax * Math.PI) / 180);
2097
+ const s = Math.sin((ax * Math.PI) / 180);
2098
+ const ma = [
2099
+ rx * (m[0] * c + m[2] * s),
2100
+ rx * (m[1] * c + m[3] * s),
2101
+ ry * (-m[0] * s + m[2] * c),
2102
+ ry * (-m[1] * s + m[3] * c),
1420
2103
  ];
1421
- }
1422
2104
 
1423
- // t = [0-1]
1424
- function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
1425
- const t1 = 1 - t;
1426
- return {
1427
- x: (t1 ** 3) * p1x
1428
- + t1 * t1 * 3 * t * c1x
1429
- + t1 * 3 * t * t * c2x
1430
- + (t ** 3) * p2x,
1431
- y: (t1 ** 3) * p1y
1432
- + t1 * t1 * 3 * t * c1y
1433
- + t1 * 3 * t * t * c2y
1434
- + (t ** 3) * p2y,
1435
- };
1436
- }
2105
+ // ma * transpose(ma) = [ J L ]
2106
+ // [ L K ]
2107
+ // L is calculated later (if the image is not a circle)
2108
+ const J = ma[0] * ma[0] + ma[2] * ma[2];
2109
+ const K = ma[1] * ma[1] + ma[3] * ma[3];
1437
2110
 
1438
- 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];
1443
- return [ax + (bx - ax) * t, ay + (by - ay) * t];
1444
- }
2111
+ // the discriminant of the characteristic polynomial of ma * transpose(ma)
2112
+ let D = ((ma[0] - ma[3]) * (ma[0] - ma[3]) + (ma[2] + ma[1]) * (ma[2] + ma[1]))
2113
+ * ((ma[0] + ma[3]) * (ma[0] + ma[3]) + (ma[2] - ma[1]) * (ma[2] - ma[1]));
1445
2114
 
1446
- function lineToCubic(x1, y1, x2, y2) {
1447
- const t = 0.5;
1448
- const p0 = [x1, y1];
1449
- const p1 = [x2, y2];
1450
- const p2 = midPoint(p0, p1, t);
1451
- const p3 = midPoint(p1, p2, t);
1452
- const p4 = midPoint(p2, p3, t);
1453
- const p5 = midPoint(p3, p4, t);
1454
- const p6 = midPoint(p4, p5, t);
1455
- const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
1456
- const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
2115
+ // the "mean eigenvalue"
2116
+ const JK = (J + K) / 2;
1457
2117
 
1458
- return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
1459
- }
2118
+ // check if the image is (almost) a circle
2119
+ if (D < epsilon * JK) {
2120
+ // if it is
2121
+ const rxy = Math.sqrt(JK);
1460
2122
 
1461
- function segmentToCubic(segment, params) {
1462
- if ('TQ'.indexOf(segment[0]) < 0) {
1463
- params.qx = null;
1464
- params.qy = null;
2123
+ return { rx: rxy, ry: rxy, ax: 0 };
1465
2124
  }
1466
2125
 
1467
- const [s1, s2] = segment.slice(1);
2126
+ // if it is not a circle
2127
+ const L = ma[0] * ma[1] + ma[2] * ma[3];
1468
2128
 
1469
- switch (segment[0]) {
1470
- case 'M':
1471
- params.x = s1;
1472
- params.y = s2;
1473
- return segment;
1474
- case 'A':
1475
- return ['C'].concat(arcToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
1476
- case 'Q':
1477
- params.qx = s1;
1478
- params.qy = s2;
1479
- return ['C'].concat(quadToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
1480
- case 'L':
1481
- return ['C'].concat(lineToCubic(params.x1, params.y1, segment[1], segment[2]));
1482
- case 'Z':
1483
- return ['C'].concat(lineToCubic(params.x1, params.y1, params.x, params.y));
2129
+ D = Math.sqrt(D);
2130
+
2131
+ // {l1,l2} = the two eigen values of ma * transpose(ma)
2132
+ const l1 = JK + D / 2;
2133
+ const l2 = JK - D / 2;
2134
+ // the x - axis - rotation angle is the argument of the l1 - eigenvector
2135
+ let AX = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? 90
2136
+ : Math.atan(Math.abs(L) > Math.abs(l1 - K) ? (l1 - J) / L
2137
+ : ((L / (l1 - K))) * 180) / Math.PI;
2138
+ let RX;
2139
+ let RY;
2140
+
2141
+ // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90
2142
+ if (AX >= 0) {
2143
+ // if ax in [0,90]
2144
+ RX = Math.sqrt(l1);
2145
+ RY = Math.sqrt(l2);
2146
+ } else {
2147
+ // if ax in ]-90,0[ => exchange axes
2148
+ AX += 90;
2149
+ RX = Math.sqrt(l2);
2150
+ RY = Math.sqrt(l1);
1484
2151
  }
1485
- return segment;
2152
+
2153
+ return { rx: RX, ry: RY, ax: AX };
1486
2154
  }
1487
2155
 
1488
- function pathToCurve(pathInput) { // pathArray|pathString
1489
- if (isCurveArray(pathInput)) {
1490
- return clonePath(pathInput);
1491
- }
2156
+ /**
2157
+ * Returns the [x,y] projected coordinates for a given an [x,y] point
2158
+ * and an [x,y,z] perspective origin point.
2159
+ *
2160
+ * Equation found here =>
2161
+ * http://en.wikipedia.org/wiki/3D_projection#Diagram
2162
+ * Details =>
2163
+ * https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
2164
+ *
2165
+ * @param {SVGPC.CSSMatrix} m the transformation matrix
2166
+ * @param {Number[]} point2D the initial [x,y] coordinates
2167
+ * @param {number[]} origin the initial [x,y] coordinates
2168
+ * @returns {Number[]} the projected [x,y] coordinates
2169
+ */
2170
+ function projection2d(m, point2D, origin) {
2171
+ const point3D = m.transformPoint({
2172
+ x: point2D[0], y: point2D[1], z: 0, w: 1,
2173
+ });
2174
+ const originX = origin[0] || 0;
2175
+ const originY = origin[1] || 0;
2176
+ const originZ = origin[2] || 0;
2177
+ const relativePositionX = point3D.x - originX;
2178
+ const relativePositionY = point3D.y - originY;
2179
+ const relativePositionZ = point3D.z - originZ;
2180
+
2181
+ return [
2182
+ relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originX,
2183
+ relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originY,
2184
+ ];
2185
+ }
1492
2186
 
1493
- const pathArray = normalizePath(pathInput);
2187
+ /**
2188
+ * Apply a 2D / 3D transformation to a `pathArray` instance.
2189
+ *
2190
+ * Since *SVGElement* doesn't support 3D transformation, this function
2191
+ * creates a 2D projection of the <path> element.
2192
+ *
2193
+ * @param {SVGPC.pathArray} path the `pathArray` to apply transformation
2194
+ * @param {any} transform the transform functions `Object`
2195
+ * @returns {SVGPC.pathArray} the resulted `pathArray`
2196
+ */
2197
+ function transformPath(path, transform) {
2198
+ let x = 0; let y = 0; let i; let j; let ii; let jj; let lx; let ly; let te;
2199
+ const absolutePath = pathToAbsolute(path);
2200
+ const normalizedPath = normalizePath(absolutePath);
2201
+ const matrixInstance = getSVGMatrix(transform);
2202
+ const transformProps = Object.keys(transform);
2203
+ const { origin } = transform;
2204
+ const {
2205
+ a, b, c, d, e, f,
2206
+ } = matrixInstance;
2207
+ const matrix2d = [a, b, c, d, e, f];
1494
2208
  const params = {
1495
2209
  x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
1496
2210
  };
1497
- const allPathCommands = [];
2211
+ let segment = [];
2212
+ let seglen = 0;
1498
2213
  let pathCommand = '';
1499
- let ii = pathArray.length;
1500
- let segment;
1501
- let seglen;
2214
+ /** @type {any} */
2215
+ let transformedPath = [];
2216
+ const allPathCommands = []; // needed for arc to curve transformation
2217
+ let result = [];
1502
2218
 
1503
- for (let i = 0; i < ii; i += 1) {
1504
- if (pathArray[i]) [pathCommand] = pathArray[i];
2219
+ if (!matrixInstance.isIdentity) {
2220
+ for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
2221
+ segment = absolutePath[i];
1505
2222
 
1506
- allPathCommands[i] = pathCommand;
1507
- pathArray[i] = segmentToCubic(pathArray[i], params);
2223
+ if (absolutePath[i]) [pathCommand] = segment;
1508
2224
 
1509
- fixArc(pathArray, allPathCommands, i);
1510
- ii = pathArray.length; // solves curveArrays ending in Z
2225
+ // REPLACE Arc path commands with Cubic Beziers
2226
+ // we don't have any scripting know-how on 3d ellipse transformation
2227
+ /// ////////////////////////////////////////
2228
+ allPathCommands[i] = pathCommand;
1511
2229
 
1512
- segment = pathArray[i];
1513
- seglen = segment.length;
2230
+ // Arcs don't work very well with 3D transformations or skews
2231
+ if (pathCommand === 'A' && (!matrixInstance.is2D || !['skewX', 'skewY'].find((p) => transformProps.includes(p)))) {
2232
+ segment = segmentToCubic(normalizedPath[i], params);
1514
2233
 
1515
- params.x1 = +segment[seglen - 2];
1516
- params.y1 = +segment[seglen - 1];
1517
- params.x2 = +(segment[seglen - 4]) || params.x1;
1518
- params.y2 = +(segment[seglen - 3]) || params.y1;
1519
- }
2234
+ absolutePath[i] = segmentToCubic(normalizedPath[i], params);
2235
+ fixArc(absolutePath, allPathCommands, i);
1520
2236
 
1521
- return pathArray;
1522
- }
2237
+ normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
2238
+ fixArc(normalizedPath, allPathCommands, i);
2239
+ ii = Math.max(absolutePath.length, normalizedPath.length);
2240
+ }
2241
+ /// ////////////////////////////////////////
1523
2242
 
1524
- // https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
2243
+ segment = normalizedPath[i];
2244
+ seglen = segment.length;
1525
2245
 
1526
- function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
1527
- // http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
1528
- return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
1529
- + (y1 * (x0 - x2)) - (x1 * (y0 - y2))
1530
- + (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
1531
- }
2246
+ params.x1 = +segment[seglen - 2];
2247
+ params.y1 = +segment[seglen - 1];
2248
+ params.x2 = +(segment[seglen - 4]) || params.x1;
2249
+ params.y2 = +(segment[seglen - 3]) || params.y1;
2250
+ // @ts-ignore
2251
+ result = { s: absolutePath[i], c: absolutePath[i][0] };
1532
2252
 
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) => {
1537
- switch (seg[0]) {
1538
- case 'M':
1539
- case 'Z':
1540
- mx = seg[0] === 'M' ? seg[1] : mx;
1541
- my = seg[0] === 'M' ? seg[2] : my;
1542
- x = mx;
1543
- y = my;
1544
- return 0;
1545
- default:
1546
- len = getCubicSegArea.apply(0, [x, y].concat(seg.slice(1)));
1547
- [x, y] = seg.slice(-2);
1548
- return len;
2253
+ if (pathCommand !== 'Z') {
2254
+ // @ts-ignore
2255
+ result.x = params.x1;
2256
+ // @ts-ignore
2257
+ result.y = params.y1;
2258
+ }
2259
+ // @ts-ignore
2260
+ transformedPath = transformedPath.concat(result);
1549
2261
  }
1550
- }).reduce((a, b) => a + b, 0);
1551
- }
1552
-
1553
- function base3(p1, p2, p3, p4, t) {
1554
- const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4;
1555
- const t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
1556
- return t * t2 - 3 * p1 + 3 * p2;
1557
- }
1558
-
1559
- // returns the cubic bezier segment length
1560
- function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
1561
- let Z;
1562
- if (z === null || Number.isNaN(+z)) Z = 1;
2262
+ // @ts-ignore
2263
+ return transformedPath.map((seg) => {
2264
+ pathCommand = seg.c;
2265
+ segment = seg.s;
2266
+ switch (pathCommand) {
2267
+ case 'A': // only apply to 2D transformations
2268
+ te = transformEllipse(matrix2d, segment[1], segment[2], segment[3]);
1563
2269
 
1564
- // Z = Z > 1 ? 1 : Z < 0 ? 0 : Z;
1565
- if (Z > 1) Z = 1;
1566
- if (Z < 0) Z = 0;
2270
+ if (matrix2d[0] * matrix2d[3] - matrix2d[1] * matrix2d[2] < 0) {
2271
+ segment[5] = +segment[5] ? 0 : 1;
2272
+ }
1567
2273
 
1568
- const z2 = Z / 2; let ct = 0; let xbase = 0; let ybase = 0; let sum = 0;
1569
- const Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678,
1570
- -0.5873, 0.5873, -0.7699, 0.7699,
1571
- -0.9041, 0.9041, -0.9816, 0.9816];
1572
- const Cvalues = [0.2491, 0.2491, 0.2335, 0.2335,
1573
- 0.2032, 0.2032, 0.1601, 0.1601,
1574
- 0.1069, 0.1069, 0.0472, 0.0472];
2274
+ [lx, ly] = projection2d(matrixInstance, [segment[6], segment[7]], origin);
1575
2275
 
1576
- Tvalues.forEach((T, i) => {
1577
- ct = z2 * T + z2;
1578
- xbase = base3(x1, x2, x3, x4, ct);
1579
- ybase = base3(y1, y2, y3, y4, ct);
1580
- sum += Cvalues[i] * Math.sqrt(xbase * xbase + ybase * ybase);
1581
- });
1582
- return z2 * sum;
1583
- }
2276
+ if ((x === lx && y === ly) || (te.rx < epsilon * te.ry) || (te.ry < epsilon * te.rx)) {
2277
+ segment = ['L', lx, ly];
2278
+ } else {
2279
+ segment = [pathCommand, te.rx, te.ry, te.ax, segment[4], segment[5], lx, ly];
2280
+ }
1584
2281
 
1585
- // calculates the shape total length
1586
- // equivalent to shape.getTotalLength()
1587
- // pathToCurve version
1588
- function getPathLength(pathArray) {
1589
- 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;
1592
- });
1593
- return totalLength;
1594
- }
2282
+ x = lx; y = ly;
2283
+ return segment;
1595
2284
 
1596
- function getDrawDirection(pathArray) {
1597
- return getPathArea(pathToCurve(pathArray)) >= 0;
1598
- }
2285
+ case 'L':
2286
+ case 'H':
2287
+ case 'V':
1599
2288
 
1600
- // calculates the shape total length
1601
- // almost equivalent to shape.getTotalLength()
1602
- function getPointAtLength(pathArray, length) {
1603
- let totalLength = 0;
1604
- let segLen;
1605
- let data;
1606
- let result;
2289
+ [lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin);
1607
2290
 
1608
- return pathToCurve(pathArray).map((seg, i, curveArray) => { // process data
1609
- data = i ? curveArray[i - 1].slice(-2).concat(seg.slice(1)) : seg.slice(1);
1610
- segLen = i ? getSegCubicLength.apply(0, data) : 0;
1611
- totalLength += segLen;
2291
+ if (x !== lx && y !== ly) {
2292
+ segment = ['L', lx, ly];
2293
+ } else if (y === ly) {
2294
+ segment = ['H', lx];
2295
+ } else if (x === lx) {
2296
+ segment = ['V', ly];
2297
+ }
1612
2298
 
1613
- if (i === 0) {
1614
- result = { x: data[0], y: data[1] };
1615
- } else if (totalLength > length && length > totalLength - segLen) {
1616
- result = getPointAtSegLength.apply(0, data.concat(1 - (totalLength - length) / segLen));
1617
- } else {
1618
- result = null;
1619
- }
2299
+ x = lx; y = ly; // now update x and y
1620
2300
 
1621
- return result;
1622
- }).filter((x) => x).slice(-1)[0]; // isolate last segment
2301
+ return segment;
2302
+ default:
2303
+ for (j = 1, jj = segment.length; j < jj; j += 2) {
2304
+ // compute line coordinates without altering previous coordinates
2305
+ [x, y] = projection2d(matrixInstance, [segment[j], segment[j + 1]], origin);
2306
+ segment[j] = x;
2307
+ segment[j + 1] = y;
2308
+ }
2309
+ return segment;
2310
+ }
2311
+ });
2312
+ }
2313
+ return clonePath(absolutePath);
1623
2314
  }
1624
2315
 
1625
- // returns the cubic bezier segment length
2316
+ /**
2317
+ * Returns the cubic-bezier segment length.
2318
+ *
2319
+ * @param {number} p1x the starting point X
2320
+ * @param {number} p1y the starting point Y
2321
+ * @param {number} c1x the first control point X
2322
+ * @param {number} c1y the first control point Y
2323
+ * @param {number} c2x the second control point X
2324
+ * @param {number} c2y the second control point Y
2325
+ * @param {number} p2x the ending point X
2326
+ * @param {number} p2y the ending point Y
2327
+ * @returns {SVGPC.segmentLimits} the length of the cubic-bezier segment
2328
+ */
1626
2329
  function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1627
2330
  let a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
1628
2331
  let b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
@@ -1632,8 +2335,9 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1632
2335
  const y = [p1y, p2y];
1633
2336
  const x = [p1x, p2x];
1634
2337
  let dot;
1635
-
2338
+ // @ts-ignore
1636
2339
  if (Math.abs(t1) > '1e12') t1 = 0.5;
2340
+ // @ts-ignore
1637
2341
  if (Math.abs(t2) > '1e12') t2 = 0.5;
1638
2342
 
1639
2343
  if (t1 > 0 && t1 < 1) {
@@ -1651,8 +2355,9 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1651
2355
  c = p1y - c1y;
1652
2356
  t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
1653
2357
  t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
1654
-
2358
+ // @ts-ignore
1655
2359
  if (Math.abs(t1) > '1e12') t1 = 0.5;
2360
+ // @ts-ignore
1656
2361
  if (Math.abs(t2) > '1e12') t2 = 0.5;
1657
2362
 
1658
2363
  if (t1 > 0 && t1 < 1) {
@@ -1671,549 +2376,532 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
1671
2376
  };
1672
2377
  }
1673
2378
 
1674
- function getPathBBox(pathArray) {
1675
- if (!pathArray) {
1676
- return {
1677
- x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0,
1678
- };
1679
- }
1680
- const pathCurve = pathToCurve(pathArray);
1681
-
1682
- let x = 0;
1683
- let y = 0;
1684
- let X = [];
1685
- let Y = [];
1686
-
1687
- pathCurve.forEach((segment) => {
1688
- const [s1, s2] = segment.slice(-2);
1689
- if (segment[0] === 'M') {
1690
- x = s1;
1691
- y = s2;
1692
- X.push(s1);
1693
- Y.push(s2);
1694
- } else {
1695
- const dim = getCubicSize.apply(0, [x, y].concat(segment.slice(1)));
1696
- X = X.concat(dim.min.x, dim.max.x);
1697
- Y = Y.concat(dim.min.y, dim.max.y);
1698
- x = s1;
1699
- y = s2;
1700
- }
1701
- });
1702
-
1703
- const xTop = Math.min.apply(0, X);
1704
- const yTop = Math.min.apply(0, Y);
1705
- const xBot = Math.max.apply(0, X);
1706
- const yBot = Math.max.apply(0, Y);
1707
- const width = xBot - xTop;
1708
- const height = yBot - yTop;
1709
-
1710
- return {
1711
- x: xTop,
1712
- y: yTop,
1713
- x2: xBot,
1714
- y2: yBot,
1715
- width,
1716
- height,
1717
- cx: xTop + width / 2,
1718
- cy: yTop + height / 2,
1719
- };
1720
- }
1721
-
1722
- function isRelativeArray(pathInput) {
1723
- return isPathArray(pathInput)
1724
- && pathInput.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
1725
- }
1726
-
1727
- function roundPath(pathArray, round) {
1728
- const decimalsOption = !Number.isNaN(+round) ? +round : SVGPCO.round && SVGPCO.decimals;
1729
- let result;
1730
-
1731
- if (decimalsOption) {
1732
- result = pathArray.map((seg) => seg.map((c) => {
1733
- const nr = +c;
1734
- const dc = 10 ** decimalsOption;
1735
- if (nr) {
1736
- return nr % 1 === 0 ? nr : Math.round(nr * dc) / dc;
1737
- }
1738
- return c;
1739
- }));
1740
- } else {
1741
- result = clonePath(pathArray);
1742
- }
1743
- return result;
1744
- }
1745
-
1746
- function pathToString(pathArray, round) {
1747
- return roundPath(pathArray, round)
1748
- .map((x) => x[0].concat(x.slice(1).join(' '))).join('');
1749
- }
1750
-
1751
- function splitPath(pathInput) {
1752
- return pathToString(pathToAbsolute(pathInput))
1753
- .replace(/(m|M)/g, '|$1')
1754
- .split('|')
1755
- .map((s) => s.trim())
1756
- .filter((s) => s);
2379
+ /**
2380
+ * Iterates an array to check if it's a `pathArray`
2381
+ * with all C (cubic bezier) segments.
2382
+ *
2383
+ * @param {string | SVGPC.pathArray} path the `Array` to be checked
2384
+ * @returns {boolean} iteration result
2385
+ */
2386
+ function isCurveArray(path) {
2387
+ return Array.isArray(path) && isPathArray(path)
2388
+ && path.slice(1).every((seg) => seg[0] === 'C');
1757
2389
  }
1758
2390
 
1759
- function pathToRelative(pathInput) {
1760
- if (isRelativeArray(pathInput)) {
1761
- return clonePath(pathInput);
1762
- }
1763
-
1764
- const pathArray = parsePathString(pathInput);
1765
- const ii = pathArray.length;
1766
- const resultArray = [];
1767
- let x = 0;
1768
- let y = 0;
1769
- let mx = 0;
1770
- let my = 0;
1771
- let start = 0;
1772
-
1773
- if (pathArray[0][0] === 'M') {
1774
- x = +pathArray[0][1];
1775
- y = +pathArray[0][2];
1776
- mx = x;
1777
- my = y;
1778
- start += 1;
1779
- resultArray.push(['M', x, y]);
1780
- }
1781
-
1782
- for (let i = start; i < ii; i += 1) {
1783
- const segment = pathArray[i];
1784
- const [pathCommand] = segment;
1785
- const relativeCommand = pathCommand.toLowerCase();
1786
- const relativeSegment = [];
1787
- let newSeg = [];
1788
- resultArray.push(relativeSegment);
2391
+ /**
2392
+ * Parses a path string value or 'pathArray' and returns a new one
2393
+ * in which all segments are converted to cubic-bezier.
2394
+ *
2395
+ * @param {string | SVGPC.pathArray} pathInput the string to be parsed or object
2396
+ * @returns {SVGPC.pathArray} the resulted `pathArray` converted to cubic-bezier
2397
+ */
2398
+ function pathToCurve(pathInput) {
2399
+ if (Array.isArray(pathInput) && isCurveArray(pathInput)) {
2400
+ return clonePath(pathInput);
2401
+ }
1789
2402
 
1790
- if (pathCommand !== relativeCommand) {
1791
- relativeSegment[0] = relativeCommand;
1792
- switch (relativeCommand) {
1793
- case 'a':
1794
- newSeg = segment.slice(1, -2).concat([+segment[6] - x, +segment[7] - y]);
2403
+ const path = normalizePath(pathInput);
2404
+ const params = {
2405
+ x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
2406
+ };
2407
+ const allPathCommands = [];
2408
+ let pathCommand = '';
2409
+ let ii = path.length;
1795
2410
 
1796
- for (let j = 0; j < newSeg.length; j += 1) {
1797
- relativeSegment.push(newSeg[j]);
1798
- }
1799
- break;
1800
- case 'v':
1801
- relativeSegment[1] = +segment[1] - y;
1802
- break;
1803
- default:
1804
- // for is here to stay for eslint
1805
- for (let j = 1; j < segment.length; j += 1) {
1806
- relativeSegment.push(+segment[j] - (j % 2 ? x : y));
1807
- }
2411
+ for (let i = 0; i < ii; i += 1) {
2412
+ const segment = path[i];
2413
+ const seglen = segment.length;
2414
+ if (segment) [pathCommand] = segment;
1808
2415
 
1809
- if (relativeCommand === 'm') {
1810
- mx = +segment[1];
1811
- my = +segment[2];
1812
- }
1813
- }
1814
- } else {
1815
- if (pathCommand === 'm') {
1816
- mx = +segment[1] + x;
1817
- my = +segment[2] + y;
1818
- }
1819
- for (let j = 0; j < segment.length; j += 1) {
1820
- relativeSegment.push(segment[j]);
1821
- }
1822
- }
2416
+ allPathCommands[i] = pathCommand;
2417
+ path[i] = segmentToCubic(segment, params);
1823
2418
 
1824
- const segLength = relativeSegment.length;
1825
- switch (relativeSegment[0]) {
1826
- case 'z':
1827
- x = mx;
1828
- y = my;
1829
- break;
1830
- case 'h':
1831
- x += relativeSegment[segLength - 1];
1832
- break;
1833
- case 'v':
1834
- y += relativeSegment[segLength - 1];
1835
- break;
1836
- default:
1837
- x += resultArray[i][segLength - 2];
1838
- y += resultArray[i][segLength - 1];
1839
- }
2419
+ fixArc(path, allPathCommands, i);
2420
+ ii = path.length; // solves curveArrays ending in Z
2421
+
2422
+ params.x1 = +segment[seglen - 2];
2423
+ params.y1 = +segment[seglen - 1];
2424
+ params.x2 = +(segment[seglen - 4]) || params.x1;
2425
+ params.y2 = +(segment[seglen - 3]) || params.y1;
1840
2426
  }
1841
2427
 
1842
- return resultArray;
2428
+ return path;
1843
2429
  }
1844
2430
 
1845
- function optimizePath(pathArray, round) {
1846
- const absolutePath = roundPath(pathToAbsolute(pathArray), round);
1847
- const relativePath = roundPath(pathToRelative(pathArray), round);
1848
- return absolutePath.map((x, i) => {
1849
- if (i) {
1850
- return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
2431
+ /**
2432
+ * Returns the bounding box of a shape.
2433
+ *
2434
+ * @param {SVGPC.pathArray} path the shape `pathArray`
2435
+ * @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
2436
+ */
2437
+ function getPathBBox(path) {
2438
+ if (!path) {
2439
+ return {
2440
+ x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0, cx: 0, cy: 0,
2441
+ };
2442
+ }
2443
+ const pathCurve = pathToCurve(path);
2444
+ // @ts-ignore
2445
+ let x = 0; let y = 0; let X = []; let Y = [];
2446
+
2447
+ pathCurve.forEach((segment) => {
2448
+ const [s1, s2] = segment.slice(-2);
2449
+ if (segment[0] === 'M') {
2450
+ x = +s1;
2451
+ y = +s2;
2452
+ X.push(s1);
2453
+ Y.push(s2);
2454
+ } else {
2455
+ // @ts-ignore
2456
+ const dim = getCubicSize.apply(0, [x, y].concat(segment.slice(1)));
2457
+ // @ts-ignore
2458
+ X = X.concat(dim.min.x, dim.max.x);
2459
+ // @ts-ignore
2460
+ Y = Y.concat(dim.min.y, dim.max.y);
2461
+ x = +s1;
2462
+ y = +s2;
1851
2463
  }
1852
- return x;
1853
2464
  });
1854
- }
1855
2465
 
1856
- // reverse CURVE based pathArray segments only
1857
- function reverseCurve(pathArray) {
1858
- const rotatedCurve = pathArray.slice(1)
1859
- .map((x, i, curveOnly) => (!i
1860
- ? pathArray[0].slice(1).concat(x.slice(1))
1861
- : curveOnly[i - 1].slice(-2).concat(x.slice(1))))
1862
- .map((x) => x.map((y, i) => x[x.length - i - 2 * (1 - (i % 2))]))
1863
- .reverse();
2466
+ // @ts-ignore
2467
+ const xTop = Math.min.apply(0, X);
2468
+ // @ts-ignore
2469
+ const yTop = Math.min.apply(0, Y);
2470
+ // @ts-ignore
2471
+ const xBot = Math.max.apply(0, X);
2472
+ // @ts-ignore
2473
+ const yBot = Math.max.apply(0, Y);
2474
+ const width = xBot - xTop;
2475
+ const height = yBot - yTop;
1864
2476
 
1865
- return [['M'].concat(rotatedCurve[0]
1866
- .slice(0, 2))]
1867
- .concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
2477
+ return {
2478
+ width,
2479
+ height,
2480
+ x: xTop,
2481
+ y: yTop,
2482
+ x2: xBot,
2483
+ y2: yBot,
2484
+ cx: xTop + width / 2,
2485
+ cy: yTop + height / 2,
2486
+ };
1868
2487
  }
1869
2488
 
1870
- function reversePath(pathString) { // pathArray | pathString
1871
- const absolutePath = pathToAbsolute(pathString);
1872
- const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
1873
- let reversedPath = [];
1874
- let segLength = 0;
1875
-
1876
- reversedPath = normalizePath(absolutePath).map((segment, i) => {
1877
- segLength = segment.length;
1878
- return {
1879
- seg: absolutePath[i], // absolute
1880
- n: segment, // normalized
1881
- c: absolutePath[i][0], // pathCommand
1882
- x: segment[segLength - 2], // x
1883
- y: segment[segLength - 1], // y
1884
- };
1885
- }).map((seg, i, pathArray) => {
1886
- const segment = seg.seg;
1887
- const data = seg.n;
1888
- const prevSeg = i && pathArray[i - 1];
1889
- const nextSeg = pathArray[i + 1] && pathArray[i + 1];
1890
- 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;
1894
- let result = [];
2489
+ /**
2490
+ * Returns the area of a single segment shape.
2491
+ *
2492
+ * http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
2493
+ *
2494
+ * @param {number} x0 the starting point X
2495
+ * @param {number} y0 the starting point Y
2496
+ * @param {number} x1 the first control point X
2497
+ * @param {number} y1 the first control point Y
2498
+ * @param {number} x2 the second control point X
2499
+ * @param {number} y2 the second control point Y
2500
+ * @param {number} x3 the ending point X
2501
+ * @param {number} y3 the ending point Y
2502
+ * @returns {number} the area of the cubic-bezier segment
2503
+ */
2504
+ function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
2505
+ return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
2506
+ + (y1 * (x0 - x2)) - (x1 * (y0 - y2))
2507
+ + (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
2508
+ }
1895
2509
 
1896
- switch (pathCommand) {
2510
+ /**
2511
+ * Returns the area of a shape.
2512
+ * @author Jürg Lehni & Jonathan Puckey
2513
+ *
2514
+ * => https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
2515
+ *
2516
+ * @param {SVGPC.pathArray} path the shape `pathArray`
2517
+ * @returns {number} the length of the cubic-bezier segment
2518
+ */
2519
+ function getPathArea(path) {
2520
+ let x = 0; let y = 0;
2521
+ let mx = 0; let my = 0;
2522
+ let len = 0;
2523
+ return pathToCurve(path).map((seg) => {
2524
+ switch (seg[0]) {
1897
2525
  case 'M':
1898
- result = isClosed ? ['Z'] : [pathCommand, x, y];
1899
- break;
1900
- case 'A':
1901
- result = segment.slice(0, -3).concat([(segment[5] === 1 ? 0 : 1), x, y]);
1902
- break;
1903
- case 'C':
1904
- if (nextSeg && nextSeg.c === 'S') {
1905
- result = ['S', segment[1], segment[2], x, y];
1906
- } else {
1907
- result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y];
1908
- }
1909
- break;
1910
- case 'S':
1911
- if ((prevSeg && 'CS'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'S'))) {
1912
- result = ['C', data[3], data[4], data[1], data[2], x, y];
1913
- } else {
1914
- result = [pathCommand, data[1], data[2], x, y];
1915
- }
1916
- break;
1917
- case 'Q':
1918
- if (nextSeg && nextSeg.c === 'T') {
1919
- result = ['T', x, y];
1920
- } else {
1921
- result = segment.slice(0, -2).concat([x, y]);
1922
- }
1923
- break;
1924
- case 'T':
1925
- if ((prevSeg && 'QT'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'T'))) {
1926
- result = ['Q', data[1], data[2], x, y];
1927
- } else {
1928
- result = [pathCommand, x, y];
1929
- }
1930
- break;
1931
2526
  case 'Z':
1932
- result = ['M', x, y];
1933
- break;
1934
- case 'H':
1935
- result = [pathCommand, x];
1936
- break;
1937
- case 'V':
1938
- result = [pathCommand, y];
1939
- break;
2527
+ mx = seg[0] === 'M' ? seg[1] : mx;
2528
+ my = seg[0] === 'M' ? seg[2] : my;
2529
+ x = mx;
2530
+ y = my;
2531
+ return 0;
1940
2532
  default:
1941
- result = segment.slice(0, -2).concat([x, y]);
2533
+ // @ts-ignore
2534
+ len = getCubicSegArea.apply(0, [x, y].concat(seg.slice(1)));
2535
+ // @ts-ignore
2536
+ [x, y] = seg.slice(-2);
2537
+ return len;
1942
2538
  }
1943
-
1944
- return result;
1945
- });
1946
-
1947
- return isClosed ? reversedPath.reverse()
1948
- : [reversedPath[0]].concat(reversedPath.slice(1).reverse());
2539
+ }).reduce((a, b) => a + b, 0);
1949
2540
  }
1950
2541
 
1951
- var epsilon = 1e-9;
1952
-
1953
- function getSVGMatrix(transformObject) {
1954
- let matrix = new CSS3Matrix();
1955
- const { origin } = transformObject;
1956
- const originX = +origin[0];
1957
- 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)
1966
-
1967
- // set translate
1968
- if (!Number.isNaN(translate) || (Array.isArray(translate) && translate.some((x) => +x !== 0))) {
1969
- matrix = Array.isArray(translate)
1970
- ? matrix.translate(+translate[0] || 0, +translate[1] || 0, +translate[2] || 0)
1971
- : matrix.translate(+translate || 0, 0, 0);
1972
- }
1973
-
1974
- if (rotate || skew || scale) {
1975
- // set SVG transform-origin, always defined
1976
- // matrix = matrix.translate(+originX,+originY,+originZ)
1977
- matrix = matrix.translate(+originX, +originY);
1978
-
1979
- // set rotation
1980
- if (rotate) {
1981
- matrix = Array.isArray(rotate) && rotate.some((x) => +x !== 0)
1982
- ? matrix.rotate(+rotate[0] || 0, +rotate[1] || 0, +rotate[2] || 0)
1983
- : matrix.rotate(+rotate || 0);
1984
- }
1985
- // set skew(s)
1986
- if (Array.isArray(skew) && skew.some((x) => +x !== 0)) {
1987
- if (Array.isArray(skew)) {
1988
- matrix = skew[0] ? matrix.skewX(+skew[0] || 0) : matrix;
1989
- matrix = skew[1] ? matrix.skewY(+skew[1] || 0) : matrix;
1990
- } else {
1991
- matrix = matrix.skewX(+skew || 0);
1992
- }
1993
- }
1994
- // set scale
1995
- if (!Number.isNaN(scale) || (Array.isArray(scale) && scale.some((x) => +x !== 1))) {
1996
- matrix = Array.isArray(scale)
1997
- ? (matrix.scale(+scale[0] || 1, +scale[1] || 1, +scale[2] || 1))
1998
- : matrix.scale(+scale || 1);
1999
- }
2000
- // set SVG transform-origin
2001
- // matrix = matrix.translate(-originX,-originY,-originZ)
2002
- matrix = matrix.translate(-originX, -originY);
2003
- }
2004
- return matrix;
2542
+ /**
2543
+ * @param {number} p1
2544
+ * @param {number} p2
2545
+ * @param {number} p3
2546
+ * @param {number} p4
2547
+ * @param {number} t a [0-1] ratio
2548
+ * @returns {number}
2549
+ */
2550
+ function base3(p1, p2, p3, p4, t) {
2551
+ const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4;
2552
+ const t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
2553
+ return t * t2 - 3 * p1 + 3 * p2;
2005
2554
  }
2006
2555
 
2007
- function transformEllipse(m, rx, ry, ax) {
2008
- // We consider the current ellipse as image of the unit circle
2009
- // by first scale(rx,ry) and then rotate(ax) ...
2010
- // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle.
2011
- const c = Math.cos((ax * Math.PI) / 180);
2012
- const s = Math.sin((ax * Math.PI) / 180);
2013
- const ma = [
2014
- rx * (m[0] * c + m[2] * s),
2015
- rx * (m[1] * c + m[3] * s),
2016
- ry * (-m[0] * s + m[2] * c),
2017
- ry * (-m[1] * s + m[3] * c),
2018
- ];
2556
+ /**
2557
+ * Returns the C (cubic-bezier) segment length.
2558
+ *
2559
+ * @param {number} x1 the starting point X
2560
+ * @param {number} y1 the starting point Y
2561
+ * @param {number} x2 the first control point X
2562
+ * @param {number} y2 the first control point Y
2563
+ * @param {number} x3 the second control point X
2564
+ * @param {number} y3 the second control point Y
2565
+ * @param {number} x4 the ending point X
2566
+ * @param {number} y4 the ending point Y
2567
+ * @param {number} z a [0-1] ratio
2568
+ * @returns {number} the cubic-bezier segment length
2569
+ */
2570
+ function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
2571
+ let Z = z;
2572
+ if (z === null || Number.isNaN(+z)) Z = 1;
2019
2573
 
2020
- // ma * transpose(ma) = [ J L ]
2021
- // [ L K ]
2022
- // L is calculated later (if the image is not a circle)
2023
- const J = ma[0] * ma[0] + ma[2] * ma[2];
2024
- const K = ma[1] * ma[1] + ma[3] * ma[3];
2574
+ // Z = Z > 1 ? 1 : Z < 0 ? 0 : Z;
2575
+ if (Z > 1) Z = 1;
2576
+ if (Z < 0) Z = 0;
2025
2577
 
2026
- // the discriminant of the characteristic polynomial of ma * transpose(ma)
2027
- let D = ((ma[0] - ma[3]) * (ma[0] - ma[3]) + (ma[2] + ma[1]) * (ma[2] + ma[1]))
2028
- * ((ma[0] + ma[3]) * (ma[0] + ma[3]) + (ma[2] - ma[1]) * (ma[2] - ma[1]));
2578
+ const z2 = Z / 2; let ct = 0; let xbase = 0; let ybase = 0; let sum = 0;
2579
+ const Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678,
2580
+ -0.5873, 0.5873, -0.7699, 0.7699,
2581
+ -0.9041, 0.9041, -0.9816, 0.9816];
2582
+ const Cvalues = [0.2491, 0.2491, 0.2335, 0.2335,
2583
+ 0.2032, 0.2032, 0.1601, 0.1601,
2584
+ 0.1069, 0.1069, 0.0472, 0.0472];
2029
2585
 
2030
- // the "mean eigenvalue"
2031
- const JK = (J + K) / 2;
2586
+ Tvalues.forEach((T, i) => {
2587
+ ct = z2 * T + z2;
2588
+ xbase = base3(x1, x2, x3, x4, ct);
2589
+ ybase = base3(y1, y2, y3, y4, ct);
2590
+ sum += Cvalues[i] * Math.sqrt(xbase * xbase + ybase * ybase);
2591
+ });
2592
+ return z2 * sum;
2593
+ }
2032
2594
 
2033
- // check if the image is (almost) a circle
2034
- if (D < epsilon * JK) {
2035
- // if it is
2036
- const rxy = Math.sqrt(JK);
2595
+ /**
2596
+ * Returns the shape total length,
2597
+ * or the equivalent to `shape.getTotalLength()`
2598
+ * pathToCurve version
2599
+ *
2600
+ * @param {SVGPC.pathArray} path the ending point Y
2601
+ * @returns {number} the shape total length
2602
+ */
2603
+ function getPathLength(path) {
2604
+ let totalLength = 0;
2605
+ pathToCurve(path).forEach((s, i, curveArray) => {
2606
+ totalLength += s[0] === 'M' ? 0
2607
+ // @ts-ignore
2608
+ : getSegCubicLength.apply(0, curveArray[i - 1].slice(-2).concat(s.slice(1)));
2609
+ });
2610
+ return totalLength;
2611
+ }
2037
2612
 
2038
- return { rx: rxy, ry: rxy, ax: 0 };
2039
- }
2613
+ /**
2614
+ * Check if a path is drawn clockwise and returns true if so,
2615
+ * false otherwise.
2616
+ *
2617
+ * @param {string | SVGPC.pathArray} path the path string or `pathArray`
2618
+ * @returns {boolean} true when clockwise or false if not
2619
+ */
2620
+ function getDrawDirection(path) {
2621
+ return getPathArea(pathToCurve(path)) >= 0;
2622
+ }
2040
2623
 
2041
- // if it is not a circle
2042
- const L = ma[0] * ma[1] + ma[2] * ma[3];
2624
+ /**
2625
+ * Returns [x,y] coordinates of a point at a given length of a shape.
2626
+ *
2627
+ * @param {string | SVGPC.pathArray} path the `pathArray` to look into
2628
+ * @param {number} length the length of the shape to look at
2629
+ * @returns {number[]} the requested [x,y] coordinates
2630
+ */
2631
+ function getPointAtLength(path, length) {
2632
+ let totalLength = 0;
2633
+ let segLen;
2634
+ let data;
2635
+ let result;
2636
+ // @ts-ignore
2637
+ return pathToCurve(path).map((seg, i, curveArray) => {
2638
+ data = i ? curveArray[i - 1].slice(-2).concat(seg.slice(1)) : seg.slice(1);
2639
+ // @ts-ignore
2640
+ segLen = i ? getSegCubicLength.apply(0, data) : 0;
2641
+ totalLength += segLen;
2043
2642
 
2044
- D = Math.sqrt(D);
2643
+ if (i === 0) {
2644
+ result = { x: data[0], y: data[1] };
2645
+ } else if (totalLength > length && length > totalLength - segLen) {
2646
+ // @ts-ignore
2647
+ result = getPointAtSegLength.apply(0, data.concat(1 - (totalLength - length) / segLen));
2648
+ } else {
2649
+ result = null;
2650
+ }
2045
2651
 
2046
- // {l1,l2} = the two eigen values of ma * transpose(ma)
2047
- const l1 = JK + D / 2;
2048
- const l2 = JK - D / 2;
2049
- // the x - axis - rotation angle is the argument of the l1 - eigenvector
2050
- let AX = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? 90
2051
- : Math.atan(Math.abs(L) > Math.abs(l1 - K) ? (l1 - J) / L
2052
- : ((L / (l1 - K))) * 180) / Math.PI;
2053
- let RX;
2054
- let RY;
2652
+ return result;
2653
+ }).filter((x) => x).slice(-1)[0]; // isolate last segment
2654
+ }
2055
2655
 
2056
- // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90
2057
- if (AX >= 0) {
2058
- // if ax in [0,90]
2059
- RX = Math.sqrt(l1);
2060
- RY = Math.sqrt(l2);
2061
- } else {
2062
- // if ax in ]-90,0[ => exchange axes
2063
- AX += 90;
2064
- RX = Math.sqrt(l2);
2065
- RY = Math.sqrt(l1);
2656
+ /**
2657
+ * Parses a path string value to determine its validity
2658
+ * then returns true if it's valid or false otherwise.
2659
+ *
2660
+ * @param {string} pathString the path string to be parsed
2661
+ * @returns {boolean} the path string validity
2662
+ */
2663
+ function isValidPath(pathString) {
2664
+ if (typeof pathString !== 'string') {
2665
+ return false;
2066
2666
  }
2067
2667
 
2068
- return { rx: RX, ry: RY, ax: AX };
2069
- }
2668
+ const path = new SVGPathArray(pathString);
2070
2669
 
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
2670
+ skipSpaces(path);
2075
2671
 
2076
- function projection2d(m, point2D, origin) {
2077
- const point3D = m.transformPoint({
2078
- x: point2D[0], y: point2D[1], z: 0, w: 1,
2079
- });
2080
- const originX = origin[0] || 0;
2081
- const originY = origin[1] || 0;
2082
- const originZ = origin[2] || 0;
2083
- const relativePositionX = point3D.x - originX;
2084
- const relativePositionY = point3D.y - originY;
2085
- const relativePositionZ = point3D.z - originZ;
2672
+ while (path.index < path.max && !path.err.length) {
2673
+ scanSegment(path);
2674
+ }
2086
2675
 
2087
- return [
2088
- relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originX,
2089
- relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originY,
2090
- ];
2676
+ return !path.err.length && 'mM'.includes(path.segments[0][0]);
2091
2677
  }
2092
2678
 
2093
- function transformPath(pathArray, transformObject) {
2094
- let x; let y; let i; let j; let ii; let jj; let lx; let ly; let te;
2095
- const absolutePath = pathToAbsolute(pathArray);
2096
- const normalizedPath = normalizePath(absolutePath);
2097
- const matrixInstance = getSVGMatrix(transformObject);
2098
- const transformProps = Object.keys(transformObject);
2099
- const { origin } = transformObject;
2679
+ /**
2680
+ * Supported shapes and their specific parameters.
2681
+ */
2682
+ const shapeParams = {
2683
+ circle: ['cx', 'cy', 'r'],
2684
+ ellipse: ['cx', 'cy', 'rx', 'ry'],
2685
+ rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
2686
+ polygon: ['points'],
2687
+ polyline: ['points'],
2688
+ glyph: [],
2689
+ };
2690
+
2691
+ /**
2692
+ * Returns a new `pathArray` from line attributes.
2693
+ *
2694
+ * @param {SVGPC.lineAttr} attr shape configuration
2695
+ * @return {SVGPC.pathArray} a new line `pathArray`
2696
+ */
2697
+ function getLinePath(attr) {
2100
2698
  const {
2101
- a, b, c, d, e, f,
2102
- } = matrixInstance;
2103
- const matrix2d = [a, b, c, d, e, f];
2104
- const params = {
2105
- x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0,
2106
- };
2107
- let segment = [];
2108
- let seglen = 0;
2109
- let pathCommand = '';
2110
- let transformedPath = [];
2111
- const allPathCommands = []; // needed for arc to curve transformation
2112
- let result = [];
2699
+ x1, y1, x2, y2,
2700
+ } = attr;
2701
+ return [['M', +x1, +y1], ['L', +x2, +y2]];
2702
+ }
2113
2703
 
2114
- if (!matrixInstance.isIdentity) {
2115
- for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
2116
- segment = absolutePath[i];
2704
+ /**
2705
+ * Returns a new `pathArray` like from polyline/polygon attributes.
2706
+ *
2707
+ * @param {SVGPC.polyAttr} attr shape configuration
2708
+ * @return {SVGPC.pathArray} a new polygon/polyline `pathArray`
2709
+ */
2710
+ function getPolyPath(attr) {
2711
+ /** @type {SVGPC.pathArray} */
2712
+ const pathArray = [];
2713
+ const points = attr.points.split(/[\s|,]/).map(Number);
2117
2714
 
2118
- if (absolutePath[i]) [pathCommand] = segment;
2715
+ let index = 0;
2716
+ while (index < points.length) {
2717
+ pathArray.push([(index ? 'L' : 'M'), (points[index]), (points[index + 1])]);
2718
+ index += 2;
2719
+ }
2119
2720
 
2120
- // REPLACE Arc path commands with Cubic Beziers
2121
- // we don't have any scripting know-how on 3d ellipse transformation
2122
- /// ////////////////////////////////////////
2123
- allPathCommands[i] = pathCommand;
2721
+ return attr.type === 'polygon' ? pathArray.concat([['z']]) : pathArray;
2722
+ }
2124
2723
 
2125
- // Arcs don't work very well with 3D transformations or skews
2126
- if (pathCommand === 'A' && (!matrixInstance.is2D || !['skewX', 'skewY'].find((p) => transformProps.includes(p)))) {
2127
- segment = segmentToCubic(normalizedPath[i], params);
2724
+ /**
2725
+ * Returns a new `pathArray` from circle attributes.
2726
+ *
2727
+ * @param {SVGPC.circleAttr} attr shape configuration
2728
+ * @return {SVGPC.pathArray} a circle `pathArray`
2729
+ */
2730
+ function getCirclePath(attr) {
2731
+ const {
2732
+ cx, cy, r,
2733
+ } = attr;
2128
2734
 
2129
- absolutePath[i] = segmentToCubic(normalizedPath[i], params);
2130
- fixArc(absolutePath, allPathCommands, i);
2735
+ return [
2736
+ ['M', (cx - r), cy],
2737
+ ['a', r, r, 0, 1, 0, (2 * r), 0],
2738
+ ['a', r, r, 0, 1, 0, (-2 * r), 0],
2739
+ ];
2740
+ }
2131
2741
 
2132
- normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
2133
- fixArc(normalizedPath, allPathCommands, i);
2134
- ii = Math.max(absolutePath.length, normalizedPath.length);
2135
- }
2136
- /// ////////////////////////////////////////
2742
+ /**
2743
+ * Returns a new `pathArray` from ellipse attributes.
2744
+ *
2745
+ * @param {SVGPC.ellipseAttr} attr shape configuration
2746
+ * @return {SVGPC.pathArray} an ellipse `pathArray`
2747
+ */
2748
+ function getEllipsePath(attr) {
2749
+ const {
2750
+ cx, cy, rx, ry,
2751
+ } = attr;
2137
2752
 
2138
- segment = normalizedPath[i];
2139
- seglen = segment.length;
2753
+ return [
2754
+ ['M', (cx - rx), cy],
2755
+ ['a', rx, ry, 0, 1, 0, (2 * rx), 0],
2756
+ ['a', rx, ry, 0, 1, 0, (-2 * rx), 0],
2757
+ ];
2758
+ }
2140
2759
 
2141
- params.x1 = +segment[seglen - 2];
2142
- params.y1 = +segment[seglen - 1];
2143
- params.x2 = +(segment[seglen - 4]) || params.x1;
2144
- params.y2 = +(segment[seglen - 3]) || params.y1;
2760
+ /**
2761
+ * Returns a new `pathArray` like from rect attributes.
2762
+ *
2763
+ * @param {SVGPC.rectAttr} attr object with properties above
2764
+ * @return {SVGPC.pathArray} a new `pathArray` from `<rect>` attributes
2765
+ */
2766
+ function getRectanglePath(attr) {
2767
+ const x = +attr.x || 0;
2768
+ const y = +attr.y || 0;
2769
+ const w = +attr.width;
2770
+ const h = +attr.height;
2771
+ let rx = +attr.rx;
2772
+ let ry = +attr.ry;
2773
+
2774
+ // Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
2775
+ if (rx || ry) {
2776
+ rx = !rx ? ry : rx;
2777
+ ry = !ry ? rx : ry;
2778
+
2779
+ if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
2780
+ if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
2781
+
2782
+ return [
2783
+ ['M', x + rx, y],
2784
+ ['h', w - rx * 2],
2785
+ ['s', rx, 0, rx, ry],
2786
+ ['v', h - ry * 2],
2787
+ ['s', 0, ry, -rx, ry],
2788
+ ['h', -w + rx * 2],
2789
+ ['s', -rx, 0, -rx, -ry],
2790
+ ['v', -h + ry * 2],
2791
+ ['s', 0, -ry, rx, -ry],
2792
+ ];
2793
+ }
2145
2794
 
2146
- result = { s: absolutePath[i], c: absolutePath[i][0] };
2795
+ return [
2796
+ ['M', x, y],
2797
+ ['h', w],
2798
+ ['v', h],
2799
+ ['H', x],
2800
+ ['Z'],
2801
+ ];
2802
+ }
2147
2803
 
2148
- if (pathCommand !== 'Z') {
2149
- result.x = params.x1;
2150
- result.y = params.y1;
2151
- }
2152
- transformedPath = transformedPath.concat(result);
2153
- }
2804
+ /**
2805
+ * Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
2806
+ * `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
2807
+ * is `true`, it will replace the target.
2808
+ *
2809
+ * The newly created `<path>` element keeps all non-specific
2810
+ * attributes like `class`, `fill`, etc.
2811
+ *
2812
+ * @param {SVGPC.shapeTypes} element target shape
2813
+ * @param {boolean} replace option to replace target
2814
+ * @return {?SVGPathElement} the newly created `<path>` element
2815
+ */
2816
+ function shapeToPath(element, replace) {
2817
+ const supportedShapes = Object.keys(shapeParams).concat(['glyph']);
2154
2818
 
2155
- transformedPath = transformedPath.map((seg) => {
2156
- pathCommand = seg.c;
2157
- segment = seg.s;
2158
- switch (pathCommand) {
2159
- case 'A': // only apply to 2D transformations
2160
- te = transformEllipse(matrix2d, segment[1], segment[2], segment[3]);
2819
+ if (!supportedShapes.some((s) => element.tagName === s)) {
2820
+ throw TypeError(`shapeToPath: ${element} is not SVGElement`);
2821
+ }
2161
2822
 
2162
- if (matrix2d[0] * matrix2d[3] - matrix2d[1] * matrix2d[2] < 0) {
2163
- segment[5] = +segment[5] ? 0 : 1;
2164
- }
2823
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2824
+ const type = element.tagName;
2825
+ /** @ts-ignore */
2826
+ const shapeAttrs = shapeParams[type];
2165
2827
 
2166
- [lx, ly] = projection2d(matrixInstance, [segment[6], segment[7]], origin);
2828
+ // set config
2829
+ const config = {};
2830
+ config.type = type;
2831
+ /** @ts-ignore */
2832
+ shapeAttrs.forEach((p) => { config[p] = element.getAttribute(p); });
2167
2833
 
2168
- if ((x === lx && y === ly) || (te.rx < epsilon * te.ry) || (te.ry < epsilon * te.rx)) {
2169
- segment = ['L', lx, ly];
2170
- } else {
2171
- segment = [pathCommand, te.rx, te.ry, te.ax, segment[4], segment[5], lx, ly];
2172
- }
2834
+ // set no-specific shape attributes: fill, stroke, etc
2835
+ Object.values(element.attributes).forEach(({ name, value }) => {
2836
+ if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
2837
+ });
2173
2838
 
2174
- x = lx; y = ly;
2175
- return segment;
2839
+ // set d
2840
+ let description;
2841
+ /** @ts-ignore */
2842
+ if (type === 'circle') description = pathToString(getCirclePath(config));
2843
+ /** @ts-ignore */
2844
+ else if (type === 'ellipse') description = pathToString(getEllipsePath(config));
2845
+ /** @ts-ignore */
2846
+ else if (['polyline', 'polygon'].includes(type)) description = pathToString(getPolyPath(config));
2847
+ /** @ts-ignore */
2848
+ else if (type === 'rect') description = pathToString(getRectanglePath(config));
2849
+ /** @ts-ignore */
2850
+ else if (type === 'line') description = pathToString(getLinePath(config));
2851
+ else if (type === 'glyph') description = element.getAttribute('d');
2852
+
2853
+ // replace target element
2854
+ if (description) {
2855
+ path.setAttribute('d', description);
2856
+ if (replace) {
2857
+ element.before(path, element);
2858
+ element.remove();
2859
+ }
2860
+ return path;
2861
+ }
2862
+ return null;
2863
+ }
2176
2864
 
2177
- case 'L':
2178
- case 'H':
2179
- case 'V':
2865
+ /**
2866
+ * Reverses all segments and their values from a `pathArray`
2867
+ * which consists of only C (cubic-bezier) path commands.
2868
+ *
2869
+ * @param {SVGPC.pathArray} path the source `pathArray`
2870
+ * @returns {SVGPC.pathArray} the reversed `pathArray`
2871
+ */
2872
+ function reverseCurve(path) {
2873
+ const rotatedCurve = path.slice(1)
2874
+ .map((x, i, curveOnly) => (!i
2875
+ ? path[0].slice(1).concat(x.slice(1))
2876
+ : curveOnly[i - 1].slice(-2).concat(x.slice(1))))
2877
+ .map((x) => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
2878
+ .reverse();
2180
2879
 
2181
- [lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin);
2880
+ // @ts-ignore
2881
+ return [['M'].concat(rotatedCurve[0].slice(0, 2))]
2882
+ // @ts-ignore
2883
+ .concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
2884
+ }
2182
2885
 
2183
- if (x !== lx && y !== ly) {
2184
- segment = ['L', lx, ly];
2185
- } else if (y === ly) {
2186
- segment = ['H', lx];
2187
- } else if (x === lx) {
2188
- segment = ['V', ly];
2189
- }
2886
+ var version = "0.1.10alpha3";
2190
2887
 
2191
- x = lx; y = ly; // now update x and y
2888
+ // @ts-ignore
2192
2889
 
2193
- return segment;
2194
- default:
2195
- for (j = 1, jj = segment.length; j < jj; j += 2) {
2196
- // compute line coordinates without altering previous coordinates
2197
- [x, y] = projection2d(matrixInstance, [segment[j], segment[j + 1]], origin);
2198
- segment[j] = x;
2199
- segment[j + 1] = y;
2200
- }
2201
- return segment;
2202
- }
2203
- });
2204
- return transformedPath;
2205
- }
2206
- return clonePath(absolutePath);
2207
- }
2890
+ /**
2891
+ * A global namespace for library version.
2892
+ * @type {string}
2893
+ */
2894
+ const Version = version;
2208
2895
 
2209
2896
  const Util = {
2210
- CSSMatrix: CSS3Matrix,
2897
+ CSSMatrix,
2211
2898
  parsePathString,
2212
2899
  isPathArray,
2213
2900
  isCurveArray,
2214
2901
  isAbsoluteArray,
2215
2902
  isRelativeArray,
2216
2903
  isNormalizedArray,
2904
+ isValidPath,
2217
2905
  pathToAbsolute,
2218
2906
  pathToRelative,
2219
2907
  pathToCurve,
@@ -2232,57 +2920,44 @@ const Util = {
2232
2920
  normalizePath,
2233
2921
  transformPath,
2234
2922
  getSVGMatrix,
2923
+ shapeToPath,
2235
2924
  options: SVGPCO,
2925
+ Version,
2236
2926
  };
2237
2927
 
2928
+ // import isPathArray from './util/isPathArray';
2929
+
2238
2930
  /**
2239
2931
  * Creates a new SVGPathCommander instance.
2932
+ *
2933
+ * @author thednp <https://github.com/thednp/svg-path-commander>
2240
2934
  * @class
2241
2935
  */
2242
2936
  class SVGPathCommander {
2243
2937
  /**
2244
2938
  * @constructor
2245
- * @param {String} pathValue the path string
2246
- * @param {Object} config instance options
2939
+ * @param {string} pathValue the path string
2940
+ * @param {any} config instance options
2247
2941
  */
2248
2942
  constructor(pathValue, config) {
2249
2943
  const options = config || {};
2250
- // check for either true or > 0
2251
- // const roundOption = +options.round === 0 || options.round === false ? 0 : SVGPCO.round;
2944
+
2252
2945
  let { round } = SVGPCO;
2253
2946
  const { round: roundOption } = options;
2254
- if (+roundOption === 0 || roundOption === false) {
2947
+ if ((roundOption && +roundOption === 0) || roundOption === false) {
2255
2948
  round = 0;
2256
2949
  }
2257
2950
 
2258
2951
  const { decimals } = round && (options || SVGPCO);
2259
- const { origin } = options;
2260
2952
 
2261
2953
  // set instance options
2262
- /**
2263
- * @type {Boolean | Number}
2264
- */
2265
2954
  this.round = round === 0 ? 0 : decimals;
2266
2955
  // ZERO | FALSE will disable rounding numbers
2267
2956
 
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
- */
2957
+ /** @type {SVGPC.pathArray} */
2281
2958
  this.segments = parsePathString(pathValue);
2282
2959
 
2283
- /**
2284
- * @type {String}
2285
- */
2960
+ /** * @type {string} */
2286
2961
  this.pathValue = pathValue;
2287
2962
 
2288
2963
  return this;
@@ -2309,25 +2984,30 @@ class SVGPathCommander {
2309
2984
  }
2310
2985
 
2311
2986
  /**
2312
- * Reverse path
2313
- * @param {Boolean | Number} onlySubpath option to reverse all pathArray(s) except first
2987
+ * Reverse the order of the segments and their values.
2988
+ * @param {boolean | number} onlySubpath option to reverse all sub-paths except first
2314
2989
  * @public
2315
2990
  */
2316
2991
  reverse(onlySubpath) {
2317
2992
  this.toAbsolute();
2318
2993
 
2319
2994
  const { segments } = this;
2320
- const subPath = splitPath(this.pathValue).length > 1 && splitPath(this.toString());
2995
+ const split = splitPath(this.toString());
2996
+ const subPath = split.length > 1 ? split : 0;
2997
+
2321
2998
  const absoluteMultiPath = subPath && clonePath(subPath)
2322
2999
  .map((x, i) => {
2323
3000
  if (onlySubpath) {
3001
+ // @ts-ignore
2324
3002
  return i ? reversePath(x) : parsePathString(x);
2325
3003
  }
3004
+ // @ts-ignore
2326
3005
  return reversePath(x);
2327
3006
  });
2328
3007
 
2329
3008
  let path = [];
2330
3009
  if (subPath) {
3010
+ // @ts-ignore
2331
3011
  path = absoluteMultiPath.flat(1);
2332
3012
  } else {
2333
3013
  path = onlySubpath ? segments : reversePath(segments);
@@ -2339,7 +3019,7 @@ class SVGPathCommander {
2339
3019
 
2340
3020
  /**
2341
3021
  * Normalize path in 2 steps:
2342
- * * convert pathArray(s) to absolute values
3022
+ * * convert `pathArray`(s) to absolute values
2343
3023
  * * convert shorthand notation to standard notation
2344
3024
  * @public
2345
3025
  */
@@ -2350,7 +3030,7 @@ class SVGPathCommander {
2350
3030
  }
2351
3031
 
2352
3032
  /**
2353
- * Optimize pathArray values:
3033
+ * Optimize `pathArray` values:
2354
3034
  * * convert segments to absolute and/or relative values
2355
3035
  * * select segments with shortest resulted string
2356
3036
  * * round values to the specified `decimals` option value
@@ -2364,31 +3044,27 @@ class SVGPathCommander {
2364
3044
  }
2365
3045
 
2366
3046
  /**
2367
- * Transform path using values from an `Object`
2368
- * with the following structure:
3047
+ * Transform path using values from an `Object` defined as `transformObject`.
3048
+ * @see SVGPC.transformObject for a quick refference
2369
3049
  *
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
3050
+ * @param {Object.<string, (number | number[])>} source a `transformObject`as described above
2378
3051
  * @public
2379
3052
  */
2380
3053
  transform(source) {
2381
- const transformObject = source || {};
3054
+ if (!source || typeof source !== 'object' || (typeof source === 'object'
3055
+ && !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
3056
+
3057
+ const transform = source || {};
2382
3058
  const { segments } = this;
2383
3059
 
2384
3060
  // if origin is not specified
2385
3061
  // it's important that we have one
2386
- if (!transformObject.origin) {
3062
+ if (!transform.origin) {
2387
3063
  const BBox = getPathBBox(segments);
2388
- transformObject.origin = [BBox.cx, BBox.cy, BBox.cx];
3064
+ transform.origin = [BBox.cx, BBox.cy, BBox.cx];
2389
3065
  }
2390
3066
 
2391
- this.segments = transformPath(segments, transformObject);
3067
+ this.segments = transformPath(segments, transform);
2392
3068
  return this;
2393
3069
  }
2394
3070
 
@@ -2421,6 +3097,7 @@ class SVGPathCommander {
2421
3097
  }
2422
3098
  }
2423
3099
 
3100
+ // @ts-ignore
2424
3101
  Object.keys(Util).forEach((x) => { SVGPathCommander[x] = Util[x]; });
2425
3102
 
2426
3103
  export { SVGPathCommander as default };