and have really nice VML rendering in IE as a bonus.
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.
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.
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.
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.
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.
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.
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;
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 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:
clip:rect style of wrapper div is nixed
var create = function () {
var con = getContainer[apply](null, arguments),
container = con.container,
height = con.height,
s,
width = con.width,
x = con.x,
y = con.y;
if (!container) {
throw new Error(\"VML container not found.\");
}
var res = new Paper,
c = doc.createElement(\"div\"),
cs = c.style;
res.wrapper = c;
width = width || 512;
height = height || 342;
x = x || 0;
y = y || 0;
var vb = createNode(\"group\");
vb.coordsize = width + \" \" + height;
vb.coordorigin = x + \" \" + y;
vb.style.cssText = R.format(\"position:absolute;left:0;top:0;width:px;height:px\", width, height);
res.width = width;
res.height = height;
res.x = x;
res.y = y;
res.coordsize = vb.coordsize;
res.coordorigin = vb.coordorigin;
res.span = doc.createElement(\"span\");
res.span.style.cssText = \"position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;\";
c[appendChild](res.span);
cs.cssText = R.format(\"width:px;height:px;position:absolute;\", width, height);
c[appendChild](vb);
res.canvas = vb;
if (container == 1) {
doc.body[appendChild](c);
cs.left = x + \"px\";
cs.top = y + \"px\";
} else {
if (container.firstChild) {
container.insertBefore(c, container.firstChild);
} else {
container[appendChild](c);
}
}
plugins.call(res, res, R.fn);
return res;
};
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.
So now to the point, why all this trouble?
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?
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
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 :-)
I set up a demo page with real SVG’s here
My version of Raphaël and SVG to Raphaël converter.
Kudos to Vector Converter for the idea of wrapper group in VML. At least I got it from there…