Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.86% covered (warning)
71.86%
406 / 565
55.45% covered (warning)
55.45%
56 / 101
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Functions
71.86% covered (warning)
71.86%
406 / 565
55.45% covered (warning)
55.45%
56 / 101
3316.90
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 operate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 clamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fround
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 number
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 scaled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 rgb
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
4.59
 rgba
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 hsl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hsla
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 hsla_hue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 hsv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hsva
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
1
 hue
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 saturation
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 lightness
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 hsvhue
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 hsvsaturation
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 hsvvalue
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 red
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 green
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 blue
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 alpha
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 luma
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 luminance
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 saturate
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
8.30
 desaturate
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 lighten
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 darken
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 fadein
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 fadeout
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 fade
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 spin
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
6.56
 mix
84.00% covered (warning)
84.00%
21 / 25
0.00% covered (danger)
0.00%
0 / 1
12.59
 greyscale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 contrast
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
11.12
 e
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 escape
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 replace
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 replace_flags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 _percent
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 unit
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 convert
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 round
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 pi
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pow
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
8.12
 ceil
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 floor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sqrt
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 abs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cos
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 atan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 asin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 acos
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 _math
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 _minmax
80.43% covered (warning)
80.43%
37 / 46
0.00% covered (danger)
0.00%
0 / 1
39.67
 min
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 max
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getunit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 argb
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 percentage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 color
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 isruleset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 iscolor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isnumber
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isstring
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 iskeyword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isurl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 ispixel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ispercentage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isem
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isunit
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
5
 tint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 shade
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 extract
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 length
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 datauri
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
182
 svggradient
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
420
 encodeURIComponent
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 colorBlend
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 multiply
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendMultiply
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 screen
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendScreen
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 overlay
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendOverlay
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 softlight
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendSoftlight
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 hardlight
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendHardlight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 difference
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendDifference
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exclusion
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendExclusion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 average
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendAverage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 negation
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 colorBlendNegation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Builtin functions
5 * @see https://lesscss.org/functions/
6 */
7class Less_Functions {
8
9    public $env;
10    public $currentFileInfo;
11
12    public function __construct( $env, array $currentFileInfo = null ) {
13        $this->env = $env;
14        $this->currentFileInfo = $currentFileInfo;
15    }
16
17    /**
18     * @param string $op
19     * @param float $a
20     * @param float $b
21     */
22    public static function operate( $op, $a, $b ) {
23        switch ( $op ) {
24            case '+':
25                return $a + $b;
26            case '-':
27                return $a - $b;
28            case '*':
29                return $a * $b;
30            case '/':
31                return $a / $b;
32        }
33    }
34
35    public static function clamp( $val, $max = 1 ) {
36        return min( max( $val, 0 ), $max );
37    }
38
39    public static function fround( $value ) {
40        if ( $value === 0 ) {
41            return $value;
42        }
43
44        if ( Less_Parser::$options['numPrecision'] ) {
45            $p = pow( 10, Less_Parser::$options['numPrecision'] );
46            return round( $value * $p ) / $p;
47        }
48        return $value;
49    }
50
51    public static function number( $n ) {
52        if ( $n instanceof Less_Tree_Dimension ) {
53            return floatval( $n->unit->is( '%' ) ? $n->value / 100 : $n->value );
54        } elseif ( is_numeric( $n ) ) {
55            return $n;
56        } else {
57            throw new Less_Exception_Compiler( "color functions take numbers as parameters" );
58        }
59    }
60
61    public static function scaled( $n, $size = 255 ) {
62        if ( $n instanceof Less_Tree_Dimension && $n->unit->is( '%' ) ) {
63            return (float)$n->value * $size / 100;
64        } else {
65            return self::number( $n );
66        }
67    }
68
69    public function rgb( $r = null, $g = null, $b = null ) {
70        if ( $r === null || $g === null || $b === null ) {
71            throw new Less_Exception_Compiler( "rgb expects three parameters" );
72        }
73        return $this->rgba( $r, $g, $b, 1.0 );
74    }
75
76    public function rgba( $r = null, $g = null, $b = null, $a = null ) {
77        $rgb = [ $r, $g, $b ];
78        $rgb = array_map( [ __CLASS__, 'scaled' ], $rgb );
79
80        $a = self::number( $a );
81        return new Less_Tree_Color( $rgb, $a );
82    }
83
84    public function hsl( $h, $s, $l ) {
85        return $this->hsla( $h, $s, $l, 1.0 );
86    }
87
88    public function hsla( $h, $s, $l, $a ) {
89        $h = fmod( self::number( $h ), 360 ) / 360; // Classic % operator will change float to int
90        $s = self::clamp( self::number( $s ) );
91        $l = self::clamp( self::number( $l ) );
92        $a = self::clamp( self::number( $a ) );
93
94        $m2 = $l <= 0.5 ? $l * ( $s + 1 ) : $l + $s - $l * $s;
95
96        $m1 = $l * 2 - $m2;
97
98        return $this->rgba(
99            self::hsla_hue( $h + 1 / 3, $m1, $m2 ) * 255,
100            self::hsla_hue( $h, $m1, $m2 ) * 255,
101            self::hsla_hue( $h - 1 / 3, $m1, $m2 ) * 255,
102            $a
103        );
104    }
105
106    /**
107     * @param float $h
108     * @param float $m1
109     * @param float $m2
110     */
111    public function hsla_hue( $h, $m1, $m2 ) {
112        $h = $h < 0 ? $h + 1 : ( $h > 1 ? $h - 1 : $h );
113        if ( $h * 6 < 1 ) {
114            return $m1 + ( $m2 - $m1 ) * $h * 6;
115        } elseif ( $h * 2 < 1 ) {
116            return $m2;
117        } elseif ( $h * 3 < 2 ) {
118            return $m1 + ( $m2 - $m1 ) * ( 2 / 3 - $h ) * 6;
119        } else {
120            return $m1;
121        }
122    }
123
124    public function hsv( $h, $s, $v ) {
125        return $this->hsva( $h, $s, $v, 1.0 );
126    }
127
128    /**
129     * @param Less_Tree|float $h
130     * @param Less_Tree|float $s
131     * @param Less_Tree|float $v
132     * @param float $a
133     */
134    public function hsva( $h, $s, $v, $a ) {
135        $h = ( ( self::number( $h ) % 360 ) / 360 ) * 360;
136        $s = self::number( $s );
137        $v = self::number( $v );
138        $a = self::number( $a );
139
140        $i = (int)floor( (int)( $h / 60 ) % 6 );
141        $f = ( $h / 60 ) - $i;
142
143        $vs = [
144            $v,
145            $v * ( 1 - $s ),
146            $v * ( 1 - $f * $s ),
147            $v * ( 1 - ( 1 - $f ) * $s )
148        ];
149
150        $perm = [
151            [ 0, 3, 1 ],
152            [ 2, 0, 1 ],
153            [ 1, 0, 3 ],
154            [ 1, 2, 0 ],
155            [ 3, 1, 0 ],
156            [ 0, 1, 2 ]
157        ];
158
159        return $this->rgba(
160            $vs[$perm[$i][0]] * 255,
161            $vs[$perm[$i][1]] * 255,
162            $vs[$perm[$i][2]] * 255,
163            $a
164        );
165    }
166
167    public function hue( $color = null ) {
168        if ( !$color instanceof Less_Tree_Color ) {
169            throw new Less_Exception_Compiler( 'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
170        }
171
172        $c = $color->toHSL();
173        return new Less_Tree_Dimension( $c['h'] );
174    }
175
176    public function saturation( $color = null ) {
177        if ( !$color instanceof Less_Tree_Color ) {
178            throw new Less_Exception_Compiler( 'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
179        }
180
181        $c = $color->toHSL();
182        return new Less_Tree_Dimension( $c['s'] * 100, '%' );
183    }
184
185    public function lightness( $color = null ) {
186        if ( !$color instanceof Less_Tree_Color ) {
187            throw new Less_Exception_Compiler( 'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
188        }
189
190        $c = $color->toHSL();
191        return new Less_Tree_Dimension( $c['l'] * 100, '%' );
192    }
193
194    public function hsvhue( $color = null ) {
195        if ( !$color instanceof Less_Tree_Color ) {
196            throw new Less_Exception_Compiler( 'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
197        }
198
199        $hsv = $color->toHSV();
200        return new Less_Tree_Dimension( $hsv['h'] );
201    }
202
203    public function hsvsaturation( $color = null ) {
204        if ( !$color instanceof Less_Tree_Color ) {
205            throw new Less_Exception_Compiler( 'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
206        }
207
208        $hsv = $color->toHSV();
209        return new Less_Tree_Dimension( $hsv['s'] * 100, '%' );
210    }
211
212    public function hsvvalue( $color = null ) {
213        if ( !$color instanceof Less_Tree_Color ) {
214            throw new Less_Exception_Compiler( 'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
215        }
216
217        $hsv = $color->toHSV();
218        return new Less_Tree_Dimension( $hsv['v'] * 100, '%' );
219    }
220
221    public function red( $color = null ) {
222        if ( !$color instanceof Less_Tree_Color ) {
223            throw new Less_Exception_Compiler( 'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
224        }
225
226        return new Less_Tree_Dimension( $color->rgb[0] );
227    }
228
229    public function green( $color = null ) {
230        if ( !$color instanceof Less_Tree_Color ) {
231            throw new Less_Exception_Compiler( 'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
232        }
233
234        return new Less_Tree_Dimension( $color->rgb[1] );
235    }
236
237    public function blue( $color = null ) {
238        if ( !$color instanceof Less_Tree_Color ) {
239            throw new Less_Exception_Compiler( 'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
240        }
241
242        return new Less_Tree_Dimension( $color->rgb[2] );
243    }
244
245    public function alpha( $color = null ) {
246        if ( !$color instanceof Less_Tree_Color ) {
247            throw new Less_Exception_Compiler( 'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
248        }
249
250        $c = $color->toHSL();
251        return new Less_Tree_Dimension( $c['a'] );
252    }
253
254    public function luma( $color = null ) {
255        if ( !$color instanceof Less_Tree_Color ) {
256            throw new Less_Exception_Compiler( 'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
257        }
258
259        return new Less_Tree_Dimension( $color->luma() * $color->alpha * 100, '%' );
260    }
261
262    public function luminance( $color = null ) {
263        if ( !$color instanceof Less_Tree_Color ) {
264            throw new Less_Exception_Compiler( 'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
265        }
266
267        $luminance =
268            ( 0.2126 * $color->rgb[0] / 255 )
269          + ( 0.7152 * $color->rgb[1] / 255 )
270          + ( 0.0722 * $color->rgb[2] / 255 );
271
272        return new Less_Tree_Dimension( $luminance * $color->alpha * 100, '%' );
273    }
274
275    public function saturate( $color = null, $amount = null, $method = null ) {
276        // filter: saturate(3.2);
277        // should be kept as is, so check for color
278        if ( $color instanceof Less_Tree_Dimension ) {
279            return null;
280        }
281
282        if ( !$color instanceof Less_Tree_Color ) {
283            throw new Less_Exception_Compiler( 'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
284        }
285        if ( !$amount instanceof Less_Tree_Dimension ) {
286            throw new Less_Exception_Compiler( 'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
287        }
288
289        $hsl = $color->toHSL();
290
291        if ( isset( $method ) && $method->value === "relative" ) {
292            $hsl['s'] += $hsl['s'] * $amount->value / 100;
293        } else {
294            $hsl['s'] += $amount->value / 100;
295        }        $hsl['s'] = self::clamp( $hsl['s'] );
296
297        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
298    }
299
300    /**
301     * @param Less_Tree_Color|null $color
302     * @param Less_Tree_Dimension|null $amount
303     */
304    public function desaturate( $color = null, $amount = null, $method = null ) {
305        if ( !$color instanceof Less_Tree_Color ) {
306            throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
307        }
308        if ( !$amount instanceof Less_Tree_Dimension ) {
309            throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
310        }
311
312        $hsl = $color->toHSL();
313
314        if ( isset( $method ) && $method->value === "relative" ) {
315            $hsl['s'] -= $hsl['s'] * $amount->value / 100;
316        } else {
317            $hsl['s'] -= $amount->value / 100;
318        }
319
320        $hsl['s'] = self::clamp( $hsl['s'] );
321
322        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
323    }
324
325    public function lighten( $color = null, $amount = null, $method = null ) {
326        if ( !$color instanceof Less_Tree_Color ) {
327            throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
328        }
329        if ( !$amount instanceof Less_Tree_Dimension ) {
330            throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
331        }
332
333        $hsl = $color->toHSL();
334
335        if ( isset( $method ) && $method->value === "relative" ) {
336            $hsl['l'] += $hsl['l'] * $amount->value / 100;
337        } else {
338            $hsl['l'] += $amount->value / 100;
339        }
340
341        $hsl['l'] = self::clamp( $hsl['l'] );
342
343        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
344    }
345
346    public function darken( $color = null, $amount = null, $method = null ) {
347        if ( !$color instanceof Less_Tree_Color ) {
348            throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
349        }
350        if ( !$amount instanceof Less_Tree_Dimension ) {
351            throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
352        }
353
354        $hsl = $color->toHSL();
355        if ( isset( $method ) && $method->value === "relative" ) {
356            $hsl['l'] -= $hsl['l'] * $amount->value / 100;
357        } else {
358            $hsl['l'] -= $amount->value / 100;
359        }
360        $hsl['l'] = self::clamp( $hsl['l'] );
361
362        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
363    }
364
365    public function fadein( $color = null, $amount = null, $method = null ) {
366        if ( !$color instanceof Less_Tree_Color ) {
367            throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
368        }
369        if ( !$amount instanceof Less_Tree_Dimension ) {
370            throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
371        }
372
373        $hsl = $color->toHSL();
374
375        if ( isset( $method ) && $method->value === "relative" ) {
376            $hsl['a'] += $hsl['a'] * $amount->value / 100;
377        } else {
378            $hsl['a'] += $amount->value / 100;
379        }
380
381        $hsl['a'] = self::clamp( $hsl['a'] );
382        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
383    }
384
385    public function fadeout( $color = null, $amount = null, $method = null ) {
386        if ( !$color instanceof Less_Tree_Color ) {
387            throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
388        }
389        if ( !$amount instanceof Less_Tree_Dimension ) {
390            throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
391        }
392
393        $hsl = $color->toHSL();
394
395        if ( isset( $method ) && $method->value === "relative" ) {
396            $hsl['a'] -= $hsl['a'] * $amount->value / 100;
397        } else {
398            $hsl['a'] -= $amount->value / 100;
399        }
400
401        $hsl['a'] = self::clamp( $hsl['a'] );
402        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
403    }
404
405    public function fade( $color = null, $amount = null ) {
406        if ( !$color instanceof Less_Tree_Color ) {
407            throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
408        }
409        if ( !$amount instanceof Less_Tree_Dimension ) {
410            throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
411        }
412
413        $hsl = $color->toHSL();
414
415        $hsl['a'] = $amount->value / 100;
416        $hsl['a'] = self::clamp( $hsl['a'] );
417        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
418    }
419
420    public function spin( $color = null, $amount = null ) {
421        if ( !$color instanceof Less_Tree_Color ) {
422            throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
423        }
424        if ( !$amount instanceof Less_Tree_Dimension ) {
425            throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
426        }
427
428        $hsl = $color->toHSL();
429        $hue = fmod( $hsl['h'] + $amount->value, 360 );
430
431        $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
432
433        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
434    }
435
436    //
437    // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
438    // https://sass-lang.com/
439    //
440
441    /**
442     * @param Less_Tree|null $color1
443     * @param Less_Tree|null $color2
444     * @param Less_Tree|null $weight
445     */
446    public function mix( $color1 = null, $color2 = null, $weight = null ) {
447        if ( !$color1 instanceof Less_Tree_Color ) {
448            $type = is_object( $color1 ) ? get_class( $color1 ) : gettype( $color1 );
449            throw new Less_Exception_Compiler( "The first argument must be a color, $type given" . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
450        }
451        if ( !$color2 instanceof Less_Tree_Color ) {
452            $type = is_object( $color2 ) ? get_class( $color2 ) : gettype( $color2 );
453            throw new Less_Exception_Compiler( "The second argument must be a color, $type given" . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
454        }
455        if ( !$weight ) {
456            $weight = new Less_Tree_Dimension( '50', '%' );
457        }
458        if ( !$weight instanceof Less_Tree_Dimension ) {
459            $type = is_object( $weight ) ? get_class( $weight ) : gettype( $weight );
460            throw new Less_Exception_Compiler( "The third argument must be a percentage, $type given" . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
461        }
462
463        $p = $weight->value / 100.0;
464        $w = $p * 2 - 1;
465        $hsl1 = $color1->toHSL();
466        $hsl2 = $color2->toHSL();
467        $a = $hsl1['a'] - $hsl2['a'];
468
469        $w1 = ( ( ( ( $w * $a ) == -1 ) ? $w : ( $w + $a ) / ( 1 + $w * $a ) ) + 1 ) / 2;
470        $w2 = 1 - $w1;
471
472        $rgb = [
473            $color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
474            $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
475            $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2
476        ];
477
478        $alpha = $color1->alpha * $p + $color2->alpha * ( 1 - $p );
479
480        return new Less_Tree_Color( $rgb, $alpha );
481    }
482
483    public function greyscale( $color ) {
484        return $this->desaturate( $color, new Less_Tree_Dimension( 100, '%' ) );
485    }
486
487    public function contrast( $color, $dark = null, $light = null, $threshold = null ) {
488        // filter: contrast(3.2);
489        // should be kept as is, so check for color
490        if ( !$color instanceof Less_Tree_Color ) {
491            return null;
492        }
493        if ( !$light ) {
494            $light = $this->rgba( 255, 255, 255, 1.0 );
495        }
496        if ( !$dark ) {
497            $dark = $this->rgba( 0, 0, 0, 1.0 );
498        }
499
500        if ( !$dark instanceof Less_Tree_Color ) {
501            throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
502        }
503        if ( !$light instanceof Less_Tree_Color ) {
504            throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
505        }
506
507        // Figure out which is actually light and dark!
508        if ( $dark->luma() > $light->luma() ) {
509            $t = $light;
510            $light = $dark;
511            $dark = $t;
512        }
513        if ( !$threshold ) {
514            $threshold = 0.43;
515        } else {
516            $threshold = self::number( $threshold );
517        }
518
519        if ( $color->luma() < $threshold ) {
520            return $light;
521        } else {
522            return $dark;
523        }
524    }
525
526    public function e( $str ) {
527        if ( is_string( $str ) ) {
528            return new Less_Tree_Anonymous( $str );
529        }
530        return new Less_Tree_Anonymous( $str instanceof Less_Tree_JavaScript ? $str->expression : $str->value );
531    }
532
533    public function escape( $str ) {
534        $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%3F' => '?', '%26' => '&', '%2C' => ',', '%2F' => '/', '%40' => '@', '%2B' => '+', '%24' => '$' ];
535
536        return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
537    }
538
539    /**
540     * todo: This function will need some additional work to make it work the same as less.js
541     *
542     */
543    public function replace( $string, $pattern, $replacement, $flags = null ) {
544        $result = $string->value;
545
546        $expr = '/' . str_replace( '/', '\\/', $pattern->value ) . '/';
547        if ( $flags && $flags->value ) {
548            $expr .= self::replace_flags( $flags->value );
549        }
550        $replacement = ( $replacement instanceof Less_Tree_Quoted ) ?
551            $replacement->value : $replacement->toCSS();
552
553        if ( $flags && $flags->value && preg_match( '/g/', $flags->value ) ) {
554            $result = preg_replace( $expr, $replacement, $result );
555        } else {
556            $result = preg_replace( $expr, $replacement, $result, 1 );
557        }
558
559        if ( $string instanceof Less_Tree_Quoted ) {
560            return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
561        }
562        return new Less_Tree_Quoted( '', $result );
563    }
564
565    public static function replace_flags( $flags ) {
566        return str_replace( [ 'e', 'g' ], '', $flags );
567    }
568
569    public function _percent( $string, ...$args ) {
570        $result = $string->value;
571
572        foreach ( $args as $arg ) {
573            if ( preg_match( '/%[sda]/i', $result, $token ) ) {
574                $token = $token[0];
575                $value = ( ( $arg instanceof Less_Tree_Quoted ) &&
576                    stristr( $token, 's' ) ? $arg->value : $arg->toCSS() );
577
578                $value = preg_match( '/[A-Z]$/', $token ) ? urlencode( $value ) : $value;
579                $result = preg_replace( '/%[sda]/i', $value, $result, 1 );
580            }
581        }
582        $result = str_replace( '%%', '%', $result );
583
584        if ( $string instanceof Less_Tree_Quoted ) {
585            return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
586        }
587        return new Less_Tree_Quoted( '', $result );
588    }
589
590    public function unit( $val, $unit = null ) {
591        if ( !( $val instanceof Less_Tree_Dimension ) ) {
592            throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
593        }
594
595        if ( $unit ) {
596            if ( $unit instanceof Less_Tree_Keyword ) {
597                $unit = $unit->value;
598            } else {
599                $unit = $unit->toCSS();
600            }
601        } else {
602            $unit = "";
603        }
604        return new Less_Tree_Dimension( $val->value, $unit );
605    }
606
607    public function convert( $val, $unit ) {
608        return $val->convertTo( $unit->value );
609    }
610
611    public function round( $n, $f = false ) {
612        $fraction = 0;
613        if ( $f !== false ) {
614            $fraction = $f->value;
615        }
616
617        return $this->_math( [ Less_Parser::class, 'round' ], null, $n, $fraction );
618    }
619
620    public function pi() {
621        return new Less_Tree_Dimension( M_PI );
622    }
623
624    public function mod( $a, $b ) {
625        return new Less_Tree_Dimension( $a->value % $b->value, $a->unit );
626    }
627
628    public function pow( $x, $y ) {
629        if ( is_numeric( $x ) && is_numeric( $y ) ) {
630            $x = new Less_Tree_Dimension( $x );
631            $y = new Less_Tree_Dimension( $y );
632        } elseif ( !( $x instanceof Less_Tree_Dimension ) || !( $y instanceof Less_Tree_Dimension ) ) {
633            throw new Less_Exception_Compiler( 'Arguments must be numbers' );
634        }
635
636        return new Less_Tree_Dimension( pow( $x->value, $y->value ), $x->unit );
637    }
638
639    // var mathFunctions = [{name:"ce ...
640    public function ceil( $n ) {
641        return $this->_math( 'ceil', null, $n );
642    }
643
644    public function floor( $n ) {
645        return $this->_math( 'floor', null, $n );
646    }
647
648    public function sqrt( $n ) {
649        return $this->_math( 'sqrt', null, $n );
650    }
651
652    public function abs( $n ) {
653        return $this->_math( 'abs', null, $n );
654    }
655
656    public function tan( $n ) {
657        return $this->_math( 'tan', '', $n );
658    }
659
660    public function sin( $n ) {
661        return $this->_math( 'sin', '', $n );
662    }
663
664    public function cos( $n ) {
665        return $this->_math( 'cos', '', $n );
666    }
667
668    public function atan( $n ) {
669        return $this->_math( 'atan', 'rad', $n );
670    }
671
672    public function asin( $n ) {
673        return $this->_math( 'asin', 'rad', $n );
674    }
675
676    public function acos( $n ) {
677        return $this->_math( 'acos', 'rad', $n );
678    }
679
680    private function _math( $fn, $unit, ...$args ) {
681        if ( $args[0] instanceof Less_Tree_Dimension ) {
682            if ( $unit === null ) {
683                $unit = $args[0]->unit;
684            } else {
685                $args[0] = $args[0]->unify();
686            }
687            $args[0] = (float)$args[0]->value;
688            return new Less_Tree_Dimension( $fn( ...$args ), $unit );
689        } elseif ( is_numeric( $args[0] ) ) {
690            return $fn( ...$args );
691        } else {
692            throw new Less_Exception_Compiler( "math functions take numbers as parameters" );
693        }
694    }
695
696    /**
697     * @param bool $isMin
698     * @param array<Less_Tree> $args
699     */
700    private function _minmax( $isMin, $args ) {
701        $arg_count = count( $args );
702
703        if ( $arg_count < 1 ) {
704            throw new Less_Exception_Compiler( 'one or more arguments required' );
705        }
706
707        $j = null;
708        $unitClone = null;
709        $unitStatic = null;
710
711        // elems only contains original argument values.
712        $order = [];
713        // key is the unit.toString() for unified tree.Dimension values,
714        // value is the index into the order array.
715        $values = [];
716
717        for ( $i = 0; $i < $arg_count; $i++ ) {
718            $current = $args[$i];
719            if ( !( $current instanceof Less_Tree_Dimension ) ) {
720                if ( $args[$i] instanceof Less_Tree_HasValueProperty && is_array( $args[$i]->value ) ) {
721                    $args[] = $args[$i]->value;
722                }
723                continue;
724            }
725            // PhanTypeInvalidDimOffset -- False positive, safe after continue or non-first iterations
726            '@phan-var non-empty-list<Less_Tree_Dimension> $order';
727
728            if ( $current->unit->toString() === '' && !$unitClone ) {
729                $temp = new Less_Tree_Dimension( $current->value, $unitClone );
730                $currentUnified = $temp->unify();
731            } else {
732                $currentUnified = $current->unify();
733            }
734
735            if ( $currentUnified->unit->toString() === "" && !$unitStatic ) {
736                $unit = $unitStatic;
737            } else {
738                $unit = $currentUnified->unit->toString();
739            }
740
741            if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
742                $unitStatic = $unit;
743            }
744
745            if ( $unit != '' && !$unitClone ) {
746                $unitClone = $current->unit->toString();
747            }
748
749            if ( isset( $values[''] ) && $unit !== '' && $unit === $unitStatic ) {
750                $j = $values[''];
751            } elseif ( isset( $values[$unit] ) ) {
752                $j = $values[$unit];
753            } else {
754
755                if ( $unitStatic && $unit !== $unitStatic ) {
756                    throw new Less_Exception_Compiler( 'incompatible types' );
757                }
758                $values[$unit] = count( $order );
759                $order[] = $current;
760                continue;
761            }
762
763            if ( $order[$j]->unit->toString() === "" && $unitClone ) {
764                $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone );
765                $referenceUnified = $temp->unify();
766            } else {
767                $referenceUnified = $order[$j]->unify();
768            }
769            if ( ( $isMin && $currentUnified->value < $referenceUnified->value ) || ( !$isMin && $currentUnified->value > $referenceUnified->value ) ) {
770                $order[$j] = $current;
771            }
772        }
773
774        if ( count( $order ) == 1 ) {
775            return $order[0];
776        }
777        $args = [];
778        foreach ( $order as $a ) {
779            $args[] = $a->toCSS();
780        }
781        return new Less_Tree_Anonymous( ( $isMin ? 'min(' : 'max(' ) . implode( ( Less_Parser::$options['compress'] ? ',' : ', ' ), $args ) . ')' );
782    }
783
784    public function min( ...$args ) {
785        return $this->_minmax( true, $args );
786    }
787
788    public function max( ...$args ) {
789        return $this->_minmax( false, $args );
790    }
791
792    public function getunit( $n ) {
793        return new Less_Tree_Anonymous( $n->unit );
794    }
795
796    public function argb( $color ) {
797        if ( !$color instanceof Less_Tree_Color ) {
798            throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
799        }
800
801        return new Less_Tree_Anonymous( $color->toARGB() );
802    }
803
804    public function percentage( $n ) {
805        return new Less_Tree_Dimension( $n->value * 100, '%' );
806    }
807
808    /**
809     * @see less-2.5.3.js#colorFunctions.color
810     * @param Less_Tree_Quoted|Less_Tree_Color|Less_Tree_Keyword $c
811     * @return Less_Tree_Color
812     */
813    public function color( $c ) {
814        if ( ( $c instanceof Less_Tree_Quoted ) &&
815            preg_match( '/^#([a-f0-9]{6}|[a-f0-9]{3})/', $c->value )
816        ) {
817            return new Less_Tree_Color( substr( $c->value, 1 ) );
818        }
819
820        if ( ( $c instanceof Less_Tree_Color ) || ( $c = Less_Tree_Color::fromKeyword( $c->value ) ) ) {
821            $c->value = null;
822            return $c;
823        }
824
825        throw new Less_Exception_Compiler( "argument must be a color keyword or 3/6 digit hex e.g. #FFF" );
826    }
827
828    public function isruleset( $n ) {
829        return new Less_Tree_Keyword( $n instanceof Less_Tree_DetachedRuleset ? 'true' : 'false' );
830    }
831
832    public function iscolor( $n ) {
833        return new Less_Tree_Keyword( $n instanceof Less_Tree_Color ? 'true' : 'false' );
834    }
835
836    public function isnumber( $n ) {
837        return new Less_Tree_Keyword( $n instanceof Less_Tree_Dimension ? 'true' : 'false' );
838    }
839
840    public function isstring( $n ) {
841        return new Less_Tree_Keyword( $n instanceof Less_Tree_Quoted ? 'true' : 'false' );
842    }
843
844    public function iskeyword( $n ) {
845        return new Less_Tree_Keyword( $n instanceof Less_Tree_Keyword ? 'true' : 'false' );
846    }
847
848    public function isurl( $n ) {
849        return new Less_Tree_Keyword( $n instanceof Less_Tree_Url ? 'true' : 'false' );
850    }
851
852    public function ispixel( $n ) {
853        return $this->isunit( $n, 'px' );
854    }
855
856    public function ispercentage( $n ) {
857        return $this->isunit( $n, '%' );
858    }
859
860    public function isem( $n ) {
861        return $this->isunit( $n, 'em' );
862    }
863
864    /**
865     * @param Less_Tree $n
866     * @param Less_Tree|string $unit
867     */
868    public function isunit( $n, $unit ) {
869        if ( $unit instanceof Less_Tree_Keyword || $unit instanceof Less_Tree_Quoted ) {
870            $unit = $unit->value;
871        }
872
873        return new Less_Tree_Keyword( $n instanceof Less_Tree_Dimension && $n->unit->is( $unit ) ? 'true' : 'false' );
874    }
875
876    public function tint( $color, $amount = null ) {
877        return $this->mix( $this->rgb( 255, 255, 255 ), $color, $amount );
878    }
879
880    public function shade( $color, $amount = null ) {
881        return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
882    }
883
884    public function extract( $values, $index ) {
885        $index = (int)$index->value - 1; // (1-based index)
886        // handle non-array values as an array of length 1
887        // return 'undefined' if index is invalid
888        if ( !( $values instanceof Less_Tree_Color ) && is_array( $values->value ) ) {
889            if ( isset( $values->value[$index] ) ) {
890                return $values->value[$index];
891            }
892            return null;
893
894        } elseif ( (int)$index === 0 ) {
895            return $values;
896        }
897
898        return null;
899    }
900
901    public function length( $values ) {
902        $n = ( $values instanceof Less_Tree_Expression || $values instanceof Less_Tree_Value ) ?
903            count( $values->value ) : 1;
904        return new Less_Tree_Dimension( $n );
905    }
906
907    public function datauri( $mimetypeNode, $filePathNode = null ) {
908        $filePath = ( $filePathNode ? $filePathNode->value : null );
909        $mimetype = $mimetypeNode->value;
910
911        $args = 2;
912        if ( !$filePath ) {
913            $filePath = $mimetype;
914            $args = 1;
915        }
916
917        $filePath = str_replace( '\\', '/', $filePath );
918        if ( Less_Environment::isPathRelative( $filePath ) ) {
919            $currentFileInfo = $this->currentFileInfo;
920            '@phan-var array $currentFileInfo';
921            if ( Less_Parser::$options['relativeUrls'] ) {
922                $temp = $currentFileInfo['currentDirectory'];
923            } else {
924                $temp = $currentFileInfo['entryPath'];
925            }
926
927            if ( !empty( $temp ) ) {
928                $filePath = Less_Environment::normalizePath( rtrim( $temp, '/' ) . '/' . $filePath );
929            }
930
931        }
932
933        // detect the mimetype if not given
934        if ( $args < 2 ) {
935
936            /* incomplete
937            $mime = require('mime');
938            mimetype = mime.lookup(path);
939
940            // use base 64 unless it's an ASCII or UTF-8 format
941            var charset = mime.charsets.lookup(mimetype);
942            useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
943            if (useBase64) mimetype += ';base64';
944            */
945
946            $mimetype = Less_Mime::lookup( $filePath );
947
948            $charset = Less_Mime::charsets_lookup( $mimetype );
949            $useBase64 = !in_array( $charset, [ 'US-ASCII', 'UTF-8' ] );
950            if ( $useBase64 ) {
951                $mimetype .= ';base64';
952            }
953
954        } else {
955            $useBase64 = preg_match( '/;base64$/', $mimetype );
956        }
957
958        if ( file_exists( $filePath ) ) {
959            $buf = @file_get_contents( $filePath );
960        } else {
961            $buf = false;
962        }
963
964        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
965        // and the --ieCompat flag is enabled, return a normal url() instead.
966        $DATA_URI_MAX_KB = 32;
967        $fileSizeInKB = round( strlen( $buf ) / 1024 );
968        if ( $fileSizeInKB >= $DATA_URI_MAX_KB ) {
969            $url = new Less_Tree_Url( ( $filePathNode ?: $mimetypeNode ), $this->currentFileInfo );
970            return $url->compile( $this->env );
971        }
972
973        if ( $buf ) {
974            $buf = $useBase64 ? base64_encode( $buf ) : rawurlencode( $buf );
975            $filePath = '"data:' . $mimetype . ',' . $buf . '"';
976        }
977
978        return new Less_Tree_Url( new Less_Tree_Anonymous( $filePath ) );
979    }
980
981    // svg-gradient
982    public function svggradient( $direction, ...$stops ) {
983        $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
984
985        if ( count( $stops ) < 2 ) {
986            throw new Less_Exception_Compiler( $throw_message );
987        }
988
989        $gradientType = 'linear';
990        $rectangleDimension = 'x="0" y="0" width="1" height="1"';
991        $useBase64 = true;
992        $directionValue = $direction->toCSS();
993
994        switch ( $directionValue ) {
995            case "to bottom":
996                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
997                break;
998            case "to right":
999                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
1000                break;
1001            case "to bottom right":
1002                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
1003                break;
1004            case "to top right":
1005                $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
1006                break;
1007            case "ellipse":
1008            case "ellipse at center":
1009                $gradientType = "radial";
1010                $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
1011                $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
1012                break;
1013            default:
1014                throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
1015        }
1016
1017        $returner = '<?xml version="1.0" ?>' .
1018            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
1019            '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
1020
1021        for ( $i = 0; $i < count( $stops ); $i++ ) {
1022
1023            if ( $stops[$i] instanceof Less_Tree_Expression ) {
1024                $color = $stops[$i]->value[0];
1025                $position = $stops[$i]->value[1];
1026            } else {
1027                $color = $stops[$i];
1028                $position = null;
1029            }
1030
1031            if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
1032                throw new Less_Exception_Compiler( $throw_message );
1033            }
1034            if ( $position ) {
1035                $positionValue = $position->toCSS();
1036            } elseif ( $i === 0 ) {
1037                $positionValue = '0%';
1038            } else {
1039                $positionValue = '100%';
1040            }
1041            $alpha = $color->alpha;
1042            $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
1043        }
1044
1045        $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
1046
1047        if ( $useBase64 ) {
1048            $returner = "'data:image/svg+xml;base64," . base64_encode( $returner ) . "'";
1049        } else {
1050            $returner = "'data:image/svg+xml," . $returner . "'";
1051        }
1052
1053        return new Less_Tree_Url( new Less_Tree_Anonymous( $returner ) );
1054    }
1055
1056    /**
1057     * Php version of javascript's `encodeURIComponent` function
1058     *
1059     * @param string $string The string to encode
1060     * @return string The encoded string
1061     */
1062    public static function encodeURIComponent( $string ) {
1063        $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ];
1064        return strtr( rawurlencode( $string ), $revert );
1065    }
1066
1067    // Color Blending
1068    // ref: https://www.w3.org/TR/compositing-1/
1069    public function colorBlend( $mode, $color1, $color2 ) {
1070        // backdrop
1071        $ab = $color1->alpha;
1072        // source
1073        $as = $color2->alpha;
1074        $result = [];
1075
1076        $ar = $as + $ab * ( 1 - $as );
1077        for ( $i = 0; $i < 3; $i++ ) {
1078            $cb = $color1->rgb[$i] / 255;
1079            $cs = $color2->rgb[$i] / 255;
1080            $cr = $mode( $cb, $cs );
1081            if ( $ar ) {
1082                $cr = ( $as * $cs + $ab * ( $cb - $as * ( $cb + $cs - $cr ) ) ) / $ar;
1083            }
1084            $result[$i] = $cr * 255;
1085        }
1086
1087        return new Less_Tree_Color( $result, $ar );
1088    }
1089
1090    public function multiply( $color1 = null, $color2 = null ) {
1091        if ( !$color1 instanceof Less_Tree_Color ) {
1092            throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1093        }
1094        if ( !$color2 instanceof Less_Tree_Color ) {
1095            throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1096        }
1097
1098        return $this->colorBlend( [ $this, 'colorBlendMultiply' ], $color1, $color2 );
1099    }
1100
1101    private function colorBlendMultiply( $cb, $cs ) {
1102        return $cb * $cs;
1103    }
1104
1105    public function screen( $color1 = null, $color2 = null ) {
1106        if ( !$color1 instanceof Less_Tree_Color ) {
1107            throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1108        }
1109        if ( !$color2 instanceof Less_Tree_Color ) {
1110            throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1111        }
1112
1113        return $this->colorBlend( [ $this, 'colorBlendScreen' ], $color1, $color2 );
1114    }
1115
1116    private function colorBlendScreen( $cb, $cs ) {
1117        return $cb + $cs - $cb * $cs;
1118    }
1119
1120    public function overlay( $color1 = null, $color2 = null ) {
1121        if ( !$color1 instanceof Less_Tree_Color ) {
1122            throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1123        }
1124        if ( !$color2 instanceof Less_Tree_Color ) {
1125            throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1126        }
1127
1128        return $this->colorBlend( [ $this, 'colorBlendOverlay' ], $color1, $color2 );
1129    }
1130
1131    private function colorBlendOverlay( $cb, $cs ) {
1132        $cb *= 2;
1133        return ( $cb <= 1 )
1134            ? $this->colorBlendMultiply( $cb, $cs )
1135            : $this->colorBlendScreen( $cb - 1, $cs );
1136    }
1137
1138    public function softlight( $color1 = null, $color2 = null ) {
1139        if ( !$color1 instanceof Less_Tree_Color ) {
1140            throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1141        }
1142        if ( !$color2 instanceof Less_Tree_Color ) {
1143            throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1144        }
1145
1146        return $this->colorBlend( [ $this, 'colorBlendSoftlight' ], $color1, $color2 );
1147    }
1148
1149    private function colorBlendSoftlight( $cb, $cs ) {
1150        $d = 1;
1151        $e = $cb;
1152        if ( $cs > 0.5 ) {
1153            $e = 1;
1154            $d = ( $cb > 0.25 ) ? sqrt( $cb )
1155                : ( ( 16 * $cb - 12 ) * $cb + 4 ) * $cb;
1156        }
1157        return $cb - ( 1 - 2 * $cs ) * $e * ( $d - $cb );
1158    }
1159
1160    public function hardlight( $color1 = null, $color2 = null ) {
1161        if ( !$color1 instanceof Less_Tree_Color ) {
1162            throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1163        }
1164        if ( !$color2 instanceof Less_Tree_Color ) {
1165            throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1166        }
1167
1168        return $this->colorBlend( [ $this, 'colorBlendHardlight' ], $color1, $color2 );
1169    }
1170
1171    private function colorBlendHardlight( $cb, $cs ) {
1172        return $this->colorBlendOverlay( $cs, $cb );
1173    }
1174
1175    public function difference( $color1 = null, $color2 = null ) {
1176        if ( !$color1 instanceof Less_Tree_Color ) {
1177            throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1178        }
1179        if ( !$color2 instanceof Less_Tree_Color ) {
1180            throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1181        }
1182
1183        return $this->colorBlend( [ $this, 'colorBlendDifference' ], $color1, $color2 );
1184    }
1185
1186    private function colorBlendDifference( $cb, $cs ) {
1187        return abs( $cb - $cs );
1188    }
1189
1190    public function exclusion( $color1 = null, $color2 = null ) {
1191        if ( !$color1 instanceof Less_Tree_Color ) {
1192            throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1193        }
1194        if ( !$color2 instanceof Less_Tree_Color ) {
1195            throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1196        }
1197
1198        return $this->colorBlend( [ $this, 'colorBlendExclusion' ], $color1, $color2 );
1199    }
1200
1201    private function colorBlendExclusion( $cb, $cs ) {
1202        return $cb + $cs - 2 * $cb * $cs;
1203    }
1204
1205    public function average( $color1 = null, $color2 = null ) {
1206        if ( !$color1 instanceof Less_Tree_Color ) {
1207            throw new Less_Exception_Compiler( 'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1208        }
1209        if ( !$color2 instanceof Less_Tree_Color ) {
1210            throw new Less_Exception_Compiler( 'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1211        }
1212
1213        return $this->colorBlend( [ $this, 'colorBlendAverage' ], $color1, $color2 );
1214    }
1215
1216    // non-w3c functions:
1217    public function colorBlendAverage( $cb, $cs ) {
1218        return ( $cb + $cs ) / 2;
1219    }
1220
1221    public function negation( $color1 = null, $color2 = null ) {
1222        if ( !$color1 instanceof Less_Tree_Color ) {
1223            throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1224        }
1225        if ( !$color2 instanceof Less_Tree_Color ) {
1226            throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1227        }
1228
1229        return $this->colorBlend( [ $this, 'colorBlendNegation' ], $color1, $color2 );
1230    }
1231
1232    public function colorBlendNegation( $cb, $cs ) {
1233        return 1 - abs( $cb + $cs - 1 );
1234    }
1235
1236    // ~ End of Color Blending
1237
1238}