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