Implement LESS image embedding
authorOri Livneh <ori@wikimedia.org>
Thu, 19 Sep 2013 23:43:46 +0000 (16:43 -0700)
committerOri.livneh <ori@wikimedia.org>
Mon, 23 Sep 2013 19:59:05 +0000 (19:59 +0000)
This patch adds two custom LESS functions: embeddable() and embed(). Both
functions take a single image file reference as their sole argument.

- embeddable() is a predicate function (= returns a LESS boolean value) that
  checks whether an image is suitable for embedding. An image is suitable if it
  exists, has an appropriate MIME type, and is not too large.
- embed() generates a CSS url() value that contains the reference image encoded
  as a data URI.

The existence of the predicate function allows embeddable() to be used as a
mixin guard. This provides an elegant means of generating a single rule when
the image is not embeddable and multiple rules (the data URI & fallback) when
it is. This technique is used to implement a .background-image mixin, included
in this patch.

Change-Id: I3e06b6d6e8630b7923fa42b3daf1ced3e656bbe7

includes/DefaultSettings.php
resources/mediawiki.less/mediawiki.mixins.less

index c85623c..7a1eb22 100644 (file)
@@ -3313,7 +3313,52 @@ $wgResourceLoaderLESSVars = array();
  *
  * @since 1.22
  */
-$wgResourceLoaderLESSFunctions = array();
+$wgResourceLoaderLESSFunctions = array(
+       /**
+        * Check if an image file reference is suitable for embedding.
+        * An image is embeddable if it (a) exists, (b) has a suitable MIME-type,
+        * (c) does not exceed IE<9 size limit of 32kb. This is a LESS predicate
+        * function; it returns a LESS boolean value and can thus be used as a
+        * mixin guard.
+        *
+        * @par Example:
+        * @code
+        *   .background-image(@url) when(embeddable(@url)) {
+        *       background-image: url(@url) !ie;
+        *   }
+        * @endcode
+        */
+       'embeddable' => function( $frame, $less ) {
+               $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
+               $url = $frame[2][0];
+               $file = realpath( $base . '/' . $url );
+               $embeddable = ( $file
+                       && strpos( $url, '//' ) === false
+                       && filesize( $file ) < CSSMin::EMBED_SIZE_LIMIT
+                       && CSSMin::getMimeType( $file ) !== false ) ? 'true' : 'false';
+               return array( 'keyword', $embeddable );
+       },
+
+       /**
+        * Convert an image URI to a base64-encoded data URI.
+        *
+        * @par Example:
+        * @code
+        *   .fancy-button {
+        *       background-image: embed('../images/button-bg.png');
+        *   }
+        * @endcode
+        */
+       'embed' => function( $frame, $less ) {
+               $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
+               $url = $frame[2][0];
+               $file = realpath( $base . '/' . $url );
+
+               $data = CSSMin::encodeImageAsDataURI( $file );
+               $less->embeddedImages[ $file ] = filemtime( $file );
+               return 'url(' . $data . ')';
+       },
+);
 
 /**
  * Default import paths for LESS modules. LESS files referenced in @import
index c0a1104..032028f 100644 (file)
  * See <http://lesscss.org/#-mixins> for more information about how to write mixins.
  */
 
-// No mixins yet!
+.background-image(@url) when (embeddable(@url)) {
+       background-image: embed(@url);
+       background-image: url(@url)!ie;
+}
+
+.background-image(@url) when not (embeddable(@url)) {
+       background-image: url(@url);
+}