@tbela99/css-parser
Version:
CSS parser for node and the browser
199 lines (197 loc) • 6.8 kB
JavaScript
const epsilon = 1e-5;
function identity() {
return [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
}
function pLength(point) {
// Calcul de la norme euclidienne
return Math.sqrt(point[0] * point[0] + point[1] * point[1] + point[2] * point[2]);
}
function normalize(point) {
const [x, y, z] = point;
const norm = Math.sqrt(point[0] * point[0] + point[1] * point[1] + point[2] * point[2]);
return norm === 0 ? [0, 0, 0] : [x / norm, y / norm, z / norm];
}
function dot(point1, point2) {
if (point1.length === 4 && point2.length === 4) {
return point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2] + point1[3] * point2[3];
}
return point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2];
}
function multiply(matrixA, matrixB) {
let result = Array(4).fill(0).map(() => Array(4).fill(0));
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
for (let k = 0; k < 4; k++) {
result[j][i] += matrixA[k][i] * matrixB[j][k];
}
}
}
return result;
}
function round(number) {
return Math.abs(number) < epsilon ? 0 : +number.toPrecision(6);
}
// translate3d(25.9808px, 0, 15px ) rotateY(60deg) skewX(49.9999deg) scale(1, 1.2)
// translate → rotate → skew → scale
function decompose(original) {
const matrix = original.flat();
// Normalize last row
if (matrix[15] === 0) {
return null;
}
for (let i = 0; i < 16; i++)
matrix[i] /= matrix[15];
// Perspective extraction
const perspective = [0, 0, 0, 1];
if (matrix[3] !== 0 || matrix[7] !== 0 || matrix[11] !== 0) {
const rightHandSide = [matrix[3], matrix[7], matrix[11], matrix[15]];
const perspectiveMatrix = matrix.slice();
perspectiveMatrix[3] = 0;
perspectiveMatrix[7] = 0;
perspectiveMatrix[11] = 0;
perspectiveMatrix[15] = 1;
const inverse = invertMatrix4(perspectiveMatrix);
if (!inverse) {
return null;
}
const transposedInverse = transposeMatrix4(inverse);
perspective[0] = dot(rightHandSide, transposedInverse.slice(0, 4));
perspective[1] = dot(rightHandSide, transposedInverse.slice(4, 8));
perspective[2] = dot(rightHandSide, transposedInverse.slice(8, 12));
perspective[3] = dot(rightHandSide, transposedInverse.slice(12, 16));
// Clear perspective from matrix
matrix[3] = 0;
matrix[7] = 0;
matrix[11] = 0;
matrix[15] = 1;
}
// Translation
const translate = [matrix[12], matrix[13], matrix[14]];
matrix[12] = matrix[13] = matrix[14] = 0;
// Build the 3x3 matrix
const row0 = [matrix[0], matrix[1], matrix[2]];
const row1 = [matrix[4], matrix[5], matrix[6]];
const row2 = [matrix[8], matrix[9], matrix[10]];
// Compute scale
const scaleX = pLength(row0);
const row0Norm = normalize(row0);
const skewXY = dot(row0Norm, row1);
const row1Proj = [
row1[0] - skewXY * row0Norm[0],
row1[1] - skewXY * row0Norm[1],
row1[2] - skewXY * row0Norm[2]
];
const scaleY = pLength(row1Proj);
const row1Norm = normalize(row1Proj);
const skewXZ = dot(row0Norm, row2);
const skewYZ = dot(row1Norm, row2);
const row2Proj = [
row2[0] - skewXZ * row0Norm[0] - skewYZ * row1Norm[0],
row2[1] - skewXZ * row0Norm[1] - skewYZ * row1Norm[1],
row2[2] - skewXZ * row0Norm[2] - skewYZ * row1Norm[2]
];
const scaleZ = pLength(row2Proj);
const row2Norm = normalize(row2Proj);
// Build rotation matrix from orthonormalized vectors
const r00 = row0Norm[0], r01 = row1Norm[0], r02 = row2Norm[0];
const r10 = row0Norm[1], r11 = row1Norm[1], r12 = row2Norm[1];
const r20 = row0Norm[2], r21 = row1Norm[2], r22 = row2Norm[2];
// Convert to quaternion
const trace = r00 + r11 + r22;
let qw, qx, qy, qz;
if (trace > 0) {
const s = 0.5 / Math.sqrt(trace + 1.0);
qw = 0.25 / s;
qx = (r21 - r12) * s;
qy = (r02 - r20) * s;
qz = (r10 - r01) * s;
}
else if (r00 > r11 && r00 > r22) {
const s = 2.0 * Math.sqrt(1.0 + r00 - r11 - r22);
qw = (r21 - r12) / s;
qx = 0.25 * s;
qy = (r01 + r10) / s;
qz = (r02 + r20) / s;
}
else if (r11 > r22) {
const s = 2.0 * Math.sqrt(1.0 + r11 - r00 - r22);
qw = (r02 - r20) / s;
qx = (r01 + r10) / s;
qy = 0.25 * s;
qz = (r12 + r21) / s;
}
else {
const s = 2.0 * Math.sqrt(1.0 + r22 - r00 - r11);
qw = (r10 - r01) / s;
qx = (r02 + r20) / s;
qy = (r12 + r21) / s;
qz = 0.25 * s;
}
[qx, qy, qz] = toZero([qx, qy, qz]);
// const q = gcd(qx, gcd(qy, qz));
let q = [Math.abs(qx), Math.abs(qy), Math.abs(qz)].reduce((acc, curr) => {
if (acc == 0 || (curr > 0 && curr < acc)) {
acc = curr;
}
return acc;
}, 0);
if (q > 0) {
qx /= q;
qy /= q;
qz /= q;
}
const rotate = [qx, qy, qz, Object.is(qw, 0) ? 0 : 2 * Math.acos(qw) * 180 / Math.PI];
const scale = [scaleX, scaleY, scaleZ];
const skew = [skewXY, skewXZ, skewYZ];
return {
translate,
scale,
rotate,
skew,
perspective
};
}
function transposeMatrix4(m) {
return [
m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15],
];
}
function invertMatrix4(m) {
new Array(16);
const det = m[0] * m[5] * m[10] * m[15] + m[0] * m[9] * m[14] * m[7] + m[0] * m[13] * m[6] * m[11]
- m[0] * m[13] * m[10] * m[7] - m[0] * m[9] * m[6] * m[15] - m[0] * m[5] * m[14] * m[11];
if (det === 0)
return null;
// For brevity, not implementing full inverse here — you'd normally use gl-matrix or similar.
// Just use a trusted library or expand this if needed.
return null; // placeholder
}
function toZero(v) {
for (let i = 0; i < v.length; i++) {
if (Math.abs(v[i]) <= epsilon) {
v[i] = 0;
}
else {
v[i] = +v[i].toPrecision(6);
}
}
return v;
}
// https://drafts.csswg.org/css-transforms-1/#2d-matrix
function is2DMatrix(matrix) {
// m13,m14, m23, m24, m31, m32, m34, m43 are all 0
return matrix[0][2] === 0 &&
matrix[0][3] === 0 &&
matrix[1][2] === 0 &&
matrix[1][3] === 0 &&
matrix[2][0] === 0 &&
matrix[2][1] === 0 &&
matrix[2][3] === 0 &&
matrix[3][2] === 0 &&
matrix[2][2] === 1 &&
matrix[3][3] === 1;
}
export { decompose, epsilon, identity, is2DMatrix, multiply, round, toZero };