You are here

Color.php in SCSS Compiler 1.0.x

File

src/Type/Color.php
View source
<?php

namespace Drupal\compiler_scss\Type;


/**
 * Defines a Color type used for type juggling in PHP-bridged functions.
 *
 * Copyright (C) 2021  Library Solutions, LLC (et al.).
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
class Color {
  const HEXCODE_EXPR = <<<'REGEX'
  /^#(?P<hexcode>(?P<hex>[0-9A-F]){3,4}|(?&hex){6}|(?&hex){8})$/i
REGEX;
  const KEYWORD_VALUES = [
    'aliceblue' => '#f0f8ff',
    'antiquewhite' => '#faebd7',
    'aqua' => '#00ffff',
    'aquamarine' => '#7fffd4',
    'azure' => '#f0ffff',
    'beige' => '#f5f5dc',
    'bisque' => '#ffe4c4',
    'black' => '#000000',
    'blanchedalmond' => '#ffebcd',
    'blue' => '#0000ff',
    'blueviolet' => '#8a2be2',
    'brown' => '#a52a2a',
    'burlywood' => '#deb887',
    'cadetblue' => '#5f9ea0',
    'chartreuse' => '#7fff00',
    'chocolate' => '#d2691e',
    'coral' => '#ff7f50',
    'cornflowerblue' => '#6495ed',
    'cornsilk' => '#fff8dc',
    'crimson' => '#dc143c',
    'cyan' => '#00ffff',
    'darkblue' => '#00008b',
    'darkcyan' => '#008b8b',
    'darkgoldenrod' => '#b8860b',
    'darkgray' => '#a9a9a9',
    'darkgreen' => '#006400',
    'darkgrey' => '#a9a9a9',
    'darkkhaki' => '#bdb76b',
    'darkmagenta' => '#8b008b',
    'darkolivegreen' => '#556b2f',
    'darkorange' => '#ff8c00',
    'darkorchid' => '#9932cc',
    'darkred' => '#8b0000',
    'darksalmon' => '#e9967a',
    'darkseagreen' => '#8fbc8f',
    'darkslateblue' => '#483d8b',
    'darkslategray' => '#2f4f4f',
    'darkslategrey' => '#2f4f4f',
    'darkturquoise' => '#00ced1',
    'darkviolet' => '#9400d3',
    'deeppink' => '#ff1493',
    'deepskyblue' => '#00bfff',
    'dimgray' => '#696969',
    'dimgrey' => '#696969',
    'dodgerblue' => '#1e90ff',
    'firebrick' => '#b22222',
    'floralwhite' => '#fffaf0',
    'forestgreen' => '#228b22',
    'fuchsia' => '#ff00ff',
    'gainsboro' => '#dcdcdc',
    'ghostwhite' => '#f8f8ff',
    'goldenrod' => '#daa520',
    'gold' => '#ffd700',
    'gray' => '#808080',
    'green' => '#008000',
    'greenyellow' => '#adff2f',
    'grey' => '#808080',
    'honeydew' => '#f0fff0',
    'hotpink' => '#ff69b4',
    'indianred' => '#cd5c5c',
    'indigo' => '#4b0082',
    'ivory' => '#fffff0',
    'khaki' => '#f0e68c',
    'lavenderblush' => '#fff0f5',
    'lavender' => '#e6e6fa',
    'lawngreen' => '#7cfc00',
    'lemonchiffon' => '#fffacd',
    'lightblue' => '#add8e6',
    'lightcoral' => '#f08080',
    'lightcyan' => '#e0ffff',
    'lightgoldenrodyellow' => '#fafad2',
    'lightgray' => '#d3d3d3',
    'lightgreen' => '#90ee90',
    'lightgrey' => '#d3d3d3',
    'lightpink' => '#ffb6c1',
    'lightsalmon' => '#ffa07a',
    'lightseagreen' => '#20b2aa',
    'lightskyblue' => '#87cefa',
    'lightslategray' => '#778899',
    'lightslategrey' => '#778899',
    'lightsteelblue' => '#b0c4de',
    'lightyellow' => '#ffffe0',
    'lime' => '#00ff00',
    'limegreen' => '#32cd32',
    'linen' => '#faf0e6',
    'magenta' => '#ff00ff',
    'maroon' => '#800000',
    'mediumaquamarine' => '#66cdaa',
    'mediumblue' => '#0000cd',
    'mediumorchid' => '#ba55d3',
    'mediumpurple' => '#9370db',
    'mediumseagreen' => '#3cb371',
    'mediumslateblue' => '#7b68ee',
    'mediumspringgreen' => '#00fa9a',
    'mediumturquoise' => '#48d1cc',
    'mediumvioletred' => '#c71585',
    'midnightblue' => '#191970',
    'mintcream' => '#f5fffa',
    'mistyrose' => '#ffe4e1',
    'moccasin' => '#ffe4b5',
    'navajowhite' => '#ffdead',
    'navy' => '#000080',
    'oldlace' => '#fdf5e6',
    'olive' => '#808000',
    'olivedrab' => '#6b8e23',
    'orange' => '#ffa500',
    'orangered' => '#ff4500',
    'orchid' => '#da70d6',
    'palegoldenrod' => '#eee8aa',
    'palegreen' => '#98fb98',
    'paleturquoise' => '#afeeee',
    'palevioletred' => '#db7093',
    'papayawhip' => '#ffefd5',
    'peachpuff' => '#ffdab9',
    'peru' => '#cd853f',
    'pink' => '#ffc0cb',
    'plum' => '#dda0dd',
    'powderblue' => '#b0e0e6',
    'purple' => '#800080',
    'rebeccapurple' => '#663399',
    'red' => '#ff0000',
    'rosybrown' => '#bc8f8f',
    'royalblue' => '#4169e1',
    'saddlebrown' => '#8b4513',
    'salmon' => '#fa8072',
    'sandybrown' => '#f4a460',
    'seagreen' => '#2e8b57',
    'seashell' => '#fff5ee',
    'sienna' => '#a0522d',
    'silver' => '#c0c0c0',
    'skyblue' => '#87ceeb',
    'slateblue' => '#6a5acd',
    'slategray' => '#708090',
    'slategrey' => '#708090',
    'snow' => '#fffafa',
    'springgreen' => '#00ff7f',
    'steelblue' => '#4682b4',
    'tan' => '#d2b48c',
    'teal' => '#008080',
    'thistle' => '#d8bfd8',
    'tomato' => '#ff6347',
    'turquoise' => '#40e0d0',
    'violet' => '#ee82ee',
    'wheat' => '#f5deb3',
    'white' => '#ffffff',
    'whitesmoke' => '#f5f5f5',
    'yellow' => '#ffff00',
    'yellowgreen' => '#9acd32',
  ];

  /**
   * The red channel value in the range [0,255].
   *
   * @var int
   */
  protected $r = 0;

  /**
   * The green channel value in the range [0,255].
   *
   * @var int
   */
  protected $g = 0;

  /**
   * The blue channel value in the range [0,255].
   *
   * @var int
   */
  protected $b = 0;

  /**
   * The alpha channel value in the range [0,255].
   *
   * @var int
   */
  protected $a = 0;

  /**
   * Constructs a Color object.
   *
   * This constructor accepts an RGB channel value in the sRGB color space in
   * the range [0,255] (inclusive) and an alpha channel value in the range [0,1]
   * (inclusive) as a real number.
   *
   * @param int $r
   *   The red channel value in the range [0,255].
   * @param int $g
   *   The green channel value in the range [0,255].
   * @param int $b
   *   The blue channel value in the range [0,255].
   * @param float $a
   *   The alpha channel value in the range [0,1].
   *
   * @throws \UnexpectedValueException
   *   If an invalid channel value was supplied.
   */
  public function __construct(int $r, int $g, int $b, float $a = 1) {
    $invalid = function ($channel) {
      return $channel < 0 || $channel > 255;
    };
    if (!empty(array_filter([
      $r,
      $g,
      $b,
    ], $invalid))) {
      throw new \UnexpectedValueException('Each color channel must be in the range [0,255] (inclusive)');
    }
    if ($a < 0 || $a > 1) {
      throw new \UnexpectedValueException('Alpha channel must be in the range [0,1] (inclusive)');
    }
    $this->r = $r;
    $this->g = $g;
    $this->b = $b;
    $this->a = intval($a * 255);
  }

  /**
   * Convert this color to its hexadecimal representation.
   *
   * @see ::toHex()
   *   The method aliased by this method.
   *
   * @return string
   *   The hexadecimal representation of this color.
   */
  public function __toString() : string {
    return $this
      ->toHex();
  }

  /**
   * Get the alpha channel value.
   *
   * @return float
   *   The alpha channel value in the range [0,1].
   */
  public function a() : float {
    return $this->a / 255;
  }

  /**
   * Fetch the argument list used to construct this color.
   *
   * This method will omit the alpha channel if it's using the default value.
   *
   * @return array
   *   An ordered array of arguments used to construct this color (R, G, B, A).
   */
  public function args() : array {
    $args = [
      $this
        ->r(),
      $this
        ->g(),
      $this
        ->b(),
      $this
        ->a(),
    ];

    // Double equals since we want to compare float and int.
    if (end($args) == 1) {
      array_pop($args);
    }
    return $args;
  }

  /**
   * Get the blue channel value.
   *
   * @return float
   *   The blue channel value in the range [0,255].
   */
  public function b() : float {
    return $this->b;
  }

  /**
   * Construct a Color object from a hex code.
   *
   * @param string $input
   *   A hexadecimal color code with a prefix of '#' (without quotes). May
   *   contain six or eight hex digits.
   *
   * @return static
   *   A Color instance for the supplied hex code.
   */
  public static function fromHex(string $input) : self {

    // Ensure that the hex code is valid before attempting to parse it.
    if (preg_match(self::HEXCODE_EXPR, $input, $matches) === 1) {
      $result = [];

      // Parse differently based on the length of the hex code.
      switch (strlen($matches['hexcode'])) {
        case 3:
        case 4:

          // This is a short hex code; multiply each channel by 17.
          $result = array_map(function ($channel) {
            return $channel !== NULL ? 17 * $channel : NULL;
          }, sscanf($matches['hexcode'], '%1x%1x%1x%1x'));
          break;
        case 6:
        case 8:

          // This is a regular hex code; just scan in the values.
          $result = sscanf($matches['hexcode'], '%2x%2x%2x%2x');
          break;
      }

      // Convert the alpha channel to a percent.
      if (is_numeric($alpha = array_pop($result))) {

        // Ensure that the percent value is on the interval [0,1].
        $result[] = max(0, min(1, $alpha / 255));
      }

      // Filter out any NULL values from sscanf() and get an ordered list of
      // arguments to pass to the constructor.
      $result = array_values(array_filter($result, function ($channel) {
        return $channel !== NULL;
      }));

      // Construct a new Color instance using the resulting argument list.
      return new static(...$result);
    }
    throw new \InvalidArgumentException('Invalid hex code');
  }

  /**
   * Creates an intermediate color from its keyword name.
   *
   * @link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
   *
   * @param string $name
   *   The name of the color as defined in the linked reference material.
   *
   * @throws \UnexpectedValueException
   *   If an invalid color keyword name was supplied.
   *
   * @return static
   *   A Color instance for the supplied keyword name.
   */
  public static function fromName(string $name) : self {
    if (!array_key_exists($name, self::KEYWORD_VALUES)) {
      throw new \UnexpectedValueException('Unknown color keyword name');
    }
    return self::fromHex(self::KEYWORD_VALUES[$name]);
  }

  /**
   * Get the green channel value.
   *
   * @return float
   *   The green channel value in the range [0,255].
   */
  public function g() : float {
    return $this->g;
  }

  /**
   * Get the red channel value.
   *
   * @return float
   *   The red channel value in the range [0,255].
   */
  public function r() : float {
    return $this->r;
  }

  /**
   * Convert this color to its hexadecimal representation.
   *
   * @return string
   *   The hexadecimal representation of this color.
   */
  public function toHex() : string {

    // Create an array of channel values for this color.
    $channels = [
      'r' => $this->r,
      'g' => $this->g,
      'b' => $this->b,
      'a' => $this->a,
    ];

    // Remove the alpha channel if it's not needed. Double equals since we want
    // to compare float and int.
    if ($channels['a'] == 255) {
      unset($channels['a']);
    }

    // Convert each channel into its hexadecimal representation.
    $channels = array_map(function (int $channel) {
      return sprintf('%02x', $channel);
    }, $channels);

    // Create a hex code using the individual channel values.
    return '#' . implode('', $channels);
  }

}

Classes

Namesort descending Description
Color Defines a Color type used for type juggling in PHP-bridged functions.