svg-path-commander 0.1.8 → 0.1.9
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/dist/svg-path-commander.esm.js +963 -351
- package/dist/svg-path-commander.esm.min.js +2 -2
- package/dist/svg-path-commander.js +968 -344
- package/dist/svg-path-commander.min.js +2 -2
- package/package.json +4 -4
- package/src/class/{SVGPathCommander.js → svg-path-commander.js} +179 -201
- package/src/class/version.js +9 -0
- package/src/convert/pathToAbsolute.js +99 -92
- package/src/convert/pathToCurve.js +45 -41
- package/src/convert/pathToRelative.js +96 -89
- package/src/convert/pathToString.js +14 -6
- package/src/index.js +9 -6
- package/src/math/distanceSquareRoot.js +14 -6
- package/src/math/epsilon.js +8 -1
- package/src/math/midPoint.js +13 -7
- package/src/math/polygonArea.js +25 -18
- package/src/math/polygonLength.js +19 -13
- package/src/math/rotateVector.js +13 -0
- package/src/options/options.js +12 -7
- package/src/parser/finalizeSegment.js +31 -0
- package/src/{util → parser}/invalidPathValue.js +2 -2
- package/src/parser/isArcCommand.js +10 -0
- package/src/parser/isDigit.js +9 -0
- package/src/{util → parser}/isDigitStart.js +13 -6
- package/src/{util → parser}/isPathCommand.js +25 -19
- package/src/parser/isSpace.js +16 -0
- package/src/parser/paramsCount.js +8 -0
- package/src/parser/parsePathString.js +40 -0
- package/src/parser/scanFlag.js +26 -0
- package/src/parser/scanParam.js +97 -0
- package/src/parser/scanSegment.js +70 -0
- package/src/parser/skipSpaces.js +15 -0
- package/src/parser/svgPathArray.js +23 -0
- package/src/process/arcToCubic.js +125 -111
- package/src/process/clonePath.js +14 -8
- package/src/process/fixArc.js +21 -0
- package/src/{util → process}/getSVGMatrix.js +57 -55
- package/src/process/lineToCubic.js +26 -17
- package/src/process/normalizePath.js +49 -41
- package/src/process/normalizeSegment.js +43 -35
- package/src/process/optimizePath.js +24 -14
- package/src/{util → process}/projection2d.js +29 -21
- package/src/process/quadToCubic.js +22 -11
- package/src/process/reverseCurve.js +19 -13
- package/src/process/reversePath.js +90 -83
- package/src/process/roundPath.js +26 -21
- package/src/process/segmentToCubic.js +38 -31
- package/src/process/shorthandToCubic.js +16 -6
- package/src/process/shorthandToQuad.js +16 -6
- package/src/process/splitCubic.js +26 -20
- package/src/process/splitPath.js +19 -10
- package/src/{util → process}/transformEllipse.js +73 -65
- package/src/process/transformPath.js +135 -125
- package/src/util/createPath.js +17 -8
- package/src/util/getCubicSize.js +62 -50
- package/src/util/getDrawDirection.js +13 -6
- package/src/util/getPathArea.js +52 -30
- package/src/util/getPathBBox.js +56 -50
- package/src/util/getPathLength.js +19 -13
- package/src/util/getPointAtLength.js +33 -28
- package/src/util/getPointAtSegLength.js +28 -14
- package/src/util/getSegArcLength.js +24 -21
- package/src/util/getSegCubicLength.js +44 -31
- package/src/util/getSegLineLength.js +14 -6
- package/src/util/getSegQuadLength.js +31 -19
- package/src/util/isAbsoluteArray.js +12 -5
- package/src/util/isCurveArray.js +12 -5
- package/src/util/isNormalizedArray.js +17 -8
- package/src/util/isPathArray.js +14 -8
- package/src/util/isRelativeArray.js +13 -6
- package/src/util/isValidPath.js +26 -0
- package/src/util/shapeToPath.js +169 -0
- package/src/util/util.js +65 -60
- package/types/index.d.ts +931 -2
- package/types/types.d.ts +69 -0
- package/src/process/finalizeSegment.js +0 -26
- package/src/process/parsePathString.js +0 -35
- package/src/process/scanFlag.js +0 -20
- package/src/process/scanParam.js +0 -93
- package/src/process/scanSegment.js +0 -64
- package/src/process/skipSpaces.js +0 -7
- package/src/util/DOMMatrix.js +0 -5
- package/src/util/fixArc.js +0 -13
- package/src/util/isArcCommand.js +0 -4
- package/src/util/isDigit.js +0 -3
- package/src/util/isSpace.js +0 -9
- package/src/util/paramsCount.js +0 -3
- package/src/util/rotateVector.js +0 -5
- package/src/util/svgPathArray.js +0 -10
- package/types/class/SVGPathCommander.d.ts +0 -91
- package/types/convert/pathToAbsolute.d.ts +0 -1
- package/types/convert/pathToCurve.d.ts +0 -1
- package/types/convert/pathToRelative.d.ts +0 -1
- package/types/convert/pathToString.d.ts +0 -1
- package/types/math/distanceSquareRoot.d.ts +0 -1
- package/types/math/epsilon.d.ts +0 -2
- package/types/math/midPoint.d.ts +0 -1
- package/types/math/polygonArea.d.ts +0 -1
- package/types/math/polygonLength.d.ts +0 -1
- package/types/options/options.d.ts +0 -6
- package/types/process/arcToCubic.d.ts +0 -1
- package/types/process/clonePath.d.ts +0 -1
- package/types/process/finalizeSegment.d.ts +0 -1
- package/types/process/lineToCubic.d.ts +0 -1
- package/types/process/normalizePath.d.ts +0 -1
- package/types/process/normalizeSegment.d.ts +0 -1
- package/types/process/optimizePath.d.ts +0 -1
- package/types/process/parsePathString.d.ts +0 -1
- package/types/process/quadToCubic.d.ts +0 -1
- package/types/process/reverseCurve.d.ts +0 -1
- package/types/process/reversePath.d.ts +0 -1
- package/types/process/roundPath.d.ts +0 -1
- package/types/process/scanFlag.d.ts +0 -1
- package/types/process/scanParam.d.ts +0 -1
- package/types/process/scanSegment.d.ts +0 -1
- package/types/process/segmentToCubic.d.ts +0 -1
- package/types/process/shorthandToCubic.d.ts +0 -4
- package/types/process/shorthandToQuad.d.ts +0 -4
- package/types/process/skipSpaces.d.ts +0 -1
- package/types/process/splitCubic.d.ts +0 -1
- package/types/process/splitPath.d.ts +0 -1
- package/types/process/transformPath.d.ts +0 -1
- package/types/util/DOMMatrix.d.ts +0 -2
- package/types/util/createPath.d.ts +0 -1
- package/types/util/fixArc.d.ts +0 -1
- package/types/util/getCubicSize.d.ts +0 -10
- package/types/util/getDrawDirection.d.ts +0 -1
- package/types/util/getPathArea.d.ts +0 -1
- package/types/util/getPathBBox.d.ts +0 -19
- package/types/util/getPathLength.d.ts +0 -1
- package/types/util/getPointAtLength.d.ts +0 -1
- package/types/util/getPointAtSegLength.d.ts +0 -4
- package/types/util/getSVGMatrix.d.ts +0 -1
- package/types/util/getSegArcLength.d.ts +0 -1
- package/types/util/getSegCubicLength.d.ts +0 -1
- package/types/util/getSegLineLength.d.ts +0 -1
- package/types/util/getSegQuadLength.d.ts +0 -1
- package/types/util/invalidPathValue.d.ts +0 -2
- package/types/util/isAbsoluteArray.d.ts +0 -1
- package/types/util/isArcCommand.d.ts +0 -1
- package/types/util/isCurveArray.d.ts +0 -1
- package/types/util/isDigit.d.ts +0 -1
- package/types/util/isDigitStart.d.ts +0 -1
- package/types/util/isNormalizedArray.d.ts +0 -1
- package/types/util/isPathArray.d.ts +0 -1
- package/types/util/isPathCommand.d.ts +0 -1
- package/types/util/isRelativeArray.d.ts +0 -1
- package/types/util/isSpace.d.ts +0 -1
- package/types/util/paramsCount.d.ts +0 -14
- package/types/util/projection2d.d.ts +0 -1
- package/types/util/rotateVector.d.ts +0 -4
- package/types/util/svgPathArray.d.ts +0 -12
- package/types/util/transformEllipse.d.ts +0 -5
- package/types/util/util.d.ts +0 -55
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* SVGPathCommander v0.1.
|
|
2
|
+
* SVGPathCommander v0.1.9 (http://thednp.github.io/svg-path-commander)
|
|
3
3
|
* Copyright 2021 © thednp
|
|
4
4
|
* Licensed under MIT (https://github.com/thednp/svg-path-commander/blob/master/LICENSE)
|
|
5
5
|
*/
|
|
6
|
-
const SVGPCO = {
|
|
7
|
-
origin: null,
|
|
8
|
-
decimals: 4,
|
|
9
|
-
round: 1,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
6
|
// DOMMatrix Static methods
|
|
13
7
|
// * `fromFloat64Array` and `fromFloat32Array` methods are not supported;
|
|
14
8
|
// * `fromArray` a more simple implementation, should also accept float[32/64]Array;
|
|
@@ -16,15 +10,20 @@ const SVGPCO = {
|
|
|
16
10
|
// * `fromString` parses and loads values from any valid CSS transform string.
|
|
17
11
|
|
|
18
12
|
/**
|
|
19
|
-
* Creates a new mutable `CSSMatrix` object given an array
|
|
13
|
+
* Creates a new mutable `CSSMatrix` object given an array of floating point values.
|
|
14
|
+
*
|
|
15
|
+
* This static method invalidates arrays that contain non-number elements.
|
|
20
16
|
*
|
|
21
17
|
* If the array has six values, the result is a 2D matrix; if the array has 16 values,
|
|
22
18
|
* the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
|
|
23
19
|
*
|
|
24
|
-
* @param {
|
|
20
|
+
* @param {number[]} array an `Array` to feed values from.
|
|
25
21
|
* @return {CSSMatrix} the resulted matrix.
|
|
26
22
|
*/
|
|
27
23
|
function fromArray(array) {
|
|
24
|
+
if (!array.every((n) => !Number.isNaN(n))) {
|
|
25
|
+
throw TypeError(`CSSMatrix: "${array}" must only have numbers.`);
|
|
26
|
+
}
|
|
28
27
|
const m = new CSSMatrix();
|
|
29
28
|
const a = Array.from(array);
|
|
30
29
|
|
|
@@ -85,19 +84,22 @@ function fromArray(array) {
|
|
|
85
84
|
m.m42 = m42;
|
|
86
85
|
m.f = m42;
|
|
87
86
|
} else {
|
|
88
|
-
throw new TypeError('CSSMatrix: expecting
|
|
87
|
+
throw new TypeError('CSSMatrix: expecting an Array of 6/16 values.');
|
|
89
88
|
}
|
|
90
89
|
return m;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
94
|
-
* Creates a new mutable `CSSMatrix`
|
|
95
|
-
* `DOMMatrix`
|
|
93
|
+
* Creates a new mutable `CSSMatrix` instance given an existing matrix or a
|
|
94
|
+
* `DOMMatrix` instance which provides the values for its properties.
|
|
96
95
|
*
|
|
97
|
-
* @param {CSSMatrix | DOMMatrix} m the source matrix to feed values from.
|
|
96
|
+
* @param {CSSMatrix | DOMMatrix | jsonMatrix} m the source matrix to feed values from.
|
|
98
97
|
* @return {CSSMatrix} the resulted matrix.
|
|
99
98
|
*/
|
|
100
99
|
function fromMatrix(m) {
|
|
100
|
+
if (![CSSMatrix, DOMMatrix, Object].some((x) => m instanceof x)) {
|
|
101
|
+
throw TypeError(`CSSMatrix: "${m}" is not a DOMMatrix / CSSMatrix compatible object.`);
|
|
102
|
+
}
|
|
101
103
|
return fromArray(
|
|
102
104
|
[m.m11, m.m12, m.m13, m.m14,
|
|
103
105
|
m.m21, m.m22, m.m23, m.m24,
|
|
@@ -107,15 +109,21 @@ function fromMatrix(m) {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* *
|
|
113
|
-
* *
|
|
112
|
+
* Creates a new mutable `CSSMatrix` instance given any valid CSS transform string.
|
|
113
|
+
*
|
|
114
|
+
* * `matrix(a, b, c, d, e, f)` - valid matrix() transform function
|
|
115
|
+
* * `matrix3d(m11, m12, m13, ...m44)` - valid matrix3d() transform function
|
|
116
|
+
* * `translate(tx, ty) rotateX(alpha)` - any valid transform function(s)
|
|
117
|
+
*
|
|
118
|
+
* @copyright thednp © 2021
|
|
114
119
|
*
|
|
115
120
|
* @param {string} source valid CSS transform string syntax.
|
|
116
121
|
* @return {CSSMatrix} the resulted matrix.
|
|
117
122
|
*/
|
|
118
123
|
function fromString(source) {
|
|
124
|
+
if (typeof source !== 'string') {
|
|
125
|
+
throw TypeError(`CSSMatrix: "${source}" is not a string.`);
|
|
126
|
+
}
|
|
119
127
|
const str = String(source).replace(/\s/g, '');
|
|
120
128
|
let m = new CSSMatrix();
|
|
121
129
|
let is2D = true;
|
|
@@ -123,17 +131,6 @@ function fromString(source) {
|
|
|
123
131
|
const [prop, value] = fn.split('(');
|
|
124
132
|
const components = value.split(',')
|
|
125
133
|
.map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
|
|
126
|
-
const [x, y, z, a] = components;
|
|
127
|
-
|
|
128
|
-
// don't add perspective if is2D
|
|
129
|
-
if (prop === 'matrix3d'
|
|
130
|
-
|| (prop === 'rotate3d' && [x, y].every((n) => !Number.isNaN(+n) && n !== 0) && a)
|
|
131
|
-
|| (['rotateX', 'rotateY'].includes(prop) && x)
|
|
132
|
-
|| (prop === 'translate3d' && [x, y, z].every((n) => !Number.isNaN(+n)) && z)
|
|
133
|
-
|| (prop === 'scale3d' && [x, y, z].every((n) => !Number.isNaN(+n) && n !== x))
|
|
134
|
-
) {
|
|
135
|
-
is2D = false;
|
|
136
|
-
}
|
|
137
134
|
return { prop, components };
|
|
138
135
|
});
|
|
139
136
|
|
|
@@ -195,9 +192,9 @@ function fromString(source) {
|
|
|
195
192
|
*
|
|
196
193
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
|
|
197
194
|
*
|
|
198
|
-
* @param {
|
|
199
|
-
* @param {
|
|
200
|
-
* @param {
|
|
195
|
+
* @param {number} x the `x-axis` position.
|
|
196
|
+
* @param {number} y the `y-axis` position.
|
|
197
|
+
* @param {number} z the `z-axis` position.
|
|
201
198
|
* @return {CSSMatrix} the resulted matrix.
|
|
202
199
|
*/
|
|
203
200
|
function Translate(x, y, z) {
|
|
@@ -215,9 +212,9 @@ function Translate(x, y, z) {
|
|
|
215
212
|
*
|
|
216
213
|
* http://en.wikipedia.org/wiki/Rotation_matrix
|
|
217
214
|
*
|
|
218
|
-
* @param {
|
|
219
|
-
* @param {
|
|
220
|
-
* @param {
|
|
215
|
+
* @param {number} rx the `x-axis` rotation.
|
|
216
|
+
* @param {number} ry the `y-axis` rotation.
|
|
217
|
+
* @param {number} rz the `z-axis` rotation.
|
|
221
218
|
* @return {CSSMatrix} the resulted matrix.
|
|
222
219
|
*/
|
|
223
220
|
function Rotate(rx, ry, rz) {
|
|
@@ -269,10 +266,10 @@ function Rotate(rx, ry, rz) {
|
|
|
269
266
|
*
|
|
270
267
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
|
|
271
268
|
*
|
|
272
|
-
* @param {
|
|
273
|
-
* @param {
|
|
274
|
-
* @param {
|
|
275
|
-
* @param {
|
|
269
|
+
* @param {number} x the `x-axis` vector length.
|
|
270
|
+
* @param {number} y the `y-axis` vector length.
|
|
271
|
+
* @param {number} z the `z-axis` vector length.
|
|
272
|
+
* @param {number} alpha the value in degrees of the rotation.
|
|
276
273
|
* @return {CSSMatrix} the resulted matrix.
|
|
277
274
|
*/
|
|
278
275
|
function RotateAxisAngle(x, y, z, alpha) {
|
|
@@ -333,9 +330,9 @@ function RotateAxisAngle(x, y, z, alpha) {
|
|
|
333
330
|
*
|
|
334
331
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
|
|
335
332
|
*
|
|
336
|
-
* @param {
|
|
337
|
-
* @param {
|
|
338
|
-
* @param {
|
|
333
|
+
* @param {number} x the `x-axis` scale.
|
|
334
|
+
* @param {number} y the `y-axis` scale.
|
|
335
|
+
* @param {number} z the `z-axis` scale.
|
|
339
336
|
* @return {CSSMatrix} the resulted matrix.
|
|
340
337
|
*/
|
|
341
338
|
function Scale(x, y, z) {
|
|
@@ -356,7 +353,7 @@ function Scale(x, y, z) {
|
|
|
356
353
|
*
|
|
357
354
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
|
|
358
355
|
*
|
|
359
|
-
* @param {
|
|
356
|
+
* @param {number} angle the angle in degrees.
|
|
360
357
|
* @return {CSSMatrix} the resulted matrix.
|
|
361
358
|
*/
|
|
362
359
|
function SkewX(angle) {
|
|
@@ -374,7 +371,7 @@ function SkewX(angle) {
|
|
|
374
371
|
*
|
|
375
372
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
|
|
376
373
|
*
|
|
377
|
-
* @param {
|
|
374
|
+
* @param {number} angle the angle in degrees.
|
|
378
375
|
* @return {CSSMatrix} the resulted matrix.
|
|
379
376
|
*/
|
|
380
377
|
function SkewY(angle) {
|
|
@@ -434,11 +431,11 @@ function Multiply(m1, m2) {
|
|
|
434
431
|
class CSSMatrix {
|
|
435
432
|
/**
|
|
436
433
|
* @constructor
|
|
437
|
-
* @param {any} args accepts all
|
|
438
|
-
*
|
|
434
|
+
* @param {any} args accepts all parameter configurations:
|
|
435
|
+
*
|
|
439
436
|
* * valid CSS transform string,
|
|
440
|
-
* * CSSMatrix/DOMMatrix instance
|
|
441
|
-
* * a 6/16 elements *Array
|
|
437
|
+
* * CSSMatrix/DOMMatrix instance,
|
|
438
|
+
* * a 6/16 elements *Array*.
|
|
442
439
|
*/
|
|
443
440
|
constructor(...args) {
|
|
444
441
|
const m = this;
|
|
@@ -522,12 +519,8 @@ class CSSMatrix {
|
|
|
522
519
|
* This method expects valid *matrix()* / *matrix3d()* string values, as well
|
|
523
520
|
* as other transform functions like *translateX(10px)*.
|
|
524
521
|
*
|
|
525
|
-
* @param {
|
|
526
|
-
* @return {CSSMatrix}
|
|
527
|
-
* can be one of the following
|
|
528
|
-
* * valid CSS matrix string,
|
|
529
|
-
* * 6/16 elements *Array*,
|
|
530
|
-
* * CSSMatrix | DOMMatrix instance.
|
|
522
|
+
* @param {string | number[] | CSSMatrix | DOMMatrix} source
|
|
523
|
+
* @return {CSSMatrix} the matrix instance
|
|
531
524
|
*/
|
|
532
525
|
setMatrixValue(source) {
|
|
533
526
|
const m = this;
|
|
@@ -541,7 +534,6 @@ class CSSMatrix {
|
|
|
541
534
|
return fromString(source);
|
|
542
535
|
// [Arguments list | Array] come here
|
|
543
536
|
} if (Array.isArray(source)) {
|
|
544
|
-
// @ts-ignore
|
|
545
537
|
return fromArray(source);
|
|
546
538
|
}
|
|
547
539
|
return m;
|
|
@@ -551,12 +543,10 @@ class CSSMatrix {
|
|
|
551
543
|
* Creates and returns a string representation of the matrix in `CSS` matrix syntax,
|
|
552
544
|
* using the appropriate `CSS` matrix notation.
|
|
553
545
|
*
|
|
554
|
-
* The 16 items in the array 3D matrix array are *transposed* in row-major order.
|
|
555
|
-
*
|
|
556
546
|
* matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
|
|
557
547
|
* matrix *matrix(a, b, c, d, e, f)*
|
|
558
548
|
*
|
|
559
|
-
* @return {
|
|
549
|
+
* @return {string} a string representation of the matrix
|
|
560
550
|
*/
|
|
561
551
|
toString() {
|
|
562
552
|
const m = this;
|
|
@@ -571,7 +561,7 @@ class CSSMatrix {
|
|
|
571
561
|
*
|
|
572
562
|
* Other methods make use of this method to feed their output values from this matrix.
|
|
573
563
|
*
|
|
574
|
-
* @return {
|
|
564
|
+
* @return {number[]} an *Array* representation of the matrix
|
|
575
565
|
*/
|
|
576
566
|
toArray() {
|
|
577
567
|
const m = this;
|
|
@@ -587,19 +577,38 @@ class CSSMatrix {
|
|
|
587
577
|
m.m41, m.m42, m.m43, m.m44];
|
|
588
578
|
}
|
|
589
579
|
// clean up the numbers
|
|
590
|
-
// eslint-disable-next-line
|
|
591
|
-
return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
|
|
580
|
+
// eslint-disable-next-line -- no-bitwise
|
|
581
|
+
return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
|
|
592
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* @typedef {object} jsonMatrix
|
|
585
|
+
* @property {number} m11
|
|
586
|
+
* @property {number} m12
|
|
587
|
+
* @property {number} m13
|
|
588
|
+
* @property {number} m14
|
|
589
|
+
* @property {number} m21
|
|
590
|
+
* @property {number} m22
|
|
591
|
+
* @property {number} m23
|
|
592
|
+
* @property {number} m24
|
|
593
|
+
* @property {number} m31
|
|
594
|
+
* @property {number} m32
|
|
595
|
+
* @property {number} m33
|
|
596
|
+
* @property {number} m34
|
|
597
|
+
* @property {number} m41
|
|
598
|
+
* @property {number} m42
|
|
599
|
+
* @property {number} m43
|
|
600
|
+
* @property {number} m44
|
|
601
|
+
*/
|
|
593
602
|
|
|
594
603
|
/**
|
|
595
|
-
* Returns a JSON representation of the `CSSMatrix`
|
|
604
|
+
* Returns a JSON representation of the `CSSMatrix` instance, a standard *Object*
|
|
596
605
|
* that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties and
|
|
597
606
|
* excludes `is2D` & `isIdentity` properties.
|
|
598
607
|
*
|
|
599
608
|
* The result can also be used as a second parameter for the `fromMatrix` static method
|
|
600
609
|
* to load values into a matrix instance.
|
|
601
610
|
*
|
|
602
|
-
* @return {
|
|
611
|
+
* @return {jsonMatrix} an *Object* with all matrix values.
|
|
603
612
|
*/
|
|
604
613
|
toJSON() {
|
|
605
614
|
return JSON.parse(JSON.stringify(this));
|
|
@@ -610,10 +619,11 @@ class CSSMatrix {
|
|
|
610
619
|
* matrix multiplied by the passed matrix, with the passed matrix to the right.
|
|
611
620
|
* This matrix is not modified.
|
|
612
621
|
*
|
|
613
|
-
* @param {CSSMatrix} m2 CSSMatrix
|
|
614
|
-
* @return {CSSMatrix} The
|
|
622
|
+
* @param {CSSMatrix | DOMMatrix | jsonMatrix} m2 CSSMatrix
|
|
623
|
+
* @return {CSSMatrix} The resulted matrix.
|
|
615
624
|
*/
|
|
616
625
|
multiply(m2) {
|
|
626
|
+
// @ts-ignore - we only access [m11, m12, ... m44] values
|
|
617
627
|
return Multiply(this, m2);
|
|
618
628
|
}
|
|
619
629
|
|
|
@@ -626,7 +636,7 @@ class CSSMatrix {
|
|
|
626
636
|
* @param {number} x X component of the translation value.
|
|
627
637
|
* @param {number} y Y component of the translation value.
|
|
628
638
|
* @param {number} z Z component of the translation value.
|
|
629
|
-
* @return {CSSMatrix} The
|
|
639
|
+
* @return {CSSMatrix} The resulted matrix
|
|
630
640
|
*/
|
|
631
641
|
translate(x, y, z) {
|
|
632
642
|
const X = x;
|
|
@@ -646,7 +656,7 @@ class CSSMatrix {
|
|
|
646
656
|
* @param {number} x The X component of the scale value.
|
|
647
657
|
* @param {number} y The Y component of the scale value.
|
|
648
658
|
* @param {number} z The Z component of the scale value.
|
|
649
|
-
* @return {CSSMatrix} The
|
|
659
|
+
* @return {CSSMatrix} The resulted matrix
|
|
650
660
|
*/
|
|
651
661
|
scale(x, y, z) {
|
|
652
662
|
const X = x;
|
|
@@ -668,7 +678,7 @@ class CSSMatrix {
|
|
|
668
678
|
* @param {number} rx The X component of the rotation, or Z if Y and Z are null.
|
|
669
679
|
* @param {number} ry The (optional) Y component of the rotation value.
|
|
670
680
|
* @param {number} rz The (optional) Z component of the rotation value.
|
|
671
|
-
* @return {CSSMatrix} The
|
|
681
|
+
* @return {CSSMatrix} The resulted matrix
|
|
672
682
|
*/
|
|
673
683
|
rotate(rx, ry, rz) {
|
|
674
684
|
let RX = rx;
|
|
@@ -689,10 +699,10 @@ class CSSMatrix {
|
|
|
689
699
|
* @param {number} y The Y component of the axis vector.
|
|
690
700
|
* @param {number} z The Z component of the axis vector.
|
|
691
701
|
* @param {number} angle The angle of rotation about the axis vector, in degrees.
|
|
692
|
-
* @return {CSSMatrix} The
|
|
702
|
+
* @return {CSSMatrix} The resulted matrix
|
|
693
703
|
*/
|
|
694
704
|
rotateAxisAngle(x, y, z, angle) {
|
|
695
|
-
if (
|
|
705
|
+
if ([x, y, z, angle].some((n) => Number.isNaN(n))) {
|
|
696
706
|
throw new TypeError('CSSMatrix: expecting 4 values');
|
|
697
707
|
}
|
|
698
708
|
return Multiply(this, RotateAxisAngle(x, y, z, angle));
|
|
@@ -703,7 +713,7 @@ class CSSMatrix {
|
|
|
703
713
|
* This matrix is not modified.
|
|
704
714
|
*
|
|
705
715
|
* @param {number} angle The angle amount in degrees to skew.
|
|
706
|
-
* @return {CSSMatrix} The
|
|
716
|
+
* @return {CSSMatrix} The resulted matrix
|
|
707
717
|
*/
|
|
708
718
|
skewX(angle) {
|
|
709
719
|
return Multiply(this, SkewX(angle));
|
|
@@ -714,24 +724,32 @@ class CSSMatrix {
|
|
|
714
724
|
* This matrix is not modified.
|
|
715
725
|
*
|
|
716
726
|
* @param {number} angle The angle amount in degrees to skew.
|
|
717
|
-
* @return {CSSMatrix} The
|
|
727
|
+
* @return {CSSMatrix} The resulted matrix
|
|
718
728
|
*/
|
|
719
729
|
skewY(angle) {
|
|
720
730
|
return Multiply(this, SkewY(angle));
|
|
721
731
|
}
|
|
722
732
|
|
|
723
733
|
/**
|
|
724
|
-
*
|
|
734
|
+
* @typedef {Object} Tuple
|
|
735
|
+
* @property {number} x the `x-axis` component
|
|
736
|
+
* @property {number} y the `y-axis` component
|
|
737
|
+
* @property {number} z the `z-axis` component
|
|
738
|
+
* @property {number} w the `w` component
|
|
739
|
+
*/
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Transforms a specified point using the matrix, returning a new
|
|
725
743
|
* Tuple *Object* comprising of the transformed point.
|
|
726
744
|
* Neither the matrix nor the original point are altered.
|
|
727
745
|
*
|
|
728
746
|
* The method is equivalent with `transformPoint()` method
|
|
729
747
|
* of the `DOMMatrix` constructor.
|
|
730
748
|
*
|
|
731
|
-
*
|
|
749
|
+
* @copyright thednp © 2021
|
|
732
750
|
*
|
|
733
|
-
* @param {
|
|
734
|
-
* @return {
|
|
751
|
+
* @param {Tuple | DOMPoint} v Tuple or DOMPoint
|
|
752
|
+
* @return {Tuple} the resulting Tuple
|
|
735
753
|
*/
|
|
736
754
|
transformPoint(v) {
|
|
737
755
|
const M = this;
|
|
@@ -749,12 +767,12 @@ class CSSMatrix {
|
|
|
749
767
|
}
|
|
750
768
|
|
|
751
769
|
/**
|
|
752
|
-
* Transforms
|
|
770
|
+
* Transforms a specified vector using the matrix, returning a new
|
|
753
771
|
* {x,y,z,w} Tuple *Object* comprising the transformed vector.
|
|
754
772
|
* Neither the matrix nor the original vector are altered.
|
|
755
773
|
*
|
|
756
|
-
* @param {
|
|
757
|
-
* @return {
|
|
774
|
+
* @param {Tuple} t Tuple with `{x,y,z,w}` components
|
|
775
|
+
* @return {Tuple} the resulting Tuple
|
|
758
776
|
*/
|
|
759
777
|
transform(t) {
|
|
760
778
|
const m = this;
|
|
@@ -784,39 +802,78 @@ CSSMatrix.fromArray = fromArray;
|
|
|
784
802
|
CSSMatrix.fromMatrix = fromMatrix;
|
|
785
803
|
CSSMatrix.fromString = fromString;
|
|
786
804
|
|
|
787
|
-
|
|
805
|
+
/**
|
|
806
|
+
* SVGPathCommander default options
|
|
807
|
+
*
|
|
808
|
+
* @type {Object.<string, (boolean | number | number[])>}
|
|
809
|
+
*/
|
|
810
|
+
const SVGPCO = {
|
|
811
|
+
origin: null,
|
|
812
|
+
decimals: 4,
|
|
813
|
+
round: 1,
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Splits an extended A (arc-to) segment into two cubic-bezier segments.
|
|
818
|
+
*
|
|
819
|
+
* @param {SVGPC.pathArray} path the `pathArray` this segment belongs to
|
|
820
|
+
* @param {string[]} allPathCommands all previous path commands
|
|
821
|
+
* @param {Number} i the index of the segment
|
|
822
|
+
*/
|
|
788
823
|
|
|
789
|
-
function fixArc(
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
const
|
|
824
|
+
function fixArc(path, allPathCommands, i) {
|
|
825
|
+
if (path[i].length > 7) {
|
|
826
|
+
path[i].shift();
|
|
827
|
+
const segment = path[i];
|
|
793
828
|
let ni = i; // ESLint
|
|
794
|
-
while (
|
|
829
|
+
while (segment.length) {
|
|
795
830
|
// if created multiple C:s, their original seg is saved
|
|
796
831
|
allPathCommands[i] = 'A';
|
|
797
|
-
|
|
832
|
+
path.splice(ni += 1, 0, ['C'].concat(segment.splice(0, 6)));
|
|
798
833
|
}
|
|
799
|
-
|
|
834
|
+
path.splice(i, 1);
|
|
800
835
|
}
|
|
801
836
|
}
|
|
802
837
|
|
|
803
|
-
|
|
838
|
+
/**
|
|
839
|
+
* @type {Object.<string, number>}
|
|
840
|
+
*/
|
|
841
|
+
const paramsCount = {
|
|
804
842
|
a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
|
|
805
843
|
};
|
|
806
844
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
845
|
+
/**
|
|
846
|
+
* Iterates an array to check if it's an actual `pathArray`.
|
|
847
|
+
*
|
|
848
|
+
* @param {SVGPC.pathArray} path the `pathArray` to be checked
|
|
849
|
+
* @returns {boolean} iteration result
|
|
850
|
+
*/
|
|
851
|
+
function isPathArray(path) {
|
|
852
|
+
return Array.isArray(path) && path.every((seg) => {
|
|
853
|
+
const lk = seg[0].toLowerCase();
|
|
854
|
+
return paramsCount[lk] === seg.length - 1 && /[achlmqstvz]/gi.test(lk);
|
|
811
855
|
});
|
|
812
856
|
}
|
|
813
857
|
|
|
814
|
-
|
|
815
|
-
|
|
858
|
+
/**
|
|
859
|
+
* Iterates an array to check if it's a `pathArray`
|
|
860
|
+
* with all C (cubic bezier) segments.
|
|
861
|
+
*
|
|
862
|
+
* @param {SVGPC.pathArray} path the `Array` to be checked
|
|
863
|
+
* @returns {boolean} iteration result
|
|
864
|
+
*/
|
|
865
|
+
function isCurveArray(path) {
|
|
866
|
+
return isPathArray(path) && path.slice(1).every((seg) => seg[0] === 'C');
|
|
816
867
|
}
|
|
817
868
|
|
|
818
|
-
|
|
819
|
-
|
|
869
|
+
/**
|
|
870
|
+
* Returns a clone of an existing `pathArray`.
|
|
871
|
+
*
|
|
872
|
+
* @param {SVGPC.pathArray} path the original `pathArray`
|
|
873
|
+
* @returns {SVGPC.pathArray} the cloned `pathArray`
|
|
874
|
+
*/
|
|
875
|
+
function clonePath(path) {
|
|
876
|
+
return path.map((x) => {
|
|
820
877
|
if (Array.isArray(x)) {
|
|
821
878
|
return clonePath(x);
|
|
822
879
|
}
|
|
@@ -824,24 +881,29 @@ function clonePath(pathArray) {
|
|
|
824
881
|
});
|
|
825
882
|
}
|
|
826
883
|
|
|
827
|
-
|
|
828
|
-
|
|
884
|
+
/**
|
|
885
|
+
* Breaks the parsing of a pathString once a segment is finalized.
|
|
886
|
+
*
|
|
887
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
888
|
+
*/
|
|
889
|
+
function finalizeSegment(path) {
|
|
890
|
+
let pathCommand = path.pathValue[path.segmentStart];
|
|
829
891
|
let pathComLK = pathCommand.toLowerCase();
|
|
830
|
-
let params =
|
|
892
|
+
let params = path.data;
|
|
831
893
|
|
|
832
894
|
// Process duplicated commands (without comand name)
|
|
833
895
|
if (pathComLK === 'm' && params.length > 2) {
|
|
834
|
-
|
|
896
|
+
path.segments.push([pathCommand, params[0], params[1]]);
|
|
835
897
|
params = params.slice(2);
|
|
836
898
|
pathComLK = 'l';
|
|
837
899
|
pathCommand = (pathCommand === 'm') ? 'l' : 'L';
|
|
838
900
|
}
|
|
839
901
|
|
|
840
902
|
if (pathComLK === 'r') {
|
|
841
|
-
|
|
903
|
+
path.segments.push([pathCommand].concat(params));
|
|
842
904
|
} else {
|
|
843
905
|
while (params.length >= paramsCount[pathComLK]) {
|
|
844
|
-
|
|
906
|
+
path.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
|
|
845
907
|
if (!paramsCount[pathComLK]) {
|
|
846
908
|
break;
|
|
847
909
|
}
|
|
@@ -851,32 +913,49 @@ function finalizeSegment(state) {
|
|
|
851
913
|
|
|
852
914
|
const invalidPathValue = 'Invalid path value';
|
|
853
915
|
|
|
854
|
-
|
|
855
|
-
|
|
916
|
+
/**
|
|
917
|
+
* Validates an A (arc-to) specific path command value.
|
|
918
|
+
* Usually a `large-arc-flag` or `sweep-flag`.
|
|
919
|
+
*
|
|
920
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
921
|
+
*/
|
|
922
|
+
function scanFlag(path) {
|
|
923
|
+
const { index } = path;
|
|
924
|
+
const ch = path.pathValue.charCodeAt(index);
|
|
856
925
|
|
|
857
926
|
if (ch === 0x30/* 0 */) {
|
|
858
|
-
|
|
859
|
-
|
|
927
|
+
path.param = 0;
|
|
928
|
+
path.index += 1;
|
|
860
929
|
return;
|
|
861
930
|
}
|
|
862
931
|
|
|
863
932
|
if (ch === 0x31/* 1 */) {
|
|
864
|
-
|
|
865
|
-
|
|
933
|
+
path.param = 1;
|
|
934
|
+
path.index += 1;
|
|
866
935
|
return;
|
|
867
936
|
}
|
|
868
937
|
|
|
869
|
-
|
|
870
|
-
state.err = `${invalidPathValue}: invalid Arc flag ${ch}`;
|
|
938
|
+
path.err = `${invalidPathValue}: invalid Arc flag ${ch}, expecting 0 or 1 at index ${index}`;
|
|
871
939
|
}
|
|
872
940
|
|
|
941
|
+
/**
|
|
942
|
+
* Checks if a character is a digit.
|
|
943
|
+
*
|
|
944
|
+
* @param {string} code the character to check
|
|
945
|
+
* @returns {boolean} check result
|
|
946
|
+
*/
|
|
873
947
|
function isDigit(code) {
|
|
874
948
|
return (code >= 48 && code <= 57); // 0..9
|
|
875
949
|
}
|
|
876
950
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
951
|
+
/**
|
|
952
|
+
* Validates every character of the path string,
|
|
953
|
+
* every path command, negative numbers or floating point numbers.
|
|
954
|
+
*
|
|
955
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
956
|
+
*/
|
|
957
|
+
function scanParam(path) {
|
|
958
|
+
const { max, pathValue, index: start } = path;
|
|
880
959
|
let index = start;
|
|
881
960
|
let zeroFirst = false;
|
|
882
961
|
let hasCeiling = false;
|
|
@@ -885,22 +964,22 @@ function scanParam(state) {
|
|
|
885
964
|
let ch;
|
|
886
965
|
|
|
887
966
|
if (index >= max) {
|
|
888
|
-
//
|
|
889
|
-
|
|
967
|
+
// path.err = 'SvgPath: missed param (at pos ' + index + ')';
|
|
968
|
+
path.err = `${invalidPathValue} at ${index}: missing param ${pathValue[index]}`;
|
|
890
969
|
return;
|
|
891
970
|
}
|
|
892
|
-
ch =
|
|
971
|
+
ch = pathValue.charCodeAt(index);
|
|
893
972
|
|
|
894
973
|
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
|
|
895
974
|
index += 1;
|
|
896
|
-
ch = (index < max) ?
|
|
975
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
897
976
|
}
|
|
898
977
|
|
|
899
978
|
// This logic is shamelessly borrowed from Esprima
|
|
900
979
|
// https://github.com/ariya/esprimas
|
|
901
980
|
if (!isDigit(ch) && ch !== 0x2E/* . */) {
|
|
902
|
-
//
|
|
903
|
-
|
|
981
|
+
// path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
|
|
982
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} is not a number`;
|
|
904
983
|
return;
|
|
905
984
|
}
|
|
906
985
|
|
|
@@ -908,79 +987,99 @@ function scanParam(state) {
|
|
|
908
987
|
zeroFirst = (ch === 0x30/* 0 */);
|
|
909
988
|
index += 1;
|
|
910
989
|
|
|
911
|
-
ch = (index < max) ?
|
|
990
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
912
991
|
|
|
913
992
|
if (zeroFirst && index < max) {
|
|
914
993
|
// decimal number starts with '0' such as '09' is illegal.
|
|
915
994
|
if (ch && isDigit(ch)) {
|
|
916
|
-
//
|
|
995
|
+
// path.err = 'SvgPath: numbers started with `0` such as `09`
|
|
917
996
|
// are illegal (at pos ' + start + ')';
|
|
918
|
-
|
|
997
|
+
path.err = `${invalidPathValue} at index ${start}: ${pathValue[start]} illegal number`;
|
|
919
998
|
return;
|
|
920
999
|
}
|
|
921
1000
|
}
|
|
922
1001
|
|
|
923
|
-
while (index < max && isDigit(
|
|
1002
|
+
while (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
924
1003
|
index += 1;
|
|
925
1004
|
hasCeiling = true;
|
|
926
1005
|
}
|
|
927
|
-
ch = (index < max) ?
|
|
1006
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
928
1007
|
}
|
|
929
1008
|
|
|
930
1009
|
if (ch === 0x2E/* . */) {
|
|
931
1010
|
hasDot = true;
|
|
932
1011
|
index += 1;
|
|
933
|
-
while (isDigit(
|
|
1012
|
+
while (isDigit(pathValue.charCodeAt(index))) {
|
|
934
1013
|
index += 1;
|
|
935
1014
|
hasDecimal = true;
|
|
936
1015
|
}
|
|
937
|
-
ch = (index < max) ?
|
|
1016
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
938
1017
|
}
|
|
939
1018
|
|
|
940
1019
|
if (ch === 0x65/* e */ || ch === 0x45/* E */) {
|
|
941
1020
|
if (hasDot && !hasCeiling && !hasDecimal) {
|
|
942
|
-
|
|
943
|
-
state.err = `${invalidPathValue}: ${state.pathValue[index]} invalid float exponent`;
|
|
1021
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
|
|
944
1022
|
return;
|
|
945
1023
|
}
|
|
946
1024
|
|
|
947
1025
|
index += 1;
|
|
948
1026
|
|
|
949
|
-
ch = (index < max) ?
|
|
1027
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
950
1028
|
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
|
|
951
1029
|
index += 1;
|
|
952
1030
|
}
|
|
953
|
-
if (index < max && isDigit(
|
|
954
|
-
while (index < max && isDigit(
|
|
1031
|
+
if (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
1032
|
+
while (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
955
1033
|
index += 1;
|
|
956
1034
|
}
|
|
957
1035
|
} else {
|
|
958
|
-
//
|
|
959
|
-
|
|
1036
|
+
// path.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
|
|
1037
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
|
|
960
1038
|
return;
|
|
961
1039
|
}
|
|
962
1040
|
}
|
|
963
1041
|
|
|
964
|
-
|
|
965
|
-
|
|
1042
|
+
path.index = index;
|
|
1043
|
+
path.param = +path.pathValue.slice(start, index);
|
|
966
1044
|
}
|
|
967
1045
|
|
|
968
|
-
|
|
1046
|
+
/**
|
|
1047
|
+
* Checks if the character is a space.
|
|
1048
|
+
*
|
|
1049
|
+
* @param {string} code the character to check
|
|
1050
|
+
* @returns {boolean} check result
|
|
1051
|
+
*/
|
|
1052
|
+
function isSpace(code) {
|
|
969
1053
|
const specialSpaces = [
|
|
970
1054
|
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
|
|
971
1055
|
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
|
|
972
|
-
|
|
1056
|
+
// Line terminators
|
|
1057
|
+
return (code === 0x0A) || (code === 0x0D) || (code === 0x2028) || (code === 0x2029)
|
|
973
1058
|
// White spaces
|
|
974
|
-
|| (
|
|
975
|
-
|| (
|
|
1059
|
+
|| (code === 0x20) || (code === 0x09) || (code === 0x0B) || (code === 0x0C) || (code === 0xA0)
|
|
1060
|
+
|| (code >= 0x1680 && specialSpaces.indexOf(code) >= 0);
|
|
976
1061
|
}
|
|
977
1062
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* Points the parser to the next character in the
|
|
1065
|
+
* path string every time it encounters any kind of
|
|
1066
|
+
* space character.
|
|
1067
|
+
*
|
|
1068
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
1069
|
+
*/
|
|
1070
|
+
function skipSpaces(path) {
|
|
1071
|
+
const { pathValue, max } = path;
|
|
1072
|
+
while (path.index < max && isSpace(pathValue.charCodeAt(path.index))) {
|
|
1073
|
+
path.index += 1;
|
|
981
1074
|
}
|
|
982
1075
|
}
|
|
983
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* Checks if the character is a path command.
|
|
1079
|
+
*
|
|
1080
|
+
* @param {string} code the character to check
|
|
1081
|
+
* @returns {boolean} check result
|
|
1082
|
+
*/
|
|
984
1083
|
function isPathCommand(code) {
|
|
985
1084
|
// eslint-disable-next-line no-bitwise -- Impossible to satisfy
|
|
986
1085
|
switch (code | 0x20) {
|
|
@@ -1001,6 +1100,13 @@ function isPathCommand(code) {
|
|
|
1001
1100
|
}
|
|
1002
1101
|
}
|
|
1003
1102
|
|
|
1103
|
+
/**
|
|
1104
|
+
* Checks if the character is or belongs to a number.
|
|
1105
|
+
* [0-9]|+|-|.
|
|
1106
|
+
*
|
|
1107
|
+
* @param {string} code the character to check
|
|
1108
|
+
* @returns {boolean} check result
|
|
1109
|
+
*/
|
|
1004
1110
|
function isDigitStart(code) {
|
|
1005
1111
|
return (code >= 48 && code <= 57) /* 0..9 */
|
|
1006
1112
|
|| code === 0x2B /* + */
|
|
@@ -1008,117 +1114,161 @@ function isDigitStart(code) {
|
|
|
1008
1114
|
|| code === 0x2E; /* . */
|
|
1009
1115
|
}
|
|
1010
1116
|
|
|
1117
|
+
/**
|
|
1118
|
+
* Checks if the character is an A (arc-to) path command.
|
|
1119
|
+
*
|
|
1120
|
+
* @param {string} code the character to check
|
|
1121
|
+
* @returns {boolean} check result
|
|
1122
|
+
*/
|
|
1011
1123
|
function isArcCommand(code) {
|
|
1012
1124
|
// eslint-disable-next-line no-bitwise -- Impossible to satisfy
|
|
1013
1125
|
return (code | 0x20) === 0x61;
|
|
1014
1126
|
}
|
|
1015
1127
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1128
|
+
/**
|
|
1129
|
+
* Scans every character in the path string to determine
|
|
1130
|
+
* where a segment starts and where it ends.
|
|
1131
|
+
*
|
|
1132
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
1133
|
+
*/
|
|
1134
|
+
function scanSegment(path) {
|
|
1135
|
+
const { max, pathValue, index } = path;
|
|
1136
|
+
const cmdCode = pathValue.charCodeAt(index);
|
|
1137
|
+
const reqParams = paramsCount[pathValue[index].toLowerCase()];
|
|
1020
1138
|
|
|
1021
|
-
|
|
1139
|
+
path.segmentStart = index;
|
|
1022
1140
|
|
|
1023
1141
|
if (!isPathCommand(cmdCode)) {
|
|
1024
|
-
|
|
1142
|
+
path.err = `${invalidPathValue}: ${pathValue[index]} not a path command`;
|
|
1025
1143
|
return;
|
|
1026
1144
|
}
|
|
1027
1145
|
|
|
1028
|
-
|
|
1029
|
-
skipSpaces(
|
|
1146
|
+
path.index += 1;
|
|
1147
|
+
skipSpaces(path);
|
|
1030
1148
|
|
|
1031
|
-
|
|
1149
|
+
path.data = [];
|
|
1032
1150
|
|
|
1033
1151
|
if (!reqParams) {
|
|
1034
1152
|
// Z
|
|
1035
|
-
finalizeSegment(
|
|
1153
|
+
finalizeSegment(path);
|
|
1036
1154
|
return;
|
|
1037
1155
|
}
|
|
1038
1156
|
|
|
1039
1157
|
for (;;) {
|
|
1040
1158
|
for (let i = reqParams; i > 0; i -= 1) {
|
|
1041
|
-
if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(
|
|
1042
|
-
else scanParam(
|
|
1159
|
+
if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(path);
|
|
1160
|
+
else scanParam(path);
|
|
1043
1161
|
|
|
1044
|
-
if (
|
|
1162
|
+
if (path.err.length) {
|
|
1045
1163
|
return;
|
|
1046
1164
|
}
|
|
1047
|
-
|
|
1165
|
+
path.data.push(path.param);
|
|
1048
1166
|
|
|
1049
|
-
skipSpaces(
|
|
1167
|
+
skipSpaces(path);
|
|
1050
1168
|
|
|
1051
1169
|
// after ',' param is mandatory
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1054
|
-
skipSpaces(
|
|
1170
|
+
if (path.index < max && pathValue.charCodeAt(path.index) === 0x2C/* , */) {
|
|
1171
|
+
path.index += 1;
|
|
1172
|
+
skipSpaces(path);
|
|
1055
1173
|
}
|
|
1056
1174
|
}
|
|
1057
1175
|
|
|
1058
|
-
if (
|
|
1176
|
+
if (path.index >= path.max) {
|
|
1059
1177
|
break;
|
|
1060
1178
|
}
|
|
1061
1179
|
|
|
1062
1180
|
// Stop on next segment
|
|
1063
|
-
if (!isDigitStart(
|
|
1181
|
+
if (!isDigitStart(pathValue.charCodeAt(path.index))) {
|
|
1064
1182
|
break;
|
|
1065
1183
|
}
|
|
1066
1184
|
}
|
|
1067
1185
|
|
|
1068
|
-
finalizeSegment(
|
|
1186
|
+
finalizeSegment(path);
|
|
1069
1187
|
}
|
|
1070
1188
|
|
|
1189
|
+
/**
|
|
1190
|
+
* The `parserPathArray` used by the parser.
|
|
1191
|
+
*
|
|
1192
|
+
* @param {string} pathString
|
|
1193
|
+
*/
|
|
1071
1194
|
function SVGPathArray(pathString) {
|
|
1195
|
+
/** @type {[string, ...number[]][]} */
|
|
1072
1196
|
this.segments = [];
|
|
1197
|
+
/** @type {string} */
|
|
1073
1198
|
this.pathValue = pathString;
|
|
1199
|
+
/** @type {number} */
|
|
1074
1200
|
this.max = pathString.length;
|
|
1201
|
+
/** @type {number} */
|
|
1075
1202
|
this.index = 0;
|
|
1203
|
+
/** @type {number} */
|
|
1076
1204
|
this.param = 0.0;
|
|
1205
|
+
/** @type {number} */
|
|
1077
1206
|
this.segmentStart = 0;
|
|
1207
|
+
/** @type {any} */
|
|
1078
1208
|
this.data = [];
|
|
1209
|
+
/** @type {string} */
|
|
1079
1210
|
this.err = '';
|
|
1080
1211
|
}
|
|
1081
1212
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1213
|
+
/**
|
|
1214
|
+
* Parses a path string value and returns an array
|
|
1215
|
+
* of segments we like to call `pathArray`.
|
|
1216
|
+
*
|
|
1217
|
+
* @param {string | SVGPC.pathArray} pathInput the string to be parsed
|
|
1218
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray`
|
|
1219
|
+
*/
|
|
1220
|
+
function parsePathString(pathInput) {
|
|
1221
|
+
if (isPathArray(pathInput)) {
|
|
1222
|
+
return clonePath(pathInput);
|
|
1086
1223
|
}
|
|
1087
1224
|
|
|
1088
|
-
const
|
|
1225
|
+
const path = new SVGPathArray(pathInput);
|
|
1089
1226
|
|
|
1090
|
-
skipSpaces(
|
|
1227
|
+
skipSpaces(path);
|
|
1091
1228
|
|
|
1092
|
-
while (
|
|
1093
|
-
scanSegment(
|
|
1229
|
+
while (path.index < path.max && !path.err.length) {
|
|
1230
|
+
scanSegment(path);
|
|
1094
1231
|
}
|
|
1095
1232
|
|
|
1096
|
-
if (
|
|
1097
|
-
|
|
1098
|
-
} else if (
|
|
1099
|
-
if ('mM'.indexOf(
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
state.segments = [];
|
|
1233
|
+
if (path.err.length) {
|
|
1234
|
+
path.segments = [];
|
|
1235
|
+
} else if (path.segments.length) {
|
|
1236
|
+
if ('mM'.indexOf(path.segments[0][0]) < 0) {
|
|
1237
|
+
path.err = `${invalidPathValue}: missing M/m`;
|
|
1238
|
+
path.segments = [];
|
|
1103
1239
|
} else {
|
|
1104
|
-
|
|
1240
|
+
path.segments[0][0] = 'M';
|
|
1105
1241
|
}
|
|
1106
1242
|
}
|
|
1107
1243
|
|
|
1108
|
-
return
|
|
1244
|
+
return path.segments;
|
|
1109
1245
|
}
|
|
1110
1246
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1247
|
+
/**
|
|
1248
|
+
* Iterates an array to check if it's a `pathArray`
|
|
1249
|
+
* with all absolute values.
|
|
1250
|
+
*
|
|
1251
|
+
* @param {SVGPC.pathArray} path the `pathArray` to be checked
|
|
1252
|
+
* @returns {boolean} iteration result
|
|
1253
|
+
*/
|
|
1254
|
+
function isAbsoluteArray(path) {
|
|
1255
|
+
return isPathArray(path) && path.every((x) => x[0] === x[0].toUpperCase());
|
|
1113
1256
|
}
|
|
1114
1257
|
|
|
1258
|
+
/**
|
|
1259
|
+
* Parses a path string value or object and returns an array
|
|
1260
|
+
* of segments, all converted to absolute values.
|
|
1261
|
+
*
|
|
1262
|
+
* @param {string | SVGPC.pathArray} pathInput the path string | object
|
|
1263
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with absolute values
|
|
1264
|
+
*/
|
|
1115
1265
|
function pathToAbsolute(pathInput) {
|
|
1116
1266
|
if (isAbsoluteArray(pathInput)) {
|
|
1117
1267
|
return clonePath(pathInput);
|
|
1118
1268
|
}
|
|
1119
1269
|
|
|
1120
|
-
const
|
|
1121
|
-
const ii =
|
|
1270
|
+
const path = parsePathString(pathInput);
|
|
1271
|
+
const ii = path.length;
|
|
1122
1272
|
const resultArray = [];
|
|
1123
1273
|
let x = 0;
|
|
1124
1274
|
let y = 0;
|
|
@@ -1126,9 +1276,9 @@ function pathToAbsolute(pathInput) {
|
|
|
1126
1276
|
let my = 0;
|
|
1127
1277
|
let start = 0;
|
|
1128
1278
|
|
|
1129
|
-
if (
|
|
1130
|
-
x = +
|
|
1131
|
-
y = +
|
|
1279
|
+
if (path[0][0] === 'M') {
|
|
1280
|
+
x = +path[0][1];
|
|
1281
|
+
y = +path[0][2];
|
|
1132
1282
|
mx = x;
|
|
1133
1283
|
my = y;
|
|
1134
1284
|
start += 1;
|
|
@@ -1136,7 +1286,7 @@ function pathToAbsolute(pathInput) {
|
|
|
1136
1286
|
}
|
|
1137
1287
|
|
|
1138
1288
|
for (let i = start; i < ii; i += 1) {
|
|
1139
|
-
const segment =
|
|
1289
|
+
const segment = path[i];
|
|
1140
1290
|
const [pathCommand] = segment;
|
|
1141
1291
|
const absCommand = pathCommand.toUpperCase();
|
|
1142
1292
|
const absoluteSegment = [];
|
|
@@ -1201,20 +1351,48 @@ function pathToAbsolute(pathInput) {
|
|
|
1201
1351
|
return resultArray;
|
|
1202
1352
|
}
|
|
1203
1353
|
|
|
1204
|
-
|
|
1354
|
+
/**
|
|
1355
|
+
* Returns the missing control point from an
|
|
1356
|
+
* T (shorthand quadratic bezier) segment.
|
|
1357
|
+
*
|
|
1358
|
+
* @param {Number} x1 curve start x
|
|
1359
|
+
* @param {Number} y1 curve start y
|
|
1360
|
+
* @param {Number} qx control point x
|
|
1361
|
+
* @param {Number} qy control point y
|
|
1362
|
+
* @param {String} prevCommand the previous path command
|
|
1363
|
+
* @returns {Object} the missing control point
|
|
1364
|
+
*/
|
|
1205
1365
|
function shorthandToQuad(x1, y1, qx, qy, prevCommand) {
|
|
1206
1366
|
return 'QT'.indexOf(prevCommand) > -1
|
|
1207
1367
|
? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
|
|
1208
1368
|
: { qx: x1, qy: y1 };
|
|
1209
1369
|
}
|
|
1210
1370
|
|
|
1211
|
-
|
|
1371
|
+
/**
|
|
1372
|
+
* Returns the missing control point from an
|
|
1373
|
+
* S (shorthand cubic bezier) segment.
|
|
1374
|
+
*
|
|
1375
|
+
* @param {Number} x1 curve start x
|
|
1376
|
+
* @param {Number} y1 curve start y
|
|
1377
|
+
* @param {Number} x2 curve end x
|
|
1378
|
+
* @param {Number} y2 curve end y
|
|
1379
|
+
* @param {String} prevCommand the previous path command
|
|
1380
|
+
* @returns {Object} the missing control point
|
|
1381
|
+
*/
|
|
1212
1382
|
function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
|
|
1213
1383
|
return 'CS'.indexOf(prevCommand) > -1
|
|
1214
1384
|
? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
|
|
1215
1385
|
: { x1, y1 };
|
|
1216
1386
|
}
|
|
1217
1387
|
|
|
1388
|
+
/**
|
|
1389
|
+
* Normalizes a single segment of a `pathArray` object.
|
|
1390
|
+
*
|
|
1391
|
+
* @param {SVGPC.pathSegment} segment the segment object
|
|
1392
|
+
* @param {Object} params the coordinates of the previous segment
|
|
1393
|
+
* @param {String} prevCommand the path command of the previous segment
|
|
1394
|
+
* @returns {SVGPC.pathSegment} the normalized segment
|
|
1395
|
+
*/
|
|
1218
1396
|
function normalizeSegment(segment, params, prevCommand) {
|
|
1219
1397
|
const [pathCommand] = segment;
|
|
1220
1398
|
const xy = segment.slice(1);
|
|
@@ -1248,40 +1426,56 @@ function normalizeSegment(segment, params, prevCommand) {
|
|
|
1248
1426
|
return result;
|
|
1249
1427
|
}
|
|
1250
1428
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1429
|
+
/**
|
|
1430
|
+
* Iterates an array to check if it's a `pathArray`
|
|
1431
|
+
* with all segments are in non-shorthand notation
|
|
1432
|
+
* with absolute values.
|
|
1433
|
+
*
|
|
1434
|
+
* @param {SVGPC.pathArray} path the `pathArray` to be checked
|
|
1435
|
+
* @returns {boolean} iteration result
|
|
1436
|
+
*/
|
|
1437
|
+
function isNormalizedArray(path) {
|
|
1438
|
+
return isPathArray(path) && path.every((seg) => {
|
|
1439
|
+
const lk = seg[0].toLowerCase();
|
|
1440
|
+
return paramsCount[lk] === seg.length - 1 && ('ACLMQZ').includes(seg[0]); // achlmqstvz
|
|
1255
1441
|
});
|
|
1256
1442
|
}
|
|
1257
1443
|
|
|
1258
|
-
|
|
1444
|
+
/**
|
|
1445
|
+
* Normalizes a `path` object for further processing:
|
|
1446
|
+
* * convert segments to absolute values
|
|
1447
|
+
* * convert shorthand path commands to their non-shorthand notation
|
|
1448
|
+
*
|
|
1449
|
+
* @param {String | SVGPC.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
1450
|
+
* @returns {SVGPC.pathArray} the normalized `pathArray`
|
|
1451
|
+
*/
|
|
1452
|
+
function normalizePath(pathInput) { // path|pathString
|
|
1259
1453
|
if (isNormalizedArray(pathInput)) {
|
|
1260
1454
|
return clonePath(pathInput);
|
|
1261
1455
|
}
|
|
1262
1456
|
|
|
1263
|
-
const
|
|
1457
|
+
const path = pathToAbsolute(pathInput);
|
|
1264
1458
|
const params = {
|
|
1265
1459
|
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
1266
1460
|
};
|
|
1267
1461
|
const allPathCommands = [];
|
|
1268
|
-
const ii =
|
|
1462
|
+
const ii = path.length;
|
|
1269
1463
|
let prevCommand = '';
|
|
1270
1464
|
let segment;
|
|
1271
1465
|
let seglen;
|
|
1272
1466
|
|
|
1273
1467
|
for (let i = 0; i < ii; i += 1) {
|
|
1274
1468
|
// save current path command
|
|
1275
|
-
const [pathCommand] =
|
|
1469
|
+
const [pathCommand] = path[i];
|
|
1276
1470
|
|
|
1277
1471
|
// Save current path command
|
|
1278
1472
|
allPathCommands[i] = pathCommand;
|
|
1279
1473
|
// Get previous path command
|
|
1280
1474
|
if (i) prevCommand = allPathCommands[i - 1];
|
|
1281
1475
|
// Previous path command is inputted to processSegment
|
|
1282
|
-
|
|
1476
|
+
path[i] = normalizeSegment(path[i], params, prevCommand);
|
|
1283
1477
|
|
|
1284
|
-
segment =
|
|
1478
|
+
segment = path[i];
|
|
1285
1479
|
seglen = segment.length;
|
|
1286
1480
|
|
|
1287
1481
|
params.x1 = +segment[seglen - 2];
|
|
@@ -1289,19 +1483,41 @@ function normalizePath(pathInput) { // pathArray|pathString
|
|
|
1289
1483
|
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
1290
1484
|
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
1291
1485
|
}
|
|
1292
|
-
return
|
|
1486
|
+
return path;
|
|
1293
1487
|
}
|
|
1294
1488
|
|
|
1489
|
+
/**
|
|
1490
|
+
* Returns an {x,y} vector rotated by a given
|
|
1491
|
+
* angle in radian.
|
|
1492
|
+
*
|
|
1493
|
+
* @param {Number} x the initial vector x
|
|
1494
|
+
* @param {Number} y the initial vector y
|
|
1495
|
+
* @returns {{x: number, y: number}} the rotated vector
|
|
1496
|
+
*/
|
|
1295
1497
|
function rotateVector(x, y, rad) {
|
|
1296
1498
|
const X = x * Math.cos(rad) - y * Math.sin(rad);
|
|
1297
1499
|
const Y = x * Math.sin(rad) + y * Math.cos(rad);
|
|
1298
1500
|
return { x: X, y: Y };
|
|
1299
1501
|
}
|
|
1300
1502
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1503
|
+
/**
|
|
1504
|
+
* Converts A (arc-to) segments to C (cubic-bezier-to).
|
|
1505
|
+
*
|
|
1506
|
+
* For more information of where this math came from visit:
|
|
1507
|
+
* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
|
1508
|
+
*
|
|
1509
|
+
* @param {Number} x1 the starting x position
|
|
1510
|
+
* @param {Number} y1 the starting y position
|
|
1511
|
+
* @param {Number} rx x-radius of the arc
|
|
1512
|
+
* @param {Number} ry y-radius of the arc
|
|
1513
|
+
* @param {Number} angle x-axis-rotation of the arc
|
|
1514
|
+
* @param {Number} LAF large-arc-flag of the arc
|
|
1515
|
+
* @param {Number} SF sweep-flag of the arc
|
|
1516
|
+
* @param {Number} x2 the ending x position
|
|
1517
|
+
* @param {Number} y2 the ending y position
|
|
1518
|
+
* @param {Number[] | null} recursive the parameters needed to split arc into 2 segments
|
|
1519
|
+
* @return {Number[] | Number[][]} the resulting cubic-bezier segment(s)
|
|
1520
|
+
*/
|
|
1305
1521
|
function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
|
|
1306
1522
|
const d120 = (Math.PI * 120) / 180;
|
|
1307
1523
|
const rad = (Math.PI / 180) * (angle || 0);
|
|
@@ -1408,6 +1624,17 @@ function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
|
|
|
1408
1624
|
});
|
|
1409
1625
|
}
|
|
1410
1626
|
|
|
1627
|
+
/**
|
|
1628
|
+
* Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
|
|
1629
|
+
*
|
|
1630
|
+
* @param {Number} x1 curve start x
|
|
1631
|
+
* @param {Number} y1 curve start y
|
|
1632
|
+
* @param {Number} qx control point x
|
|
1633
|
+
* @param {Number} qy control point y
|
|
1634
|
+
* @param {Number} x2 curve end x
|
|
1635
|
+
* @param {Number} y2 curve end y
|
|
1636
|
+
* @returns {Number[]} the cubic-bezier segment
|
|
1637
|
+
*/
|
|
1411
1638
|
function quadToCubic(x1, y1, qx, qy, x2, y2) {
|
|
1412
1639
|
const r13 = 1 / 3;
|
|
1413
1640
|
const r23 = 2 / 3;
|
|
@@ -1420,7 +1647,21 @@ function quadToCubic(x1, y1, qx, qy, x2, y2) {
|
|
|
1420
1647
|
];
|
|
1421
1648
|
}
|
|
1422
1649
|
|
|
1423
|
-
|
|
1650
|
+
/**
|
|
1651
|
+
* Returns the {x,y} coordinates of a point at a
|
|
1652
|
+
* given length of a cubic-bezier segment.
|
|
1653
|
+
*
|
|
1654
|
+
* @param {Number} p1x the starting point X
|
|
1655
|
+
* @param {Number} p1y the starting point Y
|
|
1656
|
+
* @param {Number} c1x the first control point X
|
|
1657
|
+
* @param {Number} c1y the first control point Y
|
|
1658
|
+
* @param {Number} c2x the second control point X
|
|
1659
|
+
* @param {Number} c2y the second control point Y
|
|
1660
|
+
* @param {Number} px2 the ending point X
|
|
1661
|
+
* @param {Number} py2 the ending point Y
|
|
1662
|
+
* @param {Number} t a [0-1] ratio
|
|
1663
|
+
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
1664
|
+
*/
|
|
1424
1665
|
function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
1425
1666
|
const t1 = 1 - t;
|
|
1426
1667
|
return {
|
|
@@ -1435,14 +1676,29 @@ function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
|
1435
1676
|
};
|
|
1436
1677
|
}
|
|
1437
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* Returns the coordinates of a specified distance
|
|
1681
|
+
* ratio between two points.
|
|
1682
|
+
*
|
|
1683
|
+
* @param {Number[]} a the first point coordinates
|
|
1684
|
+
* @param {Number[]} b the second point coordinates
|
|
1685
|
+
* @param {Number} t the ratio
|
|
1686
|
+
* @returns {Number[]} the midpoint coordinates
|
|
1687
|
+
*/
|
|
1438
1688
|
function midPoint(a, b, t) {
|
|
1439
|
-
const ax = a[
|
|
1440
|
-
const ay = a[1];
|
|
1441
|
-
const bx = b[0];
|
|
1442
|
-
const by = b[1];
|
|
1689
|
+
const [ax, ay] = a; const [bx, by] = b;
|
|
1443
1690
|
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
|
1444
1691
|
}
|
|
1445
1692
|
|
|
1693
|
+
/**
|
|
1694
|
+
* Converts an L (line-to) segment to C (cubic-bezier).
|
|
1695
|
+
*
|
|
1696
|
+
* @param {Number} x1 line start x
|
|
1697
|
+
* @param {Number} y1 line start y
|
|
1698
|
+
* @param {Number} x2 line end x
|
|
1699
|
+
* @param {Number} y2 line end y
|
|
1700
|
+
* @returns {Number[]} the cubic-bezier segment
|
|
1701
|
+
*/
|
|
1446
1702
|
function lineToCubic(x1, y1, x2, y2) {
|
|
1447
1703
|
const t = 0.5;
|
|
1448
1704
|
const p0 = [x1, y1];
|
|
@@ -1458,6 +1714,13 @@ function lineToCubic(x1, y1, x2, y2) {
|
|
|
1458
1714
|
return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
|
|
1459
1715
|
}
|
|
1460
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* Converts any segment to C (cubic-bezier).
|
|
1719
|
+
*
|
|
1720
|
+
* @param {SVGPC.pathSegment} segment the source segment
|
|
1721
|
+
* @param {Object.<string, number>} params the source segment parameters
|
|
1722
|
+
* @returns {SVGPC.pathSegment} the cubic-bezier segment
|
|
1723
|
+
*/
|
|
1461
1724
|
function segmentToCubic(segment, params) {
|
|
1462
1725
|
if ('TQ'.indexOf(segment[0]) < 0) {
|
|
1463
1726
|
params.qx = null;
|
|
@@ -1485,32 +1748,36 @@ function segmentToCubic(segment, params) {
|
|
|
1485
1748
|
return segment;
|
|
1486
1749
|
}
|
|
1487
1750
|
|
|
1488
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* Parses a path string value or 'pathArray' and returns a new one
|
|
1753
|
+
* in which all segments are converted to cubic-bezier.
|
|
1754
|
+
*
|
|
1755
|
+
* @param {String | SVGPC.pathArray} pathInput the string to be parsed or object
|
|
1756
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` converted to cubic-bezier
|
|
1757
|
+
*/
|
|
1758
|
+
function pathToCurve(pathInput) {
|
|
1489
1759
|
if (isCurveArray(pathInput)) {
|
|
1490
1760
|
return clonePath(pathInput);
|
|
1491
1761
|
}
|
|
1492
1762
|
|
|
1493
|
-
const
|
|
1763
|
+
const path = normalizePath(pathInput);
|
|
1494
1764
|
const params = {
|
|
1495
1765
|
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
1496
1766
|
};
|
|
1497
1767
|
const allPathCommands = [];
|
|
1498
1768
|
let pathCommand = '';
|
|
1499
|
-
let ii =
|
|
1500
|
-
let segment;
|
|
1501
|
-
let seglen;
|
|
1769
|
+
let ii = path.length;
|
|
1502
1770
|
|
|
1503
1771
|
for (let i = 0; i < ii; i += 1) {
|
|
1504
|
-
|
|
1772
|
+
const segment = path[i];
|
|
1773
|
+
const seglen = segment.length;
|
|
1774
|
+
if (segment) [pathCommand] = segment;
|
|
1505
1775
|
|
|
1506
1776
|
allPathCommands[i] = pathCommand;
|
|
1507
|
-
|
|
1777
|
+
path[i] = segmentToCubic(segment, params);
|
|
1508
1778
|
|
|
1509
|
-
fixArc(
|
|
1510
|
-
ii =
|
|
1511
|
-
|
|
1512
|
-
segment = pathArray[i];
|
|
1513
|
-
seglen = segment.length;
|
|
1779
|
+
fixArc(path, allPathCommands, i);
|
|
1780
|
+
ii = path.length; // solves curveArrays ending in Z
|
|
1514
1781
|
|
|
1515
1782
|
params.x1 = +segment[seglen - 2];
|
|
1516
1783
|
params.y1 = +segment[seglen - 1];
|
|
@@ -1518,22 +1785,44 @@ function pathToCurve(pathInput) { // pathArray|pathString
|
|
|
1518
1785
|
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
1519
1786
|
}
|
|
1520
1787
|
|
|
1521
|
-
return
|
|
1788
|
+
return path;
|
|
1522
1789
|
}
|
|
1523
1790
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1791
|
+
/**
|
|
1792
|
+
* Returns the area of a single segment shape.
|
|
1793
|
+
*
|
|
1794
|
+
* http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
|
|
1795
|
+
*
|
|
1796
|
+
* @param {number} x0 the starting point X
|
|
1797
|
+
* @param {number} y0 the starting point Y
|
|
1798
|
+
* @param {number} x1 the first control point X
|
|
1799
|
+
* @param {number} y1 the first control point Y
|
|
1800
|
+
* @param {number} x2 the second control point X
|
|
1801
|
+
* @param {number} y2 the second control point Y
|
|
1802
|
+
* @param {number} x3 the ending point X
|
|
1803
|
+
* @param {number} y3 the ending point Y
|
|
1804
|
+
* @returns {number} the area of the cubic-bezier segment
|
|
1805
|
+
*/
|
|
1526
1806
|
function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
1527
|
-
// http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
|
|
1528
1807
|
return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
|
|
1529
1808
|
+ (y1 * (x0 - x2)) - (x1 * (y0 - y2))
|
|
1530
1809
|
+ (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
|
|
1531
1810
|
}
|
|
1532
1811
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1812
|
+
/**
|
|
1813
|
+
* Returns the area of a shape.
|
|
1814
|
+
* @author Jürg Lehni & Jonathan Puckey
|
|
1815
|
+
*
|
|
1816
|
+
* => https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
|
|
1817
|
+
*
|
|
1818
|
+
* @param {SVGPC.pathArray} path the shape `pathArray`
|
|
1819
|
+
* @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
|
|
1820
|
+
*/
|
|
1821
|
+
function getPathArea(path) {
|
|
1822
|
+
let x = 0; let y = 0;
|
|
1823
|
+
let mx = 0; let my = 0;
|
|
1824
|
+
let len = 0;
|
|
1825
|
+
return pathToCurve(path).map((seg) => {
|
|
1537
1826
|
switch (seg[0]) {
|
|
1538
1827
|
case 'M':
|
|
1539
1828
|
case 'Z':
|
|
@@ -1556,7 +1845,20 @@ function base3(p1, p2, p3, p4, t) {
|
|
|
1556
1845
|
return t * t2 - 3 * p1 + 3 * p2;
|
|
1557
1846
|
}
|
|
1558
1847
|
|
|
1559
|
-
|
|
1848
|
+
/**
|
|
1849
|
+
* Returns the C (cubic-bezier) segment length.
|
|
1850
|
+
*
|
|
1851
|
+
* @param {Number} x1 the starting point X
|
|
1852
|
+
* @param {Number} y1 the starting point Y
|
|
1853
|
+
* @param {Number} x2 the first control point X
|
|
1854
|
+
* @param {Number} y2 the first control point Y
|
|
1855
|
+
* @param {Number} x3 the second control point X
|
|
1856
|
+
* @param {Number} y3 the second control point Y
|
|
1857
|
+
* @param {Number} x4 the ending point X
|
|
1858
|
+
* @param {Number} y4 the ending point Y
|
|
1859
|
+
* @param {Number} z a [0-1] ratio
|
|
1860
|
+
* @returns {Number} the cubic-bezier segment length
|
|
1861
|
+
*/
|
|
1560
1862
|
function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
|
|
1561
1863
|
let Z;
|
|
1562
1864
|
if (z === null || Number.isNaN(+z)) Z = 1;
|
|
@@ -1582,30 +1884,48 @@ function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
|
|
|
1582
1884
|
return z2 * sum;
|
|
1583
1885
|
}
|
|
1584
1886
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1887
|
+
/**
|
|
1888
|
+
* Returns the shape total length,
|
|
1889
|
+
* or the equivalent to `shape.getTotalLength()`
|
|
1890
|
+
* pathToCurve version
|
|
1891
|
+
*
|
|
1892
|
+
* @param {SVGPC.pathArray} path the ending point Y
|
|
1893
|
+
* @returns {Number} the shape total length
|
|
1894
|
+
*/
|
|
1895
|
+
function getPathLength(path) {
|
|
1589
1896
|
let totalLength = 0;
|
|
1590
|
-
pathToCurve(
|
|
1591
|
-
totalLength += s[0]
|
|
1897
|
+
pathToCurve(path).forEach((s, i, curveArray) => {
|
|
1898
|
+
totalLength += s[0] === 'M' ? 0
|
|
1899
|
+
: getSegCubicLength.apply(0, curveArray[i - 1].slice(-2).concat(s.slice(1)));
|
|
1592
1900
|
});
|
|
1593
1901
|
return totalLength;
|
|
1594
1902
|
}
|
|
1595
1903
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1904
|
+
/**
|
|
1905
|
+
* Check if a path is drawn clockwise and returns true if so,
|
|
1906
|
+
* false otherwise.
|
|
1907
|
+
*
|
|
1908
|
+
* @param {string | SVGPC.pathArray} path the path string or `pathArray`
|
|
1909
|
+
* @returns {boolean} true when clockwise or false if not
|
|
1910
|
+
*/
|
|
1911
|
+
function getDrawDirection(path) {
|
|
1912
|
+
return getPathArea(pathToCurve(path)) >= 0;
|
|
1598
1913
|
}
|
|
1599
1914
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1915
|
+
/**
|
|
1916
|
+
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
1917
|
+
*
|
|
1918
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to look into
|
|
1919
|
+
* @param {Number} length the length of the shape to look at
|
|
1920
|
+
* @returns {Number[]} the requested [x,y] coordinates
|
|
1921
|
+
*/
|
|
1922
|
+
function getPointAtLength(path, length) {
|
|
1603
1923
|
let totalLength = 0;
|
|
1604
1924
|
let segLen;
|
|
1605
1925
|
let data;
|
|
1606
1926
|
let result;
|
|
1607
1927
|
|
|
1608
|
-
return pathToCurve(
|
|
1928
|
+
return pathToCurve(path).map((seg, i, curveArray) => {
|
|
1609
1929
|
data = i ? curveArray[i - 1].slice(-2).concat(seg.slice(1)) : seg.slice(1);
|
|
1610
1930
|
segLen = i ? getSegCubicLength.apply(0, data) : 0;
|
|
1611
1931
|
totalLength += segLen;
|
|
@@ -1622,7 +1942,19 @@ function getPointAtLength(pathArray, length) {
|
|
|
1622
1942
|
}).filter((x) => x).slice(-1)[0]; // isolate last segment
|
|
1623
1943
|
}
|
|
1624
1944
|
|
|
1625
|
-
|
|
1945
|
+
/**
|
|
1946
|
+
* Returns the cubic-bezier segment length.
|
|
1947
|
+
*
|
|
1948
|
+
* @param {number} p1x the starting point X
|
|
1949
|
+
* @param {number} p1y the starting point Y
|
|
1950
|
+
* @param {number} c1x the first control point X
|
|
1951
|
+
* @param {number} c1y the first control point Y
|
|
1952
|
+
* @param {number} c2x the second control point X
|
|
1953
|
+
* @param {number} c2y the second control point Y
|
|
1954
|
+
* @param {number} p2x the ending point X
|
|
1955
|
+
* @param {number} p2y the ending point Y
|
|
1956
|
+
* @returns {SVGPC.segmentLimits} the length of the cubic-bezier segment
|
|
1957
|
+
*/
|
|
1626
1958
|
function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
|
1627
1959
|
let a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
|
|
1628
1960
|
let b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
|
|
@@ -1671,13 +2003,19 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
|
|
1671
2003
|
};
|
|
1672
2004
|
}
|
|
1673
2005
|
|
|
1674
|
-
|
|
1675
|
-
|
|
2006
|
+
/**
|
|
2007
|
+
* Returns the bounding box of a shape.
|
|
2008
|
+
*
|
|
2009
|
+
* @param {SVGPC.pathArray} path the shape `pathArray`
|
|
2010
|
+
* @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
|
|
2011
|
+
*/
|
|
2012
|
+
function getPathBBox(path) {
|
|
2013
|
+
if (!path) {
|
|
1676
2014
|
return {
|
|
1677
2015
|
x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0,
|
|
1678
2016
|
};
|
|
1679
2017
|
}
|
|
1680
|
-
const pathCurve = pathToCurve(
|
|
2018
|
+
const pathCurve = pathToCurve(path);
|
|
1681
2019
|
|
|
1682
2020
|
let x = 0;
|
|
1683
2021
|
let y = 0;
|
|
@@ -1708,28 +2046,67 @@ function getPathBBox(pathArray) {
|
|
|
1708
2046
|
const height = yBot - yTop;
|
|
1709
2047
|
|
|
1710
2048
|
return {
|
|
2049
|
+
width,
|
|
2050
|
+
height,
|
|
1711
2051
|
x: xTop,
|
|
1712
2052
|
y: yTop,
|
|
1713
2053
|
x2: xBot,
|
|
1714
2054
|
y2: yBot,
|
|
1715
|
-
width,
|
|
1716
|
-
height,
|
|
1717
2055
|
cx: xTop + width / 2,
|
|
1718
2056
|
cy: yTop + height / 2,
|
|
1719
2057
|
};
|
|
1720
2058
|
}
|
|
1721
2059
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2060
|
+
/**
|
|
2061
|
+
* Parses a path string value to determine its validity
|
|
2062
|
+
* then returns true if it's valid or false otherwise.
|
|
2063
|
+
*
|
|
2064
|
+
* @param {string} pathString the path string to be parsed
|
|
2065
|
+
* @returns {boolean} the path string validity
|
|
2066
|
+
*/
|
|
2067
|
+
function isValidPath(pathString) {
|
|
2068
|
+
if (typeof pathString !== 'string') {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
const path = new SVGPathArray(pathString);
|
|
2073
|
+
|
|
2074
|
+
skipSpaces(path);
|
|
2075
|
+
|
|
2076
|
+
while (path.index < path.max && !path.err.length) {
|
|
2077
|
+
scanSegment(path);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
return !path.err.length && 'mM'.includes(path.segments[0][0]);
|
|
1725
2081
|
}
|
|
1726
2082
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
2083
|
+
/**
|
|
2084
|
+
* Iterates an array to check if it's a `pathArray`
|
|
2085
|
+
* with relative values.
|
|
2086
|
+
*
|
|
2087
|
+
* @param {SVGPC.pathArray} path the `pathArray` to be checked
|
|
2088
|
+
* @returns {boolean} iteration result
|
|
2089
|
+
*/
|
|
2090
|
+
function isRelativeArray(path) {
|
|
2091
|
+
return isPathArray(path)
|
|
2092
|
+
&& path.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Rounds the values of a `pathArray` instance to
|
|
2097
|
+
* a specified amount of decimals and returns it.
|
|
2098
|
+
*
|
|
2099
|
+
* @param {SVGPC.pathArray} path the source `pathArray`
|
|
2100
|
+
* @param {Number | null} round the amount of decimals to round numbers to
|
|
2101
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with rounded values
|
|
2102
|
+
*/
|
|
2103
|
+
function roundPath(path, round) {
|
|
2104
|
+
const { round: defaultRound, decimals: defaultDecimals } = SVGPCO;
|
|
2105
|
+
const decimalsOption = !Number.isNaN(+round) ? +round : defaultRound && defaultDecimals;
|
|
1730
2106
|
|
|
1731
|
-
|
|
1732
|
-
|
|
2107
|
+
return !decimalsOption
|
|
2108
|
+
? clonePath(path)
|
|
2109
|
+
: path.map((seg) => seg.map((c) => {
|
|
1733
2110
|
const nr = +c;
|
|
1734
2111
|
const dc = 10 ** decimalsOption;
|
|
1735
2112
|
if (nr) {
|
|
@@ -1737,17 +2114,198 @@ function roundPath(pathArray, round) {
|
|
|
1737
2114
|
}
|
|
1738
2115
|
return c;
|
|
1739
2116
|
}));
|
|
1740
|
-
} else {
|
|
1741
|
-
result = clonePath(pathArray);
|
|
1742
|
-
}
|
|
1743
|
-
return result;
|
|
1744
2117
|
}
|
|
1745
2118
|
|
|
1746
|
-
|
|
1747
|
-
|
|
2119
|
+
/**
|
|
2120
|
+
* Returns a valid `d` attribute string value created
|
|
2121
|
+
* by rounding values and concatenating the `pathArray` segments.
|
|
2122
|
+
*
|
|
2123
|
+
* @param {SVGPC.pathArray} path the `pathArray` object
|
|
2124
|
+
* @param {Number} round amount of decimals to round values to
|
|
2125
|
+
* @returns {String} the concatenated path string
|
|
2126
|
+
*/
|
|
2127
|
+
function pathToString(path, round) {
|
|
2128
|
+
return roundPath(path, round)
|
|
1748
2129
|
.map((x) => x[0].concat(x.slice(1).join(' '))).join('');
|
|
1749
2130
|
}
|
|
1750
2131
|
|
|
2132
|
+
/**
|
|
2133
|
+
* Supported shapes and their specific parameters.
|
|
2134
|
+
*
|
|
2135
|
+
* @type {object}
|
|
2136
|
+
*/
|
|
2137
|
+
const shapeParams = {
|
|
2138
|
+
circle: ['cx', 'cy', 'r'],
|
|
2139
|
+
ellipse: ['cx', 'cy', 'rx', 'ry'],
|
|
2140
|
+
rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
|
|
2141
|
+
polygon: ['points'],
|
|
2142
|
+
polyline: ['points'],
|
|
2143
|
+
glyph: [],
|
|
2144
|
+
};
|
|
2145
|
+
|
|
2146
|
+
/**
|
|
2147
|
+
* Returns a new `pathArray` from line attributes.
|
|
2148
|
+
*
|
|
2149
|
+
* @param {SVGPC.lineAttr} attr shape configuration
|
|
2150
|
+
* @return {SVGPC.pathArray} a new line `pathArray`
|
|
2151
|
+
*/
|
|
2152
|
+
function getLinePath(attr) {
|
|
2153
|
+
const {
|
|
2154
|
+
x1, y1, x2, y2,
|
|
2155
|
+
} = attr;
|
|
2156
|
+
return [['M', +x1, +y1], ['L', +x2, +y2]];
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/**
|
|
2160
|
+
* Returns a new `pathArray` like from polyline/polygon attributes.
|
|
2161
|
+
*
|
|
2162
|
+
* @param {SVGPC.polyAttr} attr shape configuration
|
|
2163
|
+
* @return {SVGPC.pathArray} a new polygon/polyline `pathArray`
|
|
2164
|
+
*/
|
|
2165
|
+
function getPolyPath(attr) {
|
|
2166
|
+
const pathArray = [];
|
|
2167
|
+
let { points } = attr;
|
|
2168
|
+
|
|
2169
|
+
points = points.split(/[\s|,]/).map(Number);
|
|
2170
|
+
|
|
2171
|
+
let index = 0;
|
|
2172
|
+
while (index < points.length) {
|
|
2173
|
+
pathArray.push([(index ? 'L' : 'M'), (points[index]), (points[index + 1])]);
|
|
2174
|
+
index += 2;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
return attr.type === 'polygon' ? pathArray.concat([['z']]) : pathArray;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* Returns a new `pathArray` from circle/ellipse attributes.
|
|
2182
|
+
*
|
|
2183
|
+
* @param {SVGPC.ellipseAttr | SVGPC.circleAttr} attr shape configuration
|
|
2184
|
+
* @return {SVGPC.pathArray} a circle/ellipse `pathArray`
|
|
2185
|
+
*/
|
|
2186
|
+
function getEllipsePath(attr) {
|
|
2187
|
+
const {
|
|
2188
|
+
type, cx, cy, r,
|
|
2189
|
+
} = attr;
|
|
2190
|
+
let { rx, ry } = attr;
|
|
2191
|
+
|
|
2192
|
+
if (type === 'circle' && r > 0) {
|
|
2193
|
+
rx = r;
|
|
2194
|
+
ry = r;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
return [
|
|
2198
|
+
['M', (cx - rx), cy],
|
|
2199
|
+
['a', rx, ry, 0, 1, 0, (2 * rx), 0],
|
|
2200
|
+
['a', rx, ry, 0, 1, 0, (-2 * rx), 0],
|
|
2201
|
+
];
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
/**
|
|
2205
|
+
* Returns a new `pathArray` like from rect attributes.
|
|
2206
|
+
*
|
|
2207
|
+
* @param {SVGPC.rectAttr} attr object with properties above
|
|
2208
|
+
* @return {SVGPC.pathArray} a new `pathArray` from `<rect>` attributes
|
|
2209
|
+
*/
|
|
2210
|
+
function getRectanglePath(attr) {
|
|
2211
|
+
const x = +attr.x || 0;
|
|
2212
|
+
const y = +attr.y || 0;
|
|
2213
|
+
const w = +attr.width;
|
|
2214
|
+
const h = +attr.height;
|
|
2215
|
+
let rx = +attr.rx;
|
|
2216
|
+
let ry = +attr.ry;
|
|
2217
|
+
|
|
2218
|
+
// Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
|
|
2219
|
+
if (rx || ry) {
|
|
2220
|
+
rx = !rx ? ry : rx;
|
|
2221
|
+
ry = !ry ? rx : ry;
|
|
2222
|
+
|
|
2223
|
+
if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
|
|
2224
|
+
if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
|
|
2225
|
+
|
|
2226
|
+
return [
|
|
2227
|
+
['M', x + rx, y],
|
|
2228
|
+
['h', w - rx * 2],
|
|
2229
|
+
['s', rx, 0, rx, ry],
|
|
2230
|
+
['v', h - ry * 2],
|
|
2231
|
+
['s', 0, ry, -rx, ry],
|
|
2232
|
+
['h', -w + rx * 2],
|
|
2233
|
+
['s', -rx, 0, -rx, -ry],
|
|
2234
|
+
['v', -h + ry * 2],
|
|
2235
|
+
['s', 0, -ry, rx, -ry],
|
|
2236
|
+
];
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
return [
|
|
2240
|
+
['M', x, y],
|
|
2241
|
+
['h', w],
|
|
2242
|
+
['v', h],
|
|
2243
|
+
['H', x],
|
|
2244
|
+
['Z'],
|
|
2245
|
+
];
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
/**
|
|
2249
|
+
* Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
|
|
2250
|
+
* `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
|
|
2251
|
+
* is `true`, it will replace the target.
|
|
2252
|
+
*
|
|
2253
|
+
* The newly created `<path>` element keeps all non-specific
|
|
2254
|
+
* attributes like `class`, `fill`, etc.
|
|
2255
|
+
*
|
|
2256
|
+
* @param {SVGPC.shapeTypes} element target shape
|
|
2257
|
+
* @param {boolean} replace option to replace target
|
|
2258
|
+
* @return {?SVGPathElement} the newly created `<path>` element
|
|
2259
|
+
*/
|
|
2260
|
+
function shapeToPath(element, replace) {
|
|
2261
|
+
const supportedShapes = Object.keys(shapeParams).concat(['glyph']);
|
|
2262
|
+
|
|
2263
|
+
if (!supportedShapes.some((s) => element.tagName === s)) {
|
|
2264
|
+
throw TypeError(`shapeToPath: ${element} is not SVGElement`);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
2268
|
+
const type = element.tagName;
|
|
2269
|
+
const shapeAttrs = shapeParams[type];
|
|
2270
|
+
|
|
2271
|
+
// set config
|
|
2272
|
+
const config = { type };
|
|
2273
|
+
shapeAttrs.forEach((p) => { config[p] = element.getAttribute(p); });
|
|
2274
|
+
|
|
2275
|
+
// set no-specific shape attributes: fill, stroke, etc
|
|
2276
|
+
Object.values(element.attributes).forEach(({ name, value }) => {
|
|
2277
|
+
if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
// set d
|
|
2281
|
+
let description;
|
|
2282
|
+
if (['circle', 'ellipse'].includes(type)) description = pathToString(getEllipsePath(config));
|
|
2283
|
+
else if (['polyline', 'polygon'].includes(type)) description = pathToString(getPolyPath(config));
|
|
2284
|
+
else if (type === 'rect') description = pathToString(getRectanglePath(config));
|
|
2285
|
+
else if (type === 'line') description = pathToString(getLinePath(config));
|
|
2286
|
+
else if (type === 'glyph') description = element.getAttribute('d');
|
|
2287
|
+
|
|
2288
|
+
// replace target element
|
|
2289
|
+
if (description) {
|
|
2290
|
+
path.setAttribute('d', description);
|
|
2291
|
+
if (replace) {
|
|
2292
|
+
element.before(path, element);
|
|
2293
|
+
element.remove();
|
|
2294
|
+
}
|
|
2295
|
+
return path;
|
|
2296
|
+
}
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
/**
|
|
2301
|
+
* Split a path into an `Array` of sub-path strings.
|
|
2302
|
+
*
|
|
2303
|
+
* In the process, values are converted to absolute
|
|
2304
|
+
* for visual consistency.
|
|
2305
|
+
*
|
|
2306
|
+
* @param {Object | String} pathInput the cubic-bezier parameters
|
|
2307
|
+
* @return {Object} an array with all sub-path strings
|
|
2308
|
+
*/
|
|
1751
2309
|
function splitPath(pathInput) {
|
|
1752
2310
|
return pathToString(pathToAbsolute(pathInput))
|
|
1753
2311
|
.replace(/(m|M)/g, '|$1')
|
|
@@ -1756,13 +2314,20 @@ function splitPath(pathInput) {
|
|
|
1756
2314
|
.filter((s) => s);
|
|
1757
2315
|
}
|
|
1758
2316
|
|
|
2317
|
+
/**
|
|
2318
|
+
* Parses a path string value or object and returns an array
|
|
2319
|
+
* of segments, all converted to relative values.
|
|
2320
|
+
*
|
|
2321
|
+
* @param {string | SVGPC.pathArray} pathInput the path string | object
|
|
2322
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with relative values
|
|
2323
|
+
*/
|
|
1759
2324
|
function pathToRelative(pathInput) {
|
|
1760
2325
|
if (isRelativeArray(pathInput)) {
|
|
1761
2326
|
return clonePath(pathInput);
|
|
1762
2327
|
}
|
|
1763
2328
|
|
|
1764
|
-
const
|
|
1765
|
-
const ii =
|
|
2329
|
+
const path = parsePathString(pathInput);
|
|
2330
|
+
const ii = path.length;
|
|
1766
2331
|
const resultArray = [];
|
|
1767
2332
|
let x = 0;
|
|
1768
2333
|
let y = 0;
|
|
@@ -1770,9 +2335,9 @@ function pathToRelative(pathInput) {
|
|
|
1770
2335
|
let my = 0;
|
|
1771
2336
|
let start = 0;
|
|
1772
2337
|
|
|
1773
|
-
if (
|
|
1774
|
-
x = +
|
|
1775
|
-
y = +
|
|
2338
|
+
if (path[0][0] === 'M') {
|
|
2339
|
+
x = +path[0][1];
|
|
2340
|
+
y = +path[0][2];
|
|
1776
2341
|
mx = x;
|
|
1777
2342
|
my = y;
|
|
1778
2343
|
start += 1;
|
|
@@ -1780,7 +2345,7 @@ function pathToRelative(pathInput) {
|
|
|
1780
2345
|
}
|
|
1781
2346
|
|
|
1782
2347
|
for (let i = start; i < ii; i += 1) {
|
|
1783
|
-
const segment =
|
|
2348
|
+
const segment = path[i];
|
|
1784
2349
|
const [pathCommand] = segment;
|
|
1785
2350
|
const relativeCommand = pathCommand.toLowerCase();
|
|
1786
2351
|
const relativeSegment = [];
|
|
@@ -1842,9 +2407,19 @@ function pathToRelative(pathInput) {
|
|
|
1842
2407
|
return resultArray;
|
|
1843
2408
|
}
|
|
1844
2409
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2410
|
+
/**
|
|
2411
|
+
* Optimizes a `pathArray` object:
|
|
2412
|
+
* * convert segments to absolute and relative values
|
|
2413
|
+
* * create a new `pathArray` with elements with shortest segments
|
|
2414
|
+
* from absolute and relative `pathArray`s
|
|
2415
|
+
*
|
|
2416
|
+
* @param {string | SVGPC.pathArray} pathInput a string or `pathArray`
|
|
2417
|
+
* @param {number} round the amount of decimals to round values to
|
|
2418
|
+
* @returns {SVGPC.pathArray} the optimized `pathArray`
|
|
2419
|
+
*/
|
|
2420
|
+
function optimizePath(pathInput, round) {
|
|
2421
|
+
const absolutePath = roundPath(pathToAbsolute(pathInput), round);
|
|
2422
|
+
const relativePath = roundPath(pathToRelative(pathInput), round);
|
|
1848
2423
|
return absolutePath.map((x, i) => {
|
|
1849
2424
|
if (i) {
|
|
1850
2425
|
return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
|
|
@@ -1853,13 +2428,19 @@ function optimizePath(pathArray, round) {
|
|
|
1853
2428
|
});
|
|
1854
2429
|
}
|
|
1855
2430
|
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2431
|
+
/**
|
|
2432
|
+
* Reverses all segments and their values from a `pathArray`
|
|
2433
|
+
* which consists of only C (cubic-bezier) path commands.
|
|
2434
|
+
*
|
|
2435
|
+
* @param {SVGPC.pathArray} path the source `pathArray`
|
|
2436
|
+
* @returns {SVGPC.pathArray} the reversed `pathArray`
|
|
2437
|
+
*/
|
|
2438
|
+
function reverseCurve(path) {
|
|
2439
|
+
const rotatedCurve = path.slice(1)
|
|
1859
2440
|
.map((x, i, curveOnly) => (!i
|
|
1860
|
-
?
|
|
2441
|
+
? path[0].slice(1).concat(x.slice(1))
|
|
1861
2442
|
: curveOnly[i - 1].slice(-2).concat(x.slice(1))))
|
|
1862
|
-
.map((x) => x.map((
|
|
2443
|
+
.map((x) => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
|
|
1863
2444
|
.reverse();
|
|
1864
2445
|
|
|
1865
2446
|
return [['M'].concat(rotatedCurve[0]
|
|
@@ -1867,8 +2448,15 @@ function reverseCurve(pathArray) {
|
|
|
1867
2448
|
.concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
|
|
1868
2449
|
}
|
|
1869
2450
|
|
|
1870
|
-
|
|
1871
|
-
|
|
2451
|
+
/**
|
|
2452
|
+
* Reverses all segments and their values of a `pathArray`
|
|
2453
|
+
* and returns a new instance.
|
|
2454
|
+
*
|
|
2455
|
+
* @param {SVGPC.pathArray} pathInput the source `pathArray`
|
|
2456
|
+
* @returns {SVGPC.pathArray} the reversed `pathArray`
|
|
2457
|
+
*/
|
|
2458
|
+
function reversePath(pathInput) {
|
|
2459
|
+
const absolutePath = pathToAbsolute(pathInput);
|
|
1872
2460
|
const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
|
|
1873
2461
|
let reversedPath = [];
|
|
1874
2462
|
let segLength = 0;
|
|
@@ -1882,15 +2470,15 @@ function reversePath(pathString) { // pathArray | pathString
|
|
|
1882
2470
|
x: segment[segLength - 2], // x
|
|
1883
2471
|
y: segment[segLength - 1], // y
|
|
1884
2472
|
};
|
|
1885
|
-
}).map((seg, i,
|
|
2473
|
+
}).map((seg, i, path) => {
|
|
1886
2474
|
const segment = seg.seg;
|
|
1887
2475
|
const data = seg.n;
|
|
1888
|
-
const prevSeg = i &&
|
|
1889
|
-
const nextSeg =
|
|
2476
|
+
const prevSeg = i && path[i - 1];
|
|
2477
|
+
const nextSeg = path[i + 1] && path[i + 1];
|
|
1890
2478
|
const pathCommand = seg.c;
|
|
1891
|
-
const pLen =
|
|
1892
|
-
const x = i ?
|
|
1893
|
-
const y = i ?
|
|
2479
|
+
const pLen = path.length;
|
|
2480
|
+
const x = i ? path[i - 1].x : path[pLen - 1].x;
|
|
2481
|
+
const y = i ? path[i - 1].y : path[pLen - 1].y;
|
|
1894
2482
|
let result = [];
|
|
1895
2483
|
|
|
1896
2484
|
switch (pathCommand) {
|
|
@@ -1948,21 +2536,28 @@ function reversePath(pathString) { // pathArray | pathString
|
|
|
1948
2536
|
: [reversedPath[0]].concat(reversedPath.slice(1).reverse());
|
|
1949
2537
|
}
|
|
1950
2538
|
|
|
1951
|
-
|
|
2539
|
+
/**
|
|
2540
|
+
* A global namespace for epsilon.
|
|
2541
|
+
*
|
|
2542
|
+
* @type {Number}
|
|
2543
|
+
*/
|
|
2544
|
+
const epsilon = 1e-9;
|
|
1952
2545
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2546
|
+
/**
|
|
2547
|
+
* Returns a transformation matrix to apply to `<path>` elements.
|
|
2548
|
+
*
|
|
2549
|
+
* @param {SVGPC.transformObject} transform the `transformObject`
|
|
2550
|
+
* @returns {CSSMatrix} a new transformation matrix
|
|
2551
|
+
*/
|
|
2552
|
+
function getSVGMatrix(transform) {
|
|
2553
|
+
let matrix = new CSSMatrix();
|
|
2554
|
+
const { origin } = transform;
|
|
1956
2555
|
const originX = +origin[0];
|
|
1957
2556
|
const originY = +origin[1];
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
const {
|
|
1961
|
-
const {
|
|
1962
|
-
const { skew } = transformObject;
|
|
1963
|
-
const { scale } = transformObject;
|
|
1964
|
-
|
|
1965
|
-
// !isNaN(perspective) && perspective && (matrix.m34 = -1/perspective)
|
|
2557
|
+
const { translate } = transform;
|
|
2558
|
+
const { rotate } = transform;
|
|
2559
|
+
const { skew } = transform;
|
|
2560
|
+
const { scale } = transform;
|
|
1966
2561
|
|
|
1967
2562
|
// set translate
|
|
1968
2563
|
if (!Number.isNaN(translate) || (Array.isArray(translate) && translate.some((x) => +x !== 0))) {
|
|
@@ -2004,6 +2599,14 @@ function getSVGMatrix(transformObject) {
|
|
|
2004
2599
|
return matrix;
|
|
2005
2600
|
}
|
|
2006
2601
|
|
|
2602
|
+
/**
|
|
2603
|
+
* Apply a 2D transformation matrix to an ellipse.
|
|
2604
|
+
*
|
|
2605
|
+
* @param {number[]} m the 2D transformation matrix
|
|
2606
|
+
* @param {number} rx ellipse radius X
|
|
2607
|
+
* @param {number} ry ellipse radius Y
|
|
2608
|
+
* @param {number} ax ellipse rotation angle
|
|
2609
|
+
*/
|
|
2007
2610
|
function transformEllipse(m, rx, ry, ax) {
|
|
2008
2611
|
// We consider the current ellipse as image of the unit circle
|
|
2009
2612
|
// by first scale(rx,ry) and then rotate(ax) ...
|
|
@@ -2068,11 +2671,19 @@ function transformEllipse(m, rx, ry, ax) {
|
|
|
2068
2671
|
return { rx: RX, ry: RY, ax: AX };
|
|
2069
2672
|
}
|
|
2070
2673
|
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2674
|
+
/**
|
|
2675
|
+
* Returns the [x,y] projected coordinates for a given an [x,y] point
|
|
2676
|
+
* and an [x,y,z] perspective origin point.
|
|
2677
|
+
*
|
|
2678
|
+
* Equation found here =>
|
|
2679
|
+
* http://en.wikipedia.org/wiki/3D_projection#Diagram
|
|
2680
|
+
* Details =>
|
|
2681
|
+
* https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
|
|
2682
|
+
*
|
|
2683
|
+
* @param {number[]} m the transformation matrix
|
|
2684
|
+
* @param {Number[]} point2D the initial [x,y] coordinates
|
|
2685
|
+
* @returns {Number[]} the projected [x,y] coordinates
|
|
2686
|
+
*/
|
|
2076
2687
|
function projection2d(m, point2D, origin) {
|
|
2077
2688
|
const point3D = m.transformPoint({
|
|
2078
2689
|
x: point2D[0], y: point2D[1], z: 0, w: 1,
|
|
@@ -2090,13 +2701,23 @@ function projection2d(m, point2D, origin) {
|
|
|
2090
2701
|
];
|
|
2091
2702
|
}
|
|
2092
2703
|
|
|
2093
|
-
|
|
2704
|
+
/**
|
|
2705
|
+
* Apply a 2D / 3D transformation to a `pathArray` instance.
|
|
2706
|
+
*
|
|
2707
|
+
* Since *SVGElement* doesn't support 3D transformation, this function
|
|
2708
|
+
* creates a 2D projection of the <path> element.
|
|
2709
|
+
*
|
|
2710
|
+
* @param {SVGPC.pathArray} path the `pathArray` to apply transformation
|
|
2711
|
+
* @param {SVGPC.transformObject} transform the transform functions `Object`
|
|
2712
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray`
|
|
2713
|
+
*/
|
|
2714
|
+
function transformPath(path, transform) {
|
|
2094
2715
|
let x; let y; let i; let j; let ii; let jj; let lx; let ly; let te;
|
|
2095
|
-
const absolutePath = pathToAbsolute(
|
|
2716
|
+
const absolutePath = pathToAbsolute(path);
|
|
2096
2717
|
const normalizedPath = normalizePath(absolutePath);
|
|
2097
|
-
const matrixInstance = getSVGMatrix(
|
|
2098
|
-
const transformProps = Object.keys(
|
|
2099
|
-
const { origin } =
|
|
2718
|
+
const matrixInstance = getSVGMatrix(transform);
|
|
2719
|
+
const transformProps = Object.keys(transform);
|
|
2720
|
+
const { origin } = transform;
|
|
2100
2721
|
const {
|
|
2101
2722
|
a, b, c, d, e, f,
|
|
2102
2723
|
} = matrixInstance;
|
|
@@ -2206,14 +2827,25 @@ function transformPath(pathArray, transformObject) {
|
|
|
2206
2827
|
return clonePath(absolutePath);
|
|
2207
2828
|
}
|
|
2208
2829
|
|
|
2830
|
+
var version = "0.1.9";
|
|
2831
|
+
|
|
2832
|
+
// @ts-ignore
|
|
2833
|
+
|
|
2834
|
+
/**
|
|
2835
|
+
* A global namespace for library version.
|
|
2836
|
+
* @type {string}
|
|
2837
|
+
*/
|
|
2838
|
+
const Version = version;
|
|
2839
|
+
|
|
2209
2840
|
const Util = {
|
|
2210
|
-
CSSMatrix
|
|
2841
|
+
CSSMatrix,
|
|
2211
2842
|
parsePathString,
|
|
2212
2843
|
isPathArray,
|
|
2213
2844
|
isCurveArray,
|
|
2214
2845
|
isAbsoluteArray,
|
|
2215
2846
|
isRelativeArray,
|
|
2216
2847
|
isNormalizedArray,
|
|
2848
|
+
isValidPath,
|
|
2217
2849
|
pathToAbsolute,
|
|
2218
2850
|
pathToRelative,
|
|
2219
2851
|
pathToCurve,
|
|
@@ -2232,23 +2864,24 @@ const Util = {
|
|
|
2232
2864
|
normalizePath,
|
|
2233
2865
|
transformPath,
|
|
2234
2866
|
getSVGMatrix,
|
|
2867
|
+
shapeToPath,
|
|
2235
2868
|
options: SVGPCO,
|
|
2869
|
+
Version,
|
|
2236
2870
|
};
|
|
2237
2871
|
|
|
2238
2872
|
/**
|
|
2239
2873
|
* Creates a new SVGPathCommander instance.
|
|
2240
|
-
*
|
|
2874
|
+
*
|
|
2875
|
+
* @author thednp <https://github.com/thednp/svg-path-commander>
|
|
2241
2876
|
*/
|
|
2242
2877
|
class SVGPathCommander {
|
|
2243
2878
|
/**
|
|
2244
|
-
* @
|
|
2245
|
-
* @param {
|
|
2246
|
-
* @param {Object} config instance options
|
|
2879
|
+
* @param {string} pathValue the path string
|
|
2880
|
+
* @param {object} config instance options
|
|
2247
2881
|
*/
|
|
2248
2882
|
constructor(pathValue, config) {
|
|
2249
2883
|
const options = config || {};
|
|
2250
|
-
|
|
2251
|
-
// const roundOption = +options.round === 0 || options.round === false ? 0 : SVGPCO.round;
|
|
2884
|
+
|
|
2252
2885
|
let { round } = SVGPCO;
|
|
2253
2886
|
const { round: roundOption } = options;
|
|
2254
2887
|
if (+roundOption === 0 || roundOption === false) {
|
|
@@ -2256,33 +2889,16 @@ class SVGPathCommander {
|
|
|
2256
2889
|
}
|
|
2257
2890
|
|
|
2258
2891
|
const { decimals } = round && (options || SVGPCO);
|
|
2259
|
-
const { origin } = options;
|
|
2260
2892
|
|
|
2261
2893
|
// set instance options
|
|
2262
|
-
/**
|
|
2263
|
-
* @type {Boolean | Number}
|
|
2264
|
-
*/
|
|
2894
|
+
/** @type {number | boolean | undefined} */
|
|
2265
2895
|
this.round = round === 0 ? 0 : decimals;
|
|
2266
2896
|
// ZERO | FALSE will disable rounding numbers
|
|
2267
2897
|
|
|
2268
|
-
|
|
2269
|
-
const { x, y } = origin;
|
|
2270
|
-
if ([x, y].every((n) => !Number.isNaN(n))) {
|
|
2271
|
-
/**
|
|
2272
|
-
* @type {Object | null}
|
|
2273
|
-
*/
|
|
2274
|
-
this.origin = origin;
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
/**
|
|
2279
|
-
* @type {Object}
|
|
2280
|
-
*/
|
|
2898
|
+
/** @type {SVGPC.pathArray} */
|
|
2281
2899
|
this.segments = parsePathString(pathValue);
|
|
2282
2900
|
|
|
2283
|
-
/**
|
|
2284
|
-
* @type {String}
|
|
2285
|
-
*/
|
|
2901
|
+
/** * @type {string} */
|
|
2286
2902
|
this.pathValue = pathValue;
|
|
2287
2903
|
|
|
2288
2904
|
return this;
|
|
@@ -2309,8 +2925,8 @@ class SVGPathCommander {
|
|
|
2309
2925
|
}
|
|
2310
2926
|
|
|
2311
2927
|
/**
|
|
2312
|
-
* Reverse
|
|
2313
|
-
* @param {
|
|
2928
|
+
* Reverse the order of the segments and their values.
|
|
2929
|
+
* @param {boolean | number} onlySubpath option to reverse all sub-paths except first
|
|
2314
2930
|
* @public
|
|
2315
2931
|
*/
|
|
2316
2932
|
reverse(onlySubpath) {
|
|
@@ -2339,7 +2955,7 @@ class SVGPathCommander {
|
|
|
2339
2955
|
|
|
2340
2956
|
/**
|
|
2341
2957
|
* Normalize path in 2 steps:
|
|
2342
|
-
* * convert pathArray(s) to absolute values
|
|
2958
|
+
* * convert `pathArray`(s) to absolute values
|
|
2343
2959
|
* * convert shorthand notation to standard notation
|
|
2344
2960
|
* @public
|
|
2345
2961
|
*/
|
|
@@ -2350,7 +2966,7 @@ class SVGPathCommander {
|
|
|
2350
2966
|
}
|
|
2351
2967
|
|
|
2352
2968
|
/**
|
|
2353
|
-
* Optimize pathArray values:
|
|
2969
|
+
* Optimize `pathArray` values:
|
|
2354
2970
|
* * convert segments to absolute and/or relative values
|
|
2355
2971
|
* * select segments with shortest resulted string
|
|
2356
2972
|
* * round values to the specified `decimals` option value
|
|
@@ -2364,31 +2980,27 @@ class SVGPathCommander {
|
|
|
2364
2980
|
}
|
|
2365
2981
|
|
|
2366
2982
|
/**
|
|
2367
|
-
* Transform path using values from an `Object`
|
|
2368
|
-
*
|
|
2983
|
+
* Transform path using values from an `Object` defined as `transformObject`.
|
|
2984
|
+
* @see SVGPC.transformObject for a quick refference
|
|
2369
2985
|
*
|
|
2370
|
-
* {
|
|
2371
|
-
* origin: {x, y, z},
|
|
2372
|
-
* translate: {x, y, z},
|
|
2373
|
-
* rotate: {x, y, z},
|
|
2374
|
-
* skew: {x, y, z},
|
|
2375
|
-
* scale: {x, y, z}
|
|
2376
|
-
* }
|
|
2377
|
-
* @param {Object} source a transform `Object`as described above
|
|
2986
|
+
* @param {SVGPC.transformObject} source a `transformObject`as described above
|
|
2378
2987
|
* @public
|
|
2379
2988
|
*/
|
|
2380
2989
|
transform(source) {
|
|
2381
|
-
|
|
2990
|
+
if (!source || typeof source !== 'object' || (typeof source === 'object'
|
|
2991
|
+
&& !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
|
|
2992
|
+
|
|
2993
|
+
const transform = source || {};
|
|
2382
2994
|
const { segments } = this;
|
|
2383
2995
|
|
|
2384
2996
|
// if origin is not specified
|
|
2385
2997
|
// it's important that we have one
|
|
2386
|
-
if (!
|
|
2998
|
+
if (!transform.origin) {
|
|
2387
2999
|
const BBox = getPathBBox(segments);
|
|
2388
|
-
|
|
3000
|
+
transform.origin = [BBox.cx, BBox.cy, BBox.cx];
|
|
2389
3001
|
}
|
|
2390
3002
|
|
|
2391
|
-
this.segments = transformPath(segments,
|
|
3003
|
+
this.segments = transformPath(segments, transform);
|
|
2392
3004
|
return this;
|
|
2393
3005
|
}
|
|
2394
3006
|
|