diff --git a/dist/config.json.js b/dist/config.json.js index c6e7fe4..06c23a1 100644 --- a/dist/config.json.js +++ b/dist/config.json.js @@ -924,6 +924,8 @@ var map = { "overflow-x": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", @@ -935,6 +937,8 @@ var map = { "overflow-y": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index cf9994b..e041e17 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -156,15 +156,67 @@ } function roundWithPrecision(value, original) { - const length = original.toString().split('.')[1]?.length ?? 0; - if (length == 0) { - return value; - } - return +value.toFixed(length); + // const length: number = original.toString().split('.')[1]?.length ?? 0; + // if (length == 0) { + return value; + // } + // + // return +value.toFixed(length); } const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; + function getComponents(token) { + return token.chi + .filter((t) => ![exports.EnumToken.LiteralTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType].includes(t.typ)); + } + + function XYZ_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB( + /* r: */ + x * 3.1341359569958707 - + y * 1.6173863321612538 - + 0.4906619460083532 * z, + /* g: */ + x * -0.978795502912089 + + y * 1.916254567259524 + + 0.03344273116131949 * z, + /* b: */ + x * 0.07195537988411677 - + y * 0.2289768264158322 + + 1.405386058324125 * z); + } + + // L: 0% = 0.0, 100% = 100.0 + // for a and b: -100% = -125, 100% = 125 + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + // D50 LAB + function Lab_to_sRGB(l, a, b) { + // @ts-ignore + return XYZ_to_sRGB(...Lab_to_XYZ(l, a, b)); + } + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + function Lab_to_XYZ(l, a, b) { + // Convert Lab to D50-adapted XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const k = 24389 / 27; // 29^3/3^3 + const e = 216 / 24389; // 6^3/29^3 + const f = []; + // compute f, starting with the luminance-related term + f[1] = (l + 16) / 116; + f[0] = a / 500 + f[1]; + f[2] = f[1] - b / 200; + // compute xyz + const xyz = [ + Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, + l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, + Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k + ]; + // Compute XYZ by scaling xyz by reference white + return xyz.map((value, i) => value * D50[i]); + } + // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 @@ -175,13 +227,12 @@ // Extended transfer function: // For negative values, linear portion extends on reflection // of axis, then uses reflected pow below that - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; + return [r, g, b].map((val) => { let abs = Math.abs(val); - if (abs > 0.0031308) { - return roundWithPrecision(sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055), val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); } - return roundWithPrecision(12.92 * val, val); + return 12.92 * val; }); } // export function gam_a98rgb(r: number, g: number, b: number): number[] { @@ -206,9 +257,9 @@ let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs <= Et2) { - return roundWithPrecision(val / 16, val); + return roundWithPrecision(val / 16); } - return roundWithPrecision(sign * Math.pow(abs, 1.8), val); + return roundWithPrecision(sign * Math.pow(abs, 1.8)); }); } function lin_a98rgb(r, g, b) { @@ -218,7 +269,7 @@ return [r, g, b].map(function (val) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); - return roundWithPrecision(sign * Math.pow(abs, 563 / 256), val); + return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); }); } function lin_2020(r, g, b) { @@ -231,89 +282,117 @@ let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs < β * 4.5) { - return roundWithPrecision(val / 4.5, val); + return roundWithPrecision(val / 4.5); } - return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45)), val); + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); }); } - function XYZ_to_sRGB(x, y, z) { - // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); - } - function XYZ_to_lin_sRGB(x, y, z) { - // convert XYZ to linear-light sRGB - const M = [ - [12831 / 3959, -329 / 214, -1974 / 3959], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [705 / 12673, -2585 / 12673, 705 / 667], - ]; - const XYZ = [x, y, z]; // convert to XYZ - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); - } - function XYZ_D50_to_sRGB(x, y, z) { - // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); - } - function D50_to_D65(x, y, z) { - // Bradford chromatic adaptation from D50 to D65 - const M = [ - [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], - [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], - [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] - ]; - const XYZ = [x, y, z]; - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); - } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_sRGB(l, a, b) { + // console.error({l, a, b}); + // console.error({l, a, b}); // @ts-ignore - return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); - } - function OKLab_to_XYZ(l, a, b) { - // Given OKLab, convert to XYZ relative to D65 - const LMStoXYZ = [ - [1.2268798733741557, -0.5578149965554813, 0.28139105017721583], - [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], - [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418] - ]; - const OKLabtoLMS = [ - [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339], - [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402], - [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399] - ]; - const LMSnl = multiplyMatrices(OKLabtoLMS, [l, a, b]); - return multiplyMatrices(LMStoXYZ, LMSnl.map((c) => c ** 3)); + // return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); + let L = Math.pow(l * 0.99999999845051981432 + + 0.39633779217376785678 * a + + 0.21580375806075880339 * b, 3); + let M = Math.pow(l * 1.0000000088817607767 - + 0.1055613423236563494 * a - + 0.063854174771705903402 * b, 3); + let S = Math.pow(l * 1.0000000546724109177 - + 0.089484182094965759684 * a - + 1.2914855378640917399 * b, 3); + return gam_sRGB( + /* r: */ + +4.076741661347994 * L - + 3.307711590408193 * M + + 0.230969928729428 * S, + /* g: */ + -1.2684380040921763 * L + + 2.6097574006633715 * M - + 0.3413193963102197 * S, + /* b: */ + -0.004196086541837188 * L - + 0.7034186144594493 * M + + 1.7076147009309444 * S); } - // L: 0% = 0.0, 100% = 100.0 - // for a and b: -100% = -125, 100% = 125 - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - // D50 LAB - function Lab_to_sRGB(l, a, b) { + function toHexString(acc, value) { + return acc + value.toString(16).padStart(2, '0'); + } + function reduceHexValue(value) { + const named_color = NAMES_COLORS[expandHexValue(value)]; + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; + } + function expandHexValue(value) { + if (value.length == 4) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; + } + if (value.length == 5) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; + } + return value; + } + function rgb2hex(token) { + let value = '#'; + let t; // @ts-ignore - return XYZ_to_sRGB(...Lab_to_XYZ(l, a, b)); + for (let i = 0; i < 3; i++) { + // @ts-ignore + t = token.chi[i]; + // @ts-ignore + value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + // @ts-ignore + if (token.chi.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || + (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || + (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { + // @ts-ignore + value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); + } + } + return value; } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - function Lab_to_XYZ(l, a, b) { - // Convert Lab to D50-adapted XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - const k = 24389 / 27; // 29^3/3^3 - const e = 216 / 24389; // 6^3/29^3 - const f = []; - // compute f, starting with the luminance-related term - f[1] = (l + 16) / 116; - f[0] = a / 500 + f[1]; - f[2] = f[1] - b / 200; - // compute xyz - const xyz = [ - Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, - l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, - Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k - ]; - // Compute XYZ by scaling xyz by reference white - return xyz.map((value, i) => value * D50[i]); + function hsl2hex(token) { + return `${hsl2rgb(token).reduce(toHexString, '#')}`; + } + function hwb2hex(token) { + return `${hwb2rgb(token).reduce(toHexString, '#')}`; + } + function cmyk2hex(token) { + return `#${cmyk2rgb(token).reduce(toHexString, '')}`; + } + function oklab2hex(token) { + return `${oklab2rgb(token).reduce(toHexString, '#')}`; + } + function oklch2hex(token) { + return `${oklch2rgb(token).reduce(toHexString, '#')}`; + } + function lab2hex(token) { + return `${lab2rgb(token).reduce(toHexString, '#')}`; + } + function lch2hex(token) { + return `${lch2rgb(token).reduce(toHexString, '#')}`; } function hwb2rgb(token) { @@ -333,20 +412,21 @@ return hsl2rgbvalues(h, s, l, a); } function cmyk2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const c = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const m = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const y = getNumber(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const k = getNumber(t); const rgb = [ @@ -364,20 +444,21 @@ return rgb; } function oklab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); const rgb = OKLab_to_sRGB(l, a, b).map(v => { @@ -389,90 +470,94 @@ return rgb.map(((value) => minmax(value, 0, 255))); } function oklch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)).map((v) => Math.round(255 * v)); + const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); } function lab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); } function lch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch const a = c * Math.cos(360 * h * Math.PI / 180); const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(value * 255, 0, 255))); } function hslvalues(token) { + const components = getComponents(token); let t; // @ts-ignore - let h = getAngle(token.chi[0]); + let h = getAngle(components[0]); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore let s = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore let l = getNumber(t); let a = null; @@ -487,7 +572,7 @@ a = getNumber(t); } } - return { h, s, l, a }; + return a == null ? { h, s, l } : { h, s, l, a }; } function hsl2rgbvalues(h, s, l, a = null) { let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; @@ -543,83 +628,6 @@ return values; } - function toHexString(acc, value) { - return acc + value.toString(16).padStart(2, '0'); - } - function reduceHexValue(value) { - const named_color = NAMES_COLORS[expandHexValue(value)]; - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; - } - function expandHexValue(value) { - if (value.length == 4) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; - } - if (value.length == 5) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; - } - return value; - } - function rgb2hex(token) { - let value = '#'; - let t; - // @ts-ignore - for (let i = 0; i < 3; i++) { - // @ts-ignore - t = token.chi[i]; - // @ts-ignore - value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); - } - // @ts-ignore - if (token.chi.length == 4) { - // @ts-ignore - t = token.chi[3]; - // @ts-ignore - if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || - (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || - (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { - // @ts-ignore - value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); - } - } - return value; - } - function hsl2hex(token) { - return `${hsl2rgb(token).reduce(toHexString, '#')}`; - } - function hwb2hex(token) { - return `${hwb2rgb(token).reduce(toHexString, '#')}`; - } - function cmyk2hex(token) { - return `#${cmyk2rgb(token).reduce(toHexString, '')}`; - } - function oklab2hex(token) { - return `${oklab2rgb(token).reduce(toHexString, '#')}`; - } - function oklch2hex(token) { - return `${oklch2rgb(token).reduce(toHexString, '#')}`; - } - function lab2hex(token) { - return `${lab2rgb(token).reduce(toHexString, '#')}`; - } - function lch2hex(token) { - return `${lch2rgb(token).reduce(toHexString, '#')}`; - } - // name to color const COLORS_NAMES = Object.seal({ 'aliceblue': '#f0f8ff', @@ -894,8 +902,9 @@ return token.val / 360; } - function hwb2hsv(h, w, b) { - return [h, 1 - w / (1 - b), 1 - b]; + function hwb2hsv(h, w, b, a) { + // @ts-ignore + return [h, 1 - w / (1 - b), 1 - b, a]; } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js function hsl2hsv(h, s, l) { @@ -955,7 +964,52 @@ return hsv2hwb(...hsl2hsv(h, s, l)); } - function rgb2hsl(r, g, b, a) { + function rgb2hsl(token) { + const chi = getComponents(token); + // @ts-ignore + let t = chi[0]; + // @ts-ignore + let r = getNumber(t); + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g = getNumber(t); + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b = getNumber(t); + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a = null; + if (t != null) { + // @ts-ignore + a = getNumber(t) / 255; + } + return rgb2hslvalues(r, g, b, a); + } + // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js + function hsv2hsl(h, s, v, a) { + return [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + h / 2, //Lightness is (2-sat)*val/2 + //See reassignment of hue above, + // @ts-ignore + a + ]; + } + function hwb2hsl(token) { + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); + } + function rgb2hslvalues(r, g, b, a = null) { r /= 255; g /= 255; b /= 255; @@ -980,25 +1034,13 @@ } h /= 6; } - return [h, s, l, a == 1 ? null : a ?? null]; - } - // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js - function hsv2hsl(h, s, v) { - return [ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), - h / 2 //Lightness is (2-sat)*val/2 - //See reassignment of hue above - ]; - } - function hwb2hsl(h, w, b) { - return hsv2hsl(...hwb2hsv(h, w, b)); + const hsl = [h, s, l]; + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]); + } + // @ts-ignore + return hsl; } function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { @@ -1597,6 +1639,25 @@ return expr; } + function XYZ_D65_to_sRGB(x, y, z) { + // @ts-ignore + return XYZ_to_sRGB(...XYZ_D65_to_D50(x, y, z)); + } + function XYZ_D65_to_D50(x, y, z) { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // see https://github.com/LeaVerou/color.js/pull/354/files + var M = [ + [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], + [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], + [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371] + ]; + return multiplyMatrices(M, [x, y, z]); + } + // from https://github.com/Rich-Harris/vlq/tree/master // credit: Rich Harris const integer_to_char = {}; @@ -1973,11 +2034,11 @@ case 'xyz': case 'xyz-d65': // @ts-ignore - values = XYZ_to_sRGB(...values); + values = XYZ_D65_to_sRGB(...values); break; case 'xyz-d50': // @ts-ignore - values = XYZ_D50_to_sRGB(...values); + values = XYZ_to_sRGB(...values); break; } clampValues(values, colorSpace); @@ -2008,7 +2069,6 @@ } } else if (token.cal == 'mix' && token.val == 'color-mix') { - // console.debug(JSON.stringify({token}, null, 1)); const children = token.chi.reduce((acc, t) => { if (t.typ == exports.EnumToken.ColorTokenType) { acc.push([t]); @@ -2022,7 +2082,6 @@ }, [[]]); const value = colorMix(children[0][1], children[0][2], children[1][0], children[1][1], children[2][0], children[2][1]); if (value != null) { - // console.debug(JSON.stringify(value, null, 1)); token = value; } } @@ -2415,7 +2474,6 @@ } return true; } - console.debug(JSON.stringify({ children }, null, 1)); return false; } else { @@ -3606,6 +3664,8 @@ "overflow-x": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", @@ -3617,6 +3677,8 @@ "overflow-y": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", @@ -4169,7 +4231,7 @@ } if (val.typ == exports.EnumToken.FunctionTokenType) { if (funcList.includes(val.val)) { - return val.chi.every((t => [exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.StartParensTokenType, exports.EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); + return val.chi.every(((t) => [exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.StartParensTokenType, exports.EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); } // match type defined like function 'symbols()', 'url()', 'attr()' etc. // return properties.types.includes((val).val + '()') diff --git a/dist/index.cjs b/dist/index.cjs index 19211fd..e07dc2a 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -154,15 +154,67 @@ function multiplyMatrices(A, B) { } function roundWithPrecision(value, original) { - const length = original.toString().split('.')[1]?.length ?? 0; - if (length == 0) { - return value; - } - return +value.toFixed(length); + // const length: number = original.toString().split('.')[1]?.length ?? 0; + // if (length == 0) { + return value; + // } + // + // return +value.toFixed(length); } const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; +function getComponents(token) { + return token.chi + .filter((t) => ![exports.EnumToken.LiteralTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType].includes(t.typ)); +} + +function XYZ_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB( + /* r: */ + x * 3.1341359569958707 - + y * 1.6173863321612538 - + 0.4906619460083532 * z, + /* g: */ + x * -0.978795502912089 + + y * 1.916254567259524 + + 0.03344273116131949 * z, + /* b: */ + x * 0.07195537988411677 - + y * 0.2289768264158322 + + 1.405386058324125 * z); +} + +// L: 0% = 0.0, 100% = 100.0 +// for a and b: -100% = -125, 100% = 125 +// from https://www.w3.org/TR/css-color-4/#color-conversion-code +// D50 LAB +function Lab_to_sRGB(l, a, b) { + // @ts-ignore + return XYZ_to_sRGB(...Lab_to_XYZ(l, a, b)); +} +// from https://www.w3.org/TR/css-color-4/#color-conversion-code +function Lab_to_XYZ(l, a, b) { + // Convert Lab to D50-adapted XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const k = 24389 / 27; // 29^3/3^3 + const e = 216 / 24389; // 6^3/29^3 + const f = []; + // compute f, starting with the luminance-related term + f[1] = (l + 16) / 116; + f[0] = a / 500 + f[1]; + f[2] = f[1] - b / 200; + // compute xyz + const xyz = [ + Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, + l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, + Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k + ]; + // Compute XYZ by scaling xyz by reference white + return xyz.map((value, i) => value * D50[i]); +} + // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 @@ -173,13 +225,12 @@ function gam_sRGB(r, g, b) { // Extended transfer function: // For negative values, linear portion extends on reflection // of axis, then uses reflected pow below that - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; + return [r, g, b].map((val) => { let abs = Math.abs(val); - if (abs > 0.0031308) { - return roundWithPrecision(sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055), val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); } - return roundWithPrecision(12.92 * val, val); + return 12.92 * val; }); } // export function gam_a98rgb(r: number, g: number, b: number): number[] { @@ -204,9 +255,9 @@ function lin_ProPhoto(r, g, b) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs <= Et2) { - return roundWithPrecision(val / 16, val); + return roundWithPrecision(val / 16); } - return roundWithPrecision(sign * Math.pow(abs, 1.8), val); + return roundWithPrecision(sign * Math.pow(abs, 1.8)); }); } function lin_a98rgb(r, g, b) { @@ -216,7 +267,7 @@ function lin_a98rgb(r, g, b) { return [r, g, b].map(function (val) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); - return roundWithPrecision(sign * Math.pow(abs, 563 / 256), val); + return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); }); } function lin_2020(r, g, b) { @@ -229,89 +280,117 @@ function lin_2020(r, g, b) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs < β * 4.5) { - return roundWithPrecision(val / 4.5, val); + return roundWithPrecision(val / 4.5); } - return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45)), val); + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); }); } -function XYZ_to_sRGB(x, y, z) { - // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); -} -function XYZ_to_lin_sRGB(x, y, z) { - // convert XYZ to linear-light sRGB - const M = [ - [12831 / 3959, -329 / 214, -1974 / 3959], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [705 / 12673, -2585 / 12673, 705 / 667], - ]; - const XYZ = [x, y, z]; // convert to XYZ - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); -} -function XYZ_D50_to_sRGB(x, y, z) { - // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); -} -function D50_to_D65(x, y, z) { - // Bradford chromatic adaptation from D50 to D65 - const M = [ - [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], - [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], - [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] - ]; - const XYZ = [x, y, z]; - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); -} - // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_sRGB(l, a, b) { + // console.error({l, a, b}); + // console.error({l, a, b}); // @ts-ignore - return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); -} -function OKLab_to_XYZ(l, a, b) { - // Given OKLab, convert to XYZ relative to D65 - const LMStoXYZ = [ - [1.2268798733741557, -0.5578149965554813, 0.28139105017721583], - [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], - [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418] - ]; - const OKLabtoLMS = [ - [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339], - [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402], - [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399] - ]; - const LMSnl = multiplyMatrices(OKLabtoLMS, [l, a, b]); - return multiplyMatrices(LMStoXYZ, LMSnl.map((c) => c ** 3)); + // return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); + let L = Math.pow(l * 0.99999999845051981432 + + 0.39633779217376785678 * a + + 0.21580375806075880339 * b, 3); + let M = Math.pow(l * 1.0000000088817607767 - + 0.1055613423236563494 * a - + 0.063854174771705903402 * b, 3); + let S = Math.pow(l * 1.0000000546724109177 - + 0.089484182094965759684 * a - + 1.2914855378640917399 * b, 3); + return gam_sRGB( + /* r: */ + +4.076741661347994 * L - + 3.307711590408193 * M + + 0.230969928729428 * S, + /* g: */ + -1.2684380040921763 * L + + 2.6097574006633715 * M - + 0.3413193963102197 * S, + /* b: */ + -0.004196086541837188 * L - + 0.7034186144594493 * M + + 1.7076147009309444 * S); } -// L: 0% = 0.0, 100% = 100.0 -// for a and b: -100% = -125, 100% = 125 -// from https://www.w3.org/TR/css-color-4/#color-conversion-code -// D50 LAB -function Lab_to_sRGB(l, a, b) { +function toHexString(acc, value) { + return acc + value.toString(16).padStart(2, '0'); +} +function reduceHexValue(value) { + const named_color = NAMES_COLORS[expandHexValue(value)]; + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; +} +function expandHexValue(value) { + if (value.length == 4) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; + } + if (value.length == 5) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; + } + return value; +} +function rgb2hex(token) { + let value = '#'; + let t; // @ts-ignore - return XYZ_to_sRGB(...Lab_to_XYZ(l, a, b)); + for (let i = 0; i < 3; i++) { + // @ts-ignore + t = token.chi[i]; + // @ts-ignore + value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + // @ts-ignore + if (token.chi.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || + (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || + (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { + // @ts-ignore + value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); + } + } + return value; } -// from https://www.w3.org/TR/css-color-4/#color-conversion-code -function Lab_to_XYZ(l, a, b) { - // Convert Lab to D50-adapted XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - const k = 24389 / 27; // 29^3/3^3 - const e = 216 / 24389; // 6^3/29^3 - const f = []; - // compute f, starting with the luminance-related term - f[1] = (l + 16) / 116; - f[0] = a / 500 + f[1]; - f[2] = f[1] - b / 200; - // compute xyz - const xyz = [ - Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, - l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, - Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k - ]; - // Compute XYZ by scaling xyz by reference white - return xyz.map((value, i) => value * D50[i]); +function hsl2hex(token) { + return `${hsl2rgb(token).reduce(toHexString, '#')}`; +} +function hwb2hex(token) { + return `${hwb2rgb(token).reduce(toHexString, '#')}`; +} +function cmyk2hex(token) { + return `#${cmyk2rgb(token).reduce(toHexString, '')}`; +} +function oklab2hex(token) { + return `${oklab2rgb(token).reduce(toHexString, '#')}`; +} +function oklch2hex(token) { + return `${oklch2rgb(token).reduce(toHexString, '#')}`; +} +function lab2hex(token) { + return `${lab2rgb(token).reduce(toHexString, '#')}`; +} +function lch2hex(token) { + return `${lch2rgb(token).reduce(toHexString, '#')}`; } function hwb2rgb(token) { @@ -331,20 +410,21 @@ function hsl2rgb(token) { return hsl2rgbvalues(h, s, l, a); } function cmyk2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const c = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const m = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const y = getNumber(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const k = getNumber(t); const rgb = [ @@ -362,20 +442,21 @@ function cmyk2rgb(token) { return rgb; } function oklab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); const rgb = OKLab_to_sRGB(l, a, b).map(v => { @@ -387,90 +468,94 @@ function oklab2rgb(token) { return rgb.map(((value) => minmax(value, 0, 255))); } function oklch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)).map((v) => Math.round(255 * v)); + const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); } function lab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); } function lch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch const a = c * Math.cos(360 * h * Math.PI / 180); const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(value * 255, 0, 255))); } function hslvalues(token) { + const components = getComponents(token); let t; // @ts-ignore - let h = getAngle(token.chi[0]); + let h = getAngle(components[0]); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore let s = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore let l = getNumber(t); let a = null; @@ -485,7 +570,7 @@ function hslvalues(token) { a = getNumber(t); } } - return { h, s, l, a }; + return a == null ? { h, s, l } : { h, s, l, a }; } function hsl2rgbvalues(h, s, l, a = null) { let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; @@ -541,83 +626,6 @@ function hsl2rgbvalues(h, s, l, a = null) { return values; } -function toHexString(acc, value) { - return acc + value.toString(16).padStart(2, '0'); -} -function reduceHexValue(value) { - const named_color = NAMES_COLORS[expandHexValue(value)]; - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; -} -function expandHexValue(value) { - if (value.length == 4) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; - } - if (value.length == 5) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; - } - return value; -} -function rgb2hex(token) { - let value = '#'; - let t; - // @ts-ignore - for (let i = 0; i < 3; i++) { - // @ts-ignore - t = token.chi[i]; - // @ts-ignore - value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); - } - // @ts-ignore - if (token.chi.length == 4) { - // @ts-ignore - t = token.chi[3]; - // @ts-ignore - if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || - (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || - (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { - // @ts-ignore - value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); - } - } - return value; -} -function hsl2hex(token) { - return `${hsl2rgb(token).reduce(toHexString, '#')}`; -} -function hwb2hex(token) { - return `${hwb2rgb(token).reduce(toHexString, '#')}`; -} -function cmyk2hex(token) { - return `#${cmyk2rgb(token).reduce(toHexString, '')}`; -} -function oklab2hex(token) { - return `${oklab2rgb(token).reduce(toHexString, '#')}`; -} -function oklch2hex(token) { - return `${oklch2rgb(token).reduce(toHexString, '#')}`; -} -function lab2hex(token) { - return `${lab2rgb(token).reduce(toHexString, '#')}`; -} -function lch2hex(token) { - return `${lch2rgb(token).reduce(toHexString, '#')}`; -} - // name to color const COLORS_NAMES = Object.seal({ 'aliceblue': '#f0f8ff', @@ -892,8 +900,9 @@ function getAngle(token) { return token.val / 360; } -function hwb2hsv(h, w, b) { - return [h, 1 - w / (1 - b), 1 - b]; +function hwb2hsv(h, w, b, a) { + // @ts-ignore + return [h, 1 - w / (1 - b), 1 - b, a]; } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js function hsl2hsv(h, s, l) { @@ -953,7 +962,52 @@ function hsl2hwb(h, s, l) { return hsv2hwb(...hsl2hsv(h, s, l)); } -function rgb2hsl(r, g, b, a) { +function rgb2hsl(token) { + const chi = getComponents(token); + // @ts-ignore + let t = chi[0]; + // @ts-ignore + let r = getNumber(t); + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g = getNumber(t); + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b = getNumber(t); + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a = null; + if (t != null) { + // @ts-ignore + a = getNumber(t) / 255; + } + return rgb2hslvalues(r, g, b, a); +} +// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js +function hsv2hsl(h, s, v, a) { + return [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + h / 2, //Lightness is (2-sat)*val/2 + //See reassignment of hue above, + // @ts-ignore + a + ]; +} +function hwb2hsl(token) { + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); +} +function rgb2hslvalues(r, g, b, a = null) { r /= 255; g /= 255; b /= 255; @@ -978,25 +1032,13 @@ function rgb2hsl(r, g, b, a) { } h /= 6; } - return [h, s, l, a == 1 ? null : a ?? null]; -} -// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -function hsv2hsl(h, s, v) { - return [ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), - h / 2 //Lightness is (2-sat)*val/2 - //See reassignment of hue above - ]; -} -function hwb2hsl(h, w, b) { - return hsv2hsl(...hwb2hsv(h, w, b)); + const hsl = [h, s, l]; + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]); + } + // @ts-ignore + return hsl; } function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { @@ -1595,6 +1637,25 @@ function computeComponentValue(expr, values) { return expr; } +function XYZ_D65_to_sRGB(x, y, z) { + // @ts-ignore + return XYZ_to_sRGB(...XYZ_D65_to_D50(x, y, z)); +} +function XYZ_D65_to_D50(x, y, z) { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // see https://github.com/LeaVerou/color.js/pull/354/files + var M = [ + [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], + [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], + [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371] + ]; + return multiplyMatrices(M, [x, y, z]); +} + // from https://github.com/Rich-Harris/vlq/tree/master // credit: Rich Harris const integer_to_char = {}; @@ -1971,11 +2032,11 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, case 'xyz': case 'xyz-d65': // @ts-ignore - values = XYZ_to_sRGB(...values); + values = XYZ_D65_to_sRGB(...values); break; case 'xyz-d50': // @ts-ignore - values = XYZ_D50_to_sRGB(...values); + values = XYZ_to_sRGB(...values); break; } clampValues(values, colorSpace); @@ -2006,7 +2067,6 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, } } else if (token.cal == 'mix' && token.val == 'color-mix') { - // console.debug(JSON.stringify({token}, null, 1)); const children = token.chi.reduce((acc, t) => { if (t.typ == exports.EnumToken.ColorTokenType) { acc.push([t]); @@ -2020,7 +2080,6 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, }, [[]]); const value = colorMix(children[0][1], children[0][2], children[1][0], children[1][1], children[2][0], children[2][1]); if (value != null) { - // console.debug(JSON.stringify(value, null, 1)); token = value; } } @@ -2413,7 +2472,6 @@ function isColor(token) { } return true; } - console.debug(JSON.stringify({ children }, null, 1)); return false; } else { @@ -3604,6 +3662,8 @@ var map = { "overflow-x": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", @@ -3615,6 +3675,8 @@ var map = { "overflow-y": { "default": [ ], + types: [ + ], keywords: [ "auto", "visible", @@ -4167,7 +4229,7 @@ function matchType(val, properties) { } if (val.typ == exports.EnumToken.FunctionTokenType) { if (funcList.includes(val.val)) { - return val.chi.every((t => [exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.StartParensTokenType, exports.EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); + return val.chi.every(((t) => [exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.StartParensTokenType, exports.EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); } // match type defined like function 'symbols()', 'url()', 'attr()' etc. // return properties.types.includes((val).val + '()') diff --git a/dist/lib/parser/utils/syntax.js b/dist/lib/parser/utils/syntax.js index bc852f1..f10c558 100644 --- a/dist/lib/parser/utils/syntax.js +++ b/dist/lib/parser/utils/syntax.js @@ -128,7 +128,6 @@ function isColor(token) { } return true; } - console.debug(JSON.stringify({ children }, null, 1)); return false; } else { diff --git a/dist/lib/parser/utils/type.js b/dist/lib/parser/utils/type.js index 0f64d00..0a9d4b4 100644 --- a/dist/lib/parser/utils/type.js +++ b/dist/lib/parser/utils/type.js @@ -22,7 +22,7 @@ function matchType(val, properties) { } if (val.typ == EnumToken.FunctionTokenType) { if (funcList.includes(val.val)) { - return val.chi.every((t => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); + return val.chi.every(((t) => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); } // match type defined like function 'symbols()', 'url()', 'attr()' etc. // return properties.types.includes((val).val + '()') diff --git a/dist/lib/renderer/color/hsl.js b/dist/lib/renderer/color/hsl.js index 9162b7d..f096fee 100644 --- a/dist/lib/renderer/color/hsl.js +++ b/dist/lib/renderer/color/hsl.js @@ -1,6 +1,54 @@ import { hwb2hsv } from './hsv.js'; +import { getNumber } from './color.js'; +import { hslvalues } from './rgb.js'; +import { getComponents } from './utils/components.js'; -function rgb2hsl(r, g, b, a) { +function rgb2hsl(token) { + const chi = getComponents(token); + // @ts-ignore + let t = chi[0]; + // @ts-ignore + let r = getNumber(t); + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g = getNumber(t); + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b = getNumber(t); + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a = null; + if (t != null) { + // @ts-ignore + a = getNumber(t) / 255; + } + return rgb2hslvalues(r, g, b, a); +} +// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js +function hsv2hsl(h, s, v, a) { + return [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + h / 2, //Lightness is (2-sat)*val/2 + //See reassignment of hue above, + // @ts-ignore + a + ]; +} +function hwb2hsl(token) { + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); +} +function rgb2hslvalues(r, g, b, a = null) { r /= 255; g /= 255; b /= 255; @@ -25,25 +73,13 @@ function rgb2hsl(r, g, b, a) { } h /= 6; } - return [h, s, l, a == 1 ? null : a ?? null]; -} -// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -function hsv2hsl(h, s, v) { - return [ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), - h / 2 //Lightness is (2-sat)*val/2 - //See reassignment of hue above - ]; -} -function hwb2hsl(h, w, b) { - return hsv2hsl(...hwb2hsv(h, w, b)); + const hsl = [h, s, l]; + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]); + } + // @ts-ignore + return hsl; } -export { hsv2hsl, hwb2hsl, rgb2hsl }; +export { hsv2hsl, hwb2hsl, rgb2hsl, rgb2hslvalues }; diff --git a/dist/lib/renderer/color/hsv.js b/dist/lib/renderer/color/hsv.js index bc89d9e..ed036da 100644 --- a/dist/lib/renderer/color/hsv.js +++ b/dist/lib/renderer/color/hsv.js @@ -1,5 +1,6 @@ -function hwb2hsv(h, w, b) { - return [h, 1 - w / (1 - b), 1 - b]; +function hwb2hsv(h, w, b, a) { + // @ts-ignore + return [h, 1 - w / (1 - b), 1 - b, a]; } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js function hsl2hsv(h, s, l) { diff --git a/dist/lib/renderer/color/lab.js b/dist/lib/renderer/color/lab.js index 8e13954..6f6bae4 100644 --- a/dist/lib/renderer/color/lab.js +++ b/dist/lib/renderer/color/lab.js @@ -1,5 +1,10 @@ import { D50 } from './utils/constants.js'; +import '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import './color.js'; import { XYZ_to_sRGB } from './xyz.js'; +import '../sourcemap/lib/encode.js'; // L: 0% = 0.0, 100% = 100.0 // for a and b: -100% = -125, 100% = 125 diff --git a/dist/lib/renderer/color/oklab.js b/dist/lib/renderer/color/oklab.js index f42e0cb..01b1e7c 100644 --- a/dist/lib/renderer/color/oklab.js +++ b/dist/lib/renderer/color/oklab.js @@ -1,25 +1,38 @@ -import { multiplyMatrices } from './utils/matrix.js'; -import { XYZ_to_sRGB } from './xyz.js'; +import '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import './color.js'; +import { gam_sRGB } from './srgb.js'; +import '../sourcemap/lib/encode.js'; // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_sRGB(l, a, b) { + // console.error({l, a, b}); + // console.error({l, a, b}); // @ts-ignore - return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); -} -function OKLab_to_XYZ(l, a, b) { - // Given OKLab, convert to XYZ relative to D65 - const LMStoXYZ = [ - [1.2268798733741557, -0.5578149965554813, 0.28139105017721583], - [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], - [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418] - ]; - const OKLabtoLMS = [ - [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339], - [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402], - [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399] - ]; - const LMSnl = multiplyMatrices(OKLabtoLMS, [l, a, b]); - return multiplyMatrices(LMStoXYZ, LMSnl.map((c) => c ** 3)); + // return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); + let L = Math.pow(l * 0.99999999845051981432 + + 0.39633779217376785678 * a + + 0.21580375806075880339 * b, 3); + let M = Math.pow(l * 1.0000000088817607767 - + 0.1055613423236563494 * a - + 0.063854174771705903402 * b, 3); + let S = Math.pow(l * 1.0000000546724109177 - + 0.089484182094965759684 * a - + 1.2914855378640917399 * b, 3); + return gam_sRGB( + /* r: */ + +4.076741661347994 * L - + 3.307711590408193 * M + + 0.230969928729428 * S, + /* g: */ + -1.2684380040921763 * L + + 2.6097574006633715 * M - + 0.3413193963102197 * S, + /* b: */ + -0.004196086541837188 * L - + 0.7034186144594493 * M + + 1.7076147009309444 * S); } -export { OKLab_to_XYZ, OKLab_to_sRGB }; +export { OKLab_to_sRGB }; diff --git a/dist/lib/renderer/color/rgb.js b/dist/lib/renderer/color/rgb.js index 9add20e..638322d 100644 --- a/dist/lib/renderer/color/rgb.js +++ b/dist/lib/renderer/color/rgb.js @@ -1,9 +1,10 @@ import { getNumber, minmax, getAngle } from './color.js'; +import { getComponents } from './utils/components.js'; import { OKLab_to_sRGB } from './oklab.js'; -import { Lab_to_sRGB } from './lab.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; +import { Lab_to_sRGB } from './lab.js'; import '../sourcemap/lib/encode.js'; function hwb2rgb(token) { @@ -23,20 +24,21 @@ function hsl2rgb(token) { return hsl2rgbvalues(h, s, l, a); } function cmyk2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const c = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const m = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const y = getNumber(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const k = getNumber(t); const rgb = [ @@ -54,20 +56,21 @@ function cmyk2rgb(token) { return rgb; } function oklab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); const rgb = OKLab_to_sRGB(l, a, b).map(v => { @@ -79,90 +82,94 @@ function oklab2rgb(token) { return rgb.map(((value) => minmax(value, 0, 255))); } function oklch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)).map((v) => Math.round(255 * v)); + const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); } function lab2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); } function lch2rgb(token) { + const components = getComponents(token); // @ts-ignore - let t = token.chi[0]; + let t = components[0]; // @ts-ignore const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch const a = c * Math.cos(360 * h * Math.PI / 180); const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b).map((v) => Math.round(255 * v)); + const rgb = Lab_to_sRGB(l, a, b); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb.map(((value) => minmax(value * 255, 0, 255))); } function hslvalues(token) { + const components = getComponents(token); let t; // @ts-ignore - let h = getAngle(token.chi[0]); + let h = getAngle(components[0]); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore let s = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore let l = getNumber(t); let a = null; @@ -177,7 +184,7 @@ function hslvalues(token) { a = getNumber(t); } } - return { h, s, l, a }; + return a == null ? { h, s, l } : { h, s, l, a }; } function hsl2rgbvalues(h, s, l, a = null) { let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; diff --git a/dist/lib/renderer/color/srgb.js b/dist/lib/renderer/color/srgb.js index 90b1c53..3685042 100644 --- a/dist/lib/renderer/color/srgb.js +++ b/dist/lib/renderer/color/srgb.js @@ -1,4 +1,9 @@ import { roundWithPrecision } from './utils/round.js'; +import '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import './color.js'; +import '../sourcemap/lib/encode.js'; // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb @@ -10,13 +15,12 @@ function gam_sRGB(r, g, b) { // Extended transfer function: // For negative values, linear portion extends on reflection // of axis, then uses reflected pow below that - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; + return [r, g, b].map((val) => { let abs = Math.abs(val); - if (abs > 0.0031308) { - return roundWithPrecision(sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055), val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); } - return roundWithPrecision(12.92 * val, val); + return 12.92 * val; }); } // export function gam_a98rgb(r: number, g: number, b: number): number[] { @@ -41,9 +45,9 @@ function lin_ProPhoto(r, g, b) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs <= Et2) { - return roundWithPrecision(val / 16, val); + return roundWithPrecision(val / 16); } - return roundWithPrecision(sign * Math.pow(abs, 1.8), val); + return roundWithPrecision(sign * Math.pow(abs, 1.8)); }); } function lin_a98rgb(r, g, b) { @@ -53,7 +57,7 @@ function lin_a98rgb(r, g, b) { return [r, g, b].map(function (val) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); - return roundWithPrecision(sign * Math.pow(abs, 563 / 256), val); + return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); }); } function lin_2020(r, g, b) { @@ -66,9 +70,9 @@ function lin_2020(r, g, b) { let sign = val < 0 ? -1 : 1; let abs = Math.abs(val); if (abs < β * 4.5) { - return roundWithPrecision(val / 4.5, val); + return roundWithPrecision(val / 4.5); } - return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45)), val); + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); }); } diff --git a/dist/lib/renderer/color/utils/round.js b/dist/lib/renderer/color/utils/round.js index 724f5eb..985de08 100644 --- a/dist/lib/renderer/color/utils/round.js +++ b/dist/lib/renderer/color/utils/round.js @@ -1,9 +1,10 @@ function roundWithPrecision(value, original) { - const length = original.toString().split('.')[1]?.length ?? 0; - if (length == 0) { - return value; - } - return +value.toFixed(length); + // const length: number = original.toString().split('.')[1]?.length ?? 0; + // if (length == 0) { + return value; + // } + // + // return +value.toFixed(length); } export { roundWithPrecision }; diff --git a/dist/lib/renderer/color/xyz.js b/dist/lib/renderer/color/xyz.js index 1483518..00c8f5b 100644 --- a/dist/lib/renderer/color/xyz.js +++ b/dist/lib/renderer/color/xyz.js @@ -1,34 +1,25 @@ -import { multiplyMatrices } from './utils/matrix.js'; -import { roundWithPrecision } from './utils/round.js'; +import '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import './color.js'; import { gam_sRGB } from './srgb.js'; +import '../sourcemap/lib/encode.js'; function XYZ_to_sRGB(x, y, z) { // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); -} -function XYZ_to_lin_sRGB(x, y, z) { - // convert XYZ to linear-light sRGB - const M = [ - [12831 / 3959, -329 / 214, -1974 / 3959], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [705 / 12673, -2585 / 12673, 705 / 667], - ]; - const XYZ = [x, y, z]; // convert to XYZ - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); -} -function XYZ_D50_to_sRGB(x, y, z) { - // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); -} -function D50_to_D65(x, y, z) { - // Bradford chromatic adaptation from D50 to D65 - const M = [ - [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], - [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], - [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] - ]; - const XYZ = [x, y, z]; - return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); + return gam_sRGB( + /* r: */ + x * 3.1341359569958707 - + y * 1.6173863321612538 - + 0.4906619460083532 * z, + /* g: */ + x * -0.978795502912089 + + y * 1.916254567259524 + + 0.03344273116131949 * z, + /* b: */ + x * 0.07195537988411677 - + y * 0.2289768264158322 + + 1.405386058324125 * z); } -export { D50_to_D65, XYZ_D50_to_sRGB, XYZ_to_lin_sRGB, XYZ_to_sRGB }; +export { XYZ_to_sRGB }; diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 7f299bc..b1c81d3 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -1,12 +1,13 @@ import { getAngle, getNumber, clampValues, clamp, COLORS_NAMES } from './color/color.js'; -import { XYZ_D50_to_sRGB, XYZ_to_sRGB } from './color/xyz.js'; import { EnumToken } from '../ast/types.js'; import '../ast/minify.js'; import { expand } from '../ast/expand.js'; +import { gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto } from './color/srgb.js'; import { reduceHexValue, rgb2hex, hsl2hex, hwb2hex, cmyk2hex, oklab2hex, oklch2hex, lab2hex, lch2hex } from './color/hex.js'; +import { XYZ_to_sRGB } from './color/xyz.js'; import { colorMix } from './color/colormix.js'; -import { gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto } from './color/srgb.js'; import { parseRelativeColor } from './color/relativecolor.js'; +import { XYZ_D65_to_sRGB } from './color/xyzd65.js'; import { SourceMap } from './sourcemap/sourcemap.js'; import '../parser/parse.js'; import { isColor, isNewLine } from '../parser/utils/syntax.js'; @@ -296,11 +297,11 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, case 'xyz': case 'xyz-d65': // @ts-ignore - values = XYZ_to_sRGB(...values); + values = XYZ_D65_to_sRGB(...values); break; case 'xyz-d50': // @ts-ignore - values = XYZ_D50_to_sRGB(...values); + values = XYZ_to_sRGB(...values); break; } clampValues(values, colorSpace); @@ -331,7 +332,6 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, } } else if (token.cal == 'mix' && token.val == 'color-mix') { - // console.debug(JSON.stringify({token}, null, 1)); const children = token.chi.reduce((acc, t) => { if (t.typ == EnumToken.ColorTokenType) { acc.push([t]); @@ -345,7 +345,6 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, }, [[]]); const value = colorMix(children[0][1], children[0][2], children[1][0], children[1][1], children[2][0], children[2][1]); if (value != null) { - // console.debug(JSON.stringify(value, null, 1)); token = value; } } diff --git a/src/config.json b/src/config.json index 43d9f46..ca6a6dc 100644 --- a/src/config.json +++ b/src/config.json @@ -901,6 +901,7 @@ "properties": { "overflow-x": { "default": [], + "types": [], "keywords": [ "auto", "visible", @@ -911,6 +912,7 @@ }, "overflow-y": { "default": [], + "types": [], "keywords": [ "auto", "visible", diff --git a/src/lib/parser/utils/syntax.ts b/src/lib/parser/utils/syntax.ts index 39689f7..92ff985 100644 --- a/src/lib/parser/utils/syntax.ts +++ b/src/lib/parser/utils/syntax.ts @@ -207,8 +207,6 @@ export function isColor(token: Token): boolean { return true; } - console.debug(JSON.stringify({children}, null, 1)); - return false; } else { diff --git a/src/lib/parser/utils/type.ts b/src/lib/parser/utils/type.ts index c08e708..7ffd206 100644 --- a/src/lib/parser/utils/type.ts +++ b/src/lib/parser/utils/type.ts @@ -1,5 +1,5 @@ import {EnumToken} from "../../ast"; -import {FunctionToken, IdentToken, PropertyMapType, Token} from "../../../@types"; +import {IdentToken, PropertyMapType, Token} from "../../../@types"; // https://www.w3.org/TR/css-values-4/#math-function export const funcList: string[] = ['clamp', 'calc']; @@ -27,7 +27,7 @@ export function matchType(val: Token, properties: PropertyMapType): boolean { if (val.typ == EnumToken.FunctionTokenType) { if (funcList.includes(val.val)) { - return val.chi.every((t => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); + return val.chi.every(((t: Token) => [EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType].includes(t.typ) || matchType(t, properties))); } // match type defined like function 'symbols()', 'url()', 'attr()' etc. diff --git a/src/lib/renderer/color/hsl.ts b/src/lib/renderer/color/hsl.ts index 215f2c8..0ece48d 100644 --- a/src/lib/renderer/color/hsl.ts +++ b/src/lib/renderer/color/hsl.ts @@ -1,6 +1,95 @@ import {hwb2hsv} from "./hsv"; +import {ColorToken, IdentToken, NumberToken, PercentageToken, Token} from "../../../@types"; +import {getNumber} from "./color"; +import {hex2rgb, hslvalues, lab2rgb, lch2rgb, oklab2rgb} from "./rgb"; +import {getComponents} from "./utils"; -export function rgb2hsl(r: number, g: number, b: number, a?: number | null): [number, number, number, number | null] { +export function hex2hsl(token: ColorToken): [number, number, number, number | null] { + + // @ts-ignore + return rgb2hslvalues(...hex2rgb(token)); +} + +export function rgb2hsl(token: ColorToken): [number, number, number, number | null] { + + const chi: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken | IdentToken = chi[0]; + + // @ts-ignore + let r: number = getNumber(t); + + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g: number = getNumber(t); + + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b: number = getNumber(t); + + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a: number = null; + + if (t != null) { + + // @ts-ignore + a = getNumber(t) / 255; + } + + return rgb2hslvalues(r, g, b, a); +} + +// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js +export function hsv2hsl(h: number, s: number, v: number, a?: number): [number, number, number, number | null] { + return [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + + h / 2, //Lightness is (2-sat)*val/2 + //See reassignment of hue above, + // @ts-ignore + a + ] +} + + +export function hwb2hsl(token: ColorToken): [number, number, number, number] { + + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); +} + +export function lab2hsl(token: ColorToken): [number, number, number, number | null] { + + // @ts-ignore + return rgb2hslvalues(...lab2rgb(token)); +} + +export function lch2hsl(token: ColorToken): [number, number, number, number | null] { + + // @ts-ignore + return rgb2hslvalues(...lch2rgb(token)); +} + +export function oklab2hsl(token: ColorToken): [number, number, number, number | null] { + + // @ts-ignore + return rgb2hslvalues(...oklab2rgb(token)); +} + +export function rgb2hslvalues(r: number, g: number, b: number, a: number | null = null): [number, number, number, number | null] { r /= 255; g /= 255; @@ -17,37 +106,29 @@ export function rgb2hsl(r: number, g: number, b: number, a?: number | null): [nu s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; } h /= 6; } - return [ h, s, l, a == 1 ? null : a ?? null ]; -} + const hsl: number[] = [h, s, l]; -// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -export function hsv2hsl(h: number,s: number,v: number): [number, number, number] { - return[ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s*v/((h=(2-s)*v)<1?h:2-h), - - h/2 //Lightness is (2-sat)*val/2 - //See reassignment of hue above - ] -} + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]) -export function hwb2hsl(h: number, w: number, b: number): [number, number, number] { + } - return hsv2hsl(...hwb2hsv(h, w, b)); + // @ts-ignore + return hsl; } \ No newline at end of file diff --git a/src/lib/renderer/color/hsv.ts b/src/lib/renderer/color/hsv.ts index a082463..b662bbc 100644 --- a/src/lib/renderer/color/hsv.ts +++ b/src/lib/renderer/color/hsv.ts @@ -1,8 +1,9 @@ -export function hwb2hsv(h: number, w: number, b: number): [number, number, number]{ +export function hwb2hsv(h: number, w: number, b: number, a?: number): [number, number, number, number | null] { - return [h, 1 - w/(1 - b), 1 -b]; + // @ts-ignore + return [h, 1 - w/(1 - b), 1 -b, a]; } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js diff --git a/src/lib/renderer/color/index.ts b/src/lib/renderer/color/index.ts index 7ca5c52..be7e4cd 100644 --- a/src/lib/renderer/color/index.ts +++ b/src/lib/renderer/color/index.ts @@ -11,4 +11,5 @@ export * from './srgb'; export * from './xyz'; export * from './lab'; // export * from './lch'; -export * from './relativecolor'; \ No newline at end of file +export * from './relativecolor'; +export * from './xyzd65'; \ No newline at end of file diff --git a/src/lib/renderer/color/oklab.ts b/src/lib/renderer/color/oklab.ts index e085bd7..b9257c4 100644 --- a/src/lib/renderer/color/oklab.ts +++ b/src/lib/renderer/color/oklab.ts @@ -1,11 +1,51 @@ import {multiplyMatrices} from "./utils"; import {XYZ_to_sRGB} from "./xyz"; +import {gam_sRGB} from "./srgb"; // from https://www.w3.org/TR/css-color-4/#color-conversion-code export function OKLab_to_sRGB(l: number, a: number, b: number): number[] { + // console.error({l, a, b}); + + // console.error({l, a, b}); + // @ts-ignore - return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); + // return XYZ_to_sRGB(...OKLab_to_XYZ(l, a, b)); + + + let L = Math.pow( + l * 0.99999999845051981432 + + 0.39633779217376785678 * a + + 0.21580375806075880339 * b, + 3 + ); + let M = Math.pow( + l * 1.0000000088817607767 - + 0.1055613423236563494 * a - + 0.063854174771705903402 * b, + 3 + ); + let S = Math.pow( + l * 1.0000000546724109177 - + 0.089484182094965759684 * a - + 1.2914855378640917399 * b, + 3 + ); + + return gam_sRGB( + /* r: */ + +4.076741661347994 * L - + 3.307711590408193 * M + + 0.230969928729428 * S, + /* g: */ + -1.2684380040921763 * L + + 2.6097574006633715 * M - + 0.3413193963102197 * S, + /* b: */ + -0.004196086541837188 * L - + 0.7034186144594493 * M + + 1.7076147009309444 * S + ); } diff --git a/src/lib/renderer/color/relativecolor.ts b/src/lib/renderer/color/relativecolor.ts index 98e7531..83aada5 100644 --- a/src/lib/renderer/color/relativecolor.ts +++ b/src/lib/renderer/color/relativecolor.ts @@ -202,7 +202,6 @@ export function parseRelativeColor(relativeKeys: RelativeColorTypes[], original: }; } - if (aExp != null && aExp.typ == EnumToken.IdenTokenType && aExp.val == 'none') { aExp = null; diff --git a/src/lib/renderer/color/rgb.ts b/src/lib/renderer/color/rgb.ts index 5695f82..c089cb2 100644 --- a/src/lib/renderer/color/rgb.ts +++ b/src/lib/renderer/color/rgb.ts @@ -1,8 +1,23 @@ -import {ColorToken, DimensionToken, NumberToken, PercentageToken} from "../../../@types"; -import {getAngle, getNumber, minmax} from "./color"; +import {ColorToken, DimensionToken, NumberToken, PercentageToken, Token} from "../../../@types"; +import {COLORS_NAMES, getAngle, getNumber, minmax} from "./color"; +import {getComponents} from "./utils"; import {OKLab_to_sRGB} from "./oklab"; -import {Lab_to_sRGB} from "./lab"; +import {expandHexValue} from "./hex"; import {EnumToken} from "../../ast"; +import {Lab_to_sRGB} from "./lab"; + +export function hex2rgb(token: ColorToken): number[] { + + const value: string = expandHexValue(token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb: number[] = []; + + for (let i = 1; i < value.length; i += 2) { + + rgb.push(parseInt(value.slice(i, i + 2), 16)); + } + + return rgb; +} export function hwb2rgb(token: ColorToken): number[] { @@ -34,26 +49,28 @@ export function hsl2rgb(token: ColorToken): number[] { export function cmyk2rgb(token: ColorToken): number[] { + const components: Token[] = getComponents(token); + // @ts-ignore - let t: NumberToken | PercentageToken = token.chi[0]; + let t: NumberToken | PercentageToken = components[0]; // @ts-ignore const c: number = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const m: number = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const y: number = getNumber(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const k: number = getNumber(t); @@ -80,26 +97,28 @@ export function cmyk2rgb(token: ColorToken): number[] { export function oklab2rgb(token: ColorToken): number[] { + const components: Token[] = getComponents(token); + // @ts-ignore - let t: NumberToken | PercentageToken = token.chi[0]; + let t: NumberToken | PercentageToken = components[0]; // @ts-ignore const l: number = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha: number = t == null ? 1 : getNumber(t); @@ -119,132 +138,139 @@ export function oklab2rgb(token: ColorToken): number[] { export function oklch2rgb(token: ColorToken): number[] { + const components: Token[] = getComponents(token); + // @ts-ignore - let t: NumberToken | PercentageToken = token.chi[0]; + let t: NumberToken | PercentageToken = components[0]; // @ts-ignore const l: number = getNumber(t); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h: number = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha: number = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch - - const rgb: number[] = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)).map((v: number): number => Math.round(255 * v)); + const rgb: number[] = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value: number): number => minmax(value, 0, 255))); + return rgb.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); } export function lab2rgb(token: ColorToken): number[] { + const components: Token[] = getComponents(token); + // @ts-ignore - let t: NumberToken | PercentageToken = token.chi[0]; + let t: NumberToken | PercentageToken = components[0]; // @ts-ignore const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha: number = t == null ? 1 : getNumber(t); + const rgb: number[] = Lab_to_sRGB(l, a, b); - const rgb: number[] = Lab_to_sRGB(l, a, b).map((v: number): number => Math.round(255 * v)); // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push( alpha); } - return rgb.map(((value: number): number => minmax(value, 0, 255))); + return rgb.map(((value: number): number => minmax(Math.round(value * 255), 0, 255))); } export function lch2rgb(token: ColorToken): number[] { + const components: Token[] = getComponents(token); + // @ts-ignore - let t: NumberToken | PercentageToken = token.chi[0]; + let t: NumberToken | PercentageToken = components[0]; // @ts-ignore const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore const h: number = getAngle(t); // @ts-ignore - t = token.chi[3]; + t = components[3]; // @ts-ignore const alpha: number = t == null ? 1 : getNumber(t); // https://www.w3.org/TR/css-color-4/#lab-to-lch - const a: number = c * Math.cos(360 * h * Math.PI / 180); const b: number = c * Math.sin(360 * h * Math.PI / 180); - const rgb: number[] = Lab_to_sRGB(l, a, b).map((v: number): number => Math.round(255 * v)); + const rgb: number[] = Lab_to_sRGB(l, a, b); + // if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } - return rgb.map(((value: number): number => minmax(value, 0, 255))); + return rgb.map(((value: number): number => minmax(value * 255, 0, 255))); } -export function hslvalues(token: ColorToken) { +export function hslvalues(token: ColorToken): {h: number, s: number, l: number, a?: number | null} { + + const components: Token[] = getComponents(token); let t: PercentageToken | NumberToken; // @ts-ignore - let h: number = getAngle(token.chi[0]); + let h: number = getAngle(components[0]); // @ts-ignore - t = token.chi[1]; + t = components[1]; // @ts-ignore let s: number = getNumber(t); // @ts-ignore - t = token.chi[2]; + t = components[2]; // @ts-ignore let l: number = getNumber(t); @@ -266,7 +292,7 @@ export function hslvalues(token: ColorToken) { } } - return {h, s, l, a}; + return a == null ? {h, s, l} : {h, s, l, a}; } export function hsl2rgbvalues(h: number, s: number, l: number, a: number | null = null): number[] { diff --git a/src/lib/renderer/color/srgb.ts b/src/lib/renderer/color/srgb.ts index d887ef3..4bc41d7 100644 --- a/src/lib/renderer/color/srgb.ts +++ b/src/lib/renderer/color/srgb.ts @@ -1,26 +1,110 @@ - - // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 -import {roundWithPrecision} from "./utils"; +import {getComponents, roundWithPrecision} from "./utils"; +import {ColorToken, DimensionToken, NumberToken, PercentageToken, Token} from "../../../@types"; +import {getAngle, getNumber} from "./color"; +import {EnumToken} from "../../ast"; +import {Lab_to_sRGB} from "./lab"; + +export function lab2srgb(token: ColorToken): number[] { + + const components: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken = components[0]; + + // @ts-ignore + const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); + + // @ts-ignore + t = components[1]; + + // @ts-ignore + const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); + + // @ts-ignore + t = components[2]; + + // @ts-ignore + const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); + + // @ts-ignore + t = components[3]; + + // @ts-ignore + const alpha: number = t == null ? 1 : getNumber(t); + const rgb: number[] = Lab_to_sRGB(l, a, b); + + // + if (alpha != 1) { + + rgb.push(alpha); + } + + return rgb; +} + +export function lch2srgb(token: ColorToken): number[] { + + const components: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken = components[0]; + + // @ts-ignore + const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); + + // @ts-ignore + t = components[1]; + + // @ts-ignore + const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); + + // @ts-ignore + t = components[2]; + + // @ts-ignore + const h: number = getAngle(t); + + // @ts-ignore + t = components[3]; + + // @ts-ignore + const alpha: number = t == null ? 1 : getNumber(t); + + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const a: number = c * Math.cos(360 * h * Math.PI / 180); + const b: number = c * Math.sin(360 * h * Math.PI / 180); + + const rgb: number[] = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + + rgb.push(alpha); + } + + return rgb; +} export function gam_sRGB(r: number, g: number, b: number): number[] { + // convert an array of linear-light sRGB values in the range 0.0-1.0 // to gamma corrected form // https://en.wikipedia.org/wiki/SRGB // Extended transfer function: // For negative values, linear portion extends on reflection // of axis, then uses reflected pow below that - return [r, g, b].map(function (val: number): number { - let sign: number = val < 0 ? -1 : 1; + return [r, g, b].map( (val: number): number => { + let abs: number = Math.abs(val); - if (abs > 0.0031308) { - return roundWithPrecision(sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055), val); + if (Math.abs(val) > 0.0031308) { + + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); } - return roundWithPrecision(12.92 * val, val); + return 12.92 * val; }); } @@ -90,17 +174,17 @@ export function lin_2020(r: number, g: number, b: number): number[] { // to linear light (un-companded) form. // ITU-R BT.2020-2 p.4 - const α: number = 1.09929682680944 ; + const α: number = 1.09929682680944; const β: number = 0.018053968510807; return [r, g, b].map(function (val: number): number { - let sign: number = val < 0? -1 : 1; + let sign: number = val < 0 ? -1 : 1; let abs: number = Math.abs(val); - if (abs < β * 4.5 ) { + if (abs < β * 4.5) { return roundWithPrecision(val / 4.5, val); } - return roundWithPrecision(sign * (Math.pow((abs + α -1 ) / α, 1/0.45)), val); + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45)), val); }); } diff --git a/src/lib/renderer/color/utils/components.ts b/src/lib/renderer/color/utils/components.ts new file mode 100644 index 0000000..314f2cd --- /dev/null +++ b/src/lib/renderer/color/utils/components.ts @@ -0,0 +1,8 @@ +import {ColorToken, Token} from "../../../../@types"; +import {EnumToken} from "../../../ast"; + +export function getComponents(token: ColorToken): Token[] { + + return (token.chi) + .filter((t: Token) => ![EnumToken.LiteralTokenType, EnumToken.CommentTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType].includes(t.typ)); +} \ No newline at end of file diff --git a/src/lib/renderer/color/utils/constants.ts b/src/lib/renderer/color/utils/constants.ts index 3224639..696d86e 100644 --- a/src/lib/renderer/color/utils/constants.ts +++ b/src/lib/renderer/color/utils/constants.ts @@ -1,4 +1,7 @@ export const D50: number[] = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; -export const D65: number[] = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290]; \ No newline at end of file +export const D65: number[] = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290]; + +export const k: number = Math.pow(29, 3) / Math.pow(3, 3); +export const e: number = Math.pow(6, 3) / Math.pow(29, 3); diff --git a/src/lib/renderer/color/utils/index.ts b/src/lib/renderer/color/utils/index.ts index 4120018..1550b52 100644 --- a/src/lib/renderer/color/utils/index.ts +++ b/src/lib/renderer/color/utils/index.ts @@ -1,4 +1,5 @@ export * from './matrix'; export * from './round'; -export * from './constants'; \ No newline at end of file +export * from './constants'; +export * from './components'; diff --git a/src/lib/renderer/color/utils/round.ts b/src/lib/renderer/color/utils/round.ts index 137a326..7b80a82 100644 --- a/src/lib/renderer/color/utils/round.ts +++ b/src/lib/renderer/color/utils/round.ts @@ -1,13 +1,11 @@ - - export function roundWithPrecision(value: number, original: number): number { - const length = original.toString().split('.')[1]?.length ?? 0; + // const length: number = original.toString().split('.')[1]?.length ?? 0; - if (length == 0) { + // if (length == 0) { return value; - } - - return +value.toFixed(length); + // } + // + // return +value.toFixed(length); } \ No newline at end of file diff --git a/src/lib/renderer/color/xyz.ts b/src/lib/renderer/color/xyz.ts index 44d330b..9da5690 100644 --- a/src/lib/renderer/color/xyz.ts +++ b/src/lib/renderer/color/xyz.ts @@ -4,7 +4,19 @@ import {gam_sRGB} from "./srgb"; export function XYZ_to_sRGB(x: number, y: number, z: number): number[] { // @ts-ignore - return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); + return gam_sRGB( + /* r: */ + x * 3.1341359569958707 - + y * 1.6173863321612538 - + 0.4906619460083532 * z, + /* g: */ + x * -0.978795502912089 + + y * 1.916254567259524 + + 0.03344273116131949 * z, + /* b: */ + x * 0.07195537988411677 - + y * 0.2289768264158322 + + 1.405386058324125 * z); } export function XYZ_to_lin_sRGB(x: number, y: number, z: number): number[] { diff --git a/src/lib/renderer/color/xyzd65.ts b/src/lib/renderer/color/xyzd65.ts new file mode 100644 index 0000000..6a88164 --- /dev/null +++ b/src/lib/renderer/color/xyzd65.ts @@ -0,0 +1,24 @@ +import {multiplyMatrices} from "./utils"; +import {XYZ_to_sRGB} from "./xyz"; + +export function XYZ_D65_to_sRGB(x: number, y: number, z: number): number[] { + // @ts-ignore + return XYZ_to_sRGB(...XYZ_D65_to_D50(x, y, z)); +} + +export function XYZ_D65_to_D50(x: number, y: number, z: number): number[] { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // see https://github.com/LeaVerou/color.js/pull/354/files + + var M = [ + [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], + [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], + [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371] + ]; + + return multiplyMatrices(M, [x, y, z]); +} \ No newline at end of file diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 86aa032..26acb72 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -27,7 +27,7 @@ import { hsl2hex, hwb2hex, lab2hex, lch2hex, oklab2hex, oklch2hex, reduceHexValue, - rgb2hex + rgb2hex, XYZ_D65_to_sRGB } from "./color"; import {EnumToken, expand} from "../ast"; import {SourceMap} from "./sourcemap"; @@ -469,12 +469,13 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { break; case 'xyz': case 'xyz-d65': + // @ts-ignore - values = XYZ_to_sRGB(...values); + values = XYZ_D65_to_sRGB(...values); break; case 'xyz-d50': // @ts-ignore - values = XYZ_D50_to_sRGB(...values); + values = XYZ_to_sRGB(...values); break; } @@ -518,8 +519,6 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { } } else if (token.cal == 'mix' && token.val == 'color-mix') { - // console.debug(JSON.stringify({token}, null, 1)); - const children: Token[][] = (token.chi).reduce((acc: Token[][], t: Token) => { if (t.typ == EnumToken.ColorTokenType) { @@ -540,7 +539,6 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { if (value != null) { - // console.debug(JSON.stringify(value, null, 1)); token = value; } } diff --git a/test/specs/code/color.js b/test/specs/code/color.js index 7fac1dd..3883011 100644 --- a/test/specs/code/color.js +++ b/test/specs/code/color.js @@ -300,7 +300,7 @@ color: color-mix(in srgb , white , black ); .selector { color: color( srgb-linear 0.21404 0.21404 0.21404 ) `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: grey + color: #7f7f7f }`)); }); @@ -318,7 +318,7 @@ color: color(display-p3 0.5 .5 .5); .selector { color: color(prophoto-rgb 0.42467 0.42467 0.42467); `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: grey + color: #7f7f7f }`)); }); @@ -328,7 +328,7 @@ color: color(prophoto-rgb 0.42467 0.42467 0.42467); .selector { color: color(a98-rgb 0.4961 0.4961 0.4961); `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: grey + color: #7f7f7f }`)); }); @@ -337,7 +337,7 @@ color: color(a98-rgb 0.4961 0.4961 0.4961); .selector { color: color(rec2020 0.45004 0.45004 0.45004); `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: grey + color: #7f7f7f }`)); }); @@ -364,7 +364,7 @@ color: color(xyz-d50 0.58098 0.49223 0.05045); .selector { color: color(xyz 0.20344 0.21404 0.2331); `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: grey + color: #807f7f }`)); }); @@ -454,7 +454,7 @@ color: lab(54.291, 80.805, 69.891); .selector { color: lab(97.83 -12.04 62.08); `).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { - color: #fffb60 + color: #fffe7a }`)); // should be #fffe7a }); diff --git a/tools/shorthand.ts b/tools/shorthand.ts index 111bfbb..85a8e9a 100644 --- a/tools/shorthand.ts +++ b/tools/shorthand.ts @@ -515,6 +515,7 @@ export const map: ShorthandMapType = ([ properties: { default: [], + types: [], keywords: ['auto', 'visible', 'hidden', 'clip', 'scroll'] } }, @@ -522,6 +523,7 @@ export const map: ShorthandMapType = ([ shorthand: 'overflow-y', properties: { default: [], + types: [], keywords: ['auto', 'visible', 'hidden', 'clip', 'scroll'] } }