Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.62% covered (success)
95.62%
131 / 137
84.62% covered (warning)
84.62%
11 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Tree_Color
95.62% covered (success)
95.62%
131 / 137
84.62% covered (warning)
84.62%
11 / 13
64
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 luma
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 genCSS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toCSS
94.59% covered (success)
94.59%
35 / 37
0.00% covered (danger)
0.00%
0 / 1
18.05
 operate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 toRGB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toHSL
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
 toHSV
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
7.23
 toARGB
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 compare
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
6
 clamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toHex
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 fromKeyword
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare( strict_types = 1 );
3
4/**
5 * @private
6 * @see less-3.13.1.js#Color.prototype
7 */
8class Less_Tree_Color extends Less_Tree {
9    /** @var array<int|float> */
10    public $rgb;
11    /** @var int */
12    public $alpha;
13    /** @var null|string */
14    public $value;
15
16    public function __construct( $rgb, $a = null, ?string $originalForm = null ) {
17        if ( is_array( $rgb ) ) {
18            $this->rgb = $rgb;
19        } elseif ( strlen( $rgb ) >= 6 ) {
20            $this->rgb = [];
21            foreach ( str_split( $rgb, 2 ) as $i => $c ) {
22                if ( $i < 3 ) {
23                    $this->rgb[] = hexdec( $c );
24                } else {
25                    $this->alpha = hexdec( $c ) / 255;
26                }
27            }
28        } else {
29            $this->rgb = [];
30            foreach ( str_split( $rgb, 1 ) as $i => $c ) {
31                if ( $i < 3 ) {
32                    $this->rgb[] = hexdec( $c . $c );
33                } else {
34                    $this->alpha = hexdec( $c . $c ) / 255;
35                }
36            }
37        }
38        $this->alpha ??= ( is_numeric( $a ) ? $a : 1 );
39
40        if ( $originalForm !== null ) {
41            $this->value = $originalForm;
42        }
43    }
44
45    public function luma() {
46        $r = $this->rgb[0] / 255;
47        $g = $this->rgb[1] / 255;
48        $b = $this->rgb[2] / 255;
49
50        $r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
51        $g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
52        $b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
53
54        return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
55    }
56
57    /**
58     * @see Less_Tree::genCSS
59     */
60    public function genCSS( $output ) {
61        $output->add( $this->toCSS() );
62    }
63
64    public function toCSS( $doNotCompress = false ) {
65        $compress = Less_Parser::$options['compress'] && !$doNotCompress;
66        $alpha = $this->fround( $this->alpha );
67
68        // If we have alpha transparency other than 1.0, the only way to represent it
69        // is via rgba(). Otherwise, we use the hex representation,
70        // which has better compatibility with older browsers.
71        // Values are capped between `0` and `255`, rounded and zero-padded.
72        $colorFunction = null;
73        $args = [];
74        if ( $this->value ) {
75            if ( strpos( $this->value, 'rgb' ) === 0 ) {
76                if ( $alpha < 1 ) {
77                    $colorFunction = 'rgba';
78
79                }
80            } elseif ( strpos( $this->value, 'hsl' ) === 0 ) {
81                if ( $alpha < 1 ) {
82                    $colorFunction = 'hsla';
83                } else {
84                    $colorFunction = 'hsl';
85                }
86            } else {
87                return $this->value;
88            }
89
90        } else {
91            if ( $alpha < 1 ) {
92                $colorFunction = 'rgba';
93            }
94        }
95
96        switch ( $colorFunction ) {
97            case 'rgba':
98                foreach ( $this->rgb as $c ) {
99                    $args[] = $this->clamp( round( $c ), 255 );
100                }
101                $args[] = $this->clamp( $alpha, 1 );
102                break;
103            case 'hsla':
104                $args[] = $this->clamp( $alpha, 1 );
105                // fall through
106            case 'hsl':
107                $color = $this->toHSL();
108                $args = [ $this->fround( $color["h"] ),
109                    $this->fround( $color["s"] * 100 ) . "%",
110                    $this->fround( $color["l"] * 100 ) . "%",
111                    ...$args
112                ];
113
114        }
115        if ( $colorFunction ) {
116            $glue = ( $compress ? ',' : ', ' );
117            return $colorFunction . "(" . implode( $glue, $args ) . ")";
118        }
119
120        $color = $this->toRGB();
121        if ( $compress ) {
122            // Convert color to short format
123            if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
124                $color = '#' . $color[1] . $color[3] . $color[5];
125            }
126        }
127        return $color;
128    }
129
130    /**
131     * Operations have to be done per-channel, if not,
132     * channels will spill onto each other. Once we have
133     * our result, in the form of an integer triplet,
134     * we create a new Color node to hold the result.
135     *
136     * @param string $op
137     * @param self $other
138     */
139    public function operate( $op, $other ) {
140        $rgb = [];
141        $alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
142        for ( $c = 0; $c < 3; $c++ ) {
143            $rgb[$c] = $this->_operate( $op, $this->rgb[$c], $other->rgb[$c] );
144        }
145        return new self( $rgb, $alpha );
146    }
147
148    public function toRGB() {
149        return $this->toHex( $this->rgb );
150    }
151
152    public function toHSL() {
153        $r = $this->rgb[0] / 255;
154        $g = $this->rgb[1] / 255;
155        $b = $this->rgb[2] / 255;
156        $a = $this->alpha;
157
158        $max = max( $r, $g, $b );
159        $min = min( $r, $g, $b );
160        $l = ( $max + $min ) / 2;
161        $d = $max - $min;
162
163        if ( $max === $min ) {
164            $h = $s = 0;
165        } else {
166            $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
167
168            switch ( $max ) {
169                case $r:
170                    $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
171                    break;
172                case $g:
173                    $h = ( $b - $r ) / $d + 2;
174                    break;
175                case $b:
176                    $h = ( $r - $g ) / $d + 4;
177                    break;
178            }
179            $h /= 6;
180        }
181        return [ 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a ];
182    }
183
184    // Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
185    public function toHSV() {
186        $r = $this->rgb[0] / 255;
187        $g = $this->rgb[1] / 255;
188        $b = $this->rgb[2] / 255;
189        $a = $this->alpha;
190
191        $max = max( $r, $g, $b );
192        $min = min( $r, $g, $b );
193
194        $v = $max;
195
196        $d = $max - $min;
197        if ( $max === 0 ) {
198            $s = 0;
199        } else {
200            $s = $d / $max;
201        }
202
203        if ( $max === $min ) {
204            $h = 0;
205        } else {
206            switch ( $max ) {
207                case $r:
208                    $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
209                    break;
210                case $g:
211                    $h = ( $b - $r ) / $d + 2;
212                    break;
213                case $b:
214                    $h = ( $r - $g ) / $d + 4;
215                    break;
216            }
217            $h /= 6;
218        }
219        return [ 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a ];
220    }
221
222    public function toARGB() {
223        $argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
224        return $this->toHex( $argb );
225    }
226
227    /**
228     * @param mixed $x
229     * @return int|null
230     * @see less-3.13.1.js#Color.prototype.compare
231     */
232    public function compare( $x ) {
233        return ( $x instanceof self &&
234            $x->rgb[0] === $this->rgb[0] &&
235            $x->rgb[1] === $this->rgb[1] &&
236            $x->rgb[2] === $this->rgb[2] &&
237            $x->alpha === $this->alpha ) ? 0 : null;
238    }
239
240    /**
241     * @param int|float $val
242     * @param int $max
243     * @return int|float
244     * @see less-3.13.1.js#Color.prototype
245     */
246    private function clamp( $val, $max ) {
247        return min( max( $val, 0 ), $max );
248    }
249
250    public function toHex( $v ) {
251        $ret = '#';
252        foreach ( $v as $c ) {
253            $c = (int)$this->clamp( Less_Parser::round( $c ), 255 );
254            if ( $c < 16 ) {
255                $ret .= '0';
256            }
257            $ret .= dechex( $c );
258        }
259        return $ret;
260    }
261
262    /**
263     * @param string $keyword
264     */
265    public static function fromKeyword( $keyword ) {
266        $c = null;
267        $key = strtolower( $keyword );
268
269        if ( Less_Colors::hasOwnProperty( $key ) ) {
270            // detect named color
271            $c = new self( substr( Less_Colors::color( $key ), 1 ) );
272        } elseif ( $key === 'transparent' ) {
273            $c = new self( [ 0, 0, 0 ], 0 );
274        }
275
276        if ( $c instanceof self ) {
277            $c->value = $keyword;
278            return $c;
279        }
280    }
281}