Convert SVG into RaphaëlFebruary 22, 2010

and have really nice VML rendering in IE as a bonus.

Why?

One day I thought that if I need to use SVG’s for my web layouts (and I do) the better way is to draw them with the tool I’m used to - Adobe Illustrator - and just convert them into Javascript. Easier said than done, it seem that there’s no public project for this. What the heck, I have done something like this before and as SVG is pure XML all I need to do is write some small PHP script. So here we go. I have a logo I use in one of my client sites, let’s use it as a test.

Parsing SVG

I set up simple PHP XML parser with start and end tag handlers. The first issue is that does not support groups directly, but fortunately has sets. When you set set attributes they are propagated manually to the elements, so when converting one must not set attributes when defining that set, but rather collect that groups attributes and set them only after closing that group.

UPDATE 22.09.2013 the code is now in Github

Nothing difficult in converting. Basically its just converting SVG attribute array into Raphaël attr() function and simple creating Raphaël shapes.

Modifying Raphaël

UPDATE 22.09.2013 This is badly outdated now

So far so good. But as I digged deeper I noted that Raphaël is very nice and so on, but absolutely does not support real SVG files. You just cannot convert anything more than simple shapes into Raphaël’s code.

And that’s a problem because Raphaël is written in way that you cannot extend or rewrite any of crucial pieces needed, in javascript no outside access for private variables and methods exist, so the only option is to fork and write your own version of Raphaël. Which is kind of dumb thing to to, because you need to do that again when new version arrives. But what the hell.

Strokes

Raphaël, has hardcoded (?!? why) default black 1px stroke for shapes. So when converted shapes without stroke are drawn they always have that default stroke, unless you watch if there’s is a stroke definition and when not you force stroke:none. Which I did in the PHP code. Better way perhaps is to replace that hardcoded #000 with variable in Raphaël and set that to none.

Fill-rule

Raphaël, does not support fill-rule attribute. Private property availableAttrs needs to be extended with

    \"fill-rule\": \"nonzero\" 

and

    case \"fill-rule\":
        node[setAttribute](att, value);
        break;

handler is needed in setFillAndStroke private function.

viewBox

This was easy and is perhaps sufficent for somebody, but I needed viewBox support which Raphaël deeply lacks. I need that because when one draws in Illustrator then its pain in the donkey to do it exacly with dimensions needed for the final result needed for web. The right thing to do is just to draw (it’s a vector, it doesn’t matter) and not to think about width and height and then scale that SVG down with Raphaël. Also as I discovered later, there’s A VERY BIG BONUS for Internet Explorer hidden. IE does not draw vectors nicely in small scale, I think everybody touched Raphaël had noticed that. The solution is simple - to create BIG shapes (like in thousands of pixels) and scale them down. I’ll show it in the end of this article.

As it turned out, adding viewBox to SVG part of the Raphaël was rather easy. Adding to the VML part was fucking hard. But I got it covered eventually. Added function setView(width, height), for scaling. BTW setSize() does not work as a result, as for now, didn’t needed it, didn’t fix it.

Here’s how I did it.

Extending constructor.

Support for viewBox absolutely needs support for all four (x,y,width,height) parameters, because somehow one just cannot set x,y to 0,0 in Illustrator. Or one can, but I don’t know how and anyway standard supports not just 0,0.

So the first step is extend the constructor to support following syntax and not breaking existing things:

    var canvas = new Raphael(container, width, height, x, y);

in getContainer

    return {container: container, width: arguments[1], height: arguments[2]};

add:

    , x: arguments[3], y: arguments[4]

Now x&y go through to the create function.
Nothing to fear when you do not need viewBox support, use as usual, set only width, height, nothing will change to you.

Add setView() function to Paper

after: Paper[proto].setSize = setSize; add: Paper[proto].setView = setView;

SVG part

in function create

after:

    width = width || 512;
    height = height || 342;

add:

    x = x || 0;
    y = y || 0;

for cnvs:

        $(cnvs, {
            xmlns: \"http://www.w3.org/2000/svg\",
            version: 1.1,
            width: width,
            height: height
        });

add property:

    viewBox: x + \" \" + y + \" \"+ width + \" \" + height

after function SetSize() definition add:

    var setView = function (width, height) {
        width = width || this.width;
        height = height || this.height;
        this.canvas[setAttribute](\"width\", width);
        this.canvas[setAttribute](\"height\", height);
        return this;
    };

VML part

VML does not support viewBox directly, to achieve desired result we wrap special group element around all other elements.

Replace g.style.cssText and add left & top support.

    g.style.cssText = \"position:absolute;left:\"+vml.x+\"px;top:\"+vml.y+\"px;width:\" + vml.width + \"px;height:\" + vml.height + \"px\";

in functions thePath, theCircle, theRect, theEllipse, theImage and theText.

NB! add

    var vml=VML;

in he beginning of thePath (for some reason the parameter is VML there, instead of vml elsewhere), for theText and thePath

after:

    ol.width = vml.width;
    ol.height = vml.height;

add:

    ol.top = vml.y + \"px\";
    ol.left = vml.x + \"px\";

Function create needs to be modified rather heavily. Here’s short the explanation:

Because of that wrapper group also clear() needs some changes, instead of recreating span property I just set innerHTML to empty string

delete this:

this.span = doc.createElement(\"span\");
this.canvas[appendChild](this.span);

add this:

this.span.innerHTML = E;

after function SetSize() definition add:

    setView = function (width, height) {
        var cs = this.canvas.style;
        var w = this.wrapper.style;
        width == +width && (width += \"px\");
        height == +height && (height += \"px\");
        cs.width = w.width = width;
        cs.height = w.height = height;
    return this;
    },

Done! I’m pretty sure I did break something somewhere, but so far everything I need works perfectly.

Result

So now to the point, why all this trouble?

Original

This is the SVG converted from Illustrator file (default size I had) 190px x 154px. (btw all those images below are screenshots, not SVG)

Safari

Internet Explorer

It’s not pretty in IE isn’t it?

Scaled up

Now the first trick, let’s scale that SVG twice the size: c.setView(“380px”, “308px”);

Safari

Internet Explorer

Nice, but IE is still ugly

Scaled down

And now The Big Prize, I did open that same file in Illustrator, resized it a lot and saved, the result is SVG with viewBox of “-170 531.889 1304.02 1061.449”. As you can see, Illustrator set x&y to not zero and I just cannot get it back to 0&0, so that’s why I implementd full (not just width & height) viewBox support. This is so big image in original that I show you only the downsized version: c.setView(“380px”, “308px”);

Safari

Internet Explorer

BOOM!

There you have it - SVG converter and nice VML in IE :-)

Demo

I set up a demo page with real SVG’s here

Download

My version of Raphaël and SVG to Raphaël converter.

Credits

Kudos to Vector Converter for the idea of wrapper group in VML. At least I got it from there…