svg-path-commander 1.0.5 → 2.0.0-alpha2

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