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