Developing SVG Applications with Batik

Thierry Kormann
ILOG

ILOG
Les Taissounières HB2
1681 route des dolines
06560 Valbonne
France
e-mail: tkormann@ilog.fr
fax: +33 4 92 96 61 62
webpage: http://www.ilog.com

Keywords: SVG, Batik, Toolkit, Developing, Applications

Abstract

The Batik project is an open-source project at the Apache Software Foundation. The project's ambition is to give developers a set of core modules that can be used together or individually to support specific SVG solutions. This paper will explain how to leverage the Batik components to develop JavaTM applications around SVG.

Introduction

Batik is a JavaTM technology based toolkit for applications that want to use images in the Scalable Vector Graphics (SVG) [1] format for various purposes, such as viewing, generation or manipulation. The toolkit provides a set of core modules such as a SVG Document Object Model implementation (SVG DOM) - the standard Application Programming Interface (API) defined the W3C/SVG Working Group - which lets developers create and manipulate SVG content, a transcoder module which provides an easy way to convert SVG documents into raster images, or the JSVGCanvas, a swing component that can be used in any application to render static or dynamic SVG content.

The goal of this article is to explain how to use the Batik modules to develop JavaTM applications around SVG. This paper contains three sections. The first section explains how to create or manipulate SVG content in an application. The second section describes how to convert SVG images to other formats such as JPG or PNG. The last section explains how to add SVG viewing capabilities to JavaTM applications.

Creating SVG content with Batik

Batik provides two modules which can be used together or individually to create and/or manipulate SVG content. This first module is the SVG Generator that allows a JavaTM application to export its graphics in the SVG format. The second one is an implementation of the SVG DOM, the standard API to manipulate SVG content.

The SVG Generator

On the JavaTM platform, all rendering goes through the java.awt.Graphics2D abstract class, which offers methods such as drawRect, fillRect, or drawString. There are specialized concrete implementations of this abstract class for each type of output, such as a monitor or a printer. The platform will automatically choose which concrete implementation to use depending on the rendering context. As a consequence, developers only have to deal with the generic API of the Graphics2D abstract class.

The Batik toolkit provides a new concrete implementation of the Graphics2D class called SVGGraphics2D. This new concrete implementation of the Graphics2D API creates SVG content instead of drawing to a screen or a printer. In other words, every time a JavaTM program invokes a rendering method such as drawRect, the SVGGraphics2D will generate its SVG equivalent (a <rect> element in that case) and append it to a DOM [2] tree. Finally, a sequence of rendering method invocations results in a DOM tree that represents the exact same graphic created by the programmer except that it's described in SVG. The following diagram (see Figure 1) shows the relationship between the SVGGraphics2D and its DOM tree.

SVG Generator Architecture Diagram
Figure 1: The SVG Generator Architecture Diagram

In order to see how the SVG Generator works, the following example illustrates how you can create an instance of the SVGGraphics2D and use it as a regular Graphics2D object to draw graphics. Then, it demonstrates how you can stream out the generated DOM tree (which is the in-memory representation of the SVG document and the graphics as well).

import java.awt.Rectangle;
import java.awt.Graphics2D;
import java.awt.Color;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.IOException;

import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.dom.GenericDOMImplementation;

import org.w3c.dom.Document;
import org.w3c.dom.DOMImplementation;

public class TestSVGGenerator {

    public void paint(Graphics2D g2d) {
        g2d.setPaint(Color.red);
        g2d.fill(new Rectangle(10, 10, 100, 100));
    }

    public static void main(String [] args) throws IOException {
        // Get a DOMImplementation
        DOMImplementation domImpl =
            GenericDOMImplementation.getDOMImplementation();
        String svgNamespaceURI = "http://www.w3.org/2000/svg";

        // Create an instance of org.w3c.dom.Document
        Document document = 
            domImpl.createDocument(svgNamespaceURI, "svg", null);

        // Create an instance of the SVG Generator
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);

        // Render into the SVG Graphics2D implementation
        TestSVGGenerator test = new TestSVGGenerator();
        test.paint(svgGenerator);

        // Finally, stream out SVG to the standard output using UTF-8
        // character to byte encoding
        boolean useCSS = true; // we want to use CSS style attribute
        Writer out = new OutputStreamWriter(System.out, "UTF-8");
        svgGenerator.stream(out, useCSS);
    }
}

In conclusion, the SVG Generator is a powerful tool that lets JavaTM programs export their graphics in the SVG format. Applications that want to use the SVGGraphics2D class do not require any modifications of the graphics code; they simply use a SVGGraphics2D instance rather than one of the built-in Graphics2D implementation.

Programmers using the SVG Generator can access the DOM tree to further manipulate it or can directly write the content to an output stream. The SVGGraphics2D class has been designed to allow customization in many different ways. For instance, users can choose the way they want styling to be generated (using the style attribute or XML presentation attributes), they can also change the size of the SVG document, or extend the generator to support custom graphic primitives. For further information on the SVG Generator, the Batik SVG Generator Tutorial [3] will help.

The SVG Document Object Model

"The Document Object Model is a platform- and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents. The document can be further processed and the results of that processing can be incorporated back into the presented page. This is an overview of DOM-related materials here at W3C and around the web."

-- The DOM Working Group (W3C)

In SVG, the DOM is used to represents a SVG document in memory. It's the standard API to create and manipulate elements such as a <rect> or a <cirle>. The SVG Working Group has designed an extension to the DOM called the SVG DOM. The SVG DOM is part of the SVG specification and provides additional methods, specific to SVG, that help SVG developers for instance to deal with the geometry of graphical objects, access the length of a string, or get the animated value of an attribute.

Batik provides an implementation of the SVG DOM that can be used to create or manipulate SVG content. As of Batik1.5beta2, all DOM features are implemented and a subset of the SVG DOM functionalities are supported. The following example shows how you can get the Batik SVG DOM implementation.

import org.w3c.dom.DOMImplementation;
import org.apache.batik.dom.svg.SVGDOMImplementation;

DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();

Using the DOMImplementation, it is now possible to create a Document. The example bellow illustrates how to create a SVG document by using the createDocument method with the SVG namespace URI and the local name of the SVG root element as parameters.

import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.w3c.dom.Document;

DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = "http://www.w3.org/2000/svg";
Document doc = impl.createDocument(svgNS, "svg", null);

Finally, using the Document object, developers can now create SVG content. In spite of the Batik DOM implementation is a SVG DOM implementation, the created Document supports both generic XML and SVG. The following example shows how to create a red rectangle located at (10, 20), with a size of (100, 50).

import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = "http://www.w3.org/2000/svg";
Document doc = impl.createDocument(svgNS, "svg", null);

// get the root element (the svg element)
Element svgRoot = doc.getDocumentElement();

// set the width and height attribute on the svg root element
svgRoot.setAttributeNS(null, "width", "400");
svgRoot.setAttributeNS(null, "height", "450");

// create the rectangle
Element rect = doc.createElementNS(svgNS, "rect");
rect.setAttributeNS(null, "x", "10");
rect.setAttributeNS(null, "y", "20");
rect.setAttributeNS(null, "width", "100");
rect.setAttributeNS(null, "height", "50");
rect.setAttributeNS(null, "style", "fill:red");

// attach the rectangle to the svg root element
svgRoot.appendChild(rect);

The example given has the following equivalent SVG:

<svg width="400" height="450">
    <rect x="10" y="20" width="100" height="50" style="fill:red"/>
</svg>

Finally, Batik provides several different ways to use a SVG DOM tree. Two modules can be immediately used to render your SVG Document: the Transcoder module (see section 'Rendering SVG content with Batik') or the JSVGCanvas (see section 'Building an application around SVG with Batik').

Rendering SVG content with Batik

The Batik toolkit provides a module called Transcoder. One of the main class is the ImageTranscoder that lets developers convert a SVG document to a raster image such as PNG or JPG. The ImageTranscoder takes a TranscoderInput and a TranscoderOutput, which are respectively the input data to transcode and the output into which the resulting data will be stored. The transcoder supports different types of input such as an InputStream, a Document, or a Reader and different types of output such as an OutputStream, or a Writer. The following example is using the PNGTranscoder and shows how to transform a SVG document to a PNG image.

import java.io.*;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;

// Create a PNG transcoder
PNGTranscoder transcoder = new PNGTranscoder();

// Create the transcoder input
String svgInputURI = ...;
TranscoderInput input = new TranscoderInput(svgInputURI);

// Create the transcoder output
OutputStream ostream = ...;
TranscoderOutput output = new TranscoderOutput(ostream);

// Transform the svg document into a PNG image
transcoder.transcode(input, output);

// Flush and close the stream
ostream.flush();
ostream.close();

Developers can use the TranscodingHints class to control the rendering of the SVG document to convert (such as the CSS media to use or the CSS alternate style sheet to consider), or various options for the resulting image (such as the encoding quality of the JPG, the background color for image formats that do not support transparency, or the size of image). To set or change transcoding hints, users can construct a new TranscodingHints object and pass it to the ImageTranscoder. If only one or two transcoding hints have to be specified, an addTranscodingHint method is available on the ImageTranscoder itself. For example, to set the encoding quality of a JPG, the following code can be used on a JPEGTranscoder:

// Create a JPG transcoder
JPEGTranscoder transcoder = new JPEGTranscoder();
transcoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(.8));
// ...

Or in order to control the size of the image, the following code can be used on any ImageTranscoder:

// Create an ImageTranscoder
ImageTranscoder transcoder = new ...;
transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, new Integer(100));

Furthermore, the ImageTranscoder can also deal with dynamic SVG content. A transcoding hint KEY_EXECUTE_ONLOAD lets the user choose when the rendering of the SVG document must be done. The rendering can occur before or after the dispatch of the onload event. In other words, if the rendering happens after the dispatch, the onload event handlers will be executed first. In conclusion, SVG content can be modified by script before the rendering. The following example shows a simple dynamic SVG document, the resulting image (see Figure 2) and the code that has been used to produce the image.

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     width="300" height="300" viewBox="0 0 300 300">

    <script type="text/ecmascript"><![CDATA[
    function build(evt) {
        var document = evt.target.ownerDocument;
        var svgNamespaceURI = "http://www.w3.org/2000/svg";
        var e = document.createElementNS(svgNamespaceURI, "rect");
        e.setAttributeNS(null, "x", "50");
        e.setAttributeNS(null, "y", "50");
        e.setAttributeNS(null, "width", "200");
        e.setAttributeNS(null, "height", "200");
        e.setAttributeNS(null, "style", "fill:crimson");
        evt.target.appendChild(e);
    }
   ]]></script>
   <g onload="build(evt)" />
</svg>

The resulting image after the dispatch of the 'onload' event
Figure 2: The resulting image after the dispatch of the 'onload' event

// Create an ImageTranscoder
ImageTranscoder transcoder = new ...;
transcoder.addTranscodingHint(ImageTranscoder.KEY_EXECUTE_ONLOAD, Boolean.TRUE);
// ...

To summarize, the transcoder module provides an API for converting SVG document to other formats. The ImageTranscoder is a transcoder that is responsible for converting static or dynamic SVG content to a raster image such as PNG or JPG. Several options, called TranscodingHints, can be specified to control or modify how the SVG document is transformed. This module can be used by both client-side and server-side applications, each time SVG cannot be handled natively.

Building an application around SVG with Batik

The Batik toolkit provides a module called the JSVGCanvas, a swing component that can be used to display static or dynamic SVG documents. With the JSVGCanvas, developers can easily display SVG documents (from a URI or a DOM tree) and manipulate it - such as rotating, zooming, panning, selecting text, or activating hyperlinks. First this section explains how to create a JSVGCanvas and integrate it in a Swing application. The rest of this section tells you how to accomplish some common SVG canvas-related tasks such as how to track all events that occur while rendering a SVG document, or how to manipulate your SVG document using the JavaTM language.

Creating a JSVGCanvas

The JSVGCanvas is a Swing component that follows the Swing design rule[4]. It means that the component is not thread safe and all operations must be done as described in the swing tutorial. The JSVGCanvas is also a JavaBean so it can be used in visual application builders. The following example shows how developers can easily create and use the JSVGCanvas component (see also Figure 3).

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;

import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderAdapter;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;

public class SVGApplication {

    public static void main(String[] args) {
        JFrame f = new JFrame("Batik");
        SVGApplication app = new SVGApplication(f);
        f.getContentPane().add(app.createComponents());
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        f.setSize(400, 400);
        f.setVisible(true);
    }
    
    JFrame frame;
    JButton button = new JButton("Load...");
    JLabel label = new JLabel();
    JSVGCanvas svgCanvas = new JSVGCanvas();

    public SVGApplication(JFrame f) {
        frame = f;
    }

    public JComponent createComponents() {
        final JPanel panel = new JPanel(new BorderLayout());
        JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
        p.add(button);
        p.add(label);
        panel.add(p, BorderLayout.NORTH);
        panel.add(svgCanvas, BorderLayout.CENTER);

        // Set the button action.
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                JFileChooser fc = new JFileChooser(".");
                int choice = fc.showOpenDialog(panel);
                if (choice == JFileChooser.APPROVE_OPTION) {
                    File f = fc.getSelectedFile();
                    try {
                        svgCanvas.setURI(f.toURL().toString());
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });

        // Set the JSVGCanvas listeners.
        svgCanvas.addSVGDocumentLoaderListener(new SVGDocumentLoaderAdapter() {
            public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
                label.setText("Document Loading...");
            }
            public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
                label.setText("Document Loaded.");
            }
        });

        svgCanvas.addGVTTreeBuilderListener(new GVTTreeBuilderAdapter() {
            public void gvtBuildStarted(GVTTreeBuilderEvent e) {
                label.setText("Build Started...");
            }
            public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
                label.setText("Build Done.");
                frame.pack();
            }
        });

        svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
            public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
                label.setText("Rendering Started...");
            }
            public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
                label.setText("");
            }
        });

        return panel;
    }
}

The SVG Application running
Figure 3: The SVG Application running

Event handling mechanism

As shown in the previous example, each time you set a URI or a SVG DOM tree to the JSVGCanvas (using the setURI or the setSVGDocument method), the specified document is first parsed (in case of a URI), built, rendered and optionally updated. The proper way to be notified of those different phases is to implement a listener and attach it to the component. There are five types of listener:

Scripting with JavaTM

The Batik toolkit provides an easy way to script SVG documents using the JavaTM language. In the previous section, we have seen how to display a SVG document; this section is about how to manipulate a SVG document currently displayed with the JSVGCanvas. The following example demonstrates how to manipulate a SVG document. Note that developers do not have to worry about the graphical updates, after each event listener invocation the canvas is updated if needed.

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherAdapter;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent;
import org.apache.batik.script.Window;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;

public class SVGApplication {
    public static void main(String[] args) {
        new SVGApplication();
    }

    JFrame frame;
    JSVGCanvas canvas;
    Document document;
    Window window;

    public SVGApplication() {
        frame = new JFrame();
        canvas = new JSVGCanvas();
        // Forces the canvas to always be dynamic even if the current
        // document does not contain scripting or animation.
        canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
        canvas.addSVGLoadEventDispatcherListener
            (new SVGLoadEventDispatcherAdapter() {
                    public void svgLoadEventDispatchStarted
                        (SVGLoadEventDispatcherEvent e) {
                        // At this time the document is available...
                        document = canvas.getSVGDocument();
                        // ...and the window object too.
                        window = canvas.getUpdateManager().
                            getScriptingEnvironment().createWindow();
                        // Registers the listeners on the document
                        // just before the SVGLoad event is dispatched.
                        registerListeners();
                        // It is time to pack the frame.
                        frame.pack();
                    }
                });
        frame.addWindowListener(new WindowAdapter() {
                public void windowOpened(WindowEvent e) {
                    // The canvas is ready to load the base document
                    // now, from the AWT thread.
                    canvas.setURI("doc.svg");
                }
            });
        frame.getContentPane().add(canvas);
        frame.setSize(800, 600);
        frame.show();
    }

    public void registerListeners() {
        // Gets an element from the loaded document.
        Element elt = document.getElementById("elt-id");
        EventTarget t = (EventTarget)elt;
        // Adds a 'onload' listener
        t.addEventListener("SVGLoad", new OnLoadAction(), false);
        // Adds a 'onclick' listener
        t.addEventListener("click", new OnClickAction(), false);
    }

    public class OnLoadAction implements EventListener {
        public void handleEvent(Event evt) {
            // Make some actions here...
            // ... for example start an animation loop:
            window.setInterval(new Animation(), 50);
        }
    }

    public class OnClickAction implements EventListener {
        public void handleEvent(Event evt) {
            // Make some actions here...
            // ... for example schedule an action for later:
            window.setTimeout(new DelayedTask(), 500);
        }
    }

    public class Animation implements Runnable {
        public void run() {
            // Insert animation code here...
        }
    }

    public class DelayedTask implements Runnable {
        public void run() {
            // Make some actions here...
            // ... for example displays an alert dialog:
            window.alert("Delayed Action invoked!");
        }
    }
}

The DOM listeners registered on the SVG document are invoked from the canvas Update Thread. To avoid race conditions, developers must not manipulate the DOM tree from another thread. The way to switch from an external thread to the canvas update thread is to use the following code:

// Returns immediately
canvas.getUpdateManager().getUpdateRunnableQueue().
    invokeLater(new Runnable() {
       // Insert some actions on the DOM here
    });
// Waits until the Runnable is invoked
canvas.getUpdateManager().getUpdateRunnableQueue().
    invokeAndWait(new Runnable() {
       // Insert some actions on the DOM here
    });

Similar to regular event listeners, when a Runnable is invoked from the Update Thread, the graphics are updated.

Batik also provides an extension of the SVG <script> element that allows JavaTM programs to be executed from a SVG document. This feature is available to any application of Batik that uses the bridge module (for example the JSVGCanvas and the ImageTranscoder module). In order to use this extension, the 'type' attribute of a <script> element must be set to application/java-archive. In addition the xlink:href attribute must be the URI of a jar file which contains the program to run. The manifest of this jar file must contains an entry of the form:

Script-Handler: <classname>

Where <classname> must be the name of a class which implements the org.apache.batik.script.ScriptHandler interface. This class can be contained directly in the jar file, but it is also possible for it to be contained in a jar file added to the classpath using the Class-Path entry of the manifest.

Summary

In this paper, we have seen how developers can use the Batik toolkit to create, manipulate or view SVG content. The Batik modules have been designed to be extensible and easy to use and JavaTM programmers should now be ready to start writing either client-side or server-side applications around SVG. Furthermore, the Batik Project is an open volunteer project in the spirit of the Apache Software Foundation (ASF). This means that there are many ways to contribute to the project, either with direct participation (coding, documenting, answering questions, proposing ideas, reporting bugs, suggesting bug-fixes, etc..) or by resource donation (publicity, hardware, software, conference presentations, speeches, etc...). Applications that use the Batik modules, such as tools or extensions, are of special interest to the project, so feel free to participate to this effort by using the Batik mailing list - batik-users@xml.apache.org.

References

[1] "The official SVG page at W3C", SVG Working Group. Available at http://www.w3.org/Graphics/svg.

[2] "The Document Object Model", DOM Working Group. Available at http://www.w3.org/DOM.

[3] "The Batik SVG Generator Tutorial", Batik Team. Available at http://xml.apache.org/batik/svggen.html.

[4] "Creating a GUI with JFC/Swing", Swing Team (Sun Microsystems). Available at http://java.sun.com/docs/books/tutorial/uiswing/overview/threads.html.


Valid XHTML 1.0!