@tbela99/css-parser
Version:
CSS parser, minifier and validator for node and the browser
326 lines (323 loc) • 13.2 kB
JavaScript
import { multiply, toZero, identity } from './utils.js';
import { EnumToken, length2Px } from '../types.js';
import { stripCommaToken } from '../../validation/utils/list.js';
import { translateX, translateY, translateZ, translate, translate3d } from './translate.js';
import { getNumber, getAngle } from '../../syntax/color/color.js';
import { rotate, rotate3D } from './rotate.js';
import { scale3d, scale, scaleX, scaleY, scaleZ } from './scale.js';
import { minify } from './minify.js';
import { skew, skewX, skewY } from './skew.js';
import { serialize, matrix } from './matrix.js';
import { perspective } from './perspective.js';
import { transformFunctions } from '../../syntax/constants.js';
function compute(transformLists) {
transformLists = transformLists.slice();
stripCommaToken(transformLists);
let matrix = identity();
let mat;
const cumulative = [];
for (const transformList of splitTransformList(transformLists)) {
mat = computeMatrix(transformList, identity());
if (mat == null) {
return null;
}
matrix = multiply(matrix, mat);
cumulative.push(...(minify(mat) ?? transformList));
}
const serialized = serialize(matrix);
if (cumulative.length > 0) {
for (let i = 0; i < cumulative.length; i++) {
if (cumulative[i].typ == EnumToken.IdenTokenType && cumulative[i].val == "none") {
cumulative.splice(i--, 1);
}
}
if (cumulative.length == 0) {
cumulative.push({
typ: EnumToken.IdenTokenType,
val: "none",
});
}
}
return {
matrix: serialize(toZero(matrix)),
cumulative,
minified: minify(matrix) ?? [serialized],
};
}
function computeMatrix(transformList, matrixVar) {
let values = [];
let val;
let i = 0;
for (; i < transformList.length; i++) {
if (transformList[i].typ != EnumToken.TransformFunctionTokenType ||
!transformFunctions.includes(transformList[i].val)) {
return null;
}
switch (transformList[i].val) {
case "translate":
case "translateX":
case "translateY":
case "translateZ":
case "translate3d":
{
values.length = 0;
const children = stripCommaToken(transformList[i].chi.slice());
const valCount = transformList[i].val == "translate3d"
? 3
: transformList[i].val == "translate"
? 2
: 1;
for (let j = 0; j < children.length; j++) {
if (children[j].typ == EnumToken.WhitespaceTokenType) {
continue;
}
val = length2Px(children[j]);
if (val == null) {
return null;
}
values.push(val);
}
if (values.length == 0 || values.length > valCount) {
return null;
}
if (transformList[i].val == "translateX") {
matrixVar = translateX(values[0], matrixVar);
}
else if (transformList[i].val == "translateY") {
matrixVar = translateY(values[0], matrixVar);
}
else if (transformList[i].val == "translateZ") {
matrixVar = translateZ(values[0], matrixVar);
}
else if (transformList[i].val == "translate") {
matrixVar = translate(values, matrixVar);
}
else {
// @ts-ignore
matrixVar = translate3d(values, matrixVar);
}
}
break;
case "rotate":
case "rotateX":
case "rotateY":
case "rotateZ":
case "rotate3d":
{
let x = 0;
let y = 0;
let z = 0;
let angle;
let values = [];
let valuesCount = transformList[i].val == "rotate3d" ? 4 : 1;
for (const child of stripCommaToken(transformList[i].chi.slice())) {
values.push(child);
if (transformList[i].val == "rotateX") {
x = 1;
}
else if (transformList[i].val == "rotateY") {
y = 1;
}
else if (transformList[i].val == "rotate" ||
transformList[i].val == "rotateZ") {
z = 1;
}
}
if (values.length != valuesCount) {
return null;
}
if (transformList[i].val == "rotate3d") {
x = getNumber(values[0]);
y = getNumber(values[1]);
z = getNumber(values[2]);
}
angle = getAngle(values.at(-1));
if ([x, y, z, angle].some((t) => typeof t != "number" || Number.isNaN(+t))) {
return null;
}
if (transformList[i].val == "rotate" ||
transformList[i].val == "rotateZ") {
matrixVar = rotate(angle * 2 * Math.PI, matrixVar);
}
else {
matrixVar = rotate3D(angle * 2 * Math.PI, x, y, z, matrixVar);
}
}
break;
case "scale":
case "scaleX":
case "scaleY":
case "scaleZ":
case "scale3d":
{
values.length = 0;
let child;
const children = stripCommaToken(transformList[i].chi.slice());
for (let k = 0; k < children.length; k++) {
child = children[k];
if (child.typ != EnumToken.NumberTokenType && child.typ != EnumToken.PercentageTokenType) {
return null;
}
values.push(getNumber(child));
}
if (transformList[i].val == "scale3d") {
if (values.length != 3) {
return null;
}
matrixVar = scale3d(...values, matrixVar);
break;
}
if (transformList[i].val == "scale") {
if (values.length != 1 && values.length != 2) {
return null;
}
matrixVar = scale(values[0], values[1] ?? values[0], matrixVar);
break;
}
if (values.length != 1) {
return null;
}
else if (transformList[i].val == "scaleX") {
matrixVar = scaleX(values[0], matrixVar);
}
else if (transformList[i].val == "scaleY") {
matrixVar = scaleY(values[0], matrixVar);
}
else if (transformList[i].val == "scaleZ") {
matrixVar = scaleZ(values[0], matrixVar);
}
}
break;
case "skew":
case "skewX":
case "skewY":
{
values.length = 0;
let child;
let value;
for (let k = 0; k < transformList[i].chi.length; k++) {
child = transformList[i].chi[k];
if (child.typ == EnumToken.CommentTokenType ||
child.typ == EnumToken.CommaTokenType ||
child.typ == EnumToken.WhitespaceTokenType) {
continue;
}
value = getAngle(child);
values.push(value * 2 * Math.PI);
}
if (values.length == 0 ||
values.length > (transformList[i].val == "skew" ? 2 : 1)) {
return null;
}
if (transformList[i].val == "skew") {
matrixVar = skew(values, matrixVar);
}
else {
matrixVar =
transformList[i].val == "skewX"
? skewX(values[0], matrixVar)
: skewY(values[0], matrixVar);
}
}
break;
case "perspective":
{
const values = [];
let child;
let value;
for (let k = 0; k < transformList[i].chi.length; k++) {
child = transformList[i].chi[k];
if (child.typ == EnumToken.CommentTokenType || child.typ == EnumToken.WhitespaceTokenType) {
continue;
}
if (child.typ == EnumToken.IdenTokenType && child.val == "none") {
values.push(child);
continue;
}
value = length2Px(child);
if (value == null) {
return null;
}
values.push(value);
}
if (values.length != 1) {
return null;
}
matrixVar = perspective(values[0], matrixVar);
}
break;
case "matrix3d":
case "matrix":
{
const values = [];
let value;
for (const token of transformList[i].chi) {
if ([
EnumToken.WhitespaceTokenType,
EnumToken.CommentTokenType,
EnumToken.CommaTokenType,
].includes(token.typ)) {
continue;
}
value = getNumber(token);
if (value == null) {
return null;
}
values.push(value);
}
if (transformList[i].val == "matrix") {
if (values.length != 6) {
return null;
}
}
else if (values.length != 16) {
return null;
}
matrixVar = multiply(matrixVar, matrix(values));
}
break;
default:
return null;
}
}
return matrixVar;
}
function splitTransformList(transformList) {
let pattern = null;
const tokens = [];
for (let i = 0; i < transformList.length; i++) {
if (transformList[i].typ == EnumToken.CommentTokenType ||
transformList[i].typ == EnumToken.WhitespaceTokenType) {
continue;
}
if (pattern == null ||
(transformList[i].typ == EnumToken.TransformFunctionTokenType &&
!transformList[i].val.startsWith(pattern))) {
if (transformList[i].typ == EnumToken.TransformFunctionTokenType) {
if (transformList[i].val.startsWith("scale")) {
pattern = "scale";
}
else if (transformList[i].val.startsWith("rotate")) {
pattern = "rotate";
}
else if (transformList[i].val.startsWith("translate")) {
pattern = "translate";
}
else {
pattern = null;
}
tokens.push([transformList[i]]);
continue;
}
}
if (pattern != null &&
transformList[i].typ == EnumToken.TransformFunctionTokenType &&
transformList[i].val.startsWith(pattern)) {
tokens[tokens.length - 1].push(transformList[i]);
continue;
}
tokens.push([transformList[i]]);
}
return tokens;
}
export { compute, computeMatrix };