Several programs I’ve written in Java have had an image export componet to them. Up until now the export has always only been as a bitmap image. This is very easy to do using a BufferedImage and an ImageWriter. Given a Component (all AWT or Swing widgets) and a file you can create a PNG very simply:
BufferedImage b = new BufferedImage(c.getWidth(),c.getHeight(),BufferedImage.TYPE_INT_RGB);
Graphics g = b.getGraphics();
I’ve always thought though that it should be possible to create an SVG file from an arbitrary component, but never got round to trying it. However I got snowed in last week and decided to give it a go, and it turned out to be somewhat easier than I thought.
There is a natural fit between the abstract methods of the AWT Graphics class and the primitive components in the SVG spec – they even use the same coordinate system. Creating an SVG export class therefore simply requires implementing the SVG spec and translating the method calls to SVG code.
Although the basic premise is fairly straightforward – there were a few gotchas which had be scratching my head!
- The Graphics class has a create() method which returns a Graphics object. Initially I was just returning the same object each time and this seemed to work. With more complex nested objects though the coordinates were getting messed up. I worked out that it was important that each Graphics instance keeps track of its own translate coordinates as these are managed separately in each instance.
- Translate coordinates are relative and not absolute. When a new set of coordinates are passed via the translate() method these must be added to the current translation, and don’t replace it.
- Fonts are problematic. Size is fine, but font names are not trivially converted between the java font name and something SVG can understand. It may be possible to figure out some generic rules or use a translation table, but since my applications all use a default sans-serif font of varying sizes I’ve hardcoded this for now.
- Some methods, such as font metric creation are a bit of a pain to write – so I’ve cheated and created an unused BufferedImage Graphics object within my class of the appropriate size and pass any difficult method calls to that – I can then discard it at the end. This is slightly wasteful of memory, but is a quick fix.
- I coded my initial implementation on a Mac and it was all working. I then tried it on Windows and Linux and got empty SVG files. I resorted to a question on StackOverflow to figure out what was going on, and it turned out that the double buffering mechanism was causing the problems. If this was enabled then all I saw in my Graphics class was a single call to drawImage() with a complete bitmap in it. This was passed directly from the offscreen buffer, but meant I couldn’t intercept the individual shape method calls. I therefore now use the RenderManager to disable double buffering on the component before calling its paint method which ensures nothing comes between my class and the Graphics methods calls.
- Lots of code seems to assume that the object which gets passed to paint always implemets Graphics2D. I put a conditional check into my code for this, but it might be a good idea to implement (with null methods) the extra Graphics2D methods if this was to be used genrically.
At the end of all of this I now have a class which implements a single static method of:
public static String convertToSVG (Component c);
Which has worked in every instance I’ve tried. There are some methods I’ve not implemented – namely the paintImage methods and some of the drawPolygon methods. The polygon methods should be easy enough. The paintImage may be more problematic, but I don’t feel too bad leaving these out since there’s not much advantage to using SVG if you’re just going to stick a bitmap into it.
The SVG code will be in the 0.3 release of SeqMonk and I may release it as a stand alone package if anyone shows any interest in it.