text: /*
   * Copyright (C) 2009 Apple Inc.  All rights reserved.
   * Copyright (C) 2009 Joseph Pecoraro
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1.  Redistributions of source code must retain the above copyright
   *     notice, this list of conditions and the following disclaimer.
   * 2.  Redistributions in binary form must reproduce the above copyright
   *     notice, this list of conditions and the following disclaimer in the
   *     documentation and/or other materials provided with the distribution.
   * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
   *     its contributors may be used to endorse or promote products derived
   *     from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  /**
   * @unrestricted
   */
  export default class Color {
    /**
     * @param {!Array.<number>} rgba
     * @param {!Format} format
     * @param {string=} originalText
     */
    constructor(rgba, format, originalText) {
      this._rgba = rgba;
      this._originalText = originalText || null;
      this._originalTextIsValid = !!this._originalText;
      this._format = format;
      if (typeof this._rgba[3] === 'undefined')
        this._rgba[3] = 1;
  
      for (let i = 0; i < 4; ++i) {
        if (this._rgba[i] < 0) {
          this._rgba[i] = 0;
          this._originalTextIsValid = false;
        }
        if (this._rgba[i] > 1) {
          this._rgba[i] = 1;
          this._originalTextIsValid = false;
        }
      }
    }
  
    /**
     * @param {string} text
     * @return {?Color}
     */
    static parse(text) {
      // Simple - #hex, nickname
      const value = text.toLowerCase().replace(/\s+/g, '');
      const simple = /^(?:#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})|(\w+))$/i;
      let match = value.match(simple);
      if (match) {
        if (match[1]) {  // hex
          let hex = match[1].toLowerCase();
          let format;
          if (hex.length === 3) {
            format = Format.ShortHEX;
            hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
          } else if (hex.length === 4) {
            format = Format.ShortHEXA;
            hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2) +
                hex.charAt(3) + hex.charAt(3);
          } else if (hex.length === 6) {
            format = Format.HEX;
          } else {
            format = Format.HEXA;
          }
          const r = parseInt(hex.substring(0, 2), 16);
          const g = parseInt(hex.substring(2, 4), 16);
          const b = parseInt(hex.substring(4, 6), 16);
          let a = 1;
          if (hex.length === 8)
            a = parseInt(hex.substring(6, 8), 16) / 255;
          return new Color([r / 255, g / 255, b / 255, a], format, text);
        }
  
        if (match[2]) {  // nickname
          const nickname = match[2].toLowerCase();
          if (nickname in Nicknames) {
            const rgba = Nicknames[nickname];
            const color = Color.fromRGBA(rgba);
            color._format = Format.Nickname;
            color._originalText = text;
            return color;
          }
          return null;
        }
  
        return null;
      }
  
      // rgb/rgba(), hsl/hsla()
      match = text.toLowerCase().match(/^\s*(?:(rgba?)|(hsla?))\((.*)\)\s*$/);
  
      if (match) {
        const components = match[3].trim();
        let values = components.split(/\s*,\s*/);
        if (values.length === 1) {
          values = components.split(/\s+/);
          if (values[3] === '/') {
            values.splice(3, 1);
            if (values.length !== 4)
              return null;
          } else if ((values.length > 2 && values[2].indexOf('/') !== -1) || (values.length > 3 && values[3].indexOf('/') !== -1)) {
            const alpha = values.slice(2, 4).join('');
            values = values.slice(0, 2).concat(alpha.split(/\//)).concat(values.slice(4));
          } else if (values.length >= 4) {
            return null;
          }
        }
        if (values.length !== 3 && values.length !== 4 || values.indexOf('') > -1)
          return null;
        const hasAlpha = (values[3] !== undefined);
  
        if (match[1]) {  // rgb/rgba
          const rgba = [
            Color._parseRgbNumeric(values[0]), Color._parseRgbNumeric(values[1]), Color._parseRgbNumeric(values[2]),
            hasAlpha ? Color._parseAlphaNumeric(values[3]) : 1
          ];
          if (rgba.indexOf(null) > -1)
            return null;
          return new Color(rgba, hasAlpha ? Format.RGBA : Format.RGB, text);
        }
  
        if (match[2]) {  // hsl/hsla
          const hsla = [
            Color._parseHueNumeric(values[0]), Color._parseSatLightNumeric(values[1]),
            Color._parseSatLightNumeric(values[2]), hasAlpha ? Color._parseAlphaNumeric(values[3]) : 1
          ];
          if (hsla.indexOf(null) > -1)
            return null;
          const rgba = [];
          Color.hsl2rgb(hsla, rgba);
          return new Color(rgba, hasAlpha ? Format.HSLA : Format.HSL, text);
        }
      }
  
      return null;
    }
  
    /**
     * @param {!Array.<number>} rgba
     * @return {!Color}
     */
    static fromRGBA(rgba) {
      return new Color([rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3]], Format.RGBA);
    }
  
    /**
     * @param {!Array.<number>} hsva
     * @return {!Color}
     */
    static fromHSVA(hsva) {
      const rgba = [];
      Color.hsva2rgba(hsva, rgba);
      return new Color(rgba, Format.HSLA);
    }
  
    /**
     * @param {string} value
     * return {number}
     */
    static _parsePercentOrNumber(value) {
      if (isNaN(value.replace('%', '')))
        return null;
      const parsed = parseFloat(value);
  
      if (value.indexOf('%') !== -1) {
        if (value.indexOf('%') !== value.length - 1)
          return null;
        return parsed / 100;
      }
      return parsed;
    }
  
    /**
     * @param {string} value
     * return {number}
     */
    static _parseRgbNumeric(value) {
      const parsed = Color._parsePercentOrNumber(value);
      if (parsed === null)
        return null;
  
      if (value.indexOf('%') !== -1)
        return parsed;
      return parsed / 255;
    }
  
    /**
     * @param {string} value
     * return {number}
     */
    static _parseHueNumeric(value) {
      const angle = value.replace(/(deg|g?rad|turn)$/, '');
      if (isNaN(angle) || value.match(/\s+(deg|g?rad|turn)/))
        return null;
      const number = parseFloat(angle);
  
      if (value.indexOf('turn') !== -1)
        return number % 1;
      else if (value.indexOf('grad') !== -1)
        return (number / 400) % 1;
      else if (value.indexOf('rad') !== -1)
        return (number / (2 * Math.PI)) % 1;
      return (number / 360) % 1;
    }
  
    /**
     * @param {string} value
     * return {number}
     */
    static _parseSatLightNumeric(value) {
      if (value.indexOf('%') !== value.length - 1 || isNaN(value.replace('%', '')))
        return null;
      const parsed = parseFloat(value);
      return Math.min(1, parsed / 100);
    }
  
    /**
     * @param {string} value
     * return {number}
     */
    static _parseAlphaNumeric(value) {
      return Color._parsePercentOrNumber(value);
    }
  
    /**
     * @param {!Array.<number>} hsva
     * @param {!Array.<number>} out_hsla
     */
    static _hsva2hsla(hsva, out_hsla) {
      const h = hsva[0];
      let s = hsva[1];
      const v = hsva[2];
  
      const t = (2 - s) * v;
      if (v === 0 || s === 0)
        s = 0;
      else
        s *= v / (t < 1 ? t : 2 - t);
  
      out_hsla[0] = h;
      out_hsla[1] = s;
      out_hsla[2] = t / 2;
      out_hsla[3] = hsva[3];
    }
  
    /**
     * @param {!Array.<number>} hsl
     * @param {!Array.<number>} out_rgb
     */
    static hsl2rgb(hsl, out_rgb) {
      const h = hsl[0];
      let s = hsl[1];
      const l = hsl[2];
  
      function hue2rgb(p, q, h) {
        if (h < 0)
          h += 1;
        else if (h > 1)
          h -= 1;
  
        if ((h * 6) < 1)
          return p + (q - p) * h * 6;
        else if ((h * 2) < 1)
          return q;
        else if ((h * 3) < 2)
          return p + (q - p) * ((2 / 3) - h) * 6;
        else
          return p;
      }
  
      if (s < 0)
        s = 0;
  
      let q;
      if (l <= 0.5)
        q = l * (1 + s);
      else
        q = l + s - (l * s);
  
      const p = 2 * l - q;
  
      const tr = h + (1 / 3);
      const tg = h;
      const tb = h - (1 / 3);
  
      out_rgb[0] = hue2rgb(p, q, tr);
      out_rgb[1] = hue2rgb(p, q, tg);
      out_rgb[2] = hue2rgb(p, q, tb);
      out_rgb[3] = hsl[3];
    }
  
    /**
     * @param {!Array<number>} hsva
     * @param {!Array<number>} out_rgba
     */
    static hsva2rgba(hsva, out_rgba) {
      Color._hsva2hsla(hsva, Color.hsva2rgba._tmpHSLA);
      Color.hsl2rgb(Color.hsva2rgba._tmpHSLA, out_rgba);
  
      for (let i = 0; i < Color.hsva2rgba._tmpHSLA.length; i++)
        Color.hsva2rgba._tmpHSLA[i] = 0;
    }
  
    /**
     * Calculate the luminance of this color using the WCAG algorithm.
     * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
     * @param {!Array<number>} rgba
     * @return {number}
     */
    static luminance(rgba) {
      const rSRGB = rgba[0];
      const gSRGB = rgba[1];
      const bSRGB = rgba[2];
  
      const r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055) / 1.055), 2.4);
      const g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055) / 1.055), 2.4);
      const b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055) / 1.055), 2.4);
  
      return 0.2126 * r + 0.7152 * g + 0.0722 * b;
    }
  
    /**
     * Combine the two given color according to alpha blending.
     * @param {!Array<number>} fgRGBA
     * @param {!Array<number>} bgRGBA
     * @param {!Array<number>} out_blended
     */
    static blendColors(fgRGBA, bgRGBA, out_blended) {
      const alpha = fgRGBA[3];
  
      out_blended[0] = ((1 - alpha) * bgRGBA[0]) + (alpha * fgRGBA[0]);
      out_blended[1] = ((1 - alpha) * bgRGBA[1]) + (alpha * fgRGBA[1]);
      out_blended[2] = ((1 - alpha) * bgRGBA[2]) + (alpha * fgRGBA[2]);
      out_blended[3] = alpha + (bgRGBA[3] * (1 - alpha));
    }
  
    /**
     * Calculate the contrast ratio between a foreground and a background color.
     * Returns the ratio to 1, for example for two two colors with a contrast ratio of 21:1, this function will return 21.
     * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
     * @param {!Array<number>} fgRGBA
     * @param {!Array<number>} bgRGBA
     * @return {number}
     */
    static calculateContrastRatio(fgRGBA, bgRGBA) {
      Color.blendColors(fgRGBA, bgRGBA, Color.calculateContrastRatio._blendedFg);
  
      const fgLuminance = Color.luminance(Color.calculateContrastRatio._blendedFg);
      const bgLuminance = Color.luminance(bgRGBA);
      const contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) / (Math.min(fgLuminance, bgLuminance) + 0.05);
  
      for (let i = 0; i < Color.calculateContrastRatio._blendedFg.length; i++)
        Color.calculateContrastRatio._blendedFg[i] = 0;
  
      return contrastRatio;
    }
  
    /**
     * Compute a desired luminance given a given luminance and a desired contrast
     * ratio.
     * @param {number} luminance The given luminance.
     * @param {number} contrast The desired contrast ratio.
     * @param {boolean} lighter Whether the desired luminance is lighter or darker
     * than the given luminance. If no luminance can be found which meets this
     * requirement, a luminance which meets the inverse requirement will be
     * returned.
     * @return {number} The desired luminance.
     */
    static desiredLuminance(luminance, contrast, lighter) {
      function computeLuminance() {
        if (lighter)
          return (luminance + 0.05) * contrast - 0.05;
        else
          return (luminance + 0.05) / contrast - 0.05;
      }
      let desiredLuminance = computeLuminance();
      if (desiredLuminance < 0 || desiredLuminance > 1) {
        lighter = !lighter;
        desiredLuminance = computeLuminance();
      }
      return desiredLuminance;
    }
  
    /**
     * @param {!Color} color
     * @return {!Format}
     */
    static detectColorFormat(color) {
      const cf = Format;
      let format;
      const formatSetting = Common.moduleSetting('colorFormat').get();
      if (formatSetting === cf.Original)
        format = cf.Original;
      else if (formatSetting === cf.RGB)
        format = (color.hasAlpha() ? cf.RGBA : cf.RGB);
      else if (formatSetting === cf.HSL)
        format = (color.hasAlpha() ? cf.HSLA : cf.HSL);
      else if (formatSetting === cf.HEX)
        format = color.detectHEXFormat();
      else
        format = cf.RGBA;
  
      return format;
    }
  
    /**
     * @return {!Format}
     */
    format() {
      return this._format;
    }
  
    /**
     * @return {!Array.<number>} HSLA with components within [0..1]
     */
    hsla() {
      if (this._hsla)
        return this._hsla;
      const r = this._rgba[0];
      const g = this._rgba[1];
      const b = this._rgba[2];
      const max = Math.max(r, g, b);
      const min = Math.min(r, g, b);
      const diff = max - min;
      const add = max + min;
  
      let h;
      if (min === max)
        h = 0;
      else if (r === max)
        h = ((1 / 6 * (g - b) / diff) + 1) % 1;
      else if (g === max)
        h = (1 / 6 * (b - r) / diff) + 1 / 3;
      else
        h = (1 / 6 * (r - g) / diff) + 2 / 3;
  
      const l = 0.5 * add;
  
      let s;
      if (l === 0)
        s = 0;
      else if (l === 1)
        s = 0;
      else if (l <= 0.5)
        s = diff / add;
      else
        s = diff / (2 - add);
  
      this._hsla = [h, s, l, this._rgba[3]];
      return this._hsla;
    }
  
    /**
     * @return {!Array.<number>}
     */
    canonicalHSLA() {
      const hsla = this.hsla();
      return [Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100), hsla[3]];
    }
  
    /**
     * @return {!Array.<number>} HSVA with components within [0..1]
     */
    hsva() {
      const hsla = this.hsla();
      const h = hsla[0];
      let s = hsla[1];
      const l = hsla[2];
  
      s *= l < 0.5 ? l : 1 - l;
      return [h, s !== 0 ? 2 * s / (l + s) : 0, (l + s), hsla[3]];
    }
  
    /**
     * @return {boolean}
     */
    hasAlpha() {
      return this._rgba[3] !== 1;
    }
  
    /**
     * @return {!Format}
     */
    detectHEXFormat() {
      let canBeShort = true;
      for (let i = 0; i < 4; ++i) {
        const c = Math.round(this._rgba[i] * 255);
        if (c % 17) {
          canBeShort = false;
          break;
        }
      }
  
      const hasAlpha = this.hasAlpha();
      const cf = Format;
      if (canBeShort)
        return hasAlpha ? cf.ShortHEXA : cf.ShortHEX;
      return hasAlpha ? cf.HEXA : cf.HEX;
    }
  
    /**
     * @return {?string}
     */
    asString(format) {
      if (format === this._format && this._originalTextIsValid)
        return this._originalText;
  
      if (!format)
        format = this._format;
  
      /**
       * @param {number} value
       * @return {number}
       */
      function toRgbValue(value) {
        return Math.round(value * 255);
      }
  
      /**
       * @param {number} value
       * @return {string}
       */
      function toHexValue(value) {
        const hex = Math.round(value * 255).toString(16);
        return hex.length === 1 ? '0' + hex : hex;
      }
  
      /**
       * @param {number} value
       * @return {string}
       */
      function toShortHexValue(value) {
        return (Math.round(value * 255) / 17).toString(16);
      }
  
      switch (format) {
        case Format.Original:
          return this._originalText;
        case Format.RGB:
          if (this.hasAlpha())
            return null;
          return String.sprintf(
              'rgb(%d, %d, %d)', toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]));
        case Format.RGBA:
          return String.sprintf(
              'rgba(%d, %d, %d, %f)', toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]),
              this._rgba[3]);
        case Format.HSL:
          if (this.hasAlpha())
            return null;
          const hsl = this.hsla();
          return String.sprintf(
              'hsl(%d, %d%, %d%)', Math.round(hsl[0] * 360), Math.round(hsl[1] * 100), Math.round(hsl[2] * 100));
        case Format.HSLA:
          const hsla = this.hsla();
          return String.sprintf(
              'hsla(%d, %d%, %d%, %f)', Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100),
              hsla[3]);
        case Format.HEXA:
          return String
              .sprintf(
                  '#%s%s%s%s', toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2]),
                  toHexValue(this._rgba[3]))
              .toLowerCase();
        case Format.HEX:
          if (this.hasAlpha())
            return null;
          return String
              .sprintf('#%s%s%s', toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2]))
              .toLowerCase();
        case Format.ShortHEXA:
          const hexFormat = this.detectHEXFormat();
          if (hexFormat !== Format.ShortHEXA && hexFormat !== Format.ShortHEX)
            return null;
          return String
              .sprintf(
                  '#%s%s%s%s', toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]),
                  toShortHexValue(this._rgba[2]), toShortHexValue(this._rgba[3]))
              .toLowerCase();
        case Format.ShortHEX:
          if (this.hasAlpha())
            return null;
          if (this.detectHEXFormat() !== Format.ShortHEX)
            return null;
          return String
              .sprintf(
                  '#%s%s%s', toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]),
                  toShortHexValue(this._rgba[2]))
              .toLowerCase();
        case Format.Nickname:
          return this.nickname();
      }
  
      return this._originalText;
    }
  
    /**
     * @return {!Array<number>}
     */
    rgba() {
      return this._rgba.slice();
    }
  
    /**
     * @return {!Array.<number>}
     */
    canonicalRGBA() {
      const rgba = new Array(4);
      for (let i = 0; i < 3; ++i)
        rgba[i] = Math.round(this._rgba[i] * 255);
      rgba[3] = this._rgba[3];
      return rgba;
    }
  
    /**
     * @return {?string} nickname
     */
    nickname() {
      if (!Color._rgbaToNickname) {
        Color._rgbaToNickname = {};
        for (const nickname in Nicknames) {
          let rgba = Nicknames[nickname];
          if (rgba.length !== 4)
            rgba = rgba.concat(1);
          Color._rgbaToNickname[rgba] = nickname;
        }
      }
  
      return Color._rgbaToNickname[this.canonicalRGBA()] || null;
    }
  
    /**
     * @return {!{r: number, g: number, b: number, a: (number|undefined)}}
     */
    toProtocolRGBA() {
      const rgba = this.canonicalRGBA();
      const result = {r: rgba[0], g: rgba[1], b: rgba[2]};
      if (rgba[3] !== 1)
        result.a = rgba[3];
      return result;
    }
  
    /**
     * @return {!Color}
     */
    invert() {
      const rgba = [];
      rgba[0] = 1 - this._rgba[0];
      rgba[1] = 1 - this._rgba[1];
      rgba[2] = 1 - this._rgba[2];
      rgba[3] = this._rgba[3];
      return new Color(rgba, Format.RGBA);
    }
  
    /**
     * @param {number} alpha
     * @return {!Color}
     */
    setAlpha(alpha) {
      const rgba = this._rgba.slice();
      rgba[3] = alpha;
      return new Color(rgba, Format.RGBA);
    }
  
    /**
     * @param {!Color} fgColor
     * @return {!Color}
     */
    blendWith(fgColor) {
      const rgba = [];
      Color.blendColors(fgColor._rgba, this._rgba, rgba);
      return new Color(rgba, Format.RGBA);
    }
  }
  
  /** @type {!RegExp} */
  export const Regex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{8}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,4}|\b[a-zA-Z]+\b(?!-))/g;
  
  /**
   * @enum {string}
   */
  export const Format = {
    Original: 'original',
    Nickname: 'nickname',
    HEX: 'hex',
    ShortHEX: 'shorthex',
    HEXA: 'hexa',
    ShortHEXA: 'shorthexa',
    RGB: 'rgb',
    RGBA: 'rgba',
    HSL: 'hsl',
    HSLA: 'hsla'
  };
  
  export const Nicknames = {
    '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': [237, 20, 61],
    'cyan': [0, 255, 255],
    'darkblue': [0, 0, 139],
    'darkcyan': [0, 139, 139],
    'darkgoldenrod': [184, 134, 11],
    'darkgray': [169, 169, 169],
    'darkgrey': [169, 169, 169],
    'darkgreen': [0, 100, 0],
    '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],
    'grey': [128, 128, 128],
    'green': [0, 128, 0],
    'greenyellow': [173, 255, 47],
    '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],
    'lightgreen': [144, 238, 144],
    'lightgray': [211, 211, 211],
    '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],
    'transparent': [0, 0, 0, 0],
  };
  
  export const PageHighlight = {
    Content: Color.fromRGBA([111, 168, 220, .66]),
    ContentLight: Color.fromRGBA([111, 168, 220, .5]),
    ContentOutline: Color.fromRGBA([9, 83, 148]),
    Padding: Color.fromRGBA([147, 196, 125, .55]),
    PaddingLight: Color.fromRGBA([147, 196, 125, .4]),
    Border: Color.fromRGBA([255, 229, 153, .66]),
    BorderLight: Color.fromRGBA([255, 229, 153, .5]),
    Margin: Color.fromRGBA([246, 178, 107, .66]),
    MarginLight: Color.fromRGBA([246, 178, 107, .5]),
    EventTarget: Color.fromRGBA([255, 196, 196, .66]),
    Shape: Color.fromRGBA([96, 82, 177, 0.8]),
    ShapeMargin: Color.fromRGBA([96, 82, 127, .6]),
    CssGrid: Color.fromRGBA([0x4b, 0, 0x82, 1])
  };
  
  export class Generator {
    /**
     * @param {!{min: number, max: number}|number=} hueSpace
     * @param {!{min: number, max: number, count: (number|undefined)}|number=} satSpace
     * @param {!{min: number, max: number, count: (number|undefined)}|number=} lightnessSpace
     * @param {!{min: number, max: number, count: (number|undefined)}|number=} alphaSpace
     */
    constructor(hueSpace, satSpace, lightnessSpace, alphaSpace) {
      this._hueSpace = hueSpace || {min: 0, max: 360};
      this._satSpace = satSpace || 67;
      this._lightnessSpace = lightnessSpace || 80;
      this._alphaSpace = alphaSpace || 1;
      /** @type {!Map<string, string>} */
      this._colors = new Map();
    }
  
    /**
     * @param {string} id
     * @param {string} color
     */
    setColorForID(id, color) {
      this._colors.set(id, color);
    }
  
    /**
     * @param {string} id
     * @return {string}
     */
    colorForID(id) {
      let color = this._colors.get(id);
      if (!color) {
        color = this._generateColorForID(id);
        this._colors.set(id, color);
      }
      return color;
    }
  
    /**
     * @param {string} id
     * @return {string}
     */
    _generateColorForID(id) {
      const hash = String.hashCode(id);
      const h = this._indexToValueInSpace(hash, this._hueSpace);
      const s = this._indexToValueInSpace(hash >> 8, this._satSpace);
      const l = this._indexToValueInSpace(hash >> 16, this._lightnessSpace);
      const a = this._indexToValueInSpace(hash >> 24, this._alphaSpace);
      return `hsla(${h}, ${s}%, ${l}%, ${a})`;
    }
  
    /**
     * @param {number} index
     * @param {!{min: number, max: number, count: (number|undefined)}|number} space
     * @return {number}
     */
    _indexToValueInSpace(index, space) {
      if (typeof space === 'number')
        return space;
      const count = space.count || space.max - space.min;
      index %= count;
      return space.min + Math.floor(index / (count - 1) * (space.max - space.min));
    }
  }
  
  /** @type {!Array<number>} */
  Color.hsva2rgba._tmpHSLA = [0, 0, 0, 0];
  
  Color.calculateContrastRatio._blendedFg = [0, 0, 0, 0];
  
  /* Legacy exported object */
  self.Common = self.Common || {};
  Common = Common || {};
  
  /**
   * @constructor
   */
  Common.Color = Color;
  
  Common.Color.Regex = Regex;
  
  /**
   * @enum {string}
   */
  Common.Color.Format = Format;
  Common.Color.Nicknames = Nicknames;
  Common.Color.PageHighlight = PageHighlight;
  
  /**
   * @constructor
   */
  Common.Color.Generator = Generator;
  