Recipe for SVG to PNG with svg2png

The SVG files generated by Lineform have a viewBox attribute and no width and height attributes on the outermost svg element. This is good because it means that is necessary to get Webkit browsers (at least) to treat them as scalable (apparently the S in SVG was not enough of a hint). Alas! the svg2png utility I want to use to downgrade SVG files to PNG requires width and height attributes or it assumes nonsensical values. Here’s my silly recipe for achieving this without having two copies of every SVG file.

Background: Scaling SVG

Hundreds of thousands of years ago when SVG was first tried, the most used implementation was Adobe’s SVG Viewer plug-in (which Adobe dropped when Mozilla changed one of its APIs so that it stopped working). You embed SVG in a web page using the embed tag, like so:

<embed src="http://static.alleged.org.uk/logo/logo.svg"
    type="image/svg+xml" width="120" height="120" />

The SVG recommendation has rules for scaling the contents of an svg element: it may be shrunk to fit within the viewport (that is, the width and height specified), expanded to cover the viewport, or scaled anamorphically. Adobe’s plug in took the viewport size from the embed tag, even if width and height are specified on the SVG file's root element.

When Mozilla belatedly added SVG support to Gecko (and hence Firefox), they took a different approach from Adobe’s. The width and height attributes of the embed tag no longer set the viewport; instead the width and height of the SVG document are used. The result is that if the above logo were to have its width and height specified as 512 then what you see embeded would be only its top corner:

When Webkit gained SVG support they followed this new convention.

In order to use my graphic at a variety of sizes, I either need to have a different file for each size, or I can instead omit the width and height attributes, in which case Mozilla and Webkit assume the SVG graphic should fill the viewport (which is usually what you want).

Background: svg2png

The command-line program svg2png is fallout of the Cairo project. On Mac OS X, I can install it by installing Homebrew and doing

brew install svg2png

Doubtless you can install it using your favourite package manager with some effort. (Also, there are dozens of other apps also called svg2png.)

I should be able to generate a 275×100 PNG of the word ‘alleged’ from the SVG by doing

svg2png -w275 -h100 alleged.svg alleged-275x100.png

But without width and height attributes in the file, it seems that svg2png assumes the natural size of the SVG document is 450×450. The logo ends up squashed up in to a square in the middle of the image. You can fix this by setting the width and height attributes to match the width and height of the viewBox attribute.

This leaves us with the nasty requirement to have two copies of every SVG file: one with those attributes and one without.

Workaround

Here’s how I have worked around this problem. I store the SVG files without width and height. This is conveniently how they are generated by Lineform, and how they need to be for the web server.

When generating the PNG versions for legacy browsers, I use this incantation:

sed  -e 's/viewBox="0 0 \([0-9.]*\) \([0-9.]*\)"/& width="\1" height="\2"/' \
    < alleged.svg | svg2png -w275 -h100 > alleged-275x100.png

This creates a temporary version of the document with the attributes inserted, and exploits svg2png's ability to operate as a filter.

Moral

There are two under-appreciated aspects of Unix in play here.

  1. Using plain-text file formats wherever possible means that you can fix problems using generic tools like sed and awk.

  2. Converters that can read from standard input and write to standard output—called a filter in Unix parlance—allow you to fix things by stacking commands in a pipeline without even needing create temporary files.

The SVG format has annoyed some people by being based on XML rather than binary. This is an example of how being an application of XML, itself an application of plain text, is invaluable when things go wrong.