peteboere / css-crush

CSS preprocessor.
http://the-echoplex.net/csscrush
MIT License
537 stars 51 forks source link

IE9 SVG Gradient Plugin #38

Closed tristanlins closed 11 years ago

tristanlins commented 11 years ago

I created a plugin for IE9 (and other browser with SVG, but without linear-gradient support). The plugin generate a linear gradient svg and add it as embeded background-image. There are a lot thinks that are not supported (e.a. angle and multiple backgrounds), but maybe it becomes a plugin to csscrush in the future ;-)

<?php
/**
 * IE9 svg gradient fallback
 *
 * @author Tristan Lins <tristan.lins@bit3.de>
 * 
 * @before
 *     background-image: linear-gradient(red 0, green 1);
 *
 * @after
 *     background-image: #embeded SVG#;
 *     background-image: linear-gradient(red 0, green 1);
 */

csscrush_hook::add('rule_prealias', 'csscrush__ie9_svg_gradient');

function csscrush__ie9_svg_gradient(csscrush_rule $rule)
{
    $new_set = array();
    foreach ($rule as $declaration) {
        // skip other rules
        if ($declaration->skip ||
            $declaration->property !== 'background-image' ||
            !in_array('linear-gradient', $declaration->functions) ||
            count($declaration->parenTokens) !== 1
        ) {
            $new_set[] = $declaration;
            continue;
        }

        // Reference the token table
        $token_table =& csscrush::$storage->tokens->parens;

        // Get the token name for linear-gradient
        $token = $declaration->parenTokens[0];

        // Get the token value and split it into parameter list
        $token_value = substr($token_table[$token], 1, -1);
        $token_list  = csscrush_util::splitDelimList($token_value, ',', true, true);

        if (count($token_list->list)) {
            // The gradient orientation
            $orientation = array('left', 'top');

            // Parse gradient orientation
            if (preg_match('#(top|bottom|left|right)\s*(top|bottom|left|right)?#', $token_list->list[0], $match)) {
                array_shift($token_list->list);
                for ($i = 1; $i < count($match); $i++) {
                    switch ($match[$i]) {
                        case 'left':
                        case 'right':
                            $orientation[0] = $match[$i];
                        case 'top':
                        case 'bottom':
                            $orientation[1] = $match[$i];
                            break;
                    }
                }
            }

            // Generate orientation coordinates for svg
            if ($orientation[0] == 'left') {
                $x1 = '0%';
                $x2 = '0%';
            }
            else {
                $x1 = '100%';
                $x2 = '100%';
            }
            if ($orientation[1] == 'top') {
                $y1 = '0%';
                $y2 = '100%';
            }
            else {
                $y1 = '100%';
                $y2 = '0%';
            }

            // Generate the svg
            $svg = '<?xml version="1.0" ?>' . "\n";
            $svg .= '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" version="1.0" width="100%%" height="100%%" xmlns:xlink="http://www.w3.org/1999/xlink">';
            $svg .= '<defs>';
            $svg .= sprintf(
                '<linearGradient id="myLinearGradient1" x1="%s" y1="%s" x2="%s" y2="%s" spreadMethod="pad">',
                $x1,
                $y1,
                $x2,
                $y2
            );

            // Calculate pad between color positions
            $pad = 1 / (count($token_list->list) - 1);

            // Add the color stops
            foreach ($token_list->list as $index => $color) {
                // Calculate default position
                $position = $index * $pad;

                // Use defined position
                // TODO support px,em,...
                if (preg_match('#^(.+)\s+(\d+(\.\d+)?(%)?)$#', $color, $match)) {
                    $color    = $match[1];
                    $position = $match[2];
                }

                // Default color opacity
                $opacity = 1;

                // Parse rgba and rebuild to rgb because svg not support rgba color values
                if (preg_match('#rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+(\.\d+)?)\)#', $color, $match)) {
                    // Create the rgb color value
                    $color   = sprintf(
                        'rgb(%s,%s,%s)',
                        $match[1],
                        $match[2],
                        $match[3]
                    );

                    // Store the color opacity
                    $opacity = $match[4];
                }

                // Add the color stop
                $svg .= sprintf(
                    '<stop offset="%d" stop-color="%s" stop-opacity="%f"/>',
                    $position,
                    $color,
                    $opacity
                );
            }
            $svg .= '</linearGradient>';
            $svg .= '</defs>';
            $svg .= '<rect width="100%" height="100%" style="fill:url(#myLinearGradient1);" />';
            $svg .= '</svg>';

            // Add the new background-image
            $new_set[] = new csscrush_declaration(
                $declaration->property,
                sprintf('url(data:image/svg+xml;base64,%s)', base64_encode($svg))
            );
        }

        $new_set[] = $declaration;
    }

    $rule->declarations = $new_set;
}
peteboere commented 11 years ago

Thanks for contributing Tristan!

I'll give this a whirl later.

peteboere commented 11 years ago

Not automatic like this plugin, but there is now a generic svg gradients function available as a plugin: https://github.com/peteboere/css-crush/blob/master/plugins/svg-gradients.php

tristanlins commented 11 years ago

This is cool, I check it out :-)