generate iconFont
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1120 lines
30 KiB

/* eslint-disable no-undef */
/*!
2D Transformation Matrix v2.7.5
(c) Epistemex.com 2014-2018
License: MIT
*/
/**
* 2D transformation matrix object initialized with identity matrix.
*
* The matrix can synchronize a canvas 2D context by supplying the context
* as an argument, or later apply current absolute transform to an
* existing context.
*
* To synchronize a DOM element you can use [`toCSS()`]{@link Matrix#toCSS} or [`toCSS3D()`]{@link Matrix#toCSS3D}.
* together with for example the `style.transform` property.
*
* @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix
* @param {HTMLElement} [element=null] - DOM Element to synchronize
* @prop {number} a - scale x
* @prop {number} b - shear y
* @prop {number} c - shear x
* @prop {number} d - scale y
* @prop {number} e - translate x
* @prop {number} f - translate y
* @prop {CanvasRenderingContext2D} [context] - set or get current synchronized 2D context
* @prop {HTMLElement} [element] - get current synchronized DOM element
* @prop {boolean} [useCSS3D=false] - is a DOM element is defined for sync., choose whether to use 2D (false) or 3D (true) matrix to sync it.
* @constructor
* @license MIT license
* @copyright Epistemex.com 2014-2018
*/
function Matrix(context, element) {
var me = this,
_el;
me._t = me.transform;
me.a = me.d = 1;
me.b = me.c = me.e = me.f = 0;
// sync context
if (context) (me.context = context).setTransform(1, 0, 0, 1, 0, 0);
// sync DOM element
Object.defineProperty(me, 'element', {
get: function () {
return _el;
},
set: function (el) {
if (!_el) {
me._px = me._getPX();
me.useCSS3D = false;
}
_el = el;
(me._st = _el.style)[me._px] = me.toCSS();
},
});
if (element) me.element = element;
}
/**
* Returns a new matrix that transforms a triangle `t1` into another triangle
* `t2`, or throws an exception if it is impossible.
*
* Note: the method can take both arrays as well as literal objects.
* Just make sure that both arguments (`t1`, `t2`) are of the same type.
*
* @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t1 - Object or array containing the three points for the triangle.
* For object use obj.px, obj.py, obj.qx, obj.qy, obj.rx and obj.ry. For arrays provide the points in the order [px, py, qx, qy, rx, ry], or as point array [{x:,y:}, {x:,y:}, {x:,y:}]
* @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t2 - See description for t1.
* @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
* @returns {Matrix}
* @throws Exception is matrix becomes not invertible
* @static
*/
Matrix.fromTriangles = function (t1, t2, context) {
var m1 = new Matrix(),
m2 = new Matrix(context),
r1,
r2,
rx1,
ry1,
rx2,
ry2;
if (Array.isArray(t1)) {
if (typeof t1[0] === 'number') {
rx1 = t1[4];
ry1 = t1[5];
rx2 = t2[4];
ry2 = t2[5];
r1 = [t1[0] - rx1, t1[1] - ry1, t1[2] - rx1, t1[3] - ry1, rx1, ry1];
r2 = [t2[0] - rx2, t2[1] - ry2, t2[2] - rx2, t2[3] - ry2, rx2, ry2];
} else {
rx1 = t1[2].x;
ry1 = t1[2].y;
rx2 = t2[2].x;
ry2 = t2[2].y;
r1 = [
t1[0].x - rx1,
t1[0].y - ry1,
t1[1].x - rx1,
t1[1].y - ry1,
rx1,
ry1,
];
r2 = [
t2[0].x - rx2,
t2[0].y - ry2,
t2[1].x - rx2,
t2[1].y - ry2,
rx2,
ry2,
];
}
} else {
r1 = [
t1.px - t1.rx,
t1.py - t1.ry,
t1.qx - t1.rx,
t1.qy - t1.ry,
t1.rx,
t1.ry,
];
r2 = [
t2.px - t2.rx,
t2.py - t2.ry,
t2.qx - t2.rx,
t2.qy - t2.ry,
t2.rx,
t2.ry,
];
}
m1.setTransform.apply(m1, r1);
m2.setTransform.apply(m2, r2);
return m2.multiply(m1.inverse());
};
/**
* Create a matrix from a transform list from an SVG shape. The list
* can be for example baseVal (i.e. `shape.transform.baseVal`).
*
* The resulting matrix has all transformations from that list applied
* in the same order as the list.
*
* @param {SVGTransformList} tList - transform list from an SVG shape.
* @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
* @param {HTMLElement} [dom] - optional DOM element to use for the matrix
* @returns {Matrix}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGTransformList|MDN / SVGTransformList}
*/
Matrix.fromSVGTransformList = function (tList, context, dom) {
var m = new Matrix(context, dom),
i = 0;
while (i < tList.length) m.multiply(tList[i++].matrix);
return m;
};
/**
* Create and transform a new matrix based on given matrix values, or
* provide SVGMatrix or a (2D) DOMMatrix, WebKitCSSMatrix or another
* instance of a generic Matrix.
*
* @example
*
* var m = Matrix.from(1, 0.2, 0, 2, 120, 97);
* var m = Matrix.from(domMatrix, ctx);
* var m = Matrix.from(svgMatrix);
* var m = Matrix.from(cssMatrix);
* var m = Matrix.from(matrix);
* var m = Matrix.from(vector [,pre-x] [,pre-y] [,doScale]);
*
* @param {*} a - number representing a in [a-f], or a Matrix object containing properties a-f. Vector is given as an object with properties x and y.
* @param {*} [b] - b property if a is not a matrix object, or optional canvas 2D context.
* If vector is input this will be pre-translate for x.
* @param {number} [c] - If vector is input this will be pre-translate for y.
* @param {number} [d] - If vector is input, set this to true to use scale and translate of 1,
* false to use hypotenuse as translate distance instead and no scale.
* @param {number} [e]
* @param {number} [f]
* @param {CanvasRenderingContext2D} [context] - optional canvas context to synchronize
* @param {HTMLElement} [dom] - optional DOM element to use for the matrix
* @returns {Matrix}
* @static
*/
Matrix.from = function (a, b, c, d, e, f, context, dom) {
var m = new Matrix(context, dom),
scale,
dist,
q;
if (typeof a === 'number') m.setTransform(a, b, c, d, e, f);
else if (typeof a.x === 'number') {
// vector
q = Math.sqrt(a.x * a.x + a.y * a.y);
scale = dist = 1;
if (d) scale = q;
else dist = q;
m.translate(b || 0, c || 0)
.rotateFromVector(a)
.scaleU(scale)
.translate(dist, 0);
} else {
if (typeof a.is2D === 'boolean' && !a.is2D)
throw 'Cannot use 3D DOMMatrix.';
if (b) m.context = b;
if (c) m.element = c;
m.multiply(a);
}
return m;
};
Matrix.prototype = {
_getPX: function () {
var lst = ['t', 'oT', 'msT', 'mozT', 'webkitT', 'khtmlT'],
i = 0,
p,
style = document.createElement('div').style;
while ((p = lst[i++]))
if (typeof style[p + 'ransform'] !== 'undefined') return p + 'ransform';
},
/**
* Concatenates transforms of this matrix onto the given child matrix and
* returns a new matrix. This instance is used on left side.
*
* @param {Matrix|SVGMatrix} cm - child matrix to apply concatenation to
* @returns {Matrix} - new Matrix instance
*/
concat: function (cm) {
return this.clone().multiply(cm);
},
/**
* Flips the horizontal values.
* @returns {Matrix}
*/
flipX: function () {
return this._t(-1, 0, 0, 1, 0, 0);
},
/**
* Flips the vertical values.
* @returns {Matrix}
*/
flipY: function () {
return this._t(1, 0, 0, -1, 0, 0);
},
/**
* Reflects incoming (velocity) vector on the normal which will be the
* current transformed x axis. Call when a trigger condition is met.
*
* @param {number} x - vector end point for x (start = 0)
* @param {number} y - vector end point for y (start = 0)
* @returns {{x: number, y: number}}
*/
reflectVector: function (x, y) {
var v = this.applyToPoint(0, 1),
d = (v.x * x + v.y * y) * 2;
x -= d * v.x;
y -= d * v.y;
return { x: x, y: y };
},
/**
* Short-hand to reset current matrix to an identity matrix.
* @returns {Matrix}
*/
reset: function () {
return this.setTransform(1, 0, 0, 1, 0, 0);
},
/**
* Rotates current matrix by angle (accumulative).
* @param {number} angle - angle in radians
* @returns {Matrix}
*/
rotate: function (angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle);
return this._t(cos, sin, -sin, cos, 0, 0);
},
/**
* Converts a vector given as `x` and `y` to angle, and
* rotates (accumulative). x can instead contain an object with
* properties x and y and if so, y parameter will be ignored.
* @param {number|*} x
* @param {number} [y]
* @returns {Matrix}
*/
rotateFromVector: function (x, y) {
return this.rotate(
typeof x === 'number' ? Math.atan2(y, x) : Math.atan2(x.y, x.x)
);
},
/**
* Helper method to make a rotation based on an angle in degrees.
* @param {number} angle - angle in degrees
* @returns {Matrix}
*/
rotateDeg: function (angle) {
return this.rotate((angle * Math.PI) / 180);
},
/**
* Scales current matrix uniformly and accumulative.
* @param {number} f - scale factor for both x and y (1 does nothing)
* @returns {Matrix}
*/
scaleU: function (f) {
return this._t(f, 0, 0, f, 0, 0);
},
/**
* Scales current matrix accumulative.
* @param {number} sx - scale factor x (1 does nothing)
* @param {number} sy - scale factor y (1 does nothing)
* @returns {Matrix}
*/
scale: function (sx, sy) {
return this._t(sx, 0, 0, sy, 0, 0);
},
/**
* Scales current matrix on x axis accumulative.
* @param {number} sx - scale factor x (1 does nothing)
* @returns {Matrix}
*/
scaleX: function (sx) {
return this._t(sx, 0, 0, 1, 0, 0);
},
/**
* Scales current matrix on y axis accumulative.
* @param {number} sy - scale factor y (1 does nothing)
* @returns {Matrix}
*/
scaleY: function (sy) {
return this._t(1, 0, 0, sy, 0, 0);
},
/**
* Converts a vector given as `x` and `y` to normalized scale.
* @param x
* @param y
* @returns {Matrix}
*/
scaleFromVector: function (x, y) {
return this.scaleU(Math.sqrt(x * x + y * y));
},
/**
* Apply shear to the current matrix accumulative.
* @param {number} sx - amount of shear for x
* @param {number} sy - amount of shear for y
* @returns {Matrix}
*/
shear: function (sx, sy) {
return this._t(1, sy, sx, 1, 0, 0);
},
/**
* Apply shear for x to the current matrix accumulative.
* @param {number} sx - amount of shear for x
* @returns {Matrix}
*/
shearX: function (sx) {
return this._t(1, 0, sx, 1, 0, 0);
},
/**
* Apply shear for y to the current matrix accumulative.
* @param {number} sy - amount of shear for y
* @returns {Matrix}
*/
shearY: function (sy) {
return this._t(1, sy, 0, 1, 0, 0);
},
/**
* Apply skew to the current matrix accumulative. Angles in radians.
* Also see [`skewDeg()`]{@link Matrix#skewDeg}.
* @param {number} ax - angle of skew for x
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skew: function (ax, ay) {
return this.shear(Math.tan(ax), Math.tan(ay));
},
/**
* Apply skew to the current matrix accumulative. Angles in degrees.
* Also see [`skew()`]{@link Matrix#skew}.
* @param {number} ax - angle of skew for x
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skewDeg: function (ax, ay) {
return this.shear(
Math.tan((ax / 180) * Math.PI),
Math.tan((ay / 180) * Math.PI)
);
},
/**
* Apply skew for x to the current matrix accumulative. Angles in radians.
* Also see [`skewDeg()`]{@link Matrix#skewDeg}.
* @param {number} ax - angle of skew for x
* @returns {Matrix}
*/
skewX: function (ax) {
return this.shearX(Math.tan(ax));
},
/**
* Apply skew for y to the current matrix accumulative. Angles in radians.
* Also see [`skewDeg()`]{@link Matrix#skewDeg}.
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skewY: function (ay) {
return this.shearY(Math.tan(ay));
},
/**
* Set current matrix to new absolute matrix.
* @param {number} a - scale x
* @param {number} b - shear y
* @param {number} c - shear x
* @param {number} d - scale y
* @param {number} e - translate x
* @param {number} f - translate y
* @returns {Matrix}
*/
setTransform: function (a, b, c, d, e, f) {
var me = this;
me.a = a;
me.b = b;
me.c = c;
me.d = d;
me.e = e;
me.f = f;
return me._x();
},
/**
* Translate current matrix accumulative.
* @param {number} tx - translation for x
* @param {number} ty - translation for y
* @returns {Matrix}
*/
translate: function (tx, ty) {
return this._t(1, 0, 0, 1, tx, ty);
},
/**
* Translate current matrix on x axis accumulative.
* @param {number} tx - translation for x
* @returns {Matrix}
*/
translateX: function (tx) {
return this._t(1, 0, 0, 1, tx, 0);
},
/**
* Translate current matrix on y axis accumulative.
* @param {number} ty - translation for y
* @returns {Matrix}
*/
translateY: function (ty) {
return this._t(1, 0, 0, 1, 0, ty);
},
/**
* Multiplies current matrix with new matrix values. Also see [`multiply()`]{@link Matrix#multiply}.
*
* @param {number} a2 - scale x
* @param {number} b2 - skew y
* @param {number} c2 - skew x
* @param {number} d2 - scale y
* @param {number} e2 - translate x
* @param {number} f2 - translate y
* @returns {Matrix}
*/
transform: function (a2, b2, c2, d2, e2, f2) {
var me = this,
a1 = me.a,
b1 = me.b,
c1 = me.c,
d1 = me.d,
e1 = me.e,
f1 = me.f;
/* matrix column order is:
* a c e
* b d f
* 0 0 1
*/
me.a = a1 * a2 + c1 * b2;
me.b = b1 * a2 + d1 * b2;
me.c = a1 * c2 + c1 * d2;
me.d = b1 * c2 + d1 * d2;
me.e = a1 * e2 + c1 * f2 + e1;
me.f = b1 * e2 + d1 * f2 + f1;
return me._x();
},
/**
* Multiplies current matrix with source matrix.
* @param {Matrix|DOMMatrix|SVGMatrix} m - source matrix to multiply with.
* @returns {Matrix}
*/
multiply: function (m) {
return this._t(m.a, m.b, m.c, m.d, m.e, m.f);
},
/**
* Divide this matrix on input matrix which must be invertible.
* @param {Matrix} m - matrix to divide on (divisor)
* @throws Exception if input matrix is not invertible
* @returns {Matrix}
*/
divide: function (m) {
return this.multiply(m.inverse());
},
/**
* Divide current matrix on scalar value != 0.
* @param {number} d - divisor
* @throws Exception if divisor is zero
* @returns {Matrix}
*/
divideScalar: function (d) {
var me = this;
if (!d) throw 'Division on zero';
me.a /= d;
me.b /= d;
me.c /= d;
me.d /= d;
me.e /= d;
me.f /= d;
return me._x();
},
/**
* Get an inverse matrix of current matrix. The method returns a new
* matrix with values you need to use to get to an identity matrix.
* Context from parent matrix is not applied to the returned matrix.
*
* @param {boolean} [cloneContext=false] - clone current context to resulting matrix
* @param {boolean} [cloneDOM=false] - clone current DOM element to resulting matrix
* @throws Exception is input matrix is not invertible
* @returns {Matrix} - new Matrix instance
*/
inverse: function (cloneContext, cloneDOM) {
var me = this,
m = new Matrix(
cloneContext ? me.context : null,
cloneDOM ? me.element : null
),
dt = me.determinant();
if (!dt) throw 'Matrix not invertible.';
m.a = me.d / dt;
m.b = -me.b / dt;
m.c = -me.c / dt;
m.d = me.a / dt;
m.e = (me.c * me.f - me.d * me.e) / dt;
m.f = -(me.a * me.f - me.b * me.e) / dt;
return m;
},
/**
* Interpolate this matrix with another and produce a new matrix.
* `t` is a value in the range [0.0, 1.0] where 0 is this instance and
* 1 is equal to the second matrix. The `t` value is not clamped.
*
* Context from parent matrix is not applied to the returned matrix.
*
* Note: this interpolation is naive. For animation containing rotation,
* shear or skew use the [`interpolateAnim()`]{@link Matrix#interpolateAnim} method instead
* to avoid unintended flipping.
*
* @param {Matrix|SVGMatrix} m2 - the matrix to interpolate with.
* @param {number} t - interpolation [0.0, 1.0]
* @param {CanvasRenderingContext2D} [context] - optional context to affect
* @param {HTMLElement} [dom] - optional DOM element to use for the matrix
* @returns {Matrix} - new Matrix instance with the interpolated result
*/
interpolate: function (m2, t, context, dom) {
var me = this,
m = new Matrix(context, dom);
m.a = me.a + (m2.a - me.a) * t;
m.b = me.b + (m2.b - me.b) * t;
m.c = me.c + (m2.c - me.c) * t;
m.d = me.d + (m2.d - me.d) * t;
m.e = me.e + (m2.e - me.e) * t;
m.f = me.f + (m2.f - me.f) * t;
return m._x();
},
/**
* Interpolate this matrix with another and produce a new matrix.
* `t` is a value in the range [0.0, 1.0] where 0 is this instance and
* 1 is equal to the second matrix. The `t` value is not constrained.
*
* Context from parent matrix is not applied to the returned matrix.
*
* To obtain easing `t` can be preprocessed using easing-functions
* before being passed to this method.
*
* Note: this interpolation method uses decomposition which makes
* it suitable for animations (in particular where rotation takes
* places).
*
* @param {Matrix} m2 - the matrix to interpolate with.
* @param {number} t - interpolation [0.0, 1.0]
* @param {CanvasRenderingContext2D} [context] - optional context to affect
* @param {HTMLElement} [dom] - optional DOM element to use for the matrix
* @returns {Matrix} - new Matrix instance with the interpolated result
*/
interpolateAnim: function (m2, t, context, dom) {
var m = new Matrix(context, dom),
d1 = this.decompose(),
d2 = m2.decompose(),
t1 = d1.translate,
t2 = d2.translate,
s1 = d1.scale;
// QR order (t-r-s-sk)
m.translate(t1.x + (t2.x - t1.x) * t, t1.y + (t2.y - t1.y) * t);
m.rotate(d1.rotation + (d2.rotation - d1.rotation) * t);
m.scale(s1.x + (d2.scale.x - s1.x) * t, s1.y + (d2.scale.y - s1.y) * t);
//todo test skew scenarios
return m._x();
},
/**
* Decompose the current matrix into simple transforms using either
* QR (default) or LU decomposition.
*
* @param {boolean} [useLU=false] - set to true to use LU rather than QR decomposition
* @returns {*} - an object containing current decomposed values (translate, rotation, scale, skew)
* @see {@link https://en.wikipedia.org/wiki/QR_decomposition|More on QR decomposition}
* @see {@link https://en.wikipedia.org/wiki/LU_decomposition|More on LU decomposition}
*/
decompose: function (useLU) {
var me = this,
a = me.a,
b = me.b,
c = me.c,
d = me.d,
acos = Math.acos,
atan = Math.atan,
sqrt = Math.sqrt,
pi = Math.PI,
translate = { x: me.e, y: me.f },
rotation = 0,
scale = { x: 1, y: 1 },
skew = { x: 0, y: 0 },
determ = a * d - b * c, // determinant(), skip DRY here...
r,
s;
if (useLU) {
if (a) {
skew = { x: atan(c / a), y: atan(b / a) };
scale = { x: a, y: determ / a };
} else if (b) {
rotation = pi * 0.5;
scale = { x: b, y: determ / b };
skew.x = atan(d / b);
} else {
// a = b = 0
scale = { x: c, y: d };
skew.x = pi * 0.25;
}
} else {
// Apply the QR-like decomposition.
if (a || b) {
r = sqrt(a * a + b * b);
rotation = b > 0 ? acos(a / r) : -acos(a / r);
scale = { x: r, y: determ / r };
skew.x = atan((a * c + b * d) / (r * r));
} else if (c || d) {
s = sqrt(c * c + d * d);
rotation = pi * 0.5 - (d > 0 ? acos(-c / s) : -acos(c / s));
scale = { x: determ / s, y: s };
skew.y = atan((a * c + b * d) / (s * s));
} else {
// a = b = c = d = 0
scale = { x: 0, y: 0 };
}
}
return {
translate: translate,
rotation: rotation,
scale: scale,
skew: skew,
};
},
/**
* Returns the determinant of the current matrix.
* @returns {number}
*/
determinant: function () {
return this.a * this.d - this.b * this.c;
},
/**
* Apply current matrix to `x` and `y` of a point.
* Returns a point object.
*
* @param {number} x - value for x
* @param {number} y - value for y
* @returns {{x: number, y: number}} A new transformed point object
*/
applyToPoint: function (x, y) {
var me = this;
return {
x: x * me.a + y * me.c + me.e,
y: x * me.b + y * me.d + me.f,
};
},
/**
* Apply current matrix to array with point objects or point pairs.
* Returns a new array with points in the same format as the input array.
*
* A point object is an object literal:
*
* {x: x, y: y}
*
* so an array would contain either:
*
* [{x: x1, y: y1}, {x: x2, y: y2}, ... {x: xn, y: yn}]
*
* or
*
* [x1, y1, x2, y2, ... xn, yn]
*
* @param {Array} points - array with point objects or pairs
* @returns {Array} A new array with transformed points
*/
applyToArray: function (points) {
var i = 0,
p,
l,
mxPoints = [];
if (typeof points[0] === 'number') {
l = points.length;
while (i < l) {
p = this.applyToPoint(points[i++], points[i++]);
mxPoints.push(p.x, p.y);
}
} else {
while ((p = points[i++])) {
mxPoints.push(this.applyToPoint(p.x, p.y));
}
}
return mxPoints;
},
/**
* Apply current matrix to a typed array with point pairs. Although
* the input array may be an ordinary array, this method is intended
* for more performant use where typed arrays are used. The returned
* array is regardless always returned as a `Float32Array`.
*
* @param {*} points - (typed) array with point pairs [x1, y1, ..., xn, yn]
* @param {boolean} [use64=false] - use Float64Array instead of Float32Array
* @returns {*} A new typed array with transformed points
*/
applyToTypedArray: function (points, use64) {
var i = 0,
p,
l = points.length,
mxPoints = use64 ? new Float64Array(l) : new Float32Array(l);
while (i < l) {
p = this.applyToPoint(points[i], points[i + 1]);
mxPoints[i++] = p.x;
mxPoints[i++] = p.y;
}
return mxPoints;
},
/**
* Apply to any canvas 2D context object. This does not affect the
* context that optionally was referenced in constructor unless it is
* the same context.
*
* @param {CanvasRenderingContext2D} context - target context
* @returns {Matrix}
*/
applyToContext: function (context) {
var me = this;
context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
return me;
},
/**
* Apply to any DOM element. This does not affect the DOM element
* that optionally was referenced in constructor unless it is
* the same element.
*
* The method will auto-detect the correct browser prefix if any.
*
* @param {HTMLElement} element - target DOM element
* @param {boolean} [use3D=false] - use 3D transformation matrix instead of 2D
* @returns {Matrix}
*/
applyToElement: function (element, use3D) {
var me = this;
if (!me._px) me._px = me._getPX();
element.style[me._px] = use3D ? me.toCSS3D() : me.toCSS();
return me;
},
/**
* Instead of creating a new instance of a Matrix, DOMMatrix or SVGMatrix
* the current settings of this instance can be applied to an external
* object of a different (or same) type. You can also pass in an
* empty literal object.
*
* Note that the properties a-f will be set regardless of if they
* already exist or not.
*
* @param {*} obj - target object.
* @returns {Matrix}
*/
applyToObject: function (obj) {
var me = this;
obj.a = me.a;
obj.b = me.b;
obj.c = me.c;
obj.d = me.d;
obj.e = me.e;
obj.f = me.f;
return me;
},
/**
* Returns true if matrix is an identity matrix (no transforms applied).
* @returns {boolean}
*/
isIdentity: function () {
var me = this;
return me.a === 1 && !me.b && !me.c && me.d === 1 && !me.e && !me.f;
},
/**
* Returns true if matrix is invertible
* @returns {boolean}
*/
isInvertible: function () {
return !this._q(this.determinant(), 0);
},
/**
* The method is intended for situations where scale is accumulated
* via multiplications, to detect situations where scale becomes
* "trapped" with a value of zero. And in which case scale must be
* set explicitly to a non-zero value.
*
* @returns {boolean}
*/
isValid: function () {
return !(this.a * this.d);
},
/**
* Compares current matrix with another matrix. Returns true if equal
* (within epsilon tolerance).
* @param {Matrix|SVGMatrix} m - matrix to compare this matrix with
* @returns {boolean}
*/
isEqual: function (m) {
var me = this,
q = me._q;
return (
q(me.a, m.a) &&
q(me.b, m.b) &&
q(me.c, m.c) &&
q(me.d, m.d) &&
q(me.e, m.e) &&
q(me.f, m.f)
);
},
/**
* Clones current instance and returning a new matrix.
* @param {boolean} [noContext=false] don't clone context reference if true
* @returns {Matrix} - a new Matrix instance with identical transformations as this instance
*/
clone: function (noContext) {
return new Matrix(noContext ? null : this.context).multiply(this);
},
/**
* Returns an array with current matrix values.
* @returns {Array}
*/
toArray: function () {
var me = this;
return [me.a, me.b, me.c, me.d, me.e, me.f];
},
/**
* Returns a binary 32-bit floating point typed array.
* @returns {*}
*/
toTypedArray: function () {
var me = this;
return new Float32Array([me.a, me.b, me.c, me.d, me.e, me.f]);
},
/**
* Generates a string that can be used with CSS `transform`.
* @example
* element.style.transform = m.toCSS();
* @returns {string}
*/
toCSS: function () {
return 'matrix(' + this.toArray() + ')';
},
/**
* Generates a `matrix3d()` string that can be used with CSS `transform`.
* Although the matrix is for 2D use you may see performance benefits
* on some devices using a 3D CSS transform instead of a 2D.
* @example
* element.style.transform = m.toCSS3D();
* @returns {string}
*/
toCSS3D: function () {
var me = this,
n2 = ',0,0,';
return (
'matrix3d(' +
me.a +
',' +
me.b +
n2 +
me.c +
',' +
me.d +
n2 +
n2 +
',1,0,' +
me.e +
',' +
me.f +
',0,1)'
);
},
/**
* Returns a JSON compatible string of current matrix.
* @returns {string}
*/
toJSON: function () {
var me = this;
return (
'{"a":' +
me.a +
',"b":' +
me.b +
',"c":' +
me.c +
',"d":' +
me.d +
',"e":' +
me.e +
',"f":' +
me.f +
'}'
);
},
/**
* Returns a string with current matrix as comma-separated list.
* @param {number} [fixLen=4] - truncate decimal values to number of digits
* @returns {string}
*/
toString: function (fixLen) {
var me = this;
fixLen = fixLen || 4;
return (
'a=' +
me.a.toFixed(fixLen) +
' b=' +
me.b.toFixed(fixLen) +
' c=' +
me.c.toFixed(fixLen) +
' d=' +
me.d.toFixed(fixLen) +
' e=' +
me.e.toFixed(fixLen) +
' f=' +
me.f.toFixed(fixLen)
);
},
/**
* Returns a string with current matrix as comma-separated values
* string with line-end (CR+LF).
* @returns {string}
*/
toCSV: function () {
return this.toArray().join() + '\r\n';
},
/**
* Convert current matrix into a `DOMMatrix`. If `DOMMatrix` is not
* supported, a `null` is returned.
*
* @returns {DOMMatrix}
* @see {@link https://drafts.fxtf.org/geometry/#dommatrix|MDN / SVGMatrix}
*/
toDOMMatrix: function () {
var m = null;
if ('DOMMatrix' in window) {
m = new DOMMatrix();
m.a = this.a;
m.b = this.b;
m.c = this.c;
m.d = this.d;
m.e = this.e;
m.f = this.f;
}
return m;
},
/**
* Convert current matrix into a `SVGMatrix`. If `SVGMatrix` is not
* supported, a `null` is returned.
*
* @returns {SVGMatrix}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix|MDN / SVGMatrix}
*/
toSVGMatrix: function () {
var me = this,
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
svgMatrix = null;
if (svg) {
svgMatrix = svg.createSVGMatrix();
svgMatrix.a = me.a;
svgMatrix.b = me.b;
svgMatrix.c = me.c;
svgMatrix.d = me.d;
svgMatrix.e = me.e;
svgMatrix.f = me.f;
}
return svgMatrix;
},
/**
* Compares floating point values with some tolerance (epsilon)
* @param {number} f1 - float 1
* @param {number} f2 - float 2
* @returns {boolean}
* @private
*/
_q: function (f1, f2) {
return Math.abs(f1 - f2) < 1e-14;
},
/**
* Apply current absolute matrix to context if defined, to sync it.
* Apply current absolute matrix to element if defined, to sync it.
* @returns {Matrix}
* @private
*/
_x: function () {
var me = this;
if (me.context) me.context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
if (me._st) me._st[me._px] = me.useCSS3D ? me.toCSS3D() : me.toCSS(); // can be optimized pre-storing func ref.
return me;
},
};
// Node support
if (typeof exports !== 'undefined') exports.Matrix = Matrix;