Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Style;
   4  
   5  class Color extends Supervisor
   6  {
   7      const NAMED_COLORS = [
   8          'Black',
   9          'White',
  10          'Red',
  11          'Green',
  12          'Blue',
  13          'Yellow',
  14          'Magenta',
  15          'Cyan',
  16      ];
  17  
  18      // Colors
  19      const COLOR_BLACK = 'FF000000';
  20      const COLOR_WHITE = 'FFFFFFFF';
  21      const COLOR_RED = 'FFFF0000';
  22      const COLOR_DARKRED = 'FF800000';
  23      const COLOR_BLUE = 'FF0000FF';
  24      const COLOR_DARKBLUE = 'FF000080';
  25      const COLOR_GREEN = 'FF00FF00';
  26      const COLOR_DARKGREEN = 'FF008000';
  27      const COLOR_YELLOW = 'FFFFFF00';
  28      const COLOR_DARKYELLOW = 'FF808000';
  29      const COLOR_MAGENTA = 'FFFF00FF';
  30      const COLOR_CYAN = 'FF00FFFF';
  31  
  32      const NAMED_COLOR_TRANSLATIONS = [
  33          'Black' => self::COLOR_BLACK,
  34          'White' => self::COLOR_WHITE,
  35          'Red' => self::COLOR_RED,
  36          'Green' => self::COLOR_GREEN,
  37          'Blue' => self::COLOR_BLUE,
  38          'Yellow' => self::COLOR_YELLOW,
  39          'Magenta' => self::COLOR_MAGENTA,
  40          'Cyan' => self::COLOR_CYAN,
  41      ];
  42  
  43      const VALIDATE_ARGB_SIZE = 8;
  44      const VALIDATE_RGB_SIZE = 6;
  45      const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i';
  46      const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i';
  47  
  48      private const INDEXED_COLORS = [
  49          1 => 'FF000000', //  System Colour #1 - Black
  50          2 => 'FFFFFFFF', //  System Colour #2 - White
  51          3 => 'FFFF0000', //  System Colour #3 - Red
  52          4 => 'FF00FF00', //  System Colour #4 - Green
  53          5 => 'FF0000FF', //  System Colour #5 - Blue
  54          6 => 'FFFFFF00', //  System Colour #6 - Yellow
  55          7 => 'FFFF00FF', //  System Colour #7- Magenta
  56          8 => 'FF00FFFF', //  System Colour #8- Cyan
  57          9 => 'FF800000', //  Standard Colour #9
  58          10 => 'FF008000', //  Standard Colour #10
  59          11 => 'FF000080', //  Standard Colour #11
  60          12 => 'FF808000', //  Standard Colour #12
  61          13 => 'FF800080', //  Standard Colour #13
  62          14 => 'FF008080', //  Standard Colour #14
  63          15 => 'FFC0C0C0', //  Standard Colour #15
  64          16 => 'FF808080', //  Standard Colour #16
  65          17 => 'FF9999FF', //  Chart Fill Colour #17
  66          18 => 'FF993366', //  Chart Fill Colour #18
  67          19 => 'FFFFFFCC', //  Chart Fill Colour #19
  68          20 => 'FFCCFFFF', //  Chart Fill Colour #20
  69          21 => 'FF660066', //  Chart Fill Colour #21
  70          22 => 'FFFF8080', //  Chart Fill Colour #22
  71          23 => 'FF0066CC', //  Chart Fill Colour #23
  72          24 => 'FFCCCCFF', //  Chart Fill Colour #24
  73          25 => 'FF000080', //  Chart Line Colour #25
  74          26 => 'FFFF00FF', //  Chart Line Colour #26
  75          27 => 'FFFFFF00', //  Chart Line Colour #27
  76          28 => 'FF00FFFF', //  Chart Line Colour #28
  77          29 => 'FF800080', //  Chart Line Colour #29
  78          30 => 'FF800000', //  Chart Line Colour #30
  79          31 => 'FF008080', //  Chart Line Colour #31
  80          32 => 'FF0000FF', //  Chart Line Colour #32
  81          33 => 'FF00CCFF', //  Standard Colour #33
  82          34 => 'FFCCFFFF', //  Standard Colour #34
  83          35 => 'FFCCFFCC', //  Standard Colour #35
  84          36 => 'FFFFFF99', //  Standard Colour #36
  85          37 => 'FF99CCFF', //  Standard Colour #37
  86          38 => 'FFFF99CC', //  Standard Colour #38
  87          39 => 'FFCC99FF', //  Standard Colour #39
  88          40 => 'FFFFCC99', //  Standard Colour #40
  89          41 => 'FF3366FF', //  Standard Colour #41
  90          42 => 'FF33CCCC', //  Standard Colour #42
  91          43 => 'FF99CC00', //  Standard Colour #43
  92          44 => 'FFFFCC00', //  Standard Colour #44
  93          45 => 'FFFF9900', //  Standard Colour #45
  94          46 => 'FFFF6600', //  Standard Colour #46
  95          47 => 'FF666699', //  Standard Colour #47
  96          48 => 'FF969696', //  Standard Colour #48
  97          49 => 'FF003366', //  Standard Colour #49
  98          50 => 'FF339966', //  Standard Colour #50
  99          51 => 'FF003300', //  Standard Colour #51
 100          52 => 'FF333300', //  Standard Colour #52
 101          53 => 'FF993300', //  Standard Colour #53
 102          54 => 'FF993366', //  Standard Colour #54
 103          55 => 'FF333399', //  Standard Colour #55
 104          56 => 'FF333333', //  Standard Colour #56
 105      ];
 106  
 107      /**
 108       * ARGB - Alpha RGB.
 109       *
 110       * @var null|string
 111       */
 112      protected $argb;
 113  
 114      /** @var bool */
 115      private $hasChanged = false;
 116  
 117      /**
 118       * Create a new Color.
 119       *
 120       * @param string $colorValue ARGB value for the colour, or named colour
 121       * @param bool $isSupervisor Flag indicating if this is a supervisor or not
 122       *                                    Leave this value at default unless you understand exactly what
 123       *                                        its ramifications are
 124       * @param bool $isConditional Flag indicating if this is a conditional style or not
 125       *                                    Leave this value at default unless you understand exactly what
 126       *                                        its ramifications are
 127       */
 128      public function __construct($colorValue = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false)
 129      {
 130          //    Supervisor?
 131          parent::__construct($isSupervisor);
 132  
 133          //    Initialise values
 134          if (!$isConditional) {
 135              $this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK;
 136          }
 137      }
 138  
 139      /**
 140       * Get the shared style component for the currently active cell in currently active sheet.
 141       * Only used for style supervisor.
 142       *
 143       * @return Color
 144       */
 145      public function getSharedComponent()
 146      {
 147          /** @var Style */
 148          $parent = $this->parent;
 149          /** @var Border|Fill $sharedComponent */
 150          $sharedComponent = $parent->getSharedComponent();
 151          if ($sharedComponent instanceof Fill) {
 152              if ($this->parentPropertyName === 'endColor') {
 153                  return $sharedComponent->getEndColor();
 154              }
 155  
 156              return $sharedComponent->getStartColor();
 157          }
 158  
 159          return $sharedComponent->getColor();
 160      }
 161  
 162      /**
 163       * Build style array from subcomponents.
 164       *
 165       * @param array $array
 166       *
 167       * @return array
 168       */
 169      public function getStyleArray($array)
 170      {
 171          /** @var Style */
 172          $parent = $this->parent;
 173  
 174          return $parent->getStyleArray([$this->parentPropertyName => $array]);
 175      }
 176  
 177      /**
 178       * Apply styles from array.
 179       *
 180       * <code>
 181       * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
 182       * </code>
 183       *
 184       * @param array $styleArray Array containing style information
 185       *
 186       * @return $this
 187       */
 188      public function applyFromArray(array $styleArray)
 189      {
 190          if ($this->isSupervisor) {
 191              $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
 192          } else {
 193              if (isset($styleArray['rgb'])) {
 194                  $this->setRGB($styleArray['rgb']);
 195              }
 196              if (isset($styleArray['argb'])) {
 197                  $this->setARGB($styleArray['argb']);
 198              }
 199          }
 200  
 201          return $this;
 202      }
 203  
 204      private function validateColor(?string $colorValue): string
 205      {
 206          if ($colorValue === null || $colorValue === '') {
 207              return self::COLOR_BLACK;
 208          }
 209          $named = ucfirst(strtolower($colorValue));
 210          if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) {
 211              return self::NAMED_COLOR_TRANSLATIONS[$named];
 212          }
 213          if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) {
 214              return $colorValue;
 215          }
 216          if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) {
 217              return 'FF' . $colorValue;
 218          }
 219  
 220          return '';
 221      }
 222  
 223      /**
 224       * Get ARGB.
 225       */
 226      public function getARGB(): ?string
 227      {
 228          if ($this->isSupervisor) {
 229              return $this->getSharedComponent()->getARGB();
 230          }
 231  
 232          return $this->argb;
 233      }
 234  
 235      /**
 236       * Set ARGB.
 237       *
 238       * @param string $colorValue  ARGB value, or a named color
 239       *
 240       * @return $this
 241       */
 242      public function setARGB(?string $colorValue = self::COLOR_BLACK)
 243      {
 244          $this->hasChanged = true;
 245          $colorValue = $this->validateColor($colorValue);
 246          if ($colorValue === '') {
 247              return $this;
 248          }
 249  
 250          if ($this->isSupervisor) {
 251              $styleArray = $this->getStyleArray(['argb' => $colorValue]);
 252              $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
 253          } else {
 254              $this->argb = $colorValue;
 255          }
 256  
 257          return $this;
 258      }
 259  
 260      /**
 261       * Get RGB.
 262       */
 263      public function getRGB(): string
 264      {
 265          if ($this->isSupervisor) {
 266              return $this->getSharedComponent()->getRGB();
 267          }
 268  
 269          return substr($this->argb ?? '', 2);
 270      }
 271  
 272      /**
 273       * Set RGB.
 274       *
 275       * @param string $colorValue RGB value, or a named color
 276       *
 277       * @return $this
 278       */
 279      public function setRGB(?string $colorValue = self::COLOR_BLACK)
 280      {
 281          return $this->setARGB($colorValue);
 282      }
 283  
 284      /**
 285       * Get a specified colour component of an RGB value.
 286       *
 287       * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
 288       * @param int $offset Position within the RGB value to extract
 289       * @param bool $hex Flag indicating whether the component should be returned as a hex or a
 290       *                                    decimal value
 291       *
 292       * @return int|string The extracted colour component
 293       */
 294      private static function getColourComponent($rgbValue, $offset, $hex = true)
 295      {
 296          $colour = substr($rgbValue, $offset, 2) ?: '';
 297          if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) {
 298              $colour = '00';
 299          }
 300  
 301          return ($hex) ? $colour : (int) hexdec($colour);
 302      }
 303  
 304      /**
 305       * Get the red colour component of an RGB value.
 306       *
 307       * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
 308       * @param bool $hex Flag indicating whether the component should be returned as a hex or a
 309       *                                    decimal value
 310       *
 311       * @return int|string The red colour component
 312       */
 313      public static function getRed($rgbValue, $hex = true)
 314      {
 315          return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex);
 316      }
 317  
 318      /**
 319       * Get the green colour component of an RGB value.
 320       *
 321       * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
 322       * @param bool $hex Flag indicating whether the component should be returned as a hex or a
 323       *                                    decimal value
 324       *
 325       * @return int|string The green colour component
 326       */
 327      public static function getGreen($rgbValue, $hex = true)
 328      {
 329          return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex);
 330      }
 331  
 332      /**
 333       * Get the blue colour component of an RGB value.
 334       *
 335       * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
 336       * @param bool $hex Flag indicating whether the component should be returned as a hex or a
 337       *                                    decimal value
 338       *
 339       * @return int|string The blue colour component
 340       */
 341      public static function getBlue($rgbValue, $hex = true)
 342      {
 343          return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex);
 344      }
 345  
 346      /**
 347       * Adjust the brightness of a color.
 348       *
 349       * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
 350       * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
 351       *
 352       * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
 353       */
 354      public static function changeBrightness($hexColourValue, $adjustPercentage)
 355      {
 356          $rgba = (strlen($hexColourValue) === 8);
 357          $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage));
 358  
 359          /** @var int $red */
 360          $red = self::getRed($hexColourValue, false);
 361          /** @var int $green */
 362          $green = self::getGreen($hexColourValue, false);
 363          /** @var int $blue */
 364          $blue = self::getBlue($hexColourValue, false);
 365          if ($adjustPercentage > 0) {
 366              $red += (255 - $red) * $adjustPercentage;
 367              $green += (255 - $green) * $adjustPercentage;
 368              $blue += (255 - $blue) * $adjustPercentage;
 369          } else {
 370              $red += $red * $adjustPercentage;
 371              $green += $green * $adjustPercentage;
 372              $blue += $blue * $adjustPercentage;
 373          }
 374  
 375          $rgb = strtoupper(
 376              str_pad(dechex((int) $red), 2, '0', 0) .
 377              str_pad(dechex((int) $green), 2, '0', 0) .
 378              str_pad(dechex((int) $blue), 2, '0', 0)
 379          );
 380  
 381          return (($rgba) ? 'FF' : '') . $rgb;
 382      }
 383  
 384      /**
 385       * Get indexed color.
 386       *
 387       * @param int $colorIndex Index entry point into the colour array
 388       * @param bool $background Flag to indicate whether default background or foreground colour
 389       *                                            should be returned if the indexed colour doesn't exist
 390       */
 391      public static function indexedColor($colorIndex, $background = false, ?array $palette = null): self
 392      {
 393          // Clean parameter
 394          $colorIndex = (int) $colorIndex;
 395  
 396          if (empty($palette)) {
 397              if (isset(self::INDEXED_COLORS[$colorIndex])) {
 398                  return new self(self::INDEXED_COLORS[$colorIndex]);
 399              }
 400          } else {
 401              if (isset($palette[$colorIndex])) {
 402                  return new self($palette[$colorIndex]);
 403              }
 404          }
 405  
 406          return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK);
 407      }
 408  
 409      /**
 410       * Get hash code.
 411       *
 412       * @return string Hash code
 413       */
 414      public function getHashCode(): string
 415      {
 416          if ($this->isSupervisor) {
 417              return $this->getSharedComponent()->getHashCode();
 418          }
 419  
 420          return md5(
 421              $this->argb .
 422              __CLASS__
 423          );
 424      }
 425  
 426      protected function exportArray1(): array
 427      {
 428          $exportedArray = [];
 429          $this->exportArray2($exportedArray, 'argb', $this->getARGB());
 430  
 431          return $exportedArray;
 432      }
 433  
 434      public function getHasChanged(): bool
 435      {
 436          if ($this->isSupervisor) {
 437              return $this->getSharedComponent()->hasChanged;
 438          }
 439  
 440          return $this->hasChanged;
 441      }
 442  }