UNPKG

skia-canvas

Version:

A multi-threaded, GPU-accelerated, Canvas API for Node

896 lines (738 loc) 24.6 kB
// // Polyfill for DOMMatrix and friends // "use strict" const {inspect} = require('util') const isPlainObject = o => ( o !== null && typeof o === "object" && !(o instanceof DOMMatrix) && !Array.isArray(o) && !ArrayBuffer.isView(o) ) /* * vendored in order to fix its dependence on the window global [@samizdatco 2020/08/04] * removed SVGMatrix references that were guaranteed to be undefined on node [@mpaperno 2024/10/20] * added support for parsing existing matrices (and matrix-like) objects in constructor [@mpaperno 2024/10/20] * added `parseTransform*` helpers to enable CSS-style strings as constructor args [@samizdatco 2024/10/29] * 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 && 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 (let propertyName of ["top", "right", "bottom", "left"]) { let 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(); const $is2D = Symbol(); // Parsers for CSS-style string initializers const parseTransformName = name => ( name.match(/^(matrix(3d)?|(rotate|translate|scale)(3d|X|Y|Z)?|skew(X|Y)?)$/) ) const parseAngle = value => { if (value.endsWith('deg')) return parseFloat(value) if (value.endsWith('rad')) return parseFloat(value)/Math.PI * 180 if (value.endsWith('turn')) return parseFloat(value) * 360 throw new TypeError(`Angles must be in 'deg', 'rad', or 'turn' units (got: "${value}")`) } const parseLength = value => { if (value.endsWith('px')) return parseFloat(value) if (!isNaN(value) && !isNaN(parseFloat(value))) return parseFloat(value) throw new TypeError(`Lengths must be in 'px' or numeric units (got: "${value}")`) } const parseScalar = value => { if (value.endsWith('%')) return parseFloat(value) / 100 if (!isNaN(value) && !isNaN(parseFloat(value))) return parseFloat(value) throw new TypeError(`Scales must be in '%' or numeric units (got: "${value}")`) } const parseNumeric = value => { if (!isNaN(value) && !isNaN(parseFloat(value))) return parseFloat(value) throw new TypeError(`Matrix values must be in plain, numeric units (got: "${value}")`) } const parseTransformString = (transformString) => { return transformString .split(/\)\s*?/) .filter(s => !!s.trim()) .map(transform => { let [name, transformValue] = transform.split('(').map(s => s.trim()) // catch single-word initializers if (!transformValue){ if (name.match(/^(inherit|initial|revert(-layer)?|unset|none)$/)) return {op:'matrix', vals:[1,0,0,1,0,0] } throw new SyntaxError("The string did not match the expected pattern") } // otherwise check that the last term was well formed before splitting based on `)` if (!transformString.trim().endsWith(')')){ throw new SyntaxError("Expected a closing ')'") } // validate & normalize op names if (!parseTransformName(name)){ throw new SyntaxError(`Unknown transform operation: ${name}`) }else if (name=='rotate3d'){ name = 'rotateAxisAngle' } // validate the individual values & units const rawVals = transformValue.split(',').map(s => s.trim()) const values = name.startsWith('rotate') ? [ ...rawVals.slice(0,-1).map(parseLength), parseAngle(rawVals.at(-1)) ] : name.startsWith('skew') ? rawVals.map(parseAngle) : name.startsWith('scale') ? rawVals.map(parseScalar) : name.startsWith('matrix') ? rawVals.map(parseNumeric) : rawVals.map(parseLength) // special case validation for the matrix/3d ops for (const [form, len] of [['matrix', 6], ['matrix3d', 16]]){ if (name==form && values.length!=len){ throw new TypeError(`${name}() requires 6 numeric values (got ${values.length})`) } } // catch single-dimension ops and route them to the corresponding 3D matrix method const parts = name.match(/^(rotate|translate|scale)(3d|X|Y|Z)$/) if (parts){ const [_, op, dim] = parts const fill = op=='scale' ? 1 : 0 return {op, vals: dim=='X'? [values[0], fill, fill] : dim=='Y'? [fill, values[0], fill] : dim=='Z'? [fill, fill, values[0]] : values} }else{ return {op:name, vals:values} } }).flat() } let setNumber2D = (receiver, index, value) => { if (typeof value !== "number") { throw new TypeError("Expected number"); } receiver[$values][index] = value; }; let 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; } receiver[$values][index] = value; }; let newInstance = (values) => { let instance = Object.create(DOMMatrix.prototype); instance.constructor = DOMMatrix; instance[$is2D] = true; instance[$values] = values; return instance; }; let multiply = (first, second) => { let 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[$is2D]; } get isIdentity() { let 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]); if (DOMMatrix.isMatrix4(init)) return new DOMMatrix([ init.m11, init.m12, init.m13, init.m14, init.m21, init.m22, init.m23, init.m24, init.m31, init.m32, init.m33, init.m34, init.m41, init.m42, init.m43, init.m44, ]); if (DOMMatrix.isMatrix3(init) || isPlainObject(init)){ let {a=1, b=0, c=0, d=1, e=0, f=0} = init return new DOMMatrix([a, b, c, d, e, f]); } throw new TypeError(`Expected DOMMatrix, got: '${init}'`); } 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); } static isMatrix3(matrix) { if (matrix instanceof DOMMatrix) return true; if (typeof matrix != 'object') return false; for (const p of ["a", "b", "c", "d", "e", "f"]) if (typeof matrix[p] != 'number') return false; return true; } static isMatrix4(matrix) { if (matrix instanceof DOMMatrix) return true; if (typeof matrix != 'object') return false; for (const p of [ "m11", "m12", "m13", "m14", "m21", "m22", "m23", "m24", "m31", "m32", "m33", "m34", "m41", "m42", "m43", "m44", ]) { if (typeof matrix[p] != 'number') return false; } return true; } // @type // (Float64Array) => void constructor(init) { if (init instanceof DOMMatrix || isPlainObject(init)) return DOMMatrix.fromMatrix(init); if (arguments.length > 1) init = [...arguments]; this[$is2D] = true; this[$values] = new Float64Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); // Parse CSS transformList and accumulate transforms sequentially if (typeof init === "string") { if (init === "") return; let acc = new DOMMatrix() for (const {op, vals} of parseTransformString(init)) { acc = op.startsWith('matrix') ? acc.multiply(new DOMMatrix(vals)) : acc[op] ? acc[op](...vals) : acc } init = acc[$values] } 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, array, or matrix object."); } } dump(){ let mat = this[$values] console.log([ 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]" let {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{ let {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[$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 = 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[$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 = 0, y = 0) { let 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 = 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) { let 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; let c = Math.cos(angle); let s = Math.sin(angle); let t = 1 - c; let tx = t * x; let 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[$is2D] = false; } return this; } skew(sx, sy){ return newInstance(this[$values]).skewSelf(sx, sy); } skewSelf(sx, sy){ if (typeof sx !== "number" && typeof sy !== "number") { return this; } let x = isNaN(sx) ? 0 : Math.tan(sx * RAD_PER_DEGREE); let y = isNaN(sy) ? 0 : Math.tan(sy * RAD_PER_DEGREE); this[$values] = multiply([ 1, y, 0, 0, x, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ], this[$values]); return this; } skewX(sx) { return newInstance(this[$values]).skewXSelf(sx); } skewXSelf(sx) { if (typeof sx !== "number") { return this; } let 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; } let 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[$is2D]) { let det = this[$values][A] * this[$values][D] - this[$values][B] * this[$values][C]; // Invertable if (det !== 0) { let 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[$is2D] = 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) { let temp = new DOMMatrix(transformList); this[$values] = temp[$values]; this[$is2D] = temp[$is2D]; return this; } transformPoint(point) { let x = point.x; let y = point.y; let z = point.z; let w = point.w; let values = this[$values]; let nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w; let ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w; let nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w; let 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() { let name = this.is2D ? 'matrix' : 'matrix3d' let values = this.is2D ? [this.a, this.b, this.c, this.d, this.e, this.f] : this[$values] let simplify = n => n.toFixed(12).replace(/\.([^0])?0*$/, ".$1").replace(/\.$/, '').replace(/^-0$/, '0') return `${name}(${values.map(simplify).join(', ')})` } clone(){ return new DOMMatrix(this) } } for (let 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" ]) { let propertyDescriptor = Object.getOwnPropertyDescriptor(DOMMatrix.prototype, propertyName); propertyDescriptor.enumerable = true; Object.defineProperty(DOMMatrix.prototype, propertyName, propertyDescriptor); } // // Helpers to reconcile Skia and DOMMatrix’s disagreement about row/col orientation // function toSkMatrix() { if (arguments.length != 1 && arguments.length < 6){ throw new TypeError("not enough arguments") } try { const m = new DOMMatrix(...arguments); return [m.a, m.c, m.e, m.b, m.d, m.f, m.m14, m.m24, m.m44]; }catch(e){ throw new TypeError(`Invalid transform matrix argument(s): `+e); } } function fromSkMatrix(skMatrix){ let [a, b, c, d, e, f, p0, p1, p2] = skMatrix return new DOMMatrix([ a, d, 0, p0, b, e, 0, p1, 0, 0, 1, 0, c, f, 0, p2 ]) } module.exports = {DOMPoint, DOMMatrix, DOMRect, toSkMatrix, fromSkMatrix}