Scripting With Java
This page explains how to manipulate the DOM tree of an SVG document from a Java program.
How to manipulate a document in a JSVGCanvas
The follow code template demonstrates how to manipulate an SVG document displayed in a JSVGCanvas directly from a Java program.
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) { // Perform some actions here... // ...for example start an animation loop: window.setInterval(new Animation(), 50); } } public class OnClickAction implements EventListener { public void handleEvent(Event evt) { // Perform 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() { // Perform some actions here... // ...for example displays an alert dialog: window.alert("Delayed Action invoked!"); } } }
Writing thread-safe code
The DOM listeners registered on the SVG document are called from the canvas update thread. To avoid race conditions, do 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() { public void run() { // Insert some actions on the DOM here } });
or:
// Waits until the Runnable is invoked canvas.getUpdateManager().getUpdateRunnableQueue(). invokeAndWait(new Runnable() { public void run() { // Insert some actions on the DOM here } });
Like with event listeners, when a Runnable is invoked from the update thread, the graphics are updated.
Using the SVG DOM
Batik provides a fairly complete implementation of the SVG DOM. The SVG DOM provides all the functionality most applications require. In particular, the DOM implements DOM Events, including mutation and UI Events. As an example, DOM events allow you to get notified when the cursor moves over elements of interest:
// Element of Interest. Element theElem = ...; ((EventTarget) theElem).addEventListener("mouseover", mouseOverListener, true);
where mouseOverListener implements the org.w3c.dom.events.EventListener interface. This interface consists of the method:
void handleEvent(Event evt);
This is called whenever the event occurs with the element it is registered on. It is worth reviewing the DOM Events specification as there are many useful features to this interface that are not immediately obvious.
It is also worth looking at the DOM interfaces defined by the SVG specification as they provide a large number of useful methods, in particular those of SVGLocatable:
// Returns Bounding box of SVG Element. public SVGRect getBBox ( ); // Returns the transformation matrix to the screen. public SVGMatrix getScreenCTM ( ); // Returns the transformation matrix to the given element. public SVGMatrix getTransformToElement ( SVGElement element ) throws SVGException;
In particular, getScreenCTM is very useful for taking the clientX and clientY attributes of the DOM UIEvent and mapping them to the coordinate system of an element in the SVG document:
SVGMatrix mat = elem.getScreenCTM(); SVGMatrix imat = mat.inverse(); SVGPoint cPt = document.getRootElement().createSVGPoint(); cPt.setX(uiEvt.getClientX()); cPt.setY(uiEvt.getClientY()); cPt = cPt.matrixTransform(imat);
Referencing Java code from a document
Batik implements the Java bindings for SVG, and thus allows Java code to be referenced from script elements. This feature is available to any application of Batik that uses the bridge module (for example the Swing component and the transcoders).
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 that contains the code to run.
The manifest of this jar file must contains an entry of the form:
SVG-Handler-Class: classname
where classname must be the name of a class that implements the org.w3c.dom.svg.EventListenerInitializer interface. Just before the document SVGLoad event is fired, an instance of this class is created, and this instance has its initializeEventListeners method invoked. Note that there is no way to specify Java handlers in event attributes on SVG elements, so having the initializeEventListeners call addEventListener on a node is the only way to attach a Java listener from within the document.
The class specified by SVG-Handler-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.