Generating JavaScript from SVG, an Intro to Code Generation — Part 3

Welcome back again to our introduction to code generation. Generating code from some non-code description is a big topic, so we’re focusing our attention for now on the specific problem of generating JavaScript code from an SVG drawing. In the last part, we managed to generate code that could not only draw rectangles, but also render them at different sizes and aspect ratios.

Just look at those black rectangles go

In this last part of the series, we’re going to make our code generator smart enough to handle colors. We’re working from the repository at https://github.com/Cycling74/codegen-eng-blog. If you want to pick up where we left off last time, you can checkout the step-3 tag, with something like this:

git checkout tags/step-3

For the grand finale, how would we make the rectangles have color? For this, we’re going to have to go back to the SVG file. If we make a drawing in Illustrator with colored rectangles, it looks like this:

Now we’ve got a really tricky situation on our hands. We’ve got an SVG file with inline CSS! However, in the same exact way that we parse SVG as XML, we can do just the same thing with CSS. The idea is, parse and walk through the CSS text. Every time you encounter a CSS class selector that has a fill property, create a function that will color our rectangle correctly. In our generated code we'll have something like this:

So the challenge is twofold:

  1. Create a styling function for each CSS class.
  2. Call that function before drawing each rectangle.

Parsing CSS

First, let’s install our CSS parsing library.

npm i --save css-tree @types/css-tree

Now let’s think about how to get to the CSS text. If you look at the SVG file, you can see that the actual CSS is just the text of a tag called style. Still, this is a bit tough for us. While htmlparser2 does let us define a function ontext for when we encounter the text of an XML tag, it doesn't give us an easy way of knowing whether that text belongs to a style tag or not. The best solution I can come up with is something like this:

Now in order to know how to handle each CSS class selector, it’s helpful to put the CSS into AST Explorer.

Let’s try saying this in English, then we’ll worry about code. Given a CSS Rule, look in its SelectorList for a ClassSelector. The name of that ClassSelector will be the name of our styling function. Within that same Rule, look for a Block with a series of Declaration. Get declarations with the property “fill” and extract their value as a HexColor.

Here’s what that looks like in code:

This relies on two helper functions createLegalName and hexColorToColorArray. The first, createLegalName, simply turns a CSS class selector into a legal name for a JavaScript variable. The second, hexColorToColorArray goes from a hex string like #fff or #ff00ff to [1.0, 1.0, 1.0, 1.0] and [1.0, 0.0, 1.0, 1.0] respectively (note that an alpha value has been appended automatically. The babel template that actually makes the style functions, makeStyleFunction, looks like this:

Adding Styling to the Rectangles

Now we need to modify our rectangle drawing code to be style aware. That’s as simple as adding a call to the styling function to the rectangle template, though we still want to be able to draw a rectangle without a style.

Now we need to modify our rectangle drawing code to be style aware. That’s as simple as adding a call to the styling function to the rectangle template, though we still want to be able to draw a rectangle without a style.

And then we just need to pass the styleFunctionName when we create the rect drawing statements.

Adding the Style Functions

The very last thing we need to do is add the style function definitions before the paint function. So, let’s add to the makePaintFunction template.

Nearly there now, just need to call the template function with the right argument.

Celebration

Just look at that beautiful JavaScript output.

Imagine what that would have been like to program by hand? And now any kind of SVG drawing you make (provided it’s colored rectangles and colored rectangles only) can be turned into JavaScript instantly.

Next Up

If we were continuing this series, we might think about how to add behavior to our generated drawing. One of the really appealing parts of generating code is that it lets you define behaviors without having to code that behavior explicitly. So we might ask how we could define an interface object, like a slider or a knob, using just an SVG image.

Believe it or not we aren’t the first people to think about this kind of thing. If you’re familiar with Max you might also know about VCV Rack, a program for simulating Eurorack modules. VCV supports custom, user-defined modules, complete with a code generation script that will turn an SVG drawing into an active interface. This scheme uses different colors to denote different functionality, with green for inputs and blue for outputs. If you wanted to go deep into SVG-based code generation, you might follow a similar strategy for denoting different behaviors.

For now, this is the end of our code generation journey. I hope it’s been useful for you-certainly I learned a ton about working with different text parsers, and about using Babel to generate code from an AST. Until next time!

We’re the engineers behind Max, the visual programming environment for sound and video. Follow for more programming stories, side projects and rabbit holes.