blob: a1eabceb4f1ab0db1aa5ed5d3cd80a4632f994e5 [file] [log] [blame]
<?xml version="1.0"?>
<!DOCTYPE document SYSTEM "./dtd/document-v10.dtd">
<!--
Copyright 2000-2002 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- ========================================================================= -->
<!-- author spei@cs.uiowa.edu -->
<!-- author cjolif@ilog.fr -->
<!-- version $Id$ -->
<!-- ========================================================================= -->
<document>
<header>
<title>SVG Generator: SVGGraphics2D</title>
<subtitle>A brief of the SVG Generator in Java (SVGGraphics2D)</subtitle>
<authors>
<person name="Sheng Pei" email="spei@cs.uiowa.edu"/>
<person name="Vincent Hardy" email="vincent.hardy@eng.sun.com"/>
<person name="Christophe Jolif" email="cjolif@ilog.fr"/>
<person name="Paul Evenblij" email="paul_evenblij@compuware.com"/>
</authors>
</header>
<body>
<s1 title="Introduction">
<figure src="images/svggen.jpg" alt="Batik SVG Generator"/>
<p>
As SVG (Scalable Vector Graphics) is emerging as a promising graphics format
for a wide range of domains and applications, bridging it with Java becomes important.
This page explains how Batik's <code>SVGGraphics2D</code>, referred to as the SVG Generator, makes
this possible. It is divided into three parts: </p>
<ul>
<li><link href="#whatIsIt">What is <code>SVGGraphics2D</code>?</link></li>
<li><link href="#howToUse">How to use <code>SVGGraphics2D</code>?</link></li>
<li><link href="#custom">How to customize SVG Generation process?</link></li>
<li><link href="#view">How to view the generated SVG document?</link></li>
</ul>
</s1>
<anchor id="whatIsIt" />
<s1 title="What's SVGGraphics2D">
<p>
On the Java platform, all rendering goes through the <code>java.awt.Graphics2D</code>
abstract class, which offers methods such as <code>drawRect</code>, <code>fillRect</code>, or
<code>drawString</code>. There are specialized
implementations of this abstract class for each type of output, such as a monitor or a printer.
<code>SVGGraphics2D</code> is a new implementation of that interface that generates SVG content instead of
drawing to a screen or a printer.</p>
<p>
<code>SVGGraphics2D</code> provides the following:
</p>
<ul>
<li> Allows applications to export their graphics into SVG format. </li>
<li> Does not require any modification of the graphics code to export to SVG. </li>
<li> Offers the user the ability to use the DOM API to manipulate the generated document. </li>
</ul>
<figure src="images/svggenHighLevelArchi.jpg" alt="High level architecture" />
<p>
The above figure shows how the generator works with the DOM API. The W3C has defined an API for representing
XML content with a Java programming language object. That API allows programmers to manipulate, create,
and/or modify XML content in memory. The DOM API contains interfaces such as <code>Document</code>,
<code>Element</code>, and <code>Attr</code>,
which model the Java programming language equivalent of XML documents, elements and attributes.
</p>
<p>
The generator manages a tree of DOM objects that represent the SVG content corresponding to the rendering
calls made on the <code>SVGGraphics2D</code> instance. In other words, every time a program invokes a rendering method,
such as <code>fillRect</code>, on a <code>SVGGraphics2D</code> instance, a new DOM object, representing
the SVG equivalent, is appended
to the DOM tree (for example a &lt;rect&gt; element will be appended after the <code>fillRect</code> method
has been invoked).
</p>
<p>
The programmer using this generator can then access the DOM tree to further manipulate it or can directly
write the content to an output stream, as we see in the following section.
</p>
</s1>
<anchor id="howToUse" />
<s1 title="How to use SVGGraphics2D">
<p>
From the figure in the previous section we can see that in order for an instance of <code>SVGGraphics2D</code> to build
the SVG content (the DOM tree), an instance of DOM's <code>Document</code> class is needed. The DOM tree is an in-memory
representation of the SVG document, which can be further manipulated by the user using DOM API or be streamed
out into any <code>java.io.Writer</code>.
</p>
<p>
The following excerpted code example shows how to generate SVG content from Java graphics.
</p>
<source>
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 TestSVGGen {
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();
// Create an instance of org.w3c.dom.Document
Document document = domImpl.createDocument(null, "svg", null);
// Create an instance of the SVG Generator
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
// Ask the test to render into the SVG Graphics2D implementation
TestSVGGen test = new TestSVGGen();
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);
}
}</source>
<p>
We can see that generating SVG content from our <code>TestSVGGen</code> instance is a three step
process:
</p>
<p>
1. Create an instance of <code>org.w3c.dom.Document</code> that the generator will use to build its XML content;
create an SVG generator using the <code>Document</code> instance.</p>
<source>
// Get a DOMImplementation
DOMImplementation domImpl =
GenericDOMImplementation.getDOMImplementation();
// Create an instance of org.w3c.dom.Document
Document document = domImpl.createDocument(null, "svg", null);
// Create an instance of the SVG Generator
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
</source>
<p>
2. Invoke the rendering code on our SVG generator. In our example, we invoke <code>TestSVGGen</code>'s
<code>paint</code> method: </p>
<source>
// Ask the test to render into the SVG Graphics2D implementation
TestSVGGen test = new TestSVGGen();
test.paint(svgGenerator);
</source>
<p>
3. Stream out the SVG content. The SVG generator can stream its content into any <code>java.io.Writer</code>. In our
example, we stream the content to the standard output stream: </p>
<source>
// 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);
</source>
<p>
SVG has two ways of expressing properties, such as the fill color: either XML attributes or CSS inline properties.
The 'useCss' parameter allows the user to control that option.
</p>
</s1>
<anchor id="custom" />
<s1 title="SVG Generator Customization">
<p>
In the previous section, we have just seen that the SVG generation process can be customized to output SVG style as XML attributes or CSS inline properties. In this section we will talk about some examples of more advanced customizations.
</p>
<p>
Instead of creating the <code>SVGGraphics2D</code> just by using the <code>Document</code> that will be used as a factory for creating the SVG elements, we can use the constructor that use an <code>SVGGeneratorContext</code> instance. By providing your own <code>SVGGeneratorContext</code> instance, you will be able to do advanced customization. You will find below several examples of possible customizations.
</p>
<s2 title="Have your own comment in the generated SVG file">
<p>
We begin with the simplest possible example. If you integrate the Batik SVG generator in your own Java application, you may want to specialize the comment generated in the XML code. You can proceed as below.
</p>
<source>
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
ctx.setComment("Generated by FooApplication with Batik SVG Generator");
SVGGraphics2D g2d = new SVGGraphics2D(ctx, false);
</source>
</s2>
<s2 title="Use Embedded SVG Fonts in the generated SVG file">
<p>
In order to have a self-contained SVG file that doesn't have to use system fonts to be displayed, you can embed the fonts you used for drawing strings in the SVG file through the SVG fonts facility.
</p>
<source>
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
ctx.setEmbeddedFontsOn(true);
SVGGraphics2D g2d = new SVGGraphics2D(ctx, true);
</source>
</s2>
<s2 title="Customizing the way images are stored">
<p>
Every time you call one of the <code>drawImage</code> methods provided by the <code>Graphics2D</code> class,
a default representation of your image is created in a location and put in a default file. For instance, a base64
encoding is created and embedded in the SVG file by default. Alternatively, you
can choose to have your images written to separate files in a predefined directory, in one of the two raster
formats required by the SVG specification, JPEG, PNG or Tiff.
</p>
<p>
You can change the default behavior by explicitly providing the image handler to be used by the SVG generator.
Once again, you use the SVGGeneratorContext for this. In the example below, all images are converted to PNG
format and written to directory "res/images".
</p>
<source>
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
GenericImageHandler ihandler = new ImageHandlerPNGEncoder("res/images", null);
ctx.setImageHandler(ihandler);
SVGGraphics2D g2d = new SVGGraphics2D(ctx, false);
</source>
<p>
Using the default image handlers results in a new copy of the image data being written to the SVG file or an
external file, for every single <code>drawImage</code> call. If you use the same images over and over again,
then this may result in an SVG file containing a lot of redundant data. At the price of a slight performance
penalty during initial generation of the SVG DOM tree, you can choose to have your image data reused. For this
you use a specialized image handler, as shown below.
</p>
<source>
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
// Reuse our embedded base64-encoded image data
GenericImageHandler ihandler = new CachedImageHandlerBase64Encoder();
ctx.setGenericImageHandler(ihandler);
SVGGraphics2D g2d = new SVGGraphics2D(ctx, false);
</source>
<p>
With the caching image handlers, it is even possible to reuse the same copy of your image data across
several different SVG documents. Just keep a reference to the image handler, and pass it to the
<code>SVGGraphics2D</code> instance used for generating the SVG DOM tree. The following
simplified example shows how different SVG trees might be created by separate SVG generators,
efficiently storing any common images just once.
</p>
<source>
class MySVGGenerator {
// the image handler will write all images files to "res/images"
private static ImageHandler ihandler =
new CachedImageHandlerPNGEncoder("res/images", null);
public void generateSVG(JPanel myCanvas, OutputStream outStream) {
DOMImplementation domImpl =
GenericDOMImplementation.getDOMImplementation();
Document myFactory = domImpl.createDocument(null, "svg", null);
SVGGeneratorContext ctx =
SVGGeneratorContext.createDefault(myFactory);
ctx.setGenericImageHandler(ihandler);
SVGGraphics2D svgGenerator =
new SVGGraphics2D(ctx, false);
// create the SVG DOM tree
myCanvas.paintComponent(svgGenerator);
Writer out = new OutputStreamWriter(outStream, "UTF-8");
svgGenerator.stream(out, true);
}
} </source>
</s2>
<s2 title="Customizing the generated SVG style">
<p>
Your needs in matter of styling may be different from the two provided options (XML attributes or CSS inline properties). For example you may want to put the CSS properties in a stylesheet SVG element section and reference them through the class attribute. In this case you will need to define a new <code>StyleHandler</code> as below.
</p>
<source>
public class StyleSheetStyleHandler implements StyleHandler {
private CDATASection styleSheet;
// Build the handler with a reference to the StyleSheet section
public StyleSheetStyleHandler(CDATASection styleSheet) {
this.styleSheet = styleSheet;
}
public void setStyle(Element element, Map styleMap,
SVGGeneratorContext generatorContext) {
Iterator iter = styleMap.keySet().iterator();
// create a new class id in the style sheet
String id = generatorContext.getIDGenerator().generateID("C");
styleSheet.appendData("."+id+" {");
// append each key/value pairs
while (iter.hasNext()) {
String key = (String)iter.next();
String value = (String)styleMap.get(key);
styleSheet.appendData(key+":"+value+";");
}
styleSheet.appendData("}\n");
// reference the class id of the style sheet on the element to
// be styled
element.setAttribute("class", id);
}
}</source>
<p>
Then you can create and use an <code>SVGGraphics2D</code> with a correctly configured
<code>SVGGeneratorContext</code>.
</p>
<source>
// configure the SVGGraphics2D for a given Document myFactory
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
CDATASection styleSheet = myFactory.createCDATASection("");
ctx.setStyleHandler(new StyleSheetStyleHandler(styleSheet));
SVGGraphics2D g2d = new SVGGraphics2D(ctx, false);
// use the g2d to dump drawings (component.paint(g2d))
// add a style sheet to the definition section
SVGSVGElement root = (SVGSVGElement)g2d.getRoot();
Element defs = root.getElementById(SVGSyntax.ID_PREFIX_GENERIC_DEFS);
Element style = myFactory.createElementNS(SVGSyntax.SVG_NAMESPACE_URI,
SVGSyntax.SVG_STYLE_TAG);
style.setAttributeNS(null, SVGSyntax.SVG_TYPE_ATTRIBUTE, "text/css");
style.appendChild(styleSheet);
defs.appendChild(style);
// dump the root content to a given Writer myWriter
g2d.stream(root, myWriter);
</source>
</s2>
<s2 title="Extending Paint object to SVG element translation">
<p>
The <code>SVGGraphics2D</code> is able to generate SVG elements for generic Java 2D objects, but you sometimes have your own classes such as implementations of the Java 2D <code>java.awt.Paint</code> interface. In this case, you will need to write an <code>ExtensionHandler</code> that you will set on your <code>SVGGeneratorContext</code>.
</p>
<p>
In the following example we define the first draft of an <code>ExtensionHandler</code> allowing to translate a Batik implementation of the <code>java.awt.Paint</code> interface named <code>org.apache.batik.ext.awt.LinearGradientPaint</code>.
</p>
<source>
class SubExtensionHandler extends DefaultExtensionHandler
{
public SVGPaintDescriptor handlePaint(Paint paint,
SVGGeneratorContext generatorCtx)
{
if (paint instanceof LinearGradientPaint) {
LinearGradientPaint gradient = (LinearGradientPaint)paint;
String id = generatorCtx.getIDGenerator().generateID("gradient");
Element grad = generatorCtx.getDOMFactory().
createElementNS(SVGSyntax.SVG_NAMESPACE_URI,
SVGSyntax.SVG_LINEAR_GRADIENT_TAG);
grad.setAttributeNS(null, SVGSyntax.SVG_ID_ATTRIBUTE, id);
grad.setAttributeNS(null,
SVGSyntax.SVG_GRADIENT_UNITS_ATTRIBUTE,
SVGSyntax.SVG_USER_SPACE_ON_USE_VALUE);
Point2D pt = gradient.getStartPoint();
grad.setAttributeNS(null, "x1", pt.getX());
grad.setAttributeNS(null, "y1", pt.getY());
pt = gradient.getEndPoint();
grad.setAttributeNS(null, "x2", pt.getX());
grad.setAttributeNS(null, "y2", pt.getY());
switch (gradient.getCycleMethod()) {
case MultipleGradientPaint.REFLECT:
grad.setAttributeNS(null,
SVGSyntax.SVG_SPREAD_METHOD_ATTRIBUTE,
SVGSyntax.SVG_REFLECT_VALUE);
break;
case MultipleGradientPaint.REPEAT:
grad.setAttributeNS(null,
SVGSyntax.SVG_SPREAD_METHOD_ATTRIBUTE,
SVGSyntax.SVG_REPEAT_VALUE);
break;
// pad is the default...
}
// here we should write the transform of the gradient
// in the transform attribute.
// here we should write the stops of the gradients as
// children elements.
return new SVGPaintDescriptor("url(#"+ref+")",
SVGSyntax.SVG_OPAQUE_VALUE, grad);
} else
return null; // let the default mechanism do its job
}
}
</source>
<p>
You should then set it on the <code>SVGGeneratorContext</code> by using the <code>setExtensionHandler</code> method.
</p>
<source>
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(myFactory);
ctx.setExtensionHandler(new SubExtensionHandler());
SVGGraphics2D g2d = new SVGGraphics2D(ctx, false);</source>
</s2>
</s1>
<anchor id="view"/>
<s1 title="How to view the generated SVG document">
<p>The following code example illustrates how to view the SVG content generated
by an <code>SVGGraphics2D</code> object.</p>
<source>
import org.apache.batik.swing.*;
import org.apache.batik.svggen.*;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.w3c.dom.*;
import org.w3c.dom.svg.*;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
SVGDocument doc = (SVGDocument)impl.createDocument(svgNS, "svg", null);
SVGGraphics2D g = new SVGGraphics2D(doc);
// Draw into g. For example:
//
Shape circle = new Ellipse2D.Double(0,0,50,50);
g.setPaint(Color.red);
g.fill(circle);
g.translate(60,0);
g.setPaint(Color.green);
g.fill(circle);
g.translate(60,0);
g.setPaint(Color.blue);
g.fill(circle);
g.setSVGCanvasSize(new Dimension(180,50));
// The following populates the document root with the
// generated SVG content.
Element root = doc.getDocumentElement();
g.getRoot(root);
// Now, display the document
JSVGCanvas canvas = new JSVGCanvas();
JFrame f = new JFrame();
f.getContentPane().add(canvas);
canvas.setSVGDocument(doc);
f.pack();
f.setVisible(true);</source>
</s1>
</body>
</document>