Skip to content

Commit

Permalink
fix(color): allow all valid CSS colors (#2381)
Browse files Browse the repository at this point in the history
Add support for the following notation styles
- Hex number colors
- hsl / hsla color functions
- named colors
- space separated function notation
- decimal numbers in colors
- percentages in colors
- exponent numbers
- deg / rad / turn unit in hue

DEPREDATED: Use Color.parseString() instead of Color.parseRgbString()
  • Loading branch information
WilcoFiers authored Jul 17, 2020
1 parent d318e46 commit 63d69ea
Show file tree
Hide file tree
Showing 7 changed files with 593 additions and 75 deletions.
180 changes: 160 additions & 20 deletions lib/commons/color/color.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
import standards from '../../standards';

/**
* Convert a CSS color value into a number
*/
function convertColorVal(colorFunc, value, index) {
if (/%$/.test(value)) {
//<percentage>
if (index === 3) {
// alpha
return parseFloat(value) / 100;
}
return (parseFloat(value) * 255) / 100;
}
if (colorFunc[index] === 'h') {
// hue
if (/turn$/.test(value)) {
return parseFloat(value) * 360;
}
if (/rad$/.test(value)) {
return parseFloat(value) * 57.3;
}
}
return parseFloat(value);
}

/**
* Convert HSL to RGB
*/
function hslToRgb([hue, saturation, lightness, alpha]) {
// Must be fractions of 1
saturation /= 255;
lightness /= 255;

const high = (1 - Math.abs(2 * lightness - 1)) * saturation;
const low = high * (1 - Math.abs(((hue / 60) % 2) - 1));
const base = lightness - high / 2;

let colors;
if (hue < 60) {
// red - yellow
colors = [high, low, 0];
} else if (hue < 120) {
// yellow - green
colors = [low, high, 0];
} else if (hue < 180) {
// green - cyan
colors = [0, high, low];
} else if (hue < 240) {
// cyan - blue
colors = [0, low, high];
} else if (hue < 300) {
// blue - purple
colors = [low, 0, high];
} else {
// purple - red
colors = [high, 0, low];
}

return colors
.map(color => {
return Math.round((color + base) * 255);
})
.concat(alpha);
}

/**
* @class Color
* @memberof axe.commons.color
Expand Down Expand Up @@ -38,12 +104,42 @@ function Color(red, green, blue, alpha) {
);
};

var rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)$/;
var rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/;
const hexRegex = /^#[0-9a-f]{3,8}$/i;
const colorFnRegex = /^((?:rgb|hsl)a?)\s*\(([^\)]*)\)/i;

/**
* Parse any valid color string and assign its values to "this"
* @method parseString
* @memberof axe.commons.color.Color
* @instance
*/
this.parseString = function(colorString) {
// IE occasionally returns named colors instead of RGB(A) values
if (standards.cssColors[colorString] || colorString === 'transparent') {
const [red, green, blue] = standards.cssColors[colorString] || [0, 0, 0];
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = colorString === 'transparent' ? 0 : 1;
return;
}

if (colorString.match(colorFnRegex)) {
this.parseColorFnString(colorString);
return;
}

if (colorString.match(hexRegex)) {
this.parseHexString(colorString);
return;
}
throw new Error(`Unable to parse color "${colorString}"`);
};

/**
* Set the color value based on a CSS RGB/RGBA string
* @method parseRgbString
* @deprecated
* @memberof axe.commons.color.Color
* @instance
* @param {string} rgb The string value
Expand All @@ -57,30 +153,74 @@ function Color(red, green, blue, alpha) {
this.alpha = 0;
return;
}
var match = colorString.match(rgbRegex);
this.parseColorFnString(colorString);
};

if (match) {
this.red = parseInt(match[1], 10);
this.green = parseInt(match[2], 10);
this.blue = parseInt(match[3], 10);
this.alpha = 1;
/**
* Set the color value based on a CSS RGB/RGBA string
* @method parseHexString
* @deprecated
* @memberof axe.commons.color.Color
* @instance
* @param {string} rgb The string value
*/
this.parseHexString = function(colorString) {
if (!colorString.match(hexRegex) || [6, 8].includes(colorString.length)) {
return;
}
colorString = colorString.replace('#', '');
if (colorString.length < 6) {
const [r, g, b, a] = colorString;
colorString = r + r + g + g + b + b;
if (a) {
colorString += a + a;
}
}

var aRgbHex = colorString.match(/.{1,2}/g);
this.red = parseInt(aRgbHex[0], 16);
this.green = parseInt(aRgbHex[1], 16);
this.blue = parseInt(aRgbHex[2], 16);
if (aRgbHex[3]) {
this.alpha = parseInt(aRgbHex[3], 16) / 255;
} else {
this.alpha = 1;
}
};

match = colorString.match(rgbaRegex);
if (match) {
this.red = parseInt(match[1], 10);
this.green = parseInt(match[2], 10);
this.blue = parseInt(match[3], 10);

// alpha values can be between 0 and 1, with browsers having
// different floating point precision. for example,
// 'rgba(0,0,0,0.5)' results in 'rgba(0,0,0,0.498039)' in Safari
// when getting the computed style background-color property. to
// fix this, we'll round all alpha values to 2 decimal points.
this.alpha = Math.round(parseFloat(match[4]) * 100) / 100;
/**
* Set the color value based on a CSS RGB/RGBA string
* @method parseColorFnString
* @deprecated
* @memberof axe.commons.color.Color
* @instance
* @param {string} rgb The string value
*/
this.parseColorFnString = function parseColorFnString(colorString) {
const [, colorFunc, colorValStr] = colorString.match(colorFnRegex) || [];
if (!colorFunc || !colorValStr) {
return;
}

// Get array of color number strings from the string:
const colorVals = colorValStr
.split(/\s*[,\/\s]\s*/)
.map(str => str.replace(',', '').trim())
.filter(str => str !== '');

// Convert to numbers
let colorNums = colorVals.map((val, index) => {
return convertColorVal(colorFunc, val, index);
});

if (colorFunc.substr(0, 3) === 'hsl') {
colorNums = hslToRgb(colorNums);
}

this.red = colorNums[0];
this.green = colorNums[1];
this.blue = colorNums[2];
this.alpha = typeof colorNums[3] === 'number' ? colorNums[3] : 1;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/color/element-is-distinct.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function elementIsDistinct(node, ancestorNode) {
edge
) {
var borderClr = new Color();
borderClr.parseRgbString(nodeStyle.getPropertyValue(edge + '-color'));
borderClr.parseString(nodeStyle.getPropertyValue(edge + '-color'));

// Check if a border/outline was specified
return (
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/color/get-foreground-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function getForegroundColor(node, noScroll, bgColor) {
const nodeStyle = window.getComputedStyle(node);

const fgColor = new Color();
fgColor.parseRgbString(nodeStyle.getPropertyValue('color'));
fgColor.parseString(nodeStyle.getPropertyValue('color'));
const opacity = getOpacity(node);
fgColor.alpha = fgColor.alpha * opacity;
if (fgColor.alpha === 1) {
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/color/get-own-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Color from './color';
*/
function getOwnBackgroundColor(elmStyle) {
const bgColor = new Color();
bgColor.parseRgbString(elmStyle.getPropertyValue('background-color'));
bgColor.parseString(elmStyle.getPropertyValue('background-color'));

if (bgColor.alpha !== 0) {
const opacity = elmStyle.getPropertyValue('opacity');
Expand Down
153 changes: 153 additions & 0 deletions lib/standards/css-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Source: https://drafts.csswg.org/css-color/#valdef-color-rebeccapurple
const cssColors = {
aliceblue: [240, 248, 255],
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
aquamarine: [127, 255, 212],
azure: [240, 255, 255],
beige: [245, 245, 220],
bisque: [255, 228, 196],
black: [0, 0, 0],
blanchedalmond: [255, 235, 205],
blue: [0, 0, 255],
blueviolet: [138, 43, 226],
brown: [165, 42, 42],
burlywood: [222, 184, 135],
cadetblue: [95, 158, 160],
chartreuse: [127, 255, 0],
chocolate: [210, 105, 30],
coral: [255, 127, 80],
cornflowerblue: [100, 149, 237],
cornsilk: [255, 248, 220],
crimson: [220, 20, 60],
cyan: [0, 255, 255],
darkblue: [0, 0, 139],
darkcyan: [0, 139, 139],
darkgoldenrod: [184, 134, 11],
darkgray: [169, 169, 169],
darkgreen: [0, 100, 0],
darkgrey: [169, 169, 169],
darkkhaki: [189, 183, 107],
darkmagenta: [139, 0, 139],
darkolivegreen: [85, 107, 47],
darkorange: [255, 140, 0],
darkorchid: [153, 50, 204],
darkred: [139, 0, 0],
darksalmon: [233, 150, 122],
darkseagreen: [143, 188, 143],
darkslateblue: [72, 61, 139],
darkslategray: [47, 79, 79],
darkslategrey: [47, 79, 79],
darkturquoise: [0, 206, 209],
darkviolet: [148, 0, 211],
deeppink: [255, 20, 147],
deepskyblue: [0, 191, 255],
dimgray: [105, 105, 105],
dimgrey: [105, 105, 105],
dodgerblue: [30, 144, 255],
firebrick: [178, 34, 34],
floralwhite: [255, 250, 240],
forestgreen: [34, 139, 34],
fuchsia: [255, 0, 255],
gainsboro: [220, 220, 220],
ghostwhite: [248, 248, 255],
gold: [255, 215, 0],
goldenrod: [218, 165, 32],
gray: [128, 128, 128],
green: [0, 128, 0],
greenyellow: [173, 255, 47],
grey: [128, 128, 128],
honeydew: [240, 255, 240],
hotpink: [255, 105, 180],
indianred: [205, 92, 92],
indigo: [75, 0, 130],
ivory: [255, 255, 240],
khaki: [240, 230, 140],
lavender: [230, 230, 250],
lavenderblush: [255, 240, 245],
lawngreen: [124, 252, 0],
lemonchiffon: [255, 250, 205],
lightblue: [173, 216, 230],
lightcoral: [240, 128, 128],
lightcyan: [224, 255, 255],
lightgoldenrodyellow: [250, 250, 210],
lightgray: [211, 211, 211],
lightgreen: [144, 238, 144],
lightgrey: [211, 211, 211],
lightpink: [255, 182, 193],
lightsalmon: [255, 160, 122],
lightseagreen: [32, 178, 170],
lightskyblue: [135, 206, 250],
lightslategray: [119, 136, 153],
lightslategrey: [119, 136, 153],
lightsteelblue: [176, 196, 222],
lightyellow: [255, 255, 224],
lime: [0, 255, 0],
limegreen: [50, 205, 50],
linen: [250, 240, 230],
magenta: [255, 0, 255],
maroon: [128, 0, 0],
mediumaquamarine: [102, 205, 170],
mediumblue: [0, 0, 205],
mediumorchid: [186, 85, 211],
mediumpurple: [147, 112, 219],
mediumseagreen: [60, 179, 113],
mediumslateblue: [123, 104, 238],
mediumspringgreen: [0, 250, 154],
mediumturquoise: [72, 209, 204],
mediumvioletred: [199, 21, 133],
midnightblue: [25, 25, 112],
mintcream: [245, 255, 250],
mistyrose: [255, 228, 225],
moccasin: [255, 228, 181],
navajowhite: [255, 222, 173],
navy: [0, 0, 128],
oldlace: [253, 245, 230],
olive: [128, 128, 0],
olivedrab: [107, 142, 35],
orange: [255, 165, 0],
orangered: [255, 69, 0],
orchid: [218, 112, 214],
palegoldenrod: [238, 232, 170],
palegreen: [152, 251, 152],
paleturquoise: [175, 238, 238],
palevioletred: [219, 112, 147],
papayawhip: [255, 239, 213],
peachpuff: [255, 218, 185],
peru: [205, 133, 63],
pink: [255, 192, 203],
plum: [221, 160, 221],
powderblue: [176, 224, 230],
purple: [128, 0, 128],
rebeccapurple: [102, 51, 153],
red: [255, 0, 0],
rosybrown: [188, 143, 143],
royalblue: [65, 105, 225],
saddlebrown: [139, 69, 19],
salmon: [250, 128, 114],
sandybrown: [244, 164, 96],
seagreen: [46, 139, 87],
seashell: [255, 245, 238],
sienna: [160, 82, 45],
silver: [192, 192, 192],
skyblue: [135, 206, 235],
slateblue: [106, 90, 205],
slategray: [112, 128, 144],
slategrey: [112, 128, 144],
snow: [255, 250, 250],
springgreen: [0, 255, 127],
steelblue: [70, 130, 180],
tan: [210, 180, 140],
teal: [0, 128, 128],
thistle: [216, 191, 216],
tomato: [255, 99, 71],
turquoise: [64, 224, 208],
violet: [238, 130, 238],
wheat: [245, 222, 179],
white: [255, 255, 255],
whitesmoke: [245, 245, 245],
yellow: [255, 255, 0],
yellowgreen: [154, 205, 50]
};

export default cssColors;
Loading

0 comments on commit 63d69ea

Please sign in to comment.