Java Applications with Apache Batik
Page
The goal of this presentation is to provide people with enough
information to do "interesting" things with SVG and Batik. This
means going beyond just showing SVG but really interacting with
it and hopefully enabling surprising things.
In order to accomplish this, the presentation will attempt to
give you a glimpse into some of the inner structure of the Batik
components.
A few conventions, I commonly abbreviate the full Batik package:
"org.apache.batik" as just
"batik", to save space. I will
occationally use "[...]" to
indicate that something was removed/abbreviated.
SVG 1.0 became a W3C Recommendation (Standard) in September
2001. SVG 1.1 (profiles 1.0) was released in January
2003. SVG 1.2 is currently in Working Draft.
SVG can represent complex graphics as a combination of
Raster (images like PNG, JPEG) and vector graphics (lines
arcs, polygons etc). It also allows the application of
filters to these primitives to get sophisticated results.
SVG graphics can be rendered at multiple resolutions, so
graphics can look good at high and low resolutions (no
more blocky effects when printing Web graphics!).
SVG being based on XML allows for scripting using any
language with a DOM binding. SVG also defines a set of
events that allow for complex responses to user actions,
such as changing the color of a graphic element (e.g., a
rectangle) when the mouse moves over it.
SVG also supports SMILE animation. This allows one
to declaratively define animations, based on time cues, or in
response to user actions.
It is important to emphasize the "toolkit" nature of Batik.
"Squiggle" the SVG browser gets a lot of attention, and it is
a very cool piece of the the toolkit, but it is only one
piece. The intent of Batik is not to be the ultimate SVG
browser (replacing Adobe, or Corel) but to provide the tools
people need to deploy SVG in "the real world".
Things like the SVG transcoders and generators, font converters
are important pieces in the whole SVG puzzle. They provide
support for SVG workflows.
Batik has been built to be as strictly conformant to the SVG
specification as possible. It is very important for users to
have multiple reference points on content compliance. As a
toolkit, some of the browser pressure to "do what I mean" is
gone.
The "ttf2svg" application converters all or part of a TrueType
font to an SVG Font. People do need to be careful and pay
attention to Font Licenses.
The "wmf2svg" application converts Windows Meta Files to an
SVG file. There are many variants of WMF, and the converter
does not handle all of them but it does handle most of them.
Additions and corrections are always welcome.
It seems that some people are making use of the Batik CSS
Engine. Either just the SAC (CSS parser) or the complete "DOM
Style" implementation.
The SVGGraphics2D is an implementation of Graphics2D that
generates SVG. This makes it really easy to generate SVG
content from existing Java applications.
The SVGGraphics2D is a very commonly used component from
Batik, but there are three really common mistakes people make.
First, people assume that because they provide a
Document to the
SVGGraphics2D constructor, it
automatically appends elements to that document. This is not
the case, the Document passed in
is used as an element factory. To get the content tree, you
need to call "getRoot()."
Second, unless you set the SVG canvas's size, it defaults
width and height to 100%. This means that the document has no
preferred size; this is generally not what you want.
Third, if you are going to use the SVGGraphics2D with a Swing
component, you should use the
"batik.svggen.SwingSVGPrettyPrint"
class; it handles many tricky issues with rendering Swing
components.
In this section of the talk, I will cover how to load SVG and
prepare it for display.
The first method I will cover is using the Transcoder APIs to
generate traditional Raster formats (JPEG, PNG, TIFF). The
second method is using the JSVGCanvas to view the SVG
interactively in a Swing component. The third and last will
be using the various loading/processing packages directly to
get a BufferedImage.
This example is taken straight from the Batik Web site which
offers a pretty good tutorial of these APIs, which it would
be pointless to recite here.
One thing to notice, however, are the transcoder hints. These
are how you control the rasterization process. This lets you
adjust the size and region rendered, as well as provide
stylesheets, etc. Each transcoder can also provide format
specific hints, such as the JPEG "quality" setting above.
These APIs are designed to make it easy to rasterize web
icons, buttons, menu's, etc. This service is, in fact,
available as an Ant task, and at least one of the Apache Site
Builders (Forrest) uses Batik to generate PNG images from SVG.
For simple viewing of SVG, the JSVGCanvas is the simplest
solution. The Canvas has methods to accept DOM Documents in
addition to URI Strings.
The JGVTComponent can be used to render a GVT tree. It may be
helpful to know that "GVT" stands for "Graphics Vector
Toolkit." The GVT is the package of classes that form the
actual rendering tree for an SVG document. The JGVTComponent
can only render static content. The GVT package will be
discussed more a little later.
The JSVGComponent contains essentially all the display
functionality for SVG, including support for dynamic updates.
However, it has a minimal amount of "user interface."
The JSVGCanvas is essentially a drop in SVG Browser. This may
offer more interaction capabilities than you really want/need
the user to have (zoom, pan, link traversal, etc).
The JSVGViewerFrame is the frame used by the Squiggle browser.
It has a few dependencies on other SVG browser classes and
resources but may be a good starting point if you want to
embed a complete SVG browser in your application.
This diagram shows the basic structure of Batik.
On the left are the various XML handling classes.
In the middle are the bridge/transcoding classes.
On the right are the rendering and display classes.
It is worth noting that the XML/DOM classes are independent of
the Rendering & Display classes and vise versa. This is
accomplished by having the Batik DOM implementation define
interfaces for sources of geometric information. The bridge
package then implements these interfaces by using the GVT
classes.
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
This example shows how to load an SVG document from a URI and
then how to build the rendering (GVT) tree.
The User Agent class handles error display and lots of other
rendering info. More on this class later.
The DocumentLoader loads SVG files from URIs. It also caches
documents. Within the Transcoder and JSVGCanvas, the
DocumentLoader is normally newly created for each top level
document loaded. However, at least for JSVGCanvas, you can
provide the DocumentLoader it uses - so referenced SVG files
can be cached in memory across multiple top level documents.
The BridgeContext manages the processing of the SVG Document.
For dynamic documents, it stores lots of useful information,
such as the mapping between GVT nodes and DOM nodes.
The GVT's GraphicsNode interface can be used directly. In
particular, it has a
"paint(Graphics2D)", which will
paint that node and its children to the given Graphics2D, as
well as a "getBounds()" method -
which returns the "painted" bounds of the node.
import org.apache.batik.bridge.ViewBox;
import org.apache.batik.gvt.renderer.DynamicRenderer;
The ImageRenderers are designed to handle the rendering of a
GVT tree to an offscreen image. For simple use cases, you
could just create your BufferedImage and call
"createGraphics()", and pass that
to the Paint method on the GVT tree (with an appropriate
transform).
The DynamicRenderer is actually faster for "one off" rendering
scenarios. The StaticRenderer caches image data in tiles for
future rendering requests. This can be a huge help when
panning around a large static document but is useless if you
are only going to render the document once or if the document
will be changing.
The ViewBox class handles the
"viewBox" and
"preserveAspectRatio" attributes
on the SVG element, and it constructs an Affine Transform to
map the viewBox (if there is one) to the provided image W/H.
The "repaint(Rectangle)" method
is what actually does the rendering of the SVG
document. Calling
"getOffScreen()" returns the
offscreen image; the renderer is using for rendering. It is
worth noting that the renderer can operate in single and
double buffer modes.
If you are planning to use Batik in your application, you
should look at the UserAgent classes and customize them to
your needs. The JSVGCanvas forwards most of the bridge
UserAgent class calls to the SVGUserAgent.
Batik provides Adapter classes for both the Bridge UserAgent
and the swing SVGUserAgent interfaces. These provide
reasonable implementations for most of the methods. For the
SVGUserAgent, two adapters are provided: one that sends error
messages to System.err/out and one that outputs to dialogs.
The most common reasons to implement/override these interfaces
is probably to control which XML parser is used.
The JSVGCanvas and the BridgeContext support three classes of
documents; static, interactive and dynamic. As you might
expect, the more dynamic the document type the more overhead
involved in constructing and maintaining the document.
It is at least theoretically possible to dispose of the DOM
tree for static documents (all rendering state is held in the
GVT tree). This never actually happens in normal usage, as
the JSVGCanvas interface offers access to the
document being displayed, and the
transcoders store a reference as well. However, building a
static document is still lighter weight than an interactive
document (no listeners are registered).
For both Interactive and Dynamic, the DOM tree must stay
around, because they use the DOM tree to handle event
propagation - for links and cursors. However, an Interactive
tree build is only slightly more expensive than a static one.
For a Dynamic build, there is quite a bit of additional
overhead, as an additional object is created for each rendered
Element in the DOM tree to track and update the GVT tree as
changes happen. These objects are available from a Batik
proprietary method called
"getSVGContext()" on all SVG
Elements. The SVGContext
interface provides information to support the
SVGLocatable interface. SVG
nodes that have more sophisticated interfaces (text for
example) define subclasses that provide these additional
methods. The Bridge package then implements these interfaces
using the GVT package.
JSVGCanvas receives events from AWT. It forwards the events
to the GVT tree, which calculates which graphic element is the
target of the event and then dispatches the requisite event or
events (mouseover/out when the graphic element changes). The
event model here is very simple.
The Bridge listens for these events and creates similar events
in the the associated DOM tree. For dynamic documents, these
events trigger script or Java code to run.
DOM events are dispatched in two passes: capture and bubble
(in that order). During the capture phase, the event trickles
down the DOM tree from the root node to the target of the
event.
DOM defines a fairly rich event structure. SVG extended this
to include a bunch of additional event types, specific to SVG.
The list above includes the most commonly used event types but
one should read the SVG/DOM specifications to get a complete
understanding of what events are available.
To register event listeners, first you need to get an element.
The easiest way to get an element is with
"getElementById(String)". This
can be expensive, as it walks the tree looking for a matching
element, so for "fixed" elements it is best to do the lookup
once and cache the result.
Then you need to call
"addEventListener" (you many need
to cast the element to an
EventTarget). The first
parameter is a string indicating what event type to register
for, the second is the listener object, and the third is when
to call the listener - during capture or bubble.
The third argument to addEventListener controls if the
listener is registered for capture or bubble. All of the
event attributes for SVG are registered on bubble. For Java
programmers, this doesn't mean much as the event attributes can
only contain "script" content, not Java code.
For those unfamiliar with DOM events, they have two similar
attributes: "target" and
"currentTarget." The
"target" attribute always points
at the element that originated the event. The
"currentTarget" is updated as the
event is dispatched to the various elements in the document
tree.
This just shows the order of notifying listeners is just
reversed for the Bubbling phase.
The DOM event object has two useful methods called
"stopPropagation()" and
"preventDefault()".
"stopPropagation()", prevents the
event from being dispatched to any other elements in the tree
although, dispatch completes for the current element.
"preventDefault" for cancelable
events prevents the "default action" from occuring (the most
common example of this is link activation for click events)
the event does continue to propagate.
At first blush, this can seem a little excessive or an odd
system for event dispatch. However, it in fact mirrors many
of the standard procedures for event dispatch in most GUI
systems, where the frame and other container objects get first
chance at an event as it travels down the component tree. If
the leaf component does not want the event, it moves up the
component hierarchy back to the window frame.
If you want to use Java code to interact with the SVG DOM, you
must go through the UpdateManager. This
is the most common mistake people make. The most common
symptom of this mistake is that the canvas does not update
unless you move the cursor over the canvas.
The Update Manager is needed to serialize access to the DOM.
It is essentially impossible to synchronize the DOM, as many
atomic actions require numerous calls to complete. The
example I always give is that creating an SVG rectangle
requires around 7 calls,
"createElementNS", set x, y,
width, height, and fill/stroke colors, then finally adding the
element to the DOM tree.
Rather than introduce something like "lock", "unlock" calls,
which are really error prone and not part of DOM, we took an
approach similar to Swing and restrict, by convention, all
access to the DOM to one thread of execution.
The DOM event listeners should always be called from the
Update Manager thread, so you don't have to do anything
special from within a DOM event listener.
This diagram shows how a Runnable, given to the Update Manager
typically interacts with Batik.
The UpdateManager runs the runnable which typically makes a
bunch of DOM calls, usually causing mutation events, which the
bridge is listening for. When the bridge receives mutation
events, it updates the GVT tree (setting path, paint, or
whatever).
When nodes in the GVT tree are modified, they generate
GraphicsNodeChangeEvents, which the UpdateTracker listens for.
The UpdateTracker accumulates a list of "dirty regions" that
need repainting.
When the Runnable finishes, the UpdateManager queries the
UpdateTracker to see if there are any dirty regions; if their
are, it triggers a repaint of the affected areas in the
JSVGCanvas.
This last part is why directly modifying the DOM does not
cause the screen to be updated. There is no trigger for the
repaint (no one checks the UpdateTracker) until a mouse event
is sent to the DOM in an UpdateManager runnable.
The JSVGCanvas has two mechanisms outside of standard SVG for
user interaction and for decorating an SVG document:
Interactors and Overlays.
The Overlay interface is really simple; it has one method
"paint(Graphics g)" that gets
called after any part of the SVGDocument is repainted. This
is typically used to simply highlight a region on the canvas
but can be used to draw almost anything.
The Interactor interface is also fairly simple; it has methods
to indicate that it wants to start/end a modal interaction,
and it extends AWT Key and Mouse Listeners. It is common for
an Interactor to have an associated Overlay that it uses to
display user feedback.
These interfaces are used for the built-in pan, zoom, rotate
"tools", and text selection.
In general, using SVG directly is the most flexible and
powerful solution and, if done with JavaScript, can be used
with any SVG viewer. However, it has some drawbacks, perhaps
the largest is speed.
When an area of the SVG tree changes, everything that
intersects with that area must be repainted from the original
document. However, because the overlay is always drawn on top
of the SVG, it can cheat, when the overlay changes it can
just repaint the SVG from the offscreen buffer and then redraw
the overlay. For complex SVG documents, this is
much faster.
Interactors are inherently modal. By contrast, the DOM event
model is normally nonmodal (you can make it modal but it takes
at least a little work). So can be easier to use an
Interactor than to try and deactivate the normal processing.
Finally, it can be cleaner; it does not involve adding event
handlers and new elements to the DOM tree. Thus, your
document can continue to be the graphics and not get overly
mixed up with the UI.
"getOverlays()" returns a
standard List containing all of the overlays. To add your
overlay, just add it to the list. This also allows you to
order the Overlays; they are drawn in the order of the list.
Similarly, "getInteractors()"
returns a standard List containing all of the Interactors. To
add your Interactor, just add it to the list. Your Interactor
will then have it's
"startInteraction" method called
for each InputEvent. If it returns true, then all events will
be routed to your Interactor. This continues until you return
true from "endInteraction(),"
which is called after each event is dispatched.
When you disable interactions, you are shutting off all the
Interactors.
When your Overlay changes, it is up to you to request a
repaint of the affected region of the component. You can use
the standard AWT calls to do this:
"canvas.repaint(Rectangle)."
SVG is a really powerful standard. It does more than just
about any previous graphics format, and it does it in a
nice open way using existing technologies (XML, DOM, Web).
Batik makes it trivial to integrate this powerful standard
into your applications. This can be for something as simple
as Pie Chart generation or as complex as gene-sequence display
and analysis.
Even if you don't think you need SVG, you may want to look at
Batik as it embeds a really powerful graphics engine, doing
many things that are needed for any application working with
complex graphics (event handling, repaint management, etc).
Batik is a large toolkit, and there are many parts that are
not really covered in this presentation (SVG Generator, font
converter, etc). So, if you didn't see what you were
interested in, please check out the Batik Web site and/or the
mailing lists.
The first three references on this slide are the Batik Web
site URL and the two Batik mailing lists. There is an archive
of email messages at:
http://nagoya.apache.org/eyebrowse
The SVG standard and lots of other information are available
from the W3C's Web site.