Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
98 / 98
100.00% covered (success)
100.00%
23 / 23
CRAP
100.00% covered (success)
100.00%
1 / 1
Hex
100.00% covered (success)
100.00%
98 / 98
100.00% covered (success)
100.00%
23 / 23
52
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setHex
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 setR
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setG
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setB
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getHex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getR
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getG
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isValid
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 toRgb
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 toHsl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
4
 offsetGet
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 offsetSet
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Color\Color;
15
16use OutOfRangeException;
17
18/**
19 * Pop Color Hex color class
20 *
21 * @category   Pop
22 * @package    Pop\Color
23 * @author     Nick Sagona, III <dev@noladev.com>
24 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
25 * @license    https://www.popphp.org/license     New BSD License
26 * @version    1.0.2
27 */
28class Hex extends AbstractColor implements \ArrayAccess
29{
30
31    /**
32     * R value
33     * @var ?string
34     */
35    protected ?string $r = null;
36
37    /**
38     * G value
39     * @var ?string
40     */
41    protected ?string $g = null;
42
43    /**
44     * B value
45     * @var ?string
46     */
47    protected ?string $b = null;
48
49    /**
50     * Hex value
51     * @var ?string
52     */
53    protected ?string $hex = null;
54
55    /**
56     * Constructor
57     *
58     * Instantiate the CSS hex color object
59     *
60     * @param string $hex
61     */
62    public function __construct(string $hex)
63    {
64        $this->setHex($hex);
65    }
66
67    /**
68     * Set hex value
69     *
70     * @param  string $hex
71     * @throws OutOfRangeException
72     * @return self
73     */
74    public function setHex(string $hex): self
75    {
76        $hex = strtolower($hex);
77        $hex = (str_starts_with($hex, '#')) ? substr($hex, 1) : $hex;
78
79        if ((strlen($hex) != 3) && (strlen($hex) != 6)) {
80            throw new OutOfRangeException('Error: The hex string was not the correct length.');
81        }
82        if (!$this->isValid($hex)) {
83            throw new OutOfRangeException('Error: The hex string was out of range.');
84        }
85
86        if (strlen($hex) == 3) {
87            $this->setR(substr($hex, 0, 1));
88            $this->setG(substr($hex, 1, 1));
89            $this->setB(substr($hex, 2, 1));
90        } else {
91            $this->setR(substr($hex, 0, 2));
92            $this->setG(substr($hex, 2, 2));
93            $this->setB(substr($hex, 4, 2));
94        }
95
96        $this->hex = $hex;
97
98        return $this;
99    }
100
101    /**
102     * Set R value
103     *
104     * @param  string $r
105     * @throws OutOfRangeException
106     * @return self
107     */
108    public function setR(string $r): self
109    {
110        if (!$this->isValid($r)) {
111            throw new OutOfRangeException('Error: The $r hex string was out of range.');
112        }
113        $this->r = $r;
114        return $this;
115    }
116
117    /**
118     * Set G value
119     *
120     * @param  string $g
121     * @throws OutOfRangeException
122     * @return self
123     */
124    public function setG(string $g): self
125    {
126        if (!$this->isValid($g)) {
127            throw new OutOfRangeException('Error: The $g hex string was out of range.');
128        }
129        $this->g = $g;
130        return $this;
131    }
132
133    /**
134     * Set B value
135     *
136     * @param  string $b
137     * @throws OutOfRangeException
138     * @return self
139     */
140    public function setB(string $b): self
141    {
142        if (!$this->isValid($b)) {
143            throw new OutOfRangeException('Error: The $b hex string was out of range.');
144        }
145        $this->b = $b;
146        return $this;
147    }
148
149    /**
150     * Get hex value
151     *
152     * @return string
153     */
154    public function getHex(): string
155    {
156        return $this->hex;
157    }
158
159    /**
160     * Get R value
161     *
162     * @return string|null
163     */
164    public function getR(): string|null
165    {
166        return $this->r;
167    }
168
169    /**
170     * Get G value
171     *
172     * @return string|null
173     */
174    public function getG(): string|null
175    {
176        return $this->g;
177    }
178
179    /**
180     * Get B value
181     *
182     * @return string|null
183     */
184    public function getB(): string|null
185    {
186        return $this->b;
187    }
188
189    /**
190     * Method to determine if the hex value is valid
191     *
192     * @param  string $hex
193     * @return bool
194     */
195    public function isValid(string $hex): bool
196    {
197        $valid     = true;
198        $hexValues = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
199        $hexAry    = str_split($hex);
200
201        foreach ($hexAry as $h) {
202            if (!in_array($h, $hexValues)) {
203                $valid = false;
204                break;
205            }
206        }
207
208        return $valid;
209    }
210
211    /**
212     * Convert to RGB
213     *
214     * @return Rgb
215     */
216    public function toRgb(): Rgb
217    {
218        $hexR  = $this->r;
219        $hexG  = $this->g;
220        $hexB  = $this->b;
221
222        if (strlen($hexR) == 1) {
223            $hexR .= $hexR;
224        }
225        if (strlen($hexG) == 1) {
226            $hexG .= $hexG;
227        }
228        if (strlen($hexB) == 1) {
229            $hexB .= $hexB;
230        }
231
232        $r = base_convert($hexR, 16, 10);
233        $g = base_convert($hexG, 16, 10);
234        $b = base_convert($hexB, 16, 10);
235
236        return new Rgb($r, $g, $b);
237    }
238
239    /**
240     * Convert to HSL
241     *
242     * @return Hsl
243     */
244    public function toHsl(): Hsl
245    {
246        return $this->toRgb()->toHsl();
247    }
248
249    /**
250     * Convert to array
251     *
252     * @param  bool $assoc
253     * @return array
254     */
255    public function toArray(bool $assoc = true): array
256    {
257        $hex = [];
258
259        if ($assoc) {
260            $hex['hex'] = '#' . $this->hex;
261            $hex['r']   = $this->r;
262            $hex['g']   = $this->g;
263            $hex['b']   = $this->b;
264        } else {
265            $hex[] = '#' . $this->hex;
266            $hex[] = $this->r;
267            $hex[] = $this->g;
268            $hex[] = $this->b;
269        }
270
271        return $hex;
272    }
273
274    /**
275     * Convert to readable string
276     *
277     * @param  ?string $format
278     * @return string
279     */
280    public function render(?string $format = null): string
281    {
282        if (($format == self::COMMA) || ($format == self::PERCENT)) {
283            $rgb = $this->toRgb();
284            return $rgb->render($format);
285        } else {
286            return '#' . $this->hex;
287        }
288    }
289
290    /**
291     * Return CSS-formatted string
292     *
293     * @return string
294     */
295    public function __toString(): string
296    {
297        return $this->render(self::CSS);
298    }
299
300    /**
301     * Magic method to set the color value
302     *
303     * @param  string $name
304     * @param  mixed $value
305     * @throws Exception
306     * @return void
307     */
308    public function __set(string $name, mixed $value): void
309    {
310        $this->offsetSet($name, $value);
311    }
312
313    /**
314     * Magic method to return the color value
315     *
316     * @param  string $name
317     * @throws Exception
318     * @return mixed
319     */
320    public function __get(string $name): mixed
321    {
322        return $this->offsetGet($name);
323    }
324
325    /**
326     * Magic method to return whether the color value exists
327     *
328     * @param  string $name
329     * @return bool
330     */
331    public function __isset(string $name): bool
332    {
333        return $this->offsetExists($name);
334    }
335
336    /**
337     * Magic method to unset color value
338     *
339     * @param  string $name
340     * @throws Exception
341     * @return void
342     */
343    public function __unset(string $name): void
344    {
345        throw new Exception('You cannot unset the properties of this color object.');
346    }
347
348    /**
349     * ArrayAccess offsetExists
350     *
351     * @param  mixed $offset
352     * @return bool
353     */
354    public function offsetExists(mixed $offset): bool
355    {
356        return (($offset == 'r') || ($offset == 'g') || ($offset == 'b') || ($offset == 'hex'));
357    }
358
359    /**
360     * ArrayAccess offsetGet
361     *
362     * @param  mixed $offset
363     * @throws Exception
364     * @return mixed
365     */
366    public function offsetGet(mixed $offset): mixed
367    {
368        switch ($offset) {
369            case 'r':
370                return $this->getR();
371                break;
372            case 'g':
373                return $this->getG();
374                break;
375            case 'b':
376                return $this->getB();
377                break;
378            case 'hex':
379                return $this->getHex();
380                break;
381            default:
382                throw new Exception("Error: You can only use 'r', 'g', 'b' or 'hex'.");
383        }
384    }
385
386    /**
387     * ArrayAccess offsetSet
388     *
389     * @param  mixed $offset
390     * @param  mixed $value
391     * @throws Exception
392     * @return void
393     */
394    public function offsetSet(mixed $offset, mixed $value): void
395    {
396        switch ($offset) {
397            case 'r':
398                $this->setR($value);
399                break;
400            case 'g':
401                $this->setG($value);
402                break;
403            case 'b':
404                $this->setB($value);
405                break;
406            case 'hex':
407                $this->setHex($value);
408                break;
409            default:
410                throw new Exception("Error: You can only use 'r', 'g', 'b' or 'hex'.");
411        }
412    }
413
414    /**
415     * ArrayAccess offsetUnset
416     *
417     * @param  mixed $offset
418     * @throws Exception
419     * @return void
420     */
421    public function offsetUnset(mixed $offset): void
422    {
423        throw new Exception('You cannot unset the properties of this color object.');
424    }
425
426}