import {CSSValue, nonFunctionArgSeparator} from '../syntax/parser'; import {TokenType} from '../syntax/tokenizer'; import {ITypeDescriptor} from '../ITypeDescriptor'; import {angle, deg} from './angle'; import {getAbsoluteValue, isLengthPercentage} from './length-percentage'; export type Color = number; export const color: ITypeDescriptor = { name: 'color', parse: (value: CSSValue): Color => { if (value.type === TokenType.FUNCTION) { const colorFunction = SUPPORTED_COLOR_FUNCTIONS[value.name]; if (typeof colorFunction === 'undefined') { throw new Error(`Attempting to parse an unsupported color function "${value.name}"`); } return colorFunction(value.values); } if (value.type === TokenType.HASH_TOKEN) { if (value.value.length === 3) { const r = value.value.substring(0, 1); const g = value.value.substring(1, 2); const b = value.value.substring(2, 3); return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), 1); } if (value.value.length === 4) { const r = value.value.substring(0, 1); const g = value.value.substring(1, 2); const b = value.value.substring(2, 3); const a = value.value.substring(3, 4); return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), parseInt(a + a, 16) / 255); } if (value.value.length === 6) { const r = value.value.substring(0, 2); const g = value.value.substring(2, 4); const b = value.value.substring(4, 6); return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1); } if (value.value.length === 8) { const r = value.value.substring(0, 2); const g = value.value.substring(2, 4); const b = value.value.substring(4, 6); const a = value.value.substring(6, 8); return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), parseInt(a, 16) / 255); } } if (value.type === TokenType.IDENT_TOKEN) { const namedColor = COLORS[value.value.toUpperCase()]; if (typeof namedColor !== 'undefined') { return namedColor; } } return COLORS.TRANSPARENT; } }; export const isTransparent = (color: Color): boolean => (0xff & color) === 0; export const asString = (color: Color): string => { const alpha = 0xff & color; const blue = 0xff & (color >> 8); const green = 0xff & (color >> 16); const red = 0xff & (color >> 24); return alpha < 255 ? `rgba(${red},${green},${blue},${alpha / 255})` : `rgb(${red},${green},${blue})`; }; export const pack = (r: number, g: number, b: number, a: number): Color => ((r << 24) | (g << 16) | (b << 8) | (Math.round(a * 255) << 0)) >>> 0; const getTokenColorValue = (token: CSSValue, i: number): number => { if (token.type === TokenType.NUMBER_TOKEN) { return token.number; } if (token.type === TokenType.PERCENTAGE_TOKEN) { const max = i === 3 ? 1 : 255; return i === 3 ? (token.number / 100) * max : Math.round((token.number / 100) * max); } return 0; }; const rgb = (args: CSSValue[]): number => { const tokens = args.filter(nonFunctionArgSeparator); if (tokens.length === 3) { const [r, g, b] = tokens.map(getTokenColorValue); return pack(r, g, b, 1); } if (tokens.length === 4) { const [r, g, b, a] = tokens.map(getTokenColorValue); return pack(r, g, b, a); } return 0; }; function hue2rgb(t1: number, t2: number, hue: number): number { if (hue < 0) { hue += 1; } if (hue >= 1) { hue -= 1; } if (hue < 1 / 6) { return (t2 - t1) * hue * 6 + t1; } else if (hue < 1 / 2) { return t2; } else if (hue < 2 / 3) { return (t2 - t1) * 6 * (2 / 3 - hue) + t1; } else { return t1; } } const hsl = (args: CSSValue[]): number => { const tokens = args.filter(nonFunctionArgSeparator); const [hue, saturation, lightness, alpha] = tokens; const h = (hue.type === TokenType.NUMBER_TOKEN ? deg(hue.number) : angle.parse(hue)) / (Math.PI * 2); const s = isLengthPercentage(saturation) ? saturation.number / 100 : 0; const l = isLengthPercentage(lightness) ? lightness.number / 100 : 0; const a = typeof alpha !== 'undefined' && isLengthPercentage(alpha) ? getAbsoluteValue(alpha, 1) : 1; if (s === 0) { return pack(l * 255, l * 255, l * 255, 1); } const t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; const t1 = l * 2 - t2; const r = hue2rgb(t1, t2, h + 1 / 3); const g = hue2rgb(t1, t2, h); const b = hue2rgb(t1, t2, h - 1 / 3); return pack(r * 255, g * 255, b * 255, a); }; const SUPPORTED_COLOR_FUNCTIONS: { [key: string]: (args: CSSValue[]) => number; } = { hsl: hsl, hsla: hsl, rgb: rgb, rgba: rgb }; export const COLORS: {[key: string]: Color} = { ALICEBLUE: 0xf0f8ffff, ANTIQUEWHITE: 0xfaebd7ff, AQUA: 0x00ffffff, AQUAMARINE: 0x7fffd4ff, AZURE: 0xf0ffffff, BEIGE: 0xf5f5dcff, BISQUE: 0xffe4c4ff, BLACK: 0x000000ff, BLANCHEDALMOND: 0xffebcdff, BLUE: 0x0000ffff, BLUEVIOLET: 0x8a2be2ff, BROWN: 0xa52a2aff, BURLYWOOD: 0xdeb887ff, CADETBLUE: 0x5f9ea0ff, CHARTREUSE: 0x7fff00ff, CHOCOLATE: 0xd2691eff, CORAL: 0xff7f50ff, CORNFLOWERBLUE: 0x6495edff, CORNSILK: 0xfff8dcff, CRIMSON: 0xdc143cff, CYAN: 0x00ffffff, DARKBLUE: 0x00008bff, DARKCYAN: 0x008b8bff, DARKGOLDENROD: 0xb886bbff, DARKGRAY: 0xa9a9a9ff, DARKGREEN: 0x006400ff, DARKGREY: 0xa9a9a9ff, DARKKHAKI: 0xbdb76bff, DARKMAGENTA: 0x8b008bff, DARKOLIVEGREEN: 0x556b2fff, DARKORANGE: 0xff8c00ff, DARKORCHID: 0x9932ccff, DARKRED: 0x8b0000ff, DARKSALMON: 0xe9967aff, DARKSEAGREEN: 0x8fbc8fff, DARKSLATEBLUE: 0x483d8bff, DARKSLATEGRAY: 0x2f4f4fff, DARKSLATEGREY: 0x2f4f4fff, DARKTURQUOISE: 0x00ced1ff, DARKVIOLET: 0x9400d3ff, DEEPPINK: 0xff1493ff, DEEPSKYBLUE: 0x00bfffff, DIMGRAY: 0x696969ff, DIMGREY: 0x696969ff, DODGERBLUE: 0x1e90ffff, FIREBRICK: 0xb22222ff, FLORALWHITE: 0xfffaf0ff, FORESTGREEN: 0x228b22ff, FUCHSIA: 0xff00ffff, GAINSBORO: 0xdcdcdcff, GHOSTWHITE: 0xf8f8ffff, GOLD: 0xffd700ff, GOLDENROD: 0xdaa520ff, GRAY: 0x808080ff, GREEN: 0x008000ff, GREENYELLOW: 0xadff2fff, GREY: 0x808080ff, HONEYDEW: 0xf0fff0ff, HOTPINK: 0xff69b4ff, INDIANRED: 0xcd5c5cff, INDIGO: 0x4b0082ff, IVORY: 0xfffff0ff, KHAKI: 0xf0e68cff, LAVENDER: 0xe6e6faff, LAVENDERBLUSH: 0xfff0f5ff, LAWNGREEN: 0x7cfc00ff, LEMONCHIFFON: 0xfffacdff, LIGHTBLUE: 0xadd8e6ff, LIGHTCORAL: 0xf08080ff, LIGHTCYAN: 0xe0ffffff, LIGHTGOLDENRODYELLOW: 0xfafad2ff, LIGHTGRAY: 0xd3d3d3ff, LIGHTGREEN: 0x90ee90ff, LIGHTGREY: 0xd3d3d3ff, LIGHTPINK: 0xffb6c1ff, LIGHTSALMON: 0xffa07aff, LIGHTSEAGREEN: 0x20b2aaff, LIGHTSKYBLUE: 0x87cefaff, LIGHTSLATEGRAY: 0x778899ff, LIGHTSLATEGREY: 0x778899ff, LIGHTSTEELBLUE: 0xb0c4deff, LIGHTYELLOW: 0xffffe0ff, LIME: 0x00ff00ff, LIMEGREEN: 0x32cd32ff, LINEN: 0xfaf0e6ff, MAGENTA: 0xff00ffff, MAROON: 0x800000ff, MEDIUMAQUAMARINE: 0x66cdaaff, MEDIUMBLUE: 0x0000cdff, MEDIUMORCHID: 0xba55d3ff, MEDIUMPURPLE: 0x9370dbff, MEDIUMSEAGREEN: 0x3cb371ff, MEDIUMSLATEBLUE: 0x7b68eeff, MEDIUMSPRINGGREEN: 0x00fa9aff, MEDIUMTURQUOISE: 0x48d1ccff, MEDIUMVIOLETRED: 0xc71585ff, MIDNIGHTBLUE: 0x191970ff, MINTCREAM: 0xf5fffaff, MISTYROSE: 0xffe4e1ff, MOCCASIN: 0xffe4b5ff, NAVAJOWHITE: 0xffdeadff, NAVY: 0x000080ff, OLDLACE: 0xfdf5e6ff, OLIVE: 0x808000ff, OLIVEDRAB: 0x6b8e23ff, ORANGE: 0xffa500ff, ORANGERED: 0xff4500ff, ORCHID: 0xda70d6ff, PALEGOLDENROD: 0xeee8aaff, PALEGREEN: 0x98fb98ff, PALETURQUOISE: 0xafeeeeff, PALEVIOLETRED: 0xdb7093ff, PAPAYAWHIP: 0xffefd5ff, PEACHPUFF: 0xffdab9ff, PERU: 0xcd853fff, PINK: 0xffc0cbff, PLUM: 0xdda0ddff, POWDERBLUE: 0xb0e0e6ff, PURPLE: 0x800080ff, REBECCAPURPLE: 0x663399ff, RED: 0xff0000ff, ROSYBROWN: 0xbc8f8fff, ROYALBLUE: 0x4169e1ff, SADDLEBROWN: 0x8b4513ff, SALMON: 0xfa8072ff, SANDYBROWN: 0xf4a460ff, SEAGREEN: 0x2e8b57ff, SEASHELL: 0xfff5eeff, SIENNA: 0xa0522dff, SILVER: 0xc0c0c0ff, SKYBLUE: 0x87ceebff, SLATEBLUE: 0x6a5acdff, SLATEGRAY: 0x708090ff, SLATEGREY: 0x708090ff, SNOW: 0xfffafaff, SPRINGGREEN: 0x00ff7fff, STEELBLUE: 0x4682b4ff, TAN: 0xd2b48cff, TEAL: 0x008080ff, THISTLE: 0xd8bfd8ff, TOMATO: 0xff6347ff, TRANSPARENT: 0x00000000, TURQUOISE: 0x40e0d0ff, VIOLET: 0xee82eeff, WHEAT: 0xf5deb3ff, WHITE: 0xffffffff, WHITESMOKE: 0xf5f5f5ff, YELLOW: 0xffff00ff, YELLOWGREEN: 0x9acd32ff };