dc

IE filter:progid gradients in Twitter Bootstrap with a new Assetic filter

In a  project for work I came across a bug in our CSS that would leave our nav bar in IE8 (possibly IE9) with a gradient that went from blue to dark blue.  As part of this project we went with Twitter Bootstrap and are using Assetic and LessPHP to manage and compile the less files. I didn’t understand why we had this issue as we had the gradient, just the wrong colors. So I started digging into the less files, and then finally the compiled CSS.

I didn’t see anything wrong with the following CSS statement:

.btn-gradient {
	filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333', endColorstr='#222', GradientType=0);
}

It took several searches for me to come across Issue #273 in LessPHP’s tracker to even think that it was a problem with the short color codes. While not spelled out in that issue, it ends up that IE processes the #RGB in the above CSS as #000RGB. Yes, those are zeroes. This gives a blue color to the navbar that was never meant to be.

I wrote the following Assetic filter to handle the translation of #RGB into #RRGGBB for the filter:progid from above.

<?php
/**
 * Fixes short color values in filter:progid:DXImageTransform.Microsoft.gradient
 *
 * Internet Explorer tanslates #RGB to #000RGB instead of #RRGGBB. This filter
 * replaces the short color values by their long equivalents to assist IE in
 * the proper rendering of the colors.
 *
 * @copyright 2013, David Lundgren <dlundgren@syberisle.net>
 * @license MIT license <http://opensource.org/licenses/MIT>
 * @author David Lundgren <dlundgren@syberisle.net>
 */
namespace DavidsCode\Filter;

use Assetic\Asset\AssetInterface;
use Assetic\Filter\FilterInterface;

class CssProgidFilter implements FilterInterface
{
	/**
	 * Filters an asset after it has been loaded.
	 *
	 * @param Assetic\Asset\AssetInterface $asset An asset
	 */
	function filterLoad(AssetInterface $asset)
	{
	}

	/**
	 * Filters an asset just before it's dumped.
	 *
	 * @param Assetic\Asset\AssetInterface $asset An asset
	 */
	function filterDump(AssetInterface $asset)
	{
		$content      = $asset->getContent();
		$seenPatterns = array();
		if (preg_match_all('/filter:progid:DXImageTransform\.Microsoft\.gradient\(([^\)]+)\);/i', $content, $matches, PREG_SET_ORDER)) {
			foreach ($matches as $match) {
				list($search, $params) = $match;
				if (stripos($params, 'color') === false || isset($seenPatterns[$search])) {
					continue;
				}

				$newParams = $this->_translateColorParams($params);
				if ($newParams != $params) {
					$content = str_replace($search, preg_replace('/' . preg_quote($params) . '/', $newParams, $search), $content);
				}
			}
		}

		$asset->setContent($content);
	}

	/**
	 * Generates the progid parameters based on the given parameters.
	 *
	 * This translates the #RGB to #RRGGBB values for parameters with the word color in it
	 *
	 * @param $params
	 * @return string
	 */
	protected function _translateColorParams($params)
	{
		$args = explode(',', $params);
		foreach ($args as $key => $arg) {
			$param = explode('=', trim($arg));
			if (stripos($param[0], 'color') !== false) {
				$hex = trim($param[1], "'");
				if (strlen($hex) == 4) {
					$newHex = '#';
					for ($i = 1; $i < 4; ++$i) {
						$color = substr($hex, $i, 1);
						$newHex .= "{$color}{$color}";
					}
					$param[1] = "'{$newHex}'";
				}
			}
			$args[$key] = implode('=', $param);
		}

		return implode(', ', $args);
	}
}

IE no longer had the bad gradients, our designer was now happier, and the CSS compiled for production was working as expected and producing the correct gradients.

Leave a reply