| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 | <?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\Console;use Symfony\Component\Console\Exception\InvalidArgumentException;/** * @author Fabien Potencier <fabien@symfony.com> */final class Color{    private const COLORS = [        'black' => 0,        'red' => 1,        'green' => 2,        'yellow' => 3,        'blue' => 4,        'magenta' => 5,        'cyan' => 6,        'white' => 7,        'default' => 9,    ];    private const BRIGHT_COLORS = [        'gray' => 0,        'bright-red' => 1,        'bright-green' => 2,        'bright-yellow' => 3,        'bright-blue' => 4,        'bright-magenta' => 5,        'bright-cyan' => 6,        'bright-white' => 7,    ];    private const AVAILABLE_OPTIONS = [        'bold' => ['set' => 1, 'unset' => 22],        'underscore' => ['set' => 4, 'unset' => 24],        'blink' => ['set' => 5, 'unset' => 25],        'reverse' => ['set' => 7, 'unset' => 27],        'conceal' => ['set' => 8, 'unset' => 28],    ];    private $foreground;    private $background;    private $options = [];    public function __construct(string $foreground = '', string $background = '', array $options = [])    {        $this->foreground = $this->parseColor($foreground);        $this->background = $this->parseColor($background, true);        foreach ($options as $option) {            if (!isset(self::AVAILABLE_OPTIONS[$option])) {                throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));            }            $this->options[$option] = self::AVAILABLE_OPTIONS[$option];        }    }    public function apply(string $text): string    {        return $this->set().$text.$this->unset();    }    public function set(): string    {        $setCodes = [];        if ('' !== $this->foreground) {            $setCodes[] = $this->foreground;        }        if ('' !== $this->background) {            $setCodes[] = $this->background;        }        foreach ($this->options as $option) {            $setCodes[] = $option['set'];        }        if (0 === \count($setCodes)) {            return '';        }        return sprintf("\033[%sm", implode(';', $setCodes));    }    public function unset(): string    {        $unsetCodes = [];        if ('' !== $this->foreground) {            $unsetCodes[] = 39;        }        if ('' !== $this->background) {            $unsetCodes[] = 49;        }        foreach ($this->options as $option) {            $unsetCodes[] = $option['unset'];        }        if (0 === \count($unsetCodes)) {            return '';        }        return sprintf("\033[%sm", implode(';', $unsetCodes));    }    private function parseColor(string $color, bool $background = false): string    {        if ('' === $color) {            return '';        }        if ('#' === $color[0]) {            $color = substr($color, 1);            if (3 === \strlen($color)) {                $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];            }            if (6 !== \strlen($color)) {                throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));            }            return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color));        }        if (isset(self::COLORS[$color])) {            return ($background ? '4' : '3').self::COLORS[$color];        }        if (isset(self::BRIGHT_COLORS[$color])) {            return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];        }        throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));    }    private function convertHexColorToAnsi(int $color): string    {        $r = ($color >> 16) & 255;        $g = ($color >> 8) & 255;        $b = $color & 255;        // see https://github.com/termstandard/colors/ for more information about true color support        if ('truecolor' !== getenv('COLORTERM')) {            return (string) $this->degradeHexColorToAnsi($r, $g, $b);        }        return sprintf('8;2;%d;%d;%d', $r, $g, $b);    }    private function degradeHexColorToAnsi(int $r, int $g, int $b): int    {        if (0 === round($this->getSaturation($r, $g, $b) / 50)) {            return 0;        }        return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);    }    private function getSaturation(int $r, int $g, int $b): int    {        $r = $r / 255;        $g = $g / 255;        $b = $b / 255;        $v = max($r, $g, $b);        if (0 === $diff = $v - min($r, $g, $b)) {            return 0;        }        return (int) $diff * 100 / $v;    }}
 |