Merge branch 'MDL-46588-27' of git://github.com/jleyva/moodle into MOODLE_27_STABLE
[moodle.git] / lib / lessphp / Functions.php
blobca541409fa3f4c661a929660fd2a447697065b94
1 <?php
3 /**
4 * Builtin functions
6 * @package Less
7 * @subpackage function
8 * @see http://lesscss.org/functions/
9 */
10 class Less_Functions{
12 public $env;
13 public $currentFileInfo;
15 function __construct($env, $currentFileInfo = null ){
16 $this->env = $env;
17 $this->currentFileInfo = $currentFileInfo;
21 /**
22 * @param string $op
24 static public function operate( $op, $a, $b ){
25 switch ($op) {
26 case '+': return $a + $b;
27 case '-': return $a - $b;
28 case '*': return $a * $b;
29 case '/': return $a / $b;
33 static public function clamp($val, $max = 1){
34 return min( max($val, 0), $max);
37 static function fround( $value ){
39 if( $value === 0 ){
40 return $value;
43 if( Less_Parser::$options['numPrecision'] ){
44 $p = pow(10, Less_Parser::$options['numPrecision']);
45 return round( $value * $p) / $p;
47 return $value;
50 static public function number($n){
52 if ($n instanceof Less_Tree_Dimension) {
53 return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value);
54 } else if (is_numeric($n)) {
55 return $n;
56 } else {
57 throw new Less_Exception_Compiler("color functions take numbers as parameters");
61 static public 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 Less_Functions::number($n);
69 public function rgb ($r, $g, $b){
70 return $this->rgba($r, $g, $b, 1.0);
73 public function rgba($r, $g, $b, $a){
74 $rgb = array($r, $g, $b);
75 $rgb = array_map(array('Less_Functions','scaled'),$rgb);
77 $a = self::number($a);
78 return new Less_Tree_Color($rgb, $a);
81 public function hsl($h, $s, $l){
82 return $this->hsla($h, $s, $l, 1.0);
85 public function hsla($h, $s, $l, $a){
87 $h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int
88 $s = self::clamp(self::number($s));
89 $l = self::clamp(self::number($l));
90 $a = self::clamp(self::number($a));
92 $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
94 $m1 = $l * 2 - $m2;
96 return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255,
97 self::hsla_hue($h, $m1, $m2) * 255,
98 self::hsla_hue($h - 1/3, $m1, $m2) * 255,
99 $a);
103 * @param double $h
105 function hsla_hue($h, $m1, $m2){
106 $h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
107 if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
108 else if ($h * 2 < 1) return $m2;
109 else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
110 else return $m1;
113 public function hsv($h, $s, $v) {
114 return $this->hsva($h, $s, $v, 1.0);
118 * @param double $a
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);
126 $i = floor(($h / 60) % 6);
127 $f = ($h / 60) - $i;
129 $vs = array( $v,
130 $v * (1 - $s),
131 $v * (1 - $f * $s),
132 $v * (1 - (1 - $f) * $s));
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));
141 return $this->rgba($vs[$perm[$i][0]] * 255,
142 $vs[$perm[$i][1]] * 255,
143 $vs[$perm[$i][2]] * 255,
144 $a);
147 public function hue($color){
148 $c = $color->toHSL();
149 return new Less_Tree_Dimension(Less_Parser::round($c['h']));
152 public function saturation($color){
153 $c = $color->toHSL();
154 return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%');
157 public function lightness($color){
158 $c = $color->toHSL();
159 return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%');
162 public function hsvhue( $color ){
163 $hsv = $color->toHSV();
164 return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) );
168 public function hsvsaturation( $color ){
169 $hsv = $color->toHSV();
170 return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' );
173 public function hsvvalue( $color ){
174 $hsv = $color->toHSV();
175 return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' );
178 public function red($color) {
179 return new Less_Tree_Dimension( $color->rgb[0] );
182 public function green($color) {
183 return new Less_Tree_Dimension( $color->rgb[1] );
186 public function blue($color) {
187 return new Less_Tree_Dimension( $color->rgb[2] );
190 public function alpha($color){
191 $c = $color->toHSL();
192 return new Less_Tree_Dimension($c['a']);
195 public function luma ($color) {
196 return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%');
199 public function luminance( $color ){
200 $luminance =
201 (0.2126 * $color->rgb[0] / 255)
202 + (0.7152 * $color->rgb[1] / 255)
203 + (0.0722 * $color->rgb[2] / 255);
205 return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%');
208 public function saturate($color, $amount = null){
209 // filter: saturate(3.2);
210 // should be kept as is, so check for color
211 if( !property_exists($color,'rgb') ){
212 return null;
214 $hsl = $color->toHSL();
216 $hsl['s'] += $amount->value / 100;
217 $hsl['s'] = self::clamp($hsl['s']);
219 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
223 * @param Less_Tree_Dimension $amount
225 public function desaturate($color, $amount){
226 $hsl = $color->toHSL();
228 $hsl['s'] -= $amount->value / 100;
229 $hsl['s'] = self::clamp($hsl['s']);
231 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
236 public function lighten($color, $amount){
237 $hsl = $color->toHSL();
239 $hsl['l'] += $amount->value / 100;
240 $hsl['l'] = self::clamp($hsl['l']);
242 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
245 public function darken($color, $amount){
247 if( $color instanceof Less_Tree_Color ){
248 $hsl = $color->toHSL();
249 $hsl['l'] -= $amount->value / 100;
250 $hsl['l'] = self::clamp($hsl['l']);
252 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
255 Less_Functions::Expected('color',$color);
258 public function fadein($color, $amount){
259 $hsl = $color->toHSL();
260 $hsl['a'] += $amount->value / 100;
261 $hsl['a'] = self::clamp($hsl['a']);
262 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
265 public function fadeout($color, $amount){
266 $hsl = $color->toHSL();
267 $hsl['a'] -= $amount->value / 100;
268 $hsl['a'] = self::clamp($hsl['a']);
269 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
272 public function fade($color, $amount){
273 $hsl = $color->toHSL();
275 $hsl['a'] = $amount->value / 100;
276 $hsl['a'] = self::clamp($hsl['a']);
277 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
282 public function spin($color, $amount){
283 $hsl = $color->toHSL();
284 $hue = fmod($hsl['h'] + $amount->value, 360);
286 $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
288 return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
292 // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
293 // http://sass-lang.com
297 * @param Less_Tree_Color $color1
299 public function mix($color1, $color2, $weight = null){
300 if (!$weight) {
301 $weight = new Less_Tree_Dimension('50', '%');
304 $p = $weight->value / 100.0;
305 $w = $p * 2 - 1;
306 $hsl1 = $color1->toHSL();
307 $hsl2 = $color2->toHSL();
308 $a = $hsl1['a'] - $hsl2['a'];
310 $w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
311 $w2 = 1 - $w1;
313 $rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
314 $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
315 $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2);
317 $alpha = $color1->alpha * $p + $color2->alpha * (1 - $p);
319 return new Less_Tree_Color($rgb, $alpha);
322 public function greyscale($color){
323 return $this->desaturate($color, new Less_Tree_Dimension(100));
327 public function contrast( $color, $dark = null, $light = null, $threshold = null){
328 // filter: contrast(3.2);
329 // should be kept as is, so check for color
330 if( !property_exists($color,'rgb') ){
331 return null;
333 if( !$light ){
334 $light = $this->rgba(255, 255, 255, 1.0);
336 if( !$dark ){
337 $dark = $this->rgba(0, 0, 0, 1.0);
339 //Figure out which is actually light and dark!
340 if( $dark->luma() > $light->luma() ){
341 $t = $light;
342 $light = $dark;
343 $dark = $t;
345 if( !$threshold ){
346 $threshold = 0.43;
347 } else {
348 $threshold = Less_Functions::number($threshold);
351 if( $color->luma() < $threshold ){
352 return $light;
353 } else {
354 return $dark;
358 public function e ($str){
359 if( is_string($str) ){
360 return new Less_Tree_Anonymous($str);
362 return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value);
365 public function escape ($str){
367 $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$');
369 return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert));
374 * todo: This function will need some additional work to make it work the same as less.js
377 public function replace( $string, $pattern, $replacement, $flags = null ){
378 $result = $string->value;
380 $expr = '/'.str_replace('/','\\/',$pattern->value).'/';
381 if( $flags && $flags->value){
382 $expr .= self::replace_flags($flags->value);
385 $result = preg_replace($expr,$replacement->value,$result);
388 if( property_exists($string,'quote') ){
389 return new Less_Tree_Quoted( $string->quote, $result, $string->escaped);
391 return new Less_Tree_Quoted( '', $result );
394 public static function replace_flags($flags){
395 $flags = str_split($flags,1);
396 $new_flags = '';
398 foreach($flags as $flag){
399 switch($flag){
400 case 'e':
401 case 'g':
402 break;
404 default:
405 $new_flags .= $flag;
406 break;
410 return $new_flags;
413 public function _percent(){
414 $string = func_get_arg(0);
416 $args = func_get_args();
417 array_shift($args);
418 $result = $string->value;
420 foreach($args as $arg){
421 if( preg_match('/%[sda]/i',$result, $token) ){
422 $token = $token[0];
423 $value = stristr($token, 's') ? $arg->value : $arg->toCSS();
424 $value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value;
425 $result = preg_replace('/%[sda]/i',$value, $result, 1);
428 $result = str_replace('%%', '%', $result);
430 return new Less_Tree_Quoted( $string->quote , $result, $string->escaped);
433 public function unit( $val, $unit = null) {
434 if( !($val instanceof Less_Tree_Dimension) ){
435 throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') );
438 if( $unit ){
439 if( $unit instanceof Less_Tree_Keyword ){
440 $unit = $unit->value;
441 } else {
442 $unit = $unit->toCSS();
444 } else {
445 $unit = "";
447 return new Less_Tree_Dimension($val->value, $unit );
450 public function convert($val, $unit){
451 return $val->convertTo($unit->value);
454 public function round($n, $f = false) {
456 $fraction = 0;
457 if( $f !== false ){
458 $fraction = $f->value;
461 return $this->_math('Less_Parser::round',null, $n, $fraction);
464 public function pi(){
465 return new Less_Tree_Dimension(M_PI);
468 public function mod($a, $b) {
469 return new Less_Tree_Dimension( $a->value % $b->value, $a->unit);
474 public function pow($x, $y) {
475 if( is_numeric($x) && is_numeric($y) ){
476 $x = new Less_Tree_Dimension($x);
477 $y = new Less_Tree_Dimension($y);
478 }elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){
479 throw new Less_Exception_Compiler('Arguments must be numbers');
482 return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit );
485 // var mathFunctions = [{name:"ce ...
486 public function ceil( $n ){ return $this->_math('ceil', null, $n); }
487 public function floor( $n ){ return $this->_math('floor', null, $n); }
488 public function sqrt( $n ){ return $this->_math('sqrt', null, $n); }
489 public function abs( $n ){ return $this->_math('abs', null, $n); }
491 public function tan( $n ){ return $this->_math('tan', '', $n); }
492 public function sin( $n ){ return $this->_math('sin', '', $n); }
493 public function cos( $n ){ return $this->_math('cos', '', $n); }
495 public function atan( $n ){ return $this->_math('atan', 'rad', $n); }
496 public function asin( $n ){ return $this->_math('asin', 'rad', $n); }
497 public function acos( $n ){ return $this->_math('acos', 'rad', $n); }
499 private function _math() {
500 $args = func_get_args();
501 $fn = array_shift($args);
502 $unit = array_shift($args);
504 if ($args[0] instanceof Less_Tree_Dimension) {
506 if( $unit === null ){
507 $unit = $args[0]->unit;
508 }else{
509 $args[0] = $args[0]->unify();
511 $args[0] = (float)$args[0]->value;
512 return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit);
513 } else if (is_numeric($args[0])) {
514 return call_user_func_array($fn,$args);
515 } else {
516 throw new Less_Exception_Compiler("math functions take numbers as parameters");
521 * @param boolean $isMin
523 function _minmax( $isMin, $args ){
525 $arg_count = count($args);
527 if( $arg_count < 1 ){
528 throw new Less_Exception_Compiler( 'one or more arguments required');
531 $j = null;
532 $unitClone = null;
533 $unitStatic = null;
536 $order = array(); // elems only contains original argument values.
537 $values = array(); // key is the unit.toString() for unified tree.Dimension values,
538 // value is the index into the order array.
541 for( $i = 0; $i < $arg_count; $i++ ){
542 $current = $args[$i];
543 if( !($current instanceof Less_Tree_Dimension) ){
544 if( is_array($args[$i]->value) ){
545 $args[] = $args[$i]->value;
547 continue;
550 if( $current->unit->toString() === '' && !$unitClone ){
551 $temp = new Less_Tree_Dimension($current->value, $unitClone);
552 $currentUnified = $temp->unify();
553 }else{
554 $currentUnified = $current->unify();
557 if( $currentUnified->unit->toString() === "" && !$unitStatic ){
558 $unit = $unitStatic;
559 }else{
560 $unit = $currentUnified->unit->toString();
563 if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){
564 $unitStatic = $unit;
567 if( $unit != '' && !$unitClone ){
568 $unitClone = $current->unit->toString();
571 if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){
572 $j = $values[''];
573 }elseif( isset($values[$unit]) ){
574 $j = $values[$unit];
575 }else{
577 if( $unitStatic && $unit !== $unitStatic ){
578 throw new Less_Exception_Compiler( 'incompatible types');
580 $values[$unit] = count($order);
581 $order[] = $current;
582 continue;
586 if( $order[$j]->unit->toString() === "" && $unitClone ){
587 $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone);
588 $referenceUnified = $temp->unifiy();
589 }else{
590 $referenceUnified = $order[$j]->unify();
592 if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){
593 $order[$j] = $current;
597 if( count($order) == 1 ){
598 return $order[0];
600 $args = array();
601 foreach($order as $a){
602 $args[] = $a->toCSS($this->env);
604 return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')');
607 public function min(){
608 $args = func_get_args();
609 return $this->_minmax( true, $args );
612 public function max(){
613 $args = func_get_args();
614 return $this->_minmax( false, $args );
617 public function getunit($n){
618 return new Less_Tree_Anonymous($n->unit);
621 public function argb($color) {
622 return new Less_Tree_Anonymous($color->toARGB());
625 public function percentage($n) {
626 return new Less_Tree_Dimension($n->value * 100, '%');
629 public function color($n) {
631 if( $n instanceof Less_Tree_Quoted ){
632 $colorCandidate = $n->value;
633 $returnColor = Less_Tree_Color::fromKeyword($colorCandidate);
634 if( $returnColor ){
635 return $returnColor;
637 if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){
638 return new Less_Tree_Color(substr($colorCandidate, 1));
640 throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF");
641 } else {
642 throw new Less_Exception_Compiler("argument must be a string");
647 public function iscolor($n) {
648 return $this->_isa($n, 'Less_Tree_Color');
651 public function isnumber($n) {
652 return $this->_isa($n, 'Less_Tree_Dimension');
655 public function isstring($n) {
656 return $this->_isa($n, 'Less_Tree_Quoted');
659 public function iskeyword($n) {
660 return $this->_isa($n, 'Less_Tree_Keyword');
663 public function isurl($n) {
664 return $this->_isa($n, 'Less_Tree_Url');
667 public function ispixel($n) {
668 return $this->isunit($n, 'px');
671 public function ispercentage($n) {
672 return $this->isunit($n, '%');
675 public function isem($n) {
676 return $this->isunit($n, 'em');
680 * @param string $unit
682 public function isunit( $n, $unit ){
683 return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
687 * @param string $type
689 private function _isa($n, $type) {
690 return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
693 public function tint($color, $amount) {
694 return $this->mix( $this->rgb(255,255,255), $color, $amount);
697 public function shade($color, $amount) {
698 return $this->mix($this->rgb(0, 0, 0), $color, $amount);
701 public function extract($values, $index ){
702 $index = (int)$index->value - 1; // (1-based index)
703 // handle non-array values as an array of length 1
704 // return 'undefined' if index is invalid
705 if( property_exists($values,'value') && is_array($values->value) ){
706 if( isset($values->value[$index]) ){
707 return $values->value[$index];
709 return null;
711 }elseif( (int)$index === 0 ){
712 return $values;
715 return null;
718 function length($values){
719 $n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1;
720 return new Less_Tree_Dimension($n);
723 function datauri($mimetypeNode, $filePathNode = null ) {
725 $filePath = ( $filePathNode ? $filePathNode->value : null );
726 $mimetype = $mimetypeNode->value;
728 $args = 2;
729 if( !$filePath ){
730 $filePath = $mimetype;
731 $args = 1;
734 $filePath = str_replace('\\','/',$filePath);
735 if( Less_Environment::isPathRelative($filePath) ){
737 if( Less_Parser::$options['relativeUrls'] ){
738 $temp = $this->currentFileInfo['currentDirectory'];
739 } else {
740 $temp = $this->currentFileInfo['entryPath'];
743 if( !empty($temp) ){
744 $filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath);
750 // detect the mimetype if not given
751 if( $args < 2 ){
753 /* incomplete
754 $mime = require('mime');
755 mimetype = mime.lookup(path);
757 // use base 64 unless it's an ASCII or UTF-8 format
758 var charset = mime.charsets.lookup(mimetype);
759 useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
760 if (useBase64) mimetype += ';base64';
763 $mimetype = Less_Mime::lookup($filePath);
765 $charset = Less_Mime::charsets_lookup($mimetype);
766 $useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8'));
767 if( $useBase64 ){ $mimetype .= ';base64'; }
769 }else{
770 $useBase64 = preg_match('/;base64$/',$mimetype);
774 if( file_exists($filePath) ){
775 $buf = @file_get_contents($filePath);
776 }else{
777 $buf = false;
781 // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
782 // and the --ieCompat flag is enabled, return a normal url() instead.
783 $DATA_URI_MAX_KB = 32;
784 $fileSizeInKB = round( strlen($buf) / 1024 );
785 if( $fileSizeInKB >= $DATA_URI_MAX_KB ){
786 $url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo);
787 return $url->compile($this);
790 if( $buf ){
791 $buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf);
792 $filePath = '"data:' . $mimetype . ',' . $buf . '"';
795 return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) );
798 //svg-gradient
799 function svggradient( $direction ){
801 $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
802 $arguments = func_get_args();
804 if( count($arguments) < 3 ){
805 throw new Less_Exception_Compiler( $throw_message );
808 $stops = array_slice($arguments,1);
809 $gradientType = 'linear';
810 $rectangleDimension = 'x="0" y="0" width="1" height="1"';
811 $useBase64 = true;
812 $directionValue = $direction->toCSS();
815 switch( $directionValue ){
816 case "to bottom":
817 $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
818 break;
819 case "to right":
820 $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
821 break;
822 case "to bottom right":
823 $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
824 break;
825 case "to top right":
826 $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
827 break;
828 case "ellipse":
829 case "ellipse at center":
830 $gradientType = "radial";
831 $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
832 $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
833 break;
834 default:
835 throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
838 $returner = '<?xml version="1.0" ?>' .
839 '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
840 '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
842 for( $i = 0; $i < count($stops); $i++ ){
843 if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){
844 $color = $stops[$i]->value[0];
845 $position = $stops[$i]->value[1];
846 }else{
847 $color = $stops[$i];
848 $position = null;
851 if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){
852 throw new Less_Exception_Compiler( $throw_message );
854 if( $position ){
855 $positionValue = $position->toCSS();
856 }elseif( $i === 0 ){
857 $positionValue = '0%';
858 }else{
859 $positionValue = '100%';
861 $alpha = $color->alpha;
862 $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
865 $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
868 if( $useBase64 ){
869 $returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'";
870 }else{
871 $returner = "'data:image/svg+xml,".$returner."'";
874 return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
879 * @param string $type
881 private static function Expected( $type, $arg ){
883 $debug = debug_backtrace();
884 array_shift($debug);
885 $last = array_shift($debug);
886 $last = array_intersect_key($last,array('function'=>'','class'=>'','line'=>''));
888 $message = 'Object of type '.get_class($arg).' passed to darken function. Expecting `'.$type.'`. '.$arg->toCSS().'. '.print_r($last,true);
889 throw new Less_Exception_Compiler($message);
894 * Php version of javascript's `encodeURIComponent` function
896 * @param string $string The string to encode
897 * @return string The encoded string
899 public static function encodeURIComponent($string){
900 $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
901 return strtr(rawurlencode($string), $revert);
905 // Color Blending
906 // ref: http://www.w3.org/TR/compositing-1
908 public function colorBlend( $mode, $color1, $color2 ){
909 $ab = $color1->alpha; // backdrop
910 $as = $color2->alpha; // source
911 $r = array(); // result
913 $ar = $as + $ab * (1 - $as);
914 for( $i = 0; $i < 3; $i++ ){
915 $cb = $color1->rgb[$i] / 255;
916 $cs = $color2->rgb[$i] / 255;
917 $cr = call_user_func( $mode, $cb, $cs );
918 if( $ar ){
919 $cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
921 $r[$i] = $cr * 255;
924 return new Less_Tree_Color($r, $ar);
927 public function multiply($color1, $color2 ){
928 return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 );
931 private function colorBlendMultiply($cb, $cs){
932 return $cb * $cs;
935 public function screen($color1, $color2 ){
936 return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 );
939 private function colorBlendScreen( $cb, $cs){
940 return $cb + $cs - $cb * $cs;
943 public function overlay($color1, $color2){
944 return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 );
947 private function colorBlendOverlay($cb, $cs ){
948 $cb *= 2;
949 return ($cb <= 1)
950 ? $this->colorBlendMultiply($cb, $cs)
951 : $this->colorBlendScreen($cb - 1, $cs);
954 public function softlight($color1, $color2){
955 return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 );
958 private function colorBlendSoftlight($cb, $cs ){
959 $d = 1;
960 $e = $cb;
961 if( $cs > 0.5 ){
962 $e = 1;
963 $d = ($cb > 0.25) ? sqrt($cb)
964 : ((16 * $cb - 12) * $cb + 4) * $cb;
966 return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
969 public function hardlight($color1, $color2){
970 return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 );
973 private function colorBlendHardlight( $cb, $cs ){
974 return $this->colorBlendOverlay($cs, $cb);
977 public function difference($color1, $color2) {
978 return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 );
981 private function colorBlendDifference( $cb, $cs ){
982 return abs($cb - $cs);
985 public function exclusion( $color1, $color2 ){
986 return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 );
989 private function colorBlendExclusion( $cb, $cs ){
990 return $cb + $cs - 2 * $cb * $cs;
993 public function average($color1, $color2){
994 return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 );
997 // non-w3c functions:
998 function colorBlendAverage($cb, $cs ){
999 return ($cb + $cs) / 2;
1002 public function negation($color1, $color2 ){
1003 return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 );
1006 function colorBlendNegation($cb, $cs){
1007 return 1 - abs($cb + $cs - 1);
1010 // ~ End of Color Blending