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 
13 namespace ComboStrap;
14 
15 
16 use dokuwiki\StyleUtils;
17 
18 class 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