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