Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.60% |
479 / 573 |
|
58.65% |
61 / 104 |
CRAP | |
0.00% |
0 / 1 |
Less_Functions | |
83.60% |
479 / 573 |
|
58.65% |
61 / 104 |
932.17 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
_clamp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_number | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
_scaled | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
rgb | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
4 | |||
rgba | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
hsl | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hsla | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
hsla_hue | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
6 | |||
hsv | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hsva | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
1 | |||
hue | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
saturation | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
lightness | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
hsvhue | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
hsvsaturation | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
hsvvalue | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
red | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
green | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
blue | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
alpha | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
luma | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
luminance | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
saturate | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
8.30 | |||
desaturate | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
lighten | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
darken | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
fadein | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
fadeout | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
fade | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
5.39 | |||
spin | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
6.56 | |||
mix | |
84.00% |
21 / 25 |
|
0.00% |
0 / 1 |
12.59 | |||
greyscale | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
contrast | |
90.00% |
18 / 20 |
|
0.00% |
0 / 1 |
11.12 | |||
e | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
escape | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
replace | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
replace_flags | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_percent | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
7 | |||
unit | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
5.05 | |||
convert | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
round | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
pi | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
mod | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
pow | |
50.00% |
3 / 6 |
|
0.00% |
0 / 1 |
8.12 | |||
ceil | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
floor | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
sqrt | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
abs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
tan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
sin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
cos | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
atan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
asin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
acos | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_math | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
4.59 | |||
_minMax | |
80.43% |
37 / 46 |
|
0.00% |
0 / 1 |
39.67 | |||
min | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
max | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getunit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
argb | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
percentage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
color | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
5.07 | |||
isruleset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
iscolor | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
isnumber | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
isstring | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
iskeyword | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
isurl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
ispixel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
ispercentage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isem | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isunit | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
5 | |||
tint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
shade | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_self | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
extract | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
length | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
datauri | |
93.75% |
30 / 32 |
|
0.00% |
0 / 1 |
12.04 | |||
svggradient | |
62.50% |
30 / 48 |
|
0.00% |
0 / 1 |
38.04 | |||
getImageSize | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
getSvgSize | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
imagesize | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
imagewidth | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
imageheight | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
colorBlend | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
multiply | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendMultiply | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
screen | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendScreen | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
overlay | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendOverlay | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
softlight | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendSoftlight | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
hardlight | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendHardlight | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
difference | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendDifference | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
exclusion | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendExclusion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
average | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendAverage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
negation | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
6.60 | |||
colorBlendNegation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Builtin functions |
5 | * @see https://lesscss.org/functions/ |
6 | */ |
7 | class 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 |