1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace LesserPhp\Library; |
4
|
|
|
|
5
|
|
|
use LesserPhp\Color\Converter; |
6
|
|
|
use LesserPhp\Compiler; |
7
|
|
|
use LesserPhp\Exception\GeneralException; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* lesserphp |
11
|
|
|
* https://www.maswaba.de/lesserphp |
12
|
|
|
* |
13
|
|
|
* LESS CSS compiler, adapted from http://lesscss.org |
14
|
|
|
* |
15
|
|
|
* Copyright 2013, Leaf Corcoran <[email protected]> |
16
|
|
|
* Copyright 2016, Marcus Schwarz <[email protected]> |
17
|
|
|
* Licensed under MIT or GPLv3, see LICENSE |
18
|
|
|
* @package LesserPhp |
19
|
|
|
*/ |
20
|
|
|
class Functions |
21
|
|
|
{ |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var \LesserPhp\Library\Assertions |
25
|
|
|
*/ |
26
|
|
|
private $assertions; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var \LesserPhp\Library\Coerce |
30
|
|
|
*/ |
31
|
|
|
private $coerce; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var \LesserPhp\Compiler |
35
|
|
|
*/ |
36
|
|
|
private $compiler; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var \LesserPhp\Color\Converter |
40
|
|
|
*/ |
41
|
|
|
private $converter; |
42
|
|
|
|
43
|
|
|
public static $TRUE = ["keyword", "true"]; |
44
|
|
|
public static $FALSE = ["keyword", "false"]; |
45
|
|
|
public static $lengths = ["px", "m", "cm", "mm", "in", "pt", "pc"]; |
46
|
|
|
public static $times = ["s", "ms"]; |
47
|
|
|
public static $angles = ["rad", "deg", "grad", "turn"]; |
48
|
|
|
public static $lengths_to_base = [1, 3779.52755906, 37.79527559, 3.77952756, 96, 1.33333333, 16]; |
49
|
|
|
|
50
|
64 |
|
|
51
|
|
|
/** |
52
|
64 |
|
* Functions constructor. |
53
|
64 |
|
* |
54
|
64 |
|
* @param \LesserPhp\Library\Assertions $assertions |
55
|
64 |
|
* @param \LesserPhp\Library\Coerce $coerce |
56
|
64 |
|
* @param \LesserPhp\Compiler $compiler |
57
|
|
|
* @param \LesserPhp\Color\Converter $converter |
58
|
1 |
|
*/ |
59
|
|
|
public function __construct(Assertions $assertions, Coerce $coerce, Compiler $compiler, Converter $converter) |
60
|
1 |
|
{ |
61
|
|
|
$this->assertions = $assertions; |
62
|
|
|
$this->coerce = $coerce; |
63
|
1 |
|
$this->compiler = $compiler; // temporary solution to get it working |
64
|
1 |
|
$this->converter = $converter; |
65
|
1 |
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @param array $args |
69
|
1 |
|
* |
70
|
|
|
* @return array |
71
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
72
|
|
|
*/ |
73
|
|
|
public function pow(array $args) |
74
|
1 |
|
{ |
75
|
|
|
list($base, $exp) = $this->assertions->assertArgs($args, 2, 'pow'); |
76
|
1 |
|
|
77
|
|
|
return [ |
78
|
1 |
|
'number', |
79
|
|
|
pow($this->assertions->assertNumber($base), $this->assertions->assertNumber($exp)), |
80
|
|
|
$args[2][0][2], |
81
|
1 |
|
]; |
82
|
|
|
} |
83
|
1 |
|
|
84
|
1 |
|
/** |
85
|
|
|
* @return float |
86
|
|
|
*/ |
87
|
|
|
public function pi() |
88
|
1 |
|
{ |
89
|
|
|
return M_PI; |
90
|
|
|
} |
91
|
1 |
|
|
92
|
|
|
/** |
93
|
1 |
|
* @param array $args |
94
|
1 |
|
* |
95
|
|
|
* @return array |
96
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
97
|
|
|
*/ |
98
|
1 |
|
public function mod(array $args) |
99
|
|
|
{ |
100
|
|
|
list($a, $b) = $this->assertions->assertArgs($args, 2, 'mod'); |
101
|
1 |
|
|
102
|
|
|
return ['number', $this->assertions->assertNumber($a) % $this->assertions->assertNumber($b), $args[2][0][2]]; |
103
|
1 |
|
} |
104
|
1 |
|
|
105
|
|
|
/** |
106
|
|
|
* @param array $color |
107
|
|
|
* |
108
|
1 |
|
* @return int |
109
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
110
|
|
|
*/ |
111
|
2 |
|
public function red(array $color) |
112
|
|
|
{ |
113
|
2 |
|
$color = $this->coerce->coerceColor($color); |
114
|
|
|
if ($color === null) { |
115
|
|
|
throw new GeneralException('color expected for red()'); |
116
|
2 |
|
} |
117
|
2 |
|
|
118
|
|
|
return $color[1]; |
119
|
|
|
} |
120
|
2 |
|
|
121
|
|
|
/** |
122
|
|
|
* @param array $color |
123
|
1 |
|
* |
124
|
|
|
* @return int |
125
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
126
|
|
|
*/ |
127
|
|
|
public function green(array $color) |
128
|
2 |
|
{ |
129
|
|
|
$color = $this->coerce->coerceColor($color); |
130
|
2 |
|
if ($color === null) { |
131
|
|
|
throw new GeneralException('color expected for green()'); |
132
|
1 |
|
} |
133
|
|
|
|
134
|
1 |
|
return $color[2]; |
135
|
1 |
|
} |
136
|
|
|
|
137
|
1 |
|
/** |
138
|
1 |
|
* @param array $color |
139
|
|
|
* |
140
|
1 |
|
* @return int |
141
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
142
|
1 |
|
*/ |
143
|
|
|
public function blue(array $color) |
144
|
|
|
{ |
145
|
|
|
$color = $this->coerce->coerceColor($color); |
146
|
1 |
|
if ($color === null) { |
147
|
|
|
throw new GeneralException('color expected for blue()'); |
148
|
|
|
} |
149
|
3 |
|
|
150
|
|
|
return $color[3]; |
151
|
3 |
|
} |
152
|
|
|
|
153
|
2 |
|
/** |
154
|
|
|
* @param array $args |
155
|
2 |
|
* |
156
|
2 |
|
* @return array |
157
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
158
|
2 |
|
*/ |
159
|
2 |
|
public function convert(array $args) |
160
|
|
|
{ |
161
|
2 |
|
list($value, $to) = $this->assertions->assertArgs($args, 2, 'convert'); |
162
|
1 |
|
|
163
|
1 |
|
// If it's a keyword, grab the string version instead |
164
|
|
|
if (is_array($to) && $to[0] === 'keyword') { |
165
|
|
|
$to = $to[1]; |
166
|
|
|
} |
167
|
1 |
|
|
168
|
|
|
return $this->convertMe($value, $to); |
169
|
|
|
} |
170
|
1 |
|
|
171
|
|
|
/** |
172
|
1 |
|
* @param array $num |
173
|
|
|
* |
174
|
|
|
* @return array |
175
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
176
|
|
|
*/ |
177
|
1 |
|
public function abs(array $num) |
178
|
|
|
{ |
179
|
|
|
return ['number', abs($this->assertions->assertNumber($num)), $num[2]]; |
180
|
1 |
|
} |
181
|
|
|
|
182
|
1 |
|
/** |
183
|
|
|
* @param array $args |
184
|
|
|
* |
185
|
1 |
|
* @return int |
186
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
187
|
1 |
|
*/ |
188
|
|
|
public function min(array $args) |
189
|
1 |
|
{ |
190
|
|
|
$values = $this->assertions->assertMinArgs($args, 1, 'min'); |
191
|
|
|
|
192
|
1 |
|
$firstFormat = $values[0][2]; |
193
|
|
|
|
194
|
1 |
|
$minIndex = 0; |
195
|
|
|
$minValue = $values[0][1]; |
196
|
1 |
|
|
197
|
|
|
foreach ($values as $a => $value) { |
198
|
|
|
$converted = $this->convertMe($value, $firstFormat); |
199
|
1 |
|
|
200
|
|
|
if ($converted[1] < $minValue) { |
201
|
1 |
|
$minIndex = $a; |
202
|
|
|
$minValue = $value[1]; |
203
|
1 |
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
1 |
|
return $values[$minIndex]; |
207
|
|
|
} |
208
|
1 |
|
|
209
|
|
|
/** |
210
|
|
|
* @param array $args |
211
|
1 |
|
* |
212
|
|
|
* @return int |
213
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
214
|
1 |
|
*/ |
215
|
|
|
public function max(array $args) |
216
|
1 |
|
{ |
217
|
1 |
|
$values = $this->assertions->assertMinArgs($args, 1, 'max'); |
218
|
|
|
|
219
|
|
|
$firstFormat = $values[0][2]; |
220
|
1 |
|
|
221
|
|
|
$maxIndex = 0; |
222
|
|
|
$maxValue = $values[0][1]; |
223
|
2 |
|
|
224
|
|
|
foreach ($values as $a => $value) { |
225
|
2 |
|
$converted = $this->convertMe($value, $firstFormat); |
226
|
|
|
|
227
|
|
|
if ($converted[1] > $maxValue) { |
228
|
1 |
|
$maxIndex = $a; |
229
|
|
|
$maxValue = $value[1]; |
230
|
1 |
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
3 |
|
return $values[$maxIndex]; |
234
|
|
|
} |
235
|
3 |
|
|
236
|
|
|
/** |
237
|
|
|
* @param $num |
238
|
1 |
|
* |
239
|
|
|
* @return float |
240
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
241
|
|
|
*/ |
242
|
|
|
public function tan($num) |
243
|
2 |
|
{ |
244
|
|
|
return tan($this->assertions->assertNumber($num)); |
245
|
2 |
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
1 |
|
* @param $num |
249
|
|
|
* |
250
|
1 |
|
* @return float |
251
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
252
|
|
|
*/ |
253
|
1 |
|
public function sin($num) |
254
|
|
|
{ |
255
|
1 |
|
return sin($this->assertions->assertNumber($num)); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* @param $num |
260
|
|
|
* |
261
|
|
|
* @return float |
262
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
263
|
1 |
|
*/ |
264
|
|
|
public function cos($num) |
265
|
1 |
|
{ |
266
|
1 |
|
return cos($this->assertions->assertNumber($num)); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
1 |
|
* @param $num |
271
|
1 |
|
* |
272
|
1 |
|
* @return array |
273
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
274
|
1 |
|
*/ |
275
|
1 |
|
public function atan($num) |
276
|
|
|
{ |
277
|
|
|
$num = atan($this->assertions->assertNumber($num)); |
278
|
|
|
|
279
|
1 |
|
return ['number', $num, 'rad']; |
280
|
|
|
} |
281
|
1 |
|
|
282
|
|
|
/** |
283
|
|
|
* @param $num |
284
|
|
|
* |
285
|
|
|
* @return array |
286
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
287
|
|
|
*/ |
288
|
|
|
public function asin($num) |
289
|
|
|
{ |
290
|
|
|
$num = asin($this->assertions->assertNumber($num)); |
291
|
1 |
|
|
292
|
|
|
return ['number', $num, 'rad']; |
293
|
1 |
|
} |
294
|
1 |
|
|
295
|
|
|
/** |
296
|
1 |
|
* @param $num |
297
|
|
|
* |
298
|
1 |
|
* @return array |
299
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
300
|
1 |
|
*/ |
301
|
1 |
|
public function acos($num) |
302
|
1 |
|
{ |
303
|
1 |
|
$num = acos($this->assertions->assertNumber($num)); |
304
|
1 |
|
|
305
|
|
|
return ['number', $num, 'rad']; |
306
|
|
|
} |
307
|
|
|
|
308
|
1 |
|
/** |
309
|
|
|
* @param $num |
310
|
1 |
|
* |
311
|
|
|
* @return float |
312
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
313
|
|
|
*/ |
314
|
|
|
public function sqrt($num) |
315
|
1 |
|
{ |
316
|
|
|
return sqrt($this->assertions->assertNumber($num)); |
317
|
|
|
} |
318
|
|
|
|
319
|
13 |
|
/** |
320
|
|
|
* @param $value |
321
|
13 |
|
* |
322
|
13 |
|
* @return mixed |
323
|
2 |
|
* @throws \LesserPhp\Exception\GeneralException |
324
|
2 |
|
*/ |
325
|
1 |
|
public function extract($value) |
326
|
|
|
{ |
327
|
1 |
|
list($list, $idx) = $this->assertions->assertArgs($value, 2, 'extract'); |
328
|
12 |
|
$idx = $this->assertions->assertNumber($idx); |
329
|
12 |
|
// 1 indexed |
330
|
|
|
if ($list[0] === 'list' && isset($list[2][$idx - 1])) { |
331
|
12 |
|
return $list[2][$idx - 1]; |
332
|
6 |
|
} |
333
|
3 |
|
|
334
|
|
|
return null; |
335
|
4 |
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* @param $value |
339
|
1 |
|
* |
340
|
|
|
* @return array |
341
|
1 |
|
*/ |
342
|
|
|
public function isnumber($value) |
343
|
|
|
{ |
344
|
1 |
|
return $this->toBool($value[0] === 'number'); |
345
|
1 |
|
} |
346
|
1 |
|
|
347
|
|
|
/** |
348
|
1 |
|
* @param $value |
349
|
1 |
|
* |
350
|
1 |
|
* @return array |
351
|
1 |
|
*/ |
352
|
1 |
|
public function isstring($value) |
353
|
|
|
{ |
354
|
|
|
return $this->toBool($value[0] === 'string'); |
355
|
1 |
|
} |
356
|
1 |
|
|
357
|
|
|
/** |
358
|
|
|
* @param $value |
359
|
|
|
* |
360
|
1 |
|
* @return array |
361
|
1 |
|
*/ |
362
|
1 |
|
public function iscolor($value) |
363
|
1 |
|
{ |
364
|
|
|
$yesno = $this->coerce->coerceColor($value) !== null; |
365
|
|
|
return $this->toBool($yesno); |
366
|
1 |
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* @param $value |
370
|
|
|
* |
371
|
1 |
|
* @return array |
372
|
|
|
*/ |
373
|
1 |
|
public function iskeyword($value) |
374
|
|
|
{ |
375
|
|
|
return $this->toBool($value[0] === 'keyword'); |
376
|
1 |
|
} |
377
|
|
|
|
378
|
1 |
|
/** |
379
|
|
|
* @param $value |
380
|
1 |
|
* |
381
|
|
|
* @return array |
382
|
|
|
*/ |
383
|
1 |
|
public function ispixel($value) |
384
|
|
|
{ |
385
|
1 |
|
return $this->toBool($value[0] === 'number' && $value[2] === 'px'); |
386
|
|
|
} |
387
|
1 |
|
|
388
|
|
|
/** |
389
|
|
|
* @param $value |
390
|
1 |
|
* |
391
|
|
|
* @return array |
392
|
1 |
|
*/ |
393
|
1 |
|
public function ispercentage($value) |
394
|
|
|
{ |
395
|
1 |
|
return $this->toBool($value[0] === 'number' && $value[2] === '%'); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* @param $value |
400
|
|
|
* |
401
|
|
|
* @return array |
402
|
|
|
*/ |
403
|
|
|
public function isem($value) |
404
|
1 |
|
{ |
405
|
|
|
return $this->toBool($value[0] === 'number' && $value[2] === 'em'); |
406
|
1 |
|
} |
407
|
1 |
|
|
408
|
|
|
/** |
409
|
|
|
* @param $value |
410
|
1 |
|
* |
411
|
1 |
|
* @return array |
412
|
1 |
|
*/ |
413
|
|
|
public function isrem($value) |
414
|
|
|
{ |
415
|
1 |
|
return $this->toBool($value[0] === 'number' && $value[2] === 'rem'); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* @param $color |
420
|
1 |
|
* |
421
|
|
|
* @return string |
422
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
423
|
|
|
*/ |
424
|
1 |
|
public function rgbahex($color) |
425
|
1 |
|
{ |
426
|
|
|
$color = $this->coerce->coerceColor($color); |
427
|
1 |
|
if ($color === null) { |
428
|
|
|
throw new GeneralException('color expected for rgbahex'); |
429
|
|
|
} |
430
|
2 |
|
|
431
|
|
|
return sprintf( |
432
|
2 |
|
'#%02x%02x%02x%02x', |
433
|
|
|
isset($color[4]) ? $color[4] * 255 : 255, |
434
|
2 |
|
$color[1], |
435
|
2 |
|
$color[2], |
436
|
|
|
$color[3] |
437
|
2 |
|
); |
438
|
|
|
} |
439
|
|
|
|
440
|
1 |
|
/** |
441
|
|
|
* @param $color |
442
|
1 |
|
* |
443
|
|
|
* @return string |
444
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
445
|
1 |
|
*/ |
446
|
|
|
public function argb($color) |
447
|
1 |
|
{ |
448
|
|
|
return $this->rgbahex($color); |
449
|
|
|
} |
450
|
1 |
|
|
451
|
|
|
/** |
452
|
1 |
|
* Given an url, decide whether to output a regular link or the base64-encoded contents of the file |
453
|
|
|
* |
454
|
1 |
|
* @param array $value either an argument list (two strings) or a single string |
455
|
1 |
|
* |
456
|
|
|
* @return string formatted url(), either as a link or base64-encoded |
457
|
1 |
|
*/ |
458
|
|
|
public function data_uri($value) |
459
|
|
|
{ |
460
|
1 |
|
$mime = ($value[0] === 'list') ? $value[2][0][2] : null; |
461
|
|
|
$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0]; |
462
|
1 |
|
|
463
|
|
|
$fullpath = $this->findImport($url); |
464
|
1 |
|
|
465
|
|
|
if ($fullpath && ($fsize = filesize($fullpath)) !== false) { |
|
|
|
|
466
|
1 |
|
// IE8 can't handle data uris larger than 32KB |
467
|
1 |
|
if ($fsize / 1024 < 32) { |
468
|
1 |
|
if ($mime === null) { |
469
|
|
|
$finfo = new \finfo(FILEINFO_MIME); |
470
|
|
|
$mime = explode('; ', $finfo->file($fullpath)); |
471
|
1 |
|
$mime = $mime[0]; |
472
|
|
|
} |
473
|
|
|
|
474
|
1 |
|
//todo find out why this suddenly breakes data-uri-test |
475
|
|
|
if ($mime !== null && $mime !== 'text/x-php') { |
476
|
1 |
|
// fallback if the mime type is still unknown |
477
|
1 |
|
$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath))); |
478
|
|
|
} |
479
|
1 |
|
} |
480
|
|
|
} |
481
|
|
|
|
482
|
1 |
|
return 'url("' . $url . '")'; |
483
|
|
|
} |
484
|
1 |
|
|
485
|
1 |
|
// utility func to unquote a string |
486
|
|
|
|
487
|
1 |
|
/** |
488
|
|
|
* @param array $arg |
489
|
|
|
* |
490
|
1 |
|
* @return array |
491
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
492
|
1 |
|
*/ |
493
|
|
|
public function e(array $arg) |
494
|
1 |
|
{ |
495
|
|
|
switch ($arg[0]) { |
496
|
|
|
case 'list': |
497
|
1 |
|
$items = $arg[2]; |
498
|
|
|
if (isset($items[0])) { |
499
|
1 |
|
return $this->e($items[0]); |
500
|
|
|
} |
501
|
1 |
|
throw new GeneralException('unrecognised input'); |
502
|
|
|
case 'string': |
503
|
|
|
$arg[1] = ''; |
504
|
|
|
|
505
|
|
|
return $arg; |
506
|
|
|
case 'keyword': |
507
|
|
|
return $arg; |
508
|
|
|
default: |
509
|
1 |
|
return ['keyword', $this->compiler->compileValue($arg)]; |
510
|
|
|
} |
511
|
1 |
|
} |
512
|
|
|
|
513
|
1 |
|
/** |
514
|
|
|
* @param array $args |
515
|
|
|
* |
516
|
|
|
* @return array |
517
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
518
|
|
|
*/ |
519
|
|
|
public function _sprintf(array $args) |
520
|
|
|
{ |
521
|
|
|
if ($args[0] !== 'list') { |
522
|
|
|
return $args; |
523
|
|
|
} |
524
|
2 |
|
$values = $args[2]; |
525
|
|
|
$string = array_shift($values); |
526
|
2 |
|
$template = $this->compiler->compileValue($this->e($string)); |
527
|
2 |
|
|
528
|
1 |
|
$i = 0; |
529
|
|
|
if (preg_match_all('/%[dsa]/', $template, $m)) { |
530
|
|
|
foreach ($m[0] as $match) { |
531
|
1 |
|
$val = isset($values[$i]) ? |
532
|
|
|
$this->compiler->reduce($values[$i]) : ['keyword', '']; |
533
|
|
|
|
534
|
|
|
// lessjs compat, renders fully expanded color, not raw color |
535
|
1 |
|
$color = $this->coerce->coerceColor($val); |
536
|
|
|
if ($color !== null) { |
537
|
1 |
|
$val = $color; |
538
|
1 |
|
} |
539
|
|
|
|
540
|
1 |
|
$i++; |
541
|
|
|
$rep = $this->compiler->compileValue($this->e($val)); |
542
|
|
|
$template = preg_replace( |
543
|
1 |
|
'/' . Compiler::pregQuote($match) . '/', |
544
|
|
|
$rep, |
545
|
1 |
|
$template, |
546
|
|
|
1 |
547
|
1 |
|
); |
548
|
|
|
} |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
$d = $string[0] === 'string' ? $string[1] : '"'; |
552
|
|
|
|
553
|
1 |
|
return ['string', $d, [$template]]; |
554
|
|
|
} |
555
|
1 |
|
|
556
|
|
|
/** |
557
|
|
|
* @param array $arg |
558
|
|
|
* |
559
|
1 |
|
* @return array |
560
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
561
|
1 |
|
*/ |
562
|
|
|
public function floor(array $arg) |
563
|
1 |
|
{ |
564
|
1 |
|
$value = $this->assertions->assertNumber($arg); |
565
|
|
|
|
566
|
1 |
|
return ['number', floor($value), $arg[2]]; |
567
|
1 |
|
} |
568
|
|
|
|
569
|
1 |
|
/** |
570
|
|
|
* @param array $arg |
571
|
|
|
* |
572
|
1 |
|
* @return array |
573
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
574
|
|
|
*/ |
575
|
1 |
|
public function ceil(array $arg) |
576
|
1 |
|
{ |
577
|
|
|
$value = $this->assertions->assertNumber($arg); |
578
|
|
|
|
579
|
1 |
|
return ['number', ceil($value), $arg[2]]; |
580
|
1 |
|
} |
581
|
1 |
|
|
582
|
1 |
|
/** |
583
|
|
|
* @param array $arg |
584
|
|
|
* |
585
|
1 |
|
* @return array |
586
|
1 |
|
* @throws \LesserPhp\Exception\GeneralException |
587
|
|
|
*/ |
588
|
|
|
public function round(array $arg) |
589
|
1 |
|
{ |
590
|
|
|
if ($arg[0] !== 'list') { |
591
|
|
|
$value = $this->assertions->assertNumber($arg); |
592
|
1 |
|
|
593
|
|
|
return ['number', round($value), $arg[2]]; |
594
|
1 |
|
} else { |
595
|
1 |
|
$value = $this->assertions->assertNumber($arg[2][0]); |
596
|
1 |
|
$precision = $this->assertions->assertNumber($arg[2][1]); |
597
|
|
|
|
598
|
1 |
|
return ['number', round($value, $precision), $arg[2][0][2]]; |
599
|
1 |
|
} |
600
|
1 |
|
} |
601
|
1 |
|
|
602
|
1 |
|
/** |
603
|
1 |
|
* @param array $arg |
604
|
1 |
|
* |
605
|
1 |
|
* @return array |
606
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
607
|
1 |
|
*/ |
608
|
|
|
public function unit(array $arg) |
609
|
|
|
{ |
610
|
|
|
if ($arg[0] === 'list') { |
611
|
|
|
list($number, $newUnit) = $arg[2]; |
612
|
|
|
|
613
|
1 |
|
return [ |
614
|
1 |
|
'number', |
615
|
1 |
|
$this->assertions->assertNumber($number), |
616
|
|
|
$this->compiler->compileValue($this->e($newUnit)), |
617
|
|
|
]; |
618
|
1 |
|
} else { |
619
|
1 |
|
return ['number', $this->assertions->assertNumber($arg), '']; |
620
|
1 |
|
} |
621
|
1 |
|
} |
622
|
|
|
|
623
|
|
|
|
624
|
1 |
|
/** |
625
|
1 |
|
* @param array $args |
626
|
1 |
|
* |
627
|
|
|
* @return array |
628
|
|
|
*/ |
629
|
1 |
|
public function darken(array $args) |
630
|
|
|
{ |
631
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
632
|
1 |
|
|
633
|
|
|
$hsl = $this->converter->toHSL($color); |
634
|
1 |
|
$hsl[3] = $this->converter->clamp($hsl[3] - $delta, 100); |
635
|
1 |
|
|
636
|
|
|
return $this->converter->toRGB($hsl); |
637
|
|
|
} |
638
|
|
|
|
639
|
3 |
|
/** |
640
|
|
|
* @param array $args |
641
|
3 |
|
* |
642
|
3 |
|
* @return array |
643
|
|
|
*/ |
644
|
|
|
public function lighten(array $args) |
645
|
3 |
|
{ |
646
|
2 |
|
list($color, $delta) = $this->compiler->colorArgs($args); |
647
|
|
|
|
648
|
|
|
$hsl = $this->converter->toHSL($color); |
649
|
|
|
$hsl[3] = $this->converter->clamp($hsl[3] + $delta, 100); |
650
|
3 |
|
|
651
|
|
|
return $this->converter->toRGB($hsl); |
652
|
2 |
|
} |
653
|
|
|
|
654
|
1 |
|
/** |
655
|
1 |
|
* @param array $args |
656
|
1 |
|
* |
657
|
|
|
* @return array |
658
|
1 |
|
*/ |
659
|
|
|
public function saturate(array $args) |
660
|
1 |
|
{ |
661
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
662
|
|
|
|
663
|
|
|
$hsl = $this->converter->toHSL($color); |
664
|
|
|
$hsl[2] = $this->converter->clamp($hsl[2] + $delta, 100); |
665
|
3 |
|
|
666
|
|
|
return $this->converter->toRGB($hsl); |
667
|
1 |
|
} |
668
|
1 |
|
|
669
|
|
|
/** |
670
|
1 |
|
* @param array $args |
671
|
|
|
* |
672
|
|
|
* @return array |
673
|
1 |
|
*/ |
674
|
|
|
public function desaturate(array $args) |
675
|
1 |
|
{ |
676
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
677
|
|
|
|
678
|
|
|
$hsl = $this->converter->toHSL($color); |
679
|
3 |
|
$hsl[2] = $this->converter->clamp($hsl[2] - $delta, 100); |
680
|
|
|
|
681
|
1 |
|
return $this->converter->toRGB($hsl); |
682
|
1 |
|
} |
683
|
|
|
|
684
|
1 |
|
/** |
685
|
1 |
|
* @param array $args |
686
|
|
|
* |
687
|
1 |
|
* @return array |
688
|
1 |
|
*/ |
689
|
|
|
public function spin(array $args) |
690
|
1 |
|
{ |
691
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
692
|
|
|
|
693
|
|
|
$hsl = $this->converter->toHSL($color); |
694
|
|
|
|
695
|
|
|
$hsl[1] += $delta % 360; |
696
|
1 |
|
if ($hsl[1] < 0) { |
697
|
1 |
|
$hsl[1] += 360; |
698
|
|
|
} |
699
|
|
|
|
700
|
1 |
|
return $this->converter->toRGB($hsl); |
701
|
1 |
|
} |
702
|
|
|
|
703
|
|
|
/** |
704
|
1 |
|
* @param array $args |
705
|
1 |
|
* |
706
|
|
|
* @return int[] |
707
|
|
|
*/ |
708
|
1 |
|
public function fadeout(array $args) |
709
|
1 |
|
{ |
710
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
711
|
|
|
$color[4] = $this->converter->clamp((isset($color[4]) ? $color[4] : 1) - $delta / 100); |
712
|
1 |
|
|
713
|
|
|
return $color; |
714
|
1 |
|
} |
715
|
|
|
|
716
|
|
|
/** |
717
|
|
|
* @param array $args |
718
|
2 |
|
* |
719
|
|
|
* @return int[] |
720
|
|
|
*/ |
721
|
|
|
public function fadein(array $args) |
722
|
|
|
{ |
723
|
|
|
list($color, $delta) = $this->compiler->colorArgs($args); |
724
|
|
|
$color[4] = $this->converter->clamp((isset($color[4]) ? $color[4] : 1) + $delta / 100); |
725
|
|
|
|
726
|
7 |
|
return $color; |
727
|
|
|
} |
728
|
7 |
|
|
729
|
5 |
|
/** |
730
|
|
|
* @param array $color |
731
|
7 |
|
* |
732
|
|
|
* @return float |
733
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
734
|
|
|
*/ |
735
|
|
|
public function hue(array $color) |
736
|
|
|
{ |
737
|
|
|
$hsl = $this->converter->toHSL($this->assertions->assertColor($color)); |
738
|
|
|
|
739
|
|
|
return round($hsl[1]); |
740
|
|
|
} |
741
|
|
|
|
742
|
4 |
|
/** |
743
|
|
|
* @param array $color |
744
|
4 |
|
* |
745
|
4 |
|
* @return float |
746
|
4 |
|
* @throws \LesserPhp\Exception\GeneralException |
747
|
4 |
|
*/ |
748
|
|
|
public function saturation(array $color) |
749
|
|
|
{ |
750
|
|
|
$hsl = $this->converter->toHSL($this->assertions->assertColor($color)); |
751
|
2 |
|
|
752
|
|
|
return round($hsl[2]); |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
/** |
756
|
|
|
* @param array $color |
757
|
|
|
* |
758
|
|
|
* @return float |
759
|
4 |
|
* @throws \LesserPhp\Exception\GeneralException |
760
|
|
|
*/ |
761
|
4 |
|
public function lightness(array $color) |
762
|
|
|
{ |
763
|
|
|
$hsl = $this->converter->toHSL($this->assertions->assertColor($color)); |
764
|
|
|
|
765
|
|
|
return round($hsl[3]); |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
/** |
769
|
|
|
* get the alpha of a color |
770
|
|
|
* defaults to 1 for non-colors or colors without an alpha |
771
|
|
|
* |
772
|
|
|
* @param array $value |
773
|
|
|
* |
774
|
|
|
* @return int|null |
775
|
|
|
*/ |
776
|
|
|
public function alpha(array $value) |
777
|
|
|
{ |
778
|
|
|
$color = $this->coerce->coerceColor($value); |
779
|
|
|
if ($color !== null) { |
780
|
|
|
return isset($color[4]) ? $color[4] : 1; |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
return null; |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
/** |
787
|
|
|
* set the alpha of the color |
788
|
|
|
* |
789
|
|
|
* @param array $args |
790
|
|
|
* |
791
|
|
|
* @return int[] |
792
|
|
|
*/ |
793
|
|
|
public function fade(array $args) |
794
|
|
|
{ |
795
|
|
|
list($color, $alpha) = $this->compiler->colorArgs($args); |
796
|
|
|
$color[4] = $this->converter->clamp($alpha / 100.0); |
797
|
|
|
|
798
|
|
|
return $color; |
799
|
|
|
} |
800
|
|
|
|
801
|
|
|
/** |
802
|
|
|
* @param array $arg |
803
|
|
|
* |
804
|
|
|
* @return array |
805
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
806
|
|
|
*/ |
807
|
|
|
public function percentage(array $arg) |
808
|
|
|
{ |
809
|
|
|
$num = $this->assertions->assertNumber($arg); |
810
|
|
|
|
811
|
|
|
return ['number', $num * 100, '%']; |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
/** |
815
|
|
|
* mixes two colors by weight |
816
|
|
|
* mix(@color1, @color2, [@weight: 50%]); |
817
|
|
|
* http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method |
818
|
|
|
* |
819
|
|
|
* @param array $args |
820
|
|
|
* |
821
|
|
|
* @return mixed |
822
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
823
|
|
|
*/ |
824
|
|
|
public function mix(array $args) |
825
|
|
|
{ |
826
|
|
|
if ($args[0] !== 'list' || count($args[2]) < 2) { |
827
|
|
|
throw new GeneralException('mix expects (color1, color2, weight)'); |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
list($first, $second) = $args[2]; |
831
|
|
|
$first = $this->assertions->assertColor($first); |
832
|
|
|
$second = $this->assertions->assertColor($second); |
833
|
|
|
|
834
|
|
|
$firstAlpha = $this->alpha($first); |
835
|
|
|
$secondAlpha = $this->alpha($second); |
836
|
|
|
|
837
|
|
|
if (isset($args[2][2])) { |
838
|
|
|
$weight = $args[2][2][1] / 100.0; |
839
|
|
|
} else { |
840
|
|
|
$weight = 0.5; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
$w = $weight * 2 - 1; |
844
|
|
|
$a = $firstAlpha - $secondAlpha; |
845
|
|
|
|
846
|
|
|
$w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0; |
847
|
|
|
$w2 = 1.0 - $w1; |
848
|
|
|
|
849
|
|
|
$new = [ |
850
|
|
|
'color', |
851
|
|
|
$w1 * $first[1] + $w2 * $second[1], |
852
|
|
|
$w1 * $first[2] + $w2 * $second[2], |
853
|
|
|
$w1 * $first[3] + $w2 * $second[3], |
854
|
|
|
]; |
855
|
|
|
|
856
|
|
|
// do not change the following to type safe comparison... |
857
|
|
|
if ($firstAlpha != 1.0 || $secondAlpha != 1.0) { |
858
|
|
|
$new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1); |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
return $this->compiler->fixColor($new); |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* @param array $args |
866
|
|
|
* |
867
|
|
|
* @return array|null |
868
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
869
|
|
|
*/ |
870
|
|
|
public function contrast(array $args) |
871
|
|
|
{ |
872
|
|
|
$darkColor = ['color', 0, 0, 0]; |
873
|
|
|
$lightColor = ['color', 255, 255, 255]; |
874
|
|
|
$threshold = 0.43; |
875
|
|
|
|
876
|
|
|
if ($args[0] === 'list') { |
877
|
|
|
$inputColor = isset($args[2][0]) ? $this->assertions->assertColor($args[2][0]) : $lightColor; |
878
|
|
|
$darkColor = isset($args[2][1]) ? $this->assertions->assertColor($args[2][1]) : $darkColor; |
879
|
|
|
$lightColor = isset($args[2][2]) ? $this->assertions->assertColor($args[2][2]) : $lightColor; |
880
|
|
|
if (isset($args[2][3])) { |
881
|
|
|
if (isset($args[2][3][2]) && $args[2][3][2] === '%') { |
882
|
|
|
$args[2][3][1] /= 100; |
883
|
|
|
unset($args[2][3][2]); |
884
|
|
|
} |
885
|
|
|
$threshold = $this->assertions->assertNumber($args[2][3]); |
886
|
|
|
} |
887
|
|
|
} else { |
888
|
|
|
$inputColor = $this->assertions->assertColor($args); |
889
|
|
|
} |
890
|
|
|
|
891
|
|
|
$inputColor = $this->coerce->coerceColor($inputColor); |
892
|
|
|
$darkColor = $this->coerce->coerceColor($darkColor); |
893
|
|
|
$lightColor = $this->coerce->coerceColor($lightColor); |
894
|
|
|
|
895
|
|
|
//Figure out which is actually light and dark! |
896
|
|
|
if ($this->luma($darkColor) > $this->luma($lightColor)) { |
|
|
|
|
897
|
|
|
$t = $lightColor; |
898
|
|
|
$lightColor = $darkColor; |
899
|
|
|
$darkColor = $t; |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
$inputColorAlpha = $this->alpha($inputColor); |
|
|
|
|
903
|
|
|
if (($this->luma($inputColor) * $inputColorAlpha) < $threshold) { |
|
|
|
|
904
|
|
|
return $lightColor; |
905
|
|
|
} |
906
|
|
|
|
907
|
|
|
return $darkColor; |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
/** |
911
|
|
|
* @param array $color |
912
|
|
|
* |
913
|
|
|
* @return float |
914
|
|
|
*/ |
915
|
|
|
public function luma(array $color) |
916
|
|
|
{ |
917
|
|
|
$color = $this->coerce->coerceColor($color); |
918
|
|
|
return (0.2126 * $color[1] / 255) + (0.7152 * $color[2] / 255) + (0.0722 * $color[3] / 255); |
919
|
|
|
} |
920
|
|
|
|
921
|
|
|
|
922
|
|
|
/** |
923
|
|
|
* @param array $number |
924
|
|
|
* @param $to |
925
|
|
|
* |
926
|
|
|
* @return array |
927
|
|
|
* @throws \LesserPhp\Exception\GeneralException |
928
|
|
|
*/ |
929
|
|
|
public function convertMe(array $number, $to) |
930
|
|
|
{ |
931
|
|
|
$value = $this->assertions->assertNumber($number); |
932
|
|
|
$from = $number[2]; |
933
|
|
|
|
934
|
|
|
// easy out |
935
|
|
|
if ($from == $to) { |
936
|
|
|
return $number; |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
// check if the from value is a length |
940
|
|
|
if (($fromIndex = array_search($from, static::$lengths)) !== false) { |
941
|
|
|
// make sure to value is too |
942
|
|
|
if (in_array($to, static::$lengths)) { |
943
|
|
|
// do the actual conversion |
944
|
|
|
$toIndex = array_search($to, static::$lengths); |
945
|
|
|
$px = $value * static::$lengths_to_base[$fromIndex]; |
946
|
|
|
$result = $px * (1 / static::$lengths_to_base[$toIndex]); |
947
|
|
|
|
948
|
|
|
$result = round($result, 8); |
949
|
|
|
|
950
|
|
|
return ['number', $result, $to]; |
951
|
|
|
} |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
// do the same check for times |
955
|
|
|
if (in_array($from, static::$times) && in_array($to, static::$times)) { |
956
|
|
|
// currently only ms and s are valid |
957
|
|
|
if ($to === 'ms') { |
958
|
|
|
$result = $value * 1000; |
959
|
|
|
} else { |
960
|
|
|
$result = $value / 1000; |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
$result = round($result, 8); |
964
|
|
|
|
965
|
|
|
return ['number', $result, $to]; |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
// lastly check for an angle |
969
|
|
|
if (in_array($from, static::$angles)) { |
970
|
|
|
// convert whatever angle it is into degrees |
971
|
|
|
$deg = $value; |
972
|
|
|
if ($from === 'rad') { |
973
|
|
|
$deg = rad2deg($value); |
974
|
|
|
} else { |
975
|
|
|
if ($from === 'turn') { |
976
|
|
|
$deg = $value * 360; |
977
|
|
|
} else { |
978
|
|
|
if ($from === 'grad') { |
979
|
|
|
$deg = $value / (400 / 360); |
980
|
|
|
} |
981
|
|
|
} |
982
|
|
|
} |
983
|
|
|
|
984
|
|
|
// Then convert it from degrees into desired unit |
985
|
|
|
$result = 0; |
986
|
|
|
if ($to === 'deg') { |
987
|
|
|
$result = $deg; |
988
|
|
|
} |
989
|
|
|
|
990
|
|
|
if ($to === 'rad') { |
991
|
|
|
$result = deg2rad($deg); |
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
if ($to === 'turn') { |
995
|
|
|
$result = $value / 360; |
996
|
|
|
} |
997
|
|
|
|
998
|
|
|
if ($to === 'grad') { |
999
|
|
|
$result = $value * (400 / 360); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
$result = round($result, 8); |
1003
|
|
|
|
1004
|
|
|
return ['number', $result, $to]; |
1005
|
|
|
} |
1006
|
|
|
|
1007
|
|
|
// we don't know how to convert these |
1008
|
|
|
throw new GeneralException("Cannot convert {$from} to {$to}"); |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
/** |
1012
|
|
|
* @param bool $a |
1013
|
|
|
* |
1014
|
|
|
* @return array |
1015
|
|
|
*/ |
1016
|
|
|
public function toBool($a) |
1017
|
|
|
{ |
1018
|
|
|
if ($a) { |
1019
|
|
|
return static::$TRUE; |
1020
|
|
|
} else { |
1021
|
|
|
return static::$FALSE; |
1022
|
|
|
} |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
|
|
/** |
1026
|
|
|
* attempts to find the path of an import url, returns null for css files |
1027
|
|
|
* |
1028
|
|
|
* @param string $url |
1029
|
|
|
* |
1030
|
|
|
* @return null|string |
1031
|
|
|
*/ |
1032
|
|
|
public function findImport($url) |
1033
|
|
|
{ |
1034
|
|
|
foreach ($this->compiler->getImportDirs() as $dir) { |
1035
|
|
|
$full = $dir . (mb_substr($dir, -1) !== '/' ? '/' : '') . $url; |
1036
|
|
|
if ($this->fileExists($file = $full . '.less') || $this->fileExists($file = $full)) { |
1037
|
|
|
return $file; |
1038
|
|
|
} |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
return null; |
1042
|
|
|
} |
1043
|
|
|
|
1044
|
|
|
/** |
1045
|
|
|
* @param string $name |
1046
|
|
|
* |
1047
|
|
|
* @return bool |
1048
|
|
|
*/ |
1049
|
|
|
public function fileExists($name) |
1050
|
|
|
{ |
1051
|
|
|
return is_file($name); |
1052
|
|
|
} |
1053
|
|
|
} |
1054
|
|
|
|
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: