'use strict' const util = require('util') // DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix function DOMPoint(x, y, z, w) { if (!(this instanceof DOMPoint)) { throw new TypeError("Class constructors cannot be invoked without 'new'") } if (typeof x === 'object') { w = x.w z = x.z y = x.y x = x.x } this.x = typeof x === 'number' ? x : 0 this.y = typeof y === 'number' ? y : 0 this.z = typeof z === 'number' ? z : 0 this.w = typeof w === 'number' ? w : 1 } // Constants to index into _values (col-major) const M11 = 0, M12 = 1, M13 = 2, M14 = 3 const M21 = 4, M22 = 5, M23 = 6, M24 = 7 const M31 = 8, M32 = 9, M33 = 10, M34 = 11 const M41 = 12, M42 = 13, M43 = 14, M44 = 15 const DEGREE_PER_RAD = 180 / Math.PI const RAD_PER_DEGREE = Math.PI / 180 function parseMatrix(init) { var parsed = init.replace(/matrix\(/, '') parsed = parsed.split(/,/, 7) // 6 + 1 to handle too many params if (parsed.length !== 6) throw new Error(`Failed to parse ${init}`) parsed = parsed.map(parseFloat) return [ parsed[0], parsed[1], 0, 0, parsed[2], parsed[3], 0, 0, 0, 0, 1, 0, parsed[4], parsed[5], 0, 1 ] } function parseMatrix3d(init) { var parsed = init.replace(/matrix3d\(/, '') parsed = parsed.split(/,/, 17) // 16 + 1 to handle too many params if (parsed.length !== 16) throw new Error(`Failed to parse ${init}`) return parsed.map(parseFloat) } function parseTransform(tform) { var type = tform.split(/\(/, 1)[0] switch (type) { case 'matrix': return parseMatrix(tform) case 'matrix3d': return parseMatrix3d(tform) // TODO This is supposed to support any CSS transform value. default: throw new Error(`${type} parsing not implemented`) } } function DOMMatrix (init) { if (!(this instanceof DOMMatrix)) { throw new TypeError("Class constructors cannot be invoked without 'new'") } this._is2D = true this._values = new Float64Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]) var i if (typeof init === 'string') { // parse CSS transformList if (init === '') return // default identity matrix var tforms = init.split(/\)\s+/, 20).map(parseTransform) if (tforms.length === 0) return init = tforms[0] for (i = 1; i < tforms.length; i++) init = multiply(tforms[i], init) } i = 0 if (init && init.length === 6) { setNumber2D(this, M11, init[i++]) setNumber2D(this, M12, init[i++]) setNumber2D(this, M21, init[i++]) setNumber2D(this, M22, init[i++]) setNumber2D(this, M41, init[i++]) setNumber2D(this, M42, init[i++]) } else if (init && init.length === 16) { setNumber2D(this, M11, init[i++]) setNumber2D(this, M12, init[i++]) setNumber3D(this, M13, init[i++]) setNumber3D(this, M14, init[i++]) setNumber2D(this, M21, init[i++]) setNumber2D(this, M22, init[i++]) setNumber3D(this, M23, init[i++]) setNumber3D(this, M24, init[i++]) setNumber3D(this, M31, init[i++]) setNumber3D(this, M32, init[i++]) setNumber3D(this, M33, init[i++]) setNumber3D(this, M34, init[i++]) setNumber2D(this, M41, init[i++]) setNumber2D(this, M42, init[i++]) setNumber3D(this, M43, init[i++]) setNumber3D(this, M44, init[i]) } else if (init !== undefined) { throw new TypeError('Expected string or array.') } } DOMMatrix.fromMatrix = function (init) { if (!(init instanceof DOMMatrix)) throw new TypeError('Expected DOMMatrix') return new DOMMatrix(init._values) } DOMMatrix.fromFloat32Array = function (init) { if (!(init instanceof Float32Array)) throw new TypeError('Expected Float32Array') return new DOMMatrix(init) } DOMMatrix.fromFloat64Array = function (init) { if (!(init instanceof Float64Array)) throw new TypeError('Expected Float64Array') return new DOMMatrix(init) } // TODO || is for Node.js pre-v6.6.0 DOMMatrix.prototype[util.inspect.custom || 'inspect'] = function (depth, options) { if (depth < 0) return '[DOMMatrix]' return `DOMMatrix [ a: ${this.a} b: ${this.b} c: ${this.c} d: ${this.d} e: ${this.e} f: ${this.f} m11: ${this.m11} m12: ${this.m12} m13: ${this.m13} m14: ${this.m14} m21: ${this.m21} m22: ${this.m22} m23: ${this.m23} m23: ${this.m23} m31: ${this.m31} m32: ${this.m32} m33: ${this.m33} m34: ${this.m34} m41: ${this.m41} m42: ${this.m42} m43: ${this.m43} m44: ${this.m44} is2D: ${this.is2D} isIdentity: ${this.isIdentity} ]` } DOMMatrix.prototype.toString = function () { return this.is2D ? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})` : `matrix3d(${this._values.join(', ')})` } /** * Checks that `value` is a number and sets the value. */ function setNumber2D(receiver, index, value) { if (typeof value !== 'number') throw new TypeError('Expected number') return receiver._values[index] = value } /** * Checks that `value` is a number, sets `_is2D = false` if necessary and sets * the value. */ function setNumber3D(receiver, index, value) { if (typeof value !== 'number') throw new TypeError('Expected number') if (index === M33 || index === M44) { if (value !== 1) receiver._is2D = false } else if (value !== 0) receiver._is2D = false return receiver._values[index] = value } Object.defineProperties(DOMMatrix.prototype, { m11: {get: function () { return this._values[M11] }, set: function (v) { return setNumber2D(this, M11, v) }}, m12: {get: function () { return this._values[M12] }, set: function (v) { return setNumber2D(this, M12, v) }}, m13: {get: function () { return this._values[M13] }, set: function (v) { return setNumber3D(this, M13, v) }}, m14: {get: function () { return this._values[M14] }, set: function (v) { return setNumber3D(this, M14, v) }}, m21: {get: function () { return this._values[M21] }, set: function (v) { return setNumber2D(this, M21, v) }}, m22: {get: function () { return this._values[M22] }, set: function (v) { return setNumber2D(this, M22, v) }}, m23: {get: function () { return this._values[M23] }, set: function (v) { return setNumber3D(this, M23, v) }}, m24: {get: function () { return this._values[M24] }, set: function (v) { return setNumber3D(this, M24, v) }}, m31: {get: function () { return this._values[M31] }, set: function (v) { return setNumber3D(this, M31, v) }}, m32: {get: function () { return this._values[M32] }, set: function (v) { return setNumber3D(this, M32, v) }}, m33: {get: function () { return this._values[M33] }, set: function (v) { return setNumber3D(this, M33, v) }}, m34: {get: function () { return this._values[M34] }, set: function (v) { return setNumber3D(this, M34, v) }}, m41: {get: function () { return this._values[M41] }, set: function (v) { return setNumber2D(this, M41, v) }}, m42: {get: function () { return this._values[M42] }, set: function (v) { return setNumber2D(this, M42, v) }}, m43: {get: function () { return this._values[M43] }, set: function (v) { return setNumber3D(this, M43, v) }}, m44: {get: function () { return this._values[M44] }, set: function (v) { return setNumber3D(this, M44, v) }}, a: {get: function () { return this.m11 }, set: function (v) { return this.m11 = v }}, b: {get: function () { return this.m12 }, set: function (v) { return this.m12 = v }}, c: {get: function () { return this.m21 }, set: function (v) { return this.m21 = v }}, d: {get: function () { return this.m22 }, set: function (v) { return this.m22 = v }}, e: {get: function () { return this.m41 }, set: function (v) { return this.m41 = v }}, f: {get: function () { return this.m42 }, set: function (v) { return this.m42 = v }}, is2D: {get: function () { return this._is2D }}, // read-only isIdentity: { get: function () { var values = this._values return values[M11] === 1 && values[M12] === 0 && values[M13] === 0 && values[M14] === 0 && values[M21] === 0 && values[M22] === 1 && values[M23] === 0 && values[M24] === 0 && values[M31] === 0 && values[M32] === 0 && values[M33] === 1 && values[M34] === 0 && values[M41] === 0 && values[M42] === 0 && values[M43] === 0 && values[M44] === 1 } } }) /** * Instantiates a DOMMatrix, bypassing the constructor. * @param {Float64Array} values Value to assign to `_values`. This is assigned * without copying (okay because all usages are followed by a multiply). */ function newInstance(values) { var instance = Object.create(DOMMatrix.prototype) instance.constructor = DOMMatrix instance._is2D = true instance._values = values return instance } function multiply(A, B) { var dest = new Float64Array(16) for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var sum = 0 for (var k = 0; k < 4; k++) { sum += A[i * 4 + k] * B[k * 4 + j] } dest[i * 4 + j] = sum } } return dest } DOMMatrix.prototype.multiply = function (other) { return newInstance(this._values).multiplySelf(other) } DOMMatrix.prototype.multiplySelf = function (other) { this._values = multiply(other._values, this._values) if (!other.is2D) this._is2D = false return this } DOMMatrix.prototype.preMultiplySelf = function (other) { this._values = multiply(this._values, other._values) if (!other.is2D) this._is2D = false return this } DOMMatrix.prototype.translate = function (tx, ty, tz) { return newInstance(this._values).translateSelf(tx, ty, tz) } DOMMatrix.prototype.translateSelf = function (tx, ty, tz) { if (typeof tx !== 'number') tx = 0 if (typeof ty !== 'number') ty = 0 if (typeof tz !== 'number') tz = 0 this._values = multiply([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1 ], this._values) if (tz !== 0) this._is2D = false return this } DOMMatrix.prototype.scale = function (scaleX, scaleY, scaleZ, originX, originY, originZ) { return newInstance(this._values).scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ) } DOMMatrix.prototype.scale3d = function (scale, originX, originY, originZ) { return newInstance(this._values).scale3dSelf(scale, originX, originY, originZ) } DOMMatrix.prototype.scale3dSelf = function (scale, originX, originY, originZ) { return this.scaleSelf(scale, scale, scale, originX, originY, originZ) } DOMMatrix.prototype.scaleSelf = function (scaleX, scaleY, scaleZ, originX, originY, originZ) { // Not redundant with translate's checks because we need to negate the values later. if (typeof originX !== 'number') originX = 0 if (typeof originY !== 'number') originY = 0 if (typeof originZ !== 'number') originZ = 0 this.translateSelf(originX, originY, originZ) if (typeof scaleX !== 'number') scaleX = 1 if (typeof scaleY !== 'number') scaleY = scaleX if (typeof scaleZ !== 'number') scaleZ = 1 this._values = multiply([ scaleX, 0, 0, 0, 0, scaleY, 0, 0, 0, 0, scaleZ, 0, 0, 0, 0, 1 ], this._values) this.translateSelf(-originX, -originY, -originZ) if (scaleZ !== 1 || originZ !== 0) this._is2D = false return this } DOMMatrix.prototype.rotateFromVector = function (x, y) { return newInstance(this._values).rotateFromVectorSelf(x, y) } DOMMatrix.prototype.rotateFromVectorSelf = function (x, y) { if (typeof x !== 'number') x = 0 if (typeof y !== 'number') y = 0 var theta = (x === 0 && y === 0) ? 0 : Math.atan2(y, x) * DEGREE_PER_RAD return this.rotateSelf(theta) } DOMMatrix.prototype.rotate = function (rotX, rotY, rotZ) { return newInstance(this._values).rotateSelf(rotX, rotY, rotZ) } DOMMatrix.prototype.rotateSelf = function (rotX, rotY, rotZ) { if (rotY === undefined && rotZ === undefined) { rotZ = rotX rotX = rotY = 0 } if (typeof rotY !== 'number') rotY = 0 if (typeof rotZ !== 'number') rotZ = 0 if (rotX !== 0 || rotY !== 0) this._is2D = false rotX *= RAD_PER_DEGREE rotY *= RAD_PER_DEGREE rotZ *= RAD_PER_DEGREE var c, s c = Math.cos(rotZ) s = Math.sin(rotZ) this._values = multiply([ c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this._values) c = Math.cos(rotY) s = Math.sin(rotY) this._values = multiply([ c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1 ], this._values) c = Math.cos(rotX) s = Math.sin(rotX) this._values = multiply([ 1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1 ], this._values) return this } DOMMatrix.prototype.rotateAxisAngle = function (x, y, z, angle) { return newInstance(this._values).rotateAxisAngleSelf(x, y, z, angle) } DOMMatrix.prototype.rotateAxisAngleSelf = function (x, y, z, angle) { if (typeof x !== 'number') x = 0 if (typeof y !== 'number') y = 0 if (typeof z !== 'number') z = 0 // Normalize axis var length = Math.sqrt(x * x + y * y + z * z) if (length === 0) return this if (length !== 1) { x /= length y /= length z /= length } angle *= RAD_PER_DEGREE var c = Math.cos(angle) var s = Math.sin(angle) var t = 1 - c var tx = t * x var ty = t * y // NB: This is the generic transform. If the axis is a major axis, there are // faster transforms. this._values = multiply([ tx * x + c, tx * y + s * z, tx * z - s * y, 0, tx * y - s * z, ty * y + c, ty * z + s * x, 0, tx * z + s * y, ty * z - s * x, t * z * z + c, 0, 0, 0, 0, 1 ], this._values) if (x !== 0 || y !== 0) this._is2D = false return this } DOMMatrix.prototype.skewX = function (sx) { return newInstance(this._values).skewXSelf(sx) } DOMMatrix.prototype.skewXSelf = function (sx) { if (typeof sx !== 'number') return this var t = Math.tan(sx * RAD_PER_DEGREE) this._values = multiply([ 1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this._values) return this } DOMMatrix.prototype.skewY = function (sy) { return newInstance(this._values).skewYSelf(sy) } DOMMatrix.prototype.skewYSelf = function (sy) { if (typeof sy !== 'number') return this var t = Math.tan(sy * RAD_PER_DEGREE) this._values = multiply([ 1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this._values) return this } DOMMatrix.prototype.flipX = function () { return newInstance(multiply([ -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this._values)) } DOMMatrix.prototype.flipY = function () { return newInstance(multiply([ 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this._values)) } DOMMatrix.prototype.inverse = function () { return newInstance(this._values).invertSelf() } DOMMatrix.prototype.invertSelf = function () { // If not invertible, set all attributes to NaN and is2D to false throw new Error('Not implemented') } DOMMatrix.prototype.setMatrixValue = function (transformList) { var temp = new DOMMatrix(transformList) this._values = temp._values this._is2D = temp._is2D return this } DOMMatrix.prototype.transformPoint = function (point) { point = new DOMPoint(point) var x = point.x var y = point.y var z = point.z var w = point.w var values = this._values var nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w var ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w var nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w var nw = values[M14] * x + values[M24] * y + values[M34] * z + values[M44] * w return new DOMPoint(nx, ny, nz, nw) } DOMMatrix.prototype.toFloat32Array = function () { return Float32Array.from(this._values) } DOMMatrix.prototype.toFloat64Array = function () { return this._values.slice(0) } module.exports = {DOMMatrix, DOMPoint}