Generating SVGs with RaphaelJS and NodeJS

One of these mornings, in need of generating images with “copyable” text with Node, I had the chance to play around with RaphaelJS.

Where to start?

The idea is very simple, as raphael is basically just a simple API to draw SVGs on a canvas, and it does that on a browser / DOM — here you can see a map of Australia, from one of their examples:

Created with Raphaël 2.1.2

The problem is, how do you generate those images in a simple way from the server? There is no notion of DOM and window until you really deliver your response to the client, right?

A simple approach

It all starts from one docker container that runs (sorry :-P) IO.js:

1
2
3
4
5
6
7
FROM iojs:2.0

RUN npm install -g nodemon
COPY . /src
WORKDIR /src

CMD iojs index.js

At this point, you need to do simply create an index.js that will run our server and npm install 2 dependencies, raphael and jsdom.

Now, jsdom will be taking care of creating a virtual browser window and environment for raphael which, as soon as you require it, will be trying to access some global objects that are only available on a browser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var http = require('http')
var jsdom = require('jsdom')

// create the server
http.createServer(function(req, res){
  // on every request, create a virtual window
  jsdom.env(
    "<html></html>",
    [],
    function (errors, win) {
      if (errors) {
        throw errors;
      } else {
        // create / override the global window
        // object, as raphael will access it
        // globally
        global.window = win
        global.document = win.document
        global.navigator = win.navigator

        var raphael = require('raphael')
        raphael.setWindow(win)

        // start drawing some stuff with raphael,
        // which will write to the virtual "window"
        var paper = raphael(10, 30, "100%", "100%");
        var text = paper.text("50%", 40, "Some text");

        // now, to generate a plain SVG,
        // let's just strip the HTML tags
        // surrounding it
        svg = win.document.documentElement.innerHTML
        svg = svg.replace('<head></head><body>', '')
        svg = svg.replace('</body>', '')

        // let's serve the plain SVG, so that
        // it can be embedded in HTML pages with
        // <object type="image/svg+xml" data="http://localhost:3000/">
        res.writeHead(200, {"Content-Type": "image/svg+xml"})
        res.write(svg);
        res.end();
      }
    }
  );
}).listen(3000)

Now, the code is ghetto as hell, and that’s the way it’s supposed to be in a sample post like this one — you will definitely ned to address a couple things if you want to do more complex things:

This would probably be a good place to start to create a dev-friendly interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// index.js
raphael.initialize()

raphael.on('ready', function(){
  http.createServer(raphael.draw).listen(3000)
})

// raphael.js
raphael.initialize = function(){
  return dom.initialize()
}

raphael.draw = function(req, res){
  dom.createWindow().then(function(win){
    var R = require('raphael')
    R.setWindow(win)
    // do some real stuff with raphael
    // ...
    res.end()
  });
}

// dom.js
dom.initialize = function(){
  dom.createWindow().then(function(win){
    // ...
    global.window = win
    global.document = win.document
    global.navigator = win.navigator

    dom.emit('ready')
  })
}

dom.createWindow = function(){
  return Promise(function(resolve, reject){
    jsdom.env("<html></html>", [], function(errors, win){
      // ...
      resolve(win)
    })
  });
}

(note that this example assumes you handle errors on your own and have been used to promise libraries such as Q or Bluebird)

To wrap up

So yeah, it’s definitely simple to start generating SVGs from the server through Raphael, though the approach is quite hackish — and, to be honest, you might find even more gotchas since it’s running on a virtual DOM / window.

But, at the same time, this gives you an idea on how feasible something like this can be — for example, do you know those badges you see in a lot of github repos? They come from shields.io, that empowers a similar approach, although it’s simpler but more structured (they generated SVGs from templates, which I think it’s a pretty solid and robust idea).

In one of the next posts I am actually planning to show a simple way to roll your own badge generator out. Not that you should, but it’s definitely nice to see how these things work :)

Notes
  1. The ideal solution would be to inject the window to Raphael, but I dont think that’s currently possible, see: https://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js#L400 but correct me if I’m wrong
comments powered by Disqus