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