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