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