Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.83% covered (warning)
81.83%
446 / 545
55.45% covered (warning)
55.45%
56 / 101
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Functions
81.83% covered (warning)
81.83%
446 / 545
55.45% covered (warning)
55.45%
56 / 101
1007.68
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
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
6.29
 desaturate
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 lighten
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 darken
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 fadein
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 fadeout
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.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
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
9.21
 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%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 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%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 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
38.20
 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
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 iscolor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isnumber
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isstring
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 iskeyword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isurl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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
4
 _isa
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 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
93.94% covered (success)
93.94%
31 / 33
0.00% covered (danger)
0.00%
0 / 1
13.04
 svggradient
61.22% covered (warning)
61.22%
30 / 49
0.00% covered (danger)
0.00%
0 / 1
43.32
 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 = 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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $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( Less_Parser::round( $luminance * $color->alpha * 100 ), '%' );
273    }
274
275    public function saturate( $color = null, $amount = 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        $hsl['s'] += $amount->value / 100;
292        $hsl['s'] = self::clamp( $hsl['s'] );
293
294        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
295    }
296
297    /**
298     * @param Less_Tree_Color|null $color
299     * @param Less_Tree_Dimension|null $amount
300     */
301    public function desaturate( $color = null, $amount = null ) {
302        if ( !$color instanceof Less_Tree_Color ) {
303            throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
304        }
305        if ( !$amount instanceof Less_Tree_Dimension ) {
306            throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
307        }
308
309        $hsl = $color->toHSL();
310        $hsl['s'] -= $amount->value / 100;
311        $hsl['s'] = self::clamp( $hsl['s'] );
312
313        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
314    }
315
316    public function lighten( $color = null, $amount = null ) {
317        if ( !$color instanceof Less_Tree_Color ) {
318            throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
319        }
320        if ( !$amount instanceof Less_Tree_Dimension ) {
321            throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
322        }
323
324        $hsl = $color->toHSL();
325
326        $hsl['l'] += $amount->value / 100;
327        $hsl['l'] = self::clamp( $hsl['l'] );
328
329        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
330    }
331
332    public function darken( $color = null, $amount = null ) {
333        if ( !$color instanceof Less_Tree_Color ) {
334            throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
335        }
336        if ( !$amount instanceof Less_Tree_Dimension ) {
337            throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
338        }
339
340        $hsl = $color->toHSL();
341        $hsl['l'] -= $amount->value / 100;
342        $hsl['l'] = self::clamp( $hsl['l'] );
343
344        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
345    }
346
347    public function fadein( $color = null, $amount = null ) {
348        if ( !$color instanceof Less_Tree_Color ) {
349            throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
350        }
351        if ( !$amount instanceof Less_Tree_Dimension ) {
352            throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
353        }
354
355        $hsl = $color->toHSL();
356        $hsl['a'] += $amount->value / 100;
357        $hsl['a'] = self::clamp( $hsl['a'] );
358        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
359    }
360
361    public function fadeout( $color = null, $amount = null ) {
362        if ( !$color instanceof Less_Tree_Color ) {
363            throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
364        }
365        if ( !$amount instanceof Less_Tree_Dimension ) {
366            throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
367        }
368
369        $hsl = $color->toHSL();
370        $hsl['a'] -= $amount->value / 100;
371        $hsl['a'] = self::clamp( $hsl['a'] );
372        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
373    }
374
375    public function fade( $color = null, $amount = null ) {
376        if ( !$color instanceof Less_Tree_Color ) {
377            throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
378        }
379        if ( !$amount instanceof Less_Tree_Dimension ) {
380            throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
381        }
382
383        $hsl = $color->toHSL();
384
385        $hsl['a'] = $amount->value / 100;
386        $hsl['a'] = self::clamp( $hsl['a'] );
387        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
388    }
389
390    public function spin( $color = null, $amount = null ) {
391        if ( !$color instanceof Less_Tree_Color ) {
392            throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
393        }
394        if ( !$amount instanceof Less_Tree_Dimension ) {
395            throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
396        }
397
398        $hsl = $color->toHSL();
399        $hue = fmod( $hsl['h'] + $amount->value, 360 );
400
401        $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
402
403        return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
404    }
405
406    //
407    // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
408    // https://sass-lang.com/
409    //
410
411    /**
412     * @param Less_Tree|null $color1
413     * @param Less_Tree|null $color2
414     * @param Less_Tree|null $weight
415     */
416    public function mix( $color1 = null, $color2 = null, $weight = null ) {
417        if ( !$color1 instanceof Less_Tree_Color ) {
418            throw new Less_Exception_Compiler( 'The first argument to mix must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
419        }
420        if ( !$color2 instanceof Less_Tree_Color ) {
421            throw new Less_Exception_Compiler( 'The second argument to mix must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
422        }
423        if ( !$weight ) {
424            $weight = new Less_Tree_Dimension( '50', '%' );
425        }
426        if ( !$weight instanceof Less_Tree_Dimension ) {
427            throw new Less_Exception_Compiler( 'The third argument to contrast must be a percentage' . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
428        }
429
430        $p = $weight->value / 100.0;
431        $w = $p * 2 - 1;
432        $hsl1 = $color1->toHSL();
433        $hsl2 = $color2->toHSL();
434        $a = $hsl1['a'] - $hsl2['a'];
435
436        $w1 = ( ( ( ( $w * $a ) == -1 ) ? $w : ( $w + $a ) / ( 1 + $w * $a ) ) + 1 ) / 2;
437        $w2 = 1 - $w1;
438
439        $rgb = [
440            $color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
441            $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
442            $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2
443        ];
444
445        $alpha = $color1->alpha * $p + $color2->alpha * ( 1 - $p );
446
447        return new Less_Tree_Color( $rgb, $alpha );
448    }
449
450    public function greyscale( $color ) {
451        return $this->desaturate( $color, new Less_Tree_Dimension( 100, '%' ) );
452    }
453
454    public function contrast( $color, $dark = null, $light = null, $threshold = null ) {
455        // filter: contrast(3.2);
456        // should be kept as is, so check for color
457        if ( !$color instanceof Less_Tree_Color ) {
458            return null;
459        }
460        if ( !$light ) {
461            $light = $this->rgba( 255, 255, 255, 1.0 );
462        }
463        if ( !$dark ) {
464            $dark = $this->rgba( 0, 0, 0, 1.0 );
465        }
466
467        if ( !$dark instanceof Less_Tree_Color ) {
468            throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
469        }
470        if ( !$light instanceof Less_Tree_Color ) {
471            throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
472        }
473
474        // Figure out which is actually light and dark!
475        if ( $dark->luma() > $light->luma() ) {
476            $t = $light;
477            $light = $dark;
478            $dark = $t;
479        }
480        if ( !$threshold ) {
481            $threshold = 0.43;
482        } else {
483            $threshold = self::number( $threshold );
484        }
485
486        if ( $color->luma() < $threshold ) {
487            return $light;
488        } else {
489            return $dark;
490        }
491    }
492
493    public function e( $str ) {
494        if ( is_string( $str ) ) {
495            return new Less_Tree_Anonymous( $str );
496        }
497        return new Less_Tree_Anonymous( $str instanceof Less_Tree_JavaScript ? $str->expression : $str->value );
498    }
499
500    public function escape( $str ) {
501        $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'",'%3F' => '?','%26' => '&','%2C' => ',','%2F' => '/','%40' => '@','%2B' => '+','%24' => '$' ];
502
503        return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
504    }
505
506    /**
507     * todo: This function will need some additional work to make it work the same as less.js
508     *
509     */
510    public function replace( $string, $pattern, $replacement, $flags = null ) {
511        $result = $string->value;
512
513        $expr = '/' . str_replace( '/', '\\/', $pattern->value ) . '/';
514        if ( $flags && $flags->value ) {
515            $expr .= self::replace_flags( $flags->value );
516        }
517
518        $result = preg_replace( $expr, $replacement->value, $result );
519
520        if ( property_exists( $string, 'quote' ) ) {
521            return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
522        }
523        return new Less_Tree_Quoted( '', $result );
524    }
525
526    public static function replace_flags( $flags ) {
527        return str_replace( [ 'e', 'g' ], '', $flags );
528    }
529
530    public function _percent( $string, ...$args ) {
531        $result = $string->value;
532
533        foreach ( $args as $arg ) {
534            if ( preg_match( '/%[sda]/i', $result, $token ) ) {
535                $token = $token[0];
536                $value = stristr( $token, 's' ) ? $arg->value : $arg->toCSS();
537                $value = preg_match( '/[A-Z]$/', $token ) ? urlencode( $value ) : $value;
538                $result = preg_replace( '/%[sda]/i', $value, $result, 1 );
539            }
540        }
541        $result = str_replace( '%%', '%', $result );
542
543        return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
544    }
545
546    public function unit( $val, $unit = null ) {
547        if ( !( $val instanceof Less_Tree_Dimension ) ) {
548            throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
549        }
550
551        if ( $unit ) {
552            if ( $unit instanceof Less_Tree_Keyword ) {
553                $unit = $unit->value;
554            } else {
555                $unit = $unit->toCSS();
556            }
557        } else {
558            $unit = "";
559        }
560        return new Less_Tree_Dimension( $val->value, $unit );
561    }
562
563    public function convert( $val, $unit ) {
564        return $val->convertTo( $unit->value );
565    }
566
567    public function round( $n, $f = false ) {
568        $fraction = 0;
569        if ( $f !== false ) {
570            $fraction = $f->value;
571        }
572
573        return $this->_math( 'Less_Parser::round', null, $n, $fraction );
574    }
575
576    public function pi() {
577        return new Less_Tree_Dimension( M_PI );
578    }
579
580    public function mod( $a, $b ) {
581        return new Less_Tree_Dimension( $a->value % $b->value, $a->unit );
582    }
583
584    public function pow( $x, $y ) {
585        if ( is_numeric( $x ) && is_numeric( $y ) ) {
586            $x = new Less_Tree_Dimension( $x );
587            $y = new Less_Tree_Dimension( $y );
588        } elseif ( !( $x instanceof Less_Tree_Dimension ) || !( $y instanceof Less_Tree_Dimension ) ) {
589            throw new Less_Exception_Compiler( 'Arguments must be numbers' );
590        }
591
592        return new Less_Tree_Dimension( pow( $x->value, $y->value ), $x->unit );
593    }
594
595    // var mathFunctions = [{name:"ce ...
596    public function ceil( $n ) {
597        return $this->_math( 'ceil', null, $n );
598    }
599
600    public function floor( $n ) {
601    return $this->_math( 'floor', null, $n );
602    }
603
604    public function sqrt( $n ) {
605        return $this->_math( 'sqrt', null, $n );
606    }
607
608    public function abs( $n ) {
609        return $this->_math( 'abs', null, $n );
610    }
611
612    public function tan( $n ) {
613        return $this->_math( 'tan', '', $n );
614    }
615
616    public function sin( $n ) {
617        return $this->_math( 'sin', '', $n );
618    }
619
620    public function cos( $n ) {
621        return $this->_math( 'cos', '', $n );
622    }
623
624    public function atan( $n ) {
625        return $this->_math( 'atan', 'rad', $n );
626    }
627
628    public function asin( $n ) {
629        return $this->_math( 'asin', 'rad', $n );
630    }
631
632    public function acos( $n ) {
633        return $this->_math( 'acos', 'rad', $n );
634    }
635
636    private function _math( $fn, $unit, ...$args ) {
637        if ( $args[0] instanceof Less_Tree_Dimension ) {
638            if ( $unit === null ) {
639                $unit = $args[0]->unit;
640            } else {
641                $args[0] = $args[0]->unify();
642            }
643            $args[0] = (float)$args[0]->value;
644            return new Less_Tree_Dimension( $fn( ...$args ), $unit );
645        } elseif ( is_numeric( $args[0] ) ) {
646            return $fn( ...$args );
647        } else {
648            throw new Less_Exception_Compiler( "math functions take numbers as parameters" );
649        }
650    }
651
652    /**
653     * @param bool $isMin
654     * @param array<Less_Tree> $args
655     */
656    private function _minmax( $isMin, $args ) {
657        $arg_count = count( $args );
658
659        if ( $arg_count < 1 ) {
660            throw new Less_Exception_Compiler( 'one or more arguments required' );
661        }
662
663        $j = null;
664        $unitClone = null;
665        $unitStatic = null;
666
667        // elems only contains original argument values.
668        $order = [];
669        // key is the unit.toString() for unified tree.Dimension values,
670        // value is the index into the order array.
671        $values = [];
672
673        for ( $i = 0; $i < $arg_count; $i++ ) {
674            $current = $args[$i];
675            if ( !( $current instanceof Less_Tree_Dimension ) ) {
676                if ( $args[$i] instanceof Less_Tree_HasValueProperty && is_array( $args[$i]->value ) ) {
677                    $args[] = $args[$i]->value;
678                }
679                continue;
680            }
681            // PhanTypeInvalidDimOffset -- False positive, safe after continue or non-first iterations
682            '@phan-var non-empty-list<Less_Tree_Dimension> $order';
683
684            if ( $current->unit->toString() === '' && !$unitClone ) {
685                $temp = new Less_Tree_Dimension( $current->value, $unitClone );
686                $currentUnified = $temp->unify();
687            } else {
688                $currentUnified = $current->unify();
689            }
690
691            if ( $currentUnified->unit->toString() === "" && !$unitStatic ) {
692                $unit = $unitStatic;
693            } else {
694                $unit = $currentUnified->unit->toString();
695            }
696
697            if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
698                $unitStatic = $unit;
699            }
700
701            if ( $unit != '' && !$unitClone ) {
702                $unitClone = $current->unit->toString();
703            }
704
705            if ( isset( $values[''] ) && $unit !== '' && $unit === $unitStatic ) {
706                $j = $values[''];
707            } elseif ( isset( $values[$unit] ) ) {
708                $j = $values[$unit];
709            } else {
710
711                if ( $unitStatic && $unit !== $unitStatic ) {
712                    throw new Less_Exception_Compiler( 'incompatible types' );
713                }
714                $values[$unit] = count( $order );
715                $order[] = $current;
716                continue;
717            }
718
719            if ( $order[$j]->unit->toString() === "" && $unitClone ) {
720                $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone );
721                $referenceUnified = $temp->unify();
722            } else {
723                $referenceUnified = $order[$j]->unify();
724            }
725            if ( ( $isMin && $currentUnified->value < $referenceUnified->value ) || ( !$isMin && $currentUnified->value > $referenceUnified->value ) ) {
726                $order[$j] = $current;
727            }
728        }
729
730        if ( count( $order ) == 1 ) {
731            return $order[0];
732        }
733        $args = [];
734        foreach ( $order as $a ) {
735            $args[] = $a->toCSS();
736        }
737        return new Less_Tree_Anonymous( ( $isMin ? 'min(' : 'max(' ) . implode( Less_Environment::$_outputMap[','], $args ) . ')' );
738    }
739
740    public function min( ...$args ) {
741        return $this->_minmax( true, $args );
742    }
743
744    public function max( ...$args ) {
745        return $this->_minmax( false, $args );
746    }
747
748    public function getunit( $n ) {
749        return new Less_Tree_Anonymous( $n->unit );
750    }
751
752    public function argb( $color ) {
753        if ( !$color instanceof Less_Tree_Color ) {
754            throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
755        }
756
757        return new Less_Tree_Anonymous( $color->toARGB() );
758    }
759
760    public function percentage( $n ) {
761        return new Less_Tree_Dimension( $n->value * 100, '%' );
762    }
763
764    public function color( $n ) {
765        if ( $n instanceof Less_Tree_Quoted ) {
766            $colorCandidate = $n->value;
767            $returnColor = Less_Tree_Color::fromKeyword( $colorCandidate );
768            if ( $returnColor ) {
769                return $returnColor;
770            }
771            if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/', $colorCandidate ) ) {
772                return new Less_Tree_Color( substr( $colorCandidate, 1 ) );
773            }
774            throw new Less_Exception_Compiler( "argument must be a color keyword or 3/6 digit hex e.g. #FFF" );
775        } else {
776            throw new Less_Exception_Compiler( "argument must be a string" );
777        }
778    }
779
780    public function iscolor( $n ) {
781        return $this->_isa( $n, 'Less_Tree_Color' );
782    }
783
784    public function isnumber( $n ) {
785        return $this->_isa( $n, 'Less_Tree_Dimension' );
786    }
787
788    public function isstring( $n ) {
789        return $this->_isa( $n, 'Less_Tree_Quoted' );
790    }
791
792    public function iskeyword( $n ) {
793        return $this->_isa( $n, 'Less_Tree_Keyword' );
794    }
795
796    public function isurl( $n ) {
797        return $this->_isa( $n, 'Less_Tree_Url' );
798    }
799
800    public function ispixel( $n ) {
801        return $this->isunit( $n, 'px' );
802    }
803
804    public function ispercentage( $n ) {
805        return $this->isunit( $n, '%' );
806    }
807
808    public function isem( $n ) {
809        return $this->isunit( $n, 'em' );
810    }
811
812    /**
813     * @param Less_Tree $n
814     * @param Less_Tree|string $unit
815     */
816    public function isunit( $n, $unit ) {
817        if ( $unit instanceof Less_Tree_HasValueProperty ) {
818            $unit = $unit->value;
819        }
820
821        return ( $n instanceof Less_Tree_Dimension ) && $n->unit->is( $unit ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
822    }
823
824    /**
825     * @param Less_Tree $n
826     * @param string $type
827     */
828    private function _isa( $n, $type ) {
829        return is_a( $n, $type ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
830    }
831
832    public function tint( $color, $amount = null ) {
833        return $this->mix( $this->rgb( 255, 255, 255 ), $color, $amount );
834    }
835
836    public function shade( $color, $amount = null ) {
837        return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
838    }
839
840    public function extract( $values, $index ) {
841        $index = (int)$index->value - 1; // (1-based index)
842        // handle non-array values as an array of length 1
843        // return 'undefined' if index is invalid
844        if ( $values instanceof Less_Tree_HasValueProperty && is_array( $values->value ) ) {
845            if ( isset( $values->value[$index] ) ) {
846                return $values->value[$index];
847            }
848            return null;
849
850        } elseif ( (int)$index === 0 ) {
851            return $values;
852        }
853
854        return null;
855    }
856
857    public function length( $values ) {
858        $n = ( $values instanceof Less_Tree_HasValueProperty && is_array( $values->value ) ) ?
859            count( $values->value ) : 1;
860        return new Less_Tree_Dimension( $n );
861    }
862
863    public function datauri( $mimetypeNode, $filePathNode = null ) {
864        $filePath = ( $filePathNode ? $filePathNode->value : null );
865        $mimetype = $mimetypeNode->value;
866
867        $args = 2;
868        if ( !$filePath ) {
869            $filePath = $mimetype;
870            $args = 1;
871        }
872
873        $filePath = str_replace( '\\', '/', $filePath );
874        if ( Less_Environment::isPathRelative( $filePath ) ) {
875            $currentFileInfo = $this->currentFileInfo;
876            '@phan-var array $currentFileInfo';
877            if ( Less_Parser::$options['relativeUrls'] ) {
878                $temp = $currentFileInfo['currentDirectory'];
879            } else {
880                $temp = $currentFileInfo['entryPath'];
881            }
882
883            if ( !empty( $temp ) ) {
884                $filePath = Less_Environment::normalizePath( rtrim( $temp, '/' ) . '/' . $filePath );
885            }
886
887        }
888
889        // detect the mimetype if not given
890        if ( $args < 2 ) {
891
892            /* incomplete
893            $mime = require('mime');
894            mimetype = mime.lookup(path);
895
896            // use base 64 unless it's an ASCII or UTF-8 format
897            var charset = mime.charsets.lookup(mimetype);
898            useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
899            if (useBase64) mimetype += ';base64';
900            */
901
902            $mimetype = Less_Mime::lookup( $filePath );
903
904            $charset = Less_Mime::charsets_lookup( $mimetype );
905            $useBase64 = !in_array( $charset, [ 'US-ASCII', 'UTF-8' ] );
906            if ( $useBase64 ) {
907                $mimetype .= ';base64';
908            }
909
910        } else {
911            $useBase64 = preg_match( '/;base64$/', $mimetype );
912        }
913
914        if ( file_exists( $filePath ) ) {
915            $buf = @file_get_contents( $filePath );
916        } else {
917            $buf = false;
918        }
919
920        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
921        // and the --ieCompat flag is enabled, return a normal url() instead.
922        $DATA_URI_MAX_KB = 32;
923        $fileSizeInKB = round( strlen( $buf ) / 1024 );
924        if ( $fileSizeInKB >= $DATA_URI_MAX_KB ) {
925            $url = new Less_Tree_Url( ( $filePathNode ?: $mimetypeNode ), $this->currentFileInfo );
926            return $url->compile( $this->env );
927        }
928
929        if ( $buf ) {
930            $buf = $useBase64 ? base64_encode( $buf ) : rawurlencode( $buf );
931            $filePath = '"data:' . $mimetype . ',' . $buf . '"';
932        }
933
934        return new Less_Tree_Url( new Less_Tree_Anonymous( $filePath ) );
935    }
936
937    // svg-gradient
938    public function svggradient( $direction, ...$stops ) {
939        $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
940
941        if ( count( $stops ) < 2 ) {
942            throw new Less_Exception_Compiler( $throw_message );
943        }
944
945        $gradientType = 'linear';
946        $rectangleDimension = 'x="0" y="0" width="1" height="1"';
947        $useBase64 = true;
948        $directionValue = $direction->toCSS();
949
950        switch ( $directionValue ) {
951            case "to bottom":
952                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
953                break;
954            case "to right":
955                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
956                break;
957            case "to bottom right":
958                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
959                break;
960            case "to top right":
961                $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
962                break;
963            case "ellipse":
964            case "ellipse at center":
965                $gradientType = "radial";
966                $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
967                $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
968                break;
969            default:
970                throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
971        }
972
973        $returner = '<?xml version="1.0" ?>' .
974            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
975            '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
976
977        for ( $i = 0; $i < count( $stops ); $i++ ) {
978            if ( $stops[$i] instanceof Less_Tree_HasValueProperty ) {
979                $color = $stops[$i]->value[0];
980                $position = $stops[$i]->value[1];
981            } else {
982                $color = $stops[$i];
983                $position = null;
984            }
985
986            if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
987                throw new Less_Exception_Compiler( $throw_message );
988            }
989            if ( $position ) {
990                $positionValue = $position->toCSS();
991            } elseif ( $i === 0 ) {
992                $positionValue = '0%';
993            } else {
994                $positionValue = '100%';
995            }
996            $alpha = $color->alpha;
997            $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
998        }
999
1000        $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
1001
1002        if ( $useBase64 ) {
1003            $returner = "'data:image/svg+xml;base64," . base64_encode( $returner ) . "'";
1004        } else {
1005            $returner = "'data:image/svg+xml," . $returner . "'";
1006        }
1007
1008        return new Less_Tree_Url( new Less_Tree_Anonymous( $returner ) );
1009    }
1010
1011    /**
1012     * Php version of javascript's `encodeURIComponent` function
1013     *
1014     * @param string $string The string to encode
1015     * @return string The encoded string
1016     */
1017    public static function encodeURIComponent( $string ) {
1018        $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ];
1019        return strtr( rawurlencode( $string ), $revert );
1020    }
1021
1022    // Color Blending
1023    // ref: https://www.w3.org/TR/compositing-1/
1024    public function colorBlend( $mode, $color1, $color2 ) {
1025        // backdrop
1026        $ab = $color1->alpha;
1027        // source
1028        $as = $color2->alpha;
1029        $result = [];
1030
1031        $ar = $as + $ab * ( 1 - $as );
1032        for ( $i = 0; $i < 3; $i++ ) {
1033            $cb = $color1->rgb[$i] / 255;
1034            $cs = $color2->rgb[$i] / 255;
1035            $cr = $mode( $cb, $cs );
1036            if ( $ar ) {
1037                $cr = ( $as * $cs + $ab * ( $cb - $as * ( $cb + $cs - $cr ) ) ) / $ar;
1038            }
1039            $result[$i] = $cr * 255;
1040        }
1041
1042        return new Less_Tree_Color( $result, $ar );
1043    }
1044
1045    public function multiply( $color1 = null, $color2 = null ) {
1046        if ( !$color1 instanceof Less_Tree_Color ) {
1047            throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1048        }
1049        if ( !$color2 instanceof Less_Tree_Color ) {
1050            throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1051        }
1052
1053        return $this->colorBlend( [ $this,'colorBlendMultiply' ],  $color1, $color2 );
1054    }
1055
1056    private function colorBlendMultiply( $cb, $cs ) {
1057        return $cb * $cs;
1058    }
1059
1060    public function screen( $color1 = null, $color2 = null ) {
1061        if ( !$color1 instanceof Less_Tree_Color ) {
1062            throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1063        }
1064        if ( !$color2 instanceof Less_Tree_Color ) {
1065            throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1066        }
1067
1068        return $this->colorBlend( [ $this,'colorBlendScreen' ],  $color1, $color2 );
1069    }
1070
1071    private function colorBlendScreen( $cb, $cs ) {
1072        return $cb + $cs - $cb * $cs;
1073    }
1074
1075    public function overlay( $color1 = null, $color2 = null ) {
1076        if ( !$color1 instanceof Less_Tree_Color ) {
1077            throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1078        }
1079        if ( !$color2 instanceof Less_Tree_Color ) {
1080            throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1081        }
1082
1083        return $this->colorBlend( [ $this,'colorBlendOverlay' ],  $color1, $color2 );
1084    }
1085
1086    private function colorBlendOverlay( $cb, $cs ) {
1087        $cb *= 2;
1088        return ( $cb <= 1 )
1089            ? $this->colorBlendMultiply( $cb, $cs )
1090            : $this->colorBlendScreen( $cb - 1, $cs );
1091    }
1092
1093    public function softlight( $color1 = null, $color2 = null ) {
1094        if ( !$color1 instanceof Less_Tree_Color ) {
1095            throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1096        }
1097        if ( !$color2 instanceof Less_Tree_Color ) {
1098            throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1099        }
1100
1101        return $this->colorBlend( [ $this,'colorBlendSoftlight' ],  $color1, $color2 );
1102    }
1103
1104    private function colorBlendSoftlight( $cb, $cs ) {
1105        $d = 1;
1106        $e = $cb;
1107        if ( $cs > 0.5 ) {
1108            $e = 1;
1109            $d = ( $cb > 0.25 ) ? sqrt( $cb )
1110                : ( ( 16 * $cb - 12 ) * $cb + 4 ) * $cb;
1111        }
1112        return $cb - ( 1 - 2 * $cs ) * $e * ( $d - $cb );
1113    }
1114
1115    public function hardlight( $color1 = null, $color2 = null ) {
1116        if ( !$color1 instanceof Less_Tree_Color ) {
1117            throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1118        }
1119        if ( !$color2 instanceof Less_Tree_Color ) {
1120            throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1121        }
1122
1123        return $this->colorBlend( [ $this,'colorBlendHardlight' ],  $color1, $color2 );
1124    }
1125
1126    private function colorBlendHardlight( $cb, $cs ) {
1127        return $this->colorBlendOverlay( $cs, $cb );
1128    }
1129
1130    public function difference( $color1 = null, $color2 = null ) {
1131        if ( !$color1 instanceof Less_Tree_Color ) {
1132            throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1133        }
1134        if ( !$color2 instanceof Less_Tree_Color ) {
1135            throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1136        }
1137
1138        return $this->colorBlend( [ $this,'colorBlendDifference' ],  $color1, $color2 );
1139    }
1140
1141    private function colorBlendDifference( $cb, $cs ) {
1142        return abs( $cb - $cs );
1143    }
1144
1145    public function exclusion( $color1 = null, $color2 = null ) {
1146        if ( !$color1 instanceof Less_Tree_Color ) {
1147            throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1148        }
1149        if ( !$color2 instanceof Less_Tree_Color ) {
1150            throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1151        }
1152
1153        return $this->colorBlend( [ $this,'colorBlendExclusion' ],  $color1, $color2 );
1154    }
1155
1156    private function colorBlendExclusion( $cb, $cs ) {
1157        return $cb + $cs - 2 * $cb * $cs;
1158    }
1159
1160    public function average( $color1 = null, $color2 = null ) {
1161        if ( !$color1 instanceof Less_Tree_Color ) {
1162            throw new Less_Exception_Compiler( 'The first argument to average 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 average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1166        }
1167
1168        return $this->colorBlend( [ $this,'colorBlendAverage' ],  $color1, $color2 );
1169    }
1170
1171    // non-w3c functions:
1172    public function colorBlendAverage( $cb, $cs ) {
1173        return ( $cb + $cs ) / 2;
1174    }
1175
1176    public function negation( $color1 = null, $color2 = null ) {
1177        if ( !$color1 instanceof Less_Tree_Color ) {
1178            throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1179        }
1180        if ( !$color2 instanceof Less_Tree_Color ) {
1181            throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
1182        }
1183
1184        return $this->colorBlend( [ $this,'colorBlendNegation' ],  $color1, $color2 );
1185    }
1186
1187    public function colorBlendNegation( $cb, $cs ) {
1188        return 1 - abs( $cb + $cs - 1 );
1189    }
1190
1191    // ~ End of Color Blending
1192
1193}