svg-path-commander 1.0.4 → 2.0.0-alpha1

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 (132) hide show
  1. package/README.md +29 -7
  2. package/dist/svg-path-commander.cjs +2 -0
  3. package/dist/svg-path-commander.cjs.map +1 -0
  4. package/dist/svg-path-commander.d.ts +454 -0
  5. package/dist/svg-path-commander.js +2 -3775
  6. package/dist/svg-path-commander.js.map +1 -0
  7. package/dist/svg-path-commander.mjs +1194 -0
  8. package/dist/svg-path-commander.mjs.map +1 -0
  9. package/package.json +39 -42
  10. package/src/convert/pathToAbsolute.ts +101 -0
  11. package/src/convert/{pathToCurve.js → pathToCurve.ts} +13 -15
  12. package/src/convert/pathToRelative.ts +92 -0
  13. package/src/convert/pathToString.ts +17 -0
  14. package/src/{svg-path-commander.js → index.ts} +143 -67
  15. package/src/interface.ts +129 -0
  16. package/src/math/distanceSquareRoot.ts +13 -0
  17. package/src/math/midPoint.ts +16 -0
  18. package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
  19. package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
  20. package/src/math/rotateVector.ts +16 -0
  21. package/src/options/options.ts +9 -0
  22. package/src/parser/error.ts +2 -0
  23. package/src/parser/finalizeSegment.ts +31 -0
  24. package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
  25. package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
  26. package/src/parser/isDigit.ts +12 -0
  27. package/src/parser/isDigitStart.ts +14 -0
  28. package/src/parser/isPathCommand.ts +28 -0
  29. package/src/parser/isSpace.ts +23 -0
  30. package/src/parser/paramsCount.ts +16 -0
  31. package/src/parser/paramsParser.ts +14 -0
  32. package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
  33. package/src/parser/pathParser.ts +29 -0
  34. package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
  35. package/src/parser/{scanParam.js → scanParam.ts} +12 -11
  36. package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
  37. package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
  38. package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
  39. package/src/process/fixArc.ts +23 -0
  40. package/src/process/getSVGMatrix.ts +70 -0
  41. package/src/process/lineToCubic.ts +17 -0
  42. package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
  43. package/src/process/normalizeSegment.ts +47 -0
  44. package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
  45. package/src/process/{projection2d.js → projection2d.ts} +16 -15
  46. package/src/process/quadToCubic.ts +31 -0
  47. package/src/process/reverseCurve.ts +21 -0
  48. package/src/process/reversePath.ts +101 -0
  49. package/src/process/roundPath.ts +29 -0
  50. package/src/process/segmentToCubic.ts +46 -0
  51. package/src/process/shortenSegment.ts +79 -0
  52. package/src/process/splitCubic.ts +28 -0
  53. package/src/process/{splitPath.js → splitPath.ts} +9 -8
  54. package/src/process/{transformPath.js → transformPath.ts} +55 -48
  55. package/src/types.ts +193 -0
  56. package/src/util/getClosestPoint.ts +15 -0
  57. package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
  58. package/src/util/getPathArea.ts +70 -0
  59. package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
  60. package/src/util/getPointAtLength.ts +14 -0
  61. package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
  62. package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
  63. package/src/util/getSegmentAtLength.ts +15 -0
  64. package/src/util/getSegmentOfPoint.ts +18 -0
  65. package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
  66. package/src/util/isAbsoluteArray.ts +18 -0
  67. package/src/util/isCurveArray.ts +15 -0
  68. package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +5 -3
  69. package/src/util/isPathArray.ts +19 -0
  70. package/src/util/isPointInStroke.ts +15 -0
  71. package/src/util/isRelativeArray.ts +18 -0
  72. package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
  73. package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
  74. package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +72 -56
  75. package/src/util/segmentCubicFactory.ts +114 -0
  76. package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +11 -9
  77. package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +48 -33
  78. package/src/util/shapeToPath.ts +214 -0
  79. package/dist/svg-path-commander.es5.js +0 -3888
  80. package/dist/svg-path-commander.es5.min.js +0 -2
  81. package/dist/svg-path-commander.esm.js +0 -3767
  82. package/dist/svg-path-commander.esm.min.js +0 -2
  83. package/dist/svg-path-commander.min.js +0 -2
  84. package/src/convert/pathToAbsolute.js +0 -93
  85. package/src/convert/pathToRelative.js +0 -84
  86. package/src/convert/pathToString.js +0 -14
  87. package/src/index.js +0 -10
  88. package/src/math/distanceSquareRoot.js +0 -14
  89. package/src/math/epsilon.js +0 -8
  90. package/src/math/midPoint.js +0 -13
  91. package/src/math/rotateVector.js +0 -14
  92. package/src/options/options.js +0 -10
  93. package/src/parser/error.js +0 -2
  94. package/src/parser/finalizeSegment.js +0 -28
  95. package/src/parser/isDigit.js +0 -9
  96. package/src/parser/isDigitStart.js +0 -13
  97. package/src/parser/isPathCommand.js +0 -25
  98. package/src/parser/isSpace.js +0 -16
  99. package/src/parser/paramsCount.js +0 -9
  100. package/src/parser/paramsParser.js +0 -8
  101. package/src/parser/pathParser.js +0 -25
  102. package/src/process/clonePath.js +0 -9
  103. package/src/process/fixArc.js +0 -22
  104. package/src/process/fixPath.js +0 -31
  105. package/src/process/getSVGMatrix.js +0 -61
  106. package/src/process/lineToCubic.js +0 -33
  107. package/src/process/normalizeSegment.js +0 -45
  108. package/src/process/quadToCubic.js +0 -22
  109. package/src/process/reverseCurve.js +0 -18
  110. package/src/process/reversePath.js +0 -89
  111. package/src/process/roundPath.js +0 -26
  112. package/src/process/segmentToCubic.js +0 -46
  113. package/src/process/shortenSegment.js +0 -58
  114. package/src/process/splitCubic.js +0 -26
  115. package/src/util/getClosestPoint.js +0 -12
  116. package/src/util/getPathArea.js +0 -47
  117. package/src/util/getPointAtLength.js +0 -12
  118. package/src/util/getSegmentAtLength.js +0 -11
  119. package/src/util/getSegmentOfPoint.js +0 -12
  120. package/src/util/isAbsoluteArray.js +0 -14
  121. package/src/util/isCurveArray.js +0 -13
  122. package/src/util/isPathArray.js +0 -14
  123. package/src/util/isPointInStroke.js +0 -13
  124. package/src/util/isRelativeArray.js +0 -14
  125. package/src/util/segmentCubicFactory.js +0 -97
  126. package/src/util/shapeToPath.js +0 -204
  127. package/src/util/util.js +0 -82
  128. package/src/version.js +0 -8
  129. package/types/index.d.ts +0 -120
  130. package/types/more/modules.ts +0 -82
  131. package/types/more/svg.d.ts +0 -211
  132. package/types/svg-path-commander.d.ts +0 -1089
@@ -1,3775 +1,2 @@
1
- /*!
2
- * SVGPathCommander v1.0.4 (http://thednp.github.io/svg-path-commander)
3
- * Copyright 2022 © thednp
4
- * Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
5
- */
6
- (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
- typeof define === 'function' && define.amd ? define(factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SVGPathCommander = factory());
10
- })(this, (function () { 'use strict';
11
-
12
- /**
13
- * SVGPathCommander default options
14
- * @type {SVGPath.options}
15
- */
16
- const defaultOptions = {
17
- origin: [0, 0, 0],
18
- round: 4,
19
- };
20
-
21
- /**
22
- * Segment params length
23
- * @type {Record<string, number>}
24
- */
25
- const paramsCount = {
26
- a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
27
- };
28
-
29
- /**
30
- * Breaks the parsing of a pathString once a segment is finalized.
31
- *
32
- * @param {SVGPath.PathParser} path the `PathParser` instance
33
- */
34
- function finalizeSegment(path) {
35
- let pathCommand = path.pathValue[path.segmentStart];
36
- let LK = pathCommand.toLowerCase();
37
- const { data } = path;
38
-
39
- while (data.length >= paramsCount[LK]) {
40
- // overloaded `moveTo`
41
- // https://github.com/rveciana/svg-path-properties/blob/master/src/parse.ts
42
- if (LK === 'm' && data.length > 2) {
43
- path.segments.push([pathCommand, ...data.splice(0, 2)]);
44
- LK = 'l';
45
- pathCommand = pathCommand === 'm' ? 'l' : 'L';
46
- } else {
47
- path.segments.push([pathCommand, ...data.splice(0, paramsCount[LK])]);
48
- }
49
-
50
- if (!paramsCount[LK]) {
51
- break;
52
- }
53
- }
54
- }
55
-
56
- const error = 'SVGPathCommander error';
57
-
58
- /**
59
- * Validates an A (arc-to) specific path command value.
60
- * Usually a `large-arc-flag` or `sweep-flag`.
61
- *
62
- * @param {SVGPath.PathParser} path the `PathParser` instance
63
- */
64
- function scanFlag(path) {
65
- const { index, pathValue } = path;
66
- const code = pathValue.charCodeAt(index);
67
-
68
- if (code === 0x30/* 0 */) {
69
- path.param = 0;
70
- path.index += 1;
71
- return;
72
- }
73
-
74
- if (code === 0x31/* 1 */) {
75
- path.param = 1;
76
- path.index += 1;
77
- return;
78
- }
79
-
80
- path.err = `${error}: invalid Arc flag "${pathValue[index]}", expecting 0 or 1 at index ${index}`;
81
- }
82
-
83
- /**
84
- * Checks if a character is a digit.
85
- *
86
- * @param {number} code the character to check
87
- * @returns {boolean} check result
88
- */
89
- function isDigit(code) {
90
- return (code >= 48 && code <= 57); // 0..9
91
- }
92
-
93
- const invalidPathValue = 'Invalid path value';
94
-
95
- /**
96
- * Validates every character of the path string,
97
- * every path command, negative numbers or floating point numbers.
98
- *
99
- * @param {SVGPath.PathParser} path the `PathParser` instance
100
- */
101
- function scanParam(path) {
102
- const { max, pathValue, index: start } = path;
103
- let index = start;
104
- let zeroFirst = false;
105
- let hasCeiling = false;
106
- let hasDecimal = false;
107
- let hasDot = false;
108
- let ch;
109
-
110
- if (index >= max) {
111
- // path.err = 'SvgPath: missed param (at pos ' + index + ')';
112
- path.err = `${error}: ${invalidPathValue} at index ${index}, "pathValue" is missing param`;
113
- return;
114
- }
115
- ch = pathValue.charCodeAt(index);
116
-
117
- if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
118
- index += 1;
119
- // ch = (index < max) ? pathValue.charCodeAt(index) : 0;
120
- ch = pathValue.charCodeAt(index);
121
- }
122
-
123
- // This logic is shamelessly borrowed from Esprima
124
- // https://github.com/ariya/esprimas
125
- if (!isDigit(ch) && ch !== 0x2E/* . */) {
126
- // path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
127
- path.err = `${error}: ${invalidPathValue} at index ${index}, "${pathValue[index]}" is not a number`;
128
- return;
129
- }
130
-
131
- if (ch !== 0x2E/* . */) {
132
- zeroFirst = (ch === 0x30/* 0 */);
133
- index += 1;
134
-
135
- ch = pathValue.charCodeAt(index);
136
-
137
- if (zeroFirst && index < max) {
138
- // decimal number starts with '0' such as '09' is illegal.
139
- if (ch && isDigit(ch)) {
140
- // path.err = 'SvgPath: numbers started with `0` such as `09`
141
- // are illegal (at pos ' + start + ')';
142
- path.err = `${error}: ${invalidPathValue} at index ${start}, "${pathValue[start]}" illegal number`;
143
- return;
144
- }
145
- }
146
-
147
- while (index < max && isDigit(pathValue.charCodeAt(index))) {
148
- index += 1;
149
- hasCeiling = true;
150
- }
151
-
152
- ch = pathValue.charCodeAt(index);
153
- }
154
-
155
- if (ch === 0x2E/* . */) {
156
- hasDot = true;
157
- index += 1;
158
- while (isDigit(pathValue.charCodeAt(index))) {
159
- index += 1;
160
- hasDecimal = true;
161
- }
162
-
163
- ch = pathValue.charCodeAt(index);
164
- }
165
-
166
- if (ch === 0x65/* e */ || ch === 0x45/* E */) {
167
- if (hasDot && !hasCeiling && !hasDecimal) {
168
- path.err = `${error}: ${invalidPathValue} at index ${index}, "${pathValue[index]}" invalid float exponent`;
169
- return;
170
- }
171
-
172
- index += 1;
173
-
174
- ch = pathValue.charCodeAt(index);
175
-
176
- if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
177
- index += 1;
178
- }
179
- if (index < max && isDigit(pathValue.charCodeAt(index))) {
180
- while (index < max && isDigit(pathValue.charCodeAt(index))) {
181
- index += 1;
182
- }
183
- } else {
184
- path.err = `${error}: ${invalidPathValue} at index ${index}, "${pathValue[index]}" invalid integer exponent`;
185
- return;
186
- }
187
- }
188
-
189
- path.index = index;
190
- path.param = +path.pathValue.slice(start, index);
191
- }
192
-
193
- /**
194
- * Checks if the character is a space.
195
- *
196
- * @param {number} ch the character to check
197
- * @returns {boolean} check result
198
- */
199
- function isSpace(ch) {
200
- const specialSpaces = [
201
- 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
202
- 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
203
- /* istanbul ignore next */
204
- return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) // Line terminators
205
- // White spaces
206
- || (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0)
207
- || (ch >= 0x1680 && specialSpaces.includes(ch));
208
- }
209
-
210
- /**
211
- * Points the parser to the next character in the
212
- * path string every time it encounters any kind of
213
- * space character.
214
- *
215
- * @param {SVGPath.PathParser} path the `PathParser` instance
216
- */
217
- function skipSpaces(path) {
218
- const { pathValue, max } = path;
219
- while (path.index < max && isSpace(pathValue.charCodeAt(path.index))) {
220
- path.index += 1;
221
- }
222
- }
223
-
224
- /**
225
- * Checks if the character is a path command.
226
- *
227
- * @param {any} code the character to check
228
- * @returns {boolean} check result
229
- */
230
- function isPathCommand(code) {
231
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy
232
- switch (code | 0x20) {
233
- case 0x6D/* m */:
234
- case 0x7A/* z */:
235
- case 0x6C/* l */:
236
- case 0x68/* h */:
237
- case 0x76/* v */:
238
- case 0x63/* c */:
239
- case 0x73/* s */:
240
- case 0x71/* q */:
241
- case 0x74/* t */:
242
- case 0x61/* a */:
243
- // case 0x72/* r */:
244
- return true;
245
- default:
246
- return false;
247
- }
248
- }
249
-
250
- /**
251
- * Checks if the character is or belongs to a number.
252
- * [0-9]|+|-|.
253
- *
254
- * @param {number} code the character to check
255
- * @returns {boolean} check result
256
- */
257
- function isDigitStart(code) {
258
- return (code >= 48 && code <= 57) /* 0..9 */
259
- || code === 0x2B /* + */
260
- || code === 0x2D /* - */
261
- || code === 0x2E; /* . */
262
- }
263
-
264
- /**
265
- * Checks if the character is an A (arc-to) path command.
266
- *
267
- * @param {number} code the character to check
268
- * @returns {boolean} check result
269
- */
270
- function isArcCommand(code) {
271
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy
272
- return (code | 0x20) === 0x61;
273
- }
274
-
275
- /**
276
- * Scans every character in the path string to determine
277
- * where a segment starts and where it ends.
278
- *
279
- * @param {SVGPath.PathParser} path the `PathParser` instance
280
- */
281
- function scanSegment(path) {
282
- const { max, pathValue, index } = path;
283
- const cmdCode = pathValue.charCodeAt(index);
284
- const reqParams = paramsCount[pathValue[index].toLowerCase()];
285
-
286
- path.segmentStart = index;
287
-
288
- if (!isPathCommand(cmdCode)) {
289
- path.err = `${error}: ${invalidPathValue} "${pathValue[index]}" is not a path command`;
290
- return;
291
- }
292
-
293
- path.index += 1;
294
- skipSpaces(path);
295
-
296
- path.data = [];
297
-
298
- if (!reqParams) {
299
- // Z
300
- finalizeSegment(path);
301
- return;
302
- }
303
-
304
- for (;;) {
305
- for (let i = reqParams; i > 0; i -= 1) {
306
- if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(path);
307
- else scanParam(path);
308
-
309
- if (path.err.length) {
310
- return;
311
- }
312
- path.data.push(path.param);
313
-
314
- skipSpaces(path);
315
-
316
- // after ',' param is mandatory
317
- if (path.index < max && pathValue.charCodeAt(path.index) === 0x2C/* , */) {
318
- path.index += 1;
319
- skipSpaces(path);
320
- }
321
- }
322
-
323
- if (path.index >= path.max) {
324
- break;
325
- }
326
-
327
- // Stop on next segment
328
- if (!isDigitStart(pathValue.charCodeAt(path.index))) {
329
- break;
330
- }
331
- }
332
-
333
- finalizeSegment(path);
334
- }
335
-
336
- /**
337
- * Returns a clone of an existing `pathArray`.
338
- *
339
- * @param {SVGPath.pathArray | SVGPath.pathSegment} path the source `pathArray`
340
- * @returns {any} the cloned `pathArray`
341
- */
342
- function clonePath(path) {
343
- return path.map((x) => (Array.isArray(x) ? [...x] : x));
344
- }
345
-
346
- /**
347
- * The `PathParser` is used by the `parsePathString` static method
348
- * to generate a `pathArray`.
349
- *
350
- * @param {string} pathString
351
- */
352
- function PathParser(pathString) {
353
- /** @type {SVGPath.pathArray} */
354
- // @ts-ignore
355
- this.segments = [];
356
- /** @type {string} */
357
- this.pathValue = pathString;
358
- /** @type {number} */
359
- this.max = pathString.length;
360
- /** @type {number} */
361
- this.index = 0;
362
- /** @type {number} */
363
- this.param = 0.0;
364
- /** @type {number} */
365
- this.segmentStart = 0;
366
- /** @type {any} */
367
- this.data = [];
368
- /** @type {string} */
369
- this.err = '';
370
- }
371
-
372
- /**
373
- * Iterates an array to check if it's an actual `pathArray`.
374
- *
375
- * @param {string | SVGPath.pathArray} path the `pathArray` to be checked
376
- * @returns {boolean} iteration result
377
- */
378
- function isPathArray(path) {
379
- return Array.isArray(path) && path.every((seg) => {
380
- const lk = seg[0].toLowerCase();
381
- return paramsCount[lk] === seg.length - 1 && 'achlmqstvz'.includes(lk);
382
- });
383
- }
384
-
385
- /**
386
- * Parses a path string value and returns an array
387
- * of segments we like to call `pathArray`.
388
- *
389
- * @param {SVGPath.pathArray | string} pathInput the string to be parsed
390
- * @returns {SVGPath.pathArray | string} the resulted `pathArray` or error string
391
- */
392
- function parsePathString(pathInput) {
393
- if (isPathArray(pathInput)) {
394
- return clonePath(pathInput);
395
- }
396
-
397
- const path = new PathParser(pathInput);
398
-
399
- skipSpaces(path);
400
-
401
- while (path.index < path.max && !path.err.length) {
402
- scanSegment(path);
403
- }
404
-
405
- return path.err ? path.err : path.segments;
406
- }
407
-
408
- /**
409
- * Iterates an array to check if it's a `pathArray`
410
- * with all absolute values.
411
- *
412
- * @param {string | SVGPath.pathArray} path the `pathArray` to be checked
413
- * @returns {boolean} iteration result
414
- */
415
- function isAbsoluteArray(path) {
416
- return isPathArray(path)
417
- // @ts-ignore -- `isPathArray` also checks if it's `Array`
418
- && path.every(([x]) => x === x.toUpperCase());
419
- }
420
-
421
- /**
422
- * Parses a path string value or object and returns an array
423
- * of segments, all converted to absolute values.
424
- *
425
- * @param {string | SVGPath.pathArray} pathInput the path string | object
426
- * @returns {SVGPath.absoluteArray} the resulted `pathArray` with absolute values
427
- */
428
- function pathToAbsolute(pathInput) {
429
- /* istanbul ignore else */
430
- if (isAbsoluteArray(pathInput)) {
431
- // @ts-ignore -- `isAbsoluteArray` checks if it's `pathArray`
432
- return clonePath(pathInput);
433
- }
434
-
435
- const path = parsePathString(pathInput);
436
- let x = 0; let y = 0;
437
- let mx = 0; let my = 0;
438
-
439
- // @ts-ignore -- the `absoluteSegment[]` is for sure an `absolutePath`
440
- return path.map((segment) => {
441
- const values = segment.slice(1).map(Number);
442
- const [pathCommand] = segment;
443
- /** @type {SVGPath.absoluteCommand} */
444
- // @ts-ignore
445
- const absCommand = pathCommand.toUpperCase();
446
-
447
- if (pathCommand === 'M') {
448
- [x, y] = values;
449
- mx = x;
450
- my = y;
451
- return ['M', x, y];
452
- }
453
- /** @type {SVGPath.absoluteSegment} */
454
- // @ts-ignore
455
- let absoluteSegment = [];
456
-
457
- if (pathCommand !== absCommand) {
458
- switch (absCommand) {
459
- case 'A':
460
- absoluteSegment = [
461
- absCommand, values[0], values[1], values[2],
462
- values[3], values[4], values[5] + x, values[6] + y];
463
- break;
464
- case 'V':
465
- absoluteSegment = [absCommand, values[0] + y];
466
- break;
467
- case 'H':
468
- absoluteSegment = [absCommand, values[0] + x];
469
- break;
470
- default: {
471
- // use brakets for `eslint: no-case-declaration`
472
- // https://stackoverflow.com/a/50753272/803358
473
- const absValues = values.map((n, j) => n + (j % 2 ? y : x));
474
- // @ts-ignore for n, l, c, s, q, t
475
- absoluteSegment = [absCommand, ...absValues];
476
- }
477
- }
478
- } else {
479
- // @ts-ignore
480
- absoluteSegment = [absCommand, ...values];
481
- }
482
-
483
- const segLength = absoluteSegment.length;
484
- switch (absCommand) {
485
- case 'Z':
486
- x = mx;
487
- y = my;
488
- break;
489
- case 'H':
490
- // @ts-ignore
491
- [, x] = absoluteSegment;
492
- break;
493
- case 'V':
494
- // @ts-ignore
495
- [, y] = absoluteSegment;
496
- break;
497
- default:
498
- // @ts-ignore
499
- x = absoluteSegment[segLength - 2];
500
- // @ts-ignore
501
- y = absoluteSegment[segLength - 1];
502
-
503
- if (absCommand === 'M') {
504
- mx = x;
505
- my = y;
506
- }
507
- }
508
- return absoluteSegment;
509
- });
510
- }
511
-
512
- /**
513
- * Iterates an array to check if it's a `pathArray`
514
- * with relative values.
515
- *
516
- * @param {string | SVGPath.pathArray} path the `pathArray` to be checked
517
- * @returns {boolean} iteration result
518
- */
519
- function isRelativeArray(path) {
520
- return isPathArray(path)
521
- // @ts-ignore -- `isPathArray` checks if it's `Array`
522
- && path.slice(1).every(([pc]) => pc === pc.toLowerCase());
523
- }
524
-
525
- /**
526
- * Parses a path string value or object and returns an array
527
- * of segments, all converted to relative values.
528
- *
529
- * @param {string | SVGPath.pathArray} pathInput the path string | object
530
- * @returns {SVGPath.relativeArray} the resulted `pathArray` with relative values
531
- */
532
- function pathToRelative(pathInput) {
533
- /* istanbul ignore else */
534
- if (isRelativeArray(pathInput)) {
535
- return clonePath(pathInput);
536
- }
537
-
538
- const path = parsePathString(pathInput);
539
- let x = 0; let y = 0;
540
- let mx = 0; let my = 0;
541
-
542
- return path.map((segment) => {
543
- const values = segment.slice(1).map(Number);
544
- const [pathCommand] = segment;
545
- /** @type {SVGPath.relativeCommand} */
546
- const relativeCommand = pathCommand.toLowerCase();
547
-
548
- if (pathCommand === 'M') {
549
- [x, y] = values;
550
- mx = x;
551
- my = y;
552
- return ['M', x, y];
553
- }
554
-
555
- /** @type {SVGPath.relativeSegment} */
556
- let relativeSegment = [];
557
-
558
- if (pathCommand !== relativeCommand) {
559
- switch (relativeCommand) {
560
- case 'a':
561
- relativeSegment = [
562
- relativeCommand, values[0], values[1], values[2],
563
- values[3], values[4], values[5] - x, values[6] - y];
564
- break;
565
- case 'v':
566
- relativeSegment = [relativeCommand, values[0] - y];
567
- break;
568
- case 'h':
569
- relativeSegment = [relativeCommand, values[0] - x];
570
- break;
571
- default: {
572
- // use brakets for `eslint: no-case-declaration`
573
- // https://stackoverflow.com/a/50753272/803358
574
- const relValues = values.map((n, j) => n - (j % 2 ? y : x));
575
- relativeSegment = [relativeCommand, ...relValues];
576
- }
577
- }
578
- } else {
579
- if (pathCommand === 'm') {
580
- mx = values[0] + x;
581
- my = values[1] + y;
582
- }
583
- relativeSegment = [relativeCommand, ...values];
584
- }
585
-
586
- const segLength = relativeSegment.length;
587
- switch (relativeCommand) {
588
- case 'z':
589
- x = mx;
590
- y = my;
591
- break;
592
- case 'h':
593
- x += relativeSegment[1];
594
- break;
595
- case 'v':
596
- y += relativeSegment[1];
597
- break;
598
- default:
599
- x += relativeSegment[segLength - 2];
600
- y += relativeSegment[segLength - 1];
601
- }
602
- return relativeSegment;
603
- });
604
- }
605
-
606
- /**
607
- * Splits an extended A (arc-to) segment into two cubic-bezier segments.
608
- *
609
- * @param {SVGPath.pathArray} path the `pathArray` this segment belongs to
610
- * @param {string[]} allPathCommands all previous path commands
611
- * @param {number} i the segment index
612
- */
613
-
614
- function fixArc(path, allPathCommands, i) {
615
- if (path[i].length > 7) {
616
- path[i].shift();
617
- const segment = path[i];
618
- let ni = i; // ESLint
619
- while (segment.length) {
620
- // if created multiple C:s, their original seg is saved
621
- allPathCommands[i] = 'A';
622
- // @ts-ignore
623
- path.splice(ni += 1, 0, ['C', ...segment.splice(0, 6)]);
624
- }
625
- path.splice(i, 1);
626
- }
627
- }
628
-
629
- /**
630
- * Normalizes a single segment of a `pathArray` object.
631
- *
632
- * @param {SVGPath.pathSegment} segment the segment object
633
- * @param {any} params the coordinates of the previous segment
634
- * @returns {SVGPath.normalSegment} the normalized segment
635
- */
636
- function normalizeSegment(segment, params) {
637
- const [pathCommand] = segment;
638
- const {
639
- x1: px1, y1: py1, x2: px2, y2: py2,
640
- } = params;
641
- const values = segment.slice(1).map(Number);
642
- let result = segment;
643
-
644
- if (!'TQ'.includes(pathCommand)) {
645
- // optional but good to be cautious
646
- params.qx = null;
647
- params.qy = null;
648
- }
649
-
650
- if (pathCommand === 'H') {
651
- result = ['L', segment[1], py1];
652
- } else if (pathCommand === 'V') {
653
- result = ['L', px1, segment[1]];
654
- } else if (pathCommand === 'S') {
655
- const x1 = px1 * 2 - px2;
656
- const y1 = py1 * 2 - py2;
657
- params.x1 = x1;
658
- params.y1 = y1;
659
- result = ['C', x1, y1, ...values];
660
- } else if (pathCommand === 'T') {
661
- const qx = px1 * 2 - params.qx;
662
- const qy = py1 * 2 - params.qy;
663
- params.qx = qx;
664
- params.qy = qy;
665
- result = ['Q', qx, qy, ...values];
666
- } else if (pathCommand === 'Q') {
667
- const [nqx, nqy] = values;
668
- params.qx = nqx;
669
- params.qy = nqy;
670
- }
671
-
672
- return result;
673
- }
674
-
675
- /**
676
- * Iterates an array to check if it's a `pathArray`
677
- * with all segments are in non-shorthand notation
678
- * with absolute values.
679
- *
680
- * @param {string | SVGPath.pathArray} path the `pathArray` to be checked
681
- * @returns {boolean} iteration result
682
- */
683
- function isNormalizedArray(path) {
684
- // @ts-ignore -- `isAbsoluteArray` also checks if it's `Array`
685
- return isAbsoluteArray(path) && path.every(([pc]) => 'ACLMQZ'.includes(pc));
686
- }
687
-
688
- /**
689
- * @type {SVGPath.parserParams}
690
- */
691
- const paramsParser = {
692
- x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
693
- };
694
-
695
- /**
696
- * Normalizes a `path` object for further processing:
697
- * * convert segments to absolute values
698
- * * convert shorthand path commands to their non-shorthand notation
699
- *
700
- * @param {string | SVGPath.pathArray} pathInput the string to be parsed or 'pathArray'
701
- * @returns {SVGPath.normalArray} the normalized `pathArray`
702
- */
703
- function normalizePath(pathInput) {
704
- if (isNormalizedArray(pathInput)) {
705
- return clonePath(pathInput);
706
- }
707
-
708
- /** @type {SVGPath.normalArray} */
709
- const path = pathToAbsolute(pathInput);
710
- const params = { ...paramsParser };
711
- const allPathCommands = [];
712
- const ii = path.length;
713
- let pathCommand = '';
714
-
715
- for (let i = 0; i < ii; i += 1) {
716
- [pathCommand] = path[i];
717
-
718
- // Save current path command
719
- allPathCommands[i] = pathCommand;
720
- path[i] = normalizeSegment(path[i], params);
721
-
722
- const segment = path[i];
723
- const seglen = segment.length;
724
-
725
- params.x1 = +segment[seglen - 2];
726
- params.y1 = +segment[seglen - 1];
727
- params.x2 = +(segment[seglen - 4]) || params.x1;
728
- params.y2 = +(segment[seglen - 3]) || params.y1;
729
- }
730
-
731
- return path;
732
- }
733
-
734
- /**
735
- * Checks a `pathArray` for an unnecessary `Z` segment
736
- * and returns a new `pathArray` without it.
737
- *
738
- * The `pathInput` must be a single path, without
739
- * sub-paths. For multi-path `<path>` elements,
740
- * use `splitPath` first and apply this utility on each
741
- * sub-path separately.
742
- *
743
- * @param {SVGPath.pathArray | string} pathInput the `pathArray` source
744
- * @return {SVGPath.pathArray} a fixed `pathArray`
745
- */
746
- function fixPath(pathInput) {
747
- const pathArray = parsePathString(pathInput);
748
- const normalArray = normalizePath(pathArray);
749
- const { length } = pathArray;
750
- const isClosed = normalArray.slice(-1)[0][0] === 'Z';
751
- const segBeforeZ = isClosed ? length - 2 : length - 1;
752
-
753
- const [mx, my] = normalArray[0].slice(1);
754
- const [x, y] = normalArray[segBeforeZ].slice(-2);
755
-
756
- /* istanbul ignore else */
757
- if (isClosed && mx === x && my === y) {
758
- return pathArray.slice(0, -1);
759
- }
760
- return pathArray;
761
- }
762
-
763
- /**
764
- * Iterates an array to check if it's a `pathArray`
765
- * with all C (cubic bezier) segments.
766
- *
767
- * @param {string | SVGPath.pathArray} path the `Array` to be checked
768
- * @returns {boolean} iteration result
769
- */
770
- function isCurveArray(path) {
771
- // @ts-ignore -- `isPathArray` also checks if it's `Array`
772
- return isNormalizedArray(path) && path.every(([pc]) => 'MC'.includes(pc));
773
- }
774
-
775
- /**
776
- * Returns an {x,y} vector rotated by a given
777
- * angle in radian.
778
- *
779
- * @param {number} x the initial vector x
780
- * @param {number} y the initial vector y
781
- * @param {number} rad the radian vector angle
782
- * @returns {{x: number, y: number}} the rotated vector
783
- */
784
- function rotateVector(x, y, rad) {
785
- const X = x * Math.cos(rad) - y * Math.sin(rad);
786
- const Y = x * Math.sin(rad) + y * Math.cos(rad);
787
- return { x: X, y: Y };
788
- }
789
-
790
- /**
791
- * Converts A (arc-to) segments to C (cubic-bezier-to).
792
- *
793
- * For more information of where this math came from visit:
794
- * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
795
- *
796
- * @param {number} X1 the starting x position
797
- * @param {number} Y1 the starting y position
798
- * @param {number} RX x-radius of the arc
799
- * @param {number} RY y-radius of the arc
800
- * @param {number} angle x-axis-rotation of the arc
801
- * @param {number} LAF large-arc-flag of the arc
802
- * @param {number} SF sweep-flag of the arc
803
- * @param {number} X2 the ending x position
804
- * @param {number} Y2 the ending y position
805
- * @param {number[]=} recursive the parameters needed to split arc into 2 segments
806
- * @return {number[]} the resulting cubic-bezier segment(s)
807
- */
808
- function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recursive) {
809
- let x1 = X1; let y1 = Y1; let rx = RX; let ry = RY; let x2 = X2; let y2 = Y2;
810
- // for more information of where this Math came from visit:
811
- // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
812
- const d120 = (Math.PI * 120) / 180;
813
-
814
- const rad = (Math.PI / 180) * (+angle || 0);
815
- /** @type {number[]} */
816
- let res = [];
817
- let xy;
818
- let f1;
819
- let f2;
820
- let cx;
821
- let cy;
822
-
823
- if (!recursive) {
824
- xy = rotateVector(x1, y1, -rad);
825
- x1 = xy.x;
826
- y1 = xy.y;
827
- xy = rotateVector(x2, y2, -rad);
828
- x2 = xy.x;
829
- y2 = xy.y;
830
-
831
- const x = (x1 - x2) / 2;
832
- const y = (y1 - y2) / 2;
833
- let h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
834
- if (h > 1) {
835
- h = Math.sqrt(h);
836
- rx *= h;
837
- ry *= h;
838
- }
839
- const rx2 = rx * rx;
840
- const ry2 = ry * ry;
841
-
842
- const k = (LAF === SF ? -1 : 1)
843
- * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
844
- / (rx2 * y * y + ry2 * x * x)));
845
-
846
- cx = ((k * rx * y) / ry) + ((x1 + x2) / 2);
847
- cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2);
848
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
849
- f1 = (Math.asin((((y1 - cy) / ry))) * (10 ** 9) >> 0) / (10 ** 9);
850
- // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
851
- f2 = (Math.asin((((y2 - cy) / ry))) * (10 ** 9) >> 0) / (10 ** 9);
852
-
853
- f1 = x1 < cx ? Math.PI - f1 : f1;
854
- f2 = x2 < cx ? Math.PI - f2 : f2;
855
- if (f1 < 0) (f1 = Math.PI * 2 + f1);
856
- if (f2 < 0) (f2 = Math.PI * 2 + f2);
857
- if (SF && f1 > f2) {
858
- f1 -= Math.PI * 2;
859
- }
860
- if (!SF && f2 > f1) {
861
- f2 -= Math.PI * 2;
862
- }
863
- } else {
864
- [f1, f2, cx, cy] = recursive;
865
- }
866
- let df = f2 - f1;
867
- if (Math.abs(df) > d120) {
868
- const f2old = f2;
869
- const x2old = x2;
870
- const y2old = y2;
871
- f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
872
- x2 = cx + rx * Math.cos(f2);
873
- y2 = cy + ry * Math.sin(f2);
874
- res = arcToCubic(x2, y2, rx, ry, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
875
- }
876
- df = f2 - f1;
877
- const c1 = Math.cos(f1);
878
- const s1 = Math.sin(f1);
879
- const c2 = Math.cos(f2);
880
- const s2 = Math.sin(f2);
881
- const t = Math.tan(df / 4);
882
- const hx = (4 / 3) * rx * t;
883
- const hy = (4 / 3) * ry * t;
884
- const m1 = [x1, y1];
885
- const m2 = [x1 + hx * s1, y1 - hy * c1];
886
- const m3 = [x2 + hx * s2, y2 - hy * c2];
887
- const m4 = [x2, y2];
888
- m2[0] = 2 * m1[0] - m2[0];
889
- m2[1] = 2 * m1[1] - m2[1];
890
- if (recursive) {
891
- return [...m2, ...m3, ...m4, ...res];
892
- }
893
- res = [...m2, ...m3, ...m4, ...res];
894
- const newres = [];
895
- for (let i = 0, ii = res.length; i < ii; i += 1) {
896
- newres[i] = i % 2
897
- ? rotateVector(res[i - 1], res[i], rad).y
898
- : rotateVector(res[i], res[i + 1], rad).x;
899
- }
900
- return newres;
901
- }
902
-
903
- /**
904
- * Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
905
- *
906
- * @param {number} x1 curve start x
907
- * @param {number} y1 curve start y
908
- * @param {number} qx control point x
909
- * @param {number} qy control point y
910
- * @param {number} x2 curve end x
911
- * @param {number} y2 curve end y
912
- * @returns {number[]} the cubic-bezier segment
913
- */
914
- function quadToCubic(x1, y1, qx, qy, x2, y2) {
915
- const r13 = 1 / 3;
916
- const r23 = 2 / 3;
917
- return [
918
- r13 * x1 + r23 * qx, // cpx1
919
- r13 * y1 + r23 * qy, // cpy1
920
- r13 * x2 + r23 * qx, // cpx2
921
- r13 * y2 + r23 * qy, // cpy2
922
- x2, y2, // x,y
923
- ];
924
- }
925
-
926
- /**
927
- * Returns the coordinates of a specified distance
928
- * ratio between two points.
929
- *
930
- * @param {[number, number]} a the first point coordinates
931
- * @param {[number, number]} b the second point coordinates
932
- * @param {number} t the ratio
933
- * @returns {[number, number]} the midpoint coordinates
934
- */
935
- function midPoint(a, b, t) {
936
- const [ax, ay] = a; const [bx, by] = b;
937
- return [ax + (bx - ax) * t, ay + (by - ay) * t];
938
- }
939
-
940
- /**
941
- * Returns the square root of the distance
942
- * between two given points.
943
- *
944
- * @param {[number, number]} a the first point coordinates
945
- * @param {[number, number]} b the second point coordinates
946
- * @returns {number} the distance value
947
- */
948
- function distanceSquareRoot(a, b) {
949
- return Math.sqrt(
950
- (a[0] - b[0]) * (a[0] - b[0])
951
- + (a[1] - b[1]) * (a[1] - b[1]),
952
- );
953
- }
954
-
955
- /**
956
- * Returns a {x,y} point at a given length, the total length and
957
- * the minimum and maximum {x,y} coordinates of a line (L,V,H,Z) segment.
958
- *
959
- * @param {number} x1 the starting point X
960
- * @param {number} y1 the starting point Y
961
- * @param {number} x2 the ending point X
962
- * @param {number} y2 the ending point Y
963
- * @param {number=} distance the distance to point
964
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
965
- */
966
- function segmentLineFactory(x1, y1, x2, y2, distance) {
967
- const length = distanceSquareRoot([x1, y1], [x2, y2]);
968
- let point = { x: 0, y: 0 };
969
-
970
- /* istanbul ignore else */
971
- if (typeof distance === 'number') {
972
- if (distance === 0) {
973
- point = { x: x1, y: y1 };
974
- } else if (distance >= length) {
975
- point = { x: x2, y: y2 };
976
- } else {
977
- const [x, y] = midPoint([x1, y1], [x2, y2], distance / length);
978
- point = { x, y };
979
- }
980
- }
981
-
982
- return {
983
- length,
984
- point,
985
- min: {
986
- x: Math.min(x1, x2),
987
- y: Math.min(y1, y2),
988
- },
989
- max: {
990
- x: Math.max(x1, x2),
991
- y: Math.max(y1, y2),
992
- },
993
- };
994
- }
995
-
996
- /**
997
- * Converts an L (line-to) segment to C (cubic-bezier).
998
- *
999
- * @param {number} x1 line start x
1000
- * @param {number} y1 line start y
1001
- * @param {number} x2 line end x
1002
- * @param {number} y2 line end y
1003
- * @returns {number[]} the cubic-bezier segment
1004
- */
1005
- function lineToCubic(x1, y1, x2, y2) {
1006
- const t = 0.5;
1007
- /** @type {[number, number]} */
1008
- const p0 = [x1, y1];
1009
- /** @type {[number, number]} */
1010
- const p1 = [x2, y2];
1011
- const p2 = midPoint(p0, p1, t);
1012
- const p3 = midPoint(p1, p2, t);
1013
- const p4 = midPoint(p2, p3, t);
1014
- const p5 = midPoint(p3, p4, t);
1015
- const p6 = midPoint(p4, p5, t);
1016
- const seg1 = [...p0, ...p2, ...p4, ...p6, t];
1017
- // @ts-ignore
1018
- const cp1 = segmentLineFactory(...seg1).point;
1019
- const seg2 = [...p6, ...p5, ...p3, ...p1, 0];
1020
- // @ts-ignore
1021
- const cp2 = segmentLineFactory(...seg2).point;
1022
-
1023
- // @ts-ignore
1024
- return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
1025
- }
1026
-
1027
- /**
1028
- * Converts any segment to C (cubic-bezier).
1029
- *
1030
- * @param {SVGPath.pathSegment} segment the source segment
1031
- * @param {SVGPath.parserParams} params the source segment parameters
1032
- * @returns {SVGPath.cubicSegment | SVGPath.MSegment} the cubic-bezier segment
1033
- */
1034
- function segmentToCubic(segment, params) {
1035
- const [pathCommand] = segment;
1036
- const values = segment.slice(1).map(Number);
1037
- const [x, y] = values;
1038
- let args;
1039
- const {
1040
- x1: px1, y1: py1, x: px, y: py,
1041
- } = params;
1042
-
1043
- if (!'TQ'.includes(pathCommand)) {
1044
- params.qx = null;
1045
- params.qy = null;
1046
- }
1047
-
1048
- switch (pathCommand) {
1049
- case 'M':
1050
- params.x = x;
1051
- params.y = y;
1052
- return segment;
1053
- case 'A':
1054
- args = [px1, py1, ...values];
1055
- return ['C', ...arcToCubic(...args)];
1056
- case 'Q':
1057
- params.qx = x;
1058
- params.qy = y;
1059
- args = [px1, py1, ...values];
1060
- return ['C', ...quadToCubic(...args)];
1061
- case 'L':
1062
- return ['C', ...lineToCubic(px1, py1, x, y)];
1063
- case 'Z':
1064
- return ['C', ...lineToCubic(px1, py1, px, py)];
1065
- }
1066
- return segment;
1067
- }
1068
-
1069
- /**
1070
- * Parses a path string value or 'pathArray' and returns a new one
1071
- * in which all segments are converted to cubic-bezier.
1072
- *
1073
- * In addition, un-necessary `Z` segment is removed if previous segment
1074
- * extends to the `M` segment.
1075
- *
1076
- * @param {string | SVGPath.pathArray} pathInput the string to be parsed or 'pathArray'
1077
- * @returns {SVGPath.curveArray} the resulted `pathArray` converted to cubic-bezier
1078
- */
1079
- function pathToCurve(pathInput) {
1080
- /* istanbul ignore else */
1081
- if (isCurveArray(pathInput)) {
1082
- // @ts-ignore -- `isCurveArray` checks if it's `pathArray`
1083
- return clonePath(pathInput);
1084
- }
1085
-
1086
- const path = fixPath(normalizePath(pathInput));
1087
- const params = { ...paramsParser };
1088
- const allPathCommands = [];
1089
- let pathCommand = ''; // ts-lint
1090
- let ii = path.length;
1091
-
1092
- for (let i = 0; i < ii; i += 1) {
1093
- [pathCommand] = path[i];
1094
- allPathCommands[i] = pathCommand;
1095
-
1096
- path[i] = segmentToCubic(path[i], params);
1097
-
1098
- fixArc(path, allPathCommands, i);
1099
- ii = path.length;
1100
-
1101
- const segment = path[i];
1102
- const seglen = segment.length;
1103
- params.x1 = +segment[seglen - 2];
1104
- params.y1 = +segment[seglen - 1];
1105
- params.x2 = +(segment[seglen - 4]) || params.x1;
1106
- params.y2 = +(segment[seglen - 3]) || params.y1;
1107
- }
1108
-
1109
- // @ts-ignore
1110
- return path;
1111
- }
1112
-
1113
- /**
1114
- * Rounds the values of a `pathArray` instance to
1115
- * a specified amount of decimals and returns it.
1116
- *
1117
- * @param {SVGPath.pathArray} path the source `pathArray`
1118
- * @param {number | 'off'} roundOption the amount of decimals to round numbers to
1119
- * @returns {SVGPath.pathArray} the resulted `pathArray` with rounded values
1120
- */
1121
- function roundPath(path, roundOption) {
1122
- let { round } = defaultOptions;
1123
- if (roundOption === 'off' || round === 'off') return clonePath(path);
1124
- // round = roundOption >= 1 ? roundOption : round;
1125
- // allow for ZERO decimals
1126
- round = roundOption >= 0 ? roundOption : round;
1127
- // to round values to the power
1128
- // the `round` value must be integer
1129
- const pow = typeof round === 'number' && round >= 1 ? (10 ** round) : 1;
1130
-
1131
- return path.map((pi) => {
1132
- const values = pi.slice(1).map(Number)
1133
- .map((n) => (round ? (Math.round(n * pow) / pow) : Math.round(n)));
1134
- return [pi[0], ...values];
1135
- });
1136
- }
1137
-
1138
- /**
1139
- * Returns a valid `d` attribute string value created
1140
- * by rounding values and concatenating the `pathArray` segments.
1141
- *
1142
- * @param {SVGPath.pathArray} path the `pathArray` object
1143
- * @param {number | 'off'} round amount of decimals to round values to
1144
- * @returns {string} the concatenated path string
1145
- */
1146
- function pathToString(path, round) {
1147
- return roundPath(path, round)
1148
- .map((x) => x[0] + x.slice(1).join(' ')).join('');
1149
- }
1150
-
1151
- /**
1152
- * Reverses all segments of a `pathArray` and returns a new `pathArray` instance.
1153
- *
1154
- * @param {SVGPath.pathArray} pathInput the source `pathArray`
1155
- * @returns {SVGPath.pathArray} the reversed `pathArray`
1156
- */
1157
- function reversePath(pathInput) {
1158
- const absolutePath = pathToAbsolute(pathInput);
1159
- const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
1160
-
1161
- const reversedPath = normalizePath(absolutePath).map((segment, i) => {
1162
- const [x, y] = segment.slice(-2).map(Number);
1163
- return {
1164
- seg: absolutePath[i], // absolute
1165
- n: segment, // normalized
1166
- c: absolutePath[i][0], // pathCommand
1167
- x, // x
1168
- y, // y
1169
- };
1170
- }).map((seg, i, path) => {
1171
- const segment = seg.seg;
1172
- const data = seg.n;
1173
- const prevSeg = i && path[i - 1];
1174
- const nextSeg = path[i + 1];
1175
- const pathCommand = seg.c;
1176
- const pLen = path.length;
1177
- /** @type {number} */
1178
- const x = i ? path[i - 1].x : path[pLen - 1].x;
1179
- const y = i ? path[i - 1].y : path[pLen - 1].y;
1180
- /** @type {SVGPath.pathSegment} */
1181
- let result = [];
1182
-
1183
- switch (pathCommand) {
1184
- case 'M':
1185
- result = isClosed ? ['Z'] : [pathCommand, x, y];
1186
- break;
1187
- case 'A':
1188
- result = [pathCommand, ...segment.slice(1, -3), (segment[5] === 1 ? 0 : 1), x, y];
1189
- break;
1190
- case 'C':
1191
- if (nextSeg && nextSeg.c === 'S') {
1192
- result = ['S', segment[1], segment[2], x, y];
1193
- } else {
1194
- result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y];
1195
- }
1196
- break;
1197
- case 'S':
1198
- if ((prevSeg && 'CS'.includes(prevSeg.c)) && (!nextSeg || nextSeg.c !== 'S')) {
1199
- result = ['C', data[3], data[4], data[1], data[2], x, y];
1200
- } else {
1201
- result = [pathCommand, data[1], data[2], x, y];
1202
- }
1203
- break;
1204
- case 'Q':
1205
- if (nextSeg && nextSeg.c === 'T') {
1206
- result = ['T', x, y];
1207
- } else {
1208
- result = [pathCommand, ...segment.slice(1, -2), x, y];
1209
- }
1210
- break;
1211
- case 'T':
1212
- if ((prevSeg && 'QT'.includes(prevSeg.c)) && (!nextSeg || nextSeg.c !== 'T')) {
1213
- result = ['Q', data[1], data[2], x, y];
1214
- } else {
1215
- result = [pathCommand, x, y];
1216
- }
1217
- break;
1218
- case 'Z':
1219
- result = ['M', x, y];
1220
- break;
1221
- case 'H':
1222
- result = [pathCommand, x];
1223
- break;
1224
- case 'V':
1225
- result = [pathCommand, y];
1226
- break;
1227
- default:
1228
- result = [pathCommand, ...segment.slice(1, -2), x, y];
1229
- }
1230
-
1231
- return result;
1232
- });
1233
-
1234
- return isClosed ? reversedPath.reverse()
1235
- : [reversedPath[0], ...reversedPath.slice(1).reverse()];
1236
- }
1237
-
1238
- /**
1239
- * Split a path into an `Array` of sub-path strings.
1240
- *
1241
- * In the process, values are converted to absolute
1242
- * for visual consistency.
1243
- *
1244
- * @param {SVGPath.pathArray} pathInput the source `pathArray`
1245
- * @return {SVGPath.pathArray[]} an array with all sub-path strings
1246
- */
1247
- function splitPath(pathInput) {
1248
- /** @type {SVGPath.pathArray[]} */
1249
- const composite = [];
1250
- /** @type {SVGPath.pathArray} */
1251
- let path;
1252
- let pi = -1;
1253
-
1254
- pathInput.forEach((seg) => {
1255
- if (seg[0] === 'M') {
1256
- path = [seg];
1257
- pi += 1;
1258
- } else {
1259
- path = [...path, seg];
1260
- }
1261
- composite[pi] = path;
1262
- });
1263
-
1264
- return composite;
1265
- }
1266
-
1267
- /**
1268
- * Shorten a single segment of a `pathArray` object.
1269
- *
1270
- * @param {SVGPath.absoluteSegment} segment the `absoluteSegment` object
1271
- * @param {SVGPath.normalSegment} normalSegment the `normalSegment` object
1272
- * @param {any} params the coordinates of the previous segment
1273
- * @param {string} prevCommand the path command of the previous segment
1274
- * @returns {SVGPath.shortSegment | SVGPath.pathSegment} the shortened segment
1275
- */
1276
- function shortenSegment(segment, normalSegment, params, prevCommand) {
1277
- const [pathCommand] = segment;
1278
- const round4 = (/** @type {number} */n) => Math.round(n * (10 ** 4)) / 10 ** 4;
1279
- const segmentValues = segment.slice(1).map((n) => +n);
1280
- const normalValues = normalSegment.slice(1).map((n) => +n);
1281
- const {
1282
- x1: px1, y1: py1, x2: px2, y2: py2, x: px, y: py,
1283
- } = params;
1284
- let result = segment;
1285
- const [x, y] = normalValues.slice(-2);
1286
-
1287
- if (!'TQ'.includes(pathCommand)) {
1288
- // optional but good to be cautious
1289
- params.qx = null;
1290
- params.qy = null;
1291
- }
1292
-
1293
- if (['V', 'H', 'S', 'T', 'Z'].includes(pathCommand)) {
1294
- result = [pathCommand, ...segmentValues];
1295
- } else if (pathCommand === 'L') {
1296
- if (round4(px) === round4(x)) {
1297
- result = ['V', y];
1298
- } else if (round4(py) === round4(y)) {
1299
- result = ['H', x];
1300
- }
1301
- } else if (pathCommand === 'C') {
1302
- const [x1, y1] = normalValues;
1303
-
1304
- if ('CS'.includes(prevCommand)
1305
- && ((round4(x1) === round4(px1 * 2 - px2) && round4(y1) === round4(py1 * 2 - py2))
1306
- || (round4(px1) === round4(px2 * 2 - px) && round4(py1) === round4(py2 * 2 - py)))) {
1307
- result = ['S', ...normalValues.slice(-4)];
1308
- }
1309
- params.x1 = x1;
1310
- params.y1 = y1;
1311
- } else if (pathCommand === 'Q') {
1312
- const [qx, qy] = normalValues;
1313
- params.qx = qx;
1314
- params.qy = qy;
1315
-
1316
- if ('QT'.includes(prevCommand)
1317
- && ((round4(qx) === round4(px1 * 2 - px2) && round4(qy) === round4(py1 * 2 - py2))
1318
- || (round4(px1) === round4(px2 * 2 - px) && round4(py1) === round4(py2 * 2 - py)))) {
1319
- result = ['T', ...normalValues.slice(-2)];
1320
- }
1321
- }
1322
-
1323
- return result;
1324
- }
1325
-
1326
- /**
1327
- * Optimizes a `pathArray` object:
1328
- * * convert segments to shorthand if possible
1329
- * * select shortest segments from absolute and relative `pathArray`s
1330
- *
1331
- * TO DO
1332
- * * implement `auto` for rounding values based on pathBBox
1333
- * * also revers path check if it's smaller string, maybe?
1334
- *
1335
- * @param {SVGPath.pathArray} pathInput a string or `pathArray`
1336
- * @param {number | 'off'} round the amount of decimals to round values to
1337
- * @returns {SVGPath.pathArray} the optimized `pathArray`
1338
- */
1339
- function optimizePath(pathInput, round) {
1340
- const path = pathToAbsolute(pathInput);
1341
- const normalPath = normalizePath(path);
1342
- const params = { ...paramsParser };
1343
- const allPathCommands = [];
1344
- const ii = path.length;
1345
- let pathCommand = '';
1346
- let prevCommand = '';
1347
- let x = 0;
1348
- let y = 0;
1349
- let mx = 0;
1350
- let my = 0;
1351
-
1352
- for (let i = 0; i < ii; i += 1) {
1353
- [pathCommand] = path[i];
1354
-
1355
- // Save current path command
1356
- allPathCommands[i] = pathCommand;
1357
- // Get previous path command for `shortenSegment`
1358
- if (i) prevCommand = allPathCommands[i - 1];
1359
- path[i] = shortenSegment(path[i], normalPath[i], params, prevCommand);
1360
-
1361
- const segment = path[i];
1362
- const seglen = segment.length;
1363
-
1364
- // update C, S, Q, T specific params
1365
- params.x1 = +segment[seglen - 2];
1366
- params.y1 = +segment[seglen - 1];
1367
- params.x2 = +(segment[seglen - 4]) || params.x1;
1368
- params.y2 = +(segment[seglen - 3]) || params.y1;
1369
-
1370
- // update x, y params
1371
- switch (pathCommand) {
1372
- case 'Z':
1373
- x = mx;
1374
- y = my;
1375
- break;
1376
- case 'H':
1377
- [, x] = segment;
1378
- break;
1379
- case 'V':
1380
- [, y] = segment;
1381
- break;
1382
- default:
1383
- [x, y] = segment.slice(-2).map(Number);
1384
-
1385
- if (pathCommand === 'M') {
1386
- mx = x;
1387
- my = y;
1388
- }
1389
- }
1390
- params.x = x;
1391
- params.y = y;
1392
- }
1393
-
1394
- const absolutePath = roundPath(path, round);
1395
- const relativePath = roundPath(pathToRelative(path), round);
1396
-
1397
- return absolutePath.map((a, i) => {
1398
- if (i) {
1399
- return a.join('').length < relativePath[i].join('').length
1400
- ? a : relativePath[i];
1401
- }
1402
- return a;
1403
- });
1404
- }
1405
-
1406
- // DOMMatrix Static methods
1407
- // * `fromArray` is a more simple implementation, should also accept Float[32/64]Array;
1408
- // * `fromMatrix` load values from another CSSMatrix/DOMMatrix instance or JSON object;
1409
- // * `fromString` parses and loads values from any valid CSS transform string (TransformList).
1410
-
1411
- /**
1412
- * Creates a new mutable `CSSMatrix` instance given an array of 16/6 floating point values.
1413
- * This static method invalidates arrays that contain non-number elements.
1414
- *
1415
- * If the array has six values, the result is a 2D matrix; if the array has 16 values,
1416
- * the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
1417
- *
1418
- * @param {CSSM.matrix | CSSM.matrix3d} array an `Array` to feed values from.
1419
- * @return {CSSMatrix} the resulted matrix.
1420
- */
1421
- function fromArray(array) {
1422
- const m = new CSSMatrix();
1423
- const a = Array.from(array);
1424
-
1425
- if (!a.every((n) => !Number.isNaN(n))) {
1426
- throw TypeError(`CSSMatrix: "${array}" must only have numbers.`);
1427
- }
1428
- if (a.length === 16) {
1429
- const [m11, m12, m13, m14,
1430
- m21, m22, m23, m24,
1431
- m31, m32, m33, m34,
1432
- m41, m42, m43, m44] = a;
1433
-
1434
- m.m11 = m11;
1435
- m.a = m11;
1436
-
1437
- m.m21 = m21;
1438
- m.c = m21;
1439
-
1440
- m.m31 = m31;
1441
-
1442
- m.m41 = m41;
1443
- m.e = m41;
1444
-
1445
- m.m12 = m12;
1446
- m.b = m12;
1447
-
1448
- m.m22 = m22;
1449
- m.d = m22;
1450
-
1451
- m.m32 = m32;
1452
-
1453
- m.m42 = m42;
1454
- m.f = m42;
1455
-
1456
- m.m13 = m13;
1457
- m.m23 = m23;
1458
- m.m33 = m33;
1459
- m.m43 = m43;
1460
- m.m14 = m14;
1461
- m.m24 = m24;
1462
- m.m34 = m34;
1463
- m.m44 = m44;
1464
- } else if (a.length === 6) {
1465
- const [M11, M12, M21, M22, M41, M42] = a;
1466
-
1467
- m.m11 = M11;
1468
- m.a = M11;
1469
-
1470
- m.m12 = M12;
1471
- m.b = M12;
1472
-
1473
- m.m21 = M21;
1474
- m.c = M21;
1475
-
1476
- m.m22 = M22;
1477
- m.d = M22;
1478
-
1479
- m.m41 = M41;
1480
- m.e = M41;
1481
-
1482
- m.m42 = M42;
1483
- m.f = M42;
1484
- } else {
1485
- throw new TypeError('CSSMatrix: expecting an Array of 6/16 values.');
1486
- }
1487
- return m;
1488
- }
1489
-
1490
- /**
1491
- * Creates a new mutable `CSSMatrix` instance given an existing matrix or a
1492
- * `DOMMatrix` instance which provides the values for its properties.
1493
- *
1494
- * @param {CSSMatrix | DOMMatrix | CSSM.JSONMatrix} m the source matrix to feed values from.
1495
- * @return {CSSMatrix} the resulted matrix.
1496
- */
1497
- function fromMatrix(m) {
1498
- const keys = Object.keys(new CSSMatrix());
1499
- if (typeof m === 'object' && keys.every((k) => k in m)) {
1500
- return fromArray(
1501
- [m.m11, m.m12, m.m13, m.m14,
1502
- m.m21, m.m22, m.m23, m.m24,
1503
- m.m31, m.m32, m.m33, m.m34,
1504
- m.m41, m.m42, m.m43, m.m44],
1505
- );
1506
- }
1507
- throw TypeError(`CSSMatrix: "${JSON.stringify(m)}" is not a DOMMatrix / CSSMatrix / JSON compatible object.`);
1508
- }
1509
-
1510
- /**
1511
- * Creates a new mutable `CSSMatrix` given any valid CSS transform string,
1512
- * or what we call `TransformList`:
1513
- *
1514
- * * `matrix(a, b, c, d, e, f)` - valid matrix() transform function
1515
- * * `matrix3d(m11, m12, m13, ...m44)` - valid matrix3d() transform function
1516
- * * `translate(tx, ty) rotateX(alpha)` - any valid transform function(s)
1517
- *
1518
- * @copyright thednp © 2021
1519
- *
1520
- * @param {string} source valid CSS transform string syntax.
1521
- * @return {CSSMatrix} the resulted matrix.
1522
- */
1523
- function fromString(source) {
1524
- if (typeof source !== 'string') {
1525
- throw TypeError(`CSSMatrix: "${source}" is not a string.`);
1526
- }
1527
- const str = String(source).replace(/\s/g, '');
1528
- let m = new CSSMatrix();
1529
- const invalidStringError = `CSSMatrix: invalid transform string "${source}"`;
1530
-
1531
- // const px = ['perspective'];
1532
- // const length = ['translate', 'translate3d', 'translateX', 'translateY', 'translateZ'];
1533
- // const deg = ['rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'skew', 'skewX', 'skewY'];
1534
- // const abs = ['scale', 'scale3d', 'matrix', 'matrix3d'];
1535
- // const transformFunctions = px.concat(length, deg, abs);
1536
-
1537
- str.split(')').filter((f) => f).forEach((tf) => {
1538
- const [prop, value] = tf.split('(');
1539
-
1540
- // invalidate empty string
1541
- if (!value) throw TypeError(invalidStringError);
1542
-
1543
- const components = value.split(',')
1544
- .map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
1545
-
1546
- const [x, y, z, a] = components;
1547
- const xyz = [x, y, z];
1548
- const xyza = [x, y, z, a];
1549
-
1550
- // single number value expected
1551
- if (prop === 'perspective' && x && [y, z].every((n) => n === undefined)) {
1552
- m.m34 = -1 / x;
1553
- // 6/16 number values expected
1554
- } else if (prop.includes('matrix') && [6, 16].includes(components.length)
1555
- && components.every((n) => !Number.isNaN(+n))) {
1556
- const values = components.map((n) => (Math.abs(n) < 1e-6 ? 0 : n));
1557
- // @ts-ignore -- conditions should suffice
1558
- m = m.multiply(fromArray(values));
1559
- // 3 values expected
1560
- } else if (prop === 'translate3d' && xyz.every((n) => !Number.isNaN(+n))) {
1561
- m = m.translate(x, y, z);
1562
- // single/double number value(s) expected
1563
- } else if (prop === 'translate' && x && z === undefined) {
1564
- m = m.translate(x, y || 0, 0);
1565
- // all 4 values expected
1566
- } else if (prop === 'rotate3d' && xyza.every((n) => !Number.isNaN(+n)) && a) {
1567
- m = m.rotateAxisAngle(x, y, z, a);
1568
- // single value expected
1569
- } else if (prop === 'rotate' && x && [y, z].every((n) => n === undefined)) {
1570
- m = m.rotate(0, 0, x);
1571
- // 3 values expected
1572
- } else if (prop === 'scale3d' && xyz.every((n) => !Number.isNaN(+n)) && xyz.some((n) => n !== 1)) {
1573
- m = m.scale(x, y, z);
1574
- // single value expected
1575
- } else if (prop === 'scale' && !Number.isNaN(x) && x !== 1 && z === undefined) {
1576
- const nosy = Number.isNaN(+y);
1577
- const sy = nosy ? x : y;
1578
- m = m.scale(x, sy, 1);
1579
- // single/double value expected
1580
- } else if (prop === 'skew' && (x || (!Number.isNaN(x) && y)) && z === undefined) {
1581
- m = m.skew(x, y || 0);
1582
- } else if (/[XYZ]/.test(prop) && x && [y, z].every((n) => n === undefined) // a single value expected
1583
- && ['translate', 'rotate', 'scale', 'skew'].some((p) => prop.includes(p))) {
1584
- if (['skewX', 'skewY'].includes(prop)) {
1585
- // @ts-ignore unfortunately
1586
- m = m[prop](x);
1587
- } else {
1588
- const fn = prop.replace(/[XYZ]/, '');
1589
- const axis = prop.replace(fn, '');
1590
- const idx = ['X', 'Y', 'Z'].indexOf(axis);
1591
- const def = fn === 'scale' ? 1 : 0;
1592
- const axeValues = [
1593
- idx === 0 ? x : def,
1594
- idx === 1 ? x : def,
1595
- idx === 2 ? x : def];
1596
- // @ts-ignore unfortunately
1597
- m = m[fn](...axeValues);
1598
- }
1599
- } else {
1600
- throw TypeError(invalidStringError);
1601
- }
1602
- });
1603
-
1604
- return m;
1605
- }
1606
-
1607
- /**
1608
- * Returns an *Array* containing elements which comprise the matrix.
1609
- * The method can return either the 16 elements or the 6 elements
1610
- * depending on the value of the `is2D` parameter.
1611
- *
1612
- * @param {CSSMatrix | DOMMatrix | CSSM.JSONMatrix} m the source matrix to feed values from.
1613
- * @param {boolean=} is2D *Array* representation of the matrix
1614
- * @return {CSSM.matrix | CSSM.matrix3d} an *Array* representation of the matrix
1615
- */
1616
- function toArray(m, is2D) {
1617
- if (is2D) {
1618
- return [m.a, m.b, m.c, m.d, m.e, m.f];
1619
- }
1620
- return [m.m11, m.m12, m.m13, m.m14,
1621
- m.m21, m.m22, m.m23, m.m24,
1622
- m.m31, m.m32, m.m33, m.m34,
1623
- m.m41, m.m42, m.m43, m.m44];
1624
- }
1625
-
1626
- // Transform Functions
1627
- // https://www.w3.org/TR/css-transforms-1/#transform-functions
1628
-
1629
- /**
1630
- * Creates a new `CSSMatrix` for the translation matrix and returns it.
1631
- * This method is equivalent to the CSS `translate3d()` function.
1632
- *
1633
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
1634
- *
1635
- * @param {number} x the `x-axis` position.
1636
- * @param {number} y the `y-axis` position.
1637
- * @param {number} z the `z-axis` position.
1638
- * @return {CSSMatrix} the resulted matrix.
1639
- */
1640
- function Translate(x, y, z) {
1641
- const m = new CSSMatrix();
1642
- m.m41 = x;
1643
- m.e = x;
1644
- m.m42 = y;
1645
- m.f = y;
1646
- m.m43 = z;
1647
- return m;
1648
- }
1649
-
1650
- /**
1651
- * Creates a new `CSSMatrix` for the rotation matrix and returns it.
1652
- *
1653
- * http://en.wikipedia.org/wiki/Rotation_matrix
1654
- *
1655
- * @param {number} rx the `x-axis` rotation.
1656
- * @param {number} ry the `y-axis` rotation.
1657
- * @param {number} rz the `z-axis` rotation.
1658
- * @return {CSSMatrix} the resulted matrix.
1659
- */
1660
- function Rotate(rx, ry, rz) {
1661
- const m = new CSSMatrix();
1662
- const degToRad = Math.PI / 180;
1663
- const radX = rx * degToRad;
1664
- const radY = ry * degToRad;
1665
- const radZ = rz * degToRad;
1666
-
1667
- // minus sin() because of right-handed system
1668
- const cosx = Math.cos(radX);
1669
- const sinx = -Math.sin(radX);
1670
- const cosy = Math.cos(radY);
1671
- const siny = -Math.sin(radY);
1672
- const cosz = Math.cos(radZ);
1673
- const sinz = -Math.sin(radZ);
1674
-
1675
- const m11 = cosy * cosz;
1676
- const m12 = -cosy * sinz;
1677
-
1678
- m.m11 = m11;
1679
- m.a = m11;
1680
-
1681
- m.m12 = m12;
1682
- m.b = m12;
1683
-
1684
- m.m13 = siny;
1685
-
1686
- const m21 = sinx * siny * cosz + cosx * sinz;
1687
- m.m21 = m21;
1688
- m.c = m21;
1689
-
1690
- const m22 = cosx * cosz - sinx * siny * sinz;
1691
- m.m22 = m22;
1692
- m.d = m22;
1693
-
1694
- m.m23 = -sinx * cosy;
1695
-
1696
- m.m31 = sinx * sinz - cosx * siny * cosz;
1697
- m.m32 = sinx * cosz + cosx * siny * sinz;
1698
- m.m33 = cosx * cosy;
1699
-
1700
- return m;
1701
- }
1702
-
1703
- /**
1704
- * Creates a new `CSSMatrix` for the rotation matrix and returns it.
1705
- * This method is equivalent to the CSS `rotate3d()` function.
1706
- *
1707
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
1708
- *
1709
- * @param {number} x the `x-axis` vector length.
1710
- * @param {number} y the `y-axis` vector length.
1711
- * @param {number} z the `z-axis` vector length.
1712
- * @param {number} alpha the value in degrees of the rotation.
1713
- * @return {CSSMatrix} the resulted matrix.
1714
- */
1715
- function RotateAxisAngle(x, y, z, alpha) {
1716
- const m = new CSSMatrix();
1717
- const length = Math.sqrt(x * x + y * y + z * z);
1718
-
1719
- if (length === 0) {
1720
- // bad vector length, return identity
1721
- return m;
1722
- }
1723
-
1724
- const X = x / length;
1725
- const Y = y / length;
1726
- const Z = z / length;
1727
-
1728
- const angle = alpha * (Math.PI / 360);
1729
- const sinA = Math.sin(angle);
1730
- const cosA = Math.cos(angle);
1731
- const sinA2 = sinA * sinA;
1732
- const x2 = X * X;
1733
- const y2 = Y * Y;
1734
- const z2 = Z * Z;
1735
-
1736
- const m11 = 1 - 2 * (y2 + z2) * sinA2;
1737
- m.m11 = m11;
1738
- m.a = m11;
1739
-
1740
- const m12 = 2 * (X * Y * sinA2 + Z * sinA * cosA);
1741
- m.m12 = m12;
1742
- m.b = m12;
1743
-
1744
- m.m13 = 2 * (X * Z * sinA2 - Y * sinA * cosA);
1745
-
1746
- const m21 = 2 * (Y * X * sinA2 - Z * sinA * cosA);
1747
- m.m21 = m21;
1748
- m.c = m21;
1749
-
1750
- const m22 = 1 - 2 * (z2 + x2) * sinA2;
1751
- m.m22 = m22;
1752
- m.d = m22;
1753
-
1754
- m.m23 = 2 * (Y * Z * sinA2 + X * sinA * cosA);
1755
- m.m31 = 2 * (Z * X * sinA2 + Y * sinA * cosA);
1756
- m.m32 = 2 * (Z * Y * sinA2 - X * sinA * cosA);
1757
- m.m33 = 1 - 2 * (x2 + y2) * sinA2;
1758
-
1759
- return m;
1760
- }
1761
-
1762
- /**
1763
- * Creates a new `CSSMatrix` for the scale matrix and returns it.
1764
- * This method is equivalent to the CSS `scale3d()` function, except it doesn't
1765
- * accept {x, y, z} transform origin parameters.
1766
- *
1767
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
1768
- *
1769
- * @param {number} x the `x-axis` scale.
1770
- * @param {number} y the `y-axis` scale.
1771
- * @param {number} z the `z-axis` scale.
1772
- * @return {CSSMatrix} the resulted matrix.
1773
- */
1774
- function Scale(x, y, z) {
1775
- const m = new CSSMatrix();
1776
- m.m11 = x;
1777
- m.a = x;
1778
-
1779
- m.m22 = y;
1780
- m.d = y;
1781
-
1782
- m.m33 = z;
1783
- return m;
1784
- }
1785
-
1786
- /**
1787
- * Creates a new `CSSMatrix` for the shear of both the `x-axis` and`y-axis`
1788
- * matrix and returns it. This method is equivalent to the CSS `skew()` function.
1789
- *
1790
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew
1791
- *
1792
- * @param {number} angleX the X-angle in degrees.
1793
- * @param {number} angleY the Y-angle in degrees.
1794
- * @return {CSSMatrix} the resulted matrix.
1795
- */
1796
- function Skew(angleX, angleY) {
1797
- const m = new CSSMatrix();
1798
- if (angleX) {
1799
- const radX = (angleX * Math.PI) / 180;
1800
- const tX = Math.tan(radX);
1801
- m.m21 = tX;
1802
- m.c = tX;
1803
- }
1804
- if (angleY) {
1805
- const radY = (angleY * Math.PI) / 180;
1806
- const tY = Math.tan(radY);
1807
- m.m12 = tY;
1808
- m.b = tY;
1809
- }
1810
- return m;
1811
- }
1812
-
1813
- /**
1814
- * Creates a new `CSSMatrix` for the shear of the `x-axis` rotation matrix and
1815
- * returns it. This method is equivalent to the CSS `skewX()` function.
1816
- *
1817
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
1818
- *
1819
- * @param {number} angle the angle in degrees.
1820
- * @return {CSSMatrix} the resulted matrix.
1821
- */
1822
- function SkewX(angle) {
1823
- return Skew(angle, 0);
1824
- }
1825
-
1826
- /**
1827
- * Creates a new `CSSMatrix` for the shear of the `y-axis` rotation matrix and
1828
- * returns it. This method is equivalent to the CSS `skewY()` function.
1829
- *
1830
- * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
1831
- *
1832
- * @param {number} angle the angle in degrees.
1833
- * @return {CSSMatrix} the resulted matrix.
1834
- */
1835
- function SkewY(angle) {
1836
- return Skew(0, angle);
1837
- }
1838
-
1839
- /**
1840
- * Creates a new `CSSMatrix` resulted from the multiplication of two matrixes
1841
- * and returns it. Both matrixes are not changed.
1842
- *
1843
- * @param {CSSMatrix | DOMMatrix | CSSM.JSONMatrix} m1 the first matrix.
1844
- * @param {CSSMatrix | DOMMatrix | CSSM.JSONMatrix} m2 the second matrix.
1845
- * @return {CSSMatrix} the resulted matrix.
1846
- */
1847
- function Multiply(m1, m2) {
1848
- const m11 = m2.m11 * m1.m11 + m2.m12 * m1.m21 + m2.m13 * m1.m31 + m2.m14 * m1.m41;
1849
- const m12 = m2.m11 * m1.m12 + m2.m12 * m1.m22 + m2.m13 * m1.m32 + m2.m14 * m1.m42;
1850
- const m13 = m2.m11 * m1.m13 + m2.m12 * m1.m23 + m2.m13 * m1.m33 + m2.m14 * m1.m43;
1851
- const m14 = m2.m11 * m1.m14 + m2.m12 * m1.m24 + m2.m13 * m1.m34 + m2.m14 * m1.m44;
1852
-
1853
- const m21 = m2.m21 * m1.m11 + m2.m22 * m1.m21 + m2.m23 * m1.m31 + m2.m24 * m1.m41;
1854
- const m22 = m2.m21 * m1.m12 + m2.m22 * m1.m22 + m2.m23 * m1.m32 + m2.m24 * m1.m42;
1855
- const m23 = m2.m21 * m1.m13 + m2.m22 * m1.m23 + m2.m23 * m1.m33 + m2.m24 * m1.m43;
1856
- const m24 = m2.m21 * m1.m14 + m2.m22 * m1.m24 + m2.m23 * m1.m34 + m2.m24 * m1.m44;
1857
-
1858
- const m31 = m2.m31 * m1.m11 + m2.m32 * m1.m21 + m2.m33 * m1.m31 + m2.m34 * m1.m41;
1859
- const m32 = m2.m31 * m1.m12 + m2.m32 * m1.m22 + m2.m33 * m1.m32 + m2.m34 * m1.m42;
1860
- const m33 = m2.m31 * m1.m13 + m2.m32 * m1.m23 + m2.m33 * m1.m33 + m2.m34 * m1.m43;
1861
- const m34 = m2.m31 * m1.m14 + m2.m32 * m1.m24 + m2.m33 * m1.m34 + m2.m34 * m1.m44;
1862
-
1863
- const m41 = m2.m41 * m1.m11 + m2.m42 * m1.m21 + m2.m43 * m1.m31 + m2.m44 * m1.m41;
1864
- const m42 = m2.m41 * m1.m12 + m2.m42 * m1.m22 + m2.m43 * m1.m32 + m2.m44 * m1.m42;
1865
- const m43 = m2.m41 * m1.m13 + m2.m42 * m1.m23 + m2.m43 * m1.m33 + m2.m44 * m1.m43;
1866
- const m44 = m2.m41 * m1.m14 + m2.m42 * m1.m24 + m2.m43 * m1.m34 + m2.m44 * m1.m44;
1867
-
1868
- return fromArray(
1869
- [m11, m12, m13, m14,
1870
- m21, m22, m23, m24,
1871
- m31, m32, m33, m34,
1872
- m41, m42, m43, m44],
1873
- );
1874
- }
1875
-
1876
- /**
1877
- * Creates and returns a new `DOMMatrix` compatible instance
1878
- * with equivalent instance.
1879
- * @class CSSMatrix
1880
- *
1881
- * @author thednp <https://github.com/thednp/DOMMatrix/>
1882
- * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
1883
- */
1884
-
1885
- class CSSMatrix {
1886
- /**
1887
- * @constructor
1888
- * @param {any} args accepts all parameter configurations:
1889
- * * valid CSS transform string,
1890
- * * CSSMatrix/DOMMatrix instance,
1891
- * * a 6/16 elements *Array*.
1892
- */
1893
- constructor(...args) {
1894
- const m = this;
1895
- // array 6
1896
- m.a = 1; m.b = 0;
1897
- m.c = 0; m.d = 1;
1898
- m.e = 0; m.f = 0;
1899
- // array 16
1900
- m.m11 = 1; m.m12 = 0; m.m13 = 0; m.m14 = 0;
1901
- m.m21 = 0; m.m22 = 1; m.m23 = 0; m.m24 = 0;
1902
- m.m31 = 0; m.m32 = 0; m.m33 = 1; m.m34 = 0;
1903
- m.m41 = 0; m.m42 = 0; m.m43 = 0; m.m44 = 1;
1904
-
1905
- if (args.length) {
1906
- const ARGS = [16, 6].some((l) => l === args.length) ? args : args[0];
1907
-
1908
- return m.setMatrixValue(ARGS);
1909
- }
1910
- return m;
1911
- }
1912
-
1913
- /**
1914
- * A `Boolean` whose value is `true` if the matrix is the identity matrix. The identity
1915
- * matrix is one in which every value is 0 except those on the main diagonal from top-left
1916
- * to bottom-right corner (in other words, where the offsets in each direction are equal).
1917
- *
1918
- * @return {boolean} the current property value
1919
- */
1920
- get isIdentity() {
1921
- const m = this;
1922
- return (m.m11 === 1 && m.m12 === 0 && m.m13 === 0 && m.m14 === 0
1923
- && m.m21 === 0 && m.m22 === 1 && m.m23 === 0 && m.m24 === 0
1924
- && m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0
1925
- && m.m41 === 0 && m.m42 === 0 && m.m43 === 0 && m.m44 === 1);
1926
- }
1927
-
1928
- /**
1929
- * A `Boolean` flag whose value is `true` if the matrix was initialized as a 2D matrix
1930
- * and `false` if the matrix is 3D.
1931
- *
1932
- * @return {boolean} the current property value
1933
- */
1934
- get is2D() {
1935
- const m = this;
1936
- return (m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0 && m.m43 === 0 && m.m44 === 1);
1937
- }
1938
-
1939
- /**
1940
- * The `setMatrixValue` method replaces the existing matrix with one computed
1941
- * in the browser. EG: `matrix(1,0.25,-0.25,1,0,0)`
1942
- *
1943
- * The method accepts any *Array* values, the result of
1944
- * `DOMMatrix` instance method `toFloat64Array()` / `toFloat32Array()` calls
1945
- * or `CSSMatrix` instance method `toArray()`.
1946
- *
1947
- * This method expects valid *matrix()* / *matrix3d()* string values, as well
1948
- * as other transform functions like *translateX(10px)*.
1949
- *
1950
- * @param {string | CSSM.matrix | CSSM.matrix3d | CSSMatrix | DOMMatrix | CSSM.JSONMatrix} source
1951
- * @return {CSSMatrix} the matrix instance
1952
- */
1953
- setMatrixValue(source) {
1954
- const m = this;
1955
-
1956
- // CSS transform string source - TransformList first
1957
- if (typeof source === 'string' && source.length && source !== 'none') {
1958
- return fromString(source);
1959
- }
1960
- // [Arguments list | Array] come second
1961
- if ([Array, Float64Array, Float32Array].some((a) => source instanceof a)) {
1962
- // @ts-ignore
1963
- return fromArray(source);
1964
- }
1965
- // new CSSMatrix(CSSMatrix | DOMMatrix | JSON) last
1966
- if ([CSSMatrix, DOMMatrix, Object].some((a) => source instanceof a)) {
1967
- // @ts-ignore
1968
- return fromMatrix(source);
1969
- }
1970
-
1971
- return m;
1972
- }
1973
-
1974
- /**
1975
- * Returns a *Float32Array* containing elements which comprise the matrix.
1976
- * The method can return either the 16 elements or the 6 elements
1977
- * depending on the value of the `is2D` parameter.
1978
- *
1979
- * @param {boolean=} is2D *Array* representation of the matrix
1980
- * @return {Float32Array} an *Array* representation of the matrix
1981
- */
1982
- toFloat32Array(is2D) {
1983
- return Float32Array.from(toArray(this, is2D));
1984
- }
1985
-
1986
- /**
1987
- * Returns a *Float64Array* containing elements which comprise the matrix.
1988
- * The method can return either the 16 elements or the 6 elements
1989
- * depending on the value of the `is2D` parameter.
1990
- *
1991
- * @param {boolean=} is2D *Array* representation of the matrix
1992
- * @return {Float64Array} an *Array* representation of the matrix
1993
- */
1994
- toFloat64Array(is2D) {
1995
- return Float64Array.from(toArray(this, is2D));
1996
- }
1997
-
1998
- /**
1999
- * Creates and returns a string representation of the matrix in `CSS` matrix syntax,
2000
- * using the appropriate `CSS` matrix notation.
2001
- *
2002
- * matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
2003
- * matrix *matrix(a, b, c, d, e, f)*
2004
- *
2005
- * @return {string} a string representation of the matrix
2006
- */
2007
- toString() {
2008
- const m = this;
2009
- const { is2D } = m;
2010
- const values = m.toFloat64Array(is2D).join(', ');
2011
- const type = is2D ? 'matrix' : 'matrix3d';
2012
- return `${type}(${values})`;
2013
- }
2014
-
2015
- /**
2016
- * Returns a JSON representation of the `CSSMatrix` instance, a standard *Object*
2017
- * that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties as well
2018
- * as the `is2D` & `isIdentity` properties.
2019
- *
2020
- * The result can also be used as a second parameter for the `fromMatrix` static method
2021
- * to load values into another matrix instance.
2022
- *
2023
- * @return {CSSM.JSONMatrix} an *Object* with all matrix values.
2024
- */
2025
- toJSON() {
2026
- const m = this;
2027
- const { is2D, isIdentity } = m;
2028
- return { ...m, is2D, isIdentity };
2029
- }
2030
-
2031
- /**
2032
- * The Multiply method returns a new CSSMatrix which is the result of this
2033
- * matrix multiplied by the passed matrix, with the passed matrix to the right.
2034
- * This matrix is not modified.
2035
- *
2036
- * @param {CSSMatrix | DOMMatrix | CSSM.JSONMatrix} m2 CSSMatrix
2037
- * @return {CSSMatrix} The resulted matrix.
2038
- */
2039
- multiply(m2) {
2040
- return Multiply(this, m2);
2041
- }
2042
-
2043
- /**
2044
- * The translate method returns a new matrix which is this matrix post
2045
- * multiplied by a translation matrix containing the passed values. If the z
2046
- * component is undefined, a 0 value is used in its place. This matrix is not
2047
- * modified.
2048
- *
2049
- * @param {number} x X component of the translation value.
2050
- * @param {number=} y Y component of the translation value.
2051
- * @param {number=} z Z component of the translation value.
2052
- * @return {CSSMatrix} The resulted matrix
2053
- */
2054
- translate(x, y, z) {
2055
- const X = x;
2056
- let Y = y;
2057
- let Z = z;
2058
- if (Y === undefined) Y = 0;
2059
- if (Z === undefined) Z = 0;
2060
- return Multiply(this, Translate(X, Y, Z));
2061
- }
2062
-
2063
- /**
2064
- * The scale method returns a new matrix which is this matrix post multiplied by
2065
- * a scale matrix containing the passed values. If the z component is undefined,
2066
- * a 1 value is used in its place. If the y component is undefined, the x
2067
- * component value is used in its place. This matrix is not modified.
2068
- *
2069
- * @param {number} x The X component of the scale value.
2070
- * @param {number=} y The Y component of the scale value.
2071
- * @param {number=} z The Z component of the scale value.
2072
- * @return {CSSMatrix} The resulted matrix
2073
- */
2074
- scale(x, y, z) {
2075
- const X = x;
2076
- let Y = y;
2077
- let Z = z;
2078
- if (Y === undefined) Y = x;
2079
- if (Z === undefined) Z = 1; // Z must be 1 if undefined
2080
-
2081
- return Multiply(this, Scale(X, Y, Z));
2082
- }
2083
-
2084
- /**
2085
- * The rotate method returns a new matrix which is this matrix post multiplied
2086
- * by each of 3 rotation matrices about the major axes, first X, then Y, then Z.
2087
- * If the y and z components are undefined, the x value is used to rotate the
2088
- * object about the z axis, as though the vector (0,0,x) were passed. All
2089
- * rotation values are in degrees. This matrix is not modified.
2090
- *
2091
- * @param {number} rx The X component of the rotation, or Z if Y and Z are null.
2092
- * @param {number=} ry The (optional) Y component of the rotation value.
2093
- * @param {number=} rz The (optional) Z component of the rotation value.
2094
- * @return {CSSMatrix} The resulted matrix
2095
- */
2096
- rotate(rx, ry, rz) {
2097
- let RX = rx;
2098
- let RY = ry || 0;
2099
- let RZ = rz || 0;
2100
-
2101
- if (typeof rx === 'number' && ry === undefined && rz === undefined) {
2102
- RZ = RX; RX = 0; RY = 0;
2103
- }
2104
-
2105
- return Multiply(this, Rotate(RX, RY, RZ));
2106
- }
2107
-
2108
- /**
2109
- * The rotateAxisAngle method returns a new matrix which is this matrix post
2110
- * multiplied by a rotation matrix with the given axis and `angle`. The right-hand
2111
- * rule is used to determine the direction of rotation. All rotation values are
2112
- * in degrees. This matrix is not modified.
2113
- *
2114
- * @param {number} x The X component of the axis vector.
2115
- * @param {number} y The Y component of the axis vector.
2116
- * @param {number} z The Z component of the axis vector.
2117
- * @param {number} angle The angle of rotation about the axis vector, in degrees.
2118
- * @return {CSSMatrix} The resulted matrix
2119
- */
2120
- rotateAxisAngle(x, y, z, angle) {
2121
- if ([x, y, z, angle].some((n) => Number.isNaN(+n))) {
2122
- throw new TypeError('CSSMatrix: expecting 4 values');
2123
- }
2124
- return Multiply(this, RotateAxisAngle(x, y, z, angle));
2125
- }
2126
-
2127
- /**
2128
- * Specifies a skew transformation along the `x-axis` by the given angle.
2129
- * This matrix is not modified.
2130
- *
2131
- * @param {number} angle The angle amount in degrees to skew.
2132
- * @return {CSSMatrix} The resulted matrix
2133
- */
2134
- skewX(angle) {
2135
- return Multiply(this, SkewX(angle));
2136
- }
2137
-
2138
- /**
2139
- * Specifies a skew transformation along the `y-axis` by the given angle.
2140
- * This matrix is not modified.
2141
- *
2142
- * @param {number} angle The angle amount in degrees to skew.
2143
- * @return {CSSMatrix} The resulted matrix
2144
- */
2145
- skewY(angle) {
2146
- return Multiply(this, SkewY(angle));
2147
- }
2148
-
2149
- /**
2150
- * Specifies a skew transformation along both the `x-axis` and `y-axis`.
2151
- * This matrix is not modified.
2152
- *
2153
- * @param {number} angleX The X-angle amount in degrees to skew.
2154
- * @param {number} angleY The angle amount in degrees to skew.
2155
- * @return {CSSMatrix} The resulted matrix
2156
- */
2157
- skew(angleX, angleY) {
2158
- return Multiply(this, Skew(angleX, angleY));
2159
- }
2160
-
2161
- /**
2162
- * Transforms a specified vector using the matrix, returning a new
2163
- * {x,y,z,w} Tuple *Object* comprising the transformed vector.
2164
- * Neither the matrix nor the original vector are altered.
2165
- *
2166
- * The method is equivalent with `transformPoint()` method
2167
- * of the `DOMMatrix` constructor.
2168
- *
2169
- * @param {CSSM.PointTuple | DOMPoint} t Tuple with `{x,y,z,w}` components
2170
- * @return {CSSM.PointTuple | DOMPoint} the resulting Tuple
2171
- */
2172
- transformPoint(t) {
2173
- const m = this;
2174
-
2175
- const x = m.m11 * t.x + m.m21 * t.y + m.m31 * t.z + m.m41 * t.w;
2176
- const y = m.m12 * t.x + m.m22 * t.y + m.m32 * t.z + m.m42 * t.w;
2177
- const z = m.m13 * t.x + m.m23 * t.y + m.m33 * t.z + m.m43 * t.w;
2178
- const w = m.m14 * t.x + m.m24 * t.y + m.m34 * t.z + m.m44 * t.w;
2179
-
2180
- return t instanceof DOMPoint
2181
- ? new DOMPoint(x, y, z, w)
2182
- : {
2183
- x, y, z, w,
2184
- };
2185
- }
2186
- }
2187
-
2188
- // Add Transform Functions to CSSMatrix object
2189
- // without creating a TypeScript namespace.
2190
- Object.assign(CSSMatrix, {
2191
- Translate,
2192
- Rotate,
2193
- RotateAxisAngle,
2194
- Scale,
2195
- SkewX,
2196
- SkewY,
2197
- Skew,
2198
- Multiply,
2199
- fromArray,
2200
- fromMatrix,
2201
- fromString,
2202
- toArray,
2203
- });
2204
-
2205
- var version$1 = "1.0.3";
2206
-
2207
- /**
2208
- * A global namespace for library version.
2209
- * @type {string}
2210
- */
2211
- const Version$1 = version$1;
2212
-
2213
- /** @typedef {import('../types/index')} */
2214
-
2215
- Object.assign(CSSMatrix, { Version: Version$1 });
2216
-
2217
- /**
2218
- * Returns a transformation matrix to apply to `<path>` elements.
2219
- *
2220
- * @see SVGPath.transformObject
2221
- *
2222
- * @param {SVGPath.transformObject} transform the `transformObject`
2223
- * @returns {CSSMatrix} a new transformation matrix
2224
- */
2225
- function getSVGMatrix(transform) {
2226
- let matrix = new CSSMatrix();
2227
- const { origin } = transform;
2228
- const [originX, originY] = origin;
2229
- const { translate } = transform;
2230
- const { rotate } = transform;
2231
- const { skew } = transform;
2232
- const { scale } = transform;
2233
-
2234
- // set translate
2235
- if (Array.isArray(translate) && translate.every((x) => !Number.isNaN(+x))
2236
- && translate.some((x) => x !== 0)) {
2237
- matrix = matrix.translate(...translate);
2238
- } else if (typeof translate === 'number' && !Number.isNaN(translate)) {
2239
- matrix = matrix.translate(translate);
2240
- }
2241
-
2242
- if (rotate || skew || scale) {
2243
- // set SVG transform-origin, always defined
2244
- matrix = matrix.translate(originX, originY);
2245
-
2246
- // set rotation
2247
- if (Array.isArray(rotate) && rotate.every((x) => !Number.isNaN(+x))
2248
- && rotate.some((x) => x !== 0)) {
2249
- matrix = matrix.rotate(...rotate);
2250
- } else if (typeof rotate === 'number' && !Number.isNaN(rotate)) {
2251
- matrix = matrix.rotate(rotate);
2252
- }
2253
-
2254
- // set skew(s)
2255
- if (Array.isArray(skew) && skew.every((x) => !Number.isNaN(+x))
2256
- && skew.some((x) => x !== 0)) {
2257
- matrix = skew[0] ? matrix.skewX(skew[0]) : matrix;
2258
- matrix = skew[1] ? matrix.skewY(skew[1]) : matrix;
2259
- } else if (typeof skew === 'number' && !Number.isNaN(skew)) {
2260
- matrix = matrix.skewX(skew);
2261
- }
2262
-
2263
- // set scale
2264
- if (Array.isArray(scale) && scale.every((x) => !Number.isNaN(+x))
2265
- && scale.some((x) => x !== 1)) {
2266
- matrix = matrix.scale(...scale);
2267
- } else if (typeof scale === 'number' && !Number.isNaN(scale)) {
2268
- matrix = matrix.scale(scale);
2269
- }
2270
- // set SVG transform-origin
2271
- matrix = matrix.translate(-originX, -originY);
2272
- }
2273
-
2274
- return matrix;
2275
- }
2276
-
2277
- /**
2278
- * Transforms a specified point using a matrix, returning a new
2279
- * Tuple *Object* comprising of the transformed point.
2280
- * Neither the matrix nor the original point are altered.
2281
- *
2282
- * @copyright thednp © 2021
2283
- *
2284
- * @param {SVGPath.CSSMatrix} M CSSMatrix instance
2285
- * @param {[number, number, number, number]} v Tuple
2286
- * @return {[number, number, number, number]} the resulting Tuple
2287
- */
2288
- function translatePoint(M, v) {
2289
- let m = Translate(...v);
2290
-
2291
- [,,, m.m44] = v;
2292
- m = M.multiply(m);
2293
-
2294
- return [m.m41, m.m42, m.m43, m.m44];
2295
- }
2296
-
2297
- /**
2298
- * Returns the [x,y] projected coordinates for a given an [x,y] point
2299
- * and an [x,y,z] perspective origin point.
2300
- *
2301
- * Equation found here =>
2302
- * http://en.wikipedia.org/wiki/3D_projection#Diagram
2303
- * Details =>
2304
- * https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
2305
- *
2306
- * @param {SVGPath.CSSMatrix} m the transformation matrix
2307
- * @param {[number, number]} point2D the initial [x,y] coordinates
2308
- * @param {number[]} origin the [x,y,z] transform origin
2309
- * @returns {[number, number]} the projected [x,y] coordinates
2310
- */
2311
- function projection2d(m, point2D, origin) {
2312
- const [originX, originY, originZ] = origin;
2313
- const [x, y, z] = translatePoint(m, [...point2D, 0, 1]);
2314
-
2315
- const relativePositionX = x - originX;
2316
- const relativePositionY = y - originY;
2317
- const relativePositionZ = z - originZ;
2318
-
2319
- return [
2320
- // protect against division by ZERO
2321
- relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originX,
2322
- relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ) || 1) + originY,
2323
- ];
2324
- }
2325
-
2326
- /**
2327
- * Apply a 2D / 3D transformation to a `pathArray` instance.
2328
- *
2329
- * Since *SVGElement* doesn't support 3D transformation, this function
2330
- * creates a 2D projection of the <path> element.
2331
- *
2332
- * @param {SVGPath.pathArray} path the `pathArray` to apply transformation
2333
- * @param {SVGPath.transformObject} transform the transform functions `Object`
2334
- * @returns {SVGPath.pathArray} the resulted `pathArray`
2335
- */
2336
- function transformPath(path, transform) {
2337
- let x = 0; let y = 0; let i; let j; let ii; let jj; let lx; let ly;
2338
- const absolutePath = pathToAbsolute(path);
2339
- const transformProps = transform && Object.keys(transform);
2340
-
2341
- // when used as a static method, invalidate somehow
2342
- if (!transform || !transformProps.length) return clonePath(absolutePath);
2343
-
2344
- const normalizedPath = normalizePath(absolutePath);
2345
- // transform origin is extremely important
2346
- if (!transform.origin) {
2347
- const { origin: defaultOrigin } = defaultOptions;
2348
- Object.assign(transform, { origin: defaultOrigin });
2349
- }
2350
- const matrixInstance = getSVGMatrix(transform);
2351
- const { origin } = transform;
2352
- const params = { ...paramsParser };
2353
- /** @type {SVGPath.pathSegment} */
2354
- let segment = [];
2355
- let seglen = 0;
2356
- let pathCommand = '';
2357
- /** @type {SVGPath.pathTransformList[]} */
2358
- let transformedPath = [];
2359
- const allPathCommands = []; // needed for arc to curve transformation
2360
-
2361
- if (!matrixInstance.isIdentity) {
2362
- for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
2363
- segment = absolutePath[i];
2364
-
2365
- /* istanbul ignore else */
2366
- if (absolutePath[i]) [pathCommand] = segment;
2367
-
2368
- // REPLACE Arc path commands with Cubic Beziers
2369
- // we don't have any scripting know-how on 3d ellipse transformation
2370
- // Arc segments don't work 3D transformations or skews
2371
- /// ////////////////////////////////////////
2372
- allPathCommands[i] = pathCommand;
2373
-
2374
- if (pathCommand === 'A') {
2375
- segment = segmentToCubic(normalizedPath[i], params);
2376
-
2377
- absolutePath[i] = segmentToCubic(normalizedPath[i], params);
2378
- fixArc(absolutePath, allPathCommands, i);
2379
-
2380
- normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
2381
- fixArc(normalizedPath, allPathCommands, i);
2382
- ii = Math.max(absolutePath.length, normalizedPath.length);
2383
- }
2384
-
2385
- /// ////////////////////////////////////////
2386
- segment = normalizedPath[i];
2387
- seglen = segment.length;
2388
-
2389
- params.x1 = +segment[seglen - 2];
2390
- params.y1 = +segment[seglen - 1];
2391
- params.x2 = +(segment[seglen - 4]) || params.x1;
2392
- params.y2 = +(segment[seglen - 3]) || params.y1;
2393
-
2394
- /** @type {SVGPath.pathTransformList} */
2395
- const result = {
2396
- s: absolutePath[i], c: absolutePath[i][0], x: params.x1, y: params.y1,
2397
- };
2398
-
2399
- transformedPath = [...transformedPath, ...[result]];
2400
- }
2401
-
2402
- return transformedPath.map((seg) => {
2403
- pathCommand = seg.c;
2404
- segment = seg.s;
2405
- switch (pathCommand) {
2406
- case 'L':
2407
- case 'H':
2408
- case 'V':
2409
- [lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin);
2410
-
2411
- /* istanbul ignore else */
2412
- if (x !== lx && y !== ly) {
2413
- segment = ['L', lx, ly];
2414
- } else if (y === ly) {
2415
- segment = ['H', lx];
2416
- } else if (x === lx) {
2417
- segment = ['V', ly];
2418
- }
2419
-
2420
- x = lx; y = ly; // now update x and y
2421
-
2422
- return segment;
2423
- default:
2424
-
2425
- for (j = 1, jj = segment.length; j < jj; j += 2) {
2426
- [x, y] = projection2d(matrixInstance, [+segment[j], +segment[j + 1]], origin);
2427
- segment[j] = x;
2428
- segment[j + 1] = y;
2429
- }
2430
-
2431
- return segment;
2432
- }
2433
- });
2434
- }
2435
- return clonePath(absolutePath);
2436
- }
2437
-
2438
- /**
2439
- *
2440
- * @param {{x: number, y: number}} v0
2441
- * @param {{x: number, y: number}} v1
2442
- * @returns {{x: number, y: number}}
2443
- */
2444
- function angleBetween(v0, v1) {
2445
- const { x: v0x, y: v0y } = v0;
2446
- const { x: v1x, y: v1y } = v1;
2447
- const p = v0x * v1x + v0y * v1y;
2448
- const n = Math.sqrt((v0x ** 2 + v0y ** 2) * (v1x ** 2 + v1y ** 2));
2449
- const sign = v0x * v1y - v0y * v1x < 0 ? -1 : 1;
2450
- const angle = sign * Math.acos(p / n);
2451
-
2452
- return angle;
2453
- }
2454
-
2455
- /**
2456
- * Returns a {x,y} point at a given length, the total length and
2457
- * the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
2458
- * @see https://github.com/MadLittleMods/svg-curve-lib/blob/master/src/js/svg-curve-lib.js
2459
- *
2460
- * @param {number} x1 the starting x position
2461
- * @param {number} y1 the starting y position
2462
- * @param {number} RX x-radius of the arc
2463
- * @param {number} RY y-radius of the arc
2464
- * @param {number} angle x-axis-rotation of the arc
2465
- * @param {number} LAF large-arc-flag of the arc
2466
- * @param {number} SF sweep-flag of the arc
2467
- * @param {number} x the ending x position
2468
- * @param {number} y the ending y position
2469
- * @param {number} t the point distance
2470
- * @returns {{x: number, y: number}} the requested point
2471
- */
2472
- function getPointAtArcSegmentLength(x1, y1, RX, RY, angle, LAF, SF, x, y, t) {
2473
- const {
2474
- abs, sin, cos, sqrt, PI,
2475
- } = Math;
2476
- let rx = abs(RX);
2477
- let ry = abs(RY);
2478
- const xRot = ((angle % 360) + 360) % 360;
2479
- const xRotRad = xRot * (PI / 180);
2480
-
2481
- if (x1 === x && y1 === y) {
2482
- return { x: x1, y: y1 };
2483
- }
2484
-
2485
- if (rx === 0 || ry === 0) {
2486
- return segmentLineFactory(x1, y1, x, y, t).point;
2487
- }
2488
-
2489
- const dx = (x1 - x) / 2;
2490
- const dy = (y1 - y) / 2;
2491
-
2492
- const transformedPoint = {
2493
- x: cos(xRotRad) * dx + sin(xRotRad) * dy,
2494
- y: -sin(xRotRad) * dx + cos(xRotRad) * dy,
2495
- };
2496
-
2497
- const radiiCheck = transformedPoint.x ** 2 / rx ** 2 + transformedPoint.y ** 2 / ry ** 2;
2498
-
2499
- if (radiiCheck > 1) {
2500
- rx *= sqrt(radiiCheck);
2501
- ry *= sqrt(radiiCheck);
2502
- }
2503
-
2504
- const cSquareNumerator = rx ** 2 * ry ** 2
2505
- - rx ** 2 * transformedPoint.y ** 2
2506
- - ry ** 2 * transformedPoint.x ** 2;
2507
-
2508
- const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2
2509
- + ry ** 2 * transformedPoint.x ** 2;
2510
-
2511
- let cRadicand = cSquareNumerator / cSquareRootDenom;
2512
- cRadicand = cRadicand < 0 ? 0 : cRadicand;
2513
- const cCoef = (LAF !== SF ? 1 : -1) * sqrt(cRadicand);
2514
- const transformedCenter = {
2515
- x: cCoef * ((rx * transformedPoint.y) / ry),
2516
- y: cCoef * (-(ry * transformedPoint.x) / rx),
2517
- };
2518
-
2519
- const center = {
2520
- x: cos(xRotRad) * transformedCenter.x
2521
- - sin(xRotRad) * transformedCenter.y + (x1 + x) / 2,
2522
- y: sin(xRotRad) * transformedCenter.x
2523
- + cos(xRotRad) * transformedCenter.y + (y1 + y) / 2,
2524
- };
2525
-
2526
- const startVector = {
2527
- x: (transformedPoint.x - transformedCenter.x) / rx,
2528
- y: (transformedPoint.y - transformedCenter.y) / ry,
2529
- };
2530
-
2531
- const startAngle = angleBetween({ x: 1, y: 0 }, startVector);
2532
-
2533
- const endVector = {
2534
- x: (-transformedPoint.x - transformedCenter.x) / rx,
2535
- y: (-transformedPoint.y - transformedCenter.y) / ry,
2536
- };
2537
-
2538
- let sweepAngle = angleBetween(startVector, endVector);
2539
- if (!SF && sweepAngle > 0) {
2540
- sweepAngle -= 2 * PI;
2541
- } else if (SF && sweepAngle < 0) {
2542
- sweepAngle += 2 * PI;
2543
- }
2544
- sweepAngle %= 2 * PI;
2545
-
2546
- const alpha = startAngle + sweepAngle * t;
2547
- const ellipseComponentX = rx * cos(alpha);
2548
- const ellipseComponentY = ry * sin(alpha);
2549
-
2550
- const point = {
2551
- x: cos(xRotRad) * ellipseComponentX
2552
- - sin(xRotRad) * ellipseComponentY
2553
- + center.x,
2554
- y: sin(xRotRad) * ellipseComponentX
2555
- + cos(xRotRad) * ellipseComponentY
2556
- + center.y,
2557
- };
2558
-
2559
- // to be used later
2560
- // point.ellipticalArcStartAngle = startAngle;
2561
- // point.ellipticalArcEndAngle = startAngle + sweepAngle;
2562
- // point.ellipticalArcAngle = alpha;
2563
-
2564
- // point.ellipticalArcCenter = center;
2565
- // point.resultantRx = rx;
2566
- // point.resultantRy = ry;
2567
-
2568
- return point;
2569
- }
2570
-
2571
- /**
2572
- * Returns a {x,y} point at a given length, the total length and
2573
- * the shape minimum and maximum {x,y} coordinates of an A (arc-to) segment.
2574
- *
2575
- * @param {number} X1 the starting x position
2576
- * @param {number} Y1 the starting y position
2577
- * @param {number} RX x-radius of the arc
2578
- * @param {number} RY y-radius of the arc
2579
- * @param {number} angle x-axis-rotation of the arc
2580
- * @param {number} LAF large-arc-flag of the arc
2581
- * @param {number} SF sweep-flag of the arc
2582
- * @param {number} X2 the ending x position
2583
- * @param {number} Y2 the ending y position
2584
- * @param {number} distance the point distance
2585
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
2586
- */
2587
- function segmentArcFactory(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, distance) {
2588
- const distanceIsNumber = typeof distance === 'number';
2589
- let x = X1; let y = Y1;
2590
- let LENGTH = 0;
2591
- let prev = [x, y, LENGTH];
2592
- let cur = [x, y];
2593
- let t = 0;
2594
- let POINT = { x: 0, y: 0 };
2595
- let POINTS = [{ x, y }];
2596
-
2597
- if (distanceIsNumber && distance === 0) {
2598
- POINT = { x, y };
2599
- }
2600
-
2601
- const sampleSize = 300;
2602
- for (let j = 0; j <= sampleSize; j += 1) {
2603
- t = j / sampleSize;
2604
-
2605
- ({ x, y } = getPointAtArcSegmentLength(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, t));
2606
- POINTS = [...POINTS, { x, y }];
2607
- LENGTH += distanceSquareRoot(cur, [x, y]);
2608
- cur = [x, y];
2609
-
2610
- if (distanceIsNumber && LENGTH > distance && distance > prev[2]) {
2611
- const dv = (LENGTH - distance) / (LENGTH - prev[2]);
2612
-
2613
- POINT = {
2614
- x: cur[0] * (1 - dv) + prev[0] * dv,
2615
- y: cur[1] * (1 - dv) + prev[1] * dv,
2616
- };
2617
- }
2618
- prev = [x, y, LENGTH];
2619
- }
2620
-
2621
- if (distanceIsNumber && distance >= LENGTH) {
2622
- POINT = { x: X2, y: Y2 };
2623
- }
2624
-
2625
- return {
2626
- length: LENGTH,
2627
- point: POINT,
2628
- min: {
2629
- x: Math.min(...POINTS.map((n) => n.x)),
2630
- y: Math.min(...POINTS.map((n) => n.y)),
2631
- },
2632
- max: {
2633
- x: Math.max(...POINTS.map((n) => n.x)),
2634
- y: Math.max(...POINTS.map((n) => n.y)),
2635
- },
2636
- };
2637
- }
2638
-
2639
- /**
2640
- * Returns a {x,y} point at a given length, the total length and
2641
- * the minimum and maximum {x,y} coordinates of a C (cubic-bezier) segment.
2642
- *
2643
- * @param {number} x1 the starting point X
2644
- * @param {number} y1 the starting point Y
2645
- * @param {number} c1x the first control point X
2646
- * @param {number} c1y the first control point Y
2647
- * @param {number} c2x the second control point X
2648
- * @param {number} c2y the second control point Y
2649
- * @param {number} x2 the ending point X
2650
- * @param {number} y2 the ending point Y
2651
- * @param {number} t a [0-1] ratio
2652
- * @returns {{x: number, y: number}} the cubic-bezier segment length
2653
- */
2654
- function getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t) {
2655
- const t1 = 1 - t;
2656
- return {
2657
- x: (t1 ** 3) * x1
2658
- + 3 * (t1 ** 2) * t * c1x
2659
- + 3 * t1 * (t ** 2) * c2x
2660
- + (t ** 3) * x2,
2661
- y: (t1 ** 3) * y1
2662
- + 3 * (t1 ** 2) * t * c1y
2663
- + 3 * t1 * (t ** 2) * c2y
2664
- + (t ** 3) * y2,
2665
- };
2666
- }
2667
-
2668
- /**
2669
- * Returns the length of a C (cubic-bezier) segment
2670
- * or an {x,y} point at a given length.
2671
- *
2672
- * @param {number} x1 the starting point X
2673
- * @param {number} y1 the starting point Y
2674
- * @param {number} c1x the first control point X
2675
- * @param {number} c1y the first control point Y
2676
- * @param {number} c2x the second control point X
2677
- * @param {number} c2y the second control point Y
2678
- * @param {number} x2 the ending point X
2679
- * @param {number} y2 the ending point Y
2680
- * @param {number=} distance the point distance
2681
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
2682
- */
2683
- function segmentCubicFactory(x1, y1, c1x, c1y, c2x, c2y, x2, y2, distance) {
2684
- const distanceIsNumber = typeof distance === 'number';
2685
- let x = x1; let y = y1;
2686
- let LENGTH = 0;
2687
- let prev = [x, y, LENGTH];
2688
- let cur = [x, y];
2689
- let t = 0;
2690
- let POINT = { x: 0, y: 0 };
2691
- let POINTS = [{ x, y }];
2692
-
2693
- if (distanceIsNumber && distance === 0) {
2694
- POINT = { x, y };
2695
- }
2696
-
2697
- const sampleSize = 300;
2698
- for (let j = 0; j <= sampleSize; j += 1) {
2699
- t = j / sampleSize;
2700
-
2701
- ({ x, y } = getPointAtCubicSegmentLength(x1, y1, c1x, c1y, c2x, c2y, x2, y2, t));
2702
- POINTS = [...POINTS, { x, y }];
2703
- LENGTH += distanceSquareRoot(cur, [x, y]);
2704
- cur = [x, y];
2705
-
2706
- if (distanceIsNumber && LENGTH > distance && distance > prev[2]) {
2707
- const dv = (LENGTH - distance) / (LENGTH - prev[2]);
2708
-
2709
- POINT = {
2710
- x: cur[0] * (1 - dv) + prev[0] * dv,
2711
- y: cur[1] * (1 - dv) + prev[1] * dv,
2712
- };
2713
- }
2714
- prev = [x, y, LENGTH];
2715
- }
2716
-
2717
- if (distanceIsNumber && distance >= LENGTH) {
2718
- POINT = { x: x2, y: y2 };
2719
- }
2720
-
2721
- return {
2722
- length: LENGTH,
2723
- point: POINT,
2724
- min: {
2725
- x: Math.min(...POINTS.map((n) => n.x)),
2726
- y: Math.min(...POINTS.map((n) => n.y)),
2727
- },
2728
- max: {
2729
- x: Math.max(...POINTS.map((n) => n.x)),
2730
- y: Math.max(...POINTS.map((n) => n.y)),
2731
- },
2732
- };
2733
- }
2734
-
2735
- /**
2736
- * Returns the {x,y} coordinates of a point at a
2737
- * given length of a quadratic-bezier segment.
2738
- *
2739
- * @see https://github.com/substack/point-at-length
2740
- *
2741
- * @param {number} x1 the starting point X
2742
- * @param {number} y1 the starting point Y
2743
- * @param {number} cx the control point X
2744
- * @param {number} cy the control point Y
2745
- * @param {number} x2 the ending point X
2746
- * @param {number} y2 the ending point Y
2747
- * @param {number} t a [0-1] ratio
2748
- * @returns {{x: number, y: number}} the requested {x,y} coordinates
2749
- */
2750
- function getPointAtQuadSegmentLength(x1, y1, cx, cy, x2, y2, t) {
2751
- const t1 = 1 - t;
2752
- return {
2753
- x: (t1 ** 2) * x1
2754
- + 2 * t1 * t * cx
2755
- + (t ** 2) * x2,
2756
- y: (t1 ** 2) * y1
2757
- + 2 * t1 * t * cy
2758
- + (t ** 2) * y2,
2759
- };
2760
- }
2761
-
2762
- /**
2763
- * Returns a {x,y} point at a given length, the total length and
2764
- * the minimum and maximum {x,y} coordinates of a Q (quadratic-bezier) segment.
2765
- *
2766
- * @param {number} x1 the starting point X
2767
- * @param {number} y1 the starting point Y
2768
- * @param {number} qx the control point X
2769
- * @param {number} qy the control point Y
2770
- * @param {number} x2 the ending point X
2771
- * @param {number} y2 the ending point Y
2772
- * @param {number=} distance the distance to point
2773
- * @returns {SVGPath.lengthFactory} the segment length, point, min & max
2774
- */
2775
- function segmentQuadFactory(x1, y1, qx, qy, x2, y2, distance) {
2776
- const distanceIsNumber = typeof distance === 'number';
2777
- let x = x1; let y = y1;
2778
- let LENGTH = 0;
2779
- let prev = [x, y, LENGTH];
2780
- let cur = [x, y];
2781
- let t = 0;
2782
- let POINT = { x: 0, y: 0 };
2783
- let POINTS = [{ x, y }];
2784
-
2785
- if (distanceIsNumber && distance === 0) {
2786
- POINT = { x, y };
2787
- }
2788
-
2789
- const sampleSize = 300;
2790
- for (let j = 0; j <= sampleSize; j += 1) {
2791
- t = j / sampleSize;
2792
-
2793
- ({ x, y } = getPointAtQuadSegmentLength(x1, y1, qx, qy, x2, y2, t));
2794
- POINTS = [...POINTS, { x, y }];
2795
- LENGTH += distanceSquareRoot(cur, [x, y]);
2796
- cur = [x, y];
2797
-
2798
- if (distanceIsNumber && LENGTH > distance && distance > prev[2]) {
2799
- const dv = (LENGTH - distance) / (LENGTH - prev[2]);
2800
-
2801
- POINT = {
2802
- x: cur[0] * (1 - dv) + prev[0] * dv,
2803
- y: cur[1] * (1 - dv) + prev[1] * dv,
2804
- };
2805
- }
2806
- prev = [x, y, LENGTH];
2807
- }
2808
-
2809
- /* istanbul ignore else */
2810
- if (distanceIsNumber && distance >= LENGTH) {
2811
- POINT = { x: x2, y: y2 };
2812
- }
2813
-
2814
- return {
2815
- length: LENGTH,
2816
- point: POINT,
2817
- min: {
2818
- x: Math.min(...POINTS.map((n) => n.x)),
2819
- y: Math.min(...POINTS.map((n) => n.y)),
2820
- },
2821
- max: {
2822
- x: Math.max(...POINTS.map((n) => n.x)),
2823
- y: Math.max(...POINTS.map((n) => n.y)),
2824
- },
2825
- };
2826
- }
2827
-
2828
- /**
2829
- * Returns a {x,y} point at a given length
2830
- * of a shape, the shape total length and
2831
- * the shape minimum and maximum {x,y} coordinates.
2832
- *
2833
- * @param {string | SVGPath.pathArray} pathInput the `pathArray` to look into
2834
- * @param {number=} distance the length of the shape to look at
2835
- * @returns {SVGPath.lengthFactory} the path length, point, min & max
2836
- */
2837
- function pathLengthFactory(pathInput, distance) {
2838
- const path = normalizePath(pathInput);
2839
- const distanceIsNumber = typeof distance === 'number';
2840
- let isM;
2841
- let data = [];
2842
- let pathCommand;
2843
- let x = 0;
2844
- let y = 0;
2845
- let mx = 0;
2846
- let my = 0;
2847
- let seg;
2848
- let MIN = [];
2849
- let MAX = [];
2850
- let length = 0;
2851
- let min = { x: 0, y: 0 };
2852
- let max = min;
2853
- let point = min;
2854
- let POINT = min;
2855
- let LENGTH = 0;
2856
-
2857
- for (let i = 0, ll = path.length; i < ll; i += 1) {
2858
- seg = path[i];
2859
- [pathCommand] = seg;
2860
- isM = pathCommand === 'M';
2861
- data = !isM ? [x, y, ...seg.slice(1)] : data;
2862
-
2863
- // this segment is always ZERO
2864
- /* istanbul ignore else */
2865
- if (isM) {
2866
- // remember mx, my for Z
2867
- [, mx, my] = seg;
2868
- min = { x: mx, y: my };
2869
- max = min;
2870
- length = 0;
2871
-
2872
- if (distanceIsNumber && distance < 0.001) {
2873
- POINT = min;
2874
- }
2875
- } else if (pathCommand === 'L') {
2876
- ({
2877
- length, min, max, point,
2878
- } = segmentLineFactory(...data, (distance || 0) - LENGTH));
2879
- } else if (pathCommand === 'A') {
2880
- ({
2881
- length, min, max, point,
2882
- } = segmentArcFactory(...data, (distance || 0) - LENGTH));
2883
- } else if (pathCommand === 'C') {
2884
- ({
2885
- length, min, max, point,
2886
- } = segmentCubicFactory(...data, (distance || 0) - LENGTH));
2887
- } else if (pathCommand === 'Q') {
2888
- ({
2889
- length, min, max, point,
2890
- } = segmentQuadFactory(...data, (distance || 0) - LENGTH));
2891
- } else if (pathCommand === 'Z') {
2892
- data = [x, y, mx, my];
2893
- ({
2894
- length, min, max, point,
2895
- } = segmentLineFactory(...data, (distance || 0) - LENGTH));
2896
- }
2897
-
2898
- if (distanceIsNumber && LENGTH < distance && LENGTH + length >= distance) {
2899
- POINT = point;
2900
- }
2901
-
2902
- MAX = [...MAX, max];
2903
- MIN = [...MIN, min];
2904
- LENGTH += length;
2905
-
2906
- [x, y] = pathCommand !== 'Z' ? seg.slice(-2) : [mx, my];
2907
- }
2908
-
2909
- // native `getPointAtLength` behavior when the given distance
2910
- // is higher than total length
2911
- if (distanceIsNumber && distance >= LENGTH) {
2912
- POINT = { x, y };
2913
- }
2914
-
2915
- return {
2916
- length: LENGTH,
2917
- point: POINT,
2918
- min: {
2919
- x: Math.min(...MIN.map((n) => n.x)),
2920
- y: Math.min(...MIN.map((n) => n.y)),
2921
- },
2922
- max: {
2923
- x: Math.max(...MAX.map((n) => n.x)),
2924
- y: Math.max(...MAX.map((n) => n.y)),
2925
- },
2926
- };
2927
- }
2928
-
2929
- /**
2930
- * Returns the bounding box of a shape.
2931
- *
2932
- * @param {SVGPath.pathArray=} path the shape `pathArray`
2933
- * @returns {SVGPath.pathBBox} the length of the cubic-bezier segment
2934
- */
2935
- function getPathBBox(path) {
2936
- if (!path) {
2937
- return {
2938
- x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0, cx: 0, cy: 0, cz: 0,
2939
- };
2940
- }
2941
-
2942
- const {
2943
- min: { x: xMin, y: yMin },
2944
- max: { x: xMax, y: yMax },
2945
- } = pathLengthFactory(path);
2946
-
2947
- const width = xMax - xMin;
2948
- const height = yMax - yMin;
2949
-
2950
- return {
2951
- width,
2952
- height,
2953
- x: xMin,
2954
- y: yMin,
2955
- x2: xMax,
2956
- y2: yMax,
2957
- cx: xMin + width / 2,
2958
- cy: yMin + height / 2,
2959
- // an estimted guess
2960
- cz: Math.max(width, height) + Math.min(width, height) / 2,
2961
- };
2962
- }
2963
-
2964
- /**
2965
- * Returns the shape total length, or the equivalent to `shape.getTotalLength()`.
2966
- *
2967
- * The `normalizePath` version is lighter, faster, more efficient and more accurate
2968
- * with paths that are not `curveArray`.
2969
- *
2970
- * @param {string | SVGPath.pathArray} pathInput the target `pathArray`
2971
- * @returns {number} the shape total length
2972
- */
2973
- function getTotalLength(pathInput) {
2974
- return pathLengthFactory(pathInput).length;
2975
- }
2976
-
2977
- /**
2978
- * Returns [x,y] coordinates of a point at a given length of a shape.
2979
- *
2980
- * @param {string | SVGPath.pathArray} pathInput the `pathArray` to look into
2981
- * @param {number} distance the length of the shape to look at
2982
- * @returns {{x: number, y: number}} the requested {x, y} point coordinates
2983
- */
2984
- function getPointAtLength(pathInput, distance) {
2985
- return pathLengthFactory(pathInput, distance).point;
2986
- }
2987
-
2988
- /**
2989
- * Creates a new SVGPathCommander instance with the following properties:
2990
- * * segments: `pathArray`
2991
- * * round: number
2992
- * * origin: [number, number, number?]
2993
- *
2994
- * @class
2995
- * @author thednp <https://github.com/thednp/svg-path-commander>
2996
- * @returns {SVGPathCommander} a new SVGPathCommander instance
2997
- */
2998
- class SVGPathCommander {
2999
- /**
3000
- * @constructor
3001
- * @param {string} pathValue the path string
3002
- * @param {any} config instance options
3003
- */
3004
- constructor(pathValue, config) {
3005
- const instanceOptions = config || {};
3006
-
3007
- const undefPath = typeof pathValue === 'undefined';
3008
-
3009
- if (undefPath || !pathValue.length) {
3010
- throw TypeError(`${error}: "pathValue" is ${undefPath ? 'undefined' : 'empty'}`);
3011
- }
3012
-
3013
- const segments = parsePathString(pathValue);
3014
- if (typeof segments === 'string') {
3015
- throw TypeError(segments);
3016
- }
3017
-
3018
- /**
3019
- * @type {SVGPath.pathArray}
3020
- */
3021
- this.segments = segments;
3022
-
3023
- const {
3024
- width, height, cx, cy, cz,
3025
- } = this.getBBox();
3026
-
3027
- // set instance options.round
3028
- const { round: roundOption, origin: originOption } = instanceOptions;
3029
- let round;
3030
-
3031
- if (roundOption === 'auto') {
3032
- const pathScale = (`${Math.floor(Math.max(width, height))}`).length;
3033
- round = pathScale >= 4 ? 0 : 4 - pathScale;
3034
- } else if (Number.isInteger(roundOption) || roundOption === 'off') {
3035
- round = roundOption;
3036
- } else {
3037
- ({ round } = defaultOptions);
3038
- }
3039
-
3040
- // set instance options.origin
3041
- // the SVGPathCommander class will always override the default origin
3042
- let origin;
3043
- if (Array.isArray(originOption) && originOption.length >= 2) {
3044
- const [originX, originY, originZ] = originOption.map(Number);
3045
- origin = [
3046
- !Number.isNaN(originX) ? originX : cx,
3047
- !Number.isNaN(originY) ? originY : cy,
3048
- !Number.isNaN(originZ) ? originZ : cz,
3049
- ];
3050
- } else {
3051
- origin = [cx, cy, cz];
3052
- }
3053
-
3054
- /** @type {number | 'off'} */
3055
- this.round = round;
3056
- /** @type {[number, number, number=]} */
3057
- this.origin = origin;
3058
-
3059
- return this;
3060
- }
3061
-
3062
- /**
3063
- * Returns the path bounding box, equivalent to native `path.getBBox()`.
3064
- * @public
3065
- * @returns {SVGPath.pathBBox}
3066
- */
3067
- getBBox() {
3068
- return getPathBBox(this.segments);
3069
- }
3070
-
3071
- /**
3072
- * Returns the total path length, equivalent to native `path.getTotalLength()`.
3073
- * @public
3074
- * @returns {number}
3075
- */
3076
- getTotalLength() {
3077
- return getTotalLength(this.segments);
3078
- }
3079
-
3080
- /**
3081
- * Returns an `{x,y}` point in the path stroke at a given length,
3082
- * equivalent to the native `path.getPointAtLength()`.
3083
- *
3084
- * @public
3085
- * @param {number} length the length
3086
- * @returns {{x: number, y:number}} the requested point
3087
- */
3088
- getPointAtLength(length) {
3089
- return getPointAtLength(this.segments, length);
3090
- }
3091
-
3092
- /**
3093
- * Convert path to absolute values
3094
- * @public
3095
- */
3096
- toAbsolute() {
3097
- const { segments } = this;
3098
- this.segments = pathToAbsolute(segments);
3099
- return this;
3100
- }
3101
-
3102
- /**
3103
- * Convert path to relative values
3104
- * @public
3105
- */
3106
- toRelative() {
3107
- const { segments } = this;
3108
- this.segments = pathToRelative(segments);
3109
- return this;
3110
- }
3111
-
3112
- /**
3113
- * Convert path to cubic-bezier values. In addition, un-necessary `Z`
3114
- * segment is removed if previous segment extends to the `M` segment.
3115
- *
3116
- * @public
3117
- */
3118
- toCurve() {
3119
- const { segments } = this;
3120
- this.segments = pathToCurve(segments);
3121
- return this;
3122
- }
3123
-
3124
- /**
3125
- * Reverse the order of the segments and their values.
3126
- * @param {boolean} onlySubpath option to reverse all sub-paths except first
3127
- * @public
3128
- */
3129
- reverse(onlySubpath) {
3130
- this.toAbsolute();
3131
-
3132
- const { segments } = this;
3133
- const split = splitPath(segments);
3134
- const subPath = split.length > 1 ? split : 0;
3135
-
3136
- // @ts-ignore
3137
- const absoluteMultiPath = subPath && clonePath(subPath).map((x, i) => {
3138
- if (onlySubpath) {
3139
- return i ? reversePath(x) : parsePathString(x);
3140
- }
3141
- return reversePath(x);
3142
- });
3143
-
3144
- let path = [];
3145
- if (subPath) {
3146
- path = absoluteMultiPath.flat(1);
3147
- } else {
3148
- path = onlySubpath ? segments : reversePath(segments);
3149
- }
3150
-
3151
- this.segments = clonePath(path);
3152
- return this;
3153
- }
3154
-
3155
- /**
3156
- * Normalize path in 2 steps:
3157
- * * convert `pathArray`(s) to absolute values
3158
- * * convert shorthand notation to standard notation
3159
- * @public
3160
- */
3161
- normalize() {
3162
- const { segments } = this;
3163
- this.segments = normalizePath(segments);
3164
- return this;
3165
- }
3166
-
3167
- /**
3168
- * Optimize `pathArray` values:
3169
- * * convert segments to absolute and/or relative values
3170
- * * select segments with shortest resulted string
3171
- * * round values to the specified `decimals` option value
3172
- * @public
3173
- */
3174
- optimize() {
3175
- const { segments } = this;
3176
-
3177
- this.segments = optimizePath(segments, this.round);
3178
- return this;
3179
- }
3180
-
3181
- /**
3182
- * Transform path using values from an `Object` defined as `transformObject`.
3183
- * @see SVGPath.transformObject for a quick refference
3184
- *
3185
- * @param {SVGPath.transformObject} source a `transformObject`as described above
3186
- * @public
3187
- */
3188
- transform(source) {
3189
- if (!source || typeof source !== 'object' || (typeof source === 'object'
3190
- && !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
3191
-
3192
- /** @type {SVGPath.transformObject} */
3193
- const transform = {};
3194
- Object.keys(source).forEach((fn) => {
3195
- transform[fn] = Array.isArray(source[fn]) ? [...source[fn]] : Number(source[fn]);
3196
- });
3197
- const { segments } = this;
3198
-
3199
- // if origin is not specified
3200
- // it's important that we have one
3201
- const [cx, cy, cz] = this.origin;
3202
- const { origin } = transform;
3203
-
3204
- if (Array.isArray(origin) && origin.length >= 2) {
3205
- const [originX, originY, originZ] = origin.map(Number);
3206
- transform.origin = [
3207
- !Number.isNaN(originX) ? originX : cx,
3208
- !Number.isNaN(originY) ? originY : cy,
3209
- originZ || cz,
3210
- ];
3211
- } else {
3212
- transform.origin = [cx, cy, cz];
3213
- }
3214
-
3215
- this.segments = transformPath(segments, transform);
3216
- return this;
3217
- }
3218
-
3219
- /**
3220
- * Rotate path 180deg vertically
3221
- * @public
3222
- */
3223
- flipX() {
3224
- this.transform({ rotate: [0, 180, 0] });
3225
- return this;
3226
- }
3227
-
3228
- /**
3229
- * Rotate path 180deg horizontally
3230
- * @public
3231
- */
3232
- flipY() {
3233
- this.transform({ rotate: [180, 0, 0] });
3234
- return this;
3235
- }
3236
-
3237
- /**
3238
- * Export the current path to be used
3239
- * for the `d` (description) attribute.
3240
- * @public
3241
- * @return {String} the path string
3242
- */
3243
- toString() {
3244
- return pathToString(this.segments, this.round);
3245
- }
3246
- }
3247
-
3248
- /**
3249
- * Returns the area of a single cubic-bezier segment.
3250
- *
3251
- * http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
3252
- *
3253
- * @param {number} x1 the starting point X
3254
- * @param {number} y1 the starting point Y
3255
- * @param {number} c1x the first control point X
3256
- * @param {number} c1y the first control point Y
3257
- * @param {number} c2x the second control point X
3258
- * @param {number} c2y the second control point Y
3259
- * @param {number} x2 the ending point X
3260
- * @param {number} y2 the ending point Y
3261
- * @returns {number} the area of the cubic-bezier segment
3262
- */
3263
- function getCubicSegArea(x1, y1, c1x, c1y, c2x, c2y, x2, y2) {
3264
- return (3 * ((y2 - y1) * (c1x + c2x) - (x2 - x1) * (c1y + c2y)
3265
- + (c1y * (x1 - c2x)) - (c1x * (y1 - c2y))
3266
- + (y2 * (c2x + x1 / 3)) - (x2 * (c2y + y1 / 3)))) / 20;
3267
- }
3268
-
3269
- /**
3270
- * Returns the area of a shape.
3271
- * @author Jürg Lehni & Jonathan Puckey
3272
- *
3273
- * @see https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
3274
- *
3275
- * @param {SVGPath.pathArray} path the shape `pathArray`
3276
- * @returns {number} the length of the cubic-bezier segment
3277
- */
3278
- function getPathArea(path) {
3279
- let x = 0; let y = 0; let len = 0;
3280
-
3281
- return pathToCurve(path).map((seg) => {
3282
- switch (seg[0]) {
3283
- case 'M':
3284
- [, x, y] = seg;
3285
- return 0;
3286
- default:
3287
- len = getCubicSegArea(x, y, ...seg.slice(1));
3288
- [x, y] = seg.slice(-2);
3289
- return len;
3290
- }
3291
- }).reduce((a, b) => a + b, 0);
3292
- }
3293
-
3294
- /**
3295
- * Check if a path is drawn clockwise and returns true if so,
3296
- * false otherwise.
3297
- *
3298
- * @param {SVGPath.pathArray} path the path string or `pathArray`
3299
- * @returns {boolean} true when clockwise or false if not
3300
- */
3301
- function getDrawDirection(path) {
3302
- return getPathArea(pathToCurve(path)) >= 0;
3303
- }
3304
-
3305
- /**
3306
- * Returns the segment, its index and length as well as
3307
- * the length to that segment at a given length in a path.
3308
- *
3309
- * @param {string | SVGPath.pathArray} pathInput target `pathArray`
3310
- * @param {number=} distance the given length
3311
- * @returns {SVGPath.segmentProperties=} the requested properties
3312
- */
3313
- function getPropertiesAtLength(pathInput, distance) {
3314
- const pathArray = parsePathString(pathInput);
3315
-
3316
- if (typeof pathArray === 'string') {
3317
- throw TypeError(pathArray);
3318
- }
3319
-
3320
- let pathTemp = [...pathArray];
3321
- let pathLength = getTotalLength(pathTemp);
3322
- let index = pathTemp.length - 1;
3323
- let lengthAtSegment = 0;
3324
- let length = 0;
3325
- let segment = pathArray[0];
3326
- const [x, y] = segment.slice(-2);
3327
- const point = { x, y };
3328
-
3329
- // If the path is empty, return 0.
3330
- if (index <= 0 || !distance || !Number.isFinite(distance)) {
3331
- return {
3332
- segment, index: 0, length, point, lengthAtSegment,
3333
- };
3334
- }
3335
-
3336
- if (distance >= pathLength) {
3337
- pathTemp = pathArray.slice(0, -1);
3338
- lengthAtSegment = getTotalLength(pathTemp);
3339
- length = pathLength - lengthAtSegment;
3340
- return {
3341
- segment: pathArray[index], index, length, lengthAtSegment,
3342
- };
3343
- }
3344
-
3345
- const segments = [];
3346
- while (index > 0) {
3347
- segment = pathTemp[index];
3348
- pathTemp = pathTemp.slice(0, -1);
3349
- lengthAtSegment = getTotalLength(pathTemp);
3350
- length = pathLength - lengthAtSegment;
3351
- pathLength = lengthAtSegment;
3352
- segments.push({
3353
- segment, index, length, lengthAtSegment,
3354
- });
3355
- index -= 1;
3356
- }
3357
-
3358
- return segments.find(({ lengthAtSegment: l }) => l <= distance);
3359
- }
3360
-
3361
- /**
3362
- * Returns the point and segment in path closest to a given point as well as
3363
- * the distance to the path stroke.
3364
- * @see https://bl.ocks.org/mbostock/8027637
3365
- *
3366
- * @param {string | SVGPath.pathArray} pathInput target `pathArray`
3367
- * @param {{x: number, y: number}} point the given point
3368
- * @returns {SVGPath.pointProperties} the requested properties
3369
- */
3370
- function getPropertiesAtPoint(pathInput, point) {
3371
- const path = (parsePathString(pathInput));
3372
- const normalPath = normalizePath(path);
3373
- const pathLength = getTotalLength(path);
3374
- /** @param {{x: number, y: number}} p */
3375
- const distanceTo = (p) => {
3376
- const dx = p.x - point.x;
3377
- const dy = p.y - point.y;
3378
- return dx * dx + dy * dy;
3379
- };
3380
- let precision = 8;
3381
- let scan;
3382
- let scanDistance = 0;
3383
- let closest;
3384
- let bestLength = 0;
3385
- let bestDistance = Infinity;
3386
-
3387
- // linear scan for coarse approximation
3388
- for (let scanLength = 0; scanLength <= pathLength; scanLength += precision) {
3389
- scan = getPointAtLength(normalPath, scanLength);
3390
- scanDistance = distanceTo(scan);
3391
- if (scanDistance < bestDistance) {
3392
- closest = scan;
3393
- bestLength = scanLength;
3394
- bestDistance = scanDistance;
3395
- }
3396
- }
3397
-
3398
- // binary search for precise estimate
3399
- precision /= 2;
3400
- let before;
3401
- let after;
3402
- let beforeLength = 0;
3403
- let afterLength = 0;
3404
- let beforeDistance = 0;
3405
- let afterDistance = 0;
3406
-
3407
- while (precision > 0.5) {
3408
- beforeLength = bestLength - precision;
3409
- before = getPointAtLength(normalPath, beforeLength);
3410
- beforeDistance = distanceTo(before);
3411
- afterLength = bestLength + precision;
3412
- after = getPointAtLength(normalPath, afterLength);
3413
- afterDistance = distanceTo(after);
3414
- if (beforeLength >= 0 && beforeDistance < bestDistance) {
3415
- closest = before;
3416
- bestLength = beforeLength;
3417
- bestDistance = beforeDistance;
3418
- } else if (afterLength <= pathLength && afterDistance < bestDistance) {
3419
- closest = after;
3420
- bestLength = afterLength;
3421
- bestDistance = afterDistance;
3422
- } else {
3423
- precision /= 2;
3424
- }
3425
- }
3426
-
3427
- const segment = getPropertiesAtLength(path, bestLength);
3428
- const distance = Math.sqrt(bestDistance);
3429
-
3430
- return { closest, distance, segment };
3431
- }
3432
-
3433
- /**
3434
- * Returns the point in path closest to a given point.
3435
- *
3436
- * @param {string | SVGPath.pathArray} pathInput target `pathArray`
3437
- * @param {{x: number, y: number}} point the given point
3438
- * @returns {{x: number, y: number}} the best match
3439
- */
3440
- function getClosestPoint(pathInput, point) {
3441
- return getPropertiesAtPoint(pathInput, point).closest;
3442
- }
3443
-
3444
- /**
3445
- * Returns the path segment which contains a given point.
3446
- *
3447
- * @param {string | SVGPath.pathArray} path the `pathArray` to look into
3448
- * @param {{x: number, y: number}} point the point of the shape to look for
3449
- * @returns {SVGPath.pathSegment?} the requested segment
3450
- */
3451
- function getSegmentOfPoint(path, point) {
3452
- return getPropertiesAtPoint(path, point).segment;
3453
- }
3454
-
3455
- /**
3456
- * Returns the segment at a given length.
3457
- * @param {string | SVGPath.pathArray} pathInput the target `pathArray`
3458
- * @param {number} distance the distance in path to look at
3459
- * @returns {SVGPath.pathSegment?} the requested segment
3460
- */
3461
- function getSegmentAtLength(pathInput, distance) {
3462
- return getPropertiesAtLength(pathInput, distance).segment;
3463
- }
3464
-
3465
- /**
3466
- * Checks if a given point is in the stroke of a path.
3467
- *
3468
- * @param {string | SVGPath.pathArray} pathInput target path
3469
- * @param {{x: number, y: number}} point the given `{x,y}` point
3470
- * @returns {boolean} the query result
3471
- */
3472
- function isPointInStroke(pathInput, point) {
3473
- const { distance } = getPropertiesAtPoint(pathInput, point);
3474
- return Math.abs(distance) < 0.001; // 0.01 might be more permissive
3475
- }
3476
-
3477
- /**
3478
- * Parses a path string value to determine its validity
3479
- * then returns true if it's valid or false otherwise.
3480
- *
3481
- * @param {string} pathString the path string to be parsed
3482
- * @returns {boolean} the path string validity
3483
- */
3484
- function isValidPath(pathString) {
3485
- if (typeof pathString !== 'string') {
3486
- return false;
3487
- }
3488
-
3489
- const path = new PathParser(pathString);
3490
-
3491
- skipSpaces(path);
3492
-
3493
- while (path.index < path.max && !path.err.length) {
3494
- scanSegment(path);
3495
- }
3496
-
3497
- return !path.err.length && 'mM'.includes(path.segments[0][0]);
3498
- }
3499
-
3500
- /**
3501
- * Supported shapes and their specific parameters.
3502
- * @type {Object.<string, string[]>}
3503
- */
3504
- const shapeParams = {
3505
- line: ['x1', 'y1', 'x2', 'y2'],
3506
- circle: ['cx', 'cy', 'r'],
3507
- ellipse: ['cx', 'cy', 'rx', 'ry'],
3508
- rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
3509
- polygon: ['points'],
3510
- polyline: ['points'],
3511
- glyph: ['d'],
3512
- };
3513
-
3514
- /**
3515
- * Returns a new `pathArray` from line attributes.
3516
- *
3517
- * @param {SVGPath.lineAttr} attr shape configuration
3518
- * @returns {SVGPath.pathArray} a new line `pathArray`
3519
- */
3520
- function getLinePath(attr) {
3521
- const {
3522
- x1, y1, x2, y2,
3523
- } = attr;
3524
- return [['M', x1, y1], ['L', x2, y2]];
3525
- }
3526
-
3527
- /**
3528
- * Returns a new `pathArray` like from polyline/polygon attributes.
3529
- *
3530
- * @param {SVGPath.polyAttr} attr shape configuration
3531
- * @return {SVGPath.pathArray} a new polygon/polyline `pathArray`
3532
- */
3533
- function getPolyPath(attr) {
3534
- /** @type {SVGPath.pathArray} */
3535
- const pathArray = [];
3536
- const points = (attr.points || '').trim().split(/[\s|,]/).map(Number);
3537
-
3538
- let index = 0;
3539
- while (index < points.length) {
3540
- pathArray.push([(index ? 'L' : 'M'), (points[index]), (points[index + 1])]);
3541
- index += 2;
3542
- }
3543
-
3544
- return attr.type === 'polygon' ? [...pathArray, ['z']] : pathArray;
3545
- }
3546
-
3547
- /**
3548
- * Returns a new `pathArray` from circle attributes.
3549
- *
3550
- * @param {SVGPath.circleAttr} attr shape configuration
3551
- * @return {SVGPath.pathArray} a circle `pathArray`
3552
- */
3553
- function getCirclePath(attr) {
3554
- const {
3555
- cx, cy, r,
3556
- } = attr;
3557
-
3558
- return [
3559
- ['M', (cx - r), cy],
3560
- ['a', r, r, 0, 1, 0, (2 * r), 0],
3561
- ['a', r, r, 0, 1, 0, (-2 * r), 0],
3562
- ];
3563
- }
3564
-
3565
- /**
3566
- * Returns a new `pathArray` from ellipse attributes.
3567
- *
3568
- * @param {SVGPath.ellipseAttr} attr shape configuration
3569
- * @return {SVGPath.pathArray} an ellipse `pathArray`
3570
- */
3571
- function getEllipsePath(attr) {
3572
- const {
3573
- cx, cy, rx, ry,
3574
- } = attr;
3575
-
3576
- return [
3577
- ['M', (cx - rx), cy],
3578
- ['a', rx, ry, 0, 1, 0, (2 * rx), 0],
3579
- ['a', rx, ry, 0, 1, 0, (-2 * rx), 0],
3580
- ];
3581
- }
3582
-
3583
- /**
3584
- * Returns a new `pathArray` like from rect attributes.
3585
- *
3586
- * @param {SVGPath.rectAttr} attr object with properties above
3587
- * @return {SVGPath.pathArray} a new `pathArray` from `<rect>` attributes
3588
- */
3589
- function getRectanglePath(attr) {
3590
- const x = +attr.x || 0;
3591
- const y = +attr.y || 0;
3592
- const w = +attr.width;
3593
- const h = +attr.height;
3594
- let rx = +attr.rx;
3595
- let ry = +attr.ry;
3596
-
3597
- // Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
3598
- if (rx || ry) {
3599
- rx = !rx ? ry : rx;
3600
- ry = !ry ? rx : ry;
3601
-
3602
- /* istanbul ignore else */
3603
- if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
3604
- /* istanbul ignore else */
3605
- if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
3606
-
3607
- return [
3608
- ['M', x + rx, y],
3609
- ['h', w - rx * 2],
3610
- ['s', rx, 0, rx, ry],
3611
- ['v', h - ry * 2],
3612
- ['s', 0, ry, -rx, ry],
3613
- ['h', -w + rx * 2],
3614
- ['s', -rx, 0, -rx, -ry],
3615
- ['v', -h + ry * 2],
3616
- ['s', 0, -ry, rx, -ry],
3617
- ];
3618
- }
3619
-
3620
- return [
3621
- ['M', x, y],
3622
- ['h', w],
3623
- ['v', h],
3624
- ['H', x],
3625
- ['Z'],
3626
- ];
3627
- }
3628
-
3629
- /**
3630
- * Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
3631
- * `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
3632
- * is `true`, it will replace the target.
3633
- *
3634
- * It can also work with an options object,
3635
- * @see SVGPath.shapeOps
3636
- *
3637
- * The newly created `<path>` element keeps all non-specific
3638
- * attributes like `class`, `fill`, etc.
3639
- *
3640
- * @param {SVGPath.shapeTypes | SVGPath.shapeOps} element target shape
3641
- * @param {boolean=} replace option to replace target
3642
- * @return {SVGPathElement | boolean} the newly created `<path>` element
3643
- */
3644
- function shapeToPath(element, replace) {
3645
- const supportedShapes = Object.keys(shapeParams);
3646
- const { tagName } = element;
3647
-
3648
- if (tagName && !supportedShapes.some((s) => tagName === s)) {
3649
- throw TypeError(`${error}: "${tagName}" is not SVGElement`);
3650
- }
3651
-
3652
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3653
- /** @type {string} */
3654
- const type = tagName || element.type;
3655
- /** @type {any} disables TS checking for something that's specific to shape */
3656
- const config = {};
3657
- config.type = type;
3658
- const shapeAttrs = shapeParams[type];
3659
-
3660
- if (tagName) {
3661
- shapeAttrs.forEach((p) => { config[p] = element.getAttribute(p); });
3662
- // set no-specific shape attributes: fill, stroke, etc
3663
- Object.values(element.attributes).forEach(({ name, value }) => {
3664
- if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
3665
- });
3666
- } else {
3667
- Object.assign(config, element);
3668
- // set no-specific shape attributes: fill, stroke, etc
3669
- Object.keys(config).forEach((k) => {
3670
- if (!shapeAttrs.includes(k) && k !== 'type') {
3671
- path.setAttribute(k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`), config[k]);
3672
- }
3673
- });
3674
- }
3675
-
3676
- // set d
3677
- let description;
3678
- const { round } = defaultOptions;
3679
-
3680
- /* istanbul ignore else */
3681
- if (type === 'circle') description = pathToString(getCirclePath(config), round);
3682
- else if (type === 'ellipse') description = pathToString(getEllipsePath(config), round);
3683
- else if (['polyline', 'polygon'].includes(type)) description = pathToString(getPolyPath(config), round);
3684
- else if (type === 'rect') description = pathToString(getRectanglePath(config), round);
3685
- else if (type === 'line') description = pathToString(getLinePath(config), round);
3686
- else if (type === 'glyph') description = tagName ? element.getAttribute('d') : element.d;
3687
-
3688
- // replace target element
3689
- if (isValidPath(description)) {
3690
- path.setAttribute('d', description);
3691
- if (replace && tagName) {
3692
- element.before(path, element);
3693
- element.remove();
3694
- }
3695
- return path;
3696
- }
3697
- return false;
3698
- }
3699
-
3700
- /**
3701
- * Reverses all segments of a `pathArray`
3702
- * which consists of only C (cubic-bezier) path commands.
3703
- *
3704
- * @param {SVGPath.curveArray} path the source `pathArray`
3705
- * @returns {SVGPath.curveArray} the reversed `pathArray`
3706
- */
3707
- function reverseCurve(path) {
3708
- const rotatedCurve = path.slice(1)
3709
- .map((x, i, curveOnly) => (!i
3710
- ? [...path[0].slice(1), ...x.slice(1)]
3711
- : [...curveOnly[i - 1].slice(-2), ...x.slice(1)]))
3712
- .map((x) => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
3713
- .reverse();
3714
-
3715
- return [['M', ...rotatedCurve[0].slice(0, 2)],
3716
- ...rotatedCurve.map((x) => ['C', ...x.slice(2)])];
3717
- }
3718
-
3719
- /**
3720
- * @interface
3721
- */
3722
- const Util = {
3723
- CSSMatrix,
3724
- parsePathString,
3725
- isPathArray,
3726
- isCurveArray,
3727
- isAbsoluteArray,
3728
- isRelativeArray,
3729
- isNormalizedArray,
3730
- isValidPath,
3731
- pathToAbsolute,
3732
- pathToRelative,
3733
- pathToCurve,
3734
- pathToString,
3735
- getDrawDirection,
3736
- getPathArea,
3737
- getPathBBox,
3738
- pathLengthFactory,
3739
- getTotalLength,
3740
- getPointAtLength,
3741
- getClosestPoint,
3742
- getSegmentOfPoint,
3743
- getPropertiesAtPoint,
3744
- getPropertiesAtLength,
3745
- getSegmentAtLength,
3746
- isPointInStroke,
3747
- clonePath,
3748
- splitPath,
3749
- fixPath,
3750
- roundPath,
3751
- optimizePath,
3752
- reverseCurve,
3753
- reversePath,
3754
- normalizePath,
3755
- transformPath,
3756
- shapeToPath,
3757
- options: defaultOptions,
3758
- };
3759
-
3760
- var version = "1.0.4";
3761
-
3762
- /**
3763
- * A global namespace for library version.
3764
- * @type {string}
3765
- */
3766
- const Version = version;
3767
-
3768
- /** @typedef {import('../types/index')} */
3769
-
3770
- // Export to global
3771
- Object.assign(SVGPathCommander, Util, { Version });
3772
-
3773
- return SVGPathCommander;
3774
-
3775
- }));
1
+ var SVGPathCommander=function(){"use strict";const _={origin:[0,0,0],round:4},T="SVGPathCommander Error",F={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},Nt=e=>{let t=e.pathValue[e.segmentStart],n=t.toLowerCase();const{data:r}=e;for(;r.length>=F[n]&&(n==="m"&&r.length>2?(e.segments.push([t,...r.splice(0,2)]),n="l",t=t==="m"?"l":"L"):e.segments.push([t,...r.splice(0,F[n])]),!!F[n]););},Ut=e=>{const{index:t,pathValue:n}=e,r=n.charCodeAt(t);if(r===48){e.param=0,e.index+=1;return}if(r===49){e.param=1,e.index+=1;return}e.err=`${T}: invalid Arc flag "${n[t]}", expecting 0 or 1 at index ${t}`},z=e=>e>=48&&e<=57,E="Invalid path value",Kt=e=>{const{max:t,pathValue:n,index:r}=e;let s=r,i=!1,o=!1,l=!1,c=!1,a;if(s>=t){e.err=`${T}: ${E} at index ${s}, "pathValue" is missing param`;return}if(a=n.charCodeAt(s),(a===43||a===45)&&(s+=1,a=n.charCodeAt(s)),!z(a)&&a!==46){e.err=`${T}: ${E} at index ${s}, "${n[s]}" is not a number`;return}if(a!==46){if(i=a===48,s+=1,a=n.charCodeAt(s),i&&s<t&&a&&z(a)){e.err=`${T}: ${E} at index ${r}, "${n[r]}" illegal number`;return}for(;s<t&&z(n.charCodeAt(s));)s+=1,o=!0;a=n.charCodeAt(s)}if(a===46){for(c=!0,s+=1;z(n.charCodeAt(s));)s+=1,l=!0;a=n.charCodeAt(s)}if(a===101||a===69){if(c&&!o&&!l){e.err=`${T}: ${E} at index ${s}, "${n[s]}" invalid float exponent`;return}if(s+=1,a=n.charCodeAt(s),(a===43||a===45)&&(s+=1),s<t&&z(n.charCodeAt(s)))for(;s<t&&z(n.charCodeAt(s));)s+=1;else{e.err=`${T}: ${E} at index ${s}, "${n[s]}" invalid integer exponent`;return}}e.index=s,e.param=+e.pathValue.slice(r,s)},_t=e=>[5760,6158,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8239,8287,12288,65279,10,13,8232,8233,32,9,11,12,160].includes(e),Q=e=>{const{pathValue:t,max:n}=e;for(;e.index<n&&_t(t.charCodeAt(e.index));)e.index+=1},Wt=e=>{switch(e|32){case 109:case 122:case 108:case 104:case 118:case 99:case 115:case 113:case 116:case 97:return!0;default:return!1}},te=e=>z(e)||e===43||e===45||e===46,ee=e=>(e|32)===97,Ct=e=>{const{max:t,pathValue:n,index:r}=e,s=n.charCodeAt(r),i=F[n[r].toLowerCase()];if(e.segmentStart=r,!Wt(s)){e.err=`${T}: ${E} "${n[r]}" is not a path command`;return}if(e.index+=1,Q(e),e.data=[],!i){Nt(e);return}for(;;){for(let o=i;o>0;o-=1){if(ee(s)&&(o===3||o===4)?Ut(e):Kt(e),e.err.length)return;e.data.push(e.param),Q(e),e.index<t&&n.charCodeAt(e.index)===44&&(e.index+=1,Q(e))}if(e.index>=e.max||!te(n.charCodeAt(e.index)))break}Nt(e)};class vt{constructor(t){this.segments=[],this.pathValue=t,this.max=t.length,this.index=0,this.param=0,this.segmentStart=0,this.data=[],this.err=""}}const W=e=>Array.isArray(e)&&e.every(t=>{const n=t[0].toLowerCase();return F[n]===t.length-1&&"achlmqstvz".includes(n)}),D=e=>{if(W(e))return[...e];const t=new vt(e);for(Q(t);t.index<t.max&&!t.err.length;)Ct(t);if(t.err&&t.err.length)throw TypeError(t.err);return t.segments},ne=e=>{const t=e.length;let n=-1,r,s=e[t-1],i=0;for(;++n<t;)r=s,s=e[n],i+=r[1]*s[0]-r[0]*s[1];return i/2},H=(e,t)=>Math.sqrt((e[0]-t[0])*(e[0]-t[0])+(e[1]-t[1])*(e[1]-t[1])),se=e=>e.reduce((t,n,r)=>r?t+H(e[r-1],n):0,0);var re=Object.defineProperty,ie=(e,t,n)=>t in e?re(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,C=(e,t,n)=>(ie(e,typeof t!="symbol"?t+"":t,n),n);const oe={a:1,b:0,c:0,d:1,e:0,f:0,m11:1,m12:0,m13:0,m14:0,m21:0,m22:1,m23:0,m24:0,m31:0,m32:0,m33:1,m34:0,m41:0,m42:0,m43:0,m44:1,is2D:!0,isIdentity:!0},wt=e=>(e instanceof Float64Array||e instanceof Float32Array||Array.isArray(e)&&e.every(t=>typeof t=="number"))&&[6,16].some(t=>e.length===t),St=e=>e instanceof DOMMatrix||e instanceof P||typeof e=="object"&&Object.keys(oe).every(t=>e&&t in e),Y=e=>{const t=new P,n=Array.from(e);if(!wt(n))throw TypeError(`CSSMatrix: "${n.join(",")}" must be an array with 6/16 numbers.`);if(n.length===16){const[r,s,i,o,l,c,a,m,f,y,g,h,u,x,d,p]=n;t.m11=r,t.a=r,t.m21=l,t.c=l,t.m31=f,t.m41=u,t.e=u,t.m12=s,t.b=s,t.m22=c,t.d=c,t.m32=y,t.m42=x,t.f=x,t.m13=i,t.m23=a,t.m33=g,t.m43=d,t.m14=o,t.m24=m,t.m34=h,t.m44=p}else if(n.length===6){const[r,s,i,o,l,c]=n;t.m11=r,t.a=r,t.m12=s,t.b=s,t.m21=i,t.c=i,t.m22=o,t.d=o,t.m41=l,t.e=l,t.m42=c,t.f=c}return t},Tt=e=>{if(St(e))return Y([e.m11,e.m12,e.m13,e.m14,e.m21,e.m22,e.m23,e.m24,e.m31,e.m32,e.m33,e.m34,e.m41,e.m42,e.m43,e.m44]);throw TypeError(`CSSMatrix: "${JSON.stringify(e)}" is not a DOMMatrix / CSSMatrix / JSON compatible object.`)},Lt=e=>{if(typeof e!="string")throw TypeError(`CSSMatrix: "${JSON.stringify(e)}" is not a string.`);const t=String(e).replace(/\s/g,"");let n=new P;const r=`CSSMatrix: invalid transform string "${e}"`;return t.split(")").filter(s=>s).forEach(s=>{const[i,o]=s.split("(");if(!o)throw TypeError(r);const l=o.split(",").map(h=>h.includes("rad")?parseFloat(h)*(180/Math.PI):parseFloat(h)),[c,a,m,f]=l,y=[c,a,m],g=[c,a,m,f];if(i==="perspective"&&c&&[a,m].every(h=>h===void 0))n.m34=-1/c;else if(i.includes("matrix")&&[6,16].includes(l.length)&&l.every(h=>!Number.isNaN(+h))){const h=l.map(u=>Math.abs(u)<1e-6?0:u);n=n.multiply(Y(h))}else if(i==="translate3d"&&y.every(h=>!Number.isNaN(+h)))n=n.translate(c,a,m);else if(i==="translate"&&c&&m===void 0)n=n.translate(c,a||0,0);else if(i==="rotate3d"&&g.every(h=>!Number.isNaN(+h))&&f)n=n.rotateAxisAngle(c,a,m,f);else if(i==="rotate"&&c&&[a,m].every(h=>h===void 0))n=n.rotate(0,0,c);else if(i==="scale3d"&&y.every(h=>!Number.isNaN(+h))&&y.some(h=>h!==1))n=n.scale(c,a,m);else if(i==="scale"&&!Number.isNaN(c)&&c!==1&&m===void 0){const h=Number.isNaN(+a)?c:a;n=n.scale(c,h,1)}else if(i==="skew"&&(c||!Number.isNaN(c)&&a)&&m===void 0)n=n.skew(c,a||0);else if(["translate","rotate","scale","skew"].some(h=>i.includes(h))&&/[XYZ]/.test(i)&&c&&[a,m].every(h=>h===void 0))if(i==="skewX"||i==="skewY")n=n[i](c);else{const h=i.replace(/[XYZ]/,""),u=i.replace(h,""),x=["X","Y","Z"].indexOf(u),d=h==="scale"?1:0,p=[x===0?c:d,x===1?c:d,x===2?c:d];n=n[h](...p)}else throw TypeError(r)}),n},ft=(e,t)=>t?[e.a,e.b,e.c,e.d,e.e,e.f]:[e.m11,e.m12,e.m13,e.m14,e.m21,e.m22,e.m23,e.m24,e.m31,e.m32,e.m33,e.m34,e.m41,e.m42,e.m43,e.m44],kt=(e,t,n)=>{const r=new P;return r.m41=e,r.e=e,r.m42=t,r.f=t,r.m43=n,r},qt=(e,t,n)=>{const r=new P,s=Math.PI/180,i=e*s,o=t*s,l=n*s,c=Math.cos(i),a=-Math.sin(i),m=Math.cos(o),f=-Math.sin(o),y=Math.cos(l),g=-Math.sin(l),h=m*y,u=-m*g;r.m11=h,r.a=h,r.m12=u,r.b=u,r.m13=f;const x=a*f*y+c*g;r.m21=x,r.c=x;const d=c*y-a*f*g;return r.m22=d,r.d=d,r.m23=-a*m,r.m31=a*g-c*f*y,r.m32=a*y+c*f*g,r.m33=c*m,r},$t=(e,t,n,r)=>{const s=new P,i=Math.sqrt(e*e+t*t+n*n);if(i===0)return s;const o=e/i,l=t/i,c=n/i,a=r*(Math.PI/360),m=Math.sin(a),f=Math.cos(a),y=m*m,g=o*o,h=l*l,u=c*c,x=1-2*(h+u)*y;s.m11=x,s.a=x;const d=2*(o*l*y+c*m*f);s.m12=d,s.b=d,s.m13=2*(o*c*y-l*m*f);const p=2*(l*o*y-c*m*f);s.m21=p,s.c=p;const A=1-2*(u+g)*y;return s.m22=A,s.d=A,s.m23=2*(l*c*y+o*m*f),s.m31=2*(c*o*y+l*m*f),s.m32=2*(c*l*y-o*m*f),s.m33=1-2*(g+h)*y,s},Vt=(e,t,n)=>{const r=new P;return r.m11=e,r.a=e,r.m22=t,r.d=t,r.m33=n,r},tt=(e,t)=>{const n=new P;if(e){const r=e*Math.PI/180,s=Math.tan(r);n.m21=s,n.c=s}if(t){const r=t*Math.PI/180,s=Math.tan(r);n.m12=s,n.b=s}return n},Ot=e=>tt(e,0),zt=e=>tt(0,e),L=(e,t)=>{const n=t.m11*e.m11+t.m12*e.m21+t.m13*e.m31+t.m14*e.m41,r=t.m11*e.m12+t.m12*e.m22+t.m13*e.m32+t.m14*e.m42,s=t.m11*e.m13+t.m12*e.m23+t.m13*e.m33+t.m14*e.m43,i=t.m11*e.m14+t.m12*e.m24+t.m13*e.m34+t.m14*e.m44,o=t.m21*e.m11+t.m22*e.m21+t.m23*e.m31+t.m24*e.m41,l=t.m21*e.m12+t.m22*e.m22+t.m23*e.m32+t.m24*e.m42,c=t.m21*e.m13+t.m22*e.m23+t.m23*e.m33+t.m24*e.m43,a=t.m21*e.m14+t.m22*e.m24+t.m23*e.m34+t.m24*e.m44,m=t.m31*e.m11+t.m32*e.m21+t.m33*e.m31+t.m34*e.m41,f=t.m31*e.m12+t.m32*e.m22+t.m33*e.m32+t.m34*e.m42,y=t.m31*e.m13+t.m32*e.m23+t.m33*e.m33+t.m34*e.m43,g=t.m31*e.m14+t.m32*e.m24+t.m33*e.m34+t.m34*e.m44,h=t.m41*e.m11+t.m42*e.m21+t.m43*e.m31+t.m44*e.m41,u=t.m41*e.m12+t.m42*e.m22+t.m43*e.m32+t.m44*e.m42,x=t.m41*e.m13+t.m42*e.m23+t.m43*e.m33+t.m44*e.m43,d=t.m41*e.m14+t.m42*e.m24+t.m43*e.m34+t.m44*e.m44;return Y([n,r,s,i,o,l,c,a,m,f,y,g,h,u,x,d])};class P{constructor(t){return this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0,this.m11=1,this.m12=0,this.m13=0,this.m14=0,this.m21=0,this.m22=1,this.m23=0,this.m24=0,this.m31=0,this.m32=0,this.m33=1,this.m34=0,this.m41=0,this.m42=0,this.m43=0,this.m44=1,t?this.setMatrixValue(t):this}get isIdentity(){return this.m11===1&&this.m12===0&&this.m13===0&&this.m14===0&&this.m21===0&&this.m22===1&&this.m23===0&&this.m24===0&&this.m31===0&&this.m32===0&&this.m33===1&&this.m34===0&&this.m41===0&&this.m42===0&&this.m43===0&&this.m44===1}get is2D(){return this.m31===0&&this.m32===0&&this.m33===1&&this.m34===0&&this.m43===0&&this.m44===1}setMatrixValue(t){return typeof t=="string"&&t.length&&t!=="none"?Lt(t):Array.isArray(t)||t instanceof Float64Array||t instanceof Float32Array?Y(t):typeof t=="object"?Tt(t):this}toFloat32Array(t){return Float32Array.from(ft(this,t))}toFloat64Array(t){return Float64Array.from(ft(this,t))}toString(){const{is2D:t}=this,n=this.toFloat64Array(t).join(", ");return`${t?"matrix":"matrix3d"}(${n})`}toJSON(){const{is2D:t,isIdentity:n}=this;return{...this,is2D:t,isIdentity:n}}multiply(t){return L(this,t)}translate(t,n,r){const s=t;let i=n,o=r;return typeof i>"u"&&(i=0),typeof o>"u"&&(o=0),L(this,kt(s,i,o))}scale(t,n,r){const s=t;let i=n,o=r;return typeof i>"u"&&(i=t),typeof o>"u"&&(o=1),L(this,Vt(s,i,o))}rotate(t,n,r){let s=t,i=n||0,o=r||0;return typeof t=="number"&&typeof n>"u"&&typeof r>"u"&&(o=s,s=0,i=0),L(this,qt(s,i,o))}rotateAxisAngle(t,n,r,s){if([t,n,r,s].some(i=>Number.isNaN(+i)))throw new TypeError("CSSMatrix: expecting 4 values");return L(this,$t(t,n,r,s))}skewX(t){return L(this,Ot(t))}skewY(t){return L(this,zt(t))}skew(t,n){return L(this,tt(t,n))}transformPoint(t){const n=this.m11*t.x+this.m21*t.y+this.m31*t.z+this.m41*t.w,r=this.m12*t.x+this.m22*t.y+this.m32*t.z+this.m42*t.w,s=this.m13*t.x+this.m23*t.y+this.m33*t.z+this.m43*t.w,i=this.m14*t.x+this.m24*t.y+this.m34*t.z+this.m44*t.w;return t instanceof DOMPoint?new DOMPoint(n,r,s,i):{x:n,y:r,z:s,w:i}}}C(P,"Translate",kt),C(P,"Rotate",qt),C(P,"RotateAxisAngle",$t),C(P,"Scale",Vt),C(P,"SkewX",Ot),C(P,"SkewY",zt),C(P,"Skew",tt),C(P,"Multiply",L),C(P,"fromArray",Y),C(P,"fromMatrix",Tt),C(P,"fromString",Lt),C(P,"toArray",ft),C(P,"isCompatibleArray",wt),C(P,"isCompatibleObject",St);const yt=e=>W(e)&&e.every(([t])=>t===t.toUpperCase()),Z=e=>{if(yt(e))return[...e];const t=D(e);let n=0,r=0,s=0,i=0;return t.map(o=>{const l=o.slice(1).map(Number),[c]=o,a=c.toUpperCase();if(c==="M")return[n,r]=l,s=n,i=r,["M",n,r];let m=[];if(c!==a)if(a==="A")m=[a,l[0],l[1],l[2],l[3],l[4],l[5]+n,l[6]+r];else if(a==="V")m=[a,l[0]+r];else if(a==="H")m=[a,l[0]+n];else{const f=l.map((y,g)=>y+(g%2?r:n));m=[a,...f]}else m=[a,...l];return a==="Z"?(n=s,r=i):a==="H"?[,n]=m:a==="V"?[,r]=m:([n,r]=m.slice(-2),a==="M"&&(s=n,i=r)),m})},ae=(e,t)=>{const[n]=e,{x1:r,y1:s,x2:i,y2:o}=t,l=e.slice(1).map(Number);let c=e;if("TQ".includes(n)||(t.qx=null,t.qy=null),n==="H")c=["L",e[1],s];else if(n==="V")c=["L",r,e[1]];else if(n==="S"){const a=r*2-i,m=s*2-o;t.x1=a,t.y1=m,c=["C",a,m,...l]}else if(n==="T"){const a=r*2-(t.qx?t.qx:0),m=s*2-(t.qy?t.qy:0);t.qx=a,t.qy=m,c=["Q",a,m,...l]}else if(n==="Q"){const[a,m]=l;t.qx=a,t.qy=m}return c},gt=e=>yt(e)&&e.every(([t])=>"ACLMQZ".includes(t)),et={x1:0,y1:0,x2:0,y2:0,x:0,y:0,qx:null,qy:null},$=e=>{if(gt(e))return[...e];const t=Z(e),n={...et},r=t.length;for(let s=0;s<r;s+=1){t[s],t[s]=ae(t[s],n);const i=t[s],o=i.length;n.x1=+i[o-2],n.y1=+i[o-1],n.x2=+i[o-4]||n.x1,n.y2=+i[o-3]||n.y1}return t},V=(e,t,n)=>{const[r,s]=e,[i,o]=t;return[r+(i-r)*n,s+(o-s)*n]},xt=(e,t,n,r,s)=>{const i=H([e,t],[n,r]);let o={x:0,y:0};if(typeof s=="number")if(s<=0)o={x:e,y:t};else if(s>=i)o={x:n,y:r};else{const[l,c]=V([e,t],[n,r],s/i);o={x:l,y:c}}return{length:i,point:o,min:{x:Math.min(e,n),y:Math.min(t,r)},max:{x:Math.max(e,n),y:Math.max(t,r)}}},It=(e,t)=>{const{x:n,y:r}=e,{x:s,y:i}=t,o=n*s+r*i,l=Math.sqrt((n**2+r**2)*(s**2+i**2));return(n*i-r*s<0?-1:1)*Math.acos(o/l)},ce=(e,t,n,r,s,i,o,l,c,a)=>{const{abs:m,sin:f,cos:y,sqrt:g,PI:h}=Math;let u=m(n),x=m(r);const p=(s%360+360)%360*(h/180);if(e===l&&t===c)return{x:e,y:t};if(u===0||x===0)return xt(e,t,l,c,a).point;const A=(e-l)/2,b=(t-c)/2,M={x:y(p)*A+f(p)*b,y:-f(p)*A+y(p)*b},v=M.x**2/u**2+M.y**2/x**2;v>1&&(u*=g(v),x*=g(v));const O=u**2*x**2-u**2*M.y**2-x**2*M.x**2,B=u**2*M.y**2+x**2*M.x**2;let X=O/B;X=X<0?0:X;const lt=(i!==o?1:-1)*g(X),S={x:lt*(u*M.y/x),y:lt*(-(x*M.x)/u)},mt={x:y(p)*S.x-f(p)*S.y+(e+l)/2,y:f(p)*S.x+y(p)*S.y+(t+c)/2},J={x:(M.x-S.x)/u,y:(M.y-S.y)/x},ht=It({x:1,y:0},J),ut={x:(-M.x-S.x)/u,y:(-M.y-S.y)/x};let k=It(J,ut);!o&&k>0?k-=2*h:o&&k<0&&(k+=2*h),k%=2*h;const q=ht+k*a,U=u*y(q),K=x*f(q);return{x:y(p)*U-f(p)*K+mt.x,y:f(p)*U+y(p)*K+mt.y}},le=(e,t,n,r,s,i,o,l,c,a)=>{const m=typeof a=="number";let f=e,y=t,g=0,h=[f,y,g],u=[f,y],x=0,d={x:0,y:0},p=[{x:f,y}];m&&a<=0&&(d={x:f,y});const A=300;for(let b=0;b<=A;b+=1){if(x=b/A,{x:f,y}=ce(e,t,n,r,s,i,o,l,c,x),p=[...p,{x:f,y}],g+=H(u,[f,y]),u=[f,y],m&&g>a&&a>h[2]){const M=(g-a)/(g-h[2]);d={x:u[0]*(1-M)+h[0]*M,y:u[1]*(1-M)+h[1]*M}}h=[f,y,g]}return m&&a>=g&&(d={x:l,y:c}),{length:g,point:d,min:{x:Math.min(...p.map(b=>b.x)),y:Math.min(...p.map(b=>b.y))},max:{x:Math.max(...p.map(b=>b.x)),y:Math.max(...p.map(b=>b.y))}}},me=(e,t,n,r,s,i,o,l,c)=>{const a=1-c;return{x:a**3*e+3*a**2*c*n+3*a*c**2*s+c**3*o,y:a**3*t+3*a**2*c*r+3*a*c**2*i+c**3*l}},he=(e,t,n,r,s,i,o,l,c)=>{const a=typeof c=="number";let m=e,f=t,y=0,g=[m,f,y],h=[m,f],u=0,x={x:0,y:0},d=[{x:m,y:f}];a&&c<=0&&(x={x:m,y:f});const p=300;for(let A=0;A<=p;A+=1){if(u=A/p,{x:m,y:f}=me(e,t,n,r,s,i,o,l,u),d=[...d,{x:m,y:f}],y+=H(h,[m,f]),h=[m,f],a&&y>c&&c>g[2]){const b=(y-c)/(y-g[2]);x={x:h[0]*(1-b)+g[0]*b,y:h[1]*(1-b)+g[1]*b}}g=[m,f,y]}return a&&c>=y&&(x={x:o,y:l}),{length:y,point:x,min:{x:Math.min(...d.map(A=>A.x)),y:Math.min(...d.map(A=>A.y))},max:{x:Math.max(...d.map(A=>A.x)),y:Math.max(...d.map(A=>A.y))}}},ue=(e,t,n,r,s,i,o)=>{const l=1-o;return{x:l**2*e+2*l*o*n+o**2*s,y:l**2*t+2*l*o*r+o**2*i}},fe=(e,t,n,r,s,i,o)=>{const l=typeof o=="number";let c=e,a=t,m=0,f=[c,a,m],y=[c,a],g=0,h={x:0,y:0},u=[{x:c,y:a}];l&&o<=0&&(h={x:c,y:a});const x=300;for(let d=0;d<=x;d+=1){if(g=d/x,{x:c,y:a}=ue(e,t,n,r,s,i,g),u=[...u,{x:c,y:a}],m+=H(y,[c,a]),y=[c,a],l&&m>o&&o>f[2]){const p=(m-o)/(m-f[2]);h={x:y[0]*(1-p)+f[0]*p,y:y[1]*(1-p)+f[1]*p}}f=[c,a,m]}return l&&o>=m&&(h={x:s,y:i}),{length:m,point:h,min:{x:Math.min(...u.map(d=>d.x)),y:Math.min(...u.map(d=>d.y))},max:{x:Math.max(...u.map(d=>d.x)),y:Math.max(...u.map(d=>d.y))}}},nt=(e,t)=>{const n=$(e),r=typeof t=="number";let s,i=[],o,l=0,c=0,a=0,m=0,f,y=[],g=[],h=0,u={x:0,y:0},x=u,d=u,p=u,A=0;for(let b=0,M=n.length;b<M;b+=1)f=n[b],[o]=f,s=o==="M",i=s?i:[l,c,...f.slice(1)],s?([,a,m]=f,u={x:a,y:m},x=u,h=0,r&&t<.001&&(p=u)):o==="L"?{length:h,min:u,max:x,point:d}=xt(...i,(t||0)-A):o==="A"?{length:h,min:u,max:x,point:d}=le(...i,(t||0)-A):o==="C"?{length:h,min:u,max:x,point:d}=he(...i,(t||0)-A):o==="Q"?{length:h,min:u,max:x,point:d}=fe(...i,(t||0)-A):o==="Z"&&(i=[l,c,a,m],{length:h,min:u,max:x,point:d}=xt(...i,(t||0)-A)),r&&A<t&&A+h>=t&&(p=d),g=[...g,x],y=[...y,u],A+=h,[l,c]=o!=="Z"?f.slice(-2):[a,m];return r&&t>=A&&(p={x:l,y:c}),{length:A,point:p,min:{x:Math.min(...y.map(b=>b.x)),y:Math.min(...y.map(b=>b.y))},max:{x:Math.max(...g.map(b=>b.x)),y:Math.max(...g.map(b=>b.y))}}},jt=e=>{if(!e)return{x:0,y:0,width:0,height:0,x2:0,y2:0,cx:0,cy:0,cz:0};const{min:{x:t,y:n},max:{x:r,y:s}}=nt(e),i=r-t,o=s-n;return{width:i,height:o,x:t,y:n,x2:r,y2:s,cx:t+i/2,cy:n+o/2,cz:Math.max(i,o)+Math.min(i,o)/2}},dt=(e,t,n)=>{if(e[n].length>7){e[n].shift();const r=e[n];let s=n;for(;r.length;)t[n]="A",e.splice(s+=1,0,["C",...r.splice(0,6)]);e.splice(n,1)}},Et=e=>gt(e)&&e.every(([t])=>"MC".includes(t)),st=(e,t,n)=>{const r=e*Math.cos(n)-t*Math.sin(n),s=e*Math.sin(n)+t*Math.cos(n);return{x:r,y:s}},Dt=(e,t,n,r,s,i,o,l,c,a)=>{let m=e,f=t,y=n,g=r,h=l,u=c;const x=Math.PI*120/180,d=Math.PI/180*(+s||0);let p=[],A,b,M,v,O;if(a)[b,M,v,O]=a;else{A=st(m,f,-d),m=A.x,f=A.y,A=st(h,u,-d),h=A.x,u=A.y;const N=(m-h)/2,w=(f-u)/2;let j=N*N/(y*y)+w*w/(g*g);j>1&&(j=Math.sqrt(j),y*=j,g*=j);const At=y*y,Pt=g*g,Jt=(i===o?-1:1)*Math.sqrt(Math.abs((At*Pt-At*w*w-Pt*N*N)/(At*w*w+Pt*N*N)));v=Jt*y*w/g+(m+h)/2,O=Jt*-g*N/y+(f+u)/2,b=Math.asin(((f-O)/g*10**9>>0)/10**9),M=Math.asin(((u-O)/g*10**9>>0)/10**9),b=m<v?Math.PI-b:b,M=h<v?Math.PI-M:M,b<0&&(b=Math.PI*2+b),M<0&&(M=Math.PI*2+M),o&&b>M&&(b-=Math.PI*2),!o&&M>b&&(M-=Math.PI*2)}let B=M-b;if(Math.abs(B)>x){const N=M,w=h,j=u;M=b+x*(o&&M>b?1:-1),h=v+y*Math.cos(M),u=O+g*Math.sin(M),p=Dt(h,u,y,g,s,0,o,w,j,[M,N,v,O])}B=M-b;const X=Math.cos(b),lt=Math.sin(b),S=Math.cos(M),mt=Math.sin(M),J=Math.tan(B/4),ht=4/3*y*J,ut=4/3*g*J,k=[m,f],q=[m+ht*lt,f-ut*X],U=[h+ht*mt,u-ut*S],K=[h,u];if(q[0]=2*k[0]-q[0],q[1]=2*k[1]-q[1],a)return[...q,...U,...K,...p];p=[...q,...U,...K,...p];const Mt=[];for(let N=0,w=p.length;N<w;N+=1)Mt[N]=N%2?st(p[N-1],p[N],d).y:st(p[N],p[N+1],d).x;return Mt},ye=(e,t,n,r,s,i)=>{const o=.3333333333333333,l=2/3;return[o*e+l*n,o*t+l*r,o*s+l*n,o*i+l*r,s,i]},Zt=(e,t,n,r)=>[...V([e,t],[n,r],.5),n,r,n,r],rt=(e,t)=>{const[n]=e,r=e.slice(1).map(Number),[s,i]=r;let o;const{x1:l,y1:c,x:a,y:m}=t;return"TQ".includes(n)||(t.qx=null,t.qy=null),n==="M"?(t.x=s,t.y=i,e):n==="A"?(o=[l,c,...r],["C",...Dt(...o)]):n==="Q"?(t.qx=s,t.qy=i,o=[l,c,...r],["C",...ye(...o)]):n==="L"?["C",...Zt(l,c,s,i)]:n==="Z"?["C",...Zt(l,c,a,m)]:e},it=e=>{if(Et(e))return[...e];const t=$(e),n={...et},r=[];let s="",i=t.length;for(let o=0;o<i;o+=1){[s]=t[o],r[o]=s,t[o]=rt(t[o],n),dt(t,r,o),i=t.length;const l=t[o],c=l.length;n.x1=+l[c-2],n.y1=+l[c-1],n.x2=+l[c-4]||n.x1,n.y2=+l[c-3]||n.y1}return t},ge=(e,t,n,r,s,i,o,l)=>3*((l-t)*(n+s)-(o-e)*(r+i)+r*(e-s)-n*(t-i)+l*(s+e/3)-o*(i+t/3))/20,Rt=e=>{let t=0,n=0,r=0;return it(e).map(s=>{switch(s[0]){case"M":return[,t,n]=s,0;default:return r=ge(t,n,...s.slice(1)),[t,n]=s.slice(-2),r}}).reduce((s,i)=>s+i,0)},R=e=>nt(e).length,xe=e=>Rt(it(e))>=0,G=(e,t)=>nt(e,t).point,pt=(e,t)=>{const n=D(e);let r=[...n],s=R(r),i=r.length-1,o=0,l=0,c=n[0];const[a,m]=c.slice(-2),f={x:a,y:m};if(i<=0||!t||!Number.isFinite(t))return{segment:c,index:0,length:l,point:f,lengthAtSegment:o};if(t>=s)return r=n.slice(0,-1),o=R(r),l=s-o,{segment:n[i],index:i,length:l,lengthAtSegment:o};const y=[];for(;i>0;)c=r[i],r=r.slice(0,-1),o=R(r),l=s-o,s=o,y.push({segment:c,index:i,length:l,lengthAtSegment:o}),i-=1;return y.find(({lengthAtSegment:g})=>g<=t)},ot=(e,t)=>{const n=D(e),r=$(n),s=R(n),i=b=>{const M=b.x-t.x,v=b.y-t.y;return M*M+v*v};let o=8,l,c={x:0,y:0},a=0,m=0,f=1/0;for(let b=0;b<=s;b+=o)l=G(r,b),a=i(l),a<f&&(c=l,m=b,f=a);o/=2;let y,g,h=0,u=0,x=0,d=0;for(;o>.5;)h=m-o,y=G(r,h),x=i(y),u=m+o,g=G(r,u),d=i(g),h>=0&&x<f?(c=y,m=h,f=x):u<=s&&d<f?(c=g,m=u,f=d):o/=2;const p=pt(n,m),A=Math.sqrt(f);return{closest:c,distance:A,segment:p}},de=(e,t)=>ot(e,t).closest,pe=(e,t)=>ot(e,t).segment,be=(e,t)=>pt(e,t).segment,Me=(e,t)=>{const{distance:n}=ot(e,t);return Math.abs(n)<.001},Xt=e=>{if(typeof e!="string")return!1;const t=new vt(e);for(Q(t);t.index<t.max&&!t.err.length;)Ct(t);return!t.err.length&&"mM".includes(t.segments[0][0])},Ft=e=>W(e)&&e.slice(1).every(([t])=>t===t.toLowerCase()),at=(e,t)=>{let{round:n}=_;if(t==="off"||n==="off")return[...e];n=typeof t=="number"&&t>=0?t:n;const r=typeof n=="number"&&n>=1?10**n:1;return e.map(s=>{const i=s.slice(1).map(Number).map(o=>n?Math.round(o*r)/r:Math.round(o));return[s[0],...i]})},I=(e,t)=>at(e,t).map(n=>n[0]+n.slice(1).join(" ")).join(""),Qt={line:["x1","y1","x2","y2"],circle:["cx","cy","r"],ellipse:["cx","cy","rx","ry"],rect:["width","height","x","y","rx","ry"],polygon:["points"],polyline:["points"],glyph:["d"]},Ae=e=>{const{x1:t,y1:n,x2:r,y2:s}=e;return[["M",t,n],["L",r,s]]},Pe=e=>{const t=[],n=(e.points||"").trim().split(/[\s|,]/).map(Number);let r=0;for(;r<n.length;)t.push([r?"L":"M",n[r],n[r+1]]),r+=2;return e.type==="polygon"?[...t,["z"]]:t},Ne=e=>{const{cx:t,cy:n,r}=e;return[["M",t-r,n],["a",r,r,0,1,0,2*r,0],["a",r,r,0,1,0,-2*r,0]]},Ce=e=>{const{cx:t,cy:n,rx:r,ry:s}=e;return[["M",t-r,n],["a",r,s,0,1,0,2*r,0],["a",r,s,0,1,0,-2*r,0]]},ve=e=>{const t=+e.x||0,n=+e.y||0,r=+e.width,s=+e.height;let i=+e.rx,o=+e.ry;return i||o?(i=i||o,o=o||i,i*2>r&&(i-=(i*2-r)/2),o*2>s&&(o-=(o*2-s)/2),[["M",t+i,n],["h",r-i*2],["s",i,0,i,o],["v",s-o*2],["s",0,o,-i,o],["h",-r+i*2],["s",-i,0,-i,-o],["v",-s+o*2],["s",0,-o,i,-o]]):[["M",t,n],["h",r],["v",s],["H",t],["Z"]]},we=(e,t,n)=>{const r=n||document,s=r.defaultView||window,i=Object.keys(Qt),o=e instanceof s.SVGElement,l=o?e.tagName:null;if(l&&i.every(h=>l!==h))throw TypeError(`${T}: "${l}" is not SVGElement`);const c=r.createElementNS("http://www.w3.org/2000/svg","path"),a=o?l:e.type,m=Qt[a],f={type:a};o?(m.forEach(h=>{m.includes(h)&&(f[h]=e.getAttribute(h))}),Object.values(e.attributes).forEach(({name:h,value:u})=>{m.includes(h)||c.setAttribute(h,u)})):(Object.assign(f,e),Object.keys(f).forEach(h=>{!m.includes(h)&&h!=="type"&&c.setAttribute(h.replace(/[A-Z]/g,u=>`-${u.toLowerCase()}`),f[h])}));let y="";const g=_.round;return a==="circle"?y=I(Ne(f),g):a==="ellipse"?y=I(Ce(f),g):["polyline","polygon"].includes(a)?y=I(Pe(f),g):a==="rect"?y=I(ve(f),g):a==="line"?y=I(Ae(f),g):a==="glyph"&&(y=o?e.getAttribute("d"):e.d),Xt(y)?(c.setAttribute("d",y),t&&o&&(e.before(c,e),e.remove()),c):!1},Ht=e=>{const t=[];let n,r=-1;return e.forEach(s=>{s[0]==="M"?(n=[s],r+=1):n=[...n,s],t[r]=n}),t},bt=e=>{if(Ft(e))return[...e];const t=D(e);let n=0,r=0,s=0,i=0;return t.map(o=>{const l=o.slice(1).map(Number),[c]=o,a=c.toLowerCase();if(c==="M")return[n,r]=l,s=n,i=r,["M",n,r];let m=[];if(c!==a)if(a==="a")m=[a,l[0],l[1],l[2],l[3],l[4],l[5]-n,l[6]-r];else if(a==="v")m=[a,l[0]-r];else if(a==="h")m=[a,l[0]-n];else{const y=l.map((g,h)=>g-(h%2?r:n));m=[a,...y]}else c==="m"&&(s=l[0]+n,i=l[1]+r),m=[a,...l];const f=m.length;return a==="z"?(n=s,r=i):a==="h"?n+=m[1]:a==="v"?r+=m[1]:(n+=m[f-2],r+=m[f-1]),m})},Se=(e,t,n,r)=>{const[s]=e,i=d=>Math.round(d*10**4)/10**4,o=e.slice(1).map(d=>+d),l=t.slice(1).map(d=>+d),{x1:c,y1:a,x2:m,y2:f,x:y,y:g}=n;let h=e;const[u,x]=l.slice(-2);if("TQ".includes(s)||(n.qx=null,n.qy=null),["V","H","S","T","Z"].includes(s))h=[s,...o];else if(s==="L")i(y)===i(u)?h=["V",x]:i(g)===i(x)&&(h=["H",u]);else if(s==="C"){const[d,p]=l;"CS".includes(r)&&(i(d)===i(c*2-m)&&i(p)===i(a*2-f)||i(c)===i(m*2-y)&&i(a)===i(f*2-g))&&(h=["S",...l.slice(-4)]),n.x1=d,n.y1=p}else if(s==="Q"){const[d,p]=l;n.qx=d,n.qy=p,"QT".includes(r)&&(i(d)===i(c*2-m)&&i(p)===i(a*2-f)||i(c)===i(m*2-y)&&i(a)===i(f*2-g))&&(h=["T",...l.slice(-2)])}return h},Yt=(e,t)=>{const n=Z(e),r=$(n),s={...et},i=[],o=n.length;let l="",c="",a=0,m=0,f=0,y=0;for(let u=0;u<o;u+=1){[l]=n[u],i[u]=l,u&&(c=i[u-1]),n[u]=Se(n[u],r[u],s,c);const x=n[u],d=x.length;switch(s.x1=+x[d-2],s.y1=+x[d-1],s.x2=+x[d-4]||s.x1,s.y2=+x[d-3]||s.y1,l){case"Z":a=f,m=y;break;case"H":[,a]=x;break;case"V":[,m]=x;break;default:[a,m]=x.slice(-2).map(Number),l==="M"&&(f=a,y=m)}s.x=a,s.y=m}const g=at(n,t),h=at(bt(n),t);return g.map((u,x)=>x?u.join("").length<h[x].join("").length?u:h[x]:u)},Te=e=>{const t=e.slice(1).map((n,r,s)=>r?[...s[r-1].slice(-2),...n.slice(1)]:[...e[0].slice(1),...n.slice(1)]).map(n=>n.map((r,s)=>n[n.length-s-2*(1-s%2)])).reverse();return[["M",...t[0].slice(0,2)],...t.map(n=>["C",...n.slice(2)])]},ct=e=>{const t=Z(e),n=t.slice(-1)[0][0]==="Z",r=$(t).map((s,i)=>{const[o,l]=s.slice(-2).map(Number);return{seg:t[i],n:s,c:t[i][0],x:o,y:l}}).map((s,i,o)=>{const l=s.seg,c=s.n,a=i&&o[i-1],m=o[i+1],f=s.c,y=o.length,g=i?o[i-1].x:o[y-1].x,h=i?o[i-1].y:o[y-1].y;let u=[];switch(f){case"M":u=n?["Z"]:[f,g,h];break;case"A":u=[f,...l.slice(1,-3),l[5]===1?0:1,g,h];break;case"C":m&&m.c==="S"?u=["S",l[1],l[2],g,h]:u=[f,l[3],l[4],l[1],l[2],g,h];break;case"S":a&&"CS".includes(a.c)&&(!m||m.c!=="S")?u=["C",c[3],c[4],c[1],c[2],g,h]:u=[f,c[1],c[2],g,h];break;case"Q":m&&m.c==="T"?u=["T",g,h]:u=[f,...l.slice(1,-2),g,h];break;case"T":a&&"QT".includes(a.c)&&(!m||m.c!=="T")?u=["Q",c[1],c[2],g,h]:u=[f,g,h];break;case"Z":u=["M",g,h];break;case"H":u=[f,g];break;case"V":u=[f,h];break;default:u=[f,...l.slice(1,-2),g,h]}return u});return n?r.reverse():[r[0],...r.slice(1).reverse()]},Le=e=>{let t=new P;const{origin:n}=e,[r,s]=n,{translate:i}=e,{rotate:o}=e,{skew:l}=e,{scale:c}=e;return Array.isArray(i)&&i.length>=2&&i.every(a=>!Number.isNaN(+a))&&i.some(a=>a!==0)?t=t.translate(...i):typeof i=="number"&&!Number.isNaN(i)&&(t=t.translate(i)),(o||l||c)&&(t=t.translate(r,s),Array.isArray(o)&&o.length>=2&&o.every(a=>!Number.isNaN(+a))&&o.some(a=>a!==0)?t=t.rotate(...o):typeof o=="number"&&!Number.isNaN(o)&&(t=t.rotate(o)),Array.isArray(l)&&l.length===2&&l.every(a=>!Number.isNaN(+a))&&l.some(a=>a!==0)?(t=l[0]?t.skewX(l[0]):t,t=l[1]?t.skewY(l[1]):t):typeof l=="number"&&!Number.isNaN(l)&&(t=t.skewX(l)),Array.isArray(c)&&c.length>=2&&c.every(a=>!Number.isNaN(+a))&&c.some(a=>a!==1)?t=t.scale(...c):typeof c=="number"&&!Number.isNaN(c)&&(t=t.scale(c)),t=t.translate(-r,-s)),t},ke=(e,t)=>{let n=P.Translate(...t.slice(0,-1));return[,,,n.m44]=t,n=e.multiply(n),[n.m41,n.m42,n.m43,n.m44]},Gt=(e,t,n)=>{const[r,s,i]=n,[o,l,c]=ke(e,[...t,0,1]),a=o-r,m=l-s,f=c-i;return[a*(Math.abs(i)/Math.abs(f)||1)+r,m*(Math.abs(i)/Math.abs(f)||1)+s]},Bt=(e,t)=>{let n=0,r=0,s,i,o,l,c,a;const m=Z(e),f=t&&Object.keys(t);if(!t||f&&!f.length)return[...m];const y=$(m);if(!t.origin){const{origin:M}=_;Object.assign(t,{origin:M})}const g=Le(t),{origin:h}=t,u={...et};let x=[],d=0,p="",A=[];const b=[];if(!g.isIdentity){for(s=0,o=m.length;s<o;s+=1){x=m[s],m[s]&&([p]=x),b[s]=p,p==="A"&&(x=rt(y[s],u),m[s]=rt(y[s],u),dt(m,b,s),y[s]=rt(y[s],u),dt(y,b,s),o=Math.max(m.length,y.length)),x=y[s],d=x.length,u.x1=+x[d-2],u.y1=+x[d-1],u.x2=+x[d-4]||u.x1,u.y2=+x[d-3]||u.y1;const M={s:m[s],c:m[s][0],x:u.x1,y:u.y1};A=[...A,M]}return A.map(M=>{if(p=M.c,x=M.s,p==="L"||p==="H"||p==="V")return[c,a]=Gt(g,[M.x,M.y],h),n!==c&&r!==a?x=["L",c,a]:r===a?x=["H",c]:n===c&&(x=["V",a]),n=c,r=a,x;for(i=1,l=x.length;i<l;i+=2)[n,r]=Gt(g,[+x[i],+x[i+1]],h),x[i]=n,x[i+1]=r;return x})}return[...m]},qe=e=>{const n=e.slice(0,2),r=e.slice(2,4),s=e.slice(4,6),i=e.slice(6,8),o=V(n,r,.5),l=V(r,s,.5),c=V(s,i,.5),a=V(o,l,.5),m=V(l,c,.5),f=V(a,m,.5);return[["C",...o,...a,...f],["C",...m,...c,...i]]};class $e{static CSSMatrix=P;static getPathBBox=jt;static getPathArea=Rt;static getTotalLength=R;static getDrawDirection=xe;static getPointAtLength=G;static pathLengthFactory=nt;static getPropertiesAtLength=pt;static getPropertiesAtPoint=ot;static polygonLength=se;static polygonArea=ne;static getClosestPoint=de;static getSegmentOfPoint=pe;static getSegmentAtLength=be;static isPointInStroke=Me;static isValidPath=Xt;static isPathArray=W;static isAbsoluteArray=yt;static isRelativeArray=Ft;static isCurveArray=Et;static isNormalizedArray=gt;static shapeToPath=we;static parsePathString=D;static roundPath=at;static splitPath=Ht;static splitCubic=qe;static optimizePath=Yt;static reverseCurve=Te;static reversePath=ct;static normalizePath=$;static transformPath=Bt;static pathToAbsolute=Z;static pathToRelative=bt;static pathToCurve=it;static pathToString=I;constructor(t,n){const r=n||{},s=typeof t>"u";if(s||!t.length)throw TypeError(`${T}: "pathValue" is ${s?"undefined":"empty"}`);const i=D(t);this.segments=i;const{width:o,height:l,cx:c,cy:a,cz:m}=this.getBBox(),{round:f,origin:y}=r;let g;if(f==="auto"){const u=`${Math.floor(Math.max(o,l))}`.length;g=u>=4?0:4-u}else Number.isInteger(f)||f==="off"?g=f:g=_.round;let h;if(Array.isArray(y)&&y.length>=2){const[u,x,d]=y.map(Number);h=[Number.isNaN(u)?c:u,Number.isNaN(x)?a:x,Number.isNaN(d)?m:d]}else h=[c,a,m];return this.round=g,this.origin=h,this}getBBox(){return jt(this.segments)}getTotalLength(){return R(this.segments)}getPointAtLength(t){return G(this.segments,t)}toAbsolute(){const{segments:t}=this;return this.segments=Z(t),this}toRelative(){const{segments:t}=this;return this.segments=bt(t),this}toCurve(){const{segments:t}=this;return this.segments=it(t),this}reverse(t){this.toAbsolute();const{segments:n}=this,r=Ht(n),s=r.length>1?r:!1,i=s?[...s].map((l,c)=>t?c?ct(l):[...l]:ct(l)):[...n];let o=[];return s?o=i.flat(1):o=t?n:ct(n),this.segments=[...o],this}normalize(){const{segments:t}=this;return this.segments=$(t),this}optimize(){const{segments:t}=this;return this.segments=Yt(t,this.round),this}transform(t){if(!t||typeof t!="object"||typeof t=="object"&&!["translate","rotate","skew","scale"].some(c=>c in t))return this;const{segments:n,origin:[r,s,i]}=this,o={};for(const[c,a]of Object.entries(t))c==="skew"&&Array.isArray(a)||(c==="rotate"||c==="translate"||c==="origin"||c==="scale")&&Array.isArray(a)?o[c]=a.map(Number):c!=="origin"&&typeof Number(a)=="number"&&(o[c]=Number(a));const{origin:l}=o;if(Array.isArray(l)&&l.length>=2){const[c,a,m]=l.map(Number);o.origin=[Number.isNaN(c)?r:c,Number.isNaN(a)?s:a,m||i]}else o.origin=[r,s,i];return this.segments=Bt(n,o),this}flipX(){return this.transform({rotate:[0,180,0]}),this}flipY(){return this.transform({rotate:[180,0,0]}),this}toString(){return I(this.segments,this.round)}}return $e}();
2
+ //# sourceMappingURL=svg-path-commander.js.map