svg-path-commander 0.1.7 → 0.1.10-alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -13
- package/dist/svg-path-commander.esm.js +2587 -1819
- package/dist/svg-path-commander.esm.min.js +2 -2
- package/dist/svg-path-commander.js +2624 -1843
- package/dist/svg-path-commander.min.js +2 -2
- package/package.json +4 -4
- package/src/convert/pathToAbsolute.js +22 -14
- package/src/convert/pathToCurve.js +24 -20
- package/src/convert/pathToRelative.js +28 -19
- package/src/convert/pathToString.js +13 -2
- package/src/index.js +1 -4
- package/src/math/distanceSquareRoot.js +8 -0
- package/src/math/epsilon.js +8 -1
- package/src/math/midPoint.js +10 -4
- package/src/math/polygonArea.js +9 -2
- package/src/math/polygonLength.js +13 -7
- package/src/math/rotateVector.js +13 -0
- package/src/options/options.js +5 -1
- package/src/parser/finalizeSegment.js +31 -0
- package/src/parser/invalidPathValue.js +2 -0
- package/src/parser/isArcCommand.js +10 -0
- package/src/parser/isDigit.js +9 -0
- package/src/{util → parser}/isDigitStart.js +7 -0
- package/src/{util → parser}/isPathCommand.js +7 -1
- 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 +30 -14
- package/src/process/clonePath.js +9 -2
- package/src/process/fixArc.js +21 -0
- package/src/{util → process}/getSVGMatrix.js +16 -14
- package/src/process/lineToCubic.js +11 -2
- package/src/process/normalizePath.js +20 -13
- package/src/process/normalizeSegment.js +10 -2
- package/src/process/optimizePath.js +16 -5
- package/src/{util → process}/projection2d.js +14 -5
- package/src/process/quadToCubic.js +11 -0
- package/src/process/reverseCurve.js +11 -5
- package/src/process/reversePath.js +19 -12
- package/src/process/roundPath.js +24 -18
- package/src/process/segmentToCubic.js +18 -7
- package/src/process/shorthandToCubic.js +11 -1
- package/src/process/shorthandToQuad.js +11 -1
- package/src/process/splitCubic.js +9 -3
- package/src/process/splitPath.js +12 -3
- package/src/{util → process}/transformEllipse.js +9 -1
- package/src/process/transformPath.js +34 -22
- package/src/svg-path-commander.js +193 -0
- package/src/util/createPath.js +16 -7
- package/src/util/getCubicSize.js +14 -2
- package/src/util/getDrawDirection.js +11 -4
- package/src/util/getPathArea.js +30 -8
- package/src/util/getPathBBox.js +13 -7
- package/src/util/getPathLength.js +14 -8
- package/src/util/getPointAtLength.js +12 -7
- package/src/util/getPointAtSegLength.js +15 -1
- package/src/util/getSegArcLength.js +8 -5
- package/src/util/getSegCubicLength.js +14 -1
- package/src/util/getSegLineLength.js +10 -2
- package/src/util/getSegQuadLength.js +13 -1
- package/src/util/isAbsoluteArray.js +11 -3
- package/src/util/isCurveArray.js +11 -3
- package/src/util/isNormalizedArray.js +14 -5
- package/src/util/isPathArray.js +11 -5
- package/src/util/isRelativeArray.js +11 -4
- package/src/util/isValidPath.js +26 -0
- package/src/util/shapeToPath.js +181 -0
- package/src/util/util.js +36 -29
- package/src/util/version.js +9 -0
- package/types/index.d.ts +834 -2
- package/types/types.d.ts +83 -0
- package/src/class/SVGPathCommander.js +0 -107
- package/src/process/finalizeSegment.js +0 -26
- package/src/process/parsePathString.js +0 -36
- package/src/process/scanFlag.js +0 -20
- package/src/process/scanParam.js +0 -93
- package/src/process/scanSegment.js +0 -76
- package/src/process/skipSpaces.js +0 -7
- package/src/util/DOMMatrix.js +0 -5
- package/src/util/fixArc.js +0 -16
- package/src/util/invalidPathValue.js +0 -1
- 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 -11
- package/types/class/SVGPathCommander.d.ts +0 -7
- 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,1626 +1,2331 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* SVGPathCommander v0.1.
|
|
2
|
+
* SVGPathCommander v0.1.10alpha2 (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
|
+
/**
|
|
7
|
+
* SVGPathCommander default options
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
6
10
|
const SVGPCO = {
|
|
7
|
-
origin:
|
|
11
|
+
origin: [0, 0],
|
|
8
12
|
decimals: 4,
|
|
9
13
|
round: 1,
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
* @type {Object.<string, number>}
|
|
18
|
+
*/
|
|
19
|
+
const paramsCount = {
|
|
20
|
+
a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Breaks the parsing of a pathString once a segment is finalized.
|
|
20
25
|
*
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {Array} Array expected to be *Float64Array* or *Float32Array* in the column major order.
|
|
23
|
-
* @param {[a,b,c,d,e,f]} Arguments representing the 6 elements of a 2d matrix
|
|
24
|
-
* @param {[m11,m21,m31,m41..]} Arguments representing the 16 elements of a 3d matrix
|
|
26
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
25
27
|
*/
|
|
28
|
+
function finalizeSegment(path) {
|
|
29
|
+
let pathCommand = path.pathValue[path.segmentStart];
|
|
30
|
+
let pathComLK = pathCommand.toLowerCase();
|
|
31
|
+
let params = path.data;
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// Process duplicated commands (without comand name)
|
|
34
|
+
if (pathComLK === 'm' && params.length > 2) {
|
|
35
|
+
path.segments.push([pathCommand, params[0], params[1]]);
|
|
36
|
+
params = params.slice(2);
|
|
37
|
+
pathComLK = 'l';
|
|
38
|
+
pathCommand = (pathCommand === 'm') ? 'l' : 'L';
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return (m.m11 === 1 && m.m12 === 0 && m.m13 === 0 && m.m14 === 0
|
|
43
|
-
&& m.m21 === 0 && m.m22 === 1 && m.m23 === 0 && m.m24 === 0
|
|
44
|
-
&& m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0
|
|
45
|
-
&& m.m41 === 0 && m.m42 === 0 && m.m43 === 0 && m.m44 === 1);
|
|
41
|
+
if (pathComLK === 'r') {
|
|
42
|
+
path.segments.push([pathCommand].concat(params));
|
|
43
|
+
} else {
|
|
44
|
+
while (params.length >= paramsCount[pathComLK]) {
|
|
45
|
+
path.segments.push([pathCommand].concat(params.splice(0, paramsCount[pathComLK])));
|
|
46
|
+
if (!paramsCount[pathComLK]) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
46
50
|
}
|
|
51
|
+
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
* Sets a new `Boolean` flag value for `this.isIdentity` matrix property.
|
|
50
|
-
*
|
|
51
|
-
* @param {Boolean} value sets a new `Boolean` flag for this property
|
|
52
|
-
*/
|
|
53
|
-
set isIdentity(value) {
|
|
54
|
-
this.isIdentity = value;
|
|
55
|
-
}
|
|
53
|
+
const invalidPathValue = 'Invalid path value';
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
55
|
+
/**
|
|
56
|
+
* Validates an A (arc-to) specific path command value.
|
|
57
|
+
* Usually a `large-arc-flag` or `sweep-flag`.
|
|
58
|
+
*
|
|
59
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
60
|
+
*/
|
|
61
|
+
function scanFlag(path) {
|
|
62
|
+
const { index } = path;
|
|
63
|
+
const ch = path.pathValue.charCodeAt(index);
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*/
|
|
73
|
-
set is2D(value) {
|
|
74
|
-
this.is2D = value;
|
|
65
|
+
if (ch === 0x30/* 0 */) {
|
|
66
|
+
path.param = 0;
|
|
67
|
+
path.index += 1;
|
|
68
|
+
return;
|
|
75
69
|
}
|
|
76
|
-
}
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
if (ch === 0x31/* 1 */) {
|
|
72
|
+
path.param = 1;
|
|
73
|
+
path.index += 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
path.err = `${invalidPathValue}: invalid Arc flag ${ch}, expecting 0 or 1 at index ${index}`;
|
|
78
|
+
}
|
|
83
79
|
|
|
84
80
|
/**
|
|
85
|
-
*
|
|
86
|
-
* This method is equivalent to the CSS `translate3d()` function.
|
|
87
|
-
*
|
|
88
|
-
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
|
|
81
|
+
* Checks if a character is a digit.
|
|
89
82
|
*
|
|
90
|
-
* @param {
|
|
91
|
-
* @
|
|
92
|
-
* @param {Number} z the `z-axis` position.
|
|
83
|
+
* @param {string} code the character to check
|
|
84
|
+
* @returns {boolean} check result
|
|
93
85
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
m.m41 = x;
|
|
97
|
-
m.e = x;
|
|
98
|
-
m.m42 = y;
|
|
99
|
-
m.f = y;
|
|
100
|
-
m.m43 = z;
|
|
101
|
-
return m;
|
|
86
|
+
function isDigit(code) {
|
|
87
|
+
return (code >= 48 && code <= 57); // 0..9
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* http://en.wikipedia.org/wiki/Rotation_matrix
|
|
91
|
+
* Validates every character of the path string,
|
|
92
|
+
* every path command, negative numbers or floating point numbers.
|
|
108
93
|
*
|
|
109
|
-
* @param {
|
|
110
|
-
* @param {Number} ry the `y-axis` rotation.
|
|
111
|
-
* @param {Number} rz the `z-axis` rotation.
|
|
94
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
112
95
|
*/
|
|
96
|
+
function scanParam(path) {
|
|
97
|
+
const { max, pathValue, index: start } = path;
|
|
98
|
+
let index = start;
|
|
99
|
+
let zeroFirst = false;
|
|
100
|
+
let hasCeiling = false;
|
|
101
|
+
let hasDecimal = false;
|
|
102
|
+
let hasDot = false;
|
|
103
|
+
let ch;
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
if (index >= max) {
|
|
106
|
+
// path.err = 'SvgPath: missed param (at pos ' + index + ')';
|
|
107
|
+
path.err = `${invalidPathValue} at ${index}: missing param ${pathValue[index]}`;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
ch = pathValue.charCodeAt(index);
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
|
|
113
|
+
index += 1;
|
|
114
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
115
|
+
}
|
|
120
116
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
// This logic is shamelessly borrowed from Esprima
|
|
118
|
+
// https://github.com/ariya/esprimas
|
|
119
|
+
if (!isDigit(ch) && ch !== 0x2E/* . */) {
|
|
120
|
+
// path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
|
|
121
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} is not a number`;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
if (ch !== 0x2E/* . */) {
|
|
126
|
+
zeroFirst = (ch === 0x30/* 0 */);
|
|
127
|
+
index += 1;
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
m.a = cycz;
|
|
129
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
134
130
|
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
if (zeroFirst && index < max) {
|
|
132
|
+
// decimal number starts with '0' such as '09' is illegal.
|
|
133
|
+
if (ch && isDigit(ch)) {
|
|
134
|
+
// path.err = 'SvgPath: numbers started with `0` such as `09`
|
|
135
|
+
// are illegal (at pos ' + start + ')';
|
|
136
|
+
path.err = `${invalidPathValue} at index ${start}: ${pathValue[start]} illegal number`;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
while (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
142
|
+
index += 1;
|
|
143
|
+
hasCeiling = true;
|
|
144
|
+
}
|
|
145
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
146
|
+
}
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
if (ch === 0x2E/* . */) {
|
|
149
|
+
hasDot = true;
|
|
150
|
+
index += 1;
|
|
151
|
+
while (isDigit(pathValue.charCodeAt(index))) {
|
|
152
|
+
index += 1;
|
|
153
|
+
hasDecimal = true;
|
|
154
|
+
}
|
|
155
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
156
|
+
}
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
if (ch === 0x65/* e */ || ch === 0x45/* E */) {
|
|
159
|
+
if (hasDot && !hasCeiling && !hasDecimal) {
|
|
160
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
147
163
|
|
|
148
|
-
|
|
164
|
+
index += 1;
|
|
149
165
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
ch = (index < max) ? pathValue.charCodeAt(index) : 0;
|
|
167
|
+
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
|
|
168
|
+
index += 1;
|
|
169
|
+
}
|
|
170
|
+
if (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
171
|
+
while (index < max && isDigit(pathValue.charCodeAt(index))) {
|
|
172
|
+
index += 1;
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// path.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
|
|
176
|
+
path.err = `${invalidPathValue} at index ${index}: ${pathValue[index]} invalid float exponent`;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
153
180
|
|
|
154
|
-
|
|
181
|
+
path.index = index;
|
|
182
|
+
path.param = +path.pathValue.slice(start, index);
|
|
155
183
|
}
|
|
156
184
|
|
|
157
185
|
/**
|
|
158
|
-
*
|
|
159
|
-
* This method is equivalent to the CSS `rotate3d()` function.
|
|
186
|
+
* Checks if the character is a space.
|
|
160
187
|
*
|
|
161
|
-
*
|
|
188
|
+
* @param {string} code the character to check
|
|
189
|
+
* @returns {boolean} check result
|
|
190
|
+
*/
|
|
191
|
+
function isSpace(code) {
|
|
192
|
+
const specialSpaces = [
|
|
193
|
+
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
|
|
194
|
+
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF];
|
|
195
|
+
// Line terminators
|
|
196
|
+
return (code === 0x0A) || (code === 0x0D) || (code === 0x2028) || (code === 0x2029)
|
|
197
|
+
// White spaces
|
|
198
|
+
|| (code === 0x20) || (code === 0x09) || (code === 0x0B) || (code === 0x0C) || (code === 0xA0)
|
|
199
|
+
|| (code >= 0x1680 && specialSpaces.indexOf(code) >= 0);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Points the parser to the next character in the
|
|
204
|
+
* path string every time it encounters any kind of
|
|
205
|
+
* space character.
|
|
162
206
|
*
|
|
163
|
-
* @param {
|
|
164
|
-
* @param {Number} y the `y-axis` vector length.
|
|
165
|
-
* @param {Number} z the `z-axis` vector length.
|
|
166
|
-
* @param {Number} angle the value in degrees of the rotation.
|
|
207
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
167
208
|
*/
|
|
168
|
-
function
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const length = Math.sqrt(x * x + y * y + z * z);
|
|
175
|
-
let X = 0;
|
|
176
|
-
let Y = 0;
|
|
177
|
-
let Z = 1;
|
|
209
|
+
function skipSpaces(path) {
|
|
210
|
+
const { pathValue, max } = path;
|
|
211
|
+
while (path.index < max && isSpace(pathValue.charCodeAt(path.index))) {
|
|
212
|
+
path.index += 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
178
215
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Checks if the character is a path command.
|
|
218
|
+
*
|
|
219
|
+
* @param {any} code the character to check
|
|
220
|
+
* @returns {boolean} check result
|
|
221
|
+
*/
|
|
222
|
+
function isPathCommand(code) {
|
|
223
|
+
// eslint-disable-next-line no-bitwise -- Impossible to satisfy
|
|
224
|
+
switch (code | 0x20) {
|
|
225
|
+
case 0x6D/* m */:
|
|
226
|
+
case 0x7A/* z */:
|
|
227
|
+
case 0x6C/* l */:
|
|
228
|
+
case 0x68/* h */:
|
|
229
|
+
case 0x76/* v */:
|
|
230
|
+
case 0x63/* c */:
|
|
231
|
+
case 0x73/* s */:
|
|
232
|
+
case 0x71/* q */:
|
|
233
|
+
case 0x74/* t */:
|
|
234
|
+
case 0x61/* a */:
|
|
235
|
+
case 0x72/* r */:
|
|
236
|
+
return true;
|
|
237
|
+
default:
|
|
238
|
+
return false;
|
|
184
239
|
}
|
|
240
|
+
}
|
|
185
241
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Checks if the character is or belongs to a number.
|
|
244
|
+
* [0-9]|+|-|.
|
|
245
|
+
*
|
|
246
|
+
* @param {string} code the character to check
|
|
247
|
+
* @returns {boolean} check result
|
|
248
|
+
*/
|
|
249
|
+
function isDigitStart(code) {
|
|
250
|
+
return (code >= 48 && code <= 57) /* 0..9 */
|
|
251
|
+
|| code === 0x2B /* + */
|
|
252
|
+
|| code === 0x2D /* - */
|
|
253
|
+
|| code === 0x2E; /* . */
|
|
254
|
+
}
|
|
189
255
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Checks if the character is an A (arc-to) path command.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} code the character to check
|
|
260
|
+
* @returns {boolean} check result
|
|
261
|
+
*/
|
|
262
|
+
function isArcCommand(code) {
|
|
263
|
+
// eslint-disable-next-line no-bitwise -- Impossible to satisfy
|
|
264
|
+
return (code | 0x20) === 0x61;
|
|
265
|
+
}
|
|
193
266
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Scans every character in the path string to determine
|
|
269
|
+
* where a segment starts and where it ends.
|
|
270
|
+
*
|
|
271
|
+
* @param {SVGPC.parserPathArray} path the `parserPathArray` instance
|
|
272
|
+
*/
|
|
273
|
+
function scanSegment(path) {
|
|
274
|
+
const { max, pathValue, index } = path;
|
|
275
|
+
const cmdCode = pathValue.charCodeAt(index);
|
|
276
|
+
const reqParams = paramsCount[pathValue[index].toLowerCase()];
|
|
197
277
|
|
|
198
|
-
|
|
278
|
+
path.segmentStart = index;
|
|
199
279
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
280
|
+
if (!isPathCommand(cmdCode)) {
|
|
281
|
+
path.err = `${invalidPathValue}: ${pathValue[index]} not a path command`;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
203
284
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
m.d = m22;
|
|
285
|
+
path.index += 1;
|
|
286
|
+
skipSpaces(path);
|
|
207
287
|
|
|
208
|
-
|
|
209
|
-
m.m31 = 2 * (z * x * sinA2 + y * sinA * cosA);
|
|
210
|
-
m.m32 = 2 * (z * y * sinA2 - x * sinA * cosA);
|
|
211
|
-
m.m33 = 1 - 2 * (x2 + y2) * sinA2;
|
|
288
|
+
path.data = [];
|
|
212
289
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
290
|
+
if (!reqParams) {
|
|
291
|
+
// Z
|
|
292
|
+
finalizeSegment(path);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
216
295
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
m.m43 = 0;
|
|
296
|
+
for (;;) {
|
|
297
|
+
for (let i = reqParams; i > 0; i -= 1) {
|
|
298
|
+
if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(path);
|
|
299
|
+
else scanParam(path);
|
|
222
300
|
|
|
223
|
-
|
|
301
|
+
if (path.err.length) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
path.data.push(path.param);
|
|
224
305
|
|
|
225
|
-
|
|
306
|
+
skipSpaces(path);
|
|
307
|
+
|
|
308
|
+
// after ',' param is mandatory
|
|
309
|
+
if (path.index < max && pathValue.charCodeAt(path.index) === 0x2C/* , */) {
|
|
310
|
+
path.index += 1;
|
|
311
|
+
skipSpaces(path);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (path.index >= path.max) {
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Stop on next segment
|
|
320
|
+
if (!isDigitStart(pathValue.charCodeAt(path.index))) {
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
finalizeSegment(path);
|
|
226
326
|
}
|
|
227
327
|
|
|
328
|
+
// @ts-nocheck
|
|
228
329
|
/**
|
|
229
|
-
*
|
|
230
|
-
* This method is equivalent to the CSS `scale3d()` function.
|
|
231
|
-
*
|
|
232
|
-
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
|
|
330
|
+
* Returns a clone of an existing `pathArray`.
|
|
233
331
|
*
|
|
234
|
-
* @param {
|
|
235
|
-
* @
|
|
236
|
-
* @param {Number} z the `z-axis` scale.
|
|
332
|
+
* @param {SVGPC.pathArray | string[]} path the source `pathArray`
|
|
333
|
+
* @returns {SVGPC.pathArray} the cloned `pathArray`
|
|
237
334
|
*/
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
m.m33 = z;
|
|
247
|
-
return m;
|
|
335
|
+
function clonePath(path) {
|
|
336
|
+
return path.map((x) => {
|
|
337
|
+
if (Array.isArray(x)) {
|
|
338
|
+
return clonePath(x);
|
|
339
|
+
}
|
|
340
|
+
return !Number.isNaN(+x) ? +x : x;
|
|
341
|
+
});
|
|
248
342
|
}
|
|
249
343
|
|
|
250
344
|
/**
|
|
251
|
-
*
|
|
252
|
-
* returns it. This method is equivalent to the CSS `skewX()` function.
|
|
253
|
-
*
|
|
254
|
-
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
|
|
345
|
+
* The `parserPathArray` used by the parser.
|
|
255
346
|
*
|
|
256
|
-
* @param {
|
|
347
|
+
* @param {string} pathString
|
|
257
348
|
*/
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
349
|
+
function SVGPathArray(pathString) {
|
|
350
|
+
/** @type {[string, ...number[]][]} */
|
|
351
|
+
this.segments = [];
|
|
352
|
+
/** @type {string} */
|
|
353
|
+
this.pathValue = pathString;
|
|
354
|
+
/** @type {number} */
|
|
355
|
+
this.max = pathString.length;
|
|
356
|
+
/** @type {number} */
|
|
357
|
+
this.index = 0;
|
|
358
|
+
/** @type {number} */
|
|
359
|
+
this.param = 0.0;
|
|
360
|
+
/** @type {number} */
|
|
361
|
+
this.segmentStart = 0;
|
|
362
|
+
/** @type {any} */
|
|
363
|
+
this.data = [];
|
|
364
|
+
/** @type {string} */
|
|
365
|
+
this.err = '';
|
|
265
366
|
}
|
|
266
367
|
|
|
267
368
|
/**
|
|
268
|
-
*
|
|
269
|
-
* returns it. This method is equivalent to the CSS `skewY()` function.
|
|
270
|
-
*
|
|
271
|
-
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
|
|
369
|
+
* Iterates an array to check if it's an actual `pathArray`.
|
|
272
370
|
*
|
|
273
|
-
* @param {
|
|
371
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to be checked
|
|
372
|
+
* @returns {boolean} iteration result
|
|
274
373
|
*/
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
m.b = t;
|
|
281
|
-
return m;
|
|
374
|
+
function isPathArray(path) {
|
|
375
|
+
return Array.isArray(path) && path.every((seg) => {
|
|
376
|
+
const lk = seg[0].toLowerCase();
|
|
377
|
+
return paramsCount[lk] === seg.length - 1 && /[achlmqstvz]/gi.test(lk);
|
|
378
|
+
});
|
|
282
379
|
}
|
|
283
380
|
|
|
284
381
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
382
|
+
* Parses a path string value and returns an array
|
|
383
|
+
* of segments we like to call `pathArray`.
|
|
287
384
|
*
|
|
288
|
-
* @param {
|
|
289
|
-
* @
|
|
385
|
+
* @param {string | SVGPC.pathArray} pathInput the string to be parsed
|
|
386
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray`
|
|
290
387
|
*/
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const m14 = m2.m11 * m1.m14 + m2.m12 * m1.m24 + m2.m13 * m1.m34 + m2.m14 * m1.m44;
|
|
296
|
-
|
|
297
|
-
const m21 = m2.m21 * m1.m11 + m2.m22 * m1.m21 + m2.m23 * m1.m31 + m2.m24 * m1.m41;
|
|
298
|
-
const m22 = m2.m21 * m1.m12 + m2.m22 * m1.m22 + m2.m23 * m1.m32 + m2.m24 * m1.m42;
|
|
299
|
-
const m23 = m2.m21 * m1.m13 + m2.m22 * m1.m23 + m2.m23 * m1.m33 + m2.m24 * m1.m43;
|
|
300
|
-
const m24 = m2.m21 * m1.m14 + m2.m22 * m1.m24 + m2.m23 * m1.m34 + m2.m24 * m1.m44;
|
|
388
|
+
function parsePathString(pathInput) {
|
|
389
|
+
if (isPathArray(pathInput)) {
|
|
390
|
+
return clonePath(pathInput);
|
|
391
|
+
}
|
|
301
392
|
|
|
302
|
-
const
|
|
303
|
-
const m32 = m2.m31 * m1.m12 + m2.m32 * m1.m22 + m2.m33 * m1.m32 + m2.m34 * m1.m42;
|
|
304
|
-
const m33 = m2.m31 * m1.m13 + m2.m32 * m1.m23 + m2.m33 * m1.m33 + m2.m34 * m1.m43;
|
|
305
|
-
const m34 = m2.m31 * m1.m14 + m2.m32 * m1.m24 + m2.m33 * m1.m34 + m2.m34 * m1.m44;
|
|
393
|
+
const path = new SVGPathArray(pathInput);
|
|
306
394
|
|
|
307
|
-
|
|
308
|
-
const m42 = m2.m41 * m1.m12 + m2.m42 * m1.m22 + m2.m43 * m1.m32 + m2.m44 * m1.m42;
|
|
309
|
-
const m43 = m2.m41 * m1.m13 + m2.m42 * m1.m23 + m2.m43 * m1.m33 + m2.m44 * m1.m43;
|
|
310
|
-
const m44 = m2.m41 * m1.m14 + m2.m42 * m1.m24 + m2.m43 * m1.m34 + m2.m44 * m1.m44;
|
|
395
|
+
skipSpaces(path);
|
|
311
396
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
m13, m23, m33, m43,
|
|
316
|
-
m14, m24, m34, m44],
|
|
317
|
-
);
|
|
318
|
-
}
|
|
397
|
+
while (path.index < path.max && !path.err.length) {
|
|
398
|
+
scanSegment(path);
|
|
399
|
+
}
|
|
319
400
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
401
|
+
if (path.err.length) {
|
|
402
|
+
path.segments = [];
|
|
403
|
+
} else if (path.segments.length) {
|
|
404
|
+
if ('mM'.indexOf(path.segments[0][0]) < 0) {
|
|
405
|
+
path.err = `${invalidPathValue}: missing M/m`;
|
|
406
|
+
path.segments = [];
|
|
407
|
+
} else {
|
|
408
|
+
path.segments[0][0] = 'M';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
330
411
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
* The elements are stored into the array as double-precision floating-point numbers
|
|
334
|
-
* in column-major (colexographical access access or "colex") order.
|
|
335
|
-
*
|
|
336
|
-
* @return {Float64Array} matrix elements (m11, m21, m31, m41, ..)
|
|
337
|
-
*/
|
|
338
|
-
// toFloat64Array(){
|
|
339
|
-
// return Float64Array.from(this.toArray());
|
|
340
|
-
// }
|
|
412
|
+
return path.segments;
|
|
413
|
+
}
|
|
341
414
|
|
|
342
415
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
416
|
+
* Iterates an array to check if it's a `pathArray`
|
|
417
|
+
* with all absolute values.
|
|
345
418
|
*
|
|
346
|
-
* @param {
|
|
419
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to be checked
|
|
420
|
+
* @returns {boolean} iteration result
|
|
347
421
|
*/
|
|
348
|
-
function
|
|
349
|
-
return
|
|
350
|
-
|
|
351
|
-
[m.m11, m.m21, m.m31, m.m41,
|
|
352
|
-
m.m12, m.m22, m.m32, m.m42,
|
|
353
|
-
m.m13, m.m23, m.m33, m.m43,
|
|
354
|
-
m.m14, m.m24, m.m34, m.m44],
|
|
355
|
-
);
|
|
422
|
+
function isAbsoluteArray(path) {
|
|
423
|
+
return Array.isArray(path) && isPathArray(path)
|
|
424
|
+
&& path.every((x) => x[0] === x[0].toUpperCase());
|
|
356
425
|
}
|
|
357
426
|
|
|
358
427
|
/**
|
|
359
|
-
*
|
|
428
|
+
* Parses a path string value or object and returns an array
|
|
429
|
+
* of segments, all converted to absolute values.
|
|
360
430
|
*
|
|
361
|
-
* @param {
|
|
362
|
-
* @
|
|
431
|
+
* @param {string | SVGPC.pathArray} pathInput the path string | object
|
|
432
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with absolute values
|
|
363
433
|
*/
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
m12, m22, m32, m42,
|
|
369
|
-
m13, m23, m33, m43,
|
|
370
|
-
m14, m24, m34, m44] = a;
|
|
371
|
-
|
|
372
|
-
m.m11 = m11;
|
|
373
|
-
m.a = m11;
|
|
374
|
-
|
|
375
|
-
m.m21 = m21;
|
|
376
|
-
m.c = m21;
|
|
377
|
-
|
|
378
|
-
m.m31 = m31;
|
|
379
|
-
|
|
380
|
-
m.m41 = m41;
|
|
381
|
-
m.e = m41;
|
|
382
|
-
|
|
383
|
-
m.m12 = m12;
|
|
384
|
-
m.b = m12;
|
|
385
|
-
|
|
386
|
-
m.m22 = m22;
|
|
387
|
-
m.d = m22;
|
|
388
|
-
|
|
389
|
-
m.m32 = m32;
|
|
390
|
-
|
|
391
|
-
m.m42 = m42;
|
|
392
|
-
m.f = m42;
|
|
434
|
+
function pathToAbsolute(pathInput) {
|
|
435
|
+
if (Array.isArray(pathInput) && isAbsoluteArray(pathInput)) {
|
|
436
|
+
return clonePath(pathInput);
|
|
437
|
+
}
|
|
393
438
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const [m11, m12, m21, m22, m14, m24] = a;
|
|
439
|
+
const path = parsePathString(pathInput);
|
|
440
|
+
const ii = path.length;
|
|
441
|
+
/** @type {SVGPC.pathArray} */
|
|
442
|
+
const resultArray = [];
|
|
443
|
+
let x = 0;
|
|
444
|
+
let y = 0;
|
|
445
|
+
let mx = 0;
|
|
446
|
+
let my = 0;
|
|
447
|
+
let start = 0;
|
|
404
448
|
|
|
405
|
-
|
|
406
|
-
|
|
449
|
+
if (path[0][0] === 'M') {
|
|
450
|
+
x = +path[0][1];
|
|
451
|
+
y = +path[0][2];
|
|
452
|
+
mx = x;
|
|
453
|
+
my = y;
|
|
454
|
+
start += 1;
|
|
455
|
+
resultArray.push(['M', x, y]);
|
|
456
|
+
}
|
|
407
457
|
|
|
408
|
-
|
|
409
|
-
|
|
458
|
+
for (let i = start; i < ii; i += 1) {
|
|
459
|
+
const segment = path[i];
|
|
460
|
+
const [pathCommand] = segment;
|
|
461
|
+
const absCommand = pathCommand.toUpperCase();
|
|
462
|
+
const absoluteSegment = [];
|
|
463
|
+
let newSeg = [];
|
|
410
464
|
|
|
411
|
-
|
|
412
|
-
|
|
465
|
+
if (pathCommand !== absCommand) {
|
|
466
|
+
absoluteSegment[0] = absCommand;
|
|
413
467
|
|
|
414
|
-
|
|
415
|
-
|
|
468
|
+
switch (absCommand) {
|
|
469
|
+
case 'A':
|
|
470
|
+
newSeg = segment.slice(1, -2).concat([+segment[6] + x, +segment[7] + y]);
|
|
471
|
+
for (let j = 0; j < newSeg.length; j += 1) {
|
|
472
|
+
absoluteSegment.push(newSeg[j]);
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
case 'V':
|
|
476
|
+
absoluteSegment[1] = +segment[1] + y;
|
|
477
|
+
break;
|
|
478
|
+
case 'H':
|
|
479
|
+
absoluteSegment[1] = +segment[1] + x;
|
|
480
|
+
break;
|
|
481
|
+
default:
|
|
482
|
+
if (absCommand === 'M') {
|
|
483
|
+
mx = +segment[1] + x;
|
|
484
|
+
my = +segment[2] + y;
|
|
485
|
+
}
|
|
486
|
+
// for is here to stay for eslint
|
|
487
|
+
for (let j = 1; j < segment.length; j += 1) {
|
|
488
|
+
absoluteSegment.push(+segment[j] + (j % 2 ? x : y));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
for (let j = 0; j < segment.length; j += 1) {
|
|
493
|
+
absoluteSegment.push(segment[j]);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// @ts-ignore
|
|
497
|
+
resultArray.push(absoluteSegment);
|
|
416
498
|
|
|
417
|
-
|
|
418
|
-
|
|
499
|
+
const segLength = absoluteSegment.length;
|
|
500
|
+
switch (absCommand) {
|
|
501
|
+
case 'Z':
|
|
502
|
+
x = mx;
|
|
503
|
+
y = my;
|
|
504
|
+
break;
|
|
505
|
+
case 'H':
|
|
506
|
+
x = +absoluteSegment[1];
|
|
507
|
+
break;
|
|
508
|
+
case 'V':
|
|
509
|
+
y = +absoluteSegment[1];
|
|
510
|
+
break;
|
|
511
|
+
default:
|
|
512
|
+
x = +absoluteSegment[segLength - 2];
|
|
513
|
+
y = +absoluteSegment[segLength - 1];
|
|
419
514
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
515
|
+
if (absCommand === 'M') {
|
|
516
|
+
mx = x;
|
|
517
|
+
my = y;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
424
520
|
}
|
|
425
|
-
return m;
|
|
426
|
-
}
|
|
427
521
|
|
|
428
|
-
|
|
429
|
-
* Creates a new mutable `CSSMatrix` object given an array float values.
|
|
430
|
-
*
|
|
431
|
-
* If the array has six values, the result is a 2D matrix; if the array has 16 values,
|
|
432
|
-
* the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
|
|
433
|
-
*
|
|
434
|
-
* @param {Array} array The source `Array` to feed values from.
|
|
435
|
-
* @return {CSSMatrix} a The source array to feed values from.
|
|
436
|
-
*/
|
|
437
|
-
function fromArray(a) {
|
|
438
|
-
return feedFromArray(new CSSMatrix(), a);
|
|
522
|
+
return resultArray;
|
|
439
523
|
}
|
|
440
524
|
|
|
441
525
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* If the array has six values, the result is a 2D matrix; if the array has 16 values,
|
|
446
|
-
* the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
|
|
526
|
+
* Iterates an array to check if it's a `pathArray`
|
|
527
|
+
* with relative values.
|
|
447
528
|
*
|
|
448
|
-
* @param {
|
|
449
|
-
* @
|
|
529
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to be checked
|
|
530
|
+
* @returns {boolean} iteration result
|
|
450
531
|
*/
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
// function fromFloat64Array(a){ // more of an alias
|
|
456
|
-
// return feedFromArray(new CSSMatrix(), a);
|
|
457
|
-
// }
|
|
532
|
+
function isRelativeArray(path) {
|
|
533
|
+
return Array.isArray(path) && isPathArray(path)
|
|
534
|
+
&& path.slice(1).every((seg) => seg[0] === seg[0].toLowerCase());
|
|
535
|
+
}
|
|
458
536
|
|
|
459
537
|
/**
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* The method accepts *Float64Array* / *Float32Array* / any *Array* values, the result of
|
|
464
|
-
* `DOMMatrix` / `CSSMatrix` instance method calls `toFloat64Array()` / `toFloat32Array()`.
|
|
538
|
+
* Parses a path string value or object and returns an array
|
|
539
|
+
* of segments, all converted to relative values.
|
|
465
540
|
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* @param {String} source the *String* resulted from `getComputedStyle()`.
|
|
470
|
-
* @param {Array} source the *Array* resulted from `toFloat64Array()`.
|
|
541
|
+
* @param {string | SVGPC.pathArray} pathInput the path string | object
|
|
542
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with relative values
|
|
471
543
|
*/
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return m;
|
|
477
|
-
} if (source.length && typeof source[0] === 'string' && source[0].length) { // CSS transform String source
|
|
478
|
-
const string = String(source[0]).trim();
|
|
479
|
-
let type = '';
|
|
480
|
-
let values = [];
|
|
481
|
-
|
|
482
|
-
if (string === 'none') return m;
|
|
544
|
+
function pathToRelative(pathInput) {
|
|
545
|
+
if (Array.isArray(pathInput) && isRelativeArray(pathInput)) {
|
|
546
|
+
return clonePath(pathInput);
|
|
547
|
+
}
|
|
483
548
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
549
|
+
const path = parsePathString(pathInput);
|
|
550
|
+
const ii = path.length;
|
|
551
|
+
/** @type {SVGPC.pathArray} */
|
|
552
|
+
const resultArray = [];
|
|
553
|
+
let x = 0;
|
|
554
|
+
let y = 0;
|
|
555
|
+
let mx = 0;
|
|
556
|
+
let my = 0;
|
|
557
|
+
let start = 0;
|
|
487
558
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
} else if (Array.isArray(source[0])) { // Float32Array,Float64Array source
|
|
496
|
-
feedFromArray(m, source[0]);
|
|
497
|
-
} else if (Array.isArray(source)) { // Arguments list come here
|
|
498
|
-
feedFromArray(m, source);
|
|
559
|
+
if (path[0][0] === 'M') {
|
|
560
|
+
x = +path[0][1];
|
|
561
|
+
y = +path[0][2];
|
|
562
|
+
mx = x;
|
|
563
|
+
my = y;
|
|
564
|
+
start += 1;
|
|
565
|
+
resultArray.push(['M', x, y]);
|
|
499
566
|
}
|
|
500
|
-
return m;
|
|
501
|
-
};
|
|
502
567
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
* @matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
|
|
510
|
-
* @matrix *matrix(a, b, c, d, e, f)*
|
|
511
|
-
*
|
|
512
|
-
* @return {String} `String` representation of the matrix
|
|
513
|
-
*/
|
|
514
|
-
CSSMatrixProto.toString = function toString() {
|
|
515
|
-
const m = this;
|
|
516
|
-
const type = m.is2D ? 'matrix' : 'matrix3d';
|
|
517
|
-
|
|
518
|
-
return `${type}(${m.toArray(1).join(',')})`;
|
|
519
|
-
};
|
|
568
|
+
for (let i = start; i < ii; i += 1) {
|
|
569
|
+
const segment = path[i];
|
|
570
|
+
const [pathCommand] = segment;
|
|
571
|
+
const relativeCommand = pathCommand.toLowerCase();
|
|
572
|
+
const relativeSegment = []; // this a test to please TS
|
|
573
|
+
let newSeg = [];
|
|
520
574
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
* Other methods make use of this method to feed their output values from this matrix.
|
|
527
|
-
*
|
|
528
|
-
* @param {Boolean} transposed changes the order of elements in the output
|
|
529
|
-
* @return {Array} an *Array* representation of the matrix
|
|
530
|
-
*/
|
|
531
|
-
CSSMatrixProto.toArray = function toArray(transposed) {
|
|
532
|
-
const m = this;
|
|
533
|
-
let result;
|
|
575
|
+
if (pathCommand !== relativeCommand) {
|
|
576
|
+
relativeSegment[0] = relativeCommand;
|
|
577
|
+
switch (relativeCommand) {
|
|
578
|
+
case 'a':
|
|
579
|
+
newSeg = segment.slice(1, -2).concat([+segment[6] - x, +segment[7] - y]);
|
|
534
580
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
return result;
|
|
549
|
-
};
|
|
581
|
+
for (let j = 0; j < newSeg.length; j += 1) {
|
|
582
|
+
relativeSegment.push(newSeg[j]);
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
case 'v':
|
|
586
|
+
relativeSegment[1] = +segment[1] - y;
|
|
587
|
+
break;
|
|
588
|
+
default:
|
|
589
|
+
// for is here to stay for eslint
|
|
590
|
+
for (let j = 1; j < segment.length; j += 1) {
|
|
591
|
+
relativeSegment.push(+segment[j] - (j % 2 ? x : y));
|
|
592
|
+
}
|
|
550
593
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
594
|
+
if (relativeCommand === 'm') {
|
|
595
|
+
mx = +segment[1];
|
|
596
|
+
my = +segment[2];
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
if (pathCommand === 'm') {
|
|
601
|
+
mx = +segment[1] + x;
|
|
602
|
+
my = +segment[2] + y;
|
|
603
|
+
}
|
|
604
|
+
for (let j = 0; j < segment.length; j += 1) {
|
|
605
|
+
relativeSegment.push(segment[j]);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
// @ts-ignore
|
|
609
|
+
resultArray.push(relativeSegment);
|
|
610
|
+
|
|
611
|
+
const segLength = relativeSegment.length;
|
|
612
|
+
switch (relativeSegment[0]) {
|
|
613
|
+
case 'z':
|
|
614
|
+
x = mx;
|
|
615
|
+
y = my;
|
|
616
|
+
break;
|
|
617
|
+
case 'h':
|
|
618
|
+
x += +relativeSegment[segLength - 1];
|
|
619
|
+
break;
|
|
620
|
+
case 'v':
|
|
621
|
+
y += +relativeSegment[segLength - 1];
|
|
622
|
+
break;
|
|
623
|
+
default:
|
|
624
|
+
x += +resultArray[i][segLength - 2];
|
|
625
|
+
y += +resultArray[i][segLength - 1];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return resultArray;
|
|
630
|
+
}
|
|
562
631
|
|
|
563
632
|
/**
|
|
633
|
+
* Rounds the values of a `pathArray` instance to
|
|
634
|
+
* a specified amount of decimals and returns it.
|
|
564
635
|
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
636
|
+
* @param {SVGPC.pathArray} path the source `pathArray`
|
|
637
|
+
* @param {null | number} round the amount of decimals to round numbers to
|
|
638
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` with rounded values
|
|
567
639
|
*/
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
640
|
+
function roundPath(path, round) {
|
|
641
|
+
const { round: defaultRound, decimals: defaultDecimals } = SVGPCO;
|
|
642
|
+
const decimalsOption = round && !Number.isNaN(+round) ? +round
|
|
643
|
+
: defaultRound && defaultDecimals;
|
|
644
|
+
|
|
645
|
+
if (!decimalsOption) return clonePath(path);
|
|
646
|
+
|
|
647
|
+
// @ts-ignore
|
|
648
|
+
return path.map((seg) => seg.map((c) => {
|
|
649
|
+
const nr = +c;
|
|
650
|
+
const dc = 10 ** decimalsOption;
|
|
651
|
+
if (!Number.isNaN(nr)) {
|
|
652
|
+
return nr % 1 === 0 ? nr : Math.round(nr * dc) / dc;
|
|
653
|
+
}
|
|
654
|
+
return c;
|
|
655
|
+
}));
|
|
656
|
+
}
|
|
571
657
|
|
|
572
658
|
/**
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
* component is undefined, a 0 value is used in its place. This matrix is not
|
|
576
|
-
* modified.
|
|
659
|
+
* Returns a valid `d` attribute string value created
|
|
660
|
+
* by rounding values and concatenating the `pathArray` segments.
|
|
577
661
|
*
|
|
578
|
-
* @param {
|
|
579
|
-
* @param {number}
|
|
580
|
-
* @
|
|
581
|
-
* @return {CSSMatrix} The result matrix
|
|
662
|
+
* @param {SVGPC.pathArray} path the `pathArray` object
|
|
663
|
+
* @param {number | null} round amount of decimals to round values to
|
|
664
|
+
* @returns {string} the concatenated path string
|
|
582
665
|
*/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
let Z = z;
|
|
588
|
-
if (Z == null) Z = 0;
|
|
589
|
-
if (Y == null) Y = 0;
|
|
590
|
-
return Multiply(this, Translate(X, Y, Z));
|
|
591
|
-
};
|
|
666
|
+
function pathToString(path, round) {
|
|
667
|
+
return roundPath(path, round)
|
|
668
|
+
.map((x) => x[0].concat(x.slice(1).join(' '))).join('');
|
|
669
|
+
}
|
|
592
670
|
|
|
593
671
|
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* a 1 value is used in its place. If the y component is undefined, the x
|
|
597
|
-
* component value is used in its place. This matrix is not modified.
|
|
672
|
+
* Returns the missing control point from an
|
|
673
|
+
* T (shorthand quadratic bezier) segment.
|
|
598
674
|
*
|
|
599
|
-
* @param {
|
|
600
|
-
* @param {
|
|
601
|
-
* @param {
|
|
602
|
-
* @
|
|
675
|
+
* @param {Number} x1 curve start x
|
|
676
|
+
* @param {Number} y1 curve start y
|
|
677
|
+
* @param {Number} qx control point x
|
|
678
|
+
* @param {Number} qy control point y
|
|
679
|
+
* @param {String} prevCommand the previous path command
|
|
680
|
+
* @returns {Object} the missing control point
|
|
603
681
|
*/
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (Z == null) Z = x;
|
|
610
|
-
|
|
611
|
-
return Multiply(this, Scale(X, Y, Z));
|
|
612
|
-
};
|
|
682
|
+
function shorthandToQuad(x1, y1, qx, qy, prevCommand) {
|
|
683
|
+
return 'QT'.indexOf(prevCommand) > -1
|
|
684
|
+
? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
|
|
685
|
+
: { qx: x1, qy: y1 };
|
|
686
|
+
}
|
|
613
687
|
|
|
614
688
|
/**
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
* If the y and z components are undefined, the x value is used to rotate the
|
|
618
|
-
* object about the z axis, as though the vector (0,0,x) were passed. All
|
|
619
|
-
* rotation values are in degrees. This matrix is not modified.
|
|
689
|
+
* Returns the missing control point from an
|
|
690
|
+
* S (shorthand cubic bezier) segment.
|
|
620
691
|
*
|
|
621
|
-
* @param {
|
|
622
|
-
* @param {
|
|
623
|
-
* @param {
|
|
624
|
-
* @
|
|
692
|
+
* @param {Number} x1 curve start x
|
|
693
|
+
* @param {Number} y1 curve start y
|
|
694
|
+
* @param {Number} x2 curve end x
|
|
695
|
+
* @param {Number} y2 curve end y
|
|
696
|
+
* @param {String} prevCommand the previous path command
|
|
697
|
+
* @returns {Object} the missing control point
|
|
625
698
|
*/
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (RZ == null) { RZ = RX; RX = 0; }
|
|
632
|
-
return Multiply(this, Rotate(RX, RY, RZ));
|
|
633
|
-
};
|
|
699
|
+
function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
|
|
700
|
+
return 'CS'.indexOf(prevCommand) > -1
|
|
701
|
+
? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
|
|
702
|
+
: { x1, y1 };
|
|
703
|
+
}
|
|
634
704
|
|
|
635
705
|
/**
|
|
636
|
-
*
|
|
637
|
-
* multiplied by a rotation matrix with the given axis and `angle`. The right-hand
|
|
638
|
-
* rule is used to determine the direction of rotation. All rotation values are
|
|
639
|
-
* in degrees. This matrix is not modified.
|
|
706
|
+
* Normalizes a single segment of a `pathArray` object.
|
|
640
707
|
*
|
|
641
|
-
* @param {
|
|
642
|
-
* @param {
|
|
643
|
-
* @param {
|
|
644
|
-
* @
|
|
645
|
-
* @return {CSSMatrix} The `CSSMatrix` result
|
|
708
|
+
* @param {SVGPC.pathSegment} segment the segment object
|
|
709
|
+
* @param {Object} params the coordinates of the previous segment
|
|
710
|
+
* @param {String} prevCommand the path command of the previous segment
|
|
711
|
+
* @returns {SVGPC.pathSegment} the normalized segment
|
|
646
712
|
*/
|
|
713
|
+
function normalizeSegment(segment, params, prevCommand) {
|
|
714
|
+
const [pathCommand] = segment;
|
|
715
|
+
const xy = segment.slice(1);
|
|
716
|
+
let result = segment;
|
|
647
717
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
718
|
+
if ('TQ'.indexOf(segment[0]) < 0) {
|
|
719
|
+
// optional but good to be cautious
|
|
720
|
+
params.qx = null;
|
|
721
|
+
params.qy = null;
|
|
651
722
|
}
|
|
652
|
-
|
|
653
|
-
|
|
723
|
+
|
|
724
|
+
if (pathCommand === 'H') {
|
|
725
|
+
result = ['L', segment[1], params.y1];
|
|
726
|
+
} else if (pathCommand === 'V') {
|
|
727
|
+
result = ['L', params.x1, segment[1]];
|
|
728
|
+
} else if (pathCommand === 'S') {
|
|
729
|
+
const { x1, y1 } = shorthandToCubic(params.x1, params.y1, params.x2, params.y2, prevCommand);
|
|
730
|
+
params.x1 = x1;
|
|
731
|
+
params.y1 = y1;
|
|
732
|
+
result = ['C', x1, y1].concat(xy);
|
|
733
|
+
} else if (pathCommand === 'T') {
|
|
734
|
+
const { qx, qy } = shorthandToQuad(params.x1, params.y1, params.qx, params.qy, prevCommand);
|
|
735
|
+
params.qx = qx;
|
|
736
|
+
params.qy = qy;
|
|
737
|
+
result = ['Q', qx, qy].concat(xy);
|
|
738
|
+
} else if (pathCommand === 'Q') {
|
|
739
|
+
const [nqx, nqy] = xy;
|
|
740
|
+
params.qx = nqx;
|
|
741
|
+
params.qy = nqy;
|
|
742
|
+
}
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
654
745
|
|
|
655
746
|
/**
|
|
656
|
-
*
|
|
657
|
-
*
|
|
747
|
+
* Iterates an array to check if it's a `pathArray`
|
|
748
|
+
* with all segments are in non-shorthand notation
|
|
749
|
+
* with absolute values.
|
|
658
750
|
*
|
|
659
|
-
* @param {
|
|
660
|
-
* @
|
|
751
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to be checked
|
|
752
|
+
* @returns {boolean} iteration result
|
|
661
753
|
*/
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
754
|
+
function isNormalizedArray(path) {
|
|
755
|
+
return Array.isArray(path) && isPathArray(path) && path.every((seg) => {
|
|
756
|
+
const lk = seg[0].toLowerCase();
|
|
757
|
+
return paramsCount[lk] === seg.length - 1 && ('ACLMQZ').includes(seg[0]); // achlmqstvz
|
|
758
|
+
});
|
|
759
|
+
}
|
|
666
760
|
|
|
667
761
|
/**
|
|
668
|
-
*
|
|
669
|
-
*
|
|
762
|
+
* Normalizes a `path` object for further processing:
|
|
763
|
+
* * convert segments to absolute values
|
|
764
|
+
* * convert shorthand path commands to their non-shorthand notation
|
|
670
765
|
*
|
|
671
|
-
* @param {
|
|
672
|
-
* @
|
|
766
|
+
* @param {string | SVGPC.pathArray} pathInput the string to be parsed or 'pathArray'
|
|
767
|
+
* @returns {SVGPC.pathArray} the normalized `pathArray`
|
|
673
768
|
*/
|
|
769
|
+
function normalizePath(pathInput) { // path|pathString
|
|
770
|
+
if (Array.isArray(pathInput) && isNormalizedArray(pathInput)) {
|
|
771
|
+
return clonePath(pathInput);
|
|
772
|
+
}
|
|
674
773
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
774
|
+
const path = pathToAbsolute(pathInput);
|
|
775
|
+
const params = {
|
|
776
|
+
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
777
|
+
};
|
|
778
|
+
const allPathCommands = [];
|
|
779
|
+
const ii = path.length;
|
|
780
|
+
let prevCommand = '';
|
|
781
|
+
let segment;
|
|
782
|
+
let seglen;
|
|
678
783
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
* @return {CSSMatrix} this `CSSMatrix` instance
|
|
683
|
-
*/
|
|
684
|
-
CSSMatrixProto.setIdentity = function setIdentity() {
|
|
685
|
-
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
686
|
-
return feedFromArray(this, identity);
|
|
687
|
-
};
|
|
784
|
+
for (let i = 0; i < ii; i += 1) {
|
|
785
|
+
// save current path command
|
|
786
|
+
const [pathCommand] = path[i];
|
|
688
787
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
* of the `DOMMatrix` constructor.
|
|
696
|
-
*
|
|
697
|
-
* JavaScript implementation by thednp
|
|
698
|
-
*
|
|
699
|
-
* @param {Point} point the *Object* with `x`, `y`, `z` and `w` components
|
|
700
|
-
* @return {Point} a new `{x,y,z,w}` *Object*
|
|
701
|
-
*/
|
|
702
|
-
CSSMatrixProto.transformPoint = function transformPoint(v) {
|
|
703
|
-
const M = this;
|
|
704
|
-
let m = Translate(v.x, v.y, v.z);
|
|
788
|
+
// Save current path command
|
|
789
|
+
allPathCommands[i] = pathCommand;
|
|
790
|
+
// Get previous path command
|
|
791
|
+
if (i) prevCommand = allPathCommands[i - 1];
|
|
792
|
+
// Previous path command is inputted to processSegment
|
|
793
|
+
path[i] = normalizeSegment(path[i], params, prevCommand);
|
|
705
794
|
|
|
706
|
-
|
|
707
|
-
|
|
795
|
+
segment = path[i];
|
|
796
|
+
seglen = segment.length;
|
|
708
797
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
798
|
+
params.x1 = +segment[seglen - 2];
|
|
799
|
+
params.y1 = +segment[seglen - 1];
|
|
800
|
+
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
801
|
+
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
802
|
+
}
|
|
803
|
+
return path;
|
|
804
|
+
}
|
|
716
805
|
|
|
717
806
|
/**
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
* Neither the matrix nor the original vector are altered.
|
|
807
|
+
* Reverses all segments and their values of a `pathArray`
|
|
808
|
+
* and returns a new instance.
|
|
721
809
|
*
|
|
722
|
-
* @param {
|
|
723
|
-
* @
|
|
810
|
+
* @param {SVGPC.pathArray} pathInput the source `pathArray`
|
|
811
|
+
* @returns {SVGPC.pathArray} the reversed `pathArray`
|
|
724
812
|
*/
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const w = m.m41 * t.x + m.m42 * t.y + m.m43 * t.z + m.m44 * t.w;
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
x: x / w,
|
|
734
|
-
y: y / w,
|
|
735
|
-
z: z / w,
|
|
736
|
-
w,
|
|
737
|
-
};
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
// Add Transform Functions to CSSMatrix object
|
|
741
|
-
CSSMatrix.Translate = Translate;
|
|
742
|
-
CSSMatrix.Rotate = Rotate;
|
|
743
|
-
CSSMatrix.RotateAxisAngle = RotateAxisAngle;
|
|
744
|
-
CSSMatrix.Scale = Scale;
|
|
745
|
-
CSSMatrix.SkewX = SkewX;
|
|
746
|
-
CSSMatrix.SkewY = SkewY;
|
|
747
|
-
CSSMatrix.Multiply = Multiply;
|
|
748
|
-
CSSMatrix.fromMatrix = fromMatrix;
|
|
749
|
-
CSSMatrix.fromArray = fromArray;
|
|
750
|
-
CSSMatrix.feedFromArray = feedFromArray;
|
|
813
|
+
function reversePath(pathInput) {
|
|
814
|
+
const absolutePath = pathToAbsolute(pathInput);
|
|
815
|
+
const isClosed = absolutePath.slice(-1)[0][0] === 'Z';
|
|
816
|
+
let reversedPath = [];
|
|
817
|
+
let segLength = 0;
|
|
751
818
|
|
|
752
|
-
|
|
819
|
+
reversedPath = normalizePath(absolutePath).map((segment, i) => {
|
|
820
|
+
segLength = segment.length;
|
|
821
|
+
return {
|
|
822
|
+
seg: absolutePath[i], // absolute
|
|
823
|
+
n: segment, // normalized
|
|
824
|
+
c: absolutePath[i][0], // pathCommand
|
|
825
|
+
x: segment[segLength - 2], // x
|
|
826
|
+
y: segment[segLength - 1], // y
|
|
827
|
+
};
|
|
828
|
+
}).map((seg, i, path) => {
|
|
829
|
+
const segment = seg.seg;
|
|
830
|
+
const data = seg.n;
|
|
831
|
+
const prevSeg = i && path[i - 1];
|
|
832
|
+
const nextSeg = path[i + 1] && path[i + 1];
|
|
833
|
+
const pathCommand = seg.c;
|
|
834
|
+
const pLen = path.length;
|
|
835
|
+
const x = i ? path[i - 1].x : path[pLen - 1].x;
|
|
836
|
+
const y = i ? path[i - 1].y : path[pLen - 1].y;
|
|
837
|
+
let result = [];
|
|
753
838
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
839
|
+
switch (pathCommand) {
|
|
840
|
+
case 'M':
|
|
841
|
+
result = isClosed ? ['Z'] : [pathCommand, x, y];
|
|
842
|
+
break;
|
|
843
|
+
case 'A':
|
|
844
|
+
result = segment.slice(0, -3).concat([(segment[5] === 1 ? 0 : 1), x, y]);
|
|
845
|
+
break;
|
|
846
|
+
case 'C':
|
|
847
|
+
if (nextSeg && nextSeg.c === 'S') {
|
|
848
|
+
result = ['S', segment[1], segment[2], x, y];
|
|
849
|
+
} else {
|
|
850
|
+
result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y];
|
|
851
|
+
}
|
|
852
|
+
break;
|
|
853
|
+
case 'S':
|
|
854
|
+
if ((prevSeg && 'CS'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'S'))) {
|
|
855
|
+
result = ['C', data[3], data[4], data[1], data[2], x, y];
|
|
856
|
+
} else {
|
|
857
|
+
result = [pathCommand, data[1], data[2], x, y];
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
860
|
+
case 'Q':
|
|
861
|
+
if (nextSeg && nextSeg.c === 'T') {
|
|
862
|
+
result = ['T', x, y];
|
|
863
|
+
} else {
|
|
864
|
+
result = segment.slice(0, -2).concat([x, y]);
|
|
865
|
+
}
|
|
866
|
+
break;
|
|
867
|
+
case 'T':
|
|
868
|
+
if ((prevSeg && 'QT'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'T'))) {
|
|
869
|
+
result = ['Q', data[1], data[2], x, y];
|
|
870
|
+
} else {
|
|
871
|
+
result = [pathCommand, x, y];
|
|
872
|
+
}
|
|
873
|
+
break;
|
|
874
|
+
case 'Z':
|
|
875
|
+
result = ['M', x, y];
|
|
876
|
+
break;
|
|
877
|
+
case 'H':
|
|
878
|
+
result = [pathCommand, x];
|
|
879
|
+
break;
|
|
880
|
+
case 'V':
|
|
881
|
+
result = [pathCommand, y];
|
|
882
|
+
break;
|
|
883
|
+
default:
|
|
884
|
+
result = segment.slice(0, -2).concat([x, y]);
|
|
758
885
|
}
|
|
759
|
-
|
|
886
|
+
|
|
887
|
+
return result;
|
|
760
888
|
});
|
|
889
|
+
// @ts-ignore
|
|
890
|
+
return isClosed ? reversedPath.reverse()
|
|
891
|
+
: [reversedPath[0]].concat(reversedPath.slice(1).reverse());
|
|
761
892
|
}
|
|
762
893
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
return result;
|
|
894
|
+
/**
|
|
895
|
+
* Split a path into an `Array` of sub-path strings.
|
|
896
|
+
*
|
|
897
|
+
* In the process, values are converted to absolute
|
|
898
|
+
* for visual consistency.
|
|
899
|
+
*
|
|
900
|
+
* @param {SVGPC.pathArray | string} pathInput the cubic-bezier parameters
|
|
901
|
+
* @return {string[]} an array with all sub-path strings
|
|
902
|
+
*/
|
|
903
|
+
function splitPath(pathInput) {
|
|
904
|
+
return pathToString(pathToAbsolute(pathInput), 0)
|
|
905
|
+
.replace(/(m|M)/g, '|$1')
|
|
906
|
+
.split('|')
|
|
907
|
+
.map((s) => s.trim())
|
|
908
|
+
.filter((s) => s);
|
|
780
909
|
}
|
|
781
910
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
911
|
+
/**
|
|
912
|
+
* Optimizes a `pathArray` object:
|
|
913
|
+
* * convert segments to absolute and relative values
|
|
914
|
+
* * create a new `pathArray` with elements with shortest segments
|
|
915
|
+
* from absolute and relative `pathArray`s
|
|
916
|
+
*
|
|
917
|
+
* @param {string | SVGPC.pathArray} pathInput a string or `pathArray`
|
|
918
|
+
* @param {number | null} round the amount of decimals to round values to
|
|
919
|
+
* @returns {SVGPC.pathArray} the optimized `pathArray`
|
|
920
|
+
*/
|
|
921
|
+
function optimizePath(pathInput, round) {
|
|
922
|
+
const absolutePath = roundPath(pathToAbsolute(pathInput), round);
|
|
923
|
+
const relativePath = roundPath(pathToRelative(pathInput), round);
|
|
924
|
+
return absolutePath.map((x, i) => {
|
|
925
|
+
if (i) {
|
|
926
|
+
return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
|
|
794
927
|
}
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
var paramsCount = {
|
|
800
|
-
a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0,
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
function isPathArray(pathArray) {
|
|
804
|
-
return Array.isArray(pathArray) && pathArray.every((seg) => {
|
|
805
|
-
const pathCommand = seg[0].toLowerCase();
|
|
806
|
-
return paramsCount[pathCommand] === seg.length - 1 && /[achlmrqstvz]/g.test(pathCommand);
|
|
928
|
+
return x;
|
|
807
929
|
});
|
|
808
930
|
}
|
|
809
931
|
|
|
810
|
-
|
|
811
|
-
|
|
932
|
+
/**
|
|
933
|
+
* A global namespace for epsilon.
|
|
934
|
+
*
|
|
935
|
+
* @type {Number}
|
|
936
|
+
*/
|
|
937
|
+
const epsilon = 1e-9;
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Returns an {x,y} vector rotated by a given
|
|
941
|
+
* angle in radian.
|
|
942
|
+
*
|
|
943
|
+
* @param {Number} x the initial vector x
|
|
944
|
+
* @param {Number} y the initial vector y
|
|
945
|
+
* @returns {{x: number, y: number}} the rotated vector
|
|
946
|
+
*/
|
|
947
|
+
function rotateVector(x, y, rad) {
|
|
948
|
+
const X = x * Math.cos(rad) - y * Math.sin(rad);
|
|
949
|
+
const Y = x * Math.sin(rad) + y * Math.cos(rad);
|
|
950
|
+
return { x: X, y: Y };
|
|
812
951
|
}
|
|
813
952
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
953
|
+
/**
|
|
954
|
+
* Converts A (arc-to) segments to C (cubic-bezier-to).
|
|
955
|
+
*
|
|
956
|
+
* For more information of where this math came from visit:
|
|
957
|
+
* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
|
958
|
+
*
|
|
959
|
+
* @param {number} x1 the starting x position
|
|
960
|
+
* @param {number} y1 the starting y position
|
|
961
|
+
* @param {number} rx x-radius of the arc
|
|
962
|
+
* @param {number} ry y-radius of the arc
|
|
963
|
+
* @param {number} angle x-axis-rotation of the arc
|
|
964
|
+
* @param {number} LAF large-arc-flag of the arc
|
|
965
|
+
* @param {number} SF sweep-flag of the arc
|
|
966
|
+
* @param {number} x2 the ending x position
|
|
967
|
+
* @param {number} y2 the ending y position
|
|
968
|
+
* @param {number[] | null} recursive the parameters needed to split arc into 2 segments
|
|
969
|
+
* @return {number[]} the resulting cubic-bezier segment(s)
|
|
970
|
+
*/
|
|
971
|
+
function arcToCubic(x1, y1, rx, ry, angle, LAF, SF, x2, y2, recursive) {
|
|
972
|
+
const d120 = (Math.PI * 120) / 180;
|
|
973
|
+
const rad = (Math.PI / 180) * (angle || 0);
|
|
974
|
+
/** @type {number[]} */
|
|
975
|
+
let res = [];
|
|
976
|
+
let X1 = x1;
|
|
977
|
+
let X2 = x2;
|
|
978
|
+
let Y1 = y1;
|
|
979
|
+
let Y2 = y2;
|
|
980
|
+
let RX = rx;
|
|
981
|
+
let RY = ry;
|
|
982
|
+
let xy;
|
|
983
|
+
let f1;
|
|
984
|
+
let f2;
|
|
985
|
+
let cx;
|
|
986
|
+
let cy;
|
|
818
987
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
988
|
+
if (!recursive) {
|
|
989
|
+
xy = rotateVector(X1, Y1, -rad);
|
|
990
|
+
X1 = xy.x;
|
|
991
|
+
Y1 = xy.y;
|
|
992
|
+
xy = rotateVector(X2, Y2, -rad);
|
|
993
|
+
X2 = xy.x;
|
|
994
|
+
Y2 = xy.y;
|
|
826
995
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
996
|
+
const x = (X1 - X2) / 2;
|
|
997
|
+
const y = (Y1 - Y2) / 2;
|
|
998
|
+
let h = (x ** 2) / (RX ** 2) + (y ** 2) / (RY ** 2);
|
|
999
|
+
if (h > 1) {
|
|
1000
|
+
h = Math.sqrt(h);
|
|
1001
|
+
RX *= h;
|
|
1002
|
+
RY *= h;
|
|
835
1003
|
}
|
|
836
|
-
|
|
837
|
-
|
|
1004
|
+
const rx2 = RX ** 2;
|
|
1005
|
+
const ry2 = RY ** 2;
|
|
1006
|
+
const k = (LAF === SF ? -1 : 1)
|
|
1007
|
+
* Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
|
|
1008
|
+
/ (rx2 * y * y + ry2 * x * x)));
|
|
1009
|
+
|
|
1010
|
+
cx = ((k * RX * y) / RY) + ((X1 + X2) / 2);
|
|
1011
|
+
cy = ((k * -RY * x) / RX) + ((Y1 + Y2) / 2);
|
|
838
1012
|
|
|
839
|
-
|
|
1013
|
+
// eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
|
|
1014
|
+
f1 = Math.asin((((Y1 - cy) / RY) * 10 ** 9 >> 0) / (10 ** 9));
|
|
1015
|
+
// eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
|
|
1016
|
+
f2 = Math.asin((((Y2 - cy) / RY) * 10 ** 9 >> 0) / (10 ** 9));
|
|
840
1017
|
|
|
841
|
-
|
|
842
|
-
|
|
1018
|
+
f1 = X1 < cx ? Math.PI - f1 : f1;
|
|
1019
|
+
f2 = X2 < cx ? Math.PI - f2 : f2;
|
|
843
1020
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
state.index += 1;
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
1021
|
+
if (f1 < 0) { f1 = Math.PI * 2 + f1; }
|
|
1022
|
+
if (f2 < 0) { f2 = Math.PI * 2 + f2; }
|
|
849
1023
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1024
|
+
if (SF && f1 > f2) {
|
|
1025
|
+
f1 -= Math.PI * 2;
|
|
1026
|
+
}
|
|
1027
|
+
if (!SF && f2 > f1) {
|
|
1028
|
+
f2 -= Math.PI * 2;
|
|
1029
|
+
}
|
|
1030
|
+
} else {
|
|
1031
|
+
const [r1, r2, r3, r4] = recursive;
|
|
1032
|
+
f1 = r1;
|
|
1033
|
+
f2 = r2;
|
|
1034
|
+
cx = r3;
|
|
1035
|
+
cy = r4;
|
|
854
1036
|
}
|
|
855
1037
|
|
|
856
|
-
|
|
857
|
-
state.err = `${invalidPathValue}: invalid Arc flag ${ch}`;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function isDigit(code) {
|
|
861
|
-
return (code >= 48 && code <= 57); // 0..9
|
|
862
|
-
}
|
|
1038
|
+
let df = f2 - f1;
|
|
863
1039
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
let zeroFirst = false;
|
|
869
|
-
let hasCeiling = false;
|
|
870
|
-
let hasDecimal = false;
|
|
871
|
-
let hasDot = false;
|
|
872
|
-
let ch;
|
|
1040
|
+
if (Math.abs(df) > d120) {
|
|
1041
|
+
const f2old = f2;
|
|
1042
|
+
const x2old = X2;
|
|
1043
|
+
const y2old = Y2;
|
|
873
1044
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1045
|
+
f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
|
|
1046
|
+
X2 = cx + RX * Math.cos(f2);
|
|
1047
|
+
Y2 = cy + RY * Math.sin(f2);
|
|
1048
|
+
res = arcToCubic(X2, Y2, RX, RY, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
|
|
878
1049
|
}
|
|
879
|
-
ch = state.pathValue.charCodeAt(index);
|
|
880
1050
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1051
|
+
df = f2 - f1;
|
|
1052
|
+
const c1 = Math.cos(f1);
|
|
1053
|
+
const s1 = Math.sin(f1);
|
|
1054
|
+
const c2 = Math.cos(f2);
|
|
1055
|
+
const s2 = Math.sin(f2);
|
|
1056
|
+
const t = Math.tan(df / 4);
|
|
1057
|
+
const hx = (4 / 3) * RX * t;
|
|
1058
|
+
const hy = (4 / 3) * RY * t;
|
|
1059
|
+
const m1 = [X1, Y1];
|
|
1060
|
+
const m2 = [X1 + hx * s1, Y1 - hy * c1];
|
|
1061
|
+
const m3 = [X2 + hx * s2, Y2 - hy * c2];
|
|
1062
|
+
const m4 = [X2, Y2];
|
|
1063
|
+
m2[0] = 2 * m1[0] - m2[0];
|
|
1064
|
+
m2[1] = 2 * m1[1] - m2[1];
|
|
885
1065
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (!isDigit(ch) && ch !== 0x2E/* . */) {
|
|
889
|
-
// state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
|
|
890
|
-
state.err = `${invalidPathValue} at index ${index}: ${state.pathValue[index]} is not a number`;
|
|
891
|
-
return;
|
|
1066
|
+
if (recursive) {
|
|
1067
|
+
return [m2, m3, m4].concat(res).map((n) => +n);
|
|
892
1068
|
}
|
|
1069
|
+
res = [m2, m3, m4].concat(res).join().split(',').map((n) => +n);
|
|
1070
|
+
return res.map((rz, i) => {
|
|
1071
|
+
if (i % 2) {
|
|
1072
|
+
return rotateVector(res[i - 1], rz, rad).y;
|
|
1073
|
+
}
|
|
1074
|
+
return rotateVector(rz, res[i + 1], rad).x;
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
893
1077
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1078
|
+
/**
|
|
1079
|
+
* Converts a Q (quadratic-bezier) segment to C (cubic-bezier).
|
|
1080
|
+
*
|
|
1081
|
+
* @param {Number} x1 curve start x
|
|
1082
|
+
* @param {Number} y1 curve start y
|
|
1083
|
+
* @param {Number} qx control point x
|
|
1084
|
+
* @param {Number} qy control point y
|
|
1085
|
+
* @param {Number} x2 curve end x
|
|
1086
|
+
* @param {Number} y2 curve end y
|
|
1087
|
+
* @returns {Number[]} the cubic-bezier segment
|
|
1088
|
+
*/
|
|
1089
|
+
function quadToCubic(x1, y1, qx, qy, x2, y2) {
|
|
1090
|
+
const r13 = 1 / 3;
|
|
1091
|
+
const r23 = 2 / 3;
|
|
1092
|
+
return [
|
|
1093
|
+
r13 * x1 + r23 * qx, // cpx1
|
|
1094
|
+
r13 * y1 + r23 * qy, // cpy1
|
|
1095
|
+
r13 * x2 + r23 * qx, // cpx2
|
|
1096
|
+
r13 * y2 + r23 * qy, // cpy2
|
|
1097
|
+
x2, y2, // x,y
|
|
1098
|
+
];
|
|
1099
|
+
}
|
|
897
1100
|
|
|
898
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* Returns the {x,y} coordinates of a point at a
|
|
1103
|
+
* given length of a cubic-bezier segment.
|
|
1104
|
+
*
|
|
1105
|
+
* @param {Number} p1x the starting point X
|
|
1106
|
+
* @param {Number} p1y the starting point Y
|
|
1107
|
+
* @param {Number} c1x the first control point X
|
|
1108
|
+
* @param {Number} c1y the first control point Y
|
|
1109
|
+
* @param {Number} c2x the second control point X
|
|
1110
|
+
* @param {Number} c2y the second control point Y
|
|
1111
|
+
* @param {Number} px2 the ending point X
|
|
1112
|
+
* @param {Number} py2 the ending point Y
|
|
1113
|
+
* @param {Number} t a [0-1] ratio
|
|
1114
|
+
* @returns {{x: number, y: number}} the requested {x,y} coordinates
|
|
1115
|
+
*/
|
|
1116
|
+
function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
1117
|
+
const t1 = 1 - t;
|
|
1118
|
+
return {
|
|
1119
|
+
x: (t1 ** 3) * p1x
|
|
1120
|
+
+ t1 * t1 * 3 * t * c1x
|
|
1121
|
+
+ t1 * 3 * t * t * c2x
|
|
1122
|
+
+ (t ** 3) * p2x,
|
|
1123
|
+
y: (t1 ** 3) * p1y
|
|
1124
|
+
+ t1 * t1 * 3 * t * c1y
|
|
1125
|
+
+ t1 * 3 * t * t * c2y
|
|
1126
|
+
+ (t ** 3) * p2y,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
899
1129
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1130
|
+
/**
|
|
1131
|
+
* Returns the coordinates of a specified distance
|
|
1132
|
+
* ratio between two points.
|
|
1133
|
+
*
|
|
1134
|
+
* @param {Number[]} a the first point coordinates
|
|
1135
|
+
* @param {Number[]} b the second point coordinates
|
|
1136
|
+
* @param {Number} t the ratio
|
|
1137
|
+
* @returns {Number[]} the midpoint coordinates
|
|
1138
|
+
*/
|
|
1139
|
+
function midPoint(a, b, t) {
|
|
1140
|
+
const [ax, ay] = a; const [bx, by] = b;
|
|
1141
|
+
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
|
1142
|
+
}
|
|
909
1143
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1144
|
+
/**
|
|
1145
|
+
* Converts an L (line-to) segment to C (cubic-bezier).
|
|
1146
|
+
*
|
|
1147
|
+
* @param {Number} x1 line start x
|
|
1148
|
+
* @param {Number} y1 line start y
|
|
1149
|
+
* @param {Number} x2 line end x
|
|
1150
|
+
* @param {Number} y2 line end y
|
|
1151
|
+
* @returns {Number[]} the cubic-bezier segment
|
|
1152
|
+
*/
|
|
1153
|
+
function lineToCubic(x1, y1, x2, y2) {
|
|
1154
|
+
const t = 0.5;
|
|
1155
|
+
const p0 = [x1, y1];
|
|
1156
|
+
const p1 = [x2, y2];
|
|
1157
|
+
const p2 = midPoint(p0, p1, t);
|
|
1158
|
+
const p3 = midPoint(p1, p2, t);
|
|
1159
|
+
const p4 = midPoint(p2, p3, t);
|
|
1160
|
+
const p5 = midPoint(p3, p4, t);
|
|
1161
|
+
const p6 = midPoint(p4, p5, t);
|
|
1162
|
+
const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
|
|
1163
|
+
const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
|
|
916
1164
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
index += 1;
|
|
920
|
-
while (isDigit(state.pathValue.charCodeAt(index))) {
|
|
921
|
-
index += 1;
|
|
922
|
-
hasDecimal = true;
|
|
923
|
-
}
|
|
924
|
-
ch = (index < max) ? state.pathValue.charCodeAt(index) : 0;
|
|
925
|
-
}
|
|
1165
|
+
return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
|
|
1166
|
+
}
|
|
926
1167
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1168
|
+
/**
|
|
1169
|
+
* Converts any segment to C (cubic-bezier).
|
|
1170
|
+
*
|
|
1171
|
+
* @param {SVGPC.pathSegment} segment the source segment
|
|
1172
|
+
* @param {SVGPC.parserParams} params the source segment parameters
|
|
1173
|
+
* @returns {SVGPC.pathSegment} the cubic-bezier segment
|
|
1174
|
+
*/
|
|
1175
|
+
function segmentToCubic(segment, params) {
|
|
1176
|
+
if ('TQ'.indexOf(segment[0]) < 0) {
|
|
1177
|
+
params.qx = null;
|
|
1178
|
+
params.qy = null;
|
|
1179
|
+
}
|
|
933
1180
|
|
|
934
|
-
|
|
1181
|
+
const [s1, s2] = segment.slice(1);
|
|
935
1182
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1183
|
+
switch (segment[0]) {
|
|
1184
|
+
case 'M':
|
|
1185
|
+
params.x = +s1;
|
|
1186
|
+
params.y = +s2;
|
|
1187
|
+
return segment;
|
|
1188
|
+
case 'A':
|
|
1189
|
+
// @ts-ignore
|
|
1190
|
+
return ['C'].concat(arcToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
|
|
1191
|
+
case 'Q':
|
|
1192
|
+
params.qx = +s1;
|
|
1193
|
+
params.qy = +s2;
|
|
1194
|
+
// @ts-ignore
|
|
1195
|
+
return ['C'].concat(quadToCubic.apply(0, [params.x1, params.y1].concat(segment.slice(1))));
|
|
1196
|
+
case 'L':
|
|
1197
|
+
// @ts-ignore
|
|
1198
|
+
return ['C'].concat(lineToCubic(params.x1, params.y1, segment[1], segment[2]));
|
|
1199
|
+
case 'Z':
|
|
1200
|
+
// @ts-ignore
|
|
1201
|
+
return ['C'].concat(lineToCubic(params.x1, params.y1, params.x, params.y));
|
|
949
1202
|
}
|
|
950
|
-
|
|
951
|
-
state.index = index;
|
|
952
|
-
state.param = +state.pathValue.slice(start, index);
|
|
1203
|
+
return segment;
|
|
953
1204
|
}
|
|
954
1205
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|| (ch >= 0x1680 && specialSpaces.indexOf(ch) >= 0);
|
|
963
|
-
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Splits an extended A (arc-to) segment into two cubic-bezier segments.
|
|
1208
|
+
*
|
|
1209
|
+
* @param {SVGPC.pathArray} path the `pathArray` this segment belongs to
|
|
1210
|
+
* @param {string[]} allPathCommands all previous path commands
|
|
1211
|
+
* @param {Number} i the index of the segment
|
|
1212
|
+
*/
|
|
964
1213
|
|
|
965
|
-
function
|
|
966
|
-
|
|
967
|
-
|
|
1214
|
+
function fixArc(path, allPathCommands, i) {
|
|
1215
|
+
if (path[i].length > 7) {
|
|
1216
|
+
path[i].shift();
|
|
1217
|
+
const segment = path[i];
|
|
1218
|
+
let ni = i; // ESLint
|
|
1219
|
+
while (segment.length) {
|
|
1220
|
+
// if created multiple C:s, their original seg is saved
|
|
1221
|
+
allPathCommands[i] = 'A';
|
|
1222
|
+
path.splice(ni += 1, 0, ['C'].concat(segment.splice(0, 6)));
|
|
1223
|
+
}
|
|
1224
|
+
path.splice(i, 1);
|
|
968
1225
|
}
|
|
969
1226
|
}
|
|
970
1227
|
|
|
971
|
-
|
|
972
|
-
// eslint-disable no-bitwise
|
|
973
|
-
switch (code | 0x20) {
|
|
974
|
-
case 0x6D/* m */:
|
|
975
|
-
case 0x7A/* z */:
|
|
976
|
-
case 0x6C/* l */:
|
|
977
|
-
case 0x68/* h */:
|
|
978
|
-
case 0x76/* v */:
|
|
979
|
-
case 0x63/* c */:
|
|
980
|
-
case 0x73/* s */:
|
|
981
|
-
case 0x71/* q */:
|
|
982
|
-
case 0x74/* t */:
|
|
983
|
-
case 0x61/* a */:
|
|
984
|
-
case 0x72/* r */:
|
|
985
|
-
return true;
|
|
986
|
-
default:
|
|
987
|
-
return false;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
1228
|
+
var version$1 = "0.0.16alpha4";
|
|
990
1229
|
|
|
991
|
-
|
|
992
|
-
return (code >= 48 && code <= 57) /* 0..9 */
|
|
993
|
-
|| code === 0x2B /* + */
|
|
994
|
-
|| code === 0x2D /* - */
|
|
995
|
-
|| code === 0x2E; /* . */
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
function isArcCommand(code) {
|
|
999
|
-
// eslint disable no-bitwise
|
|
1000
|
-
return (code | 0x20) === 0x61;
|
|
1001
|
-
}
|
|
1230
|
+
// @ts-ignore
|
|
1002
1231
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* A global namespace for library version.
|
|
1234
|
+
* @type {string}
|
|
1235
|
+
*/
|
|
1236
|
+
const DMVersion = version$1;
|
|
1008
1237
|
|
|
1009
|
-
|
|
1238
|
+
// DOMMatrix Static methods
|
|
1239
|
+
// * `fromFloat64Array` and `fromFloat32Array` methods are not supported;
|
|
1240
|
+
// * `fromArray` a more simple implementation, should also accept float[32/64]Array;
|
|
1241
|
+
// * `fromMatrix` load values from another CSSMatrix/DOMMatrix instance;
|
|
1242
|
+
// * `fromString` parses and loads values from any valid CSS transform string.
|
|
1010
1243
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1244
|
+
/**
|
|
1245
|
+
* Creates a new mutable `CSSMatrix` object given an array of floating point values.
|
|
1246
|
+
*
|
|
1247
|
+
* This static method invalidates arrays that contain non-number elements.
|
|
1248
|
+
*
|
|
1249
|
+
* If the array has six values, the result is a 2D matrix; if the array has 16 values,
|
|
1250
|
+
* the result is a 3D matrix. Otherwise, a TypeError exception is thrown.
|
|
1251
|
+
*
|
|
1252
|
+
* @param {number[]} array an `Array` to feed values from.
|
|
1253
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1254
|
+
*/
|
|
1255
|
+
function fromArray(array) {
|
|
1256
|
+
if (!array.every((n) => !Number.isNaN(n))) {
|
|
1257
|
+
throw TypeError(`CSSMatrix: "${array}" must only have numbers.`);
|
|
1017
1258
|
}
|
|
1259
|
+
const m = new CSSMatrix();
|
|
1260
|
+
const a = Array.from(array);
|
|
1018
1261
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1262
|
+
if (a.length === 16) {
|
|
1263
|
+
const [m11, m12, m13, m14,
|
|
1264
|
+
m21, m22, m23, m24,
|
|
1265
|
+
m31, m32, m33, m34,
|
|
1266
|
+
m41, m42, m43, m44] = a;
|
|
1021
1267
|
|
|
1022
|
-
|
|
1268
|
+
m.m11 = m11;
|
|
1269
|
+
m.a = m11;
|
|
1023
1270
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
finalizeSegment(state);
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1271
|
+
m.m21 = m21;
|
|
1272
|
+
m.c = m21;
|
|
1029
1273
|
|
|
1030
|
-
|
|
1274
|
+
m.m31 = m31;
|
|
1031
1275
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (isArcCommand(cmdCode) && (i === 3 || i === 4)) scanFlag(state);
|
|
1035
|
-
else scanParam(state);
|
|
1276
|
+
m.m41 = m41;
|
|
1277
|
+
m.e = m41;
|
|
1036
1278
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
state.data.push(state.param);
|
|
1279
|
+
m.m12 = m12;
|
|
1280
|
+
m.b = m12;
|
|
1041
1281
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1282
|
+
m.m22 = m22;
|
|
1283
|
+
m.d = m22;
|
|
1044
1284
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1285
|
+
m.m32 = m32;
|
|
1286
|
+
|
|
1287
|
+
m.m42 = m42;
|
|
1288
|
+
m.f = m42;
|
|
1051
1289
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1290
|
+
m.m13 = m13;
|
|
1291
|
+
m.m23 = m23;
|
|
1292
|
+
m.m33 = m33;
|
|
1293
|
+
m.m43 = m43;
|
|
1294
|
+
m.m14 = m14;
|
|
1295
|
+
m.m24 = m24;
|
|
1296
|
+
m.m34 = m34;
|
|
1297
|
+
m.m44 = m44;
|
|
1298
|
+
} else if (a.length === 6) {
|
|
1299
|
+
const [m11, m12, m21, m22, m41, m42] = a;
|
|
1056
1300
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1301
|
+
m.m11 = m11;
|
|
1302
|
+
m.a = m11;
|
|
1060
1303
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
break;
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1304
|
+
m.m12 = m12;
|
|
1305
|
+
m.b = m12;
|
|
1066
1306
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1307
|
+
m.m21 = m21;
|
|
1308
|
+
m.c = m21;
|
|
1069
1309
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
this.pathValue = pathString;
|
|
1073
|
-
this.max = pathString.length;
|
|
1074
|
-
this.index = 0;
|
|
1075
|
-
this.param = 0.0;
|
|
1076
|
-
this.segmentStart = 0;
|
|
1077
|
-
this.data = [];
|
|
1078
|
-
this.err = '';
|
|
1079
|
-
// return this;
|
|
1080
|
-
}
|
|
1310
|
+
m.m22 = m22;
|
|
1311
|
+
m.d = m22;
|
|
1081
1312
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
if (isPathArray(pathString)) {
|
|
1085
|
-
return clonePath(pathString);
|
|
1086
|
-
}
|
|
1313
|
+
m.m41 = m41;
|
|
1314
|
+
m.e = m41;
|
|
1087
1315
|
|
|
1088
|
-
|
|
1316
|
+
m.m42 = m42;
|
|
1317
|
+
m.f = m42;
|
|
1318
|
+
} else {
|
|
1319
|
+
throw new TypeError('CSSMatrix: expecting an Array of 6/16 values.');
|
|
1320
|
+
}
|
|
1321
|
+
return m;
|
|
1322
|
+
}
|
|
1089
1323
|
|
|
1090
|
-
|
|
1324
|
+
/**
|
|
1325
|
+
* Creates a new mutable `CSSMatrix` instance given an existing matrix or a
|
|
1326
|
+
* `DOMMatrix` instance which provides the values for its properties.
|
|
1327
|
+
*
|
|
1328
|
+
* @param {CSSMatrix | DOMMatrix | DMNS.jsonMatrix} m the source matrix to feed values from.
|
|
1329
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1330
|
+
*/
|
|
1331
|
+
function fromMatrix(m) {
|
|
1332
|
+
const keys = [
|
|
1333
|
+
'm11', 'm12', 'm13', 'm14',
|
|
1334
|
+
'm21', 'm22', 'm23', 'm24',
|
|
1335
|
+
'm31', 'm32', 'm33', 'm34',
|
|
1336
|
+
'm41', 'm42', 'm43', 'm44'];
|
|
1337
|
+
if ([CSSMatrix, DOMMatrix].some((x) => m instanceof x)
|
|
1338
|
+
|| (typeof m === 'object' && keys.every((k) => k in m))) {
|
|
1339
|
+
return fromArray(
|
|
1340
|
+
[m.m11, m.m12, m.m13, m.m14,
|
|
1341
|
+
m.m21, m.m22, m.m23, m.m24,
|
|
1342
|
+
m.m31, m.m32, m.m33, m.m34,
|
|
1343
|
+
m.m41, m.m42, m.m43, m.m44],
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
throw TypeError(`CSSMatrix: "${m}" is not a DOMMatrix / CSSMatrix compatible object.`);
|
|
1347
|
+
}
|
|
1091
1348
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1349
|
+
/**
|
|
1350
|
+
* Creates a new mutable `CSSMatrix` instance given any valid CSS transform string.
|
|
1351
|
+
*
|
|
1352
|
+
* * `matrix(a, b, c, d, e, f)` - valid matrix() transform function
|
|
1353
|
+
* * `matrix3d(m11, m12, m13, ...m44)` - valid matrix3d() transform function
|
|
1354
|
+
* * `translate(tx, ty) rotateX(alpha)` - any valid transform function(s)
|
|
1355
|
+
*
|
|
1356
|
+
* @copyright thednp © 2021
|
|
1357
|
+
*
|
|
1358
|
+
* @param {string} source valid CSS transform string syntax.
|
|
1359
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1360
|
+
*/
|
|
1361
|
+
function fromString(source) {
|
|
1362
|
+
if (typeof source !== 'string') {
|
|
1363
|
+
throw TypeError(`CSSMatrix: "${source}" is not a string.`);
|
|
1094
1364
|
}
|
|
1365
|
+
const str = String(source).replace(/\s/g, '');
|
|
1366
|
+
let m = new CSSMatrix();
|
|
1367
|
+
let is2D = true;
|
|
1368
|
+
const tramsformObject = str.split(')').filter((f) => f).map((fn) => {
|
|
1369
|
+
const [prop, value] = fn.split('(');
|
|
1370
|
+
const components = value.split(',')
|
|
1371
|
+
.map((n) => (n.includes('rad') ? parseFloat(n) * (180 / Math.PI) : parseFloat(n)));
|
|
1372
|
+
const [x, y, z, a] = components;
|
|
1373
|
+
|
|
1374
|
+
// don't add perspective if is2D
|
|
1375
|
+
if (is2D && (prop === 'matrix3d' // only modify is2D once
|
|
1376
|
+
|| (prop === 'rotate3d' && [x, y].every((n) => !Number.isNaN(+n) && n !== 0) && a)
|
|
1377
|
+
|| (['rotateX', 'rotateY'].includes(prop) && x)
|
|
1378
|
+
|| (prop === 'translate3d' && [x, y, z].every((n) => !Number.isNaN(+n)) && z)
|
|
1379
|
+
|| (prop === 'scale3d' && [x, y, z].every((n) => !Number.isNaN(+n) && n !== x))
|
|
1380
|
+
)) {
|
|
1381
|
+
is2D = false;
|
|
1382
|
+
}
|
|
1383
|
+
return { prop, components };
|
|
1384
|
+
});
|
|
1095
1385
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1386
|
+
tramsformObject.forEach((tf) => {
|
|
1387
|
+
const { prop, components } = tf;
|
|
1388
|
+
const [x, y, z, a] = components;
|
|
1389
|
+
const xyz = [x, y, z];
|
|
1390
|
+
const xyza = [x, y, z, a];
|
|
1391
|
+
|
|
1392
|
+
if (prop === 'perspective' && !is2D) {
|
|
1393
|
+
m.m34 = -1 / x;
|
|
1394
|
+
} else if (prop.includes('matrix')) {
|
|
1395
|
+
const values = components.map((n) => (Math.abs(n) < 1e-6 ? 0 : n));
|
|
1396
|
+
if ([6, 16].includes(values.length)) {
|
|
1397
|
+
m = m.multiply(fromArray(values));
|
|
1398
|
+
}
|
|
1399
|
+
} else if (['translate', 'translate3d'].some((p) => prop === p) && x) {
|
|
1400
|
+
m = m.translate(x, y || 0, z || 0);
|
|
1401
|
+
} else if (prop === 'rotate3d' && xyza.every((n) => !Number.isNaN(+n)) && a) {
|
|
1402
|
+
m = m.rotateAxisAngle(x, y, z, a);
|
|
1403
|
+
} else if (prop === 'scale3d' && xyz.every((n) => !Number.isNaN(+n)) && xyz.some((n) => n !== 1)) {
|
|
1404
|
+
m = m.scale(x, y, z);
|
|
1405
|
+
} else if (prop === 'rotate' && x) {
|
|
1406
|
+
m = m.rotate(0, 0, x);
|
|
1407
|
+
} else if (prop === 'scale' && !Number.isNaN(x) && x !== 1) {
|
|
1408
|
+
const nosy = Number.isNaN(+y);
|
|
1409
|
+
const sy = nosy ? x : y;
|
|
1410
|
+
m = m.scale(x, sy, 1);
|
|
1411
|
+
} else if (prop === 'skew' && (x || y)) {
|
|
1412
|
+
m = x ? m.skewX(x) : m;
|
|
1413
|
+
m = y ? m.skewY(y) : m;
|
|
1414
|
+
} else if (/[XYZ]/.test(prop) && x) {
|
|
1415
|
+
if (prop.includes('skew')) {
|
|
1416
|
+
// @ts-ignore unfortunately
|
|
1417
|
+
m = m[prop](x);
|
|
1418
|
+
} else {
|
|
1419
|
+
const fn = prop.replace(/[XYZ]/, '');
|
|
1420
|
+
const axis = prop.replace(fn, '');
|
|
1421
|
+
const idx = ['X', 'Y', 'Z'].indexOf(axis);
|
|
1422
|
+
const axeValues = [
|
|
1423
|
+
idx === 0 ? x : 0,
|
|
1424
|
+
idx === 1 ? x : 0,
|
|
1425
|
+
idx === 2 ? x : 0];
|
|
1426
|
+
// @ts-ignore unfortunately
|
|
1427
|
+
m = m[fn](...axeValues);
|
|
1428
|
+
}
|
|
1105
1429
|
}
|
|
1106
|
-
}
|
|
1430
|
+
});
|
|
1107
1431
|
|
|
1108
|
-
return
|
|
1432
|
+
return m;
|
|
1109
1433
|
}
|
|
1110
1434
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1435
|
+
// Transform Functions
|
|
1436
|
+
// https://www.w3.org/TR/css-transforms-1/#transform-functions
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Creates a new `CSSMatrix` for the translation matrix and returns it.
|
|
1440
|
+
* This method is equivalent to the CSS `translate3d()` function.
|
|
1441
|
+
*
|
|
1442
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d
|
|
1443
|
+
*
|
|
1444
|
+
* @param {number} x the `x-axis` position.
|
|
1445
|
+
* @param {number} y the `y-axis` position.
|
|
1446
|
+
* @param {number} z the `z-axis` position.
|
|
1447
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1448
|
+
*/
|
|
1449
|
+
function Translate(x, y, z) {
|
|
1450
|
+
const m = new CSSMatrix();
|
|
1451
|
+
m.m41 = x;
|
|
1452
|
+
m.e = x;
|
|
1453
|
+
m.m42 = y;
|
|
1454
|
+
m.f = y;
|
|
1455
|
+
m.m43 = z;
|
|
1456
|
+
return m;
|
|
1113
1457
|
}
|
|
1114
1458
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1459
|
+
/**
|
|
1460
|
+
* Creates a new `CSSMatrix` for the rotation matrix and returns it.
|
|
1461
|
+
*
|
|
1462
|
+
* http://en.wikipedia.org/wiki/Rotation_matrix
|
|
1463
|
+
*
|
|
1464
|
+
* @param {number} rx the `x-axis` rotation.
|
|
1465
|
+
* @param {number} ry the `y-axis` rotation.
|
|
1466
|
+
* @param {number} rz the `z-axis` rotation.
|
|
1467
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1468
|
+
*/
|
|
1469
|
+
function Rotate(rx, ry, rz) {
|
|
1470
|
+
const m = new CSSMatrix();
|
|
1471
|
+
const degToRad = Math.PI / 180;
|
|
1472
|
+
const radX = rx * degToRad;
|
|
1473
|
+
const radY = ry * degToRad;
|
|
1474
|
+
const radZ = rz * degToRad;
|
|
1119
1475
|
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
let start = 0;
|
|
1476
|
+
// minus sin() because of right-handed system
|
|
1477
|
+
const cosx = Math.cos(radX);
|
|
1478
|
+
const sinx = -Math.sin(radX);
|
|
1479
|
+
const cosy = Math.cos(radY);
|
|
1480
|
+
const siny = -Math.sin(radY);
|
|
1481
|
+
const cosz = Math.cos(radZ);
|
|
1482
|
+
const sinz = -Math.sin(radZ);
|
|
1128
1483
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
y = +pathArray[0][2];
|
|
1132
|
-
mx = x;
|
|
1133
|
-
my = y;
|
|
1134
|
-
start += 1;
|
|
1135
|
-
resultArray.push(['M', x, y]);
|
|
1136
|
-
}
|
|
1484
|
+
const m11 = cosy * cosz;
|
|
1485
|
+
const m12 = -cosy * sinz;
|
|
1137
1486
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
const [pathCommand] = segment;
|
|
1141
|
-
const absCommand = pathCommand.toUpperCase();
|
|
1142
|
-
const absoluteSegment = [];
|
|
1143
|
-
let newSeg = [];
|
|
1144
|
-
resultArray.push(absoluteSegment);
|
|
1487
|
+
m.m11 = m11;
|
|
1488
|
+
m.a = m11;
|
|
1145
1489
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1490
|
+
m.m12 = m12;
|
|
1491
|
+
m.b = m12;
|
|
1148
1492
|
|
|
1149
|
-
|
|
1150
|
-
case 'A':
|
|
1151
|
-
newSeg = segment.slice(1, -2).concat([+segment[6] + x, +segment[7] + y]);
|
|
1152
|
-
for (let j = 0; j < newSeg.length; j += 1) {
|
|
1153
|
-
absoluteSegment.push(newSeg[j]);
|
|
1154
|
-
}
|
|
1155
|
-
break;
|
|
1156
|
-
case 'V':
|
|
1157
|
-
absoluteSegment[1] = +segment[1] + y;
|
|
1158
|
-
break;
|
|
1159
|
-
case 'H':
|
|
1160
|
-
absoluteSegment[1] = +segment[1] + x;
|
|
1161
|
-
break;
|
|
1162
|
-
default:
|
|
1163
|
-
if (absCommand === 'M') {
|
|
1164
|
-
mx = +segment[1] + x;
|
|
1165
|
-
my = +segment[2] + y;
|
|
1166
|
-
}
|
|
1167
|
-
// for is here to stay for eslint
|
|
1168
|
-
for (let j = 1; j < segment.length; j += 1) {
|
|
1169
|
-
absoluteSegment.push(+segment[j] + (j % 2 ? x : y));
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
} else {
|
|
1173
|
-
for (let j = 0; j < segment.length; j += 1) {
|
|
1174
|
-
absoluteSegment.push(segment[j]);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1493
|
+
m.m13 = siny;
|
|
1177
1494
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
x = mx;
|
|
1182
|
-
y = my;
|
|
1183
|
-
break;
|
|
1184
|
-
case 'H':
|
|
1185
|
-
x = +absoluteSegment[1];
|
|
1186
|
-
break;
|
|
1187
|
-
case 'V':
|
|
1188
|
-
y = +absoluteSegment[1];
|
|
1189
|
-
break;
|
|
1190
|
-
default:
|
|
1191
|
-
x = +absoluteSegment[segLength - 2];
|
|
1192
|
-
y = +absoluteSegment[segLength - 1];
|
|
1495
|
+
const m21 = sinx * siny * cosz + cosx * sinz;
|
|
1496
|
+
m.m21 = m21;
|
|
1497
|
+
m.c = m21;
|
|
1193
1498
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1499
|
+
const m22 = cosx * cosz - sinx * siny * sinz;
|
|
1500
|
+
m.m22 = m22;
|
|
1501
|
+
m.d = m22;
|
|
1200
1502
|
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1503
|
+
m.m23 = -sinx * cosy;
|
|
1203
1504
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
? { qx: x1 * 2 - qx, qy: y1 * 2 - qy }
|
|
1208
|
-
: { qx: x1, qy: y1 };
|
|
1209
|
-
}
|
|
1505
|
+
m.m31 = sinx * sinz - cosx * siny * cosz;
|
|
1506
|
+
m.m32 = sinx * cosz + cosx * siny * sinz;
|
|
1507
|
+
m.m33 = cosx * cosy;
|
|
1210
1508
|
|
|
1211
|
-
|
|
1212
|
-
function shorthandToCubic(x1, y1, x2, y2, prevCommand) {
|
|
1213
|
-
return 'CS'.indexOf(prevCommand) > -1
|
|
1214
|
-
? { x1: x1 * 2 - x2, y1: y1 * 2 - y2 }
|
|
1215
|
-
: { x1, y1 };
|
|
1509
|
+
return m;
|
|
1216
1510
|
}
|
|
1217
1511
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1512
|
+
/**
|
|
1513
|
+
* Creates a new `CSSMatrix` for the rotation matrix and returns it.
|
|
1514
|
+
* This method is equivalent to the CSS `rotate3d()` function.
|
|
1515
|
+
*
|
|
1516
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d
|
|
1517
|
+
*
|
|
1518
|
+
* @param {number} x the `x-axis` vector length.
|
|
1519
|
+
* @param {number} y the `y-axis` vector length.
|
|
1520
|
+
* @param {number} z the `z-axis` vector length.
|
|
1521
|
+
* @param {number} alpha the value in degrees of the rotation.
|
|
1522
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1523
|
+
*/
|
|
1524
|
+
function RotateAxisAngle(x, y, z, alpha) {
|
|
1525
|
+
const m = new CSSMatrix();
|
|
1526
|
+
const angle = alpha * (Math.PI / 360);
|
|
1527
|
+
const sinA = Math.sin(angle);
|
|
1528
|
+
const cosA = Math.cos(angle);
|
|
1529
|
+
const sinA2 = sinA * sinA;
|
|
1530
|
+
const length = Math.sqrt(x * x + y * y + z * z);
|
|
1531
|
+
let X = x;
|
|
1532
|
+
let Y = y;
|
|
1533
|
+
let Z = z;
|
|
1222
1534
|
|
|
1223
|
-
if (
|
|
1224
|
-
//
|
|
1225
|
-
|
|
1226
|
-
|
|
1535
|
+
if (length === 0) {
|
|
1536
|
+
// bad vector length, use something reasonable
|
|
1537
|
+
X = 0;
|
|
1538
|
+
Y = 0;
|
|
1539
|
+
Z = 1;
|
|
1540
|
+
} else {
|
|
1541
|
+
X /= length;
|
|
1542
|
+
Y /= length;
|
|
1543
|
+
Z /= length;
|
|
1227
1544
|
}
|
|
1228
1545
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
result = ['L', params.x1, segment[1]];
|
|
1233
|
-
} else if (pathCommand === 'S') {
|
|
1234
|
-
const { x1, y1 } = shorthandToCubic(params.x1, params.y1, params.x2, params.y2, prevCommand);
|
|
1235
|
-
params.x1 = x1;
|
|
1236
|
-
params.y1 = y1;
|
|
1237
|
-
result = ['C', x1, y1].concat(xy);
|
|
1238
|
-
} else if (pathCommand === 'T') {
|
|
1239
|
-
const { qx, qy } = shorthandToQuad(params.x1, params.y1, params.qx, params.qy, prevCommand);
|
|
1240
|
-
params.qx = qx;
|
|
1241
|
-
params.qy = qy;
|
|
1242
|
-
result = ['Q', qx, qy].concat(xy);
|
|
1243
|
-
} else if (pathCommand === 'Q') {
|
|
1244
|
-
const [nqx, nqy] = xy;
|
|
1245
|
-
params.qx = nqx;
|
|
1246
|
-
params.qy = nqy;
|
|
1247
|
-
}
|
|
1248
|
-
return result;
|
|
1249
|
-
}
|
|
1546
|
+
const x2 = X * X;
|
|
1547
|
+
const y2 = Y * Y;
|
|
1548
|
+
const z2 = Z * Z;
|
|
1250
1549
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
return paramsCount[pathCommand] === seg.length - 1 && /[ACLMQZ]/.test(seg[0]); // achlmrqstvz
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1550
|
+
const m11 = 1 - 2 * (y2 + z2) * sinA2;
|
|
1551
|
+
m.m11 = m11;
|
|
1552
|
+
m.a = m11;
|
|
1257
1553
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1554
|
+
const m12 = 2 * (X * Y * sinA2 + Z * sinA * cosA);
|
|
1555
|
+
m.m12 = m12;
|
|
1556
|
+
m.b = m12;
|
|
1262
1557
|
|
|
1263
|
-
|
|
1264
|
-
const params = {
|
|
1265
|
-
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
1266
|
-
};
|
|
1267
|
-
const allPathCommands = [];
|
|
1268
|
-
const ii = pathArray.length;
|
|
1269
|
-
let prevCommand = '';
|
|
1270
|
-
let segment;
|
|
1271
|
-
let seglen;
|
|
1558
|
+
m.m13 = 2 * (X * Z * sinA2 - Y * sinA * cosA);
|
|
1272
1559
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1560
|
+
const m21 = 2 * (Y * X * sinA2 - Z * sinA * cosA);
|
|
1561
|
+
m.m21 = m21;
|
|
1562
|
+
m.c = m21;
|
|
1276
1563
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (i) prevCommand = allPathCommands[i - 1];
|
|
1281
|
-
// Previous path command is inputted to processSegment
|
|
1282
|
-
pathArray[i] = normalizeSegment(pathArray[i], params, prevCommand);
|
|
1564
|
+
const m22 = 1 - 2 * (z2 + x2) * sinA2;
|
|
1565
|
+
m.m22 = m22;
|
|
1566
|
+
m.d = m22;
|
|
1283
1567
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1568
|
+
m.m23 = 2 * (Y * Z * sinA2 + X * sinA * cosA);
|
|
1569
|
+
m.m31 = 2 * (Z * X * sinA2 + Y * sinA * cosA);
|
|
1570
|
+
m.m32 = 2 * (Z * Y * sinA2 - X * sinA * cosA);
|
|
1571
|
+
m.m33 = 1 - 2 * (x2 + y2) * sinA2;
|
|
1286
1572
|
|
|
1287
|
-
|
|
1288
|
-
params.y1 = +segment[seglen - 1];
|
|
1289
|
-
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
1290
|
-
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
1291
|
-
}
|
|
1292
|
-
return roundPath(pathArray, round);
|
|
1573
|
+
return m;
|
|
1293
1574
|
}
|
|
1294
1575
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1576
|
+
/**
|
|
1577
|
+
* Creates a new `CSSMatrix` for the scale matrix and returns it.
|
|
1578
|
+
* This method is equivalent to the CSS `scale3d()` function.
|
|
1579
|
+
*
|
|
1580
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d
|
|
1581
|
+
*
|
|
1582
|
+
* @param {number} x the `x-axis` scale.
|
|
1583
|
+
* @param {number} y the `y-axis` scale.
|
|
1584
|
+
* @param {number} z the `z-axis` scale.
|
|
1585
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1586
|
+
*/
|
|
1587
|
+
function Scale(x, y, z) {
|
|
1588
|
+
const m = new CSSMatrix();
|
|
1589
|
+
m.m11 = x;
|
|
1590
|
+
m.a = x;
|
|
1591
|
+
|
|
1592
|
+
m.m22 = y;
|
|
1593
|
+
m.d = y;
|
|
1594
|
+
|
|
1595
|
+
m.m33 = z;
|
|
1596
|
+
return m;
|
|
1299
1597
|
}
|
|
1300
1598
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1599
|
+
/**
|
|
1600
|
+
* Creates a new `CSSMatrix` for the shear of the `x-axis` rotation matrix and
|
|
1601
|
+
* returns it. This method is equivalent to the CSS `skewX()` function.
|
|
1602
|
+
*
|
|
1603
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX
|
|
1604
|
+
*
|
|
1605
|
+
* @param {number} angle the angle in degrees.
|
|
1606
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1607
|
+
*/
|
|
1608
|
+
function SkewX(angle) {
|
|
1609
|
+
const m = new CSSMatrix();
|
|
1610
|
+
const radA = (angle * Math.PI) / 180;
|
|
1611
|
+
const t = Math.tan(radA);
|
|
1612
|
+
m.m21 = t;
|
|
1613
|
+
m.c = t;
|
|
1614
|
+
return m;
|
|
1615
|
+
}
|
|
1304
1616
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1617
|
+
/**
|
|
1618
|
+
* Creates a new `CSSMatrix` for the shear of the `y-axis` rotation matrix and
|
|
1619
|
+
* returns it. This method is equivalent to the CSS `skewY()` function.
|
|
1620
|
+
*
|
|
1621
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY
|
|
1622
|
+
*
|
|
1623
|
+
* @param {number} angle the angle in degrees.
|
|
1624
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1625
|
+
*/
|
|
1626
|
+
function SkewY(angle) {
|
|
1627
|
+
const m = new CSSMatrix();
|
|
1628
|
+
const radA = (angle * Math.PI) / 180;
|
|
1629
|
+
const t = Math.tan(radA);
|
|
1630
|
+
m.m12 = t;
|
|
1631
|
+
m.b = t;
|
|
1632
|
+
return m;
|
|
1633
|
+
}
|
|
1320
1634
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1635
|
+
/**
|
|
1636
|
+
* Creates a new `CSSMatrix` resulted from the multiplication of two matrixes
|
|
1637
|
+
* and returns it. Both matrixes are not changed.
|
|
1638
|
+
*
|
|
1639
|
+
* @param {CSSMatrix} m1 the first matrix.
|
|
1640
|
+
* @param {CSSMatrix} m2 the second matrix.
|
|
1641
|
+
* @return {CSSMatrix} the resulted matrix.
|
|
1642
|
+
*/
|
|
1643
|
+
function Multiply(m1, m2) {
|
|
1644
|
+
const m11 = m2.m11 * m1.m11 + m2.m12 * m1.m21 + m2.m13 * m1.m31 + m2.m14 * m1.m41;
|
|
1645
|
+
const m12 = m2.m11 * m1.m12 + m2.m12 * m1.m22 + m2.m13 * m1.m32 + m2.m14 * m1.m42;
|
|
1646
|
+
const m13 = m2.m11 * m1.m13 + m2.m12 * m1.m23 + m2.m13 * m1.m33 + m2.m14 * m1.m43;
|
|
1647
|
+
const m14 = m2.m11 * m1.m14 + m2.m12 * m1.m24 + m2.m13 * m1.m34 + m2.m14 * m1.m44;
|
|
1328
1648
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
h = Math.sqrt(h);
|
|
1334
|
-
RX *= h;
|
|
1335
|
-
RY *= h;
|
|
1336
|
-
}
|
|
1337
|
-
const rx2 = RX ** 2;
|
|
1338
|
-
const ry2 = RY ** 2;
|
|
1339
|
-
const k = (LAF === SF ? -1 : 1)
|
|
1340
|
-
* Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
|
|
1341
|
-
/ (rx2 * y * y + ry2 * x * x)));
|
|
1649
|
+
const m21 = m2.m21 * m1.m11 + m2.m22 * m1.m21 + m2.m23 * m1.m31 + m2.m24 * m1.m41;
|
|
1650
|
+
const m22 = m2.m21 * m1.m12 + m2.m22 * m1.m22 + m2.m23 * m1.m32 + m2.m24 * m1.m42;
|
|
1651
|
+
const m23 = m2.m21 * m1.m13 + m2.m22 * m1.m23 + m2.m23 * m1.m33 + m2.m24 * m1.m43;
|
|
1652
|
+
const m24 = m2.m21 * m1.m14 + m2.m22 * m1.m24 + m2.m23 * m1.m34 + m2.m24 * m1.m44;
|
|
1342
1653
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1654
|
+
const m31 = m2.m31 * m1.m11 + m2.m32 * m1.m21 + m2.m33 * m1.m31 + m2.m34 * m1.m41;
|
|
1655
|
+
const m32 = m2.m31 * m1.m12 + m2.m32 * m1.m22 + m2.m33 * m1.m32 + m2.m34 * m1.m42;
|
|
1656
|
+
const m33 = m2.m31 * m1.m13 + m2.m32 * m1.m23 + m2.m33 * m1.m33 + m2.m34 * m1.m43;
|
|
1657
|
+
const m34 = m2.m31 * m1.m14 + m2.m32 * m1.m24 + m2.m33 * m1.m34 + m2.m34 * m1.m44;
|
|
1345
1658
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1659
|
+
const m41 = m2.m41 * m1.m11 + m2.m42 * m1.m21 + m2.m43 * m1.m31 + m2.m44 * m1.m41;
|
|
1660
|
+
const m42 = m2.m41 * m1.m12 + m2.m42 * m1.m22 + m2.m43 * m1.m32 + m2.m44 * m1.m42;
|
|
1661
|
+
const m43 = m2.m41 * m1.m13 + m2.m42 * m1.m23 + m2.m43 * m1.m33 + m2.m44 * m1.m43;
|
|
1662
|
+
const m44 = m2.m41 * m1.m14 + m2.m42 * m1.m24 + m2.m43 * m1.m34 + m2.m44 * m1.m44;
|
|
1350
1663
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1664
|
+
return fromArray(
|
|
1665
|
+
[m11, m12, m13, m14,
|
|
1666
|
+
m21, m22, m23, m24,
|
|
1667
|
+
m31, m32, m33, m34,
|
|
1668
|
+
m41, m42, m43, m44],
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1353
1671
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Creates and returns a new `DOMMatrix` compatible *Object*
|
|
1674
|
+
* with equivalent instance methods.
|
|
1675
|
+
*
|
|
1676
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
|
|
1677
|
+
* https://github.com/thednp/DOMMatrix/
|
|
1678
|
+
*/
|
|
1356
1679
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1680
|
+
class CSSMatrix {
|
|
1681
|
+
/**
|
|
1682
|
+
* @constructor
|
|
1683
|
+
* @param {any} args accepts all parameter configurations:
|
|
1684
|
+
*
|
|
1685
|
+
* * valid CSS transform string,
|
|
1686
|
+
* * CSSMatrix/DOMMatrix instance,
|
|
1687
|
+
* * a 6/16 elements *Array*.
|
|
1688
|
+
*/
|
|
1689
|
+
constructor(...args) {
|
|
1690
|
+
const m = this;
|
|
1691
|
+
// array 6
|
|
1692
|
+
m.a = 1; m.b = 0;
|
|
1693
|
+
m.c = 0; m.d = 1;
|
|
1694
|
+
m.e = 0; m.f = 0;
|
|
1695
|
+
// array 16
|
|
1696
|
+
m.m11 = 1; m.m12 = 0; m.m13 = 0; m.m14 = 0;
|
|
1697
|
+
m.m21 = 0; m.m22 = 1; m.m23 = 0; m.m24 = 0;
|
|
1698
|
+
m.m31 = 0; m.m32 = 0; m.m33 = 1; m.m34 = 0;
|
|
1699
|
+
m.m41 = 0; m.m42 = 0; m.m43 = 0; m.m44 = 1;
|
|
1700
|
+
|
|
1701
|
+
if (args && args.length) {
|
|
1702
|
+
let ARGS = args;
|
|
1703
|
+
|
|
1704
|
+
if (args instanceof Array) {
|
|
1705
|
+
if ((args[0] instanceof Array && [16, 6].includes(args[0].length))
|
|
1706
|
+
|| typeof args[0] === 'string'
|
|
1707
|
+
|| [CSSMatrix, DOMMatrix].some((x) => args[0] instanceof x)) {
|
|
1708
|
+
[ARGS] = args;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return m.setMatrixValue(ARGS);
|
|
1362
1712
|
}
|
|
1363
|
-
|
|
1364
|
-
const [r1, r2, r3, r4] = recursive;
|
|
1365
|
-
f1 = r1;
|
|
1366
|
-
f2 = r2;
|
|
1367
|
-
cx = r3;
|
|
1368
|
-
cy = r4;
|
|
1713
|
+
return m;
|
|
1369
1714
|
}
|
|
1370
1715
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1716
|
+
/**
|
|
1717
|
+
* Sets a new `Boolean` flag value for `this.isIdentity` matrix property.
|
|
1718
|
+
*
|
|
1719
|
+
* @param {Boolean} value sets a new flag for this property
|
|
1720
|
+
*/
|
|
1721
|
+
set isIdentity(value) {
|
|
1722
|
+
this.isIdentity = value;
|
|
1723
|
+
}
|
|
1376
1724
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1725
|
+
/**
|
|
1726
|
+
* A `Boolean` whose value is `true` if the matrix is the identity matrix. The identity
|
|
1727
|
+
* matrix is one in which every value is 0 except those on the main diagonal from top-left
|
|
1728
|
+
* to bottom-right corner (in other words, where the offsets in each direction are equal).
|
|
1729
|
+
*
|
|
1730
|
+
* @return {Boolean} the current property value
|
|
1731
|
+
*/
|
|
1732
|
+
get isIdentity() {
|
|
1733
|
+
const m = this;
|
|
1734
|
+
return (m.m11 === 1 && m.m12 === 0 && m.m13 === 0 && m.m14 === 0
|
|
1735
|
+
&& m.m21 === 0 && m.m22 === 1 && m.m23 === 0 && m.m24 === 0
|
|
1736
|
+
&& m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0
|
|
1737
|
+
&& m.m41 === 0 && m.m42 === 0 && m.m43 === 0 && m.m44 === 1);
|
|
1381
1738
|
}
|
|
1382
1739
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
const m2 = [X1 + hx * s1, Y1 - hy * c1];
|
|
1393
|
-
const m3 = [X2 + hx * s2, Y2 - hy * c2];
|
|
1394
|
-
const m4 = [X2, Y2];
|
|
1395
|
-
m2[0] = 2 * m1[0] - m2[0];
|
|
1396
|
-
m2[1] = 2 * m1[1] - m2[1];
|
|
1397
|
-
|
|
1398
|
-
if (recursive) {
|
|
1399
|
-
return [m2, m3, m4].concat(res);
|
|
1740
|
+
/**
|
|
1741
|
+
* A `Boolean` flag whose value is `true` if the matrix was initialized as a 2D matrix
|
|
1742
|
+
* and `false` if the matrix is 3D.
|
|
1743
|
+
*
|
|
1744
|
+
* @return {Boolean} the current property value
|
|
1745
|
+
*/
|
|
1746
|
+
get is2D() {
|
|
1747
|
+
const m = this;
|
|
1748
|
+
return (m.m31 === 0 && m.m32 === 0 && m.m33 === 1 && m.m34 === 0 && m.m43 === 0 && m.m44 === 1);
|
|
1400
1749
|
}
|
|
1401
|
-
res = [m2, m3, m4].concat(res).join().split(',');
|
|
1402
|
-
return res.map((rz, i) => {
|
|
1403
|
-
if (i % 2) {
|
|
1404
|
-
return rotateVector(res[i - 1], rz, rad).y;
|
|
1405
|
-
}
|
|
1406
|
-
return rotateVector(rz, res[i + 1], rad).x;
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
function quadToCubic(x1, y1, qx, qy, x2, y2) {
|
|
1411
|
-
const r13 = 1 / 3;
|
|
1412
|
-
const r23 = 2 / 3;
|
|
1413
|
-
return [
|
|
1414
|
-
r13 * x1 + r23 * qx, // cpx1
|
|
1415
|
-
r13 * y1 + r23 * qy, // cpy1
|
|
1416
|
-
r13 * x2 + r23 * qx, // cpx2
|
|
1417
|
-
r13 * y2 + r23 * qy, // cpy2
|
|
1418
|
-
x2, y2, // x,y
|
|
1419
|
-
];
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// t = [0-1]
|
|
1423
|
-
function getPointAtSegLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
1424
|
-
const t1 = 1 - t;
|
|
1425
|
-
return {
|
|
1426
|
-
x: (t1 ** 3) * p1x
|
|
1427
|
-
+ t1 * t1 * 3 * t * c1x
|
|
1428
|
-
+ t1 * 3 * t * t * c2x
|
|
1429
|
-
+ (t ** 3) * p2x,
|
|
1430
|
-
y: (t1 ** 3) * p1y
|
|
1431
|
-
+ t1 * t1 * 3 * t * c1y
|
|
1432
|
-
+ t1 * 3 * t * t * c2y
|
|
1433
|
-
+ (t ** 3) * p2y,
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
function midPoint(a, b, t) {
|
|
1438
|
-
const ax = a[0];
|
|
1439
|
-
const ay = a[1];
|
|
1440
|
-
const bx = b[0];
|
|
1441
|
-
const by = b[1];
|
|
1442
|
-
return [ax + (bx - ax) * t, ay + (by - ay) * t];
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
function lineToCubic(x1, y1, x2, y2) {
|
|
1446
|
-
const t = 0.5;
|
|
1447
|
-
const p0 = [x1, y1];
|
|
1448
|
-
const p1 = [x2, y2];
|
|
1449
|
-
const p2 = midPoint(p0, p1, t);
|
|
1450
|
-
const p3 = midPoint(p1, p2, t);
|
|
1451
|
-
const p4 = midPoint(p2, p3, t);
|
|
1452
|
-
const p5 = midPoint(p3, p4, t);
|
|
1453
|
-
const p6 = midPoint(p4, p5, t);
|
|
1454
|
-
const cp1 = getPointAtSegLength.apply(0, p0.concat(p2, p4, p6, t));
|
|
1455
|
-
const cp2 = getPointAtSegLength.apply(0, p6.concat(p5, p3, p1, 0));
|
|
1456
|
-
|
|
1457
|
-
return [cp1.x, cp1.y, cp2.x, cp2.y, x2, y2];
|
|
1458
|
-
}
|
|
1459
1750
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* Sets a new `Boolean` flag value for `this.is2D` matrix property.
|
|
1753
|
+
*
|
|
1754
|
+
* @param {Boolean} value sets a new flag for this property
|
|
1755
|
+
*/
|
|
1756
|
+
set is2D(value) {
|
|
1757
|
+
this.is2D = value;
|
|
1464
1758
|
}
|
|
1465
1759
|
|
|
1466
|
-
|
|
1760
|
+
/**
|
|
1761
|
+
* The `setMatrixValue` method replaces the existing matrix with one computed
|
|
1762
|
+
* in the browser. EG: `matrix(1,0.25,-0.25,1,0,0)`
|
|
1763
|
+
*
|
|
1764
|
+
* The method accepts any *Array* values, the result of
|
|
1765
|
+
* `DOMMatrix` instance method `toFloat64Array()` / `toFloat32Array()` calls
|
|
1766
|
+
* or `CSSMatrix` instance method `toArray()`.
|
|
1767
|
+
*
|
|
1768
|
+
* This method expects valid *matrix()* / *matrix3d()* string values, as well
|
|
1769
|
+
* as other transform functions like *translateX(10px)*.
|
|
1770
|
+
*
|
|
1771
|
+
* @param {string | number[] | CSSMatrix | DOMMatrix} source
|
|
1772
|
+
* @return {CSSMatrix} the matrix instance
|
|
1773
|
+
*/
|
|
1774
|
+
setMatrixValue(source) {
|
|
1775
|
+
const m = this;
|
|
1467
1776
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
return
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
return ['C'].concat(lineToCubic(params.x1, params.y1, segment[1], segment[2]));
|
|
1481
|
-
case 'Z':
|
|
1482
|
-
return ['C'].concat(lineToCubic(params.x1, params.y1, params.x, params.y));
|
|
1777
|
+
// new CSSMatrix(CSSMatrix | DOMMatrix)
|
|
1778
|
+
if ([DOMMatrix, CSSMatrix].some((x) => source instanceof x)) {
|
|
1779
|
+
// @ts-ignore
|
|
1780
|
+
return fromMatrix(source);
|
|
1781
|
+
// CSS transform string source
|
|
1782
|
+
} if (typeof source === 'string' && source.length && source !== 'none') {
|
|
1783
|
+
return fromString(source);
|
|
1784
|
+
// [Arguments list | Array] come here
|
|
1785
|
+
} if (Array.isArray(source)) {
|
|
1786
|
+
return fromArray(source);
|
|
1787
|
+
}
|
|
1788
|
+
return m;
|
|
1483
1789
|
}
|
|
1484
|
-
return segment;
|
|
1485
|
-
}
|
|
1486
1790
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1791
|
+
/**
|
|
1792
|
+
* Creates and returns a string representation of the matrix in `CSS` matrix syntax,
|
|
1793
|
+
* using the appropriate `CSS` matrix notation.
|
|
1794
|
+
*
|
|
1795
|
+
* matrix3d *matrix3d(m11, m12, m13, m14, m21, ...)*
|
|
1796
|
+
* matrix *matrix(a, b, c, d, e, f)*
|
|
1797
|
+
*
|
|
1798
|
+
* @return {string} a string representation of the matrix
|
|
1799
|
+
*/
|
|
1800
|
+
toString() {
|
|
1801
|
+
const m = this;
|
|
1802
|
+
const values = m.toArray().join(',');
|
|
1803
|
+
const type = m.is2D ? 'matrix' : 'matrix3d';
|
|
1804
|
+
return `${type}(${values})`;
|
|
1490
1805
|
}
|
|
1491
1806
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1807
|
+
/**
|
|
1808
|
+
* Returns an *Array* containing all 16 elements which comprise the matrix.
|
|
1809
|
+
* The method can return either the elements.
|
|
1810
|
+
*
|
|
1811
|
+
* Other methods make use of this method to feed their output values from this matrix.
|
|
1812
|
+
*
|
|
1813
|
+
* @return {number[]} an *Array* representation of the matrix
|
|
1814
|
+
*/
|
|
1815
|
+
toArray() {
|
|
1816
|
+
const m = this;
|
|
1817
|
+
const pow6 = (10 ** 6);
|
|
1818
|
+
let result;
|
|
1504
1819
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1820
|
+
if (m.is2D) {
|
|
1821
|
+
result = [m.a, m.b, m.c, m.d, m.e, m.f];
|
|
1822
|
+
} else {
|
|
1823
|
+
result = [m.m11, m.m12, m.m13, m.m14,
|
|
1824
|
+
m.m21, m.m22, m.m23, m.m24,
|
|
1825
|
+
m.m31, m.m32, m.m33, m.m34,
|
|
1826
|
+
m.m41, m.m42, m.m43, m.m44];
|
|
1827
|
+
}
|
|
1828
|
+
// clean up the numbers
|
|
1829
|
+
// eslint-disable-next-line -- no-bitwise
|
|
1830
|
+
return result.map((n) => (Math.abs(n) < 1e-6 ? 0 : ((n * pow6) >> 0) / pow6));
|
|
1831
|
+
}
|
|
1507
1832
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1833
|
+
/**
|
|
1834
|
+
* Returns a JSON representation of the `CSSMatrix` instance, a standard *Object*
|
|
1835
|
+
* that includes `{a,b,c,d,e,f}` and `{m11,m12,m13,..m44}` properties and
|
|
1836
|
+
* excludes `is2D` & `isIdentity` properties.
|
|
1837
|
+
*
|
|
1838
|
+
* The result can also be used as a second parameter for the `fromMatrix` static method
|
|
1839
|
+
* to load values into a matrix instance.
|
|
1840
|
+
*
|
|
1841
|
+
* @return {DMNS.jsonMatrix} an *Object* with all matrix values.
|
|
1842
|
+
*/
|
|
1843
|
+
toJSON() {
|
|
1844
|
+
return JSON.parse(JSON.stringify(this));
|
|
1845
|
+
}
|
|
1510
1846
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1847
|
+
/**
|
|
1848
|
+
* The Multiply method returns a new CSSMatrix which is the result of this
|
|
1849
|
+
* matrix multiplied by the passed matrix, with the passed matrix to the right.
|
|
1850
|
+
* This matrix is not modified.
|
|
1851
|
+
*
|
|
1852
|
+
* @param {CSSMatrix | DOMMatrix | DMNS.jsonMatrix} m2 CSSMatrix
|
|
1853
|
+
* @return {CSSMatrix} The resulted matrix.
|
|
1854
|
+
*/
|
|
1855
|
+
multiply(m2) {
|
|
1856
|
+
// @ts-ignore - we only access [m11, m12, ... m44] values
|
|
1857
|
+
return Multiply(this, m2);
|
|
1858
|
+
}
|
|
1513
1859
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1860
|
+
/**
|
|
1861
|
+
* The translate method returns a new matrix which is this matrix post
|
|
1862
|
+
* multiplied by a translation matrix containing the passed values. If the z
|
|
1863
|
+
* component is undefined, a 0 value is used in its place. This matrix is not
|
|
1864
|
+
* modified.
|
|
1865
|
+
*
|
|
1866
|
+
* @param {number} x X component of the translation value.
|
|
1867
|
+
* @param {number | null} y Y component of the translation value.
|
|
1868
|
+
* @param {number | null} z Z component of the translation value.
|
|
1869
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1870
|
+
*/
|
|
1871
|
+
translate(x, y, z) {
|
|
1872
|
+
const X = x;
|
|
1873
|
+
let Y = y;
|
|
1874
|
+
let Z = z;
|
|
1875
|
+
if (Z == null) Z = 0;
|
|
1876
|
+
if (Y == null) Y = 0;
|
|
1877
|
+
return Multiply(this, Translate(X, Y, Z));
|
|
1518
1878
|
}
|
|
1519
|
-
return roundPath(pathArray, round);
|
|
1520
|
-
}
|
|
1521
1879
|
|
|
1522
|
-
|
|
1880
|
+
/**
|
|
1881
|
+
* The scale method returns a new matrix which is this matrix post multiplied by
|
|
1882
|
+
* a scale matrix containing the passed values. If the z component is undefined,
|
|
1883
|
+
* a 1 value is used in its place. If the y component is undefined, the x
|
|
1884
|
+
* component value is used in its place. This matrix is not modified.
|
|
1885
|
+
*
|
|
1886
|
+
* @param {number} x The X component of the scale value.
|
|
1887
|
+
* @param {number | null} y The Y component of the scale value.
|
|
1888
|
+
* @param {number | null} z The Z component of the scale value.
|
|
1889
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1890
|
+
*/
|
|
1891
|
+
scale(x, y, z) {
|
|
1892
|
+
const X = x;
|
|
1893
|
+
let Y = y;
|
|
1894
|
+
let Z = z;
|
|
1895
|
+
if (Y == null) Y = x;
|
|
1896
|
+
if (Z == null) Z = x;
|
|
1897
|
+
|
|
1898
|
+
return Multiply(this, Scale(X, Y, Z));
|
|
1899
|
+
}
|
|
1523
1900
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1901
|
+
/**
|
|
1902
|
+
* The rotate method returns a new matrix which is this matrix post multiplied
|
|
1903
|
+
* by each of 3 rotation matrices about the major axes, first X, then Y, then Z.
|
|
1904
|
+
* If the y and z components are undefined, the x value is used to rotate the
|
|
1905
|
+
* object about the z axis, as though the vector (0,0,x) were passed. All
|
|
1906
|
+
* rotation values are in degrees. This matrix is not modified.
|
|
1907
|
+
*
|
|
1908
|
+
* @param {number} rx The X component of the rotation, or Z if Y and Z are null.
|
|
1909
|
+
* @param {number | null} ry The (optional) Y component of the rotation value.
|
|
1910
|
+
* @param {number | null} rz The (optional) Z component of the rotation value.
|
|
1911
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1912
|
+
*/
|
|
1913
|
+
rotate(rx, ry, rz) {
|
|
1914
|
+
let RX = rx;
|
|
1915
|
+
let RY = ry;
|
|
1916
|
+
let RZ = rz;
|
|
1917
|
+
if (RY == null) RY = 0;
|
|
1918
|
+
if (RZ == null) { RZ = RX; RX = 0; }
|
|
1919
|
+
return Multiply(this, Rotate(RX, RY, RZ));
|
|
1920
|
+
}
|
|
1530
1921
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
return len;
|
|
1922
|
+
/**
|
|
1923
|
+
* The rotateAxisAngle method returns a new matrix which is this matrix post
|
|
1924
|
+
* multiplied by a rotation matrix with the given axis and `angle`. The right-hand
|
|
1925
|
+
* rule is used to determine the direction of rotation. All rotation values are
|
|
1926
|
+
* in degrees. This matrix is not modified.
|
|
1927
|
+
*
|
|
1928
|
+
* @param {number} x The X component of the axis vector.
|
|
1929
|
+
* @param {number} y The Y component of the axis vector.
|
|
1930
|
+
* @param {number} z The Z component of the axis vector.
|
|
1931
|
+
* @param {number} angle The angle of rotation about the axis vector, in degrees.
|
|
1932
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1933
|
+
*/
|
|
1934
|
+
rotateAxisAngle(x, y, z, angle) {
|
|
1935
|
+
if ([x, y, z, angle].some((n) => Number.isNaN(n))) {
|
|
1936
|
+
throw new TypeError('CSSMatrix: expecting 4 values');
|
|
1547
1937
|
}
|
|
1548
|
-
|
|
1549
|
-
}
|
|
1938
|
+
return Multiply(this, RotateAxisAngle(x, y, z, angle));
|
|
1939
|
+
}
|
|
1550
1940
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Specifies a skew transformation along the `x-axis` by the given angle.
|
|
1943
|
+
* This matrix is not modified.
|
|
1944
|
+
*
|
|
1945
|
+
* @param {number} angle The angle amount in degrees to skew.
|
|
1946
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1947
|
+
*/
|
|
1948
|
+
skewX(angle) {
|
|
1949
|
+
return Multiply(this, SkewX(angle));
|
|
1950
|
+
}
|
|
1556
1951
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1952
|
+
/**
|
|
1953
|
+
* Specifies a skew transformation along the `y-axis` by the given angle.
|
|
1954
|
+
* This matrix is not modified.
|
|
1955
|
+
*
|
|
1956
|
+
* @param {number} angle The angle amount in degrees to skew.
|
|
1957
|
+
* @return {CSSMatrix} The resulted matrix
|
|
1958
|
+
*/
|
|
1959
|
+
skewY(angle) {
|
|
1960
|
+
return Multiply(this, SkewY(angle));
|
|
1961
|
+
}
|
|
1561
1962
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1963
|
+
/**
|
|
1964
|
+
* Transforms a specified point using the matrix, returning a new
|
|
1965
|
+
* Tuple *Object* comprising of the transformed point.
|
|
1966
|
+
* Neither the matrix nor the original point are altered.
|
|
1967
|
+
*
|
|
1968
|
+
* The method is equivalent with `transformPoint()` method
|
|
1969
|
+
* of the `DOMMatrix` constructor.
|
|
1970
|
+
*
|
|
1971
|
+
* @copyright thednp © 2021
|
|
1972
|
+
*
|
|
1973
|
+
* @param {DMNS.PointTuple | DOMPoint} v Tuple or DOMPoint
|
|
1974
|
+
* @return {DMNS.PointTuple} the resulting Tuple
|
|
1975
|
+
*/
|
|
1976
|
+
transformPoint(v) {
|
|
1977
|
+
const M = this;
|
|
1978
|
+
let m = Translate(v.x, v.y, v.z);
|
|
1565
1979
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
-0.5873, 0.5873, -0.7699, 0.7699,
|
|
1569
|
-
-0.9041, 0.9041, -0.9816, 0.9816];
|
|
1570
|
-
const Cvalues = [0.2491, 0.2491, 0.2335, 0.2335,
|
|
1571
|
-
0.2032, 0.2032, 0.1601, 0.1601,
|
|
1572
|
-
0.1069, 0.1069, 0.0472, 0.0472];
|
|
1980
|
+
m.m44 = v.w || 1;
|
|
1981
|
+
m = M.multiply(m);
|
|
1573
1982
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1983
|
+
return {
|
|
1984
|
+
x: m.m41,
|
|
1985
|
+
y: m.m42,
|
|
1986
|
+
z: m.m43,
|
|
1987
|
+
w: m.m44,
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1582
1990
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1991
|
+
/**
|
|
1992
|
+
* Transforms a specified vector using the matrix, returning a new
|
|
1993
|
+
* {x,y,z,w} Tuple *Object* comprising the transformed vector.
|
|
1994
|
+
* Neither the matrix nor the original vector are altered.
|
|
1995
|
+
*
|
|
1996
|
+
* @param {DMNS.PointTuple} t Tuple with `{x,y,z,w}` components
|
|
1997
|
+
* @return {DMNS.PointTuple} the resulting Tuple
|
|
1998
|
+
*/
|
|
1999
|
+
transform(t) {
|
|
2000
|
+
const m = this;
|
|
2001
|
+
const x = m.m11 * t.x + m.m12 * t.y + m.m13 * t.z + m.m14 * t.w;
|
|
2002
|
+
const y = m.m21 * t.x + m.m22 * t.y + m.m23 * t.z + m.m24 * t.w;
|
|
2003
|
+
const z = m.m31 * t.x + m.m32 * t.y + m.m33 * t.z + m.m34 * t.w;
|
|
2004
|
+
const w = m.m41 * t.x + m.m42 * t.y + m.m43 * t.z + m.m44 * t.w;
|
|
1593
2005
|
|
|
1594
|
-
|
|
1595
|
-
|
|
2006
|
+
return {
|
|
2007
|
+
x: x / w,
|
|
2008
|
+
y: y / w,
|
|
2009
|
+
z: z / w,
|
|
2010
|
+
w,
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
1596
2013
|
}
|
|
1597
2014
|
|
|
1598
|
-
//
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
2015
|
+
// Add Transform Functions to CSSMatrix object
|
|
2016
|
+
CSSMatrix.Translate = Translate;
|
|
2017
|
+
CSSMatrix.Rotate = Rotate;
|
|
2018
|
+
CSSMatrix.RotateAxisAngle = RotateAxisAngle;
|
|
2019
|
+
CSSMatrix.Scale = Scale;
|
|
2020
|
+
CSSMatrix.SkewX = SkewX;
|
|
2021
|
+
CSSMatrix.SkewY = SkewY;
|
|
2022
|
+
CSSMatrix.Multiply = Multiply;
|
|
2023
|
+
CSSMatrix.fromArray = fromArray;
|
|
2024
|
+
CSSMatrix.fromMatrix = fromMatrix;
|
|
2025
|
+
CSSMatrix.fromString = fromString;
|
|
2026
|
+
CSSMatrix.Version = DMVersion;
|
|
1605
2027
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
2028
|
+
/**
|
|
2029
|
+
* Returns a transformation matrix to apply to `<path>` elements.
|
|
2030
|
+
*
|
|
2031
|
+
* @param {SVGPC.transformObject} transform the `transformObject`
|
|
2032
|
+
* @returns {CSSMatrix} a new transformation matrix
|
|
2033
|
+
*/
|
|
2034
|
+
function getSVGMatrix(transform) {
|
|
2035
|
+
let matrix = new CSSMatrix();
|
|
2036
|
+
const { origin } = transform;
|
|
2037
|
+
const originX = +origin[0];
|
|
2038
|
+
const originY = +origin[1];
|
|
2039
|
+
const { translate } = transform;
|
|
2040
|
+
const { rotate } = transform;
|
|
2041
|
+
const { skew } = transform;
|
|
2042
|
+
const { scale } = transform;
|
|
1610
2043
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2044
|
+
// set translate
|
|
2045
|
+
if ((Array.isArray(translate) && translate.some((x) => +x !== 0)) || !Number.isNaN(translate)) {
|
|
2046
|
+
matrix = Array.isArray(translate)
|
|
2047
|
+
? matrix.translate(+translate[0] || 0, +translate[1] || 0, +translate[2] || 0)
|
|
2048
|
+
: matrix.translate(+translate || 0);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (rotate || skew || scale) {
|
|
2052
|
+
// set SVG transform-origin, always defined
|
|
2053
|
+
// matrix = matrix.translate(+originX,+originY,+originZ)
|
|
2054
|
+
matrix = matrix.translate(+originX, +originY);
|
|
2055
|
+
|
|
2056
|
+
// set rotation
|
|
2057
|
+
if (rotate) {
|
|
2058
|
+
matrix = Array.isArray(rotate) && rotate.some((x) => +x !== 0)
|
|
2059
|
+
? matrix.rotate(+rotate[0] || 0, +rotate[1] || 0, +rotate[2] || 0)
|
|
2060
|
+
: matrix.rotate(+rotate || 0);
|
|
2061
|
+
}
|
|
2062
|
+
// set skew(s)
|
|
2063
|
+
if (Array.isArray(skew) && skew.some((x) => +x !== 0)) {
|
|
2064
|
+
if (Array.isArray(skew)) {
|
|
2065
|
+
matrix = skew[0] ? matrix.skewX(+skew[0] || 0) : matrix;
|
|
2066
|
+
matrix = skew[1] ? matrix.skewY(+skew[1] || 0) : matrix;
|
|
2067
|
+
} else {
|
|
2068
|
+
matrix = matrix.skewX(+skew || 0);
|
|
2069
|
+
}
|
|
1617
2070
|
}
|
|
2071
|
+
// set scale
|
|
2072
|
+
if (!Number.isNaN(scale) || (Array.isArray(scale) && scale.some((x) => +x !== 1))) {
|
|
2073
|
+
matrix = Array.isArray(scale)
|
|
2074
|
+
? (matrix.scale(+scale[0] || 1, +scale[1] || 1, +scale[2] || 1))
|
|
2075
|
+
: matrix.scale(+scale || 1);
|
|
2076
|
+
}
|
|
2077
|
+
// set SVG transform-origin
|
|
2078
|
+
// matrix = matrix.translate(-originX,-originY,-originZ)
|
|
2079
|
+
matrix = matrix.translate(-originX, -originY);
|
|
2080
|
+
}
|
|
2081
|
+
return matrix;
|
|
2082
|
+
}
|
|
1618
2083
|
|
|
1619
|
-
|
|
1620
|
-
|
|
2084
|
+
/**
|
|
2085
|
+
* Apply a 2D transformation matrix to an ellipse.
|
|
2086
|
+
*
|
|
2087
|
+
* @param {number[]} m the 2D transformation matrix
|
|
2088
|
+
* @param {number} rx ellipse radius X
|
|
2089
|
+
* @param {number} ry ellipse radius Y
|
|
2090
|
+
* @param {number} ax ellipse rotation angle
|
|
2091
|
+
*/
|
|
2092
|
+
function transformEllipse(m, rx, ry, ax) {
|
|
2093
|
+
// We consider the current ellipse as image of the unit circle
|
|
2094
|
+
// by first scale(rx,ry) and then rotate(ax) ...
|
|
2095
|
+
// So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle.
|
|
2096
|
+
const c = Math.cos((ax * Math.PI) / 180);
|
|
2097
|
+
const s = Math.sin((ax * Math.PI) / 180);
|
|
2098
|
+
const ma = [
|
|
2099
|
+
rx * (m[0] * c + m[2] * s),
|
|
2100
|
+
rx * (m[1] * c + m[3] * s),
|
|
2101
|
+
ry * (-m[0] * s + m[2] * c),
|
|
2102
|
+
ry * (-m[1] * s + m[3] * c),
|
|
2103
|
+
];
|
|
2104
|
+
|
|
2105
|
+
// ma * transpose(ma) = [ J L ]
|
|
2106
|
+
// [ L K ]
|
|
2107
|
+
// L is calculated later (if the image is not a circle)
|
|
2108
|
+
const J = ma[0] * ma[0] + ma[2] * ma[2];
|
|
2109
|
+
const K = ma[1] * ma[1] + ma[3] * ma[3];
|
|
2110
|
+
|
|
2111
|
+
// the discriminant of the characteristic polynomial of ma * transpose(ma)
|
|
2112
|
+
let D = ((ma[0] - ma[3]) * (ma[0] - ma[3]) + (ma[2] + ma[1]) * (ma[2] + ma[1]))
|
|
2113
|
+
* ((ma[0] + ma[3]) * (ma[0] + ma[3]) + (ma[2] - ma[1]) * (ma[2] - ma[1]));
|
|
2114
|
+
|
|
2115
|
+
// the "mean eigenvalue"
|
|
2116
|
+
const JK = (J + K) / 2;
|
|
2117
|
+
|
|
2118
|
+
// check if the image is (almost) a circle
|
|
2119
|
+
if (D < epsilon * JK) {
|
|
2120
|
+
// if it is
|
|
2121
|
+
const rxy = Math.sqrt(JK);
|
|
2122
|
+
|
|
2123
|
+
return { rx: rxy, ry: rxy, ax: 0 };
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// if it is not a circle
|
|
2127
|
+
const L = ma[0] * ma[1] + ma[2] * ma[3];
|
|
2128
|
+
|
|
2129
|
+
D = Math.sqrt(D);
|
|
2130
|
+
|
|
2131
|
+
// {l1,l2} = the two eigen values of ma * transpose(ma)
|
|
2132
|
+
const l1 = JK + D / 2;
|
|
2133
|
+
const l2 = JK - D / 2;
|
|
2134
|
+
// the x - axis - rotation angle is the argument of the l1 - eigenvector
|
|
2135
|
+
let AX = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? 90
|
|
2136
|
+
: Math.atan(Math.abs(L) > Math.abs(l1 - K) ? (l1 - J) / L
|
|
2137
|
+
: ((L / (l1 - K))) * 180) / Math.PI;
|
|
2138
|
+
let RX;
|
|
2139
|
+
let RY;
|
|
2140
|
+
|
|
2141
|
+
// if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90
|
|
2142
|
+
if (AX >= 0) {
|
|
2143
|
+
// if ax in [0,90]
|
|
2144
|
+
RX = Math.sqrt(l1);
|
|
2145
|
+
RY = Math.sqrt(l2);
|
|
2146
|
+
} else {
|
|
2147
|
+
// if ax in ]-90,0[ => exchange axes
|
|
2148
|
+
AX += 90;
|
|
2149
|
+
RX = Math.sqrt(l2);
|
|
2150
|
+
RY = Math.sqrt(l1);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
return { rx: RX, ry: RY, ax: AX };
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* Returns the [x,y] projected coordinates for a given an [x,y] point
|
|
2158
|
+
* and an [x,y,z] perspective origin point.
|
|
2159
|
+
*
|
|
2160
|
+
* Equation found here =>
|
|
2161
|
+
* http://en.wikipedia.org/wiki/3D_projection#Diagram
|
|
2162
|
+
* Details =>
|
|
2163
|
+
* https://stackoverflow.com/questions/23792505/predicted-rendering-of-css-3d-transformed-pixel
|
|
2164
|
+
*
|
|
2165
|
+
* @param {SVGPC.CSSMatrix} m the transformation matrix
|
|
2166
|
+
* @param {Number[]} point2D the initial [x,y] coordinates
|
|
2167
|
+
* @param {number[]} origin the initial [x,y] coordinates
|
|
2168
|
+
* @returns {Number[]} the projected [x,y] coordinates
|
|
2169
|
+
*/
|
|
2170
|
+
function projection2d(m, point2D, origin) {
|
|
2171
|
+
const point3D = m.transformPoint({
|
|
2172
|
+
x: point2D[0], y: point2D[1], z: 0, w: 1,
|
|
2173
|
+
});
|
|
2174
|
+
const originX = origin[0] || 0;
|
|
2175
|
+
const originY = origin[1] || 0;
|
|
2176
|
+
const originZ = origin[2] || 0;
|
|
2177
|
+
const relativePositionX = point3D.x - originX;
|
|
2178
|
+
const relativePositionY = point3D.y - originY;
|
|
2179
|
+
const relativePositionZ = point3D.z - originZ;
|
|
2180
|
+
|
|
2181
|
+
return [
|
|
2182
|
+
relativePositionX * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originX,
|
|
2183
|
+
relativePositionY * (Math.abs(originZ) / Math.abs(relativePositionZ)) + originY,
|
|
2184
|
+
];
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
* Apply a 2D / 3D transformation to a `pathArray` instance.
|
|
2189
|
+
*
|
|
2190
|
+
* Since *SVGElement* doesn't support 3D transformation, this function
|
|
2191
|
+
* creates a 2D projection of the <path> element.
|
|
2192
|
+
*
|
|
2193
|
+
* @param {SVGPC.pathArray} path the `pathArray` to apply transformation
|
|
2194
|
+
* @param {any} transform the transform functions `Object`
|
|
2195
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray`
|
|
2196
|
+
*/
|
|
2197
|
+
function transformPath(path, transform) {
|
|
2198
|
+
let x = 0; let y = 0; let i; let j; let ii; let jj; let lx; let ly; let te;
|
|
2199
|
+
const absolutePath = pathToAbsolute(path);
|
|
2200
|
+
const normalizedPath = normalizePath(absolutePath);
|
|
2201
|
+
const matrixInstance = getSVGMatrix(transform);
|
|
2202
|
+
const transformProps = Object.keys(transform);
|
|
2203
|
+
const { origin } = transform;
|
|
2204
|
+
const {
|
|
2205
|
+
a, b, c, d, e, f,
|
|
2206
|
+
} = matrixInstance;
|
|
2207
|
+
const matrix2d = [a, b, c, d, e, f];
|
|
2208
|
+
const params = {
|
|
2209
|
+
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
2210
|
+
};
|
|
2211
|
+
let segment = [];
|
|
2212
|
+
let seglen = 0;
|
|
2213
|
+
let pathCommand = '';
|
|
2214
|
+
/** @type {any} */
|
|
2215
|
+
let transformedPath = [];
|
|
2216
|
+
const allPathCommands = []; // needed for arc to curve transformation
|
|
2217
|
+
let result = [];
|
|
2218
|
+
|
|
2219
|
+
if (!matrixInstance.isIdentity) {
|
|
2220
|
+
for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
|
|
2221
|
+
segment = absolutePath[i];
|
|
2222
|
+
|
|
2223
|
+
if (absolutePath[i]) [pathCommand] = segment;
|
|
2224
|
+
|
|
2225
|
+
// REPLACE Arc path commands with Cubic Beziers
|
|
2226
|
+
// we don't have any scripting know-how on 3d ellipse transformation
|
|
2227
|
+
/// ////////////////////////////////////////
|
|
2228
|
+
allPathCommands[i] = pathCommand;
|
|
2229
|
+
|
|
2230
|
+
// Arcs don't work very well with 3D transformations or skews
|
|
2231
|
+
if (pathCommand === 'A' && (!matrixInstance.is2D || !['skewX', 'skewY'].find((p) => transformProps.includes(p)))) {
|
|
2232
|
+
segment = segmentToCubic(normalizedPath[i], params);
|
|
2233
|
+
|
|
2234
|
+
absolutePath[i] = segmentToCubic(normalizedPath[i], params);
|
|
2235
|
+
fixArc(absolutePath, allPathCommands, i);
|
|
2236
|
+
|
|
2237
|
+
normalizedPath[i] = segmentToCubic(normalizedPath[i], params);
|
|
2238
|
+
fixArc(normalizedPath, allPathCommands, i);
|
|
2239
|
+
ii = Math.max(absolutePath.length, normalizedPath.length);
|
|
2240
|
+
}
|
|
2241
|
+
/// ////////////////////////////////////////
|
|
2242
|
+
|
|
2243
|
+
segment = normalizedPath[i];
|
|
2244
|
+
seglen = segment.length;
|
|
2245
|
+
|
|
2246
|
+
params.x1 = +segment[seglen - 2];
|
|
2247
|
+
params.y1 = +segment[seglen - 1];
|
|
2248
|
+
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
2249
|
+
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
2250
|
+
// @ts-ignore
|
|
2251
|
+
result = { s: absolutePath[i], c: absolutePath[i][0] };
|
|
2252
|
+
|
|
2253
|
+
if (pathCommand !== 'Z') {
|
|
2254
|
+
// @ts-ignore
|
|
2255
|
+
result.x = params.x1;
|
|
2256
|
+
// @ts-ignore
|
|
2257
|
+
result.y = params.y1;
|
|
2258
|
+
}
|
|
2259
|
+
// @ts-ignore
|
|
2260
|
+
transformedPath = transformedPath.concat(result);
|
|
2261
|
+
}
|
|
2262
|
+
// @ts-ignore
|
|
2263
|
+
return transformedPath.map((seg) => {
|
|
2264
|
+
pathCommand = seg.c;
|
|
2265
|
+
segment = seg.s;
|
|
2266
|
+
switch (pathCommand) {
|
|
2267
|
+
case 'A': // only apply to 2D transformations
|
|
2268
|
+
te = transformEllipse(matrix2d, segment[1], segment[2], segment[3]);
|
|
2269
|
+
|
|
2270
|
+
if (matrix2d[0] * matrix2d[3] - matrix2d[1] * matrix2d[2] < 0) {
|
|
2271
|
+
segment[5] = +segment[5] ? 0 : 1;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
[lx, ly] = projection2d(matrixInstance, [segment[6], segment[7]], origin);
|
|
2275
|
+
|
|
2276
|
+
if ((x === lx && y === ly) || (te.rx < epsilon * te.ry) || (te.ry < epsilon * te.rx)) {
|
|
2277
|
+
segment = ['L', lx, ly];
|
|
2278
|
+
} else {
|
|
2279
|
+
segment = [pathCommand, te.rx, te.ry, te.ax, segment[4], segment[5], lx, ly];
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
x = lx; y = ly;
|
|
2283
|
+
return segment;
|
|
2284
|
+
|
|
2285
|
+
case 'L':
|
|
2286
|
+
case 'H':
|
|
2287
|
+
case 'V':
|
|
2288
|
+
|
|
2289
|
+
[lx, ly] = projection2d(matrixInstance, [seg.x, seg.y], origin);
|
|
2290
|
+
|
|
2291
|
+
if (x !== lx && y !== ly) {
|
|
2292
|
+
segment = ['L', lx, ly];
|
|
2293
|
+
} else if (y === ly) {
|
|
2294
|
+
segment = ['H', lx];
|
|
2295
|
+
} else if (x === lx) {
|
|
2296
|
+
segment = ['V', ly];
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
x = lx; y = ly; // now update x and y
|
|
2300
|
+
|
|
2301
|
+
return segment;
|
|
2302
|
+
default:
|
|
2303
|
+
for (j = 1, jj = segment.length; j < jj; j += 2) {
|
|
2304
|
+
// compute line coordinates without altering previous coordinates
|
|
2305
|
+
[x, y] = projection2d(matrixInstance, [segment[j], segment[j + 1]], origin);
|
|
2306
|
+
segment[j] = x;
|
|
2307
|
+
segment[j + 1] = y;
|
|
2308
|
+
}
|
|
2309
|
+
return segment;
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
return clonePath(absolutePath);
|
|
1621
2314
|
}
|
|
1622
2315
|
|
|
1623
|
-
|
|
2316
|
+
/**
|
|
2317
|
+
* Returns the cubic-bezier segment length.
|
|
2318
|
+
*
|
|
2319
|
+
* @param {number} p1x the starting point X
|
|
2320
|
+
* @param {number} p1y the starting point Y
|
|
2321
|
+
* @param {number} c1x the first control point X
|
|
2322
|
+
* @param {number} c1y the first control point Y
|
|
2323
|
+
* @param {number} c2x the second control point X
|
|
2324
|
+
* @param {number} c2y the second control point Y
|
|
2325
|
+
* @param {number} p2x the ending point X
|
|
2326
|
+
* @param {number} p2y the ending point Y
|
|
2327
|
+
* @returns {SVGPC.segmentLimits} the length of the cubic-bezier segment
|
|
2328
|
+
*/
|
|
1624
2329
|
function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
|
1625
2330
|
let a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
|
|
1626
2331
|
let b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
|
|
@@ -1669,21 +2374,79 @@ function getCubicSize(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
|
|
1669
2374
|
};
|
|
1670
2375
|
}
|
|
1671
2376
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2377
|
+
/**
|
|
2378
|
+
* Iterates an array to check if it's a `pathArray`
|
|
2379
|
+
* with all C (cubic bezier) segments.
|
|
2380
|
+
*
|
|
2381
|
+
* @param {string | SVGPC.pathArray} path the `Array` to be checked
|
|
2382
|
+
* @returns {boolean} iteration result
|
|
2383
|
+
*/
|
|
2384
|
+
function isCurveArray(path) {
|
|
2385
|
+
return Array.isArray(path) && isPathArray(path)
|
|
2386
|
+
&& path.slice(1).every((seg) => seg[0] === 'C');
|
|
2387
|
+
}
|
|
1679
2388
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2389
|
+
/**
|
|
2390
|
+
* Parses a path string value or 'pathArray' and returns a new one
|
|
2391
|
+
* in which all segments are converted to cubic-bezier.
|
|
2392
|
+
*
|
|
2393
|
+
* @param {string | SVGPC.pathArray} pathInput the string to be parsed or object
|
|
2394
|
+
* @returns {SVGPC.pathArray} the resulted `pathArray` converted to cubic-bezier
|
|
2395
|
+
*/
|
|
2396
|
+
function pathToCurve(pathInput) {
|
|
2397
|
+
if (Array.isArray(pathInput) && isCurveArray(pathInput)) {
|
|
2398
|
+
return clonePath(pathInput);
|
|
2399
|
+
}
|
|
1684
2400
|
|
|
1685
|
-
|
|
1686
|
-
|
|
2401
|
+
const path = normalizePath(pathInput);
|
|
2402
|
+
const params = {
|
|
2403
|
+
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null,
|
|
2404
|
+
};
|
|
2405
|
+
const allPathCommands = [];
|
|
2406
|
+
let pathCommand = '';
|
|
2407
|
+
let ii = path.length;
|
|
2408
|
+
|
|
2409
|
+
for (let i = 0; i < ii; i += 1) {
|
|
2410
|
+
const segment = path[i];
|
|
2411
|
+
const seglen = segment.length;
|
|
2412
|
+
if (segment) [pathCommand] = segment;
|
|
2413
|
+
|
|
2414
|
+
allPathCommands[i] = pathCommand;
|
|
2415
|
+
path[i] = segmentToCubic(segment, params);
|
|
2416
|
+
|
|
2417
|
+
fixArc(path, allPathCommands, i);
|
|
2418
|
+
ii = path.length; // solves curveArrays ending in Z
|
|
2419
|
+
|
|
2420
|
+
params.x1 = +segment[seglen - 2];
|
|
2421
|
+
params.y1 = +segment[seglen - 1];
|
|
2422
|
+
params.x2 = +(segment[seglen - 4]) || params.x1;
|
|
2423
|
+
params.y2 = +(segment[seglen - 3]) || params.y1;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
return path;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* Returns the bounding box of a shape.
|
|
2431
|
+
*
|
|
2432
|
+
* @param {SVGPC.pathArray} path the shape `pathArray`
|
|
2433
|
+
* @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
|
|
2434
|
+
*/
|
|
2435
|
+
function getPathBBox(path) {
|
|
2436
|
+
if (!path) {
|
|
2437
|
+
return {
|
|
2438
|
+
x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0,
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
const pathCurve = pathToCurve(path);
|
|
2442
|
+
|
|
2443
|
+
let x = 0;
|
|
2444
|
+
let y = 0;
|
|
2445
|
+
let X = [];
|
|
2446
|
+
let Y = [];
|
|
2447
|
+
|
|
2448
|
+
pathCurve.forEach((segment) => {
|
|
2449
|
+
const [s1, s2] = segment.slice(-2);
|
|
1687
2450
|
if (segment[0] === 'M') {
|
|
1688
2451
|
x = s1;
|
|
1689
2452
|
y = s2;
|
|
@@ -1706,491 +2469,413 @@ function getPathBBox(pathArray, round) {
|
|
|
1706
2469
|
const height = yBot - yTop;
|
|
1707
2470
|
|
|
1708
2471
|
return {
|
|
2472
|
+
width,
|
|
2473
|
+
height,
|
|
1709
2474
|
x: xTop,
|
|
1710
2475
|
y: yTop,
|
|
1711
2476
|
x2: xBot,
|
|
1712
2477
|
y2: yBot,
|
|
1713
|
-
width,
|
|
1714
|
-
height,
|
|
1715
2478
|
cx: xTop + width / 2,
|
|
1716
2479
|
cy: yTop + height / 2,
|
|
1717
2480
|
};
|
|
1718
2481
|
}
|
|
1719
2482
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
2483
|
+
/**
|
|
2484
|
+
* Returns the area of a single segment shape.
|
|
2485
|
+
*
|
|
2486
|
+
* http://objectmix.com/graphics/133553-area-closed-bezier-curve.html
|
|
2487
|
+
*
|
|
2488
|
+
* @param {number} x0 the starting point X
|
|
2489
|
+
* @param {number} y0 the starting point Y
|
|
2490
|
+
* @param {number} x1 the first control point X
|
|
2491
|
+
* @param {number} y1 the first control point Y
|
|
2492
|
+
* @param {number} x2 the second control point X
|
|
2493
|
+
* @param {number} y2 the second control point Y
|
|
2494
|
+
* @param {number} x3 the ending point X
|
|
2495
|
+
* @param {number} y3 the ending point Y
|
|
2496
|
+
* @returns {number} the area of the cubic-bezier segment
|
|
2497
|
+
*/
|
|
2498
|
+
function getCubicSegArea(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
2499
|
+
return (3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
|
|
2500
|
+
+ (y1 * (x0 - x2)) - (x1 * (y0 - y2))
|
|
2501
|
+
+ (y3 * (x2 + x0 / 3)) - (x3 * (y2 + y0 / 3)))) / 20;
|
|
1735
2502
|
}
|
|
1736
2503
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
let
|
|
1748
|
-
let my = 0;
|
|
1749
|
-
let
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
start += 1;
|
|
1757
|
-
resultArray.push(['M', x, y]);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
for (let i = start; i < ii; i += 1) {
|
|
1761
|
-
const segment = pathArray[i];
|
|
1762
|
-
const [pathCommand] = segment;
|
|
1763
|
-
const relativeCommand = pathCommand.toLowerCase();
|
|
1764
|
-
const relativeSegment = [];
|
|
1765
|
-
let newSeg = [];
|
|
1766
|
-
resultArray.push(relativeSegment);
|
|
1767
|
-
|
|
1768
|
-
if (pathCommand !== relativeCommand) {
|
|
1769
|
-
relativeSegment[0] = relativeCommand;
|
|
1770
|
-
switch (relativeCommand) {
|
|
1771
|
-
case 'a':
|
|
1772
|
-
newSeg = segment.slice(1, -2).concat([+segment[6] - x, +segment[7] - y]);
|
|
1773
|
-
|
|
1774
|
-
for (let j = 0; j < newSeg.length; j += 1) {
|
|
1775
|
-
relativeSegment.push(newSeg[j]);
|
|
1776
|
-
}
|
|
1777
|
-
break;
|
|
1778
|
-
case 'v':
|
|
1779
|
-
relativeSegment[1] = +segment[1] - y;
|
|
1780
|
-
break;
|
|
1781
|
-
default:
|
|
1782
|
-
// for is here to stay for eslint
|
|
1783
|
-
for (let j = 1; j < segment.length; j += 1) {
|
|
1784
|
-
relativeSegment.push(+segment[j] - (j % 2 ? x : y));
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
if (relativeCommand === 'm') {
|
|
1788
|
-
mx = +segment[1];
|
|
1789
|
-
my = +segment[2];
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
} else {
|
|
1793
|
-
if (pathCommand === 'm') {
|
|
1794
|
-
mx = +segment[1] + x;
|
|
1795
|
-
my = +segment[2] + y;
|
|
1796
|
-
}
|
|
1797
|
-
for (let j = 0; j < segment.length; j += 1) {
|
|
1798
|
-
relativeSegment.push(segment[j]);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
const segLength = relativeSegment.length;
|
|
1803
|
-
switch (relativeSegment[0]) {
|
|
1804
|
-
case 'z':
|
|
2504
|
+
/**
|
|
2505
|
+
* Returns the area of a shape.
|
|
2506
|
+
* @author Jürg Lehni & Jonathan Puckey
|
|
2507
|
+
*
|
|
2508
|
+
* => https://github.com/paperjs/paper.js/blob/develop/src/path/Path.js
|
|
2509
|
+
*
|
|
2510
|
+
* @param {SVGPC.pathArray} path the shape `pathArray`
|
|
2511
|
+
* @returns {SVGPC.pathBBox} the length of the cubic-bezier segment
|
|
2512
|
+
*/
|
|
2513
|
+
function getPathArea(path) {
|
|
2514
|
+
let x = 0; let y = 0;
|
|
2515
|
+
let mx = 0; let my = 0;
|
|
2516
|
+
let len = 0;
|
|
2517
|
+
return pathToCurve(path).map((seg) => {
|
|
2518
|
+
switch (seg[0]) {
|
|
2519
|
+
case 'M':
|
|
2520
|
+
case 'Z':
|
|
2521
|
+
mx = seg[0] === 'M' ? seg[1] : mx;
|
|
2522
|
+
my = seg[0] === 'M' ? seg[2] : my;
|
|
1805
2523
|
x = mx;
|
|
1806
2524
|
y = my;
|
|
1807
|
-
|
|
1808
|
-
case 'h':
|
|
1809
|
-
x += relativeSegment[segLength - 1];
|
|
1810
|
-
break;
|
|
1811
|
-
case 'v':
|
|
1812
|
-
y += relativeSegment[segLength - 1];
|
|
1813
|
-
break;
|
|
2525
|
+
return 0;
|
|
1814
2526
|
default:
|
|
1815
|
-
|
|
1816
|
-
y
|
|
1817
|
-
|
|
1818
|
-
}
|
|
1819
|
-
return roundPath(resultArray, round);
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
function optimizePath(pathArray, round) {
|
|
1823
|
-
const absolutePath = pathToAbsolute(pathArray, round);
|
|
1824
|
-
const relativePath = pathToRelative(pathArray, round);
|
|
1825
|
-
return absolutePath.map((x, i) => {
|
|
1826
|
-
if (i) {
|
|
1827
|
-
return x.join('').length < relativePath[i].join('').length ? x : relativePath[i];
|
|
2527
|
+
len = getCubicSegArea.apply(0, [x, y].concat(seg.slice(1)));
|
|
2528
|
+
[x, y] = seg.slice(-2);
|
|
2529
|
+
return len;
|
|
1828
2530
|
}
|
|
1829
|
-
|
|
1830
|
-
});
|
|
2531
|
+
}).reduce((a, b) => a + b, 0);
|
|
1831
2532
|
}
|
|
1832
2533
|
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
? pathArray[0].slice(1).concat(x.slice(1))
|
|
1838
|
-
: curveOnly[i - 1].slice(-2).concat(x.slice(1))))
|
|
1839
|
-
.map((x) => x.map((y, i) => x[x.length - i - 2 * (1 - (i % 2))]))
|
|
1840
|
-
.reverse();
|
|
1841
|
-
|
|
1842
|
-
return [['M'].concat(rotatedCurve[0]
|
|
1843
|
-
.slice(0, 2))]
|
|
1844
|
-
.concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
|
|
2534
|
+
function base3(p1, p2, p3, p4, t) {
|
|
2535
|
+
const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4;
|
|
2536
|
+
const t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
2537
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
1845
2538
|
}
|
|
1846
2539
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2540
|
+
/**
|
|
2541
|
+
* Returns the C (cubic-bezier) segment length.
|
|
2542
|
+
*
|
|
2543
|
+
* @param {Number} x1 the starting point X
|
|
2544
|
+
* @param {Number} y1 the starting point Y
|
|
2545
|
+
* @param {Number} x2 the first control point X
|
|
2546
|
+
* @param {Number} y2 the first control point Y
|
|
2547
|
+
* @param {Number} x3 the second control point X
|
|
2548
|
+
* @param {Number} y3 the second control point Y
|
|
2549
|
+
* @param {Number} x4 the ending point X
|
|
2550
|
+
* @param {Number} y4 the ending point Y
|
|
2551
|
+
* @param {Number} z a [0-1] ratio
|
|
2552
|
+
* @returns {Number} the cubic-bezier segment length
|
|
2553
|
+
*/
|
|
2554
|
+
function getSegCubicLength(x1, y1, x2, y2, x3, y3, x4, y4, z) {
|
|
2555
|
+
let Z;
|
|
2556
|
+
if (z === null || Number.isNaN(+z)) Z = 1;
|
|
1852
2557
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
seg: absolutePath[i], // absolute
|
|
1857
|
-
n: segment, // normalized
|
|
1858
|
-
c: absolutePath[i][0], // pathCommand
|
|
1859
|
-
x: segment[segLength - 2], // x
|
|
1860
|
-
y: segment[segLength - 1], // y
|
|
1861
|
-
};
|
|
1862
|
-
}).map((seg, i, pathArray) => {
|
|
1863
|
-
const segment = seg.seg;
|
|
1864
|
-
const data = seg.n;
|
|
1865
|
-
const prevSeg = i && pathArray[i - 1];
|
|
1866
|
-
const nextSeg = pathArray[i + 1] && pathArray[i + 1];
|
|
1867
|
-
const pathCommand = seg.c;
|
|
1868
|
-
const pLen = pathArray.length;
|
|
1869
|
-
const x = i ? pathArray[i - 1].x : pathArray[pLen - 1].x;
|
|
1870
|
-
const y = i ? pathArray[i - 1].y : pathArray[pLen - 1].y;
|
|
1871
|
-
let result = [];
|
|
2558
|
+
// Z = Z > 1 ? 1 : Z < 0 ? 0 : Z;
|
|
2559
|
+
if (Z > 1) Z = 1;
|
|
2560
|
+
if (Z < 0) Z = 0;
|
|
1872
2561
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
case 'C':
|
|
1881
|
-
if (nextSeg && nextSeg.c === 'S') {
|
|
1882
|
-
result = ['S', segment[1], segment[2], x, y];
|
|
1883
|
-
} else {
|
|
1884
|
-
result = [pathCommand, segment[3], segment[4], segment[1], segment[2], x, y];
|
|
1885
|
-
}
|
|
1886
|
-
break;
|
|
1887
|
-
case 'S':
|
|
1888
|
-
if ((prevSeg && 'CS'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'S'))) {
|
|
1889
|
-
result = ['C', data[3], data[4], data[1], data[2], x, y];
|
|
1890
|
-
} else {
|
|
1891
|
-
result = [pathCommand, data[1], data[2], x, y];
|
|
1892
|
-
}
|
|
1893
|
-
break;
|
|
1894
|
-
case 'Q':
|
|
1895
|
-
if (nextSeg && nextSeg.c === 'T') {
|
|
1896
|
-
result = ['T', x, y];
|
|
1897
|
-
} else {
|
|
1898
|
-
result = segment.slice(0, -2).concat([x, y]);
|
|
1899
|
-
}
|
|
1900
|
-
break;
|
|
1901
|
-
case 'T':
|
|
1902
|
-
if ((prevSeg && 'QT'.indexOf(prevSeg.c) > -1) && (!nextSeg || (nextSeg && nextSeg.c !== 'T'))) {
|
|
1903
|
-
result = ['Q', data[1], data[2], x, y];
|
|
1904
|
-
} else {
|
|
1905
|
-
result = [pathCommand, x, y];
|
|
1906
|
-
}
|
|
1907
|
-
break;
|
|
1908
|
-
case 'Z':
|
|
1909
|
-
result = ['M', x, y];
|
|
1910
|
-
break;
|
|
1911
|
-
case 'H':
|
|
1912
|
-
result = [pathCommand, x];
|
|
1913
|
-
break;
|
|
1914
|
-
case 'V':
|
|
1915
|
-
result = [pathCommand, y];
|
|
1916
|
-
break;
|
|
1917
|
-
default:
|
|
1918
|
-
result = segment.slice(0, -2).concat([x, y]);
|
|
1919
|
-
}
|
|
2562
|
+
const z2 = Z / 2; let ct = 0; let xbase = 0; let ybase = 0; let sum = 0;
|
|
2563
|
+
const Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678,
|
|
2564
|
+
-0.5873, 0.5873, -0.7699, 0.7699,
|
|
2565
|
+
-0.9041, 0.9041, -0.9816, 0.9816];
|
|
2566
|
+
const Cvalues = [0.2491, 0.2491, 0.2335, 0.2335,
|
|
2567
|
+
0.2032, 0.2032, 0.1601, 0.1601,
|
|
2568
|
+
0.1069, 0.1069, 0.0472, 0.0472];
|
|
1920
2569
|
|
|
1921
|
-
|
|
2570
|
+
Tvalues.forEach((T, i) => {
|
|
2571
|
+
ct = z2 * T + z2;
|
|
2572
|
+
xbase = base3(x1, x2, x3, x4, ct);
|
|
2573
|
+
ybase = base3(y1, y2, y3, y4, ct);
|
|
2574
|
+
sum += Cvalues[i] * Math.sqrt(xbase * xbase + ybase * ybase);
|
|
1922
2575
|
});
|
|
1923
|
-
|
|
1924
|
-
return isClosed ? reversedPath.reverse()
|
|
1925
|
-
: [reversedPath[0]].concat(reversedPath.slice(1).reverse());
|
|
2576
|
+
return z2 * sum;
|
|
1926
2577
|
}
|
|
1927
2578
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2579
|
+
/**
|
|
2580
|
+
* Returns the shape total length,
|
|
2581
|
+
* or the equivalent to `shape.getTotalLength()`
|
|
2582
|
+
* pathToCurve version
|
|
2583
|
+
*
|
|
2584
|
+
* @param {SVGPC.pathArray} path the ending point Y
|
|
2585
|
+
* @returns {Number} the shape total length
|
|
2586
|
+
*/
|
|
2587
|
+
function getPathLength(path) {
|
|
2588
|
+
let totalLength = 0;
|
|
2589
|
+
pathToCurve(path).forEach((s, i, curveArray) => {
|
|
2590
|
+
totalLength += s[0] === 'M' ? 0
|
|
2591
|
+
: getSegCubicLength.apply(0, curveArray[i - 1].slice(-2).concat(s.slice(1)));
|
|
2592
|
+
});
|
|
2593
|
+
return totalLength;
|
|
2594
|
+
}
|
|
1941
2595
|
|
|
1942
|
-
|
|
2596
|
+
/**
|
|
2597
|
+
* Check if a path is drawn clockwise and returns true if so,
|
|
2598
|
+
* false otherwise.
|
|
2599
|
+
*
|
|
2600
|
+
* @param {string | SVGPC.pathArray} path the path string or `pathArray`
|
|
2601
|
+
* @returns {boolean} true when clockwise or false if not
|
|
2602
|
+
*/
|
|
2603
|
+
function getDrawDirection(path) {
|
|
2604
|
+
return getPathArea(pathToCurve(path)) >= 0;
|
|
2605
|
+
}
|
|
1943
2606
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2607
|
+
/**
|
|
2608
|
+
* Returns [x,y] coordinates of a point at a given length of a shape.
|
|
2609
|
+
*
|
|
2610
|
+
* @param {string | SVGPC.pathArray} path the `pathArray` to look into
|
|
2611
|
+
* @param {Number} length the length of the shape to look at
|
|
2612
|
+
* @returns {Number[]} the requested [x,y] coordinates
|
|
2613
|
+
*/
|
|
2614
|
+
function getPointAtLength(path, length) {
|
|
2615
|
+
let totalLength = 0;
|
|
2616
|
+
let segLen;
|
|
2617
|
+
let data;
|
|
2618
|
+
let result;
|
|
1950
2619
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2620
|
+
return pathToCurve(path).map((seg, i, curveArray) => {
|
|
2621
|
+
data = i ? curveArray[i - 1].slice(-2).concat(seg.slice(1)) : seg.slice(1);
|
|
2622
|
+
segLen = i ? getSegCubicLength.apply(0, data) : 0;
|
|
2623
|
+
totalLength += segLen;
|
|
1955
2624
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
// set skew(s)
|
|
1963
|
-
if (Array.isArray(skew) && skew.some((x) => +x !== 0)) {
|
|
1964
|
-
if (Array.isArray(skew)) {
|
|
1965
|
-
matrix = skew[0] ? matrix.skewX(+skew[0] || 0) : matrix;
|
|
1966
|
-
matrix = skew[1] ? matrix.skewY(+skew[1] || 0) : matrix;
|
|
1967
|
-
} else {
|
|
1968
|
-
matrix = matrix.skewX(+skew || 0);
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
// set scale
|
|
1972
|
-
if (!Number.isNaN(scale) || (Array.isArray(scale) && scale.some((x) => +x !== 1))) {
|
|
1973
|
-
matrix = Array.isArray(scale)
|
|
1974
|
-
? (matrix.scale(+scale[0] || 1, +scale[1] || 1, +scale[2] || 1))
|
|
1975
|
-
: matrix.scale(+scale || 1);
|
|
2625
|
+
if (i === 0) {
|
|
2626
|
+
result = { x: data[0], y: data[1] };
|
|
2627
|
+
} else if (totalLength > length && length > totalLength - segLen) {
|
|
2628
|
+
result = getPointAtSegLength.apply(0, data.concat(1 - (totalLength - length) / segLen));
|
|
2629
|
+
} else {
|
|
2630
|
+
result = null;
|
|
1976
2631
|
}
|
|
1977
|
-
// set SVG transform-origin
|
|
1978
|
-
// matrix = matrix.translate(-originX,-originY,-originZ)
|
|
1979
|
-
matrix = matrix.translate(-originX, -originY);
|
|
1980
|
-
}
|
|
1981
|
-
return matrix;
|
|
1982
|
-
}
|
|
1983
2632
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
// So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle.
|
|
1988
|
-
const c = Math.cos((ax * Math.PI) / 180);
|
|
1989
|
-
const s = Math.sin((ax * Math.PI) / 180);
|
|
1990
|
-
const ma = [
|
|
1991
|
-
rx * (m[0] * c + m[2] * s),
|
|
1992
|
-
rx * (m[1] * c + m[3] * s),
|
|
1993
|
-
ry * (-m[0] * s + m[2] * c),
|
|
1994
|
-
ry * (-m[1] * s + m[3] * c),
|
|
1995
|
-
];
|
|
1996
|
-
|
|
1997
|
-
// ma * transpose(ma) = [ J L ]
|
|
1998
|
-
// [ L K ]
|
|
1999
|
-
// L is calculated later (if the image is not a circle)
|
|
2000
|
-
const J = ma[0] * ma[0] + ma[2] * ma[2];
|
|
2001
|
-
const K = ma[1] * ma[1] + ma[3] * ma[3];
|
|
2633
|
+
return result;
|
|
2634
|
+
}).filter((x) => x).slice(-1)[0]; // isolate last segment
|
|
2635
|
+
}
|
|
2002
2636
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2637
|
+
/**
|
|
2638
|
+
* Parses a path string value to determine its validity
|
|
2639
|
+
* then returns true if it's valid or false otherwise.
|
|
2640
|
+
*
|
|
2641
|
+
* @param {string} pathString the path string to be parsed
|
|
2642
|
+
* @returns {boolean} the path string validity
|
|
2643
|
+
*/
|
|
2644
|
+
function isValidPath(pathString) {
|
|
2645
|
+
if (typeof pathString !== 'string') {
|
|
2646
|
+
return false;
|
|
2647
|
+
}
|
|
2006
2648
|
|
|
2007
|
-
|
|
2008
|
-
const JK = (J + K) / 2;
|
|
2649
|
+
const path = new SVGPathArray(pathString);
|
|
2009
2650
|
|
|
2010
|
-
|
|
2011
|
-
if (D < epsilon * JK) {
|
|
2012
|
-
// if it is
|
|
2013
|
-
const rxy = Math.sqrt(JK);
|
|
2651
|
+
skipSpaces(path);
|
|
2014
2652
|
|
|
2015
|
-
|
|
2653
|
+
while (path.index < path.max && !path.err.length) {
|
|
2654
|
+
scanSegment(path);
|
|
2016
2655
|
}
|
|
2017
2656
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2657
|
+
return !path.err.length && 'mM'.includes(path.segments[0][0]);
|
|
2658
|
+
}
|
|
2020
2659
|
|
|
2021
|
-
|
|
2660
|
+
/**
|
|
2661
|
+
* Supported shapes and their specific parameters.
|
|
2662
|
+
*/
|
|
2663
|
+
const shapeParams = {
|
|
2664
|
+
circle: ['cx', 'cy', 'r'],
|
|
2665
|
+
ellipse: ['cx', 'cy', 'rx', 'ry'],
|
|
2666
|
+
rect: ['width', 'height', 'x', 'y', 'rx', 'ry'],
|
|
2667
|
+
polygon: ['points'],
|
|
2668
|
+
polyline: ['points'],
|
|
2669
|
+
glyph: [],
|
|
2670
|
+
};
|
|
2022
2671
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2672
|
+
/**
|
|
2673
|
+
* Returns a new `pathArray` from line attributes.
|
|
2674
|
+
*
|
|
2675
|
+
* @param {SVGPC.lineAttr} attr shape configuration
|
|
2676
|
+
* @return {SVGPC.pathArray} a new line `pathArray`
|
|
2677
|
+
*/
|
|
2678
|
+
function getLinePath(attr) {
|
|
2679
|
+
const {
|
|
2680
|
+
x1, y1, x2, y2,
|
|
2681
|
+
} = attr;
|
|
2682
|
+
return [['M', +x1, +y1], ['L', +x2, +y2]];
|
|
2683
|
+
}
|
|
2032
2684
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2685
|
+
/**
|
|
2686
|
+
* Returns a new `pathArray` like from polyline/polygon attributes.
|
|
2687
|
+
*
|
|
2688
|
+
* @param {SVGPC.polyAttr} attr shape configuration
|
|
2689
|
+
* @return {SVGPC.pathArray} a new polygon/polyline `pathArray`
|
|
2690
|
+
*/
|
|
2691
|
+
function getPolyPath(attr) {
|
|
2692
|
+
/** @type {SVGPC.pathArray} */
|
|
2693
|
+
const pathArray = [];
|
|
2694
|
+
const points = attr.points.split(/[\s|,]/).map(Number);
|
|
2695
|
+
|
|
2696
|
+
let index = 0;
|
|
2697
|
+
while (index < points.length) {
|
|
2698
|
+
pathArray.push([(index ? 'L' : 'M'), (points[index]), (points[index + 1])]);
|
|
2699
|
+
index += 2;
|
|
2043
2700
|
}
|
|
2044
2701
|
|
|
2045
|
-
return
|
|
2702
|
+
return attr.type === 'polygon' ? pathArray.concat([['z']]) : pathArray;
|
|
2046
2703
|
}
|
|
2047
2704
|
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
const originY = origin[1] || 0;
|
|
2059
|
-
const originZ = origin[2] || 0;
|
|
2060
|
-
const relativePositionX = point3D.x - originX;
|
|
2061
|
-
const relativePositionY = point3D.y - originY;
|
|
2062
|
-
const relativePositionZ = point3D.z - originZ;
|
|
2705
|
+
/**
|
|
2706
|
+
* Returns a new `pathArray` from circle attributes.
|
|
2707
|
+
*
|
|
2708
|
+
* @param {SVGPC.circleAttr} attr shape configuration
|
|
2709
|
+
* @return {SVGPC.pathArray} a circle `pathArray`
|
|
2710
|
+
*/
|
|
2711
|
+
function getCirclePath(attr) {
|
|
2712
|
+
const {
|
|
2713
|
+
cx, cy, r,
|
|
2714
|
+
} = attr;
|
|
2063
2715
|
|
|
2064
2716
|
return [
|
|
2065
|
-
|
|
2066
|
-
|
|
2717
|
+
['M', (cx - r), cy],
|
|
2718
|
+
['a', r, r, 0, 1, 0, (2 * r), 0],
|
|
2719
|
+
['a', r, r, 0, 1, 0, (-2 * r), 0],
|
|
2067
2720
|
];
|
|
2068
2721
|
}
|
|
2069
2722
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2723
|
+
/**
|
|
2724
|
+
* Returns a new `pathArray` from ellipse attributes.
|
|
2725
|
+
*
|
|
2726
|
+
* @param {SVGPC.ellipseAttr} attr shape configuration
|
|
2727
|
+
* @return {SVGPC.pathArray} an ellipse `pathArray`
|
|
2728
|
+
*/
|
|
2729
|
+
function getEllipsePath(attr) {
|
|
2077
2730
|
const {
|
|
2078
|
-
|
|
2079
|
-
} =
|
|
2080
|
-
const matrix2d = [a, b, c, d, e, f];
|
|
2081
|
-
const params = {
|
|
2082
|
-
x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0,
|
|
2083
|
-
};
|
|
2084
|
-
let segment = [];
|
|
2085
|
-
let seglen = 0;
|
|
2086
|
-
let pathCommand = '';
|
|
2087
|
-
let transformedPath = [];
|
|
2088
|
-
const allPathCommands = []; // needed for arc to curve transformation
|
|
2089
|
-
let result = [];
|
|
2090
|
-
|
|
2091
|
-
if (!matrixInstance.isIdentity) {
|
|
2092
|
-
for (i = 0, ii = absolutePath.length; i < ii; i += 1) {
|
|
2093
|
-
segment = absolutePath[i];
|
|
2731
|
+
cx, cy, rx, ry,
|
|
2732
|
+
} = attr;
|
|
2094
2733
|
|
|
2095
|
-
|
|
2734
|
+
return [
|
|
2735
|
+
['M', (cx - rx), cy],
|
|
2736
|
+
['a', rx, ry, 0, 1, 0, (2 * rx), 0],
|
|
2737
|
+
['a', rx, ry, 0, 1, 0, (-2 * rx), 0],
|
|
2738
|
+
];
|
|
2739
|
+
}
|
|
2096
2740
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2741
|
+
/**
|
|
2742
|
+
* Returns a new `pathArray` like from rect attributes.
|
|
2743
|
+
*
|
|
2744
|
+
* @param {SVGPC.rectAttr} attr object with properties above
|
|
2745
|
+
* @return {SVGPC.pathArray} a new `pathArray` from `<rect>` attributes
|
|
2746
|
+
*/
|
|
2747
|
+
function getRectanglePath(attr) {
|
|
2748
|
+
const x = +attr.x || 0;
|
|
2749
|
+
const y = +attr.y || 0;
|
|
2750
|
+
const w = +attr.width;
|
|
2751
|
+
const h = +attr.height;
|
|
2752
|
+
let rx = +attr.rx;
|
|
2753
|
+
let ry = +attr.ry;
|
|
2754
|
+
|
|
2755
|
+
// Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
|
|
2756
|
+
if (rx || ry) {
|
|
2757
|
+
rx = !rx ? ry : rx;
|
|
2758
|
+
ry = !ry ? rx : ry;
|
|
2759
|
+
|
|
2760
|
+
if (rx * 2 > w) rx -= (rx * 2 - w) / 2;
|
|
2761
|
+
if (ry * 2 > h) ry -= (ry * 2 - h) / 2;
|
|
2762
|
+
|
|
2763
|
+
return [
|
|
2764
|
+
['M', x + rx, y],
|
|
2765
|
+
['h', w - rx * 2],
|
|
2766
|
+
['s', rx, 0, rx, ry],
|
|
2767
|
+
['v', h - ry * 2],
|
|
2768
|
+
['s', 0, ry, -rx, ry],
|
|
2769
|
+
['h', -w + rx * 2],
|
|
2770
|
+
['s', -rx, 0, -rx, -ry],
|
|
2771
|
+
['v', -h + ry * 2],
|
|
2772
|
+
['s', 0, -ry, rx, -ry],
|
|
2773
|
+
];
|
|
2774
|
+
}
|
|
2101
2775
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2776
|
+
return [
|
|
2777
|
+
['M', x, y],
|
|
2778
|
+
['h', w],
|
|
2779
|
+
['v', h],
|
|
2780
|
+
['H', x],
|
|
2781
|
+
['Z'],
|
|
2782
|
+
];
|
|
2783
|
+
}
|
|
2105
2784
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2785
|
+
/**
|
|
2786
|
+
* Returns a new `<path>` element created from attributes of a `<line>`, `<polyline>`,
|
|
2787
|
+
* `<polygon>`, `<rect>`, `<ellipse>`, `<circle>` or `<glyph>`. If `replace` parameter
|
|
2788
|
+
* is `true`, it will replace the target.
|
|
2789
|
+
*
|
|
2790
|
+
* The newly created `<path>` element keeps all non-specific
|
|
2791
|
+
* attributes like `class`, `fill`, etc.
|
|
2792
|
+
*
|
|
2793
|
+
* @param {SVGPC.shapeTypes} element target shape
|
|
2794
|
+
* @param {boolean} replace option to replace target
|
|
2795
|
+
* @return {?SVGPathElement} the newly created `<path>` element
|
|
2796
|
+
*/
|
|
2797
|
+
function shapeToPath(element, replace) {
|
|
2798
|
+
const supportedShapes = Object.keys(shapeParams).concat(['glyph']);
|
|
2108
2799
|
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
}
|
|
2113
|
-
/// ////////////////////////////////////////
|
|
2800
|
+
if (!supportedShapes.some((s) => element.tagName === s)) {
|
|
2801
|
+
throw TypeError(`shapeToPath: ${element} is not SVGElement`);
|
|
2802
|
+
}
|
|
2114
2803
|
|
|
2115
|
-
|
|
2116
|
-
|
|
2804
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
2805
|
+
const type = element.tagName;
|
|
2806
|
+
/** @type {string[]} */
|
|
2807
|
+
const shapeAttrs = shapeParams[type];
|
|
2117
2808
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2809
|
+
// set config
|
|
2810
|
+
const config = {};
|
|
2811
|
+
config.type = type;
|
|
2812
|
+
shapeAttrs.forEach((p) => { config[p] = element.getAttribute(p); });
|
|
2122
2813
|
|
|
2123
|
-
|
|
2814
|
+
// set no-specific shape attributes: fill, stroke, etc
|
|
2815
|
+
Object.values(element.attributes).forEach(({ name, value }) => {
|
|
2816
|
+
if (!shapeAttrs.includes(name)) path.setAttribute(name, value);
|
|
2817
|
+
});
|
|
2124
2818
|
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2819
|
+
// set d
|
|
2820
|
+
let description;
|
|
2821
|
+
if (type === 'circle') description = pathToString(getCirclePath(config));
|
|
2822
|
+
else if (type === 'ellipse') description = pathToString(getEllipsePath(config));
|
|
2823
|
+
else if (['polyline', 'polygon'].includes(type)) description = pathToString(getPolyPath(config));
|
|
2824
|
+
else if (type === 'rect') description = pathToString(getRectanglePath(config));
|
|
2825
|
+
else if (type === 'line') description = pathToString(getLinePath(config));
|
|
2826
|
+
else if (type === 'glyph') description = element.getAttribute('d');
|
|
2827
|
+
|
|
2828
|
+
// replace target element
|
|
2829
|
+
if (description) {
|
|
2830
|
+
path.setAttribute('d', description);
|
|
2831
|
+
if (replace) {
|
|
2832
|
+
element.before(path, element);
|
|
2833
|
+
element.remove();
|
|
2130
2834
|
}
|
|
2835
|
+
return path;
|
|
2836
|
+
}
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2131
2839
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
segment = ['L', lx, ly];
|
|
2147
|
-
} else {
|
|
2148
|
-
segment = [pathCommand, te.rx, te.ry, te.ax, segment[4], segment[5], lx, ly];
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
x = lx; y = ly;
|
|
2152
|
-
return segment;
|
|
2153
|
-
|
|
2154
|
-
case 'L':
|
|
2155
|
-
case 'H':
|
|
2156
|
-
case 'V':
|
|
2840
|
+
/**
|
|
2841
|
+
* Reverses all segments and their values from a `pathArray`
|
|
2842
|
+
* which consists of only C (cubic-bezier) path commands.
|
|
2843
|
+
*
|
|
2844
|
+
* @param {SVGPC.pathArray} path the source `pathArray`
|
|
2845
|
+
* @returns {SVGPC.pathArray} the reversed `pathArray`
|
|
2846
|
+
*/
|
|
2847
|
+
function reverseCurve(path) {
|
|
2848
|
+
const rotatedCurve = path.slice(1)
|
|
2849
|
+
.map((x, i, curveOnly) => (!i
|
|
2850
|
+
? path[0].slice(1).concat(x.slice(1))
|
|
2851
|
+
: curveOnly[i - 1].slice(-2).concat(x.slice(1))))
|
|
2852
|
+
.map((x) => x.map((_, i) => x[x.length - i - 2 * (1 - (i % 2))]))
|
|
2853
|
+
.reverse();
|
|
2157
2854
|
|
|
2158
|
-
|
|
2855
|
+
return [['M'].concat(rotatedCurve[0]
|
|
2856
|
+
.slice(0, 2))]
|
|
2857
|
+
.concat(rotatedCurve.map((x) => ['C'].concat(x.slice(2))));
|
|
2858
|
+
}
|
|
2159
2859
|
|
|
2160
|
-
|
|
2161
|
-
segment = ['L', lx, ly];
|
|
2162
|
-
} else if (y === ly) {
|
|
2163
|
-
segment = ['H', lx];
|
|
2164
|
-
} else if (x === lx) {
|
|
2165
|
-
segment = ['V', ly];
|
|
2166
|
-
}
|
|
2860
|
+
var version = "0.1.10alpha2";
|
|
2167
2861
|
|
|
2168
|
-
|
|
2862
|
+
// @ts-ignore
|
|
2169
2863
|
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
segment[j] = x;
|
|
2176
|
-
segment[j + 1] = y;
|
|
2177
|
-
}
|
|
2178
|
-
return segment;
|
|
2179
|
-
}
|
|
2180
|
-
});
|
|
2181
|
-
return roundPath(transformedPath, round);
|
|
2182
|
-
}
|
|
2183
|
-
return clonePath(absolutePath);
|
|
2184
|
-
}
|
|
2864
|
+
/**
|
|
2865
|
+
* A global namespace for library version.
|
|
2866
|
+
* @type {string}
|
|
2867
|
+
*/
|
|
2868
|
+
const Version = version;
|
|
2185
2869
|
|
|
2186
|
-
|
|
2187
|
-
CSSMatrix
|
|
2870
|
+
const Util = {
|
|
2871
|
+
CSSMatrix,
|
|
2188
2872
|
parsePathString,
|
|
2189
2873
|
isPathArray,
|
|
2190
2874
|
isCurveArray,
|
|
2191
2875
|
isAbsoluteArray,
|
|
2192
2876
|
isRelativeArray,
|
|
2193
2877
|
isNormalizedArray,
|
|
2878
|
+
isValidPath,
|
|
2194
2879
|
pathToAbsolute,
|
|
2195
2880
|
pathToRelative,
|
|
2196
2881
|
pathToCurve,
|
|
@@ -2209,101 +2894,184 @@ var util = {
|
|
|
2209
2894
|
normalizePath,
|
|
2210
2895
|
transformPath,
|
|
2211
2896
|
getSVGMatrix,
|
|
2897
|
+
shapeToPath,
|
|
2212
2898
|
options: SVGPCO,
|
|
2899
|
+
Version,
|
|
2213
2900
|
};
|
|
2214
2901
|
|
|
2902
|
+
// import isPathArray from './util/isPathArray';
|
|
2903
|
+
|
|
2904
|
+
/**
|
|
2905
|
+
* Creates a new SVGPathCommander instance.
|
|
2906
|
+
*
|
|
2907
|
+
* @author thednp <https://github.com/thednp/svg-path-commander>
|
|
2908
|
+
* @class
|
|
2909
|
+
*/
|
|
2215
2910
|
class SVGPathCommander {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2911
|
+
/**
|
|
2912
|
+
* @constructor
|
|
2913
|
+
* @param {string} pathValue the path string
|
|
2914
|
+
* @param {any} config instance options
|
|
2915
|
+
*/
|
|
2916
|
+
constructor(pathValue, config) {
|
|
2917
|
+
const options = config || {};
|
|
2918
|
+
|
|
2919
|
+
let { round } = SVGPCO;
|
|
2920
|
+
const { round: roundOption } = options;
|
|
2921
|
+
if ((roundOption && +roundOption === 0) || roundOption === false) {
|
|
2922
|
+
round = 0;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
const { decimals } = round && (options || SVGPCO);
|
|
2222
2926
|
|
|
2223
2927
|
// set instance options
|
|
2224
|
-
this.round =
|
|
2225
|
-
|
|
2226
|
-
? originOption : null;
|
|
2928
|
+
this.round = round === 0 ? 0 : decimals;
|
|
2929
|
+
// ZERO | FALSE will disable rounding numbers
|
|
2227
2930
|
|
|
2228
|
-
|
|
2229
|
-
this.segments =
|
|
2931
|
+
/** @type {SVGPC.pathArray} */
|
|
2932
|
+
this.segments = parsePathString(pathValue);
|
|
2933
|
+
|
|
2934
|
+
/** * @type {string} */
|
|
2230
2935
|
this.pathValue = pathValue;
|
|
2936
|
+
|
|
2231
2937
|
return this;
|
|
2232
2938
|
}
|
|
2233
|
-
}
|
|
2234
2939
|
|
|
2235
|
-
|
|
2940
|
+
/**
|
|
2941
|
+
* Convert path to absolute values
|
|
2942
|
+
* @public
|
|
2943
|
+
*/
|
|
2944
|
+
toAbsolute() {
|
|
2945
|
+
const { segments } = this;
|
|
2946
|
+
this.segments = pathToAbsolute(segments);
|
|
2947
|
+
return this;
|
|
2948
|
+
}
|
|
2236
2949
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
};
|
|
2247
|
-
SVGPCProto.reverse = function reverse(onlySubpath) {
|
|
2248
|
-
this.toAbsolute();
|
|
2249
|
-
|
|
2250
|
-
const subPath = splitPath(this.pathValue).length > 1 && splitPath(this.toString());
|
|
2251
|
-
const absoluteMultiPath = subPath && clonePath(subPath)
|
|
2252
|
-
.map((x, i) => {
|
|
2253
|
-
if (onlySubpath) {
|
|
2254
|
-
return i ? reversePath(x) : parsePathString(x);
|
|
2255
|
-
}
|
|
2256
|
-
return reversePath(x);
|
|
2257
|
-
});
|
|
2950
|
+
/**
|
|
2951
|
+
* Convert path to relative values
|
|
2952
|
+
* @public
|
|
2953
|
+
*/
|
|
2954
|
+
toRelative() {
|
|
2955
|
+
const { segments } = this;
|
|
2956
|
+
this.segments = pathToRelative(segments);
|
|
2957
|
+
return this;
|
|
2958
|
+
}
|
|
2258
2959
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
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
|
+
reverse(onlySubpath) {
|
|
2966
|
+
this.toAbsolute();
|
|
2967
|
+
|
|
2968
|
+
const { segments } = this;
|
|
2969
|
+
const split = splitPath(this.toString());
|
|
2970
|
+
const subPath = split.length > 1 ? split : 0;
|
|
2971
|
+
|
|
2972
|
+
const absoluteMultiPath = subPath && clonePath(subPath)
|
|
2973
|
+
.map((x, i) => {
|
|
2974
|
+
if (onlySubpath) {
|
|
2975
|
+
// @ts-ignore
|
|
2976
|
+
return i ? reversePath(x) : parsePathString(x);
|
|
2977
|
+
}
|
|
2978
|
+
// @ts-ignore
|
|
2979
|
+
return reversePath(x);
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
let path = [];
|
|
2983
|
+
if (subPath) {
|
|
2984
|
+
// @ts-ignore
|
|
2985
|
+
path = absoluteMultiPath.flat(1);
|
|
2986
|
+
} else {
|
|
2987
|
+
path = onlySubpath ? segments : reversePath(segments);
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
this.segments = clonePath(path);
|
|
2991
|
+
return this;
|
|
2264
2992
|
}
|
|
2265
2993
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
};
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
this.segments = clonePath(path);
|
|
2277
|
-
return this;
|
|
2278
|
-
};
|
|
2279
|
-
SVGPCProto.transform = function transform(transformInput) {
|
|
2280
|
-
const transformObject = transformInput || {};
|
|
2281
|
-
if (!transformObject.origin) {
|
|
2282
|
-
const BBox = getPathBBox(this.segments);
|
|
2283
|
-
transformObject.origin = [BBox.cx, BBox.cy, BBox.cx];
|
|
2994
|
+
/**
|
|
2995
|
+
* Normalize path in 2 steps:
|
|
2996
|
+
* * convert `pathArray`(s) to absolute values
|
|
2997
|
+
* * convert shorthand notation to standard notation
|
|
2998
|
+
* @public
|
|
2999
|
+
*/
|
|
3000
|
+
normalize() {
|
|
3001
|
+
const { segments } = this;
|
|
3002
|
+
this.segments = normalizePath(segments);
|
|
3003
|
+
return this;
|
|
2284
3004
|
}
|
|
2285
3005
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
3006
|
+
/**
|
|
3007
|
+
* Optimize `pathArray` values:
|
|
3008
|
+
* * convert segments to absolute and/or relative values
|
|
3009
|
+
* * select segments with shortest resulted string
|
|
3010
|
+
* * round values to the specified `decimals` option value
|
|
3011
|
+
* @public
|
|
3012
|
+
*/
|
|
3013
|
+
optimize() {
|
|
3014
|
+
const { segments } = this;
|
|
2291
3015
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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 SVGPC.transformObject for a quick refference
|
|
3023
|
+
*
|
|
3024
|
+
* @param {Object.<string, (number | number[])>} source a `transformObject`as described above
|
|
3025
|
+
* @public
|
|
3026
|
+
*/
|
|
3027
|
+
transform(source) {
|
|
3028
|
+
if (!source || typeof source !== 'object' || (typeof source === 'object'
|
|
3029
|
+
&& !['translate', 'rotate', 'skew', 'scale'].some((x) => x in source))) return this;
|
|
3030
|
+
|
|
3031
|
+
const transform = source || {};
|
|
3032
|
+
const { segments } = this;
|
|
3033
|
+
|
|
3034
|
+
// if origin is not specified
|
|
3035
|
+
// it's important that we have one
|
|
3036
|
+
if (!transform.origin) {
|
|
3037
|
+
const BBox = getPathBBox(segments);
|
|
3038
|
+
transform.origin = [BBox.cx, BBox.cy, BBox.cx];
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
this.segments = transformPath(segments, transform);
|
|
3042
|
+
return this;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
/**
|
|
3046
|
+
* Rotate path 180deg horizontally
|
|
3047
|
+
* @public
|
|
3048
|
+
*/
|
|
3049
|
+
flipX() {
|
|
3050
|
+
this.transform({ rotate: [180, 0, 0] });
|
|
3051
|
+
return this;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
/**
|
|
3055
|
+
* Rotate path 180deg vertically
|
|
3056
|
+
* @public
|
|
3057
|
+
*/
|
|
3058
|
+
flipY() {
|
|
3059
|
+
this.transform({ rotate: [0, 180, 0] });
|
|
3060
|
+
return this;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
/**
|
|
3064
|
+
* Export the current path to be used
|
|
3065
|
+
* for the `d` (description) attribute.
|
|
3066
|
+
* @public
|
|
3067
|
+
* @return {String} the path string
|
|
3068
|
+
*/
|
|
3069
|
+
toString() {
|
|
3070
|
+
return pathToString(this.segments, this.round);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
2306
3073
|
|
|
2307
|
-
|
|
3074
|
+
// @ts-ignore
|
|
3075
|
+
Object.keys(Util).forEach((x) => { SVGPathCommander[x] = Util[x]; });
|
|
2308
3076
|
|
|
2309
3077
|
export { SVGPathCommander as default };
|