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