123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- 'use strict'
- const util = require('util')
- // DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix
- class DOMPoint {
- constructor (x, y, z, w) {
- if (typeof x === 'object' && x !== null) {
- 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; const M12 = 1; const M13 = 2; const M14 = 3
- const M21 = 4; const M22 = 5; const M23 = 6; const M24 = 7
- const M31 = 8; const M32 = 9; const M33 = 10; const M34 = 11
- const M41 = 12; const M42 = 13; const M43 = 14; const M44 = 15
- const DEGREE_PER_RAD = 180 / Math.PI
- const RAD_PER_DEGREE = Math.PI / 180
- function parseMatrix (init) {
- let 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) {
- let 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) {
- const 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`)
- }
- }
- class DOMMatrix {
- constructor (init) {
- this._is2D = true
- this._values = new Float64Array([
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
- ])
- let i
- if (typeof init === 'string') { // parse CSS transformList
- if (init === '') return // default identity matrix
- const 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.')
- }
- }
- toString () {
- return this.is2D
- ? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`
- : `matrix3d(${this._values.join(', ')})`
- }
- multiply (other) {
- return newInstance(this._values).multiplySelf(other)
- }
- multiplySelf (other) {
- this._values = multiply(other._values, this._values)
- if (!other.is2D) this._is2D = false
- return this
- }
- preMultiplySelf (other) {
- this._values = multiply(this._values, other._values)
- if (!other.is2D) this._is2D = false
- return this
- }
- translate (tx, ty, tz) {
- return newInstance(this._values).translateSelf(tx, ty, tz)
- }
- translateSelf (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
- }
- scale (scaleX, scaleY, scaleZ, originX, originY, originZ) {
- return newInstance(this._values).scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
- }
- scale3d (scale, originX, originY, originZ) {
- return newInstance(this._values).scale3dSelf(scale, originX, originY, originZ)
- }
- scale3dSelf (scale, originX, originY, originZ) {
- return this.scaleSelf(scale, scale, scale, originX, originY, originZ)
- }
- scaleSelf (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
- }
- rotateFromVector (x, y) {
- return newInstance(this._values).rotateFromVectorSelf(x, y)
- }
- rotateFromVectorSelf (x, y) {
- if (typeof x !== 'number') x = 0
- if (typeof y !== 'number') y = 0
- const theta = (x === 0 && y === 0) ? 0 : Math.atan2(y, x) * DEGREE_PER_RAD
- return this.rotateSelf(theta)
- }
- rotate (rotX, rotY, rotZ) {
- return newInstance(this._values).rotateSelf(rotX, rotY, rotZ)
- }
- rotateSelf (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
- let 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
- }
- rotateAxisAngle (x, y, z, angle) {
- return newInstance(this._values).rotateAxisAngleSelf(x, y, z, angle)
- }
- rotateAxisAngleSelf (x, y, z, angle) {
- if (typeof x !== 'number') x = 0
- if (typeof y !== 'number') y = 0
- if (typeof z !== 'number') z = 0
- // Normalize axis
- const 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
- const c = Math.cos(angle)
- const s = Math.sin(angle)
- const t = 1 - c
- const tx = t * x
- const 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
- }
- skewX (sx) {
- return newInstance(this._values).skewXSelf(sx)
- }
- skewXSelf (sx) {
- if (typeof sx !== 'number') return this
- const 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
- }
- skewY (sy) {
- return newInstance(this._values).skewYSelf(sy)
- }
- skewYSelf (sy) {
- if (typeof sy !== 'number') return this
- const 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
- }
- flipX () {
- return newInstance(multiply([
- -1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
- ], this._values))
- }
- flipY () {
- return newInstance(multiply([
- 1, 0, 0, 0,
- 0, -1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
- ], this._values))
- }
- inverse () {
- return newInstance(this._values).invertSelf()
- }
- invertSelf () {
- const m = this._values
- const inv = m.map(v => 0)
- inv[0] = m[5] * m[10] * m[15] -
- m[5] * m[11] * m[14] -
- m[9] * m[6] * m[15] +
- m[9] * m[7] * m[14] +
- m[13] * m[6] * m[11] -
- m[13] * m[7] * m[10]
- inv[4] = -m[4] * m[10] * m[15] +
- m[4] * m[11] * m[14] +
- m[8] * m[6] * m[15] -
- m[8] * m[7] * m[14] -
- m[12] * m[6] * m[11] +
- m[12] * m[7] * m[10]
- inv[8] = m[4] * m[9] * m[15] -
- m[4] * m[11] * m[13] -
- m[8] * m[5] * m[15] +
- m[8] * m[7] * m[13] +
- m[12] * m[5] * m[11] -
- m[12] * m[7] * m[9]
- inv[12] = -m[4] * m[9] * m[14] +
- m[4] * m[10] * m[13] +
- m[8] * m[5] * m[14] -
- m[8] * m[6] * m[13] -
- m[12] * m[5] * m[10] +
- m[12] * m[6] * m[9]
- // If the determinant is zero, this matrix cannot be inverted, and all
- // values should be set to NaN, with the is2D flag set to false.
- const det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
- if (det === 0) {
- this._values = m.map(v => NaN)
- this._is2D = false
- return this
- }
- inv[1] = -m[1] * m[10] * m[15] +
- m[1] * m[11] * m[14] +
- m[9] * m[2] * m[15] -
- m[9] * m[3] * m[14] -
- m[13] * m[2] * m[11] +
- m[13] * m[3] * m[10]
- inv[5] = m[0] * m[10] * m[15] -
- m[0] * m[11] * m[14] -
- m[8] * m[2] * m[15] +
- m[8] * m[3] * m[14] +
- m[12] * m[2] * m[11] -
- m[12] * m[3] * m[10]
- inv[9] = -m[0] * m[9] * m[15] +
- m[0] * m[11] * m[13] +
- m[8] * m[1] * m[15] -
- m[8] * m[3] * m[13] -
- m[12] * m[1] * m[11] +
- m[12] * m[3] * m[9]
- inv[13] = m[0] * m[9] * m[14] -
- m[0] * m[10] * m[13] -
- m[8] * m[1] * m[14] +
- m[8] * m[2] * m[13] +
- m[12] * m[1] * m[10] -
- m[12] * m[2] * m[9]
- inv[2] = m[1] * m[6] * m[15] -
- m[1] * m[7] * m[14] -
- m[5] * m[2] * m[15] +
- m[5] * m[3] * m[14] +
- m[13] * m[2] * m[7] -
- m[13] * m[3] * m[6]
- inv[6] = -m[0] * m[6] * m[15] +
- m[0] * m[7] * m[14] +
- m[4] * m[2] * m[15] -
- m[4] * m[3] * m[14] -
- m[12] * m[2] * m[7] +
- m[12] * m[3] * m[6]
- inv[10] = m[0] * m[5] * m[15] -
- m[0] * m[7] * m[13] -
- m[4] * m[1] * m[15] +
- m[4] * m[3] * m[13] +
- m[12] * m[1] * m[7] -
- m[12] * m[3] * m[5]
- inv[14] = -m[0] * m[5] * m[14] +
- m[0] * m[6] * m[13] +
- m[4] * m[1] * m[14] -
- m[4] * m[2] * m[13] -
- m[12] * m[1] * m[6] +
- m[12] * m[2] * m[5]
- inv[3] = -m[1] * m[6] * m[11] +
- m[1] * m[7] * m[10] +
- m[5] * m[2] * m[11] -
- m[5] * m[3] * m[10] -
- m[9] * m[2] * m[7] +
- m[9] * m[3] * m[6]
- inv[7] = m[0] * m[6] * m[11] -
- m[0] * m[7] * m[10] -
- m[4] * m[2] * m[11] +
- m[4] * m[3] * m[10] +
- m[8] * m[2] * m[7] -
- m[8] * m[3] * m[6]
- inv[11] = -m[0] * m[5] * m[11] +
- m[0] * m[7] * m[9] +
- m[4] * m[1] * m[11] -
- m[4] * m[3] * m[9] -
- m[8] * m[1] * m[7] +
- m[8] * m[3] * m[5]
- inv[15] = m[0] * m[5] * m[10] -
- m[0] * m[6] * m[9] -
- m[4] * m[1] * m[10] +
- m[4] * m[2] * m[9] +
- m[8] * m[1] * m[6] -
- m[8] * m[2] * m[5]
- inv.forEach((v, i) => { inv[i] = v / det })
- this._values = inv
- return this
- }
- setMatrixValue (transformList) {
- const temp = new DOMMatrix(transformList)
- this._values = temp._values
- this._is2D = temp._is2D
- return this
- }
- transformPoint (point) {
- point = new DOMPoint(point)
- const x = point.x
- const y = point.y
- const z = point.z
- const w = point.w
- const values = this._values
- const nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w
- const ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w
- const nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w
- const nw = values[M14] * x + values[M24] * y + values[M34] * z + values[M44] * w
- return new DOMPoint(nx, ny, nz, nw)
- }
- toFloat32Array () {
- return Float32Array.from(this._values)
- }
- toFloat64Array () {
- return this._values.slice(0)
- }
- static fromMatrix (init) {
- if (!(init instanceof DOMMatrix)) throw new TypeError('Expected DOMMatrix')
- return new DOMMatrix(init._values)
- }
- static fromFloat32Array (init) {
- if (!(init instanceof Float32Array)) throw new TypeError('Expected Float32Array')
- return new DOMMatrix(init)
- }
- static fromFloat64Array (init) {
- if (!(init instanceof Float64Array)) throw new TypeError('Expected Float64Array')
- return new DOMMatrix(init)
- }
- [util.inspect.custom || 'inspect'] (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} ]`
- }
- }
- /**
- * 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 () { return this._values[M11] }, set (v) { return setNumber2D(this, M11, v) } },
- m12: { get () { return this._values[M12] }, set (v) { return setNumber2D(this, M12, v) } },
- m13: { get () { return this._values[M13] }, set (v) { return setNumber3D(this, M13, v) } },
- m14: { get () { return this._values[M14] }, set (v) { return setNumber3D(this, M14, v) } },
- m21: { get () { return this._values[M21] }, set (v) { return setNumber2D(this, M21, v) } },
- m22: { get () { return this._values[M22] }, set (v) { return setNumber2D(this, M22, v) } },
- m23: { get () { return this._values[M23] }, set (v) { return setNumber3D(this, M23, v) } },
- m24: { get () { return this._values[M24] }, set (v) { return setNumber3D(this, M24, v) } },
- m31: { get () { return this._values[M31] }, set (v) { return setNumber3D(this, M31, v) } },
- m32: { get () { return this._values[M32] }, set (v) { return setNumber3D(this, M32, v) } },
- m33: { get () { return this._values[M33] }, set (v) { return setNumber3D(this, M33, v) } },
- m34: { get () { return this._values[M34] }, set (v) { return setNumber3D(this, M34, v) } },
- m41: { get () { return this._values[M41] }, set (v) { return setNumber2D(this, M41, v) } },
- m42: { get () { return this._values[M42] }, set (v) { return setNumber2D(this, M42, v) } },
- m43: { get () { return this._values[M43] }, set (v) { return setNumber3D(this, M43, v) } },
- m44: { get () { return this._values[M44] }, set (v) { return setNumber3D(this, M44, v) } },
- a: { get () { return this.m11 }, set (v) { return (this.m11 = v) } },
- b: { get () { return this.m12 }, set (v) { return (this.m12 = v) } },
- c: { get () { return this.m21 }, set (v) { return (this.m21 = v) } },
- d: { get () { return this.m22 }, set (v) { return (this.m22 = v) } },
- e: { get () { return this.m41 }, set (v) { return (this.m41 = v) } },
- f: { get () { return this.m42 }, set (v) { return (this.m42 = v) } },
- is2D: { get () { return this._is2D } }, // read-only
- isIdentity: {
- get () {
- const 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) {
- const instance = Object.create(DOMMatrix.prototype)
- instance.constructor = DOMMatrix
- instance._is2D = true
- instance._values = values
- return instance
- }
- function multiply (A, B) {
- const dest = new Float64Array(16)
- for (let i = 0; i < 4; i++) {
- for (let j = 0; j < 4; j++) {
- let sum = 0
- for (let k = 0; k < 4; k++) {
- sum += A[i * 4 + k] * B[k * 4 + j]
- }
- dest[i * 4 + j] = sum
- }
- }
- return dest
- }
- module.exports = { DOMMatrix, DOMPoint }
|