Merge "Fixed some @params documentation (includes/api)"
[lhc/web/wiklou.git] / includes / libs / lessc.inc.php
index 6db21be..57d45ed 100644 (file)
@@ -1,10 +1,10 @@
 <?php
 
 /**
- * lessphp v0.4.0@785ad53840
+ * lessphp v0.4.0@b7cd5c79e8
  * http://leafo.net/lessphp
  *
- * LESS css compiler, adapted from http://lesscss.org
+ * LESS CSS compiler, adapted from http://lesscss.org
  *
  * For ease of distribution, lessphp 0.4.0 is under a dual license.
  * You are free to pick which one suits your needs.
@@ -40,7 +40,7 @@
 
 
 /**
- * The less compiler and parser.
+ * The LESS compiler and parser.
  *
  * Converting LESS to CSS is a three stage process. The incoming file is parsed
  * by `lessc_parser` into a syntax tree, then it is compiled into another tree
@@ -67,8 +67,9 @@
  */
 class lessc {
        static public $VERSION = "v0.4.0";
-       static protected $TRUE = array("keyword", "true");
-       static protected $FALSE = array("keyword", "false");
+
+       static public $TRUE = array("keyword", "true");
+       static public $FALSE = array("keyword", "false");
 
        protected $libFunctions = array();
        protected $registeredVars = array();
@@ -90,8 +91,6 @@ class lessc {
        protected $sourceParser = null;
        protected $sourceLoc = null;
 
-       static public $defaultValue = array("keyword", "");
-
        static protected $nextImportId = 0; // uniquely identify imports
 
        // attempts to find the path of an import url, returns null for css files
@@ -311,34 +310,68 @@ class lessc {
                foreach ($this->sortProps($block->props) as $prop) {
                        $this->compileProp($prop, $block, $out);
                }
+               $out->lines = $this->deduplicate($out->lines);
+       }
 
-               $out->lines = array_values(array_unique($out->lines));
+       /**
+        * Deduplicate lines in a block. Comments are not deduplicated. If a
+        * duplicate rule is detected, the comments immediately preceding each
+        * occurence are consolidated.
+        */
+       protected function deduplicate($lines) {
+               $unique = array();
+               $comments = array();
+
+               foreach($lines as $line) {
+                       if (strpos($line, '/*') === 0) {
+                               $comments[] = $line;
+                               continue;
+                       }
+                       if (!in_array($line, $unique)) {
+                               $unique[] = $line;
+                       }
+                       array_splice($unique, array_search($line, $unique), 0, $comments);
+                       $comments = array();
+               }
+               return array_merge($unique, $comments);
        }
 
        protected function sortProps($props, $split = false) {
                $vars = array();
                $imports = array();
                $other = array();
+               $stack = array();
 
                foreach ($props as $prop) {
                        switch ($prop[0]) {
+                       case "comment":
+                               $stack[] = $prop;
+                               break;
                        case "assign":
+                               $stack[] = $prop;
                                if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
-                                       $vars[] = $prop;
+                                       $vars = array_merge($vars, $stack);
                                } else {
-                                       $other[] = $prop;
+                                       $other = array_merge($other, $stack);
                                }
+                               $stack = array();
                                break;
                        case "import":
                                $id = self::$nextImportId++;
                                $prop[] = $id;
-                               $imports[] = $prop;
+                               $stack[] = $prop;
+                               $imports = array_merge($imports, $stack);
                                $other[] = array("import_mixin", $id);
+                               $stack = array();
                                break;
                        default:
-                               $other[] = $prop;
+                               $stack[] = $prop;
+                               $other = array_merge($other, $stack);
+                               $stack = array();
+                               break;
                        }
                }
+               $other = array_merge($other, $stack);
 
                if ($split) {
                        return array(array_merge($vars, $imports), $other);
@@ -712,8 +745,7 @@ class lessc {
                        $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
 
                        if ($mixins === null) {
-                               // fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n");
-                               break; // throw error here??
+                               $this->throwError("{$prop[1][0]} is undefined");
                        }
 
                        foreach ($mixins as $mixin) {
@@ -979,6 +1011,39 @@ class lessc {
                return $this->lib_rgbahex($color);
        }
 
+       /**
+        * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
+        *
+        * @param  array  $value either an argument list (two strings) or a single string
+        * @return string        formatted url(), either as a link or base64-encoded
+        */
+       protected function lib_data_uri($value) {
+               $mime = ($value[0] === 'list') ? $value[2][0][2] : null;
+               $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
+
+               $fullpath = $this->findImport($url);
+
+               if($fullpath && ($fsize = filesize($fullpath)) !== false) {
+                       // IE8 can't handle data uris larger than 32KB
+                       if($fsize/1024 < 32) {
+                               if(is_null($mime)) {
+                                       if(class_exists('finfo')) { // php 5.3+
+                                               $finfo = new finfo(FILEINFO_MIME);
+                                               $mime = explode('; ', $finfo->file($fullpath));
+                                               $mime = $mime[0];
+                                       } elseif(function_exists('mime_content_type')) { // PHP 5.2
+                                               $mime = mime_content_type($fullpath);
+                                       }
+                               }
+
+                               if(!is_null($mime)) // fallback if the mime type is still unknown
+                                       $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
+                       }
+               }
+
+               return 'url("'.$url.'")';
+       }
+
        // utility func to unquote a string
        protected function lib_e($arg) {
                switch ($arg[0]) {
@@ -987,7 +1052,7 @@ class lessc {
                                if (isset($items[0])) {
                                        return $this->lib_e($items[0]);
                                }
-                               return self::$defaultValue;
+                               $this->throwError("unrecognised input");
                        case "string":
                                $arg[1] = "";
                                return $arg;
@@ -1061,7 +1126,7 @@ class lessc {
         * Helper function to get arguments for color manipulation functions.
         * takes a list that contains a color like thing and a percentage
         */
-       protected function colorArgs($args) {
+       public function colorArgs($args) {
                if ($args[0] != 'list' || count($args[2]) < 2) {
                        return array(array('color', 0, 0, 0), 0);
                }
@@ -1202,36 +1267,56 @@ class lessc {
        }
 
        protected function lib_contrast($args) {
-               if ($args[0] != 'list' || count($args[2]) < 3) {
-                       return array(array('color', 0, 0, 0), 0);
-               }
+           $darkColor  = array('color', 0, 0, 0);
+           $lightColor = array('color', 255, 255, 255);
+           $threshold  = 0.43;
 
-               list($inputColor, $darkColor, $lightColor) = $args[2];
+           if ( $args[0] == 'list' ) {
+               $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
+               $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
+               $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
+               $threshold  = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
+           }
+           else {
+               $inputColor  = $this->assertColor($args);
+           }
 
-               $inputColor = $this->assertColor($inputColor);
-               $darkColor = $this->assertColor($darkColor);
-               $lightColor = $this->assertColor($lightColor);
-               $hsl = $this->toHSL($inputColor);
+           $inputColor = $this->coerceColor($inputColor);
+           $darkColor  = $this->coerceColor($darkColor);
+           $lightColor = $this->coerceColor($lightColor);
 
-               if ($hsl[3] > 50) {
-                       return $darkColor;
-               }
+           //Figure out which is actually light and dark!
+           if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
+               $t  = $lightColor;
+               $lightColor = $darkColor;
+               $darkColor  = $t;
+           }
 
-               return $lightColor;
+           $inputColor_alpha = $this->lib_alpha($inputColor);
+           if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
+               return $lightColor;
+           }
+           return $darkColor;
        }
 
-       protected function assertColor($value, $error = "expected color value") {
+       protected function lib_luma($color) {
+           $color = $this->coerceColor($color);
+           return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
+       }
+
+
+       public function assertColor($value, $error = "expected color value") {
                $color = $this->coerceColor($value);
                if (is_null($color)) $this->throwError($error);
                return $color;
        }
 
-       protected function assertNumber($value, $error = "expecting number") {
+       public function assertNumber($value, $error = "expecting number") {
                if ($value[0] == "number") return $value[1];
                $this->throwError($error);
        }
 
-       protected function assertArgs($value, $expectedArgs, $name="") {
+       public function assertArgs($value, $expectedArgs, $name="") {
                if ($expectedArgs == 1) {
                        return $value;
                } else {
@@ -1415,7 +1500,7 @@ class lessc {
                        }
 
                        $seen[$key] = true;
-                       $out = $this->reduce($this->get($key, self::$defaultValue));
+                       $out = $this->reduce($this->get($key));
                        $seen[$key] = false;
                        return $out;
                case "list":
@@ -1443,8 +1528,9 @@ class lessc {
 
                        list(, $name, $args) = $value;
                        if ($name == "%") $name = "_sprintf";
+
                        $f = isset($this->libFunctions[$name]) ?
-                               $this->libFunctions[$name] : array($this, 'lib_'.$name);
+                               $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
 
                        if (is_callable($f)) {
                                if ($args[0] == 'list')
@@ -1551,7 +1637,7 @@ class lessc {
                return $value;
        }
 
-       protected function toBool($a) {
+       public function toBool($a) {
                if ($a) return self::$TRUE;
                else return self::$FALSE;
        }
@@ -1774,7 +1860,7 @@ class lessc {
 
 
        // get the highest occurrence entry for a name
-       protected function get($name, $default=null) {
+       protected function get($name) {
                $current = $this->env;
 
                $isArguments = $name == $this->vPrefix . 'arguments';
@@ -1791,7 +1877,7 @@ class lessc {
                        }
                }
 
-               return $default;
+               $this->throwError("variable $name is undefined");
        }
 
        // inject array of unparsed strings into environment as variables
@@ -2027,14 +2113,14 @@ class lessc {
                return $this->allParsedFiles;
        }
 
-       protected function addParsedFile($file) {
+       public function addParsedFile($file) {
                $this->allParsedFiles[realpath($file)] = filemtime($file);
        }
 
        /**
         * Uses the current value of $this->count to show line and line number
         */
-       protected function throwError($msg = null) {
+       public function throwError($msg = null) {
                if ($this->sourceLoc >= 0) {
                        $this->sourceParser->throwError($msg, $this->sourceLoc);
                }
@@ -2300,14 +2386,13 @@ class lessc_parser {
                $this->whitespace();
 
                // parse the entire file
-               $lastCount = $this->count;
                while (false !== $this->parseChunk());
 
                if ($this->count != strlen($this->buffer))
                        $this->throwError();
 
                // TODO report where the block was opened
-               if (!is_null($this->env->parent))
+               if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
                        throw new exception('parse error: unclosed block');
 
                return $this->env;
@@ -2353,6 +2438,10 @@ class lessc_parser {
                if (empty($this->buffer)) return false;
                $s = $this->seek();
 
+               if ($this->whitespace()) {
+                       return true;
+               }
+
                // setting a property
                if ($this->keyword($key) && $this->assign() &&
                        $this->propertyValue($value, $key) && $this->end())
@@ -2433,7 +2522,7 @@ class lessc_parser {
                }
 
                // opening a simple block
-               if ($this->tags($tags) && $this->literal('{')) {
+               if ($this->tags($tags) && $this->literal('{', false)) {
                        $tags = $this->fixTags($tags);
                        $this->pushBlock($tags);
                        return true;
@@ -2708,7 +2797,6 @@ class lessc_parser {
 
        // an import statement
        protected function import(&$out) {
-               $s = $this->seek();
                if (!$this->literal('@import')) return false;
 
                // @import "something.css" media;
@@ -3068,7 +3156,6 @@ class lessc_parser {
        // list of tags of specifying mixin path
        // optionally separated by > (lazy, accepts extra >)
        protected function mixinTags(&$tags) {
-               $s = $this->seek();
                $tags = array();
                while ($this->tag($tt, true)) {
                        $tags[] = $tt;
@@ -3296,7 +3383,7 @@ class lessc_parser {
 
        // consume an end of statement delimiter
        protected function end() {
-               if ($this->literal(';')) {
+               if ($this->literal(';', false)) {
                        return true;
                } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
                        // if there is end of file or a closing block next then we don't need a ;
@@ -3445,9 +3532,9 @@ class lessc_parser {
                if ($this->writeComments) {
                        $gotWhite = false;
                        while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
-                               if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
+                               if (isset($m[1]) && empty($this->seenComments[$this->count])) {
                                        $this->append(array("comment", $m[1]));
-                                       $this->commentsSeen[$this->count] = true;
+                                       $this->seenComments[$this->count] = true;
                                }
                                $this->count += strlen($m[0]);
                                $gotWhite = true;