Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.64% covered (success)
94.64%
106 / 112
84.62% covered (warning)
84.62%
11 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Tree_Color
94.64% covered (success)
94.64%
106 / 112
84.62% covered (warning)
84.62%
11 / 13
54.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 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
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
10.20
 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            // TODO: Less.js 3.13 supports 8-digit rgba as #RRGGBBAA
19            $this->rgb = [];
20            foreach ( str_split( $rgb, 2 ) as $c ) {
21                $this->rgb[] = hexdec( $c );
22            }
23        } else {
24            $this->rgb = [];
25            // TODO: Less.js 3.13 supports 4-digit short rgba as #RGBA
26            foreach ( str_split( $rgb, 1 ) as $c ) {
27                $this->rgb[] = hexdec( $c . $c );
28            }
29        }
30
31        $this->alpha = is_numeric( $a ) ? $a : 1;
32
33        if ( $originalForm !== null ) {
34            $this->value = $originalForm;
35        }
36    }
37
38    public function luma() {
39        $r = $this->rgb[0] / 255;
40        $g = $this->rgb[1] / 255;
41        $b = $this->rgb[2] / 255;
42
43        $r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
44        $g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
45        $b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
46
47        return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
48    }
49
50    /**
51     * @see Less_Tree::genCSS
52     */
53    public function genCSS( $output ) {
54        $output->add( $this->toCSS() );
55    }
56
57    public function toCSS( $doNotCompress = false ) {
58        $compress = Less_Parser::$options['compress'] && !$doNotCompress;
59        $alpha = $this->fround( $this->alpha );
60
61        // `value` is set if this color was originally
62        // converted from a named color string so we need
63        // to respect this and try to output named color too.
64        if ( $this->value ) {
65            return $this->value;
66        }
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        //
73        // TODO: Less.js 3.13 supports hsla() and hsl() as well
74        if ( $alpha < 1 ) {
75            $values = [];
76            foreach ( $this->rgb as $c ) {
77                $values[] = $this->clamp( round( $c ), 255 );
78            }
79            $values[] = $alpha;
80
81            $glue = ( $compress ? ',' : ', ' );
82            return "rgba(" . implode( $glue, $values ) . ")";
83        }
84
85        $color = $this->toRGB();
86        if ( $compress ) {
87            // Convert color to short format
88            if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
89                $color = '#' . $color[1] . $color[3] . $color[5];
90            }
91        }
92        return $color;
93    }
94
95    /**
96     * Operations have to be done per-channel, if not,
97     * channels will spill onto each other. Once we have
98     * our result, in the form of an integer triplet,
99     * we create a new Color node to hold the result.
100     *
101     * @param string $op
102     * @param self $other
103     */
104    public function operate( $op, $other ) {
105        $rgb = [];
106        $alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
107        for ( $c = 0; $c < 3; $c++ ) {
108            $rgb[$c] = $this->_operate( $op, $this->rgb[$c], $other->rgb[$c] );
109        }
110        return new self( $rgb, $alpha );
111    }
112
113    public function toRGB() {
114        return $this->toHex( $this->rgb );
115    }
116
117    public function toHSL() {
118        $r = $this->rgb[0] / 255;
119        $g = $this->rgb[1] / 255;
120        $b = $this->rgb[2] / 255;
121        $a = $this->alpha;
122
123        $max = max( $r, $g, $b );
124        $min = min( $r, $g, $b );
125        $l = ( $max + $min ) / 2;
126        $d = $max - $min;
127
128        if ( $max === $min ) {
129            $h = $s = 0;
130        } else {
131            $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
132
133            switch ( $max ) {
134                case $r:
135                    $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
136                    break;
137                case $g:
138                    $h = ( $b - $r ) / $d + 2;
139                    break;
140                case $b:
141                    $h = ( $r - $g ) / $d + 4;
142                    break;
143            }
144            $h /= 6;
145        }
146        return [ 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a ];
147    }
148
149    // Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
150    public function toHSV() {
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
159        $v = $max;
160
161        $d = $max - $min;
162        if ( $max === 0 ) {
163            $s = 0;
164        } else {
165            $s = $d / $max;
166        }
167
168        if ( $max === $min ) {
169            $h = 0;
170        } else {
171            switch ( $max ) {
172                case $r:
173                    $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
174                    break;
175                case $g:
176                    $h = ( $b - $r ) / $d + 2;
177                    break;
178                case $b:
179                    $h = ( $r - $g ) / $d + 4;
180                    break;
181            }
182            $h /= 6;
183        }
184        return [ 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a ];
185    }
186
187    public function toARGB() {
188        $argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
189        return $this->toHex( $argb );
190    }
191
192    /**
193     * @param mixed $x
194     * @return int|null
195     * @see less-3.13.1.js#Color.prototype.compare
196     */
197    public function compare( $x ) {
198        return ( $x instanceof self &&
199            $x->rgb[0] === $this->rgb[0] &&
200            $x->rgb[1] === $this->rgb[1] &&
201            $x->rgb[2] === $this->rgb[2] &&
202            $x->alpha === $this->alpha ) ? 0 : null;
203    }
204
205    /**
206     * @param int|float $val
207     * @param int $max
208     * @return int|float
209     * @see less-3.13.1.js#Color.prototype
210     */
211    private function clamp( $val, $max ) {
212        return min( max( $val, 0 ), $max );
213    }
214
215    public function toHex( $v ) {
216        $ret = '#';
217        foreach ( $v as $c ) {
218            $c = $this->clamp( Less_Parser::round( $c ), 255 );
219            if ( $c < 16 ) {
220                $ret .= '0';
221            }
222            $ret .= dechex( $c );
223        }
224        return $ret;
225    }
226
227    /**
228     * @param string $keyword
229     */
230    public static function fromKeyword( $keyword ) {
231        $c = null;
232        $key = strtolower( $keyword );
233
234        if ( Less_Colors::hasOwnProperty( $key ) ) {
235            // detect named color
236            $c = new self( substr( Less_Colors::color( $key ), 1 ) );
237        } elseif ( $key === 'transparent' ) {
238            $c = new self( [ 0, 0, 0 ], 0 );
239        }
240
241        if ( $c instanceof self ) {
242            $c->value = $keyword;
243            return $c;
244        }
245    }
246}