Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.62% |
131 / 137 |
|
84.62% |
11 / 13 |
CRAP | |
0.00% |
0 / 1 |
| Less_Tree_Color | |
95.62% |
131 / 137 |
|
84.62% |
11 / 13 |
64 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
9 | |||
| luma | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| genCSS | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| toCSS | |
94.59% |
35 / 37 |
|
0.00% |
0 / 1 |
18.05 | |||
| operate | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| toRGB | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| toHSL | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
7 | |||
| toHSV | |
83.33% |
20 / 24 |
|
0.00% |
0 / 1 |
7.23 | |||
| toARGB | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| compare | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
6 | |||
| clamp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| toHex | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| fromKeyword | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @private |
| 4 | * @see less-3.13.1.js#Color.prototype |
| 5 | */ |
| 6 | class 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 | } |