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