@tbela99/css-parser
Version:
CSS parser, minifier and validator for node and the browser
625 lines (622 loc) • 22.7 kB
JavaScript
import { ColorType, EnumToken } from '../../ast/types.js';
import { color2RgbToken, lch2RgbToken, lab2RgbToken, oklch2RgbToken, oklab2RgbToken, cmyk2RgbToken, hwb2RgbToken, hsl2RgbToken, hex2RgbToken } from './rgb.js';
import { color2HslToken, lch2HslToken, lab2HslToken, oklch2HslToken, oklab2HslToken, cmyk2HslToken, hwb2HslToken, hex2HslToken, rgb2HslToken } from './hsl.js';
import { color2hwbToken, cmyk2hwbToken, lch2hwbToken, lab2hwbToken, oklch2hwbToken, oklab2hwbToken, hsl2hwbToken, rgb2hwbToken } from './hwb.js';
import { color2labToken, oklch2labToken, oklab2labToken, lch2labToken, cmyk2labToken, hwb2labToken, hsl2labToken, rgb2labToken, hex2labToken } from './lab.js';
import { color2lchToken, oklch2lchToken, oklab2lchToken, lab2lchToken, cmyk2lchToken, hwb2lchToken, hsl2lchToken, rgb2lchToken, hex2lchToken } from './lch.js';
import { color2oklabToken, oklch2oklabToken, lch2oklabToken, lab2oklabToken, cmyk2oklabToken, hwb2oklabToken, hsl2oklabToken, rgb2oklabToken, hex2oklabToken } from './oklab.js';
import { color2oklchToken, lch2oklchToken, oklab2oklchToken, lab2oklchToken, cmyk2oklchToken, hwb2oklchToken, hsl2oklchToken, rgb2oklchToken, hex2oklchToken } from './oklch.js';
import { getColorComponents } from './utils/components.js';
import { oklch2srgbvalues, lch2srgbvalues, oklab2srgbvalues, lab2srgbvalues, cmyk2srgbvalues, hwb2srgbvalues, hsl2srgb, rgb2srgb, hex2srgbvalues, xyz2srgb, lsrgb2srgbvalues, srgb2lsrgbvalues } from './srgb.js';
import { prophotorgb2srgbvalues, srgb2prophotorgbvalues } from './prophotorgb.js';
import { rec20202srgb, srgb2rec2020values } from './rec2020.js';
import { srgb2xyz_d65, srgb2xyz } from './xyz.js';
import { lp32srgbvalues, p32srgbvalues, srgb2p3values } from './p3.js';
import { xyzd502srgb } from './xyzd50.js';
import { colorMix } from './color-mix.js';
import { reduceHexValue, rgb2HexToken, color2HexToken, lch2HexToken, lab2HexToken, oklch2HexToken, oklab2HexToken, cmyk2HexToken, hwb2HexToken, hsl2HexToken } from './hex.js';
import { parseRelativeColorComponents } from './relative-color.js';
import { isIdentColor } from '../syntax.js';
import { color2cmykToken, lch2cmykToken, lab2cmykToken, oklch2cmykToken, oklab2cmyk, hwb2cmykToken, hsl2cmykToken, rgb2cmykToken } from './cmyk.js';
import { a98rgb2srgbvalues, srgb2a98values } from './a98rgb.js';
import { LOC, colorPrecision, colorFuncColorSpace, epsilon, anglePrecision } from '../constants.js';
import { trimArray } from '../../validation/match.js';
import { alpha } from './alpha.js';
import { equalsIgnoreCase } from '../../parser/utils/text.js';
/**
* Converts a color to another color space
* @param token
* @param to
*
* ```ts
*
* const token = {typ: EnumToken.ColorTokenType, kin: ColorType.HEX, val: '#F00'}
* const result = convertColor(token, ColorType.LCH);
*
* ```
*/
function convertColor(token, to) {
if (token.kin == ColorType.SYS ||
token.kin == ColorType.DPSYS ||
(isIdentColor(token) && "currentcolor" == token.val)) {
return token;
}
if (token.kin == ColorType.ALPHA) {
const args = token.chi.filter((token) => token.typ !== EnumToken.WhitespaceTokenType && token.typ !== EnumToken.CommentTokenType);
if (args.at(-2)?.typ === EnumToken.LiteralTokenType && "/" === args.at(-2)?.val) {
args.splice(args.length - 2, 1);
}
// @ts-expect-error
token = alpha(...trimArray(args.slice(1)));
if (token == null) {
return null;
}
}
if (token.kin == ColorType.COLOR_MIX && to != ColorType.COLOR_MIX) {
const children = token.chi.reduce((acc, t) => {
if (t.typ === EnumToken.CommaTokenType) {
acc.push([]);
}
else if (t.typ !== EnumToken.WhitespaceTokenType && t.typ !== EnumToken.CommentTokenType) {
acc[acc.length - 1].push(t);
}
return acc;
}, [[]]);
token = colorMix(...children.flat(2));
if (token == null) {
return null;
}
}
if (token.cal == "rel" &&
["rgb", "hsl", "hwb", "lab", "lch", "oklab", "oklch", "color"].some((t) => equalsIgnoreCase(t, token.val))) {
const chi = getColorComponents(token);
const offset = token.val == "color" ? 2 : 1;
if (chi != null) {
// @ts-ignore
const color = chi[1];
const components = parseRelativeColorComponents(equalsIgnoreCase(token.val, "color")
? chi[offset].val
: token.val, color, chi[offset + 1], chi[offset + 2], chi[offset + 3], chi[offset + 4]);
if (components == null) {
return null;
}
let { cal, ...tk } = {
...token,
chi: [...(token.val == "color" ? [chi[offset]] : []), ...Object.values(components)],
kin: ColorType[token.val.toUpperCase().replaceAll("-", "_")],
};
tk[LOC] = token[LOC];
token = tk;
}
}
if (token.kin == to) {
if (token.kin == ColorType.HEX || token.kin == ColorType.LIT) {
token.val = reduceHexValue(token.val);
token.kin = token.val[0] == "#" ? ColorType.HEX : ColorType.LIT;
}
return token;
}
if (token.kin == ColorType.COLOR) {
const colorSpace = token.chi.find((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(t.typ));
if (colorSpace.val == ColorType[to].toLowerCase().replaceAll("_", "-")) {
for (const chi of token.chi) {
if (chi.typ == EnumToken.NumberTokenType && typeof chi.val == "number") {
chi.val = toPrecisionValue(getNumber(chi));
}
}
return token;
}
}
if (to == ColorType.HSL) {
switch (token.kin) {
case ColorType.RGB:
case ColorType.RGBA:
return rgb2HslToken(token);
case ColorType.HEX:
case ColorType.LIT:
return hex2HslToken(token);
case ColorType.HWB:
return hwb2HslToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2HslToken(token);
case ColorType.OKLAB:
return oklab2HslToken(token);
case ColorType.OKLCH:
return oklch2HslToken(token);
case ColorType.LAB:
return lab2HslToken(token);
case ColorType.LCH:
return lch2HslToken(token);
case ColorType.COLOR:
return color2HslToken(token);
}
}
else if (to == ColorType.HWB) {
switch (token.kin) {
case ColorType.RGB:
case ColorType.RGBA:
return rgb2hwbToken(token);
case ColorType.HEX:
case ColorType.LIT:
return rgb2hwbToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2hwbToken(token);
case ColorType.OKLAB:
return oklab2hwbToken(token);
case ColorType.OKLCH:
return oklch2hwbToken(token);
case ColorType.LAB:
return lab2hwbToken(token);
case ColorType.LCH:
return lch2hwbToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2hwbToken(token);
case ColorType.COLOR:
return color2hwbToken(token);
}
}
else if (to == ColorType.DEVICE_CMYK) {
switch (token.kin) {
case ColorType.RGB:
case ColorType.RGBA:
return rgb2cmykToken(token);
case ColorType.HEX:
case ColorType.LIT:
return rgb2cmykToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2cmykToken(token);
case ColorType.HWB:
return hwb2cmykToken(token);
case ColorType.OKLAB:
return oklab2cmyk(token);
case ColorType.OKLCH:
return oklch2cmykToken(token);
case ColorType.LAB:
return lab2cmykToken(token);
case ColorType.LCH:
return lch2cmykToken(token);
case ColorType.COLOR:
return color2cmykToken(token);
}
}
else if (to == ColorType.HEX || to == ColorType.LIT) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT: {
const val = reduceHexValue(token.val);
return {
typ: EnumToken.ColorTokenType,
val: val,
kin: val[0] == "#" ? ColorType.HEX : ColorType.LIT,
};
}
case ColorType.HSL:
return hsl2HexToken(token);
case ColorType.HWB:
return hwb2HexToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2HexToken(token);
case ColorType.OKLAB:
return oklab2HexToken(token);
case ColorType.OKLCH:
return oklch2HexToken(token);
case ColorType.LAB:
return lab2HexToken(token);
case ColorType.LCH:
return lch2HexToken(token);
case ColorType.COLOR:
return color2HexToken(token);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2HexToken(token);
}
}
else if (to == ColorType.RGB) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2RgbToken(token);
case ColorType.HSL:
return hsl2RgbToken(token);
case ColorType.HWB:
return hwb2RgbToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2RgbToken(token);
case ColorType.OKLAB:
return oklab2RgbToken(token);
case ColorType.OKLCH:
return oklch2RgbToken(token);
case ColorType.LAB:
return lab2RgbToken(token);
case ColorType.LCH:
return lch2RgbToken(token);
case ColorType.COLOR:
return color2RgbToken(token);
}
}
else if (to == ColorType.LAB) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2labToken(token);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2labToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2labToken(token);
case ColorType.HWB:
return hwb2labToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2labToken(token);
case ColorType.LCH:
return lch2labToken(token);
case ColorType.OKLAB:
return oklab2labToken(token);
case ColorType.OKLCH:
return oklch2labToken(token);
case ColorType.COLOR:
return color2labToken(token);
}
}
else if (to == ColorType.LCH) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2lchToken(token);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2lchToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2lchToken(token);
case ColorType.HWB:
return hwb2lchToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2lchToken(token);
case ColorType.LAB:
return lab2lchToken(token);
case ColorType.OKLAB:
return oklab2lchToken(token);
case ColorType.OKLCH:
return oklch2lchToken(token);
case ColorType.COLOR:
return color2lchToken(token);
}
}
else if (to == ColorType.OKLAB) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2oklabToken(token);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2oklabToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2oklabToken(token);
case ColorType.HWB:
return hwb2oklabToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2oklabToken(token);
case ColorType.LAB:
return lab2oklabToken(token);
case ColorType.LCH:
return lch2oklabToken(token);
case ColorType.OKLCH:
return oklch2oklabToken(token);
case ColorType.COLOR:
return color2oklabToken(token);
}
}
else if (to == ColorType.OKLCH) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2oklchToken(token);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2oklchToken(token);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2oklchToken(token);
case ColorType.HWB:
return hwb2oklchToken(token);
case ColorType.DEVICE_CMYK:
return cmyk2oklchToken(token);
case ColorType.LAB:
return lab2oklchToken(token);
case ColorType.OKLAB:
return oklab2oklchToken(token);
case ColorType.LCH:
return lch2oklchToken(token);
case ColorType.COLOR:
return color2oklchToken(token);
}
}
else if (colorFuncColorSpace.includes(ColorType[to].toLowerCase().replaceAll("_", "-").toLowerCase().replaceAll("_", "-"))) {
switch (token.kin) {
case ColorType.HEX:
case ColorType.LIT:
return hex2colorToken(token, to);
case ColorType.RGB:
case ColorType.RGBA:
return rgb2colorToken(token, to);
case ColorType.HSL:
case ColorType.HSLA:
return hsl2colorToken(token, to);
case ColorType.HWB:
return hwb2colorToken(token, to);
case ColorType.DEVICE_CMYK:
return cmyk2colorToken(token, to);
case ColorType.LAB:
return lab2colorToken(token, to);
case ColorType.OKLAB:
return oklab2colorToken(token, to);
case ColorType.LCH:
return lch2colorToken(token, to);
case ColorType.OKLCH:
return oklch2colorToken(token, to);
case ColorType.COLOR:
return color2colorToken(token, to);
}
}
return null;
}
function hex2colorToken(token, to) {
const values = hex2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function rgb2colorToken(token, to) {
const values = rgb2srgb(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function hsl2colorToken(token, to) {
const values = hsl2srgb(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function hwb2colorToken(token, to) {
const values = hwb2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function cmyk2colorToken(token, to) {
const values = cmyk2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function lab2colorToken(token, to) {
const values = lab2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function oklab2colorToken(token, to) {
const values = oklab2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function lch2colorToken(token, to) {
const values = lch2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function oklch2colorToken(token, to) {
const values = oklch2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function color2colorToken(token, to) {
const values = color2srgbvalues(token);
if (values == null) {
return null;
}
return values2colortoken(values, to);
}
function srgb2srgbcolorspace(val, to) {
const values = [];
switch (to) {
case ColorType.SRGB:
values.push(...val);
break;
case ColorType.SRGB_LINEAR:
// @ts-ignore
values.push(...srgb2lsrgbvalues(...val));
break;
case ColorType.DISPLAY_P3:
// @ts-ignore
values.push(...srgb2p3values(...val));
break;
case ColorType.DISPLAY_P3_LINEAR:
// @ts-ignore
values.push(...srgb2lp3values(...val));
break;
case ColorType.PROPHOTO_RGB:
// @ts-ignore
values.push(...srgb2prophotorgbvalues(...val));
break;
case ColorType.A98_RGB:
// @ts-ignore
values.push(...srgb2a98values(...val));
break;
case ColorType.REC2020:
// @ts-ignore
values.push(...srgb2rec2020values(...val));
break;
case ColorType.XYZ:
case ColorType.XYZ_D65:
// @ts-ignore
values.push(...srgb2xyz(...val));
break;
case ColorType.XYZ_D50:
// @ts-ignore
values.push(...srgb2xyz_d65(...val));
break;
}
return values;
}
function minmax(value, min, max) {
return value < min ? min : value > max ? max : value;
}
function color2srgbvalues(token) {
const components = getColorComponents(token);
if (components == null) {
return null;
}
const colorSpace = components.shift();
let values = components.map((val) => getNumber(val));
switch (colorSpace.val) {
case "display-p3":
// @ts-ignore
values = p32srgbvalues(...values);
break;
case "display-p3-linear":
// @ts-ignore
values = lp32srgbvalues(...values);
break;
case "srgb-linear":
// @ts-ignore
values = lsrgb2srgbvalues(...values);
break;
case "prophoto-rgb":
// @ts-ignore
values = prophotorgb2srgbvalues(...values);
break;
case "a98-rgb":
// @ts-ignore
values = a98rgb2srgbvalues(...values);
break;
case "rec2020":
// @ts-ignore
values = rec20202srgb(...values);
break;
case "xyz":
case "xyz-d65":
// @ts-ignore
values = xyz2srgb(...values);
break;
case "xyz-d50":
// @ts-ignore
values = xyzd502srgb(...values);
break;
}
if (values.length == 4) {
values[3] = toPrecisionValue(values[3], 2);
}
return values;
}
function values2colortoken(values, to) {
values = srgb2srgbcolorspace(values, to);
const chi = [
{ typ: EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) },
{ typ: EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) },
{ typ: EnumToken.NumberTokenType, val: toPrecisionValue(values[2]) },
];
if (values.length == 4) {
chi.push({ typ: EnumToken.LiteralTokenType, val: "/" }, {
typ: EnumToken.PercentageTokenType,
val: toPrecisionValue(values[3], 2) * 100,
});
}
const colorSpace = ColorType[to].toLowerCase().replaceAll("_", "-");
return colorFuncColorSpace.includes(colorSpace)
? {
typ: EnumToken.ColorTokenType,
val: "color",
chi: [{ typ: EnumToken.IdenTokenType, val: colorSpace }].concat(chi),
kin: ColorType.COLOR,
}
: {
typ: EnumToken.ColorTokenType,
val: colorSpace,
chi,
kin: to,
};
}
function getNumber(token) {
if (token.typ == EnumToken.IdenTokenType && token.val == "none") {
return 0;
}
let val;
// @ts-ignore
if (typeof token.val != "number" && token.val?.typ == EnumToken.FractionTokenType) {
val = (token.val.l.val /
// @ts-expect-error
token.val.r.val);
}
else {
val = token.val;
}
// @ts-ignore
return token.typ == EnumToken.PercentageTokenType ? val / 100 : val;
}
/**
* convert angle to turn
* @param token
*/
function getAngle(token) {
if (token.typ == EnumToken.IdenTokenType) {
if (token.val == "none") {
return 0;
}
}
if (token.typ == EnumToken.AngleTokenType) {
switch (token.unit) {
case "deg":
// @ts-ignore
return token.val / 360;
case "rad":
// @ts-ignore
return token.val / (2 * Math.PI);
case "grad":
// @ts-ignore
return token.val / 400;
case "turn":
// @ts-ignore
return +token.val;
}
}
// @ts-ignore
return token.val / 360;
}
function toPrecisionValue(value, precision = colorPrecision) {
const div = Math.pow(10, precision);
value = Math.round(value * div) / div;
return Math.abs(value) < epsilon ? 0 : value;
}
function toPrecisionAngle(angle, precision = colorPrecision, correctValue = true) {
angle = toPrecisionValue(angle, precision);
if (correctValue && Math.abs(angle) >= 360) {
angle %= 360;
}
if (Math.abs(angle) < anglePrecision) {
angle = 0;
}
if (correctValue && angle < 0) {
angle += 360;
}
return angle;
}
export { cmyk2colorToken, color2colorToken, color2srgbvalues, convertColor, getAngle, getNumber, hex2colorToken, hsl2colorToken, hwb2colorToken, lab2colorToken, lch2colorToken, minmax, oklab2colorToken, oklch2colorToken, rgb2colorToken, toPrecisionAngle, toPrecisionValue };