Enhancing Server-Side SVG Generation using the Batik Extenstion Handler Mechanism

For the SVG Open 2009 Conference


Table of Contents

Overview
Batik Extension Handler Mechanism
JFreeChart
External CSS
SVG Patterns
Interactivity and Animation
Logical Grouping Based on Java Stack Introspection
Appendix - Code Listing
com.virtela.filter.ExtensionPaint
com.virtela.filter.CompositePaint
com.virtela.filter.ExtensionSVGGraphics2D
com.virtela.filter.AttributePaint
com.virtela.filter.ExternalCSSPaint
com.virtela.filter.SVGPatternTexturePaint
com.virtela.filter.TooltipPaint
com.virtela.filter.AnimatePaint
com.virtela.filter.AnimateTransformPaint
tooltip.js
Bibliography

JFreeChart is a mature, established Java-based API for generating professional quality charts. The charts can be output to SVG using the aforementioned Batik code, as demonstrated by [JFreeChart]. The enhancements to the SVG generation presented in this paper allow JFreeChart users to leverage their existing code base while augmenting the functionality of their charts, and bridge the functionality gap of the native Java version to the web.

External CSS stylesheets are supported by creating a specialized Paint(documented at [Paint]) that refers to the class to be used. The SVG output of Batik is also enhanced to include the specific stylesheet(s). [CSS] specifies the mechanism to incorporate external CSS stylesheets within SVG. More than one stylesheet can be used and the media-type attribute is supported. For example, the same SVG chart can have a two stylesheets, one each for the "screen" and "print" media types. Each stylesheet can include different colors, patterns, etc. The CSS enhancement can also be applied to standlone AWT. In this case, the stylesheet is parsed, and the color applied to the object as Java AWT paints it.

The ExtensionPaint class is the base class for all the paints, and is derived from Paint. AttributePaint derives from ExtensionPaint and is a general purpose class for setting any attributes. ExternalCSSPaint derives from AttributePaint and sets the class attribute.

Most SVG viewers and web browsers that support SVG also suppport external CSS. There are some caching idiosyncracies, in some cases it can be difficult to force the viewer to reload the style sheet. There are varying levels of support for the media-type attribute. In the worst case, all style sheets are used, and the last one specified "wins". For this reason, I suggest specifying the stylesheet for the "print" media-type first, and furthermore overridiing each and every print style with a corresponding style in the "screen" stylesheet. Batik leads the pack, allowing the user to completely specify which media-type(s) to use. There is universally very little support for external references within an external stylesheet, for example to reference a pattern in a seperate SVG file. Again, Batik is the clear winner with full support.




Paint p = new ExternalCSSPaint("series1");
plot.setSectionPaint(name, p);
...

          


<?xml-stylesheet media="print" href="chartbw.css" type="text/css"?>
<?xml-stylesheet media="screen" href="chart.css" type="text/css"?>

          


.series1 { fill:green }
...

          


.series1 { fill:#EEEEEE }
...

          

High quality patterns are supported via the creation of a specialized Paint that refers to the pattern to be used. The source pattern is SVG. [SVGPatt] specifies the mechanism to incorporate patterns in an SVG document. When the SVG Pattern is detected by the Batik Extension Handler, the pattern is included in the defs section of the SVG and used in the SVG element being renderered by Batik. The patterns can be used even when SVG is not produced. In this use case, the pattern is rendered to a bitmap, and the bitmap used as a fill pattern to standalone AWT graphics calls.

The SVGPatternTexturePaint class is derived from ExtensionPaint. It defines static factory methods to create some basic 2 color patterns such as star, stripe, and spot. It can also create a pattern from any SVG. While you can use patterns via ExternalCSSPaint, SVGPatternTexturePaint is necessary due to the lack of browser support for external references.



Paint p = SVGPatternTexturePaint.createStarPaint(14, Color.GREEN, Color.WHITE);
plot.setSectionPaint(name, p); 

          

Interactivity and Animation are supported by creating another specialized Paint object. In this case, the Paint object specifies Javascript code to be executed simultaneously with certain mouse events. Declarative SVG animations are created in a similar manner. Batik SVG generation is enhanced with a corresponding change to import specific javascript files. Via this mechanism, tooltips, highlights, drilldowns, and various chart animations are demonstrated in charts created by JFreeChart and output to SVG.

The ToolTipPaint class is derived from ExtensionPaint. It appends title and desc child elements to the current element. Some viewers and browsers will display the contents of title, desc, or both as a tooltip. Unfortunately, tooltip support is the exception rather than the rule. Batik creates a tooltip containing both title and desc. The Opera browser creates a tooltip with just the title - this is actually what the SVG spec says to do. Firefox and Webkit-based browsers both will find the first title element, anywhere in the SVG DOM, and use that as the browser title, which is an undesirable behavior. Fortunately, there exist some open-source javascript libraries that implement tooltip support. One such library is the SVG Whiz! Tooltips implementation ([SVGWhiz]), which has been modified slightly and included in this project here. The general strategy is to create a rect with text, set up an event handler, and when the mouse moves display the rect in the proper place with the proper text. Additionally, the libraries scale the tooltip object to the right size independent of the overall zoom level. For some reason this works erratically in Google Chrome when using full-page zoom. Another benefit of the javascript approach is full control over the size, style and timeliness of the tooltip. This includes consistent presentation across SVG viewers.



Paint p = new TooltipPaint("http", "272638.000");
plot.setSectionPaint("http", p); 

          

The AttributePaint class mentioned previously can be used to put javascript code into attributes such as mouseover, mousemove, click, etc. This technique can be used to create highlights and drilldowns, or trigger any other javascript-based effect. If the SVG is embedded in an HTML page, the javascript can communicate with the owner document, affect its DOM, call its javscript methods, etc. Via the same mechanism multiple SVG documents might be instrumented to communicate with each other.



Map<String, String> attrs = new HashMap<String, String>();
attrs.put("onmouseover", "highlight(evt)");
attrs.put("onmouseout", "unhighlight(evt)");
AttributePaint highlightPaint = new AttributePaint(hattrs);

          


function highlight(evt) {
   var targetshape = evt.target;      
   targetshape.setAttribute("stroke-width", "2px");
   targetshape.setAttribute("stroke", "black");      
}
   
function unhighlight(evt) {
   var targetshape = evt.target;      
   targetshape.setAttribute("stroke", "none");
}

          

[SVGAnim] specifies the semantics of the SVG animate element. The AnimatePaint class inherits from the ExtensionPaint class. It creates an animate child element and sets its attributes appropriately. Subtle animations can be beneficial to the overall user experience.

[SVGAnimT] specifies the sematics of the SVG animatetransform element. The AnimateTransformPaint class is derived from AnimatePaint and creates an animatetransform element instead of animate. It adds the type attribute, which can be one of translate, scale, rotate, skewX, or skeyY.



Paint p = new AnimateTransformPaint(AnimateTransformPaint.Type.rotate, "10s", 
   "0,391,260", "360,391,260");
plot.setSectionPaint(name, p);

          

A technique is demonstrated by which the SVG produced by Batik contains a logical grouping of objects. Without this enhancement, Batik output is relatively flat. With this enhancement, Batik introspects the java stack, and creates group (g) elements corresponding to the class and method names in the stack. Each group contains a class attribute with the name of a method in the stack trace, and its children are the SVG elements drawn in that method and its successors. This technique is demonstrated using JFreeChart and is shown to work in conjunction with the aforementioned enhancements.

The ExtensionSVGGraphics2D class is derived from Batik's SVGGraphics2D class documented at [SVGG2D]. In addition to handling the ExtensionPaint logic, it overrides the default DOMGroupManager(documented at [DOMGM]) and DOMTreeManager(documented at [DOMTM]) objects with its own internal implementation. DOMTreeManager is modified to ignore some calls to appendGroup. This is because the groups in question have been appended elsewhere to the DOM. The modified DOMGroupManager maintains its own hierarchy of group elements that reflect the state of the java stack as elements are added. It keeps track of the relevant state of the stack between invocations of addElement, and on each invocation compares the last stack trace to the current, creating new group elements accordingly. It invokes the default DOMGroupManager. The default groups are appended as child elements, and are split up as necessary using cloneNode(). These enhancements have been tested and are safe for single-threaded operation, but have not been tested, and are not recommended for multi-threaded SVG production. There is minimal impact to the size of the resulting SVG.

The java stack trace is collected using the Thread.getStackTrace() method of Java(documented at [Stack]). There are several shortcomings with this approach. The method name alone isn't sufficient in cases with method overloading, which is prevalent in JFreeChart. The line numbers returned aren't meaningful in this context - they don't identify the method, only the line number within the method. This is both too exact and inexact for our purposes, as well as too brittle to be the basis for CSS class names. Furthermore, even with an exact method match, some users might want to know which iteration of that method it is. For the purposes of this code, methods in the stack trace are considered equal if the class name and method name match.

The resulting logical grouping can be used in conjuction with CSS. For example, styles can be applied to the new groups by class name, or applied to their child nodes via CSS pseudo-selectors. SVG Filters can be applied to an entire group via a style. When the same ExternalCSSPaint class is used to paint both the legend and the data series, this technique could give each a different style.

Javascript code can also leverage the new group elements. For example, mouse event logic can walk up the DOM tree, looking for specific group elements, and have different behavior depending on where in the DOM the element exists.

The group elements that are created can be usefull for post-processing. They can be leveraged in XPath queries in XSLT or other XML tools. A down-side of the AnimatePaint and AnimateTransformPaint is that the child elements are created for each element painted, which can be unwieldy. As an alternative, a post-process can add an animate or animatetransform element as the child of a group. Furthermore, a post-process can re-order, modify, or remove unwanted elements altogether. The grouping can also make manual editing easier in Inkscape or any vector drawing program that supports SVG.


<g fill="white" text-rendering="geometricPrecision" font-family="sans-serif...>
   <rect x="777.6656" width="171.3344" height="323.8682" y="99.0659" clip-.../>
   <line clip-path="url(#clipPath1)" fill="none" x1="778.1656" x2="948.5" .../>
   <line clip-path="url(#clipPath1)" fill="none" x1="778.1656" x2="948.5" .../>
   <line clip-path="url(#clipPath1)" fill="none" x1="778.1656" x2="778.165.../>
   <line clip-path="url(#clipPath1)" fill="none" x1="948.5" x2="948.5" y1=.../>
...

       

<g class="org_jfree_chart_JFreeChart_draw">
   <g class="org_jfree_chart_JFreeChart_drawTitle">
      <g class="org_jfree_chart_title_LegendTitle_draw">
         <g fill="white" text-rendering="geometricPrecision" font-family="s...>
            <rect x="777.6656" width="171.3344" height="323.8682" y="99.06.../>
         </g>
         <g class="org_jfree_chart_block_LineBorder_draw">
            <g fill="white" text-rendering="geometricPrecision" font-family...>
               <line clip-path="url(#clipPath1)" fill="none" x1="778.1656".../>
               <line clip-path="url(#clipPath1)" fill="none" x1="778.1656".../>
               <line clip-path="url(#clipPath1)" fill="none" x1="778.1656".../>
               <line clip-path="url(#clipPath1)" fill="none" x1="948.5" x2.../>
            </g>
         </g>
...

       


package com.virtela.filter;

import java.awt.Paint;
import java.io.Serializable;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.awt.PaintContext;
import java.awt.image.ColorModel;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.awt.RenderingHints;
import java.awt.image.Raster;
import java.awt.Transparency;
import org.w3c.dom.Element;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.SVGSyntax;

/**
 * ExtensionPaint is the base class for extending the Batik paint mechanism.
 * Each ExtensionPaint must be instrumented to return an appropriate 
 * SVGPaintDescriptor and to set its style on an Element in the Batik DOM.
 */
public abstract class ExtensionPaint implements Paint, Serializable
{  
   /**
    * Set my style on an elment in the DOM. Add / modify / remove attributes,
    * add child nodes, etc.
    */
   public void setStyle(Element element, SVGGeneratorContext generatorCtx) {
   }
   
   /** 
    * Build an SVGPaintDescriptor. 
    */
   public SVGPaintDescriptor getPaintDescriptor(SVGGeneratorContext generatorCtx) 
   {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      try {
         ObjectOutputStream oos = new ObjectOutputStream(baos);
         oos.writeObject(this);
         oos.flush();
         oos.close();
         //String obj = new String(baos.toByteArray(), "UTF-8");
         byte[] tmp = baos.toByteArray();
         //System.out.println(tmp.length);
         String obj = new String(tmp, "ISO-8859-1");
         //System.out.println(obj + "*" + obj.length() + "*" + obj.hashCode());         
         return new SVGPaintDescriptor("extension(" + obj + ")", 
            SVGSyntax.SVG_OPAQUE_VALUE);
      }
      catch (IOException ioe) {
         throw new RuntimeException(ioe);
      }
   } 
   
   // java.awt.Paint methods
   public PaintContext createContext(
      final ColorModel cm, 
      Rectangle deviceBounds,
      Rectangle2D userBounds,
      AffineTransform xform,
      RenderingHints hints
   )
   {
      return new PaintContext() {
         public void dispose() {
         }
         public ColorModel getColorModel() {
            return cm;
         }
         public Raster getRaster(int x, int y, int w, int h) {
            return null;
         }
      };
   }     
   
   public int getTransparency() {
      return Transparency.OPAQUE;
   }
   
   /**
    * For convenience of subclasses that need to manipulate the DOM.
    */
   public static final String svgNS = "http://www.w3.org/2000/svg";
}

            


package com.virtela.filter;

import java.awt.Paint;
import org.w3c.dom.Element;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.SVGPaint;

/**
 * CompositePaint is an ExtensionPaint that is a composite of Paint objects.
 * There are logical limitations on the paints that can be combined.
 * Only one of the paints can define a def.
 */
public class CompositePaint extends ExtensionPaint
{
   /**
    * Create a CompositePaint from the given paints.
    */
   public CompositePaint(Paint... paints) {
      this.paints = paints;
   }
   
   /**
    * Invoke the setStyle method on each of my paints that is an ExtensionPaint.
    * Invoke the regular StyleHandler otherwise.
    */
   public void setStyle(Element element, SVGGeneratorContext generatorCtx) {
      for (Paint paint : paints) {
         if (paint instanceof ExtensionPaint) {
            ((ExtensionPaint)paint).setStyle(element, generatorCtx);
         }
         else {
            // TODO: figure out which attribute(s) to set. Need to pass
            // information from StyleHandler in to here.
            SVGPaintDescriptor svgpd = new SVGPaint(generatorCtx).toSVG(paint);
            element.setAttributeNS(null, "fill",  svgpd.getPaintValue());
         }
      }
   }
   
   /**
    * Invoke getPaintDescriptor() on each of my paints looking for a def.
    * Only one may define a def (i.e. return a non-null value from 
    * SVGPaintDescriptor.getDef()). Returns an SVGPaintDescriptor that is the 
    * combination of the superclass getPaintDescriptor() and at most one def.
    */
   public SVGPaintDescriptor getPaintDescriptor(SVGGeneratorContext generatorCtx) 
   {
      // invoke getPaintDescriptor on all the paints.
      // at most one can define a def -- otherwise the last one "wins".
      Element def = null;
      for (Paint paint : paints) {
         if (paint instanceof ExtensionPaint) {
            SVGPaintDescriptor descriptor = 
               ((ExtensionPaint)paint).getPaintDescriptor(generatorCtx);
            if (descriptor.getDef() != null) {
               def = descriptor.getDef();
            }
         }
      }
      // create a new descriptor using super.getPaintDescriptor() and def.
      // this will cause batik to include the def in the defs section
      SVGPaintDescriptor descriptor = super.getPaintDescriptor(generatorCtx);
      return new SVGPaintDescriptor(descriptor.getPaintValue(), 
         descriptor.getOpacityValue(), def);
   }
   
   Paint[] paints = null;
}

            


package com.virtela.filter;

import java.util.*;
import java.awt.Paint;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.DOMImplementation;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.DefaultExtensionHandler;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.StyleHandler;
import org.apache.batik.svggen.DOMGroupManager;
import org.apache.batik.svggen.DOMTreeManager;

/**
 * ExtensionSVGGraphics2D is a SVGGraphics2D with an ExtensionHandler
 * and StyleHandler that is aware of ExtensionPaint and its subclasses.
 * It also overrides the DOMGroupManager and DOMTreeManager to output
 * a group <g> hieararchy that forms a logical grouping corresponding
 * to the java stack trace as each element is added to the DOM.
 */
public class ExtensionSVGGraphics2D 
   extends SVGGraphics2D
{
   /**
    * Create a new ExtensionSVGGraphics2D. 
    * I will create the Document used internally.
    */
   public ExtensionSVGGraphics2D() {
       super(
          createContext(), 
          false
       );
       setDOMGroupManager(groupMgr);
       setDOMTreeManager(treeMgr);
   }
   
   /**
    * Create a new ExtensionSVGGraphics2D using the given Document.
    */
   public ExtensionSVGGraphics2D(Document domFactory) {
      super(
         createContext(domFactory),
         false
      );
      setDOMGroupManager(groupMgr);
      setDOMTreeManager(treeMgr);
   }         
   
   /**
    * Determine if two stack frames are equal, for the purpose of building and
    * maintaining the hierarchy.
    * This is used for two purposes:
    * 1) eliminating redundant hierarchy levels, e.g. with polymorphism.
    * 2) comparision of the stack frames in subsequent invocations of addElement.
    * The default is to compare class and method name only.
    */
   public boolean stackEquals(StackTraceElement ste1, StackTraceElement ste2) {
      // define my own equals for StackTraceElement. I only care about class
      // and method names, none of the other stuff. Or maybe I should care about
      // the line number of the previous stack element... being invoked from
      // the same line of the caller would mean I'm the same method invocation
      // also need to come up with a naming standard for the class attr.
      // dots are probably bad in class names b/c the css rule won't make sense.
      // and should put the aforementioned line number in there.
      return 
         (ste1 == ste2) ||
         (ste2 == null ? false : (ste1.getClassName().equals(ste2.getClassName()) 
            && ste1.getMethodName().equals(ste2.getMethodName())))
         ;
   }
                  
   /**
    * Format the StackTraceElement into a class name.
    * The default is to use the full class name and method name, replacing all 
    * dot '.' characters with an underscore '_'. (Dots have meaning in css)
    * I don't recommend using the line number, it would be much more fragile.
    */
   public String formatClassName(StackTraceElement ste) {
      return ste.getClassName().replace(".", "_") + "_" + 
         ste.getMethodName().replace(".", "_");
   }
      
   /**
    * Determine if a particular stack frame is relevant; e.g. will be included
    * in the hierarchy
    */
   public boolean inScope(StackTraceElement ste) {
      // ignore batik and ourself.
      return !ste.getClassName().startsWith("org.apache.batik.") && 
         !ste.getClassName().startsWith(
            "com.virtela.filter.ExtensionSVGGraphics2D"
         );
   }             
            
   static SVGGeneratorContext createContext()
   {
      DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();                                   
      Document document = domImpl.createDocument(svgNS, "svg", null);
      return createContext(document);
   }
   
   static SVGGeneratorContext createContext(final Document document) {         
      SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
      ctx.setExtensionHandler(new DefaultExtensionHandler() {
         public SVGPaintDescriptor handlePaint(Paint paint, 
            SVGGeneratorContext generatorCtx) 
         {
            if (paint instanceof ExtensionPaint) {
               return ((ExtensionPaint)paint).getPaintDescriptor(generatorCtx);
            }
            return null;
         }
      });
      final StyleHandler delegateStyleHandler = ctx.getStyleHandler();
      ctx.setStyleHandler(new StyleHandler() {
         public void setStyle(Element element, Map styleMap, 
            SVGGeneratorContext generatorCtx) 
         {                
            Iterator iter = styleMap.keySet().iterator();
            while (iter.hasNext()) {
               String key = (String) iter.next();
               String value = (String) styleMap.get(key);
               if (("fill".equals(key) || "stroke".equals(key)) && 
                  value.startsWith("extension(")) 
               {
                  iter.remove();
                  value = value.substring(10, value.length() - 1);
                  try {
                     ByteArrayInputStream bais = 
                        new ByteArrayInputStream(value.getBytes("ISO-8859-1"));
                     ObjectInputStream ois = new ObjectInputStream(bais);
                     ExtensionPaint ep = (ExtensionPaint)ois.readObject();
                     ep.setStyle(element, generatorCtx);
                  }
                  catch (Exception e) {                    
                     throw new RuntimeException(e);                     
                  }
               }
            }
            // force Batik SVG generation to make pretty text. 
            // Otherwise puts values like "auto" and "optimizeLegibility" 
            // which are illegible.
            if (styleMap.containsKey("text-rendering")) { 
               styleMap.put("text-rendering", "geometricPrecision");
            }
            delegateStyleHandler.setStyle(element, styleMap, generatorCtx);
         }
      });
      return ctx;
   }
   
   /**
    * I am a DOMTreeManager that sometimes ignores calls to the appendGroup 
    * method. This is because my corresponding DOMGroupManager has appended 
    * the group elsewhere.
    */
   DOMTreeManager treeMgr = new DOMTreeManager(gc, generatorCtx, 
      DEFAULT_MAX_GC_OVERRIDES) 
   {
      public void appendGroup(Element group, DOMGroupManager groupManager) {
         if (!ignoreAppendGroup) {
            super.appendGroup(group, groupManager);
         }
      }
   };
   
   /**
    * I am a DOMGroupManager that also builds a hierarchy of group <g> elements 
    * corresponding to the current stack trace each time addElement is called. 
    * The class attribute of each group is set to match the first stack frame 
    * that is in scope. The groups that DOMGroupManager would normally make are 
    * placed as children of my groups. When those groups clash with my groups, 
    * the group is split (via clone) and each is placed appropriately.
    */
   DOMGroupManager groupMgr = new DOMGroupManager(gc, treeMgr) {                       
      
      /**
       * Add an element to the DOM, to the appropriate group based on the current 
       * stack trace.
       */
      public void addElement(Element element, short method) {
         String methodName = null;
         LinkedList<StackTraceElement> thisStack = 
            new LinkedList<StackTraceElement>();
         LinkedList<Element> thisGroups = new LinkedList<Element>();
         StackTraceElement[] trace = Thread.currentThread().getStackTrace();            
         StackTraceElement last = null;
         for (StackTraceElement ste : trace) {                     
            if (!ste.getClassName().startsWith("java.lang.Thread") 
               && inScope(ste)) 
            { 
               // first 2 stack trace elements are Thread.getStackTrace() 
               // and Thread.dumpThreads().
               if (last == null || (!stackEquals(ste, last)) ) {
                  thisStack.addFirst(ste);
               }                        
               last = ste;
            }
         }
                                             
         for (int i = 0; i < thisStack.size(); i++) {
            StackTraceElement ste1 = thisStack.get(i);
            StackTraceElement ste2 = null;
            Element g2 = null;
            if (currentStack.size() >= i + 1) {
               ste2 = currentStack.get(i);
               g2 = currentGroups.get(i);
            }
            if (!stackEquals(ste1,ste2)) {
               Element group = 
                  getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
               group.setAttributeNS(null, "class", formatClassName(ste1));
               if (thisGroups.isEmpty()) {
                  domTreeManager.appendGroup(group, this);
               }
               else {
                  thisGroups.getLast().appendChild(group);
               }
               thisGroups.add(group);
               currentStack.clear();
               currentGroups.clear();
            }
            else {
               if (g2 != null) {
                  thisGroups.add(g2);
               }
            }
            
         }                                                      
         
         currentStack = thisStack;
         currentGroups = thisGroups;                                                     
         
         ignoreAppendGroup = true;
         super.addElement(element, method);
         ignoreAppendGroup = false;
         
         // If I've put a currentGroup somewhere already, then I need to leave
         // it alone. And if the element should go in a new place, clone 
         // currentGroup, move the element there, and make that group a child  
         // of currentGroups.
         Element parent = currentGroupToParent.get(currentGroup); 
         if (parent != currentGroups.getLast()) {
            currentGroup = (Element)currentGroup.cloneNode(false);
            currentGroup.appendChild(element);                     
         }
         currentGroups.getLast().appendChild(currentGroup);
         currentGroupToParent.put(currentGroup, currentGroups.getLast());
      }
   };
   
   LinkedList<Element> currentGroups = new LinkedList<Element>();
   LinkedList<StackTraceElement> currentStack = 
      new LinkedList<StackTraceElement>();
   boolean ignoreAppendGroup = false;
   Map<Element, Element> currentGroupToParent = new HashMap<Element, Element>();
   
   public static final String svgNS = "http://www.w3.org/2000/svg";
}

            


package com.virtela.filter;

import java.util.Map;
import java.util.Iterator;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.Color;
import java.awt.Paint;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.ProcessingInstruction;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.DefaultExtensionHandler;
import org.apache.batik.svggen.StyleHandler;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.SVGSyntax;

/**
 * A TexturePaint that is created from an SVG Pattern element.
 * The original SVG Pattern element is retained so that a renderer that knows 
 * about this class can use it. This allows high-quality, vector patterns when 
 * outputing via Batik to SVG output. SVGPatternTexturePaint also contains 
 * static factory methods to create several 2-color patterns, as well as 
 * general-purpose constructors to create a pattern from a DOM Element (pattern)
 * or String.
 */
public class SVGPatternTexturePaint 
   extends ExtensionPaint //TexturePaint
{  
   /**
    * Create an SVGPatternTexturePaint that is a repeating brick pattern.
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background (brick).
    * @param strokeColor The color of the stroke (mortar).
    * @param strokeWidth The width of the stroke (mortar), in pixels.
    */
   public static SVGPatternTexturePaint createBrickPaint(int size, 
      Color backgroundColor, Color strokeColor, int strokeWidth) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />"); 
      sb.append("<path d=\"M 50,100 L 50,50 M 100,0 L 0,0 0,50 100,50\" ");            
      sb.append("style=\"fill:none;stroke:");
      sb.append(colorToCSSHex(strokeColor));
      sb.append(";stroke-width:");
      sb.append(strokeWidth);
      sb.append("\" />");      
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating checkerboard pattern.
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background.
    * @param checkColor The color of the check.    
    */
   public static SVGPatternTexturePaint createCheckPaint(int size, 
      Color backgroundColor, Color checkColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<rect width=\"50\" height=\"50\" x=\"0\" y=\"0\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(checkColor));
      sb.append("\" />");
      sb.append("<rect width=\"50\" height=\"50\" x=\"50\" y=\"50\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(checkColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating grid pattern.
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background.
    * @param strokeColor The color of the stroke.
    * @param strokeWidth The width of the stroke, in pixels.
    */
   public static SVGPatternTexturePaint createGridPaint(int size, 
      Color backgroundColor, Color strokeColor, int strokeWidth) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M 1,99 L 99,99 99,1\" ");
      sb.append("style=\"fill:none;stroke:");
      sb.append(colorToCSSHex(strokeColor));
      sb.append(";stroke-width:");
      sb.append(strokeWidth);
      sb.append("\" />");      
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating polka pattern 
    * (random sized and placed spots).
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background.
    * @param polkaColor The color of the polkas.    
    */
   public static SVGPatternTexturePaint createPolkaPaint(int size, 
      Color backgroundColor, Color polkaColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append(
         "<path d=\"M 30 15 A 15 15 0 1 1  0,15 A 15 15 0 1 1  30 15 z\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 30 15 A 15 15 0 1 1  0,15 A 15 15 0 1 1  30 15 " +
         "z\" transform=\"matrix(0.866667,0,0,0.866667,21.99999,41)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");     
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 " +
         "z\" transform=\"matrix(1.05,0,0,1.05,-28.15,-11.3)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 " +
         "z\" transform=\"matrix(0.9,0,0,0.9,20.3,-56.4)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 "
       + "z\" transform=\"matrix(0.9,0,0,0.9,36.3,-21.4)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 " +
         "z\" transform=\"matrix(0.4,0,0,0.4,62.8,53.6)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");       
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating spot pattern.
    * @param size The size in pixels of the height and width of the  pattern.
    * @param backgroundColor The color of the background.
    * @param spotColor The color of the spots.    
    */
   public static SVGPatternTexturePaint createSpotPaint(int size, 
      Color backgroundColor, Color spotColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />"); 
      sb.append("<circle cx=\"25\" cy=\"25\" r=\"25\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(spotColor));
      sb.append("\" />");
      sb.append("<circle cx=\"75\" cy=\"75\" r=\"25\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(spotColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating star pattern.
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background.
    * @param starColor The color of the stars.    
    */
   public static SVGPatternTexturePaint createStarPaint(int size, 
      Color backgroundColor, Color starColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M 33.171361,22.362954 C 33.206821,22.469591 " + 
         "28.234412,25.908525 28.199472,26.015333 C 28.164258,26.122981 " +
         "30.12424,31.889333 30.033069,31.956534 C 29.94261,32.023211 " +
         "25.13543,28.356858 25.023052,28.356634 C 24.909792,28.356408 " +
         "20.031332,32.002363 19.939247,31.936421 C 19.84788,31.870993 " +
         "21.849288,26.166128 21.814775,26.059181 C 21.77999,25.951394 " +
         "16.804954,22.438365 16.839213,22.33041 C 16.873205,22.223296 " +
         "22.917323,22.363848 23.00837,22.297976 C 23.100133,22.231585 " +
         "24.903851,16.41446 25.017109,16.413682 C 25.129485,16.41291 " +
         "26.863547,22.204641 26.95433,22.270876 C 27.045827,22.337632 " +
         "33.135622,22.255479 33.171361,22.362954 z\" ");
      sb.append(
         "transform=\"matrix(3.055837,0,0,3.204708,-51.46136,-52.55065)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(starColor));
      sb.append("\" />");
      sb.append("<path d=\"M 33.171361,22.362954 C 33.206821,22.469591 " +
         "28.234412,25.908525 28.199472,26.015333 C 28.164258,26.122981 " +
         "30.12424,31.889333 30.033069,31.956534 C 29.94261,32.023211 " +
         "25.13543,28.356858 25.023052,28.356634 C 24.909792,28.356408 " +
         "20.031332,32.002363 19.939247,31.936421 C 19.84788,31.870993 " +
         "21.849288,26.166128 21.814775,26.059181 C 21.77999,25.951394 " +
         "16.804954,22.438365 16.839213,22.33041 C 16.873205,22.223296 " +
         "22.917323,22.363848 23.00837,22.297976 C 23.100133,22.231585 " +
         "24.903851,16.41446 25.017109,16.413682 C 25.129485,16.41291 " +
         "26.863547,22.204641 26.95433,22.270876 C 27.045827,22.337632 " +
         "33.135622,22.255479 33.171361,22.362954 z\" ");
      sb.append(
         "transform=\"matrix(3.055837,0,0,3.204708,-1.457891,-2.601058)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(starColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating stripe pattern 
    * (45 degrees).
    * @param size The size in pixels of the height and width of the  pattern.
    * @param backgroundColor The color of the background.
    * @param stripeColor The color of the stripes.    
    */
   public static SVGPatternTexturePaint createStripePaint(int size, 
      Color backgroundColor, Color stripeColor)
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M -2,102 L 102,-2\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");
      sb.append("<path d=\"M -14,14 L 14,-14\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");   
      sb.append("<path d=\"M 84,116 L 116,84\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");
      sb.append("</pattern>");    
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating pattern of the 
    * Virtela whirlygig. The Virtela whirlygig is a Service Mark(SM) of
    * Virtela Communications.
    * @param size The size in pixels of the height and width of the pattern.
    * @param backgroundColor The color of the background.
    * @param virtelaColor The color of the whirlygigs.    
    */
   public static SVGPatternTexturePaint createVirtelaPaint(int size, 
      Color backgroundColor, Color virtelaColor)
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append(
         "<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<g ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(virtelaColor));
      sb.append("\">");
      sb.append(
         "<g transform=\"matrix(0.980915,0,0,1,-4.530406e-2,-0.126984)\" >");      
      sb.append("<path d=\"M 50.726,44.209 C 50.919,42.959 51.019,41.68 " +
         "51.019,40.375 C 51.019,26.555 39.845,15.346 26.04,15.286 L 26.04," +
         "20.303 C 37.075,20.363 46.001,29.326 46.001,40.375 C 46.001,41.687" +
         " 45.873,42.968 45.633,44.209 L 50.726,44.209 z\" />");
      sb.append("<path d=\"M 0,44.585 C 0.987,45.378 2.047,46.104 3.177," +
         "46.757 C 15.144,53.665 30.438,49.594 37.392,37.668 L 33.045,35.158" +
         " C 27.478,44.685 15.252,47.933 5.687,42.41 C 4.547,41.754 3.501," +
         "41.003 2.547,40.174 L 0,44.585 z\" />");
      sb.append("<path d=\"M 25.174,0 C 23.994,0.459 22.836,1.014 21.706," +
         "1.665 C 9.739,8.575 5.617,23.855 12.469,35.84 L 16.814,33.331 C " +
         "11.349,23.746 14.647,11.534 24.216,6.01 C 25.352,5.356 26.526," +
         "4.824 27.719,4.411 L 25.174,0 z\" />");
      sb.append("</g>");
      sb.append("<g transform=\"matrix(0.980915,0,0,1,49.9547,50)\" >");
      sb.append("<path d=\"M 50.726,44.209 C 50.919,42.959 51.019,41.68 " +
         "51.019,40.375 C 51.019,26.555 39.845,15.346 26.04,15.286 L 26.04," +
         "20.303 C 37.075,20.363 46.001,29.326 46.001,40.375 C 46.001,41.687" + 
         " 45.873,42.968 45.633,44.209 L 50.726,44.209 z\" />");
      sb.append("<path d=\"M 0,44.585 C 0.987,45.378 2.047,46.104 3.177," +
         "46.757 C 15.144,53.665 30.438,49.594 37.392,37.668 L 33.045,35.158" +
         "C 27.478,44.685 15.252,47.933 5.687,42.41 C 4.547,41.754 3.501," +
         "41.003 2.547,40.174 L 0,44.585 z\" />");
      sb.append("<path d=\"M 25.174,0 C 23.994,0.459 22.836,1.014 21.706," +
         "1.665 C 9.739,8.575 5.617,23.855 12.469,35.84 L 16.814,33.331 C " +
         "11.349,23.746 14.647,11.534 24.216,6.01 C 25.352,5.356 26.526," +
         "4.824 27.719,4.411 L 25.174,0 z\" />");
      sb.append("</g>");
      sb.append("</g>");
      sb.append("</pattern>");    
      return new SVGPatternTexturePaint(sb.toString());
   }             
     
   /**
    * Create an SVGPatternTexturePaint from a pattern represented as a String.
    * @param pattern The pattern.
    */
   public SVGPatternTexturePaint(String pattern) 
      throws SAXException, IOException, TranscoderException
   {
      this(parse(pattern));
   }
   
   /**
    * Create an SVGPatternTexturePaint from a pattern represented as a DOM 
    * Element.
    * @param pattern The pattern.
    */
   public SVGPatternTexturePaint(Element pattern) 
      throws TranscoderException
   {            
      //super(render(pattern), getAnchor(pattern));
      this.pattern = pattern;
   }
   
   /**
    * The pattern represented as a DOM Element.
    */
   Element pattern;
   
   /**
    * Return the pattern.
    */
   public Element getPattern() {
      return pattern;
   }  
   
   /**
    * Parse a pattern represented as a String into a DOM Element
    * @param pattern The pattern.
    * @return The pattern as a DOM Element.
    */
   static Element parse(String pattern) 
      throws SAXException, IOException
   {
      InputSource is = 
         new InputSource(new ByteArrayInputStream(pattern.getBytes()));
      Document doc = documentBuilder.get().parse(is);
      return doc.getDocumentElement();
   }
   
   /**
    * Render the given pattern onto a BufferedImage.
    * @param pattern The pattern.
    * @return A BufferedImage containing the rasterized pattern.
    */
   static BufferedImage render(Element pattern) 
      throws TranscoderException
   {
      // render the SVG to a BufferedImage and return it.
      // Holds the BufferedImage
      final BufferedImage[] holder = new BufferedImage[] {null}; 
      
      // Override Batik ImageTranscoder, don't perform the writeImage step.
      ImageTranscoder transcoder = new ImageTranscoder() {
         public BufferedImage createImage(int width, int height) {
            holder[0] = new BufferedImage(width, height, 
               BufferedImage.TYPE_INT_RGB);
            return holder[0];
         }
         public void writeImage(BufferedImage img, TranscoderOutput output) 
            throws TranscoderException 
         {
            // do nothing. The BufferedImage is the final output.
         }
      };
      
      // Use the DOM to build an SVG filling a rect with the pattern.
      String svgNS = "http://www.w3.org/2000/svg";
      Document doc = documentBuilder.get().getDOMImplementation()
         .createDocument(svgNS, "svg", null);
      Element svg = doc.getDocumentElement();      
      // Set the width and height of the SVG to the width and height of the 
      // pattern.
      svg.setAttribute("width", pattern.getAttribute("width"));
      svg.setAttribute("height", pattern.getAttribute("height"));
      Element defs = doc.createElement("defs");      
      Element pattern2 = (Element)doc.importNode(pattern, true);
      pattern2.setAttribute("id", "pattern");
      defs.appendChild(pattern2);
      svg.appendChild(defs);
      Element rect = doc.createElement("rect");
      rect.setAttribute("x", "0");
      rect.setAttribute("y", "0");
      rect.setAttribute("width", pattern.getAttribute("width"));
      rect.setAttribute("height", pattern.getAttribute("height"));
      rect.setAttribute("fill", "url(#pattern)");
      svg.appendChild(rect);
            
      try {
         // Batik hates TranscoderInput(doc) but likes it if I put it out to a
         // string and let Batik read it back in...
         Transformer identity = tFactory.newTransformer();
         StringWriter sw = new StringWriter();
         StreamResult result = new StreamResult(sw);
         DOMSource source = new DOMSource(doc);
         identity.transform(source, result);
         String xmlString = sw.toString();//result.getWriter().toString();
         ByteArrayInputStream bais = 
            new ByteArrayInputStream(xmlString.getBytes());
         
         // Batik transcode step.
         transcoder.transcode(new TranscoderInput(bais), 
            new TranscoderOutput());
      }            
      catch (TransformerException te) {
         throw new RuntimeException(te);
      }      
      
      return holder[0];
   }
   
   /**
    * Translate an awt Color to a css-friendly hex String.
    * For example Color.white would be "#FFFFFF".
    * null is handled specially as "none".
    * @param color The color to translate.
    * @return The color as a css-friendly String.
    */
   public static String colorToCSSHex(Color color) {
      StringBuilder sb = new StringBuilder();
      if (color != null) {
         sb.append("#");
         String red = Integer.toHexString(color.getRed());
         if (red.length() == 1) {
            sb.append("0");
         }
         sb.append(red);
         String green = Integer.toHexString(color.getGreen());
         if (green.length() == 1) {
            sb.append("0");
         }
         sb.append(green);
         String blue = Integer.toHexString(color.getBlue());
         if (blue.length() == 1) {
            sb.append("0");
         }
         sb.append(blue);
      }
      else {
         sb.append("none");
      }
      return sb.toString();
   }
   
   /**
    * Create an anchor for the given pattern.
    * The anchor is simply a rectangle based at (0, 0) with the height and 
    * width of the pattern.
    * @param pattern The pattern.
    * @returns The anchor.
    */
   static Rectangle2D getAnchor(Element pattern) {
      // get the bounds of the pattern and return it.
      return new Rectangle(
         0, 
         0, 
         Integer.parseInt(pattern.getAttribute("width")), 
         Integer.parseInt(pattern.getAttribute("height"))
      );
   }      
   
   public SVGPaintDescriptor getPaintDescriptor(
      SVGGeneratorContext generatorCtx) 
   {
      Document doc = generatorCtx.getDOMFactory();
      // This makes sure the workaround for Mozilla Bug 308590 doesn't 
      // apply the wrong pattern b/c of overlap with an existing ID.
      int randomPrefix = new java.util.Random().nextInt();
      String id = randomPrefix + 
         generatorCtx.getIDGenerator().generateID("pattern");               
      Element pat = (Element)doc.importNode(getPattern(), true);
      pat.setAttributeNS(null, SVGSyntax.SVG_ID_ATTRIBUTE, id);
      paintValue = "url(#" + id + ")";
      return new SVGPaintDescriptor(paintValue, SVGSyntax.SVG_OPAQUE_VALUE, 
         pat);
   }
   
   String paintValue = null;
   
   public void setStyle(Element element, SVGGeneratorContext generatorCtx) {      
      element.setAttributeNS(null, "fill", paintValue); 
   }
   
   /**
    * ThreadLocal DocumentBuilder used to parse XML documents.
    */
   static ThreadLocal<DocumentBuilder> documentBuilder = new ThreadLocal() {
      public DocumentBuilder initialValue() {
         synchronized(DocumentBuilderFactory.class) {
            try {
               return DocumentBuilderFactory.newInstance().newDocumentBuilder();
            }
            catch (ParserConfigurationException pce) {
               throw new RuntimeException(pce);
            }
         }
      }
   };
     
   static TransformerFactory tFactory = TransformerFactory.newInstance();
}

            


package com.virtela.filter;

import org.w3c.dom.Element;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.w3c.dom.Document;

/**
 * AnimatePaint is an ExtensionPaint that adds an <animate>
 * child element to whatever is painted with it.
 * It also serves as the base class for other animation elements.
 * @see http://www.w3.org/TR/SVG/animate.html
 */
public class AnimatePaint extends ExtensionPaint
{
   
   /**
    * Enumerates the allowed values of the "attributeType" attribute.
    */
   public static enum AttributeType {
      CSS,
      XML,
      auto
   }
   
   /**
    * Enumerates the allowed values of the "restart" attribute.
    */
   public static enum Restart {
      always,
      whenNotActive,
      never
   }
   
   /**
    * Enumerates the allowed values of the "fill" attribute.
    */
   public static enum Fill {
      freeze,
      remove
   }
   
   /**
    * Enumerates the allowed values of the "calcMode" attribute.
    */
   public static enum CalcMode {
      discrete,
      linear,
      paced,
      spline
   }
   
   /**
    * Enumerates the allowed values of the "additive" attribute.
    */
   public static enum Additive {
      replace,
      sum
   }
   
   /**
    * Enumerates the allowed values of the "accumulate" attribute.
    */
   public static enum Accumulate {
      none,
      sum
   }
   
   /**
    * Simple constructor using a small subset of the attributes.
    */
   public AnimatePaint(
      String attributeName,
      AttributeType attributeType,
      String dur,
      String from,
      String to      
   )
   {
      this(
         attributeName,
         attributeType,
         null,
         dur,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         from,
         to,
         null,
         null,
         null
      );
   }
   
   /**
    * Full constructor with all possible attributes.
    */
   public AnimatePaint(
      String attributeName, 
      AttributeType attributeType,
      String begin,
      String dur,
      String end,
      String min,
      String max,
      Restart restart,
      String repeatCount,
      String repeatDur,
      Fill fill,
      CalcMode calcMode,
      String keyTimes,
      String keySplines,
      String from,
      String to,
      String by,
      Additive additive,
      Accumulate accumulate
   )       
   {
      this.attributeName = attributeName;
      this.attributeType = attributeType;
      this.begin = begin;
      this.dur = dur;
      this.end = end;
      this.min = min;
      this.max = max;
      this.restart = restart;
      this.repeatCount = repeatCount;
      this.repeatDur = repeatDur;
      this.fill = fill;
      this.calcMode = calcMode;
      this.keyTimes = keyTimes;
      this.keySplines = keySplines;
      this.from = from;
      this.to = to;
      this.by = by;
      this.additive = additive;
      this.accumulate = accumulate;
   }
   
   /**
    * Adds an animate element as a child to the given element.
    */
   public void setStyle(Element element, SVGGeneratorContext generatorCtx) {
      Document doc = generatorCtx.getDOMFactory();
      Element e = doc.createElementNS(svgNS, getElementName());
      setAttributes(e);
      element.appendChild(e);
   }
   
   /**
    * The name of the element to make.
    */
   String getElementName() {return "animate";}
   
   /** 
    * Set the attributes of the animate element.
    */
   void setAttributes(Element e) {
      e.setAttributeNS(null, "attributeName", attributeName);
      e.setAttributeNS(null, "attributeType", attributeType.name());
      if (begin != null) {e.setAttributeNS(null, "begin", begin);}
      if (dur != null) {e.setAttributeNS(null, "dur", dur);}
      if (end != null) {e.setAttributeNS(null, "end", end);}
      if (min != null) {e.setAttributeNS(null, "min", min);}
      if (max != null) {e.setAttributeNS(null, "max", max);}
      if (restart != null) {e.setAttributeNS(null, "restart", restart.name());}
      if (repeatCount != null) {e.setAttributeNS(null, "repeatCount", 
         repeatCount);}
      if (repeatDur != null) {e.setAttributeNS(null, "repeatDur", repeatDur);}
      if (fill != null) {e.setAttributeNS(null, "fill", fill.name());}
      if (calcMode != null) {e.setAttributeNS(null, "calcMode", 
         calcMode.name());}
      if (keyTimes != null) {e.setAttributeNS(null, "keyTimes", keyTimes);}
      if (keySplines != null) {e.setAttributeNS(null, "keySplines", 
         keySplines);}
      if (from != null) {e.setAttributeNS(null, "from", from);}
      if (to != null) {e.setAttributeNS(null, "to", to);}
      if (by != null) {e.setAttributeNS(null, "by", by);}
      if (additive != null) {e.setAttributeNS(null, "additive", 
         additive.name());}
      if (accumulate != null) {e.setAttributeNS(null, "accumulate", 
         accumulate.name());}
   }
   
   String attributeName;
   AttributeType attributeType = AttributeType.auto;
   String begin;
   String dur;
   String end;
   String min;
   String max;
   Restart restart;
   String repeatCount;
   String repeatDur;
   Fill fill;
   CalcMode calcMode;
   String keyTimes;
   String keySplines;
   String from;
   String to;
   String by;
   Additive additive;
   Accumulate accumulate;
}

            


package com.virtela.filter;

import org.w3c.dom.Element;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.w3c.dom.Document;

/**
 * AnimateTransformPaint is an AnimatePaint that adds
 * an <animateTransform> element instead of <animate>,
 * and has the additional attribute "type".
 * @see http://www.w3.org/TR/SVG/animate.html#AnimateTransformElement
 */
public class AnimateTransformPaint extends AnimatePaint
{
      
   /**
    * Enumerates the allowed values of the "type" attribute
    */
   public static enum Type {
      translate,
      scale,
      rotate,
      skewX,
      skewY
   }
   
   /**
    * Simple constructor using a small subset of the attributes.
    */
   public AnimateTransformPaint(
      Type type,
      String dur,
      String from,
      String to      
   ) 
   {
      this(
         type,
         null,
         dur,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         null,
         from,
         to,
         null,
         null,
         null
      );
   }
   
   /**
    * Full constructor with all possible attributes.
    */
   public AnimateTransformPaint(
      Type type,
      String begin,
      String dur,
      String end,
      String min,
      String max,
      Restart restart,
      String repeatCount,
      String repeatDur,
      Fill fill,
      CalcMode calcMode,
      String keyTimes,
      String keySplines,
      String from,
      String to,
      String by,
      Additive additive,
      Accumulate accumulate
   )
   {
      super(
         "transform",
         AttributeType.XML,
         begin,
         dur,
         end,
         min,
         max,
         restart,
         repeatCount,
         repeatDur,
         fill,
         calcMode,
         keyTimes,
         keySplines,
         from,
         to,
         by,
         additive,
         accumulate
      );
      this.type = type;
   }
      
   /**
    * Override getElementName() to create <animateTransform> instead of 
    * <animate>
    */
   String getElementName() {return "animateTransform";}
   
   /**
    * Override setAttributes() to add my additional attribute.
    */
   void setAttributes(Element e) {
      // set the type attributes
      e.setAttributeNS(null, "type", type.name());
      
      // set the base animation attributes
      super.setAttributes(e);
   }
   
   /**
    * The type of transform.
    */
   Type type;
}

            

       
// This code is derived from http://svg-whiz.com/svg/Tooltip2.svg
// and is thus licensed under a Creative Commons Attribution-ShareAlike 2.5 
// License. See http://creativecommons.org/licenses/by-sa/2.5/
var redVal=0;
var greenVal=0;
var blueVal=0;

var SVGDocument = null;
var SVGRoot = null;
var SVGViewBox = null;
var svgns = 'http://www.w3.org/2000/svg';
var xlinkns = 'http://www.w3.org/1999/xlink';
var toolTip = null;
var TrueCoords = null;
var tipBox = null;
var tipText = null;
var tipTitle = null;
var tipDesc = null;

var lastElement = null;
var titleText = '';
var titleDesc = '';


function Init(evt)
{
   SVGDocument = evt.target.ownerDocument;
   SVGRoot = SVGDocument.documentElement;
   TrueCoords = SVGRoot.createSVGPoint();

   toolTip = SVGDocument.getElementById('ToolTip');
   tipBox = SVGDocument.getElementById('tipbox');
   tipText = SVGDocument.getElementById('tipText');
   tipTitle = SVGDocument.getElementById('tipTitle');
   tipDesc = SVGDocument.getElementById('tipDesc');
   //window.status = (TrueCoords);

   //create event for object
   SVGRoot.addEventListener('mousemove', ShowTooltip, false);
   SVGRoot.addEventListener('mouseout', HideTooltip, false);  
}

function GetTrueCoords(evt)
{
   // find the current zoom level and pan setting, and adjust the reported
   //    mouse position accordingly
   var newScale = SVGRoot.currentScale;
   var translation = SVGRoot.currentTranslate;
   TrueCoords.x = (evt.clientX - translation.x)/newScale;
   TrueCoords.y = (evt.clientY - translation.y)/newScale;
}

function HideTooltip( evt )
{
   toolTip.setAttributeNS(null, 'visibility', 'hidden');
}

function ShowTooltip( evt )
{
   GetTrueCoords( evt );

   var tipScale = 1/SVGRoot.currentScale;
   var textWidth = 0;
   var tspanWidth = 0;
   var boxHeight = 20;

   tipBox.setAttributeNS(null, 'transform', 'scale(' + tipScale + ',' + 
      tipScale + ')' );
   tipText.setAttributeNS(null, 'transform', 'scale(' + tipScale + ',' + 
      tipScale + ')' );

   var titleValue = '';
   var descValue = '';
   var targetElement = evt.target;
   if ( lastElement != targetElement )
   {
      while (true) {
         var targetTitle = targetElement.getElementsByTagName('title').item(0);
         if ( targetTitle && targetTitle.firstChild && targetTitle.parentNode 
            == targetElement)
         {
            // if there is a 'title' element, use its contents for the tooltip 
            // title
            titleValue = targetTitle.firstChild.nodeValue;
            break;
         }
         else {
            // walk up the node tree until I find a title
            targetElement = targetElement.parentNode;
            if (!targetElement) {
               break;
            }
         }
      }

      if (targetElement) { 
         var targetDesc = targetElement.getElementsByTagName('desc').item(0);
         if ( targetDesc && targetDesc.firstChild && targetDesc.parentNode 
            == targetElement)
         {
            // if there is a 'desc' element, use its contents for the tooltip 
            // desc
            descValue = targetDesc.firstChild.nodeValue;
   
            if ( '' == titleValue )
            {
               // if there is no 'title' element, use the contents of the 
               // 'desc' element for the tooltip title instead
               titleValue = descValue;
               descValue = '';
            }
         }
      }

      // selectively assign the tooltip title and desc the proper values,
      //   and hide those which don't have text values
      //
      var titleDisplay = 'none';
      if ( '' != titleValue )
      {
         tipTitle.firstChild.nodeValue = titleValue;
         titleDisplay = 'inline';
      }
      tipTitle.setAttributeNS(null, 'display', titleDisplay );


      var descDisplay = 'none';
      if ( '' != descValue )
      {
         tipDesc.firstChild.nodeValue = descValue;
         descDisplay = 'inline';
      }
      tipDesc.setAttributeNS(null, 'display', descDisplay );
   }

   // if there are tooltip contents to be displayed, adjust the size and 
   // position of the box
   if ( '' != titleValue )
   {
      var xPos = TrueCoords.x + (10 * tipScale);
      var yPos = TrueCoords.y + (10 * tipScale);

      //return rectangle around text as SVGRect object
      var outline = tipText.getBBox();
      tipBox.setAttributeNS(null, 'width', Number(outline.width) + 10);
      tipBox.setAttributeNS(null, 'height', Number(outline.height) + 10);

      // update position
      toolTip.setAttributeNS(null, 'transform', 'translate(' + xPos + ',' + 
         yPos + ')');
      toolTip.setAttributeNS(null, 'visibility', 'visible');
   }
}