const { inspect } = require('util') /* * vendored in order to fix its dependence on the window global [cds 2020/08/04] * otherwise unchanged from https://github.com/jarek-foksa/geometry-polyfill/tree/f36bbc8f4bc43539d980687904ce46c8e915543d */ // @info // DOMPoint polyfill // @src // https://drafts.fxtf.org/geometry/#DOMPoint // https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/geometry/dom_point_read_only.cc class DOMPoint { constructor(x = 0, y = 0, z = 0, w = 1) { this.x = x this.y = y this.z = z this.w = w } static fromPoint(otherPoint) { return new DOMPoint( otherPoint.x, otherPoint.y, otherPoint.z !== undefined ? otherPoint.z : 0, otherPoint.w !== undefined ? otherPoint.w : 1, ) } matrixTransform(matrix) { if ((matrix.is2D || matrix instanceof SVGMatrix) && this.z === 0 && this.w === 1) { return new DOMPoint( this.x * matrix.a + this.y * matrix.c + matrix.e, this.x * matrix.b + this.y * matrix.d + matrix.f, 0, 1, ) } else { return new DOMPoint( this.x * matrix.m11 + this.y * matrix.m21 + this.z * matrix.m31 + this.w * matrix.m41, this.x * matrix.m12 + this.y * matrix.m22 + this.z * matrix.m32 + this.w * matrix.m42, this.x * matrix.m13 + this.y * matrix.m23 + this.z * matrix.m33 + this.w * matrix.m43, this.x * matrix.m14 + this.y * matrix.m24 + this.z * matrix.m34 + this.w * matrix.m44, ) } } toJSON() { return { x: this.x, y: this.y, z: this.z, w: this.w, } } } // @info // DOMRect polyfill // @src // https://drafts.fxtf.org/geometry/#DOMRect // https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/geometry/dom_rect_read_only.cc class DOMRect { constructor(x = 0, y = 0, width = 0, height = 0) { this.x = x this.y = y this.width = width this.height = height } static fromRect(otherRect) { return new DOMRect(otherRect.x, otherRect.y, otherRect.width, otherRect.height) } get top() { return this.y } get left() { return this.x } get right() { return this.x + this.width } get bottom() { return this.y + this.height } toJSON() { return { x: this.x, y: this.y, width: this.width, height: this.height, top: this.top, left: this.left, right: this.right, bottom: this.bottom, } } } for (const propertyName of ['top', 'right', 'bottom', 'left']) { const propertyDescriptor = Object.getOwnPropertyDescriptor(DOMRect.prototype, propertyName) propertyDescriptor.enumerable = true Object.defineProperty(DOMRect.prototype, propertyName, propertyDescriptor) } // @info // DOMMatrix polyfill (SVG 2) // @src // https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/geometry/dom_matrix_read_only.cc // https://github.com/tocharomera/generativecanvas/blob/master/node-canvas/lib/DOMMatrix.js 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 A = M11, B = M12 const C = M21, D = M22 const E = M41, F = M42 const DEGREE_PER_RAD = 180 / Math.PI const RAD_PER_DEGREE = Math.PI / 180 const VALUES = Symbol('values') const IS_2D = Symbol('is2D') function parseMatrix(init) { let parsed = init.replace(/matrix\(/, '').split(/,/, 7) 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) { const parsed = init.replace(/matrix3d\(/, '').split(/,/, 17) if (parsed.length !== 16) { throw new Error(`Failed to parse ${init}`) } return parsed.map(parseFloat) } function parseTransform(tform) { const type = tform.split(/\(/, 1)[0] if (type === 'matrix') { return parseMatrix(tform) } else if (type === 'matrix3d') { return parseMatrix3d(tform) } else { throw new Error(`${type} parsing not implemented`) } } const setNumber2D = (receiver, index, value) => { if (typeof value !== 'number') { throw new TypeError('Expected number') } receiver[VALUES][index] = value } const setNumber3D = (receiver, index, value) => { if (typeof value !== 'number') { throw new TypeError('Expected number') } if (index === M33 || index === M44) { if (value !== 1) { receiver[IS_2D] = false } } else if (value !== 0) { receiver[IS_2D] = false } receiver[VALUES][index] = value } const newInstance = (values) => { const instance = Object.create(DOMMatrix.prototype) instance.constructor = DOMMatrix instance[IS_2D] = true instance[VALUES] = values return instance } const multiply = (first, second) => { 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 += first[i * 4 + k] * second[k * 4 + j] } dest[i * 4 + j] = sum } } return dest } class DOMMatrix { get m11() { return this[VALUES][M11] } set m11(value) { setNumber2D(this, M11, value) } get m12() { return this[VALUES][M12] } set m12(value) { setNumber2D(this, M12, value) } get m13() { return this[VALUES][M13] } set m13(value) { setNumber3D(this, M13, value) } get m14() { return this[VALUES][M14] } set m14(value) { setNumber3D(this, M14, value) } get m21() { return this[VALUES][M21] } set m21(value) { setNumber2D(this, M21, value) } get m22() { return this[VALUES][M22] } set m22(value) { setNumber2D(this, M22, value) } get m23() { return this[VALUES][M23] } set m23(value) { setNumber3D(this, M23, value) } get m24() { return this[VALUES][M24] } set m24(value) { setNumber3D(this, M24, value) } get m31() { return this[VALUES][M31] } set m31(value) { setNumber3D(this, M31, value) } get m32() { return this[VALUES][M32] } set m32(value) { setNumber3D(this, M32, value) } get m33() { return this[VALUES][M33] } set m33(value) { setNumber3D(this, M33, value) } get m34() { return this[VALUES][M34] } set m34(value) { setNumber3D(this, M34, value) } get m41() { return this[VALUES][M41] } set m41(value) { setNumber2D(this, M41, value) } get m42() { return this[VALUES][M42] } set m42(value) { setNumber2D(this, M42, value) } get m43() { return this[VALUES][M43] } set m43(value) { setNumber3D(this, M43, value) } get m44() { return this[VALUES][M44] } set m44(value) { setNumber3D(this, M44, value) } get a() { return this[VALUES][A] } set a(value) { setNumber2D(this, A, value) } get b() { return this[VALUES][B] } set b(value) { setNumber2D(this, B, value) } get c() { return this[VALUES][C] } set c(value) { setNumber2D(this, C, value) } get d() { return this[VALUES][D] } set d(value) { setNumber2D(this, D, value) } get e() { return this[VALUES][E] } set e(value) { setNumber2D(this, E, value) } get f() { return this[VALUES][F] } set f(value) { setNumber2D(this, F, value) } get is2D() { return this[IS_2D] } get isIdentity() { 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 ) } static fromMatrix(init) { if (init instanceof DOMMatrix) { return new DOMMatrix(init[VALUES]) } else if (init instanceof SVGMatrix) { return new DOMMatrix([init.a, init.b, init.c, init.d, init.e, init.f]) } else { throw new TypeError('Expected DOMMatrix') } } 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) } // @type // (Float64Array) => void constructor(init) { this[IS_2D] = true this[VALUES] = new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) // Parse CSS transformList if (typeof init === 'string') { if (init === '') { return } else { const tforms = init.split(/\)\s+/, 20).map(parseTransform) if (tforms.length === 0) { return } init = tforms[0] for (let i = 1; i < tforms.length; i++) { init = multiply(tforms[i], init) } } } let i = 0 if (init && init.length === 6) { setNumber2D(this, A, init[i++]) setNumber2D(this, B, init[i++]) setNumber2D(this, C, init[i++]) setNumber2D(this, D, init[i++]) setNumber2D(this, E, init[i++]) setNumber2D(this, F, 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.') } } dump() { const mat = this[VALUES] console.info([mat.slice(0, 4), mat.slice(4, 8), mat.slice(8, 12), mat.slice(12, 16)]) } [inspect.custom](depth, options) { if (depth < 0) return '[DOMMatrix]' const { a, b, c, d, e, f, is2D, isIdentity } = this if (this.is2D) { return `DOMMatrix ${inspect({ a, b, c, d, e, f, is2D, isIdentity }, { colors: true })}` } else { const { m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44, is2D, isIdentity } = this return `DOMMatrix ${inspect( { a, b, c, d, e, f, m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44, is2D, isIdentity, }, { colors: true }, )}` } } multiply(other) { return newInstance(this[VALUES]).multiplySelf(other) } multiplySelf(other) { this[VALUES] = multiply(other[VALUES], this[VALUES]) if (!other.is2D) { this[IS_2D] = false } return this } preMultiplySelf(other) { this[VALUES] = multiply(this[VALUES], other[VALUES]) if (!other.is2D) { this[IS_2D] = false } return this } translate(tx, ty, tz) { return newInstance(this[VALUES]).translateSelf(tx, ty, tz) } translateSelf(tx = 0, ty = 0, 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[IS_2D] = 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[IS_2D] = false } return this } rotateFromVector(x, y) { return newInstance(this[VALUES]).rotateFromVectorSelf(x, y) } rotateFromVectorSelf(x = 0, 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[IS_2D] = false } rotX *= RAD_PER_DEGREE rotY *= RAD_PER_DEGREE rotZ *= RAD_PER_DEGREE let c = Math.cos(rotZ) let 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 = 0, y = 0, z = 0, angle = 0) { 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 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[IS_2D] = 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() { if (this[IS_2D]) { const det = this[VALUES][A] * this[VALUES][D] - this[VALUES][B] * this[VALUES][C] // Invertable if (det !== 0) { const result = new DOMMatrix() result.a = this[VALUES][D] / det result.b = -this[VALUES][B] / det result.c = -this[VALUES][C] / det result.d = this[VALUES][A] / det result.e = (this[VALUES][C] * this[VALUES][F] - this[VALUES][D] * this[VALUES][E]) / det result.f = (this[VALUES][B] * this[VALUES][E] - this[VALUES][A] * this[VALUES][F]) / det return result } // Not invertable else { this[IS_2D] = false this[VALUES] = [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN] } } else { throw new Error('3D matrix inversion is not implemented.') } } setMatrixValue(transformList) { const temp = new DOMMatrix(transformList) this[VALUES] = temp[VALUES] this[IS_2D] = temp[IS_2D] return this } transformPoint(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) } toJSON() { return { 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, m24: this.m24, 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, } } toString() { if (this.is2D) { return `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})` } else { return `matrix3d(${this[VALUES].join(', ')})` } } } for (const propertyName of [ 'a', 'b', 'c', 'd', 'e', 'f', 'm11', 'm12', 'm13', 'm14', 'm21', 'm22', 'm23', 'm24', 'm31', 'm32', 'm33', 'm34', 'm41', 'm42', 'm43', 'm44', 'is2D', 'isIdentity', ]) { const propertyDescriptor = Object.getOwnPropertyDescriptor(DOMMatrix.prototype, propertyName) propertyDescriptor.enumerable = true Object.defineProperty(DOMMatrix.prototype, propertyName, propertyDescriptor) } module.exports = { DOMPoint, DOMMatrix, DOMRect }