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