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