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

Hi there! I recently wrote an ebook on web application security, currently sold on leanpub, the Amazon Kindle store and gumroad.

It contains 160+ pages of content dedicated to securing web applications and improving your security awareness when building web apps, with chapters ranging from explaining how to secure HTTP cookies with the right flags to understanding why it is important to consider joining a bug bounty program.

Feel free to skim through some of the free chapters published on this blog and, if the content seems interesting enough to you, grab a copy on leanpub, the Amazon Kindle store, gumroad or simply checkout right down below!

Buy the Web Application Security ebook for $6.99

In the mood for some more reading?

...or check the archives.