UNPKG

@tbela99/css-parser

Version:

CSS parser, minifier and validator for node and the browser

372 lines (369 loc) 13.1 kB
import { multiply, decompose, round, toZero, identity } from './utils.js'; import { epsilon } from '../../syntax/constants.js'; import { EnumToken } from '../types.js'; import { computeMatrix } from './compute.js'; import { parseMatrix } from './matrix.js'; // translate → rotate → skew → scale function minify(matrix) { const decomposed = decompose(matrix); if (decomposed == null) { return null; } const transforms = new Set(["translate", "scale", "skew", "perspective", "rotate"]); const scales = new Set(["x", "y", "z"]); const skew = new Set(["x", "y"]); let result = []; // check identity if (round(decomposed.translate[0]) == 0 && round(decomposed.translate[1]) == 0 && round(decomposed.translate[2]) == 0) { transforms.delete("translate"); } if (round(decomposed.scale[0]) == 1 && round(decomposed.scale[1]) == 1 && round(decomposed.scale[2]) == 1) { transforms.delete("scale"); } if (round(decomposed.skew[0]) == 0 && round(decomposed.skew[1]) == 0) { transforms.delete("skew"); } if (round(decomposed.perspective[2]) == 0) { transforms.delete("perspective"); } if (round(decomposed.rotate[3]) == 0) { transforms.delete("rotate"); } if (transforms.has("translate")) { let coordinates = new Set(["x", "y", "z"]); for (let i = 0; i < 3; i++) { if (round(decomposed.translate[i]) == 0) { coordinates.delete(i == 0 ? "x" : i == 1 ? "y" : "z"); } } if (coordinates.size == 1) { if (coordinates.has("x")) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "translate", chi: [{ typ: EnumToken.LengthTokenType, val: round(decomposed.translate[0]), unit: "px" }], }); } else { let axis = coordinates.has("y") ? "y" : "z"; let index = axis == "y" ? 1 : 2; result.push({ typ: EnumToken.TransformFunctionTokenType, val: "translate" + axis.toUpperCase(), chi: [{ typ: EnumToken.LengthTokenType, val: round(decomposed.translate[index]), unit: "px" }], }); } } else if (coordinates.has("z")) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "translate3d", chi: [ decomposed.translate[0] == 0 ? { typ: EnumToken.NumberTokenType, val: 0, } : { typ: EnumToken.LengthTokenType, val: round(decomposed.translate[0]), unit: "px" }, { typ: EnumToken.CommaTokenType }, decomposed.translate[1] == 0 ? { typ: EnumToken.NumberTokenType, val: 0, } : { typ: EnumToken.LengthTokenType, val: round(decomposed.translate[1]), unit: "px" }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.LengthTokenType, val: round(decomposed.translate[2]), unit: "px" }, ], }); } else { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "translate", chi: [ { typ: EnumToken.LengthTokenType, val: round(decomposed.translate[0]), unit: "px" }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.LengthTokenType, val: round(decomposed.translate[1]), unit: "px" }, ], }); } } if (transforms.has("rotate")) { const [x, y, z, angle] = decomposed.rotate; if (y == 0 && z == 0) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "rotateX", chi: [ { typ: EnumToken.AngleTokenType, val: round(angle), unit: "deg", }, ], }); } else if (x == 0 && z == 0) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "rotateY", chi: [ { typ: EnumToken.AngleTokenType, val: round(angle), unit: "deg", }, ], }); } else if (x == 0 && y == 0) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "rotate", chi: [ { typ: EnumToken.AngleTokenType, val: round(angle), unit: "deg", }, ], }); } else { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "rotate3d", chi: [ { typ: EnumToken.NumberTokenType, val: round(x), }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.NumberTokenType, val: round(y), }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.NumberTokenType, val: round(z), }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.AngleTokenType, val: round(angle), unit: "deg", }, ], }); } } if (transforms.has("skew")) { if (round(decomposed.skew[1]) == 0) { skew.delete("y"); } for (let i = 0; i < 2; i++) { decomposed.skew[i] = round((Math.atan(decomposed.skew[i]) * 180) / Math.PI); } if (skew.size == 1) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "skew" + (skew.has("x") ? "" : "Y"), chi: [{ typ: EnumToken.AngleTokenType, val: round(decomposed.skew[0]), unit: "deg" }], }); } else { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "skew", chi: [ { typ: EnumToken.AngleTokenType, val: round(decomposed.skew[0]), unit: "deg" }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.AngleTokenType, val: round(decomposed.skew[1]), unit: "deg" }, ], }); } } if (transforms.has("scale")) { const [sx, sy, sz] = toZero(decomposed.scale); if (sz == 1) { scales.delete("z"); } if (sy == 1) { scales.delete("y"); } if (sx == 1) { scales.delete("x"); } if (scales.size == 1) { let prefix = scales.has("x") ? "X" : scales.has("y") ? "Y" : "Z"; result.push({ typ: EnumToken.TransformFunctionTokenType, val: "scale" + prefix, chi: [{ typ: EnumToken.NumberTokenType, val: round(prefix == "Z" ? sz : prefix == "Y" ? sy : sx) }], }); } else if (!scales.has("z")) { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "scale", chi: [ { typ: EnumToken.NumberTokenType, val: round(sx) }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.NumberTokenType, val: round(sy) }, ], }); } else { result.push({ typ: EnumToken.TransformFunctionTokenType, val: "scale3d", chi: [ { typ: EnumToken.NumberTokenType, val: round(sx) }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.NumberTokenType, val: round(sy) }, { typ: EnumToken.CommaTokenType }, { typ: EnumToken.NumberTokenType, val: round(sz) }, ], }); } } // identity return result.length == 0 || (result.length == 1 && eqMatrix(identity(), result)) ? [ { typ: EnumToken.IdenTokenType, val: "none", }, ] : result; } function eqMatrix(a, b) { let mat = identity(); let tmp = identity(); const data = (Array.isArray(a) ? a : parseMatrix(a)); for (const transform of b) { tmp = computeMatrix([transform], identity()); if (tmp == null) { return false; } mat = multiply(mat, tmp); } if (mat == null) { return false; } for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { if (Math.abs(mat[i * 4 + j] - data[i * 4 + j]) > epsilon) { return false; } } } return true; } function minifyTransformFunctions(transform) { const name = transform.val.toLowerCase(); if ("skewx" == name) { transform.val = "skew"; return transform; } if (!["translate", "translate3d", "scale", "scale3d"].includes(name)) { return transform; } const values = []; for (const token of transform.chi) { if (token.typ == EnumToken.CommentTokenType || token.typ == EnumToken.WhitespaceTokenType || token.typ == EnumToken.CommaTokenType) { continue; } if (![ EnumToken.NumberTokenType, EnumToken.LengthTokenType, EnumToken.AngleTokenType, EnumToken.PercentageTokenType, ].includes(token.typ)) { return transform; } if (token.typ == EnumToken.PercentageTokenType && typeof token.val == "number" && name.startsWith("scale")) { Object.assign(token, { typ: EnumToken.NumberTokenType, val: token.val / 100, }); } values.push(token); } if (values.length == 0) { return transform; } if ((name == "translate" || name == "scale") && values.length > 2) { return transform; } const ignoredValue = name.startsWith("scale") ? 1 : 0; const t = new Set(["x", "y", "z"]); let i = 3; while (i--) { if (values.length <= i || values[i].val == ignoredValue) { t.delete(i == 0 ? "x" : i == 1 ? "y" : "z"); } } if (name == "translate3d" || name == "translate") { if (t.size == 0) { return { typ: EnumToken.TransformFunctionTokenType, val: "translate", chi: [{ typ: EnumToken.NumberTokenType, val: 0 }], }; } if (t.size == 1) { return { typ: EnumToken.TransformFunctionTokenType, val: "translate" + (t.has("x") ? "" : t.has("y") ? "Y" : "Z"), chi: [values[t.has("x") ? 0 : t.has("y") ? 1 : 2]], }; } if (t.size == 2) { if (t.has("z")) { return transform; } return { typ: EnumToken.TransformFunctionTokenType, val: "translate", chi: [values[0], { typ: EnumToken.CommaTokenType }, values[1]], }; } } if (name == "scale3d" || name == "scale") { if (t.size == 0) { return { typ: EnumToken.TransformFunctionTokenType, val: "scale", chi: [{ typ: EnumToken.NumberTokenType, val: 1 }], }; } if (t.size == 1) { return { typ: EnumToken.TransformFunctionTokenType, val: "scale" + (t.has("x") ? "X" : t.has("y") ? "Y" : "Z"), chi: [values[t.has("x") ? 0 : t.has("y") ? 1 : 2]], }; } if (t.size == 2) { if (t.has("z")) { return transform; } return { typ: EnumToken.TransformFunctionTokenType, val: "scale", chi: [values[0], { typ: EnumToken.CommaTokenType }, values[1]], }; } } return transform; } export { eqMatrix, minify, minifyTransformFunctions };