1<?php
2/**
3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14
15
16use dokuwiki\StyleUtils;
17
18class ColorRgb
19{
20
21    const COLOR = "color";
22
23    const BORDER_COLOR = "border-color";
24
25    const BOOTSTRAP_COLORS = array(
26        'blue',
27        'indigo',
28        'purple',
29        'pink',
30        'red',
31        'orange',
32        'yellow',
33        'green',
34        'teal',
35        'cyan',
36        //'white', css value for now otherwise we don't know the value when tinting
37        //'gray', css value for now otherwise we don't know the value when tinting
38        'gray-dark',
39        self::PRIMARY_VALUE,
40        self::SECONDARY_VALUE,
41        'success',
42        'info',
43        'warning',
44        'danger',
45        'light',
46        'dark'
47    );
48    /**
49     * https://drafts.csswg.org/css-color/#color-keywords
50     *
51     * See also: https://www.w3.org/TR/SVG11/types.html#ColorKeywords
52     */
53    const CSS_COLOR_NAMES = array(
54        'aliceblue' => '#F0F8FF',
55        'antiquewhite' => '#FAEBD7',
56        'aqua' => '#00FFFF',
57        'aquamarine' => '#7FFFD4',
58        'azure' => '#F0FFFF',
59        'beige' => '#F5F5DC',
60        'bisque' => '#FFE4C4',
61        'black' => '#000000',
62        'blanchedalmond' => '#FFEBCD',
63        'blue' => '#0000FF',
64        'blueviolet' => '#8A2BE2',
65        'brown' => '#A52A2A',
66        'burlywood' => '#DEB887',
67        'cadetblue' => '#5F9EA0',
68        'chartreuse' => '#7FFF00',
69        'chocolate' => '#D2691E',
70        'coral' => '#FF7F50',
71        'cornflowerblue' => '#6495ED',
72        'cornsilk' => '#FFF8DC',
73        'crimson' => '#DC143C',
74        'cyan' => '#00FFFF',
75        'darkblue' => '#00008B',
76        'darkcyan' => '#008B8B',
77        'darkgoldenrod' => '#B8860B',
78        'darkgray' => '#A9A9A9',
79        'darkgreen' => '#006400',
80        'darkgrey' => '#A9A9A9',
81        'darkkhaki' => '#BDB76B',
82        'darkmagenta' => '#8B008B',
83        'darkolivegreen' => '#556B2F',
84        'darkorange' => '#FF8C00',
85        'darkorchid' => '#9932CC',
86        'darkred' => '#8B0000',
87        'darksalmon' => '#E9967A',
88        'darkseagreen' => '#8FBC8F',
89        'darkslateblue' => '#483D8B',
90        'darkslategray' => '#2F4F4F',
91        'darkslategrey' => '#2F4F4F',
92        'darkturquoise' => '#00CED1',
93        'darkviolet' => '#9400D3',
94        'deeppink' => '#FF1493',
95        'deepskyblue' => '#00BFFF',
96        'dimgray' => '#696969',
97        'dimgrey' => '#696969',
98        'dodgerblue' => '#1E90FF',
99        'firebrick' => '#B22222',
100        'floralwhite' => '#FFFAF0',
101        'forestgreen' => '#228B22',
102        'fuchsia' => '#FF00FF',
103        'gainsboro' => '#DCDCDC',
104        'ghostwhite' => '#F8F8FF',
105        'gold' => '#FFD700',
106        'goldenrod' => '#DAA520',
107        'gray' => '#808080',
108        'green' => '#008000',
109        'greenyellow' => '#ADFF2F',
110        'grey' => '#808080',
111        'honeydew' => '#F0FFF0',
112        'hotpink' => '#FF69B4',
113        'indianred' => '#CD5C5C',
114        'indigo' => '#4B0082',
115        'ivory' => '#FFFFF0',
116        'khaki' => '#F0E68C',
117        'lavender' => '#E6E6FA',
118        'lavenderblush' => '#FFF0F5',
119        'lawngreen' => '#7CFC00',
120        'lemonchiffon' => '#FFFACD',
121        'lightblue' => '#ADD8E6',
122        'lightcoral' => '#F08080',
123        'lightcyan' => '#E0FFFF',
124        'lightgoldenrodyellow' => '#FAFAD2',
125        'lightgray' => '#D3D3D3',
126        'lightgreen' => '#90EE90',
127        'lightgrey' => '#D3D3D3',
128        'lightpink' => '#FFB6C1',
129        'lightsalmon' => '#FFA07A',
130        'lightseagreen' => '#20B2AA',
131        'lightskyblue' => '#87CEFA',
132        'lightslategray' => '#778899',
133        'lightslategrey' => '#778899',
134        'lightsteelblue' => '#B0C4DE',
135        'lightyellow' => '#FFFFE0',
136        'lime' => '#00FF00',
137        'limegreen' => '#32CD32',
138        'linen' => '#FAF0E6',
139        'magenta' => '#FF00FF',
140        'maroon' => '#800000',
141        'mediumaquamarine' => '#66CDAA',
142        'mediumblue' => '#0000CD',
143        'mediumorchid' => '#BA55D3',
144        'mediumpurple' => '#9370DB',
145        'mediumseagreen' => '#3CB371',
146        'mediumslateblue' => '#7B68EE',
147        'mediumspringgreen' => '#00FA9A',
148        'mediumturquoise' => '#48D1CC',
149        'mediumvioletred' => '#C71585',
150        'midnightblue' => '#191970',
151        'mintcream' => '#F5FFFA',
152        'mistyrose' => '#FFE4E1',
153        'moccasin' => '#FFE4B5',
154        'navajowhite' => '#FFDEAD',
155        'navy' => '#000080',
156        'oldlace' => '#FDF5E6',
157        'olive' => '#808000',
158        'olivedrab' => '#6B8E23',
159        'orange' => '#FFA500',
160        'orangered' => '#FF4500',
161        'orchid' => '#DA70D6',
162        'palegoldenrod' => '#EEE8AA',
163        'palegreen' => '#98FB98',
164        'paleturquoise' => '#AFEEEE',
165        'palevioletred' => '#DB7093',
166        'papayawhip' => '#FFEFD5',
167        'peachpuff' => '#FFDAB9',
168        'peru' => '#CD853F',
169        'pink' => '#FFC0CB',
170        'plum' => '#DDA0DD',
171        'powderblue' => '#B0E0E6',
172        'purple' => '#800080',
173        'rebeccapurple' => '#663399',
174        'red' => '#FF0000',
175        'rosybrown' => '#BC8F8F',
176        'royalblue' => '#4169E1',
177        'saddlebrown' => '#8B4513',
178        'salmon' => '#FA8072',
179        'sandybrown' => '#F4A460',
180        'seagreen' => '#2E8B57',
181        'seashell' => '#FFF5EE',
182        'sienna' => '#A0522D',
183        'silver' => '#C0C0C0',
184        'skyblue' => '#87CEEB',
185        'slateblue' => '#6A5ACD',
186        'slategray' => '#708090',
187        'slategrey' => '#708090',
188        'snow' => '#FFFAFA',
189        'springgreen' => '#00FF7F',
190        'steelblue' => '#4682B4',
191        'tan' => '#D2B48C',
192        'teal' => '#008080',
193        'tip' => self::TIP_COLOR,
194        'thistle' => '#D8BFD8',
195        'tomato' => '#FF6347',
196        'turquoise' => '#40E0D0',
197        'violet' => '#EE82EE',
198        'wheat' => '#F5DEB3',
199        'white' => '#FFFFFF',
200        'whitesmoke' => '#F5F5F5',
201        'yellow' => '#FFFF00',
202        'yellowgreen' => '#9ACD32'
203    );
204
205    /**
206     * Branding colors
207     */
208    const PRIMARY_VALUE = "primary";
209    const SECONDARY_VALUE = "secondary";
210
211    const SECONDARY_COLOR_CONF = "secondaryColor";
212    const BRANDING_COLOR_CANONICAL = "branding-colors";
213    public const BACKGROUND_COLOR = "background-color";
214
215    // the value is a bootstrap name
216    const VALUE_TYPE_BOOTSTRAP_NAME = "bootstrap";
217    const VALUE_TYPE_RGB_HEX = "rgb-hex";
218    const VALUE_TYPE_RGB_ARRAY = "rgb-array";
219    const VALUE_TYPE_RESET = "reset";
220    const VALUE_TYPE_CSS_NAME = "css-name";
221    const VALUE_TYPE_UNKNOWN_NAME = "unknown-name";
222
223    /**
224     * Minimum recommended ratio by the w3c
225     */
226    const MINIMUM_CONTRAST_RATIO = 5;
227    const WHITE = "white";
228    const TIP_COLOR = "#ffee33";
229    const CURRENT_COLOR = "currentColor";
230
231    /**
232     * @var array
233     */
234    private static $dokuWikiStyles;
235
236    /**
237     * @var int
238     */
239    private $red;
240    /**
241     * @var mixed
242     */
243    private $green;
244    /**
245     * @var mixed
246     */
247    private $blue;
248    /**
249     * @var string
250     */
251    private $nameType = self::VALUE_TYPE_UNKNOWN_NAME;
252    /**
253     * The color name
254     * It can be:
255     *   * a bootstrap
256     *   * a css name
257     *   * or `reset`
258     * @var null|string
259     */
260    private $name;
261    private $transparency;
262
263
264    /**
265     * The styles of the dokuwiki systems
266     * @return array
267     */
268    public static function getDokuWikiStyles(): array
269    {
270        if (self::$dokuWikiStyles === null) {
271            self::$dokuWikiStyles = (new StyleUtils())->cssStyleini();
272        }
273        return self::$dokuWikiStyles;
274
275    }
276
277    /**
278     * @return ColorRgb - the default primary color in case of any errors
279     * Used only in case of errors
280     */
281    public static function getDefaultPrimary(): ColorRgb
282    {
283        try {
284            return self::createFromHex("#0d6efd");
285        } catch (ExceptionCompile $e) {
286            throw new ExceptionRuntimeInternal("It should not happen as the rbg channles are handwritten");
287        }
288    }
289
290    /**
291     * Same round instructions than SCSS to be able to do the test
292     * have value as bootstrap
293     * @param float $value
294     * @return float
295     */
296    private static function round(float $value): float
297    {
298        $rest = fmod($value, 1);
299        if ($rest < 0.5) {
300            return round($value, 0, PHP_ROUND_HALF_DOWN);
301        } else {
302            return round($value, 0, PHP_ROUND_HALF_UP);
303        }
304    }
305
306    /**
307     * @throws ExceptionCompile
308     */
309    public static function createFromRgbChannels(int $red, int $green, int $blue): ColorRgb
310    {
311        return (new ColorRgb())
312            ->setRgbChannels([$red, $green, $blue]);
313    }
314
315    /**
316     * Utility function to get white
317     * @throws ExceptionCompile
318     */
319    public static function getWhite(): ColorRgb
320    {
321
322        return (new ColorRgb())
323            ->setName("white")
324            ->setRgbChannels([255, 255, 255])
325            ->setNameType(self::VALUE_TYPE_CSS_NAME);
326
327    }
328
329    /**
330     * Utility function to get black
331     */
332    public static function getBlack(): ColorRgb
333    {
334
335        try {
336            return (new ColorRgb())
337                ->setName("black")
338                ->setRgbChannels([0, 0, 0])
339                ->setNameType(self::VALUE_TYPE_CSS_NAME);
340        } catch (ExceptionCompile $e) {
341            throw new ExceptionRuntimeInternal("It should not happen as the rbg channles are handwritten");
342        }
343
344    }
345
346    /**
347     * @throws ExceptionBadArgument
348     */
349    public static function createFromHex(string $color): ColorRgb
350    {
351
352        return (new ColorRgb())
353            ->setHex($color);
354
355
356    }
357
358
359    /**
360     * @return ColorHsl
361     * @throws ExceptionCompile
362     */
363    public function toHsl(): ColorHsl
364    {
365
366        if ($this->red === null) {
367            throw new ExceptionCompile("This color ($this) does not have any channel known, we can't transform it to hsl");
368        }
369        $red = $this->red / 255;
370        $green = $this->green / 255;
371        $blue = $this->blue / 255;
372
373        $max = max($red, $green, $blue);
374        $min = min($red, $green, $blue);
375
376
377        $lightness = ($max + $min) / 2;
378        $d = $max - $min;
379
380        if ($d == 0) {
381            $hue = $saturation = 0; // achromatic
382        } else {
383            $saturation = $d / (1 - abs(2 * $lightness - 1));
384
385            switch ($max) {
386                case $red:
387                    $hue = 60 * fmod((($green - $blue) / $d), 6);
388                    if ($blue > $green) {
389                        $hue += 360;
390                    }
391                    break;
392
393                case $green:
394                    $hue = 60 * (($blue - $red) / $d + 2);
395                    break;
396
397                default:
398                case $blue:
399                    $hue = 60 * (($red - $green) / $d + 4);
400                    break;
401
402            }
403        }
404
405        /**
406         * No round to get a neat inverse
407         */
408
409        return ColorHsl::createFromChannels($hue, $saturation * 100, $lightness * 100);
410
411
412    }
413
414
415    /**
416     * @param array|string|ColorRgb $color
417     * @param int|null $weight
418     * @return ColorRgb
419     *
420     *
421     * Because Bootstrap uses the mix function of SCSS
422     * https://sass-lang.com/documentation/modules/color#mix
423     * We try to be as clause as possible
424     *
425     * https://gist.github.com/jedfoster/7939513
426     *
427     * This is a linear extrapolation along the segment
428     * @throws ExceptionCompile
429     */
430    function mix($color, ?int $weight = 50): ColorRgb
431    {
432        if ($weight === null) {
433            $weight = 50;
434        }
435
436        $color2 = ColorRgb::createFromString($color);
437        $targetRed = self::round(Math::lerp($color2->getRed(), $this->getRed(), $weight));
438        $targetGreen = self::round(Math::lerp($color2->getGreen(), $this->getGreen(), $weight));
439        $targetBlue = self::round(Math::lerp($color2->getBlue(), $this->getBlue(), $weight));
440        return ColorRgb::createFromRgbChannels($targetRed, $targetGreen, $targetBlue);
441
442    }
443
444    /**
445     * @throws ExceptionCompile
446     */
447    function unmix($color, ?int $weight = 50): ColorRgb
448    {
449        if ($weight === null) {
450            $weight = 50;
451        }
452
453        $color2 = ColorRgb::createFromString($color);
454        $targetRed = self::round(Math::unlerp($color2->getRed(), $this->getRed(), $weight));
455        if ($targetRed < 0) {
456            throw new ExceptionCompile("This is not possible, the red value ({$color2->getBlue()}) with the percentage $weight could not be unmixed. They were not calculated with color mixing.");
457        }
458        $targetGreen = self::round(Math::unlerp($color2->getGreen(), $this->getGreen(), $weight));
459        if ($targetGreen < 0) {
460            throw new ExceptionCompile("This is not possible, the green value ({$color2->getGreen()}) with the percentage $weight could not be unmixed. They were not calculated with color mixing.");
461        }
462        $targetBlue = self::round(Math::unlerp($color2->getBlue(), $this->getBlue(), $weight));
463        if ($targetBlue < 0) {
464            throw new ExceptionCompile("This is not possible, the blue value ({$color2->getBlue()}) with the percentage $weight could not be unmixed. They were not calculated with color mixing.");
465        }
466        return ColorRgb::createFromRgbChannels($targetRed, $targetGreen, $targetBlue);
467
468    }
469
470    /**
471     * Takes an hexadecimal color and returns the rgb channels
472     *
473     * @param mixed $hex
474     *
475     * @throws ExceptionBadArgument
476     */
477    function hex2rgb($hex = '#000000'): array
478    {
479        if ($hex[0] !== "#") {
480            throw new ExceptionBadArgument("The color value ($hex) does not start with a #, this is not valid CSS hexadecimal color value");
481        }
482        $digits = str_replace("#", "", $hex);
483        $hexLen = strlen($digits);
484        $transparency = false;
485        switch ($hexLen) {
486            case 3:
487                $lengthColorHex = 1;
488                break;
489            case 6:
490                $lengthColorHex = 2;
491                break;
492            case 8:
493                $lengthColorHex = 2;
494                $transparency = true;
495                break;
496            default:
497                throw new ExceptionBadArgument("The digit color value ($hex) is not 3 or 6 in length, this is not a valid CSS hexadecimal color value");
498        }
499        $result = preg_match("/[0-9a-f]{3,8}/i", $digits);
500        if ($result !== 1) {
501            throw new ExceptionBadArgument("The digit color value ($hex) is not a hexadecimal value, this is not a valid CSS hexadecimal color value");
502        }
503        $channelHexs = str_split($digits, $lengthColorHex);
504        $rgbDec = [];
505        foreach ($channelHexs as $channelHex) {
506            if ($lengthColorHex === 1) {
507                $channelHex .= $channelHex;
508            }
509            $rgbDec[] = hexdec($channelHex);
510        }
511        if (!$transparency) {
512            $rgbDec[] = null;
513        }
514        return $rgbDec;
515    }
516
517    /**
518     * rgb2hex
519     *
520     * @return string
521     */
522    function toRgbHex(): string
523    {
524
525        switch ($this->nameType) {
526            case self::VALUE_TYPE_CSS_NAME:
527                return strtolower(self::CSS_COLOR_NAMES[$this->name]);
528            default:
529                $toCssHex = function ($x) {
530                    return str_pad(dechex($x), 2, "0", STR_PAD_LEFT);
531                };
532                $redHex = $toCssHex($this->getRed());
533                $greenHex = $toCssHex($this->getGreen());
534                $blueHex = $toCssHex($this->getBlue());
535                $withoutAlpha = "#" . $redHex . $greenHex . $blueHex;
536                if ($this->transparency === null) {
537                    return $withoutAlpha;
538                }
539                return $withoutAlpha . $toCssHex($this->getTransparency());
540        }
541
542    }
543
544    /**
545     * @throws ExceptionBadArgument
546     */
547    public
548    static function createFromString(string $color): ColorRgb
549    {
550        if ($color[0] === "#") {
551            return self::createFromHex($color);
552        } else {
553            return self::createFromName($color);
554        }
555    }
556
557    /**
558     *
559     */
560    public
561    static function createFromName(string $color): ColorRgb
562    {
563        return (new ColorRgb())
564            ->setName($color);
565    }
566
567
568    public
569    function toCssValue(): string
570    {
571
572        switch ($this->nameType) {
573            case self::VALUE_TYPE_RGB_ARRAY:
574                return $this->toRgbHex();
575            case self::VALUE_TYPE_CSS_NAME:
576            case self::VALUE_TYPE_RGB_HEX;
577                return $this->name;
578            case self::VALUE_TYPE_BOOTSTRAP_NAME:
579                $bootstrapVersion = Bootstrap::getBootStrapMajorVersion();
580                switch ($bootstrapVersion) {
581                    case Bootstrap::BootStrapFiveMajorVersion:
582                        $colorValue = "bs-" . $this->name;
583                        break;
584                    default:
585                        $colorValue = $this->name;
586                        break;
587                }
588                return "var(--" . $colorValue . ")";
589            case self::VALUE_TYPE_RESET:
590                return "inherit!important";
591            default:
592                // unknown color name
593                if ($this->name === null) {
594                    LogUtility::msg("The name should not be null");
595                    return "black";
596                }
597                return $this->name;
598        }
599
600
601    }
602
603    public
604    function getRed()
605    {
606        return $this->red;
607    }
608
609    public
610    function getGreen()
611    {
612        return $this->green;
613    }
614
615    public
616    function getBlue()
617    {
618        return $this->blue;
619    }
620
621    /**
622     * Mix with black
623     * @var int $percentage between 0 and 100
624     */
625    public
626    function shade(int $percentage): ColorRgb
627    {
628        try {
629            return $this->mix('black', $percentage);
630        } catch (ExceptionCompile $e) {
631            // should not happen
632            LogUtility::msg("Error while shading. Error: {$e->getMessage()}");
633            return $this;
634        }
635    }
636
637    public
638    function getNameType(): string
639    {
640        return $this->nameType;
641    }
642
643    /**
644     * @param int $percentage between -100 and 100
645     * @return $this
646     */
647    public
648    function scale(int $percentage): ColorRgb
649    {
650        if ($percentage === 0) {
651            return $this;
652        }
653        if ($percentage > 0) {
654            return $this->shade($percentage);
655        } else {
656            return $this->tint(abs($percentage));
657        }
658
659    }
660
661
662    public
663    function toRgbChannels(): array
664    {
665        return [$this->getRed(), $this->getGreen(), $this->getBlue()];
666    }
667
668    /**
669     * @param int $percentage between 0 and 100
670     * @return $this
671     */
672    public
673    function tint(int $percentage): ColorRgb
674    {
675        try {
676            return $this->mix("white", $percentage);
677        } catch (ExceptionCompile $e) {
678            // should not happen
679            LogUtility::msg("Error while tinting ($this) with a percentage ($percentage. Error: {$e->getMessage()}");
680            return $this;
681        }
682    }
683
684    public
685    function __toString()
686    {
687        return $this->toCssValue();
688    }
689
690    public
691    function getLuminance(): float
692    {
693        $toLuminanceFactor = function ($channel) {
694            $pigmentRatio = $channel / 255;
695            return $pigmentRatio <= 0.03928 ? $pigmentRatio / 12.92 : pow(($pigmentRatio + 0.055) / 1.055, 2.4);
696        };
697        $R = $toLuminanceFactor($this->getRed());
698        $G = $toLuminanceFactor($this->getGreen());
699        $B = $toLuminanceFactor($this->getBlue());
700        return $R * 0.2126 + $G * 0.7152 + $B * 0.0722;
701
702    }
703
704    /**
705     * The ratio that returns the chrome browser
706     * @param ColorRgb $colorRgb
707     * @return float
708     * @throws ExceptionCompile
709     */
710    public
711    function getContrastRatio(ColorRgb $colorRgb): float
712    {
713        $actualColorHsl = $this->toHsl();
714        $actualLightness = $actualColorHsl->getLightness();
715        $targetColorHsl = $colorRgb->toHsl();
716        $targetLightNess = $targetColorHsl->getLightness();
717        if ($actualLightness > $targetLightNess) {
718            $lighter = $this;
719            $darker = $colorRgb;
720        } else {
721            $lighter = $colorRgb;
722            $darker = $this;
723        }
724        $ratio = ($lighter->getLuminance() + 0.05) / ($darker->getLuminance() + 0.05);
725        return floor($ratio * 100) / 100;
726    }
727
728    /**
729     * @throws ExceptionCompile
730     */
731    public
732    function toMinimumContrastRatio(string $color, float $minimum = self::MINIMUM_CONTRAST_RATIO, $darknessIncrement = 5): ColorRgb
733    {
734        $targetColor = ColorRgb::createFromString($color);
735        $ratio = $this->getContrastRatio($targetColor);
736        $newColorRgb = $this;
737        $newColorHsl = $this->toHsl();
738        while ($ratio < $minimum) {
739            $newColorHsl = $newColorHsl->darken($darknessIncrement);
740            $newColorRgb = $newColorHsl->toRgb();
741            if ($newColorHsl->getLightness() === 0) {
742                break;
743            }
744            $ratio = $newColorRgb->getContrastRatio($targetColor);
745        }
746        return $newColorRgb;
747    }
748
749    /**
750     * Returns the complimentary color
751     */
752    public
753    function complementary(): ColorRgb
754    {
755        try {
756            return $this
757                ->toHsl()
758                ->toComplement()
759                ->toRgb();
760        } catch (ExceptionCompile $e) {
761            LogUtility::msg("Error while getting the complementary color of ($this). Error: {$e->getMessage()}");
762            return $this;
763        }
764
765    }
766
767    public
768    function getName(): string
769    {
770        $hexColor = $this->toRgbHex();
771        if (in_array($hexColor, self::CSS_COLOR_NAMES)) {
772            return self::CSS_COLOR_NAMES[$hexColor];
773        }
774        return $hexColor;
775    }
776
777    /**
778     * @throws ExceptionCompile
779     */
780    public
781    function toMinimumContrastRatioAgainstWhite(float $minimumContrastRatio = self::MINIMUM_CONTRAST_RATIO, int $darknessIncrement = 5): ColorRgb
782    {
783        return $this->toMinimumContrastRatio(self::WHITE, $minimumContrastRatio, $darknessIncrement);
784    }
785
786    /**
787     * @throws ExceptionBadArgument
788     */
789    private
790    function setHex(string $color): ColorRgb
791    {
792        // Hexadecimal
793        if ($color[0] !== "#") {
794            throw new ExceptionBadArgument("The value is not an hexadecimal color value ($color)");
795        }
796        [$this->red, $this->green, $this->blue, $this->transparency] = $this->hex2rgb($color);
797        $this->nameType = self::VALUE_TYPE_RGB_HEX;
798        $this->name = $color;
799        return $this;
800    }
801
802    /**
803     * @throws ExceptionCompile
804     */
805    public
806    function setRgbChannels(array $colorValue): ColorRgb
807    {
808        if (sizeof($colorValue) != 3) {
809            throw new ExceptionCompile("A rgb color array should be of length 3");
810        }
811        foreach ($colorValue as $color) {
812            try {
813                $channel = DataType::toInteger($color);
814            } catch (ExceptionCompile $e) {
815                throw new ExceptionCompile("The rgb color $color is not an integer. Error: {$e->getMessage()}");
816            }
817            if ($channel < 0 and $channel > 255) {
818                throw new ExceptionCompile("The rgb color $color is not between 0 and 255");
819            }
820        }
821        [$this->red, $this->green, $this->blue] = $colorValue;
822        $this->nameType = self::VALUE_TYPE_RGB_ARRAY;
823        return $this;
824    }
825
826    private
827    function setNameType(string $type): ColorRgb
828    {
829        $this->nameType = $type;
830        return $this;
831    }
832
833    /**
834     * Via a name
835     */
836    private
837    function setName(string $name): ColorRgb
838    {
839
840        $qualifiedName = strtolower($name);
841        $this->name = $qualifiedName;
842        if (in_array($qualifiedName, self::BOOTSTRAP_COLORS)) {
843            /**
844             * Branding colors overwrite
845             */
846            switch ($this->name) {
847                case ColorRgb::PRIMARY_VALUE:
848                    $primaryColor = Site::getPrimaryColorValue();
849                    if ($primaryColor !== null) {
850                        if ($primaryColor !== ColorRgb::PRIMARY_VALUE) {
851                            return self::createFromString($primaryColor);
852                        }
853                        LogUtility::msg("The primary color cannot be set with the value primary. Default to bootstrap color.", self::BRANDING_COLOR_CANONICAL);
854                    }
855                    break;
856                case ColorRgb::SECONDARY_VALUE:
857                    $secondaryColor = Site::getSecondaryColorValue();
858                    if ($secondaryColor !== null) {
859                        if ($secondaryColor !== ColorRgb::SECONDARY_VALUE) {
860                            return self::createFromString($secondaryColor);
861                        }
862                        LogUtility::msg("The secondary color cannot be set with the value secondary. Default to bootstrap color.", self::BRANDING_COLOR_CANONICAL);
863                    }
864                    break;
865            }
866
867            return $this->setNameType(self::VALUE_TYPE_BOOTSTRAP_NAME);
868        }
869        if ($qualifiedName === self::VALUE_TYPE_RESET) {
870            return $this->setNameType(self::VALUE_TYPE_RESET);
871        }
872        if (in_array($qualifiedName, array_keys(self::CSS_COLOR_NAMES))) {
873            $this->setHex(self::CSS_COLOR_NAMES[$qualifiedName])
874                ->setNameType(self::VALUE_TYPE_CSS_NAME);
875            $this->name = $qualifiedName; // hex is a also a name, the previous setHex overwrite the name
876            return $this;
877        }
878        LogUtility::msg("The color name ($name) is unknown");
879        return $this;
880
881    }
882
883    public
884    function getTransparency()
885    {
886        return $this->transparency;
887    }
888
889
890
891
892}
893