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.
- package/README.md +26 -4
- package/dist/svg-path-commander.cjs +2 -0
- package/dist/svg-path-commander.cjs.map +1 -0
- package/dist/svg-path-commander.d.ts +454 -0
- package/dist/svg-path-commander.js +2 -3762
- package/dist/svg-path-commander.js.map +1 -0
- package/dist/svg-path-commander.mjs +1194 -0
- package/dist/svg-path-commander.mjs.map +1 -0
- package/package.json +39 -42
- package/src/convert/pathToAbsolute.ts +101 -0
- package/src/convert/{pathToCurve.js → pathToCurve.ts} +12 -14
- package/src/convert/pathToRelative.ts +92 -0
- package/src/convert/pathToString.ts +17 -0
- package/src/{svg-path-commander.js → index.ts} +143 -66
- package/src/interface.ts +129 -0
- package/src/math/distanceSquareRoot.ts +13 -0
- package/src/math/midPoint.ts +16 -0
- package/src/math/{polygonArea.js → polygonArea.ts} +6 -4
- package/src/math/{polygonLength.js → polygonLength.ts} +5 -3
- package/src/math/rotateVector.ts +16 -0
- package/src/options/options.ts +9 -0
- package/src/parser/error.ts +2 -0
- package/src/parser/finalizeSegment.ts +31 -0
- package/src/parser/{invalidPathValue.js → invalidPathValue.ts} +0 -0
- package/src/parser/{isArcCommand.js → isArcCommand.ts} +5 -4
- package/src/parser/isDigit.ts +12 -0
- package/src/parser/isDigitStart.ts +14 -0
- package/src/parser/isPathCommand.ts +28 -0
- package/src/parser/isSpace.ts +23 -0
- package/src/parser/paramsCount.ts +16 -0
- package/src/parser/paramsParser.ts +14 -0
- package/src/parser/{parsePathString.js → parsePathString.ts} +13 -7
- package/src/parser/pathParser.ts +29 -0
- package/src/parser/{scanFlag.js → scanFlag.ts} +8 -5
- package/src/parser/{scanParam.js → scanParam.ts} +12 -11
- package/src/parser/{scanSegment.js → scanSegment.ts} +9 -5
- package/src/parser/{skipSpaces.js → skipSpaces.ts} +5 -3
- package/src/process/{arcToCubic.js → arcToCubic.ts} +42 -28
- package/src/process/fixArc.ts +23 -0
- package/src/process/getSVGMatrix.ts +70 -0
- package/src/process/lineToCubic.ts +17 -0
- package/src/process/{normalizePath.js → normalizePath.ts} +10 -10
- package/src/process/normalizeSegment.ts +47 -0
- package/src/process/{optimizePath.js → optimizePath.ts} +18 -21
- package/src/process/{projection2d.js → projection2d.ts} +16 -15
- package/src/process/quadToCubic.ts +31 -0
- package/src/process/reverseCurve.ts +21 -0
- package/src/process/reversePath.ts +101 -0
- package/src/process/roundPath.ts +29 -0
- package/src/process/segmentToCubic.ts +46 -0
- package/src/process/shortenSegment.ts +79 -0
- package/src/process/splitCubic.ts +28 -0
- package/src/process/{splitPath.js → splitPath.ts} +9 -8
- package/src/process/{transformPath.js → transformPath.ts} +55 -48
- package/src/types.ts +193 -0
- package/src/util/getClosestPoint.ts +15 -0
- package/src/util/{getDrawDirection.js → getDrawDirection.ts} +7 -4
- package/src/util/getPathArea.ts +70 -0
- package/src/util/{getPathBBox.js → getPathBBox.ts} +16 -5
- package/src/util/getPointAtLength.ts +14 -0
- package/src/util/{getPropertiesAtLength.js → getPropertiesAtLength.ts} +28 -19
- package/src/util/{getPropertiesAtPoint.js → getPropertiesAtPoint.ts} +16 -12
- package/src/util/getSegmentAtLength.ts +15 -0
- package/src/util/getSegmentOfPoint.ts +18 -0
- package/src/util/{getTotalLength.js → getTotalLength.ts} +6 -4
- package/src/util/isAbsoluteArray.ts +18 -0
- package/src/util/{isCurveArray.js → isCurveArray.ts} +6 -4
- package/src/util/{isNormalizedArray.js → isNormalizedArray.ts} +4 -2
- package/src/util/isPathArray.ts +19 -0
- package/src/util/isPointInStroke.ts +15 -0
- package/src/util/isRelativeArray.ts +18 -0
- package/src/util/{isValidPath.js → isValidPath.ts} +5 -4
- package/src/util/{pathLengthFactory.js → pathLengthFactory.ts} +38 -31
- package/src/util/{segmentArcFactory.js → segmentArcFactory.ts} +71 -55
- package/src/util/segmentCubicFactory.ts +114 -0
- package/src/util/{segmentLineFactory.js → segmentLineFactory.ts} +10 -8
- package/src/util/{segmentQuadFactory.js → segmentQuadFactory.ts} +47 -32
- package/src/util/shapeToPath.ts +214 -0
- package/dist/svg-path-commander.es5.js +0 -3875
- package/dist/svg-path-commander.es5.min.js +0 -2
- package/dist/svg-path-commander.esm.js +0 -3754
- package/dist/svg-path-commander.esm.min.js +0 -2
- package/dist/svg-path-commander.min.js +0 -2
- package/src/convert/pathToAbsolute.js +0 -86
- package/src/convert/pathToRelative.js +0 -84
- package/src/convert/pathToString.js +0 -14
- package/src/index.js +0 -10
- package/src/math/distanceSquareRoot.js +0 -14
- package/src/math/epsilon.js +0 -8
- package/src/math/midPoint.js +0 -13
- package/src/math/rotateVector.js +0 -14
- package/src/options/options.js +0 -10
- package/src/parser/error.js +0 -2
- package/src/parser/finalizeSegment.js +0 -28
- package/src/parser/isDigit.js +0 -9
- package/src/parser/isDigitStart.js +0 -13
- package/src/parser/isPathCommand.js +0 -25
- package/src/parser/isSpace.js +0 -16
- package/src/parser/paramsCount.js +0 -9
- package/src/parser/paramsParser.js +0 -8
- package/src/parser/pathParser.js +0 -24
- package/src/process/clonePath.js +0 -9
- package/src/process/fixArc.js +0 -21
- package/src/process/fixPath.js +0 -31
- package/src/process/getSVGMatrix.js +0 -61
- package/src/process/lineToCubic.js +0 -30
- package/src/process/normalizeSegment.js +0 -45
- package/src/process/quadToCubic.js +0 -22
- package/src/process/reverseCurve.js +0 -18
- package/src/process/reversePath.js +0 -89
- package/src/process/roundPath.js +0 -26
- package/src/process/segmentToCubic.js +0 -46
- package/src/process/shortenSegment.js +0 -58
- package/src/process/splitCubic.js +0 -26
- package/src/util/getClosestPoint.js +0 -12
- package/src/util/getPathArea.js +0 -47
- package/src/util/getPointAtLength.js +0 -12
- package/src/util/getSegmentAtLength.js +0 -11
- package/src/util/getSegmentOfPoint.js +0 -12
- package/src/util/isAbsoluteArray.js +0 -14
- package/src/util/isPathArray.js +0 -14
- package/src/util/isPointInStroke.js +0 -13
- package/src/util/isRelativeArray.js +0 -14
- package/src/util/segmentCubicFactory.js +0 -97
- package/src/util/shapeToPath.js +0 -204
- package/src/util/util.js +0 -82
- package/src/version.js +0 -8
- package/types/index.d.ts +0 -120
- package/types/more/modules.ts +0 -82
- package/types/more/svg.d.ts +0 -211
- package/types/svg-path-commander.d.ts +0 -1089
|
@@ -1,3762 +1,2 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
|
|
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
|