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