Index: trunk/src/com/kitfox/svg/A.java
===================================================================
--- trunk/src/com/kitfox/svg/A.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/A.java	(revision 4256)
@@ -0,0 +1,102 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.net.URI;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class A extends Group {
+
+    URI href;
+    String title;
+
+    /** Creates a new instance of Stop */
+    public A() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String offset = attrs.getValue("offset");
+        this.offset = (float)XMLParseUtil.parseRatio(offset);
+
+        buildStop();
+    }
+    */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("xlink:href")))
+        {
+            href = sty.getURIValue(getXMLBase());
+        }
+        
+        if (getPres(sty.setName("xlink:title")))
+        {
+            title = sty.getStringValue();
+        }
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        boolean changeState = super.updateTime(curTime);
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("xlink:href")))
+        {
+            href = sty.getURIValue(getXMLBase());
+        }
+        
+        if (getPres(sty.setName("xlink:title")))
+        {
+            title = sty.getStringValue();
+        }
+        
+        return changeState;
+    }
+}
Index: trunk/src/com/kitfox/svg/Circle.java
===================================================================
--- trunk/src/com/kitfox/svg/Circle.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Circle.java	(revision 4256)
@@ -0,0 +1,171 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Circle extends ShapeElement 
+{
+
+    float cx = 0f;
+    float cy = 0f;
+    float r = 0f;
+
+
+    Ellipse2D.Float circle = new Ellipse2D.Float();
+
+    /** Creates a new instance of Rect */
+    public Circle() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String cx = attrs.getValue("cx");
+        String cy = attrs.getValue("cy");
+        String r = attrs.getValue("r");
+
+        this.cx = XMLParseUtil.parseFloat(cx);
+        this.cy = XMLParseUtil.parseFloat(cy);
+        this.r = XMLParseUtil.parseFloat(r);
+
+        build();
+        
+        //setBounds(this.cx - this.r, this.cy - this.r, this.r * 2.0, this.r * 2.0);
+    }
+*/
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+//        super.loaderEndElement(helper);
+
+//        build();
+    }
+     */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("cx"))) cx = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("cy"))) cy = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("r"))) r = sty.getFloatValueWithUnits();
+        
+        circle.setFrame(cx - r, cy - r, r * 2f, r * 2f);
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, circle);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(circle);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(circle.getBounds2D()));
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("cx")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != cx)
+            {
+                cx = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("cy")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != cy)
+            {
+                cy = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("r")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != r)
+            {
+                r = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (shapeChange)
+        {
+            build();
+//            circle.setFrame(cx - r, cy - r, r * 2f, r * 2f);
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+    
+}
+
+
+
Index: trunk/src/com/kitfox/svg/ClipPath.java
===================================================================
--- trunk/src/com/kitfox/svg/ClipPath.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/ClipPath.java	(revision 4256)
@@ -0,0 +1,159 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Shape;
+import java.awt.geom.Area;
+import java.util.Iterator;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class ClipPath extends SVGElement 
+{
+
+    public static final int CP_USER_SPACE_ON_USE = 0;
+    public static final int CP_OBJECT_BOUNDING_BOX = 1;
+
+    int clipPathUnits = CP_USER_SPACE_ON_USE;
+
+    /** Creates a new instance of Stop */
+    public ClipPath() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String clipPathUnits = attrs.getValue("clipPathUnits");
+
+        if (clipPathUnits.equals("objectBoundingBox")) this.clipPathUnits = CP_OBJECT_BOUNDING_BOX;
+
+    }
+*/
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+		super.loaderAddChild(helper, child);
+
+//        if (child instanceof ShapeElement) members.add(child);
+    }
+
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+//        super.loaderEndElement(helper);
+
+//        build();
+    }
+    */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        clipPathUnits = (getPres(sty.setName("clipPathUnits"))
+            && sty.getStringValue().equals("objectBoundingBox")) 
+            ? CP_OBJECT_BOUNDING_BOX 
+            : CP_USER_SPACE_ON_USE;
+    }
+    
+    public int getClipPathUnits()
+    {
+        return clipPathUnits;
+    }
+
+    public Shape getClipPathShape()
+    {
+        if (children.size() == 0) return null;
+        if (children.size() == 1) return ((ShapeElement)children.get(0)).getShape();
+
+        Area clipArea = null;
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            ShapeElement se = (ShapeElement)it.next();
+
+            if (clipArea == null)
+            {
+                Shape shape = se.getShape();
+                if (shape != null) clipArea = new Area(se.getShape());
+                continue;
+            }
+
+            Shape shape = se.getShape();
+            if (shape != null) clipArea.intersect(new Area(shape));
+        }
+
+        return clipArea;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+
+        
+        if (getPres(sty.setName("clipPathUnits")))
+        {
+            String newUnitsStrn = sty.getStringValue();
+            int newUnits = newUnitsStrn.equals("objectBoundingBox")
+                ? CP_OBJECT_BOUNDING_BOX 
+                : CP_USER_SPACE_ON_USE;
+                
+            if (newUnits != clipPathUnits)
+            {
+                clipPathUnits = newUnits;
+                shapeChange = true;
+            }
+        }
+
+        if (shapeChange)
+        {
+            build();
+        }
+
+        return shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Defs.java
===================================================================
--- trunk/src/com/kitfox/svg/Defs.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Defs.java	(revision 4256)
@@ -0,0 +1,70 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Defs extends TransformableElement
+{
+
+    /** Creates a new instance of Stop */
+    public Defs() {
+    }
+
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+		super.loaderAddChild(helper, child);
+
+//        members.add(child);
+    }
+
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        boolean stateChange = false;
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            stateChange = stateChange || ele.updateTime(curTime);
+        }
+        
+        return super.updateTime(curTime) || stateChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Desc.java
===================================================================
--- trunk/src/com/kitfox/svg/Desc.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Desc.java	(revision 4256)
@@ -0,0 +1,58 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 19, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+/**
+ * Holds title textual information within tree
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Desc extends SVGElement {
+
+    StringBuffer text = new StringBuffer();
+
+    /** Creates a new instance of Stop */
+    public Desc() {
+    }
+
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+        this.text.append(text);
+    }
+
+    public String getText() { return text.toString(); }
+    
+    public boolean updateTime(double curTime)
+    {
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Ellipse.java
===================================================================
--- trunk/src/com/kitfox/svg/Ellipse.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Ellipse.java	(revision 4256)
@@ -0,0 +1,192 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Ellipse extends ShapeElement {
+
+    float cx = 0.0f;
+    float cy = 0.0f;
+    float rx = 0.0f;
+    float ry = 0.0f;
+
+    Ellipse2D.Float ellipse = new Ellipse2D.Float();
+
+    /** Creates a new instance of Rect */
+    public Ellipse() {
+    }
+/*
+    protected void init(String idIn, Style parentStyle, String cx, String cy, String rx, String ry) {
+        super.init(idIn, parentStyle);
+
+        this.cx = parseDouble(cx);
+        this.cy = parseDouble(cy);
+        this.rx = parseDouble(rx);
+        this.ry = parseDouble(ry);
+
+        setBounds(this.cx - this.rx, this.cy - this.ry, this.rx * 2.0, this.ry * 2.0);
+    }
+*/
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String cx = attrs.getValue("cx");
+        String cy = attrs.getValue("cy");
+        String rx = attrs.getValue("rx");
+        String ry = attrs.getValue("ry");
+
+        this.cx = XMLParseUtil.parseDouble(cx);
+        this.cy = XMLParseUtil.parseDouble(cy);
+        this.rx = XMLParseUtil.parseDouble(rx);
+        this.ry = XMLParseUtil.parseDouble(ry);
+
+        build();
+    }
+    */
+    
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        super.loaderEndElement(helper);
+
+        build();
+    }
+     */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("cx"))) cx = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("cy"))) cy = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("rx"))) rx = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("ry"))) ry = sty.getFloatValueWithUnits();
+        
+        ellipse.setFrame(cx - rx, cy - ry, rx * 2f, ry * 2f);
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, ellipse);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(ellipse);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(ellipse.getBounds2D()));
+    }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("cx")))
+        {
+            float newCx = sty.getFloatValueWithUnits();
+            if (newCx != cx)
+            {
+                cx = newCx;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("cy")))
+        {
+            float newCy = sty.getFloatValueWithUnits();
+            if (newCy != cy)
+            {
+                cy = newCy;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("rx")))
+        {
+            float newRx = sty.getFloatValueWithUnits();
+            if (newRx != rx)
+            {
+                rx = newRx;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("ry")))
+        {
+            float newRy = sty.getFloatValueWithUnits();
+            if (newRy != ry)
+            {
+                ry = newRy;
+                shapeChange = true;
+            }
+        }
+        
+        if (shapeChange)
+        {
+            build();
+//            ellipse.setFrame(cx - rx, cy - ry, rx * 2f, ry * 2f);
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/FeDistantLight.java
===================================================================
--- trunk/src/com/kitfox/svg/FeDistantLight.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FeDistantLight.java	(revision 4256)
@@ -0,0 +1,100 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class FeDistantLight extends FeLight 
+{
+    float azimuth = 0f;
+    float elevation = 0f;
+    
+
+    /** Creates a new instance of FillElement */
+    public FeDistantLight() {
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        
+        if (getPres(sty.setName("azimuth"))) azimuth = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("elevation"))) elevation = sty.getFloatValueWithUnits();
+    }
+
+    public float getAzimuth() { return azimuth; }
+    public float getElevation() { return elevation; }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("azimuth")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != azimuth)
+            {
+                azimuth = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("elevation")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != elevation)
+            {
+                elevation = newVal;
+                stateChange = true;
+            }
+        }
+        
+        return stateChange;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/FeLight.java
===================================================================
--- trunk/src/com/kitfox/svg/FeLight.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FeLight.java	(revision 4256)
@@ -0,0 +1,50 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class FeLight extends FilterEffects 
+{
+
+    /** Creates a new instance of FillElement */
+    public FeLight() {
+    }
+
+}
+
Index: trunk/src/com/kitfox/svg/FePointLight.java
===================================================================
--- trunk/src/com/kitfox/svg/FePointLight.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FePointLight.java	(revision 4256)
@@ -0,0 +1,114 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class FePointLight extends FeLight 
+{
+    float x = 0f;
+    float y = 0f;
+    float z = 0f;
+    
+
+    /** Creates a new instance of FillElement */
+    public FePointLight() {
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("z"))) z = sty.getFloatValueWithUnits();
+    }
+
+    public float getX() { return x; }
+    public float getY() { return y; }
+    public float getZ() { return z; }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("z")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != z)
+            {
+                z = newVal;
+                stateChange = true;
+            }
+        }
+        
+        return stateChange;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/FeSpotLight.java
===================================================================
--- trunk/src/com/kitfox/svg/FeSpotLight.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FeSpotLight.java	(revision 4256)
@@ -0,0 +1,177 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class FeSpotLight extends FeLight 
+{
+    float x = 0f;
+    float y = 0f;
+    float z = 0f;
+    float pointsAtX = 0f;
+    float pointsAtY = 0f;
+    float pointsAtZ = 0f;
+    float specularComponent = 0f;
+    float limitingConeAngle = 0f;
+    
+
+    /** Creates a new instance of FillElement */
+    public FeSpotLight() {
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("z"))) z = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("pointsAtX"))) pointsAtX = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("pointsAtY"))) pointsAtY = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("pointsAtZ"))) pointsAtZ = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("specularComponent"))) specularComponent = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("limitingConeAngle"))) limitingConeAngle = sty.getFloatValueWithUnits();
+    }
+
+    public float getX() { return x; }
+    public float getY() { return y; }
+    public float getZ() { return z; }
+    public float getPointsAtX() { return pointsAtX; }
+    public float getPointsAtY() { return pointsAtY; }
+    public float getPointsAtZ() { return pointsAtZ; }
+    public float getSpecularComponent() { return specularComponent; }
+    public float getLimitingConeAngle() { return limitingConeAngle; }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("z")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != z)
+            {
+                z = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("pointsAtX")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != pointsAtX)
+            {
+                pointsAtX = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("pointsAtY")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != pointsAtY)
+            {
+                pointsAtY = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("pointsAtZ")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != pointsAtZ)
+            {
+                pointsAtZ = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("specularComponent")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != specularComponent)
+            {
+                specularComponent = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("limitingConeAngle")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != limitingConeAngle)
+            {
+                limitingConeAngle = newVal;
+                stateChange = true;
+            }
+        }
+        
+        return stateChange;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/FillElement.java
===================================================================
--- trunk/src/com/kitfox/svg/FillElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FillElement.java	(revision 4256)
@@ -0,0 +1,51 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class FillElement extends SVGElement {
+
+    /** Creates a new instance of FillElement */
+    public FillElement() {
+    }
+
+    /**
+     * Requests the paint defined by this element.  Passes in information
+     * to allow paint to be customized
+     * @param bounds - bounding box of shape being rendered
+     * @param xform - The current transformation that the shape is being rendered
+     * under.
+     */
+    abstract public Paint getPaint(Rectangle2D bounds, AffineTransform xform);
+}
Index: trunk/src/com/kitfox/svg/Filter.java
===================================================================
--- trunk/src/com/kitfox/svg/Filter.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Filter.java	(revision 4256)
@@ -0,0 +1,226 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Filter extends SVGElement 
+{
+    public static final int FU_OBJECT_BOUNDING_BOX = 0;
+    public static final int FU_USER_SPACE_ON_USE = 1;
+
+    protected int filterUnits = FU_OBJECT_BOUNDING_BOX;
+    
+    public static final int PU_OBJECT_BOUNDING_BOX = 0;
+    public static final int PU_USER_SPACE_ON_USE = 1;
+
+    protected int primitiveUnits = PU_OBJECT_BOUNDING_BOX;
+    
+    float x = 0f;
+    float y = 0f;
+    float width = 1f;
+    float height = 1f;
+    
+    Point2D filterRes = new Point2D.Double();
+    
+    URL href = null;
+
+    final ArrayList filterEffects = new ArrayList();
+
+    /** Creates a new instance of FillElement */
+    public Filter() {
+    }
+
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+
+        if (child instanceof FilterEffects)
+        {
+            filterEffects.add(child);
+        }
+    }
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        
+        if (getPres(sty.setName("filterUnits")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) filterUnits = FU_USER_SPACE_ON_USE;
+            else filterUnits = FU_OBJECT_BOUNDING_BOX;
+        }
+
+        if (getPres(sty.setName("primitiveUnits")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) primitiveUnits = PU_USER_SPACE_ON_USE;
+            else primitiveUnits = PU_OBJECT_BOUNDING_BOX;
+        }
+
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("width"))) width = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("height"))) height = sty.getFloatValueWithUnits();
+
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                href = src.toURL();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new SVGException(e);
+        }
+
+    }
+
+    public float getX() { return x; }
+    public float getY() { return y; }
+    public float getWidth() { return width; }
+    public float getHeight() { return height; }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("width")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != width)
+            {
+                width = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("height")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != height)
+            {
+                height = newVal;
+                stateChange = true;
+            }
+        }
+        
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                URL newVal = src.toURL();
+                
+                if (!newVal.equals(href))
+                {
+                    href = newVal;
+                    stateChange = true;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new SVGException(e);
+        }
+
+        if (getPres(sty.setName("filterUnits")))
+        {
+            int newVal;
+            String strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) newVal = FU_USER_SPACE_ON_USE;
+            else newVal = FU_OBJECT_BOUNDING_BOX;
+            if (newVal != filterUnits)
+            {
+                filterUnits = newVal;
+                stateChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("primitiveUnits")))
+        {
+            int newVal;
+            String strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) newVal = PU_USER_SPACE_ON_USE;
+            else newVal = PU_OBJECT_BOUNDING_BOX;
+            if (newVal != filterUnits)
+            {
+                primitiveUnits = newVal;
+                stateChange = true;
+            }
+        }
+
+        
+        
+        return stateChange;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/FilterEffects.java
===================================================================
--- trunk/src/com/kitfox/svg/FilterEffects.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FilterEffects.java	(revision 4256)
@@ -0,0 +1,233 @@
+/*
+ * FillElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on March 18, 2004, 6:52 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class FilterEffects extends SVGElement 
+{
+    public static final int FP_SOURCE_GRAPHIC = 0;
+    public static final int FP_SOURCE_ALPHA = 1;
+    public static final int FP_BACKGROUND_IMAGE = 2;
+    public static final int FP_BACKGROUND_ALPHA = 3;
+    public static final int FP_FILL_PAINT = 4;
+    public static final int FP_STROKE_PAINT = 5;
+    public static final int FP_CUSTOM = 5;
+
+    private int filterPrimitiveTypeIn;
+    private String filterPrimitiveRefIn;
+    
+    
+    float x = 0f;
+    float y = 0f;
+    float width = 1f;
+    float height = 1f;
+    
+    String result = "defaultFilterName";
+    
+    
+    
+    URL href = null;
+
+
+    /** Creates a new instance of FillElement */
+    public FilterEffects() {
+    }
+
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+
+        if (child instanceof FilterEffects)
+        {
+//            filterEffects.add(child);
+        }
+    }
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        /*
+        if (getPres(sty.setName("filterUnits")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) filterUnits = FU_USER_SPACE_ON_USE;
+            else filterUnits = FU_OBJECT_BOUNDING_BOX;
+        }
+
+        if (getPres(sty.setName("primitiveUnits")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) primitiveUnits = PU_USER_SPACE_ON_USE;
+            else primitiveUnits = PU_OBJECT_BOUNDING_BOX;
+        }
+
+        if (getPres(sty.setName("x"))) x = sty.getFloatValue();
+
+        if (getPres(sty.setName("y"))) y = sty.getFloatValue();
+
+        if (getPres(sty.setName("width"))) width = sty.getFloatValue();
+
+        if (getPres(sty.setName("height"))) height = sty.getFloatValue();
+
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                href = src.toURL();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new SVGException(e);
+        }
+*/
+    }
+
+    public float getX() { return x; }
+    public float getY() { return y; }
+    public float getWidth() { return width; }
+    public float getHeight() { return height; }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("width")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != width)
+            {
+                width = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("height")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != height)
+            {
+                height = newVal;
+                stateChange = true;
+            }
+        }
+        
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                URL newVal = src.toURL();
+                
+                if (!newVal.equals(href))
+                {
+                    href = newVal;
+                    stateChange = true;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new SVGException(e);
+        }
+
+        /*
+        if (getPres(sty.setName("filterUnits")))
+        {
+            int newVal;
+            String strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) newVal = FU_USER_SPACE_ON_USE;
+            else newVal = FU_OBJECT_BOUNDING_BOX;
+            if (newVal != filterUnits)
+            {
+                filterUnits = newVal;
+                stateChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("primitiveUnits")))
+        {
+            int newVal;
+            String strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) newVal = PU_USER_SPACE_ON_USE;
+            else newVal = PU_OBJECT_BOUNDING_BOX;
+            if (newVal != filterUnits)
+            {
+                primitiveUnits = newVal;
+                stateChange = true;
+            }
+        }
+
+        */
+        
+        return stateChange;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/Font.java
===================================================================
--- trunk/src/com/kitfox/svg/Font.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Font.java	(revision 4256)
@@ -0,0 +1,247 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.util.*;
+
+/**
+ * Implements an embedded font.
+ *
+ * SVG specification: http://www.w3.org/TR/SVG/fonts.html
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Font extends SVGElement
+{
+    int horizOriginX = 0;
+    int horizOriginY = 0;
+    int horizAdvX = -1;  //Must be specified
+    int vertOriginX = -1;  //Defaults to horizAdvX / 2
+    int vertOriginY = -1;  //Defaults to font's ascent
+    int vertAdvY = -1;  //Defaults to one 'em'.  See font-face
+
+    FontFace fontFace = null;
+    MissingGlyph missingGlyph = null;
+    final HashMap glyphs = new HashMap();
+
+    /** Creates a new instance of Font */
+    public Font()
+    {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String horizOriginX = attrs.getValue("horiz-origin-x");
+        String horizOriginY = attrs.getValue("horiz-origin-y");
+        String horizAdvX = attrs.getValue("horiz-adv-x");
+        String vertOriginX = attrs.getValue("vert-origin-x");
+        String vertOriginY = attrs.getValue("vert-origin-y");
+        String vertAdvY = attrs.getValue("vert-adv-y");
+
+        if (horizOriginX != null) this.horizOriginX = XMLParseUtil.parseInt(horizOriginX);
+        if (horizOriginY != null) this.horizOriginY = XMLParseUtil.parseInt(horizOriginY);
+        if (horizAdvX != null) this.horizAdvX = XMLParseUtil.parseInt(horizAdvX);
+        if (vertOriginX != null) this.vertOriginX = XMLParseUtil.parseInt(vertOriginX);
+        if (vertOriginY != null) this.vertOriginY = XMLParseUtil.parseInt(vertOriginY);
+        if (vertAdvY != null) this.vertAdvY = XMLParseUtil.parseInt(vertAdvY);
+
+    }
+*/
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+		super.loaderAddChild(helper, child);
+
+        if (child instanceof Glyph)
+        {
+            glyphs.put(((Glyph)child).getUnicode(), child);
+        }
+        else if (child instanceof MissingGlyph)
+        {
+            missingGlyph = (MissingGlyph)child;
+        }
+        else if (child instanceof FontFace)
+        {
+            fontFace = (FontFace)child;
+        }
+    }
+
+    public void loaderEndElement(SVGLoaderHelper helper) throws SVGParseException
+    {
+        super.loaderEndElement(helper);
+
+        //build();
+        
+        helper.universe.registerFont(this);
+    }
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("horiz-origin-x"))) horizOriginX = sty.getIntValue();
+        
+        if (getPres(sty.setName("horiz-origin-y"))) horizOriginY = sty.getIntValue();
+        
+        if (getPres(sty.setName("horiz-adv-x"))) horizAdvX = sty.getIntValue();
+        
+        if (getPres(sty.setName("vert-origin-x"))) vertOriginX = sty.getIntValue();
+        
+        if (getPres(sty.setName("vert-origin-y"))) vertOriginY = sty.getIntValue();
+        
+        if (getPres(sty.setName("vert-adv-y"))) vertAdvY = sty.getIntValue();
+    }
+    
+    public FontFace getFontFace() { return fontFace; }
+
+    public MissingGlyph getGlyph(String unicode)
+    {
+        Glyph retVal = (Glyph)glyphs.get(unicode);
+        if (retVal == null) return missingGlyph;
+        return retVal;
+    }
+
+    public int getHorizOriginX() { return horizOriginX; }
+    public int getHorizOriginY() { return horizOriginY; }
+    public int getHorizAdvX() { return horizAdvX; }
+
+    public int getVertOriginX()
+    {
+        if (vertOriginX != -1) return vertOriginX;
+        vertOriginX = getHorizAdvX() / 2;
+        return vertOriginX;
+    }
+
+    public int getVertOriginY()
+    {
+        if (vertOriginY != -1) return vertOriginY;
+        vertOriginY = fontFace.getAscent();
+        return vertOriginY;
+    }
+
+    public int getVertAdvY()
+    {
+        if (vertAdvY != -1) return vertAdvY;
+        vertAdvY = fontFace.getUnitsPerEm();
+        return vertAdvY;
+    }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Fonts can't change
+        return false;
+        /*
+        if (trackManager.getNumTracks() == 0) return false;
+        
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean stateChange = false;
+        
+        if (getPres(sty.setName("horiz-origin-x")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != horizOriginX)
+            {
+                horizOriginX = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("horiz-origin-y")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != horizOriginY)
+            {
+                horizOriginY = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("horiz-adv-x")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != horizAdvX)
+            {
+                horizAdvX = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("vert-origin-x")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != vertOriginX)
+            {
+                vertOriginX = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("vert-origin-y")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != vertOriginY)
+            {
+                vertOriginY = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("vert-adv-y")))
+        {
+            int newVal = sty.getIntValue();
+            if (newVal != vertAdvY)
+            {
+                vertAdvY = newVal;
+                stateChange = true;
+            }
+        }
+        
+        return shapeChange;
+        */
+    }
+}
Index: trunk/src/com/kitfox/svg/FontFace.java
===================================================================
--- trunk/src/com/kitfox/svg/FontFace.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/FontFace.java	(revision 4256)
@@ -0,0 +1,215 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.awt.geom.*;
+import java.awt.*;
+
+
+/**
+ * Implements an embedded font.
+ *
+ * SVG specification: http://www.w3.org/TR/SVG/fonts.html
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class FontFace extends SVGElement
+{
+    String fontFamily;
+
+    /** Em size of coordinate system font is defined in */
+    int unitsPerEm = 1000;
+
+    int ascent = -1;
+    int descent = -1;
+    int accentHeight = -1;
+
+    int underlinePosition = -1;
+    int underlineThickness = -1;
+    int strikethroughPosition = -1;
+    int strikethroughThickness = -1;
+    int overlinePosition = -1;
+    int overlineThickness = -1;
+
+    /** Creates a new instance of Font */
+    public FontFace()
+    {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        fontFamily = attrs.getValue("font-family");
+
+        String unitsPerEm = attrs.getValue("units-per-em");
+        String ascent = attrs.getValue("ascent");
+        String descent = attrs.getValue("descent");
+        String accentHeight = attrs.getValue("accent-height");
+
+        String underlinePosition = attrs.getValue("underline-position");
+        String underlineThickness = attrs.getValue("underline-thickness");
+        String strikethroughPosition = attrs.getValue("strikethrough-position");
+        String strikethroughThickness = attrs.getValue("strikethrough-thickness");
+        String overlinePosition = attrs.getValue("overline-position");
+        String overlineThickness = attrs.getValue("overline-thickness");
+
+
+        if (unitsPerEm != null) this.unitsPerEm = XMLParseUtil.parseInt(unitsPerEm);
+        if (ascent != null) this.ascent = XMLParseUtil.parseInt(ascent);
+        if (descent != null) this.descent = XMLParseUtil.parseInt(descent);
+        if (accentHeight != null) this.accentHeight = XMLParseUtil.parseInt(accentHeight);
+
+        if (underlinePosition != null) this.underlinePosition = XMLParseUtil.parseInt(underlinePosition);
+        if (underlineThickness != null) this.underlineThickness = XMLParseUtil.parseInt(underlineThickness);
+        if (strikethroughPosition != null) this.strikethroughPosition = XMLParseUtil.parseInt(strikethroughPosition);
+        if (strikethroughThickness != null) this.strikethroughThickness = XMLParseUtil.parseInt(strikethroughThickness);
+        if (overlinePosition != null) this.overlinePosition = XMLParseUtil.parseInt(overlinePosition);
+        if (overlineThickness != null) this.overlineThickness = XMLParseUtil.parseInt(overlineThickness);
+
+//        unitFontXform.setToScale(1.0 / (double)unitsPerEm, 1.0 / (double)unitsPerEm);
+    }
+  */  
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        super.loaderEndElement(helper);
+
+        build();
+        
+//        unitFontXform.setToScale(1.0 / (double)unitsPerEm, 1.0 / (double)unitsPerEm);
+    }
+     */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("font-family"))) fontFamily = sty.getStringValue();
+        
+        if (getPres(sty.setName("units-per-em"))) unitsPerEm = sty.getIntValue();
+        if (getPres(sty.setName("ascent"))) ascent = sty.getIntValue();
+        if (getPres(sty.setName("descent"))) descent = sty.getIntValue();
+        if (getPres(sty.setName("accent-height"))) accentHeight = sty.getIntValue();
+
+        if (getPres(sty.setName("underline-position"))) underlinePosition = sty.getIntValue();
+        if (getPres(sty.setName("underline-thickness"))) underlineThickness = sty.getIntValue();
+        if (getPres(sty.setName("strikethrough-position"))) strikethroughPosition = sty.getIntValue();
+        if (getPres(sty.setName("strikethrough-thickenss"))) strikethroughThickness = sty.getIntValue();
+        if (getPres(sty.setName("overline-position"))) overlinePosition = sty.getIntValue();
+        if (getPres(sty.setName("overline-thickness"))) overlineThickness = sty.getIntValue();
+    }
+
+
+    public String getFontFamily() { return fontFamily; }
+
+    public int getUnitsPerEm() { return unitsPerEm; }
+
+    public int getAscent()
+    {
+        if (ascent == -1)
+            ascent = unitsPerEm - ((Font)parent).getVertOriginY();
+        return ascent;
+    }
+
+    public int getDescent()
+    {
+        if (descent == -1)
+            descent = ((Font)parent).getVertOriginY();
+        return descent;
+    }
+
+    public int getAccentHeight()
+    {
+        if (accentHeight == -1)
+            accentHeight = getAscent();
+        return accentHeight;
+    }
+
+    public int getUnderlinePosition()
+    {
+        if (underlinePosition == -1)
+            underlinePosition = unitsPerEm * 5 / 6;
+        return underlinePosition;
+    }
+
+    public int getUnderlineThickness()
+    {
+        if (underlineThickness == -1)
+            underlineThickness = unitsPerEm / 20;
+        return underlineThickness;
+    }
+
+    public int getStrikethroughPosition()
+    {
+        if (strikethroughPosition == -1)
+            strikethroughPosition = unitsPerEm * 3 / 6;
+        return strikethroughPosition;
+    }
+
+    public int getStrikethroughThickness()
+    {
+        if (strikethroughThickness == -1)
+            strikethroughThickness = unitsPerEm / 20;
+        return strikethroughThickness;
+    }
+
+    public int getOverlinePosition()
+    {
+        if (overlinePosition == -1)
+            overlinePosition = unitsPerEm * 5 / 6;
+        return overlinePosition;
+    }
+
+    public int getOverlineThickness()
+    {
+        if (overlineThickness == -1)
+            overlineThickness = unitsPerEm / 20;
+        return overlineThickness;
+    }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime)
+    {
+        //Fonts can't change
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Glyph.java
===================================================================
--- trunk/src/com/kitfox/svg/Glyph.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Glyph.java	(revision 4256)
@@ -0,0 +1,102 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.pathcmd.*;
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+
+/**
+ * Implements an embedded font.
+ *
+ * SVG specification: http://www.w3.org/TR/SVG/fonts.html
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Glyph extends MissingGlyph
+{
+    /**
+     * One or more characters indicating the unicode sequence that denotes
+     * this glyph.
+     */
+    String unicode;
+
+    /** Creates a new instance of Font */
+    public Glyph()
+    {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        //Get unicode sequence that maps to this glyph
+        unicode = attrs.getValue("unicode");
+    }
+*/
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        super.loaderEndElement(helper);
+
+        build();
+    }
+     */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("unicode"))) unicode = sty.getStringValue();
+    }
+    
+    public String getUnicode() { return unicode; }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Fonts can't change
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Gradient.java
===================================================================
--- trunk/src/com/kitfox/svg/Gradient.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Gradient.java	(revision 4256)
@@ -0,0 +1,288 @@
+/*
+ * Gradient.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 3:25 AM
+ */
+
+package com.kitfox.svg;
+
+import java.net.*;
+import java.util.*;
+import java.awt.geom.*;
+import java.awt.*;
+
+import com.kitfox.svg.xml.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class Gradient extends FillElement
+{
+
+    public static final int SM_PAD = 0;
+    public static final int SM_REPEAT = 1;
+    public static final int SM_REFLECT = 2;
+
+    int spreadMethod = SM_PAD;
+
+    public static final int GU_OBJECT_BOUNDING_BOX = 0;
+    public static final int GU_USER_SPACE_ON_USE = 1;
+
+    protected int gradientUnits = GU_OBJECT_BOUNDING_BOX;
+
+    //Either this gradient contains a list of stops, or it will take it's
+    // stops from the referenced gradient
+    ArrayList stops = new ArrayList();
+    URI stopRef = null;
+//    Gradient stopRef = null;
+
+    protected AffineTransform gradientTransform = null;
+
+    //Cache arrays of stop values here
+    float[] stopFractions;
+    Color[] stopColors;
+
+    /** Creates a new instance of Gradient */
+    public Gradient() {
+    }
+    
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+
+        if (!(child instanceof Stop)) return;
+        appendStop((Stop)child);
+    }
+
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        String strn;
+        
+        if (getPres(sty.setName("spreadMethod")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("repeat")) spreadMethod = SM_REPEAT;
+            else if (strn.equals("reflect")) spreadMethod = SM_REFLECT;
+            else spreadMethod = SM_PAD;
+        }
+
+        if (getPres(sty.setName("gradientUnits")))
+        {
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) gradientUnits = GU_USER_SPACE_ON_USE;
+            else gradientUnits = GU_OBJECT_BOUNDING_BOX;
+        }
+
+        if (getPres(sty.setName("gradientTransform"))) gradientTransform = parseTransform(sty.getStringValue());
+        //If we still don't have one, set it to identity
+        if (gradientTransform == null) gradientTransform = new AffineTransform();
+
+        
+        //Check to see if we're using our own stops or referencing someone else's
+        if (getPres(sty.setName("xlink:href")))
+        {
+            try {
+                stopRef = sty.getURIValue(getXMLBase());
+//System.err.println("Gradient: " + sty.getStringValue() + ", " + getXMLBase() + ", " + src);
+//                URI src = getXMLBase().resolve(href);
+//                stopRef = (Gradient)diagram.getUniverse().getElement(src);
+            }
+            catch (Exception e)
+            {
+                throw new SVGException("Could not resolve relative URL in Gradient: " + sty.getStringValue() + ", " + getXMLBase(), e);
+            }
+        }
+    }
+    
+    public float[] getStopFractions()
+    {
+        if (stopRef != null)
+        {
+            Gradient grad = (Gradient)diagram.getUniverse().getElement(stopRef);
+            return grad.getStopFractions();
+        }
+
+        if (stopFractions != null) return stopFractions;
+
+        stopFractions = new float[stops.size()];
+        int idx = 0;
+        for (Iterator it = stops.iterator(); it.hasNext();)
+        {
+            Stop stop = (Stop)it.next();
+            float val = stop.offset;
+            if (idx != 0 && val < stopFractions[idx - 1]) val = stopFractions[idx - 1];
+            stopFractions[idx++] = val;
+        }
+
+        return stopFractions;
+    }
+
+    public Color[] getStopColors()
+    {
+        if (stopRef != null)
+        {
+            Gradient grad = (Gradient)diagram.getUniverse().getElement(stopRef);
+            return grad.getStopColors();
+        }
+
+        if (stopColors != null) return stopColors;
+
+        stopColors = new Color[stops.size()];
+        int idx = 0;
+        for (Iterator it = stops.iterator(); it.hasNext();)
+        {
+            Stop stop = (Stop)it.next();
+            int stopColorVal = stop.color.getRGB();
+            Color stopColor = new Color((stopColorVal >> 16) & 0xff, (stopColorVal >> 8) & 0xff, stopColorVal & 0xff, clamp((int)(stop.opacity * 255), 0, 255));
+            stopColors[idx++] = stopColor;
+        }
+
+        return stopColors;
+    }
+    
+    public void setStops(Color[] colors, float[] fractions)
+    {
+        if (colors.length != fractions.length)
+        {
+            throw new IllegalArgumentException();
+        }
+        
+        this.stopColors = colors;
+        this.stopFractions = fractions;
+        stopRef = null;
+    }
+    
+    private int clamp(int val, int min, int max)
+    {
+        if (val < min) return min;
+        if (val > max) return max;
+        return val;
+    }
+    
+    public void setStopRef(URI grad)
+    {
+        stopRef = grad;
+    }
+
+    public void appendStop(Stop stop)
+    {
+        stops.add(stop);
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean stateChange = false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        String strn;
+        
+
+        if (getPres(sty.setName("spreadMethod")))
+        {
+            int newVal;
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("repeat")) newVal = SM_REPEAT;
+            else if (strn.equals("reflect")) newVal = SM_REFLECT;
+            else newVal = SM_PAD;
+            if (spreadMethod != newVal)
+            {
+                spreadMethod = newVal;
+                stateChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("gradientUnits")))
+        {
+            int newVal;
+            strn = sty.getStringValue().toLowerCase();
+            if (strn.equals("userspaceonuse")) newVal = GU_USER_SPACE_ON_USE;
+            else newVal = GU_OBJECT_BOUNDING_BOX;
+            if (newVal != gradientUnits)
+            {
+                gradientUnits = newVal;
+                stateChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("gradientTransform")))
+        {
+            AffineTransform newVal = parseTransform(sty.getStringValue());
+            if (newVal != null && newVal.equals(gradientTransform))
+            {
+                gradientTransform = newVal;
+                stateChange = true;
+            }
+        }
+
+        
+        //Check to see if we're using our own stops or referencing someone else's
+        if (getPres(sty.setName("xlink:href")))
+        {
+            try {
+                URI newVal = sty.getURIValue(getXMLBase());
+                if ((newVal == null && stopRef != null) || !newVal.equals(stopRef))
+                {
+                    stopRef = newVal;
+                    stateChange = true;
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        
+        //Check stops, if any
+        for (Iterator it = stops.iterator(); it.hasNext();)
+        {
+            Stop stop = (Stop)it.next();
+            if (stop.updateTime(curTime))
+            {
+                stateChange = true;
+                stopFractions = null;
+                stopColors = null;
+            }
+        }
+        
+        return stateChange;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/Group.java
===================================================================
--- trunk/src/com/kitfox/svg/Group.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Group.java	(revision 4256)
@@ -0,0 +1,301 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Group extends ShapeElement
+{
+
+    //Cache bounding box for faster clip testing
+    Rectangle2D boundingBox;
+    Shape cachedShape;
+
+    //Cache clip bounds
+    final Rectangle clipBounds = new Rectangle();
+
+    /** Creates a new instance of Stop */
+    public Group() {
+    }
+
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+        //Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        //String transform = attrs.getValue("transform");
+    }
+     */
+
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+
+//        members.add(child);
+    }
+
+    protected boolean outsideClip(Graphics2D g) throws SVGException
+    {
+        g.getClipBounds(clipBounds);
+        Rectangle2D rect = getBoundingBox();
+
+//if (rect == null)
+//{
+//    rect = getBoundingBox();
+//}
+
+//        if (rect.intersects(clipBounds))
+        if (rect.intersects(clipBounds))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
+    {
+        Point2D xPoint = new Point2D.Double(point.getX(), point.getY());
+        if (xform != null)
+        {
+            try
+            {
+                xform.inverseTransform(point, xPoint);
+            } 
+            catch (NoninvertibleTransformException ex)
+            {
+                throw new SVGException(ex);
+            }
+        }
+        
+        
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            if (ele instanceof RenderableElement)
+            {
+                RenderableElement rendEle = (RenderableElement)ele;
+                
+                rendEle.pick(xPoint, boundingBox, retVec);
+            }
+        }
+    }
+
+    void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
+    {
+        if (xform != null)
+        {
+            ltw = new AffineTransform(ltw);
+            ltw.concatenate(xform);
+        }
+        
+        
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            if (ele instanceof RenderableElement)
+            {
+                RenderableElement rendEle = (RenderableElement)ele;
+                
+                rendEle.pick(pickArea, ltw, boundingBox, retVec);
+            }
+        }
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        //Don't process if not visible
+        StyleAttribute styleAttrib = new StyleAttribute();
+        if (getStyle(styleAttrib.setName("visibility")))
+        {
+            if (!styleAttrib.getStringValue().equals("visible")) return;
+        }
+        
+        //Do not process offscreen groups
+        boolean ignoreClip = diagram.ignoringClipHeuristic();
+        if (!ignoreClip && outsideClip(g)) return;
+
+        beginLayer(g);
+
+        Iterator it = children.iterator();
+
+        try
+        {
+            g.getClipBounds(clipBounds);
+        }
+        catch (Exception e)
+        {
+            //For some reason, getClipBounds can throw a null pointer exception for 
+            // some types of Graphics2D
+            ignoreClip = true;
+        }
+
+        while (it.hasNext())
+        {
+            SVGElement ele = (SVGElement)it.next();
+            if (ele instanceof RenderableElement)
+            {
+                RenderableElement rendEle = (RenderableElement)ele;
+
+//                if (shapeEle == null) continue;
+
+                if (!(ele instanceof Group))
+                {
+                    //Skip if clipping area is outside our bounds
+                    if (!ignoreClip && !rendEle.getBoundingBox().intersects(clipBounds)) 
+                    {
+                        continue;
+                    }
+                }
+
+                rendEle.render(g);
+            }
+        }
+
+        finishLayer(g);
+    }
+
+
+    /**
+     * Retrieves the cached bounding box of this group
+     */
+    public Shape getShape()
+    {
+        if (cachedShape == null) calcShape();
+        return cachedShape;
+    }
+
+    public void calcShape()
+    {
+        Area retShape = new Area();
+
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+
+            if (ele instanceof ShapeElement)
+            {
+                ShapeElement shpEle = (ShapeElement)ele;
+                Shape shape = shpEle.getShape();
+                if (shape != null)
+                {
+                    retShape.add(new Area(shape));
+                }
+            }
+        }
+
+        cachedShape = shapeToParent(retShape);
+    }
+
+    /**
+     * Retrieves the cached bounding box of this group
+     */
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        if (boundingBox == null) calcBoundingBox();
+//        calcBoundingBox();
+        return boundingBox;
+    }
+
+    /**
+     * Recalculates the bounding box by taking the union of the bounding boxes
+     * of all children.  Caches the result.
+     */
+    public void calcBoundingBox() throws SVGException
+    {
+//        Rectangle2D retRect = new Rectangle2D.Float();
+        Rectangle2D retRect = null;
+
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+
+            if (ele instanceof RenderableElement)
+            {
+                RenderableElement rendEle = (RenderableElement)ele;
+                Rectangle2D bounds = rendEle.getBoundingBox();
+                if (bounds != null)
+                {
+                    if (retRect == null) retRect = bounds;
+                    else retRect = retRect.createUnion(bounds);
+                }
+            }
+        }
+
+//        if (xform != null)
+//        {
+//            retRect = xform.createTransformedShape(retRect).getBounds2D();
+//        }
+
+        //If no contents, use degenerate rectangle
+        if (retRect == null) retRect = new Rectangle2D.Float();
+
+        boundingBox = boundsToParent(retRect);
+    }
+
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        boolean changeState = super.updateTime(curTime);
+        Iterator it = children.iterator();
+
+        //Distribute message to all members of this group
+        while (it.hasNext())
+        {
+            SVGElement ele = (SVGElement)it.next();
+            boolean updateVal = ele.updateTime(curTime);
+
+            changeState = changeState || updateVal;
+
+            //Update our shape if shape aware children change
+            if (ele instanceof ShapeElement) cachedShape = null;
+            if (ele instanceof RenderableElement) boundingBox = null;
+        }
+
+        return changeState;
+    }
+}
Index: trunk/src/com/kitfox/svg/Hkern.java
===================================================================
--- trunk/src/com/kitfox/svg/Hkern.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Hkern.java	(revision 4256)
@@ -0,0 +1,65 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Hkern extends SVGElement
+{
+    String u1;
+    String u2;
+    int k;
+
+
+    protected void build() throws SVGException
+    {
+        super.build();
+
+        StyleAttribute sty = new StyleAttribute();
+
+
+        //Read glyph spacing info
+        if (getPres(sty.setName("u1"))) u1 = sty.getStringValue();
+
+        if (getPres(sty.setName("u2"))) u2 = sty.getStringValue();
+
+        if (getPres(sty.setName("k"))) k = sty.getIntValue();
+    }
+
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Fonts can't change
+        return false;
+    }
+    
+    
+}
Index: trunk/src/com/kitfox/svg/ImageSVG.java
===================================================================
--- trunk/src/com/kitfox/svg/ImageSVG.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/ImageSVG.java	(revision 4256)
@@ -0,0 +1,308 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.app.data.Handler;
+import com.kitfox.svg.xml.*;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.net.*;
+import java.util.List;
+
+/**
+ * Implements an embedded font.
+ *
+ * SVG specification: http://www.w3.org/TR/SVG/fonts.html
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class ImageSVG extends RenderableElement
+{
+    float x = 0f;
+    float y = 0f;
+    float width = 0f;
+    float height = 0f;
+
+//    BufferedImage href = null;
+    URL imageSrc = null;
+
+    AffineTransform xform;
+    Rectangle2D bounds;
+
+    /** Creates a new instance of Font */
+    public ImageSVG()
+    {
+    }
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("width"))) width = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("height"))) height = sty.getFloatValueWithUnits();
+
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                if ("data".equals(src.getScheme()))
+                {
+                    imageSrc = new URL(null, src.toASCIIString(), new Handler());
+                }
+                else
+                {
+                    try {
+                        imageSrc = src.toURL();
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                        imageSrc = null;
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new SVGException(e);
+        }
+
+        diagram.getUniverse().registerImage(imageSrc);
+        
+        //Set widths if not set
+        BufferedImage img = diagram.getUniverse().getImage(imageSrc);
+        if (img == null)
+        {
+            xform = new AffineTransform();
+            bounds = new Rectangle2D.Float();
+            return;
+        }
+        
+        if (width == 0) width = img.getWidth();
+        if (height == 0) height = img.getHeight();
+        
+        //Determine image xform
+        xform = new AffineTransform();
+//        xform.setToScale(this.width / img.getWidth(), this.height / img.getHeight());
+//        xform.translate(this.x, this.y);
+        xform.translate(this.x, this.y);
+        xform.scale(this.width / img.getWidth(), this.height / img.getHeight());
+        
+        bounds = new Rectangle2D.Float(this.x, this.y, this.width, this.height);
+    }
+    
+    
+    
+    public float getX() { return x; }
+    public float getY() { return y; }
+    public float getWidth() { return width; }
+    public float getHeight() { return height; }
+
+    void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
+    {
+        if (getBoundingBox().contains(point))
+        {
+            retVec.add(getPath(null));
+        }
+    }
+
+    void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
+    {
+        if (ltw.createTransformedShape(getBoundingBox()).intersects(pickArea))
+        {
+            retVec.add(getPath(null));
+        }
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        StyleAttribute styleAttrib = new StyleAttribute();
+        if (getStyle(styleAttrib.setName("visibility")))
+        {
+            if (!styleAttrib.getStringValue().equals("visible")) return;
+        }
+        
+        beginLayer(g);
+        
+        float opacity = 1f;
+        if (getStyle(styleAttrib.setName("opacity")))
+        {
+            opacity = styleAttrib.getRatioValue();
+        }
+        
+        if (opacity <= 0) return;
+
+        Composite oldComp = null;
+        
+        if (opacity < 1)
+        {
+            oldComp = g.getComposite();
+            Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
+            g.setComposite(comp);
+        }
+        
+        BufferedImage img = diagram.getUniverse().getImage(imageSrc);
+        if (img == null) return;
+        
+        AffineTransform curXform = g.getTransform();
+        g.transform(xform);
+        
+        g.drawImage(img, 0, 0, null);
+        
+        g.setTransform(curXform);
+        if (oldComp != null) g.setComposite(oldComp);
+        
+        finishLayer(g);
+    }
+    
+    public Rectangle2D getBoundingBox()
+    {
+        return boundsToParent(bounds);
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("width")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != width)
+            {
+                width = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("height")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != height)
+            {
+                height = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        try {
+            if (getPres(sty.setName("xlink:href")))
+            {
+                URI src = sty.getURIValue(getXMLBase());
+                URL newVal = src.toURL();
+                
+                if (!newVal.equals(imageSrc))
+                {
+                    imageSrc = newVal;
+                    shapeChange = true;
+                }
+            }
+        }
+        catch (IllegalArgumentException ie)
+        {
+            new Exception("Image provided with illegal value for href: \"" + sty.getStringValue() + '"', ie).printStackTrace();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+        
+        if (shapeChange)
+        {
+            build();
+//            diagram.getUniverse().registerImage(imageSrc);
+//
+//            //Set widths if not set
+//            BufferedImage img = diagram.getUniverse().getImage(imageSrc);
+//            if (img == null)
+//            {
+//                xform = new AffineTransform();
+//                bounds = new Rectangle2D.Float();
+//            }
+//            else
+//            {
+//                if (width == 0) width = img.getWidth();
+//                if (height == 0) height = img.getHeight();
+//
+//                //Determine image xform
+//                xform = new AffineTransform();
+////                xform.setToScale(this.width / img.getWidth(), this.height / img.getHeight());
+////                xform.translate(this.x, this.y);
+//                xform.translate(this.x, this.y);
+//                xform.scale(this.width / img.getWidth(), this.height / img.getHeight());
+//
+//                bounds = new Rectangle2D.Float(this.x, this.y, this.width, this.height);
+//            }
+//
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Line.java
===================================================================
--- trunk/src/com/kitfox/svg/Line.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Line.java	(revision 4256)
@@ -0,0 +1,172 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Line extends ShapeElement {
+
+    float x1 = 0f;
+    float y1 = 0f;
+    float x2 = 0f;
+    float y2 = 0f;
+
+    Line2D.Float line;
+//    RectangularShape rect;
+
+    /** Creates a new instance of Rect */
+    public Line() {
+    }
+
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String x1 = attrs.getValue("x1");
+        String y1 = attrs.getValue("y1");
+        String x2 = attrs.getValue("x2");
+        String y2 = attrs.getValue("y2");
+
+        this.x1 = XMLParseUtil.parseFloat(x1);
+        this.y1 = XMLParseUtil.parseFloat(y1);
+        this.x2 = XMLParseUtil.parseFloat(x2);
+        this.y2 = XMLParseUtil.parseFloat(y2);
+
+        build();
+    }
+*/
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x1"))) x1 = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("y1"))) y1 = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("x2"))) x2 = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("y2"))) y2 = sty.getFloatValueWithUnits();
+
+        line = new Line2D.Float(x1, y1, x2, y2);
+    }
+    
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, line);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(line);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(line.getBounds2D()));
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x1")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x1)
+            {
+                x1 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y1")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y1)
+            {
+                y1 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("x2")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x2)
+            {
+                x2 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y2")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y2)
+            {
+                y2 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (shapeChange)
+        {
+            build();
+//            line = new Line2D.Float(x1, y1, x2, y2);
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/LinearGradient.java
===================================================================
--- trunk/src/com/kitfox/svg/LinearGradient.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/LinearGradient.java	(revision 4256)
@@ -0,0 +1,210 @@
+/*
+ * LinearGradient.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:54 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.geom.*;
+import java.awt.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+//import org.apache.batik.ext.awt.*;
+import com.kitfox.svg.batik.*;
+
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class LinearGradient extends Gradient {
+
+    float x1 = 0f;
+    float y1 = 0f;
+    float x2 = 1f;
+    float y2 = 0f;
+
+    /** Creates a new instance of LinearGradient */
+    public LinearGradient() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String x1 = attrs.getValue("x1");
+        String x2 = attrs.getValue("x2");
+        String y1 = attrs.getValue("y1");
+        String y2 = attrs.getValue("y2");
+
+        if (x1 != null) this.x1 = (float)XMLParseUtil.parseRatio(x1);
+        if (y1 != null) this.y1 = (float)XMLParseUtil.parseRatio(y1);
+        if (x2 != null) this.x2 = (float)XMLParseUtil.parseRatio(x2);
+        if (y2 != null) this.y2 = (float)XMLParseUtil.parseRatio(y2);
+    }
+*/
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        super.loaderEndElement(helper);
+        
+        build();
+    }
+    */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x1"))) x1 = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y1"))) y1 = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("x2"))) x2 = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y2"))) y2 = sty.getFloatValueWithUnits();
+    }
+    
+    public Paint getPaint(Rectangle2D bounds, AffineTransform xform)
+    {
+        com.kitfox.svg.batik.MultipleGradientPaint.CycleMethodEnum method;
+        switch (spreadMethod)
+        {
+            default:
+            case SM_PAD:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.NO_CYCLE;
+                break;
+            case SM_REPEAT:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.REPEAT;
+                break;
+            case SM_REFLECT:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.REFLECT;
+                break;
+        }
+
+        com.kitfox.svg.batik.LinearGradientPaint paint;
+        if (gradientUnits == GU_USER_SPACE_ON_USE)
+        {
+//            paint = new LinearGradientPaint(x1, y1, x2, y2, getStopFractions(), getStopColors(), method);
+            paint = new com.kitfox.svg.batik.LinearGradientPaint(
+                new Point2D.Float(x1, y1),
+                new Point2D.Float(x2, y2),
+                getStopFractions(),
+                getStopColors(),
+                method,
+                com.kitfox.svg.batik.MultipleGradientPaint.SRGB,
+                gradientTransform);
+        }
+        else
+        {
+            AffineTransform viewXform = new AffineTransform();
+            viewXform.translate(bounds.getX(), bounds.getY());
+            
+            //This is a hack to get around shapes that have a width or height of 0.  Should be close enough to the true answer.
+            double width = bounds.getWidth();
+            double height = bounds.getHeight();
+            if (width == 0) width = 1;
+            if (height == 0) height = 1;
+            viewXform.scale(width, height);
+
+            viewXform.concatenate(gradientTransform);
+
+            paint = new com.kitfox.svg.batik.LinearGradientPaint(
+                new Point2D.Float(x1, y1),
+                new Point2D.Float(x2, y2),
+                getStopFractions(),
+                getStopColors(),
+                method,
+                com.kitfox.svg.batik.MultipleGradientPaint.SRGB,
+                viewXform);
+        }
+
+        return paint;
+    }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return stopChange;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x1")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x1)
+            {
+                x1 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y1")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y1)
+            {
+                y1 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("x2")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x2)
+            {
+                x2 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y2")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y2)
+            {
+                y2 = newVal;
+                shapeChange = true;
+            }
+        }
+
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Marker.java
===================================================================
--- trunk/src/com/kitfox/svg/Marker.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Marker.java	(revision 4256)
@@ -0,0 +1,263 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Marker extends Group
+{
+    AffineTransform viewXform;
+    AffineTransform markerXform;
+    Rectangle2D viewBox;
+
+    float refX;
+    float refY;
+    float markerWidth = 3;
+    float markerHeight = 3;
+    float orient = Float.NaN;
+
+    protected void build() throws SVGException
+    {
+        super.build();
+
+        StyleAttribute sty = new StyleAttribute();
+
+        if (getPres(sty.setName("refX"))) refX = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("refY"))) refY = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("markerWidth"))) markerWidth = sty.getFloatValueWithUnits();
+        if (getPres(sty.setName("markerHeight"))) markerHeight = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("orient")))
+        {
+            if ("auto".equals(sty.getStringValue()))
+            {
+                orient = Float.NaN;
+            }
+            else
+            {
+                orient = sty.getFloatValue();
+            }
+        }
+
+        if (getPres(sty.setName("viewBox")))
+        {
+            float[] dim = sty.getFloatList();
+            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
+        }
+
+        if (viewBox == null)
+        {
+            viewBox = new Rectangle(0, 0, 1, 1);
+        }
+
+        //Transform pattern onto unit square
+        viewXform = new AffineTransform();
+        viewXform.scale(1.0 / viewBox.getWidth(), 1.0 / viewBox.getHeight());
+        viewXform.translate(-viewBox.getX(), -viewBox.getY());
+
+        markerXform = new AffineTransform();
+        markerXform.scale(markerWidth, markerHeight);
+        markerXform.concatenate(viewXform);
+        markerXform.translate(-refX, -refY);
+    }
+
+    protected boolean outsideClip(Graphics2D g) throws SVGException
+    {
+        g.getClipBounds(clipBounds);
+        Rectangle2D rect = super.getBoundingBox();
+        if (rect.intersects(clipBounds))
+        {
+            return false;
+        }
+
+        return true;
+
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        AffineTransform oldXform = g.getTransform();
+        g.transform(markerXform);
+
+        super.render(g);
+
+        g.setTransform(oldXform);
+    }
+
+    public void render(Graphics2D g, MarkerPos pos, float strokeWidth) throws SVGException
+    {
+        AffineTransform cacheXform = g.getTransform();
+
+        g.translate(pos.x, pos.y);
+        g.scale(strokeWidth, strokeWidth);
+        g.rotate(Math.atan2(pos.dy, pos.dx));
+
+        g.transform(markerXform);
+
+        super.render(g);
+
+        g.setTransform(cacheXform);
+    }
+
+    public Shape getShape()
+    {
+        Shape shape = super.getShape();
+        return markerXform.createTransformedShape(shape);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        Rectangle2D rect = super.getBoundingBox();
+        return markerXform.createTransformedShape(rect).getBounds2D();
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        boolean changeState = super.updateTime(curTime);
+
+        //Marker properties do not change
+        return changeState;
+    }
+
+    //--------------------------------
+    public static final int MARKER_START = 0;
+    public static final int MARKER_MID = 1;
+    public static final int MARKER_END = 2;
+
+    public static class MarkerPos
+    {
+        int type;
+        double x;
+        double y;
+        double dx;
+        double dy;
+
+        public MarkerPos(int type, double x, double y, double dx, double dy)
+        {
+            this.type = type;
+            this.x = x;
+            this.y = y;
+            this.dx = dx;
+            this.dy = dy;
+        }
+    }
+
+    public static class MarkerLayout
+    {
+        private ArrayList markerList = new ArrayList();
+        boolean started = false;
+
+        public void layout(Shape shape)
+        {
+            double px = 0;
+            double py = 0;
+            double[] coords = new double[6];
+            for (PathIterator it = shape.getPathIterator(null);
+                    !it.isDone(); it.next())
+            {
+                switch (it.currentSegment(coords))
+                {
+                    case PathIterator.SEG_MOVETO:
+                        px = coords[0];
+                        py = coords[1];
+                        started = false;
+                        break;
+                    case PathIterator.SEG_CLOSE:
+                        started = false;
+                        break;
+                    case PathIterator.SEG_LINETO:
+                    {
+                        double x = coords[0];
+                        double y = coords[1];
+                        markerIn(px, py, x - px, y - py);
+                        markerOut(x, y, x - px, y - py);
+                        px = x;
+                        py = y;
+                        break;
+                    }
+                    case PathIterator.SEG_QUADTO:
+                    {
+                        double k0x = coords[0];
+                        double k0y = coords[1];
+                        double x = coords[2];
+                        double y = coords[3];
+                        markerIn(px, py, k0x - px, k0y - py);
+                        markerOut(x, y, x - k0x, y - k0y);
+                        px = x;
+                        py = y;
+                        break;
+                    }
+                    case PathIterator.SEG_CUBICTO:
+                    {
+                        double k0x = coords[0];
+                        double k0y = coords[1];
+                        double k1x = coords[2];
+                        double k1y = coords[3];
+                        double x = coords[4];
+                        double y = coords[5];
+                        markerIn(px, py, k0x - px, k0y - py);
+                        markerOut(x, y, x - k1x, y - k1y);
+                        px = x;
+                        py = y;
+                        break;
+                    }
+                }
+            }
+
+            for (int i = 1; i < markerList.size(); ++i)
+            {
+                MarkerPos prev = (MarkerPos)markerList.get(i - 1);
+                MarkerPos cur = (MarkerPos)markerList.get(i);
+
+                if (cur.type == MARKER_START)
+                {
+                    prev.type = MARKER_END;
+                }
+            }
+            MarkerPos last = (MarkerPos)markerList.get(markerList.size() - 1);
+            last.type = MARKER_END;
+        }
+
+        private void markerIn(double x, double y, double dx, double dy)
+        {
+            if (started == false)
+            {
+                started = true;
+                markerList.add(new MarkerPos(MARKER_START, x, y, dx, dy));
+            }
+        }
+
+        private void markerOut(double x, double y, double dx, double dy)
+        {
+            markerList.add(new MarkerPos(MARKER_MID, x, y, dx, dy));
+        }
+
+        /**
+         * @return the markerList
+         */
+        public ArrayList getMarkerList()
+        {
+            return markerList;
+        }
+    }
+}
Index: trunk/src/com/kitfox/svg/Metadata.java
===================================================================
--- trunk/src/com/kitfox/svg/Metadata.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Metadata.java	(revision 4256)
@@ -0,0 +1,46 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 19, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+/**
+ * Does not hold any information.  Included to allow metadata tag to be parsed.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Metadata extends SVGElement 
+{
+    /** Creates a new instance of Stop */
+    public Metadata() {
+    }
+
+    public boolean updateTime(double curTime)
+    {
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/MissingGlyph.java
===================================================================
--- trunk/src/com/kitfox/svg/MissingGlyph.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/MissingGlyph.java	(revision 4256)
@@ -0,0 +1,246 @@
+/*
+ * Font.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 10:00 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.*;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.pathcmd.*;
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+
+/**
+ * Implements an embedded font.
+ *
+ * SVG specification: http://www.w3.org/TR/SVG/fonts.html
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class MissingGlyph extends ShapeElement
+{
+    //We may define a path
+//    ExtendedGeneralPath path = null;
+    Shape path = null;
+
+    //Alternately, we may have child graphical elements
+
+    int horizAdvX = -1;  //Inherits font's value if not set
+    int vertOriginX = -1;  //Inherits font's value if not set
+    int vertOriginY = -1;  //Inherits font's value if not set
+    int vertAdvY = -1;  //Inherits font's value if not set
+
+    /** Creates a new instance of Font */
+    public MissingGlyph()
+    {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        //If glyph path was specified, calculate it
+        String commandList = attrs.getValue("d");
+        if (commandList != null)
+        {
+            StyleAttribute atyleAttrib = getStyle("fill-rule");
+            String fillRule = (atyleAttrib == null) ? "nonzero" : atyleAttrib.getStringValue();
+
+            PathCommand[] commands = parsePathList(commandList);
+
+//            ExtendedGeneralPath buildPath = new ExtendedGeneralPath(
+            GeneralPath buildPath = new GeneralPath(
+                fillRule.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO,
+                commands.length);
+
+            BuildHistory hist = new BuildHistory();
+
+            for (int i = 0; i < commands.length; i++)
+            {
+                PathCommand cmd = commands[i];
+                cmd.appendPath(buildPath, hist);
+            }
+
+            //Reflect glyph path to put it in user coordinate system
+            AffineTransform at = new AffineTransform();
+            at.scale(1, -1);
+            path = at.createTransformedShape(buildPath);
+        }
+
+
+        //Read glyph spacing info
+        String horizAdvX = attrs.getValue("horiz-adv-x");
+        String vertOriginX = attrs.getValue("vert-origin-x");
+        String vertOriginY = attrs.getValue("vert-origin-y");
+        String vertAdvY = attrs.getValue("vert-adv-y");
+
+        if (horizAdvX != null) this.horizAdvX = XMLParseUtil.parseInt(horizAdvX);
+        if (vertOriginX != null) this.vertOriginX = XMLParseUtil.parseInt(vertOriginX);
+        if (vertOriginY != null) this.vertOriginY = XMLParseUtil.parseInt(vertOriginY);
+        if (vertAdvY != null) this.vertAdvY = XMLParseUtil.parseInt(vertAdvY);
+
+    }
+*/
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        String commandList = "";
+        if (getPres(sty.setName("d"))) commandList = sty.getStringValue();
+
+    
+        //If glyph path was specified, calculate it
+        if (commandList != null)
+        {
+//            StyleAttribute atyleAttrib = getStyle("fill-rule");
+            String fillRule = getStyle(sty.setName("fill-rule")) ? sty.getStringValue() : "nonzero";
+
+            PathCommand[] commands = parsePathList(commandList);
+
+//            ExtendedGeneralPath buildPath = new ExtendedGeneralPath(
+            GeneralPath buildPath = new GeneralPath(
+                fillRule.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO,
+                commands.length);
+
+            BuildHistory hist = new BuildHistory();
+
+            for (int i = 0; i < commands.length; i++)
+            {
+                PathCommand cmd = commands[i];
+                cmd.appendPath(buildPath, hist);
+            }
+
+            //Reflect glyph path to put it in user coordinate system
+            AffineTransform at = new AffineTransform();
+            at.scale(1, -1);
+            path = at.createTransformedShape(buildPath);
+        }
+
+
+        //Read glyph spacing info
+        if (getPres(sty.setName("horiz-adv-x"))) horizAdvX = sty.getIntValue();
+
+        if (getPres(sty.setName("vert-origin-x"))) vertOriginX = sty.getIntValue();
+
+        if (getPres(sty.setName("vert-origin-y"))) vertOriginY = sty.getIntValue();
+
+        if (getPres(sty.setName("vert-adv-y"))) vertAdvY = sty.getIntValue();
+    }
+
+    public Shape getPath()
+    {
+        return path;
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        //Do not push or pop stack
+
+        if (path != null) renderShape(g, path);
+        
+        Iterator it = children.iterator();
+        while (it.hasNext())
+        {
+            SVGElement ele = (SVGElement)it.next();
+            if (ele instanceof RenderableElement)
+            {
+                ((RenderableElement)ele).render(g);
+            }
+        }
+
+        //Do not push or pop stack
+    }
+
+    public int getHorizAdvX()
+    {
+        if (horizAdvX == -1)
+            horizAdvX = ((Font)parent).getHorizAdvX();
+        return horizAdvX;
+    }
+
+    public int getVertOriginX()
+    {
+        if (vertOriginX == -1)
+            vertOriginX = getHorizAdvX() / 2;
+        return vertOriginX;
+    }
+
+    public int getVertOriginY()
+    {
+        if (vertOriginY == -1)
+            vertOriginY = ((Font)parent).getFontFace().getAscent();
+        return vertOriginY;
+    }
+
+    public int getVertAdvY()
+    {
+        if (vertAdvY == -1)
+            vertAdvY = ((Font)parent).getFontFace().getUnitsPerEm();
+        return vertAdvY;
+
+    }
+
+    public Shape getShape()
+    {
+        if (path != null) return shapeToParent(path);
+        return null;
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        if (path != null) return boundsToParent(includeStrokeInBounds(path.getBounds2D()));
+        return null;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Fonts can't change
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Path.java
===================================================================
--- trunk/src/com/kitfox/svg/Path.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Path.java	(revision 4256)
@@ -0,0 +1,154 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+
+import com.kitfox.svg.pathcmd.*;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Path extends ShapeElement {
+
+//    PathCommand[] commands = null;
+
+    int fillRule = GeneralPath.WIND_NON_ZERO;
+    String d = "";
+//    ExtendedGeneralPath path;
+    GeneralPath path;
+
+    /** Creates a new instance of Rect */
+    public Path() {
+    }
+
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        StyleAttribute styleAttrib = getStyle("fill-rule");
+        String fillRule = (styleAttrib == null) ? "nonzero" : styleAttrib.getStringValue();
+        
+        String d = attrs.getValue("d");
+        path = buildPath(d, fillRule.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO);
+    }
+    */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+    
+        String fillRuleStrn  = (getStyle(sty.setName("fill-rule"))) ? sty.getStringValue() : "nonzero";
+        fillRule = fillRuleStrn.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO;
+        
+//        String d = "";
+        if (getPres(sty.setName("d"))) d = sty.getStringValue();
+
+//System.err.println(d);
+        
+        path = buildPath(d, fillRule);
+        
+//System.err.println(d);
+    }
+    
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, path);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(path);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(path.getBounds2D()));
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getStyle(sty.setName("fill-rule")))
+        {
+            int newVal = sty.getStringValue().equals("evenodd") 
+                ? GeneralPath.WIND_EVEN_ODD 
+                : GeneralPath.WIND_NON_ZERO;
+            if (newVal != fillRule)
+            {
+                fillRule = newVal;
+                changeState = true;
+            }
+        }
+        
+        if (getPres(sty.setName("d")))
+        {
+            String newVal = sty.getStringValue();
+            if (!newVal.equals(d))
+            {
+                d = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (shapeChange)
+        {
+            build();
+//            path = buildPath(d, fillRule);
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/PatternSVG.java
===================================================================
--- trunk/src/com/kitfox/svg/PatternSVG.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/PatternSVG.java	(revision 4256)
@@ -0,0 +1,306 @@
+/*
+ * Gradient.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 3:25 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.net.*;
+import java.util.*;
+import java.awt.geom.*;
+import java.awt.*;
+import java.awt.image.*;
+
+import com.kitfox.svg.pattern.*;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class PatternSVG extends FillElement {
+
+    public static final int GU_OBJECT_BOUNDING_BOX = 0;
+    public static final int GU_USER_SPACE_ON_USE = 1;
+
+    int gradientUnits = GU_OBJECT_BOUNDING_BOX;
+
+    float x;
+    float y;
+    float width;
+    float height;
+
+    AffineTransform patternXform = new AffineTransform();
+    Rectangle2D.Float viewBox;
+
+    Paint texPaint;
+
+    /** Creates a new instance of Gradient */
+    public PatternSVG() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String href = attrs.getValue("xlink:href");
+        //If we have a link to another pattern, initialize ourselves with it's values
+        if (href != null)
+        {
+//System.err.println("Gradient.loaderStartElement() href '" + href + "'");
+            try {
+                URI src = getXMLBase().resolve(href);
+//                URL url = srcUrl.toURL();
+//                URL url = new URL(helper.docRoot, href);
+                PatternSVG patSrc = (PatternSVG)helper.universe.getElement(src);
+
+                gradientUnits = patSrc.gradientUnits;
+                x = patSrc.x;
+                y = patSrc.y;
+                width = patSrc.width;
+                height = patSrc.height;
+                viewBox = patSrc.viewBox;
+                patternXform.setTransform(patSrc.patternXform);
+                members.addAll(patSrc.members);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+
+        String gradientUnits = attrs.getValue("gradientUnits");
+
+        if (gradientUnits != null)
+        {
+            if (gradientUnits.toLowerCase().equals("userspaceonuse")) this.gradientUnits = GU_USER_SPACE_ON_USE;
+            else this.gradientUnits = GU_OBJECT_BOUNDING_BOX;
+        }
+
+        String patternTransform = attrs.getValue("patternTransform");
+        if (patternTransform != null)
+        {
+            patternXform = parseTransform(patternTransform);
+        }
+
+        String x = attrs.getValue("x");
+        String y = attrs.getValue("y");
+        String width = attrs.getValue("width");
+        String height = attrs.getValue("height");
+
+        if (x != null) this.x = XMLParseUtil.parseFloat(x);
+        if (y != null) this.y = XMLParseUtil.parseFloat(y);
+        if (width != null) this.width = XMLParseUtil.parseFloat(width);
+        if (height != null) this.height = XMLParseUtil.parseFloat(height);
+
+        String viewBoxStrn = attrs.getValue("viewBox");
+        if (viewBoxStrn != null)
+        {
+            float[] dim = XMLParseUtil.parseFloatList(viewBoxStrn);
+            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
+        }
+    }
+  */  
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+
+//        members.add(child);
+    }
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+		//Load style string
+        String href = null;
+        if (getPres(sty.setName("xlink:href"))) href = sty.getStringValue();
+        //String href = attrs.getValue("xlink:href");
+        //If we have a link to another pattern, initialize ourselves with it's values
+        if (href != null)
+        {
+//System.err.println("Gradient.loaderStartElement() href '" + href + "'");
+            try {
+                URI src = getXMLBase().resolve(href);
+                PatternSVG patSrc = (PatternSVG)diagram.getUniverse().getElement(src);
+
+                gradientUnits = patSrc.gradientUnits;
+                x = patSrc.x;
+                y = patSrc.y;
+                width = patSrc.width;
+                height = patSrc.height;
+                viewBox = patSrc.viewBox;
+                patternXform.setTransform(patSrc.patternXform);
+                children.addAll(patSrc.children);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        String gradientUnits = "";
+        if (getPres(sty.setName("gradientUnits"))) gradientUnits = sty.getStringValue().toLowerCase();
+        if (gradientUnits.equals("userspaceonuse")) this.gradientUnits = GU_USER_SPACE_ON_USE;
+        else this.gradientUnits = GU_OBJECT_BOUNDING_BOX;
+
+        String patternTransform = "";
+        if (getPres(sty.setName("patternTransform"))) patternTransform = sty.getStringValue();
+        patternXform = parseTransform(patternTransform);
+
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("width"))) width = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("height"))) height = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("viewBox")))
+        {
+            float[] dim = sty.getFloatList();
+            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
+        }
+            
+        preparePattern();
+    }
+    
+/*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        build();
+    }
+    */
+
+    protected void preparePattern() throws SVGException
+    {
+        //For now, treat all fills as UserSpaceOnUse.  Otherwise, we'll need
+        // a different paint for every object.
+        int tileWidth = (int)width;
+        int tileHeight = (int)height;
+
+        float stretchX = 1f, stretchY = 1f;
+        if (!patternXform.isIdentity())
+        {
+            //Scale our source tile so that we can have nice sampling from it.
+            float xlateX = (float)patternXform.getTranslateX();
+            float xlateY = (float)patternXform.getTranslateY();
+
+            Point2D.Float pt = new Point2D.Float(), pt2 = new Point2D.Float();
+
+            pt.setLocation(width, 0);
+            patternXform.transform(pt, pt2);
+            pt2.x -= xlateX;
+            pt2.y -= xlateY;
+            stretchX = (float)Math.sqrt(pt2.x * pt2.x + pt2.y * pt2.y) * 1.5f / width;
+
+            pt.setLocation(height, 0);
+            patternXform.transform(pt, pt2);
+            pt2.x -= xlateX;
+            pt2.y -= xlateY;
+            stretchY = (float)Math.sqrt(pt2.x * pt2.x + pt2.y * pt2.y) * 1.5f / height;
+
+            tileWidth *= stretchX;
+            tileHeight *= stretchY;
+        }
+
+        if (tileWidth == 0 || tileHeight == 0) 
+        {
+            //Use defaults if tile has degenerate size
+            return;
+        }
+        
+        BufferedImage buf = new BufferedImage(tileWidth, tileHeight, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = buf.createGraphics();
+        g.setClip(0, 0, tileWidth, tileHeight);
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            if (ele instanceof RenderableElement)
+            {
+                AffineTransform xform = new AffineTransform();
+
+                if (viewBox == null)
+                {
+                    xform.translate(-x, -y);
+                }
+                else
+                {
+                    xform.scale(tileWidth / viewBox.width, tileHeight / viewBox.height);
+                    xform.translate(-viewBox.x, -viewBox.y);
+                }
+
+                g.setTransform(xform);
+                ((RenderableElement)ele).render(g);
+            }
+        }
+
+        g.dispose();
+
+//try {
+//javax.imageio.ImageIO.write(buf, "png", new java.io.File("c:\\tmp\\texPaint.png"));
+//} catch (Exception e ) {}
+
+        if (patternXform.isIdentity())
+        {
+            texPaint = new TexturePaint(buf, new Rectangle2D.Float(x, y, width, height));
+        }
+        else
+        {
+            patternXform.scale(1 / stretchX, 1 / stretchY);
+            texPaint = new PatternPaint(buf, patternXform);
+        }
+    }
+
+    public Paint getPaint(Rectangle2D bounds, AffineTransform xform)
+    {
+        return texPaint;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Patterns don't change state
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Polygon.java
===================================================================
--- trunk/src/com/kitfox/svg/Polygon.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Polygon.java	(revision 4256)
@@ -0,0 +1,178 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.XMLParseUtil;
+import java.awt.geom.*;
+import java.awt.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Polygon extends ShapeElement {
+
+    int fillRule = GeneralPath.WIND_NON_ZERO;
+    String pointsStrn = "";
+//    float[] points = null;
+    GeneralPath path;
+
+    /** Creates a new instance of Rect */
+    public Polygon() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+
+        points = XMLParseUtil.parseFloatList(attrs.getValue("points"));
+
+        build();
+    }
+*/
+/*
+    public void build()
+    {
+        StyleAttribute styleAttrib = getStyle("fill-rule");
+        String fillRule = (styleAttrib == null) ? "nonzero" : styleAttrib.getStringValue();
+
+        path = new GeneralPath(
+            fillRule.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO,
+            points.length / 2);
+
+        path.moveTo(points[0], points[1]);
+        for (int i = 2; i < points.length; i += 2)
+        {
+            path.lineTo(points[i], points[i + 1]);
+        }
+        path.closePath();
+    }
+*/
+    
+//static int yyyyy = 0;
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("points"))) pointsStrn = sty.getStringValue();
+        
+        String fillRuleStrn = getStyle(sty.setName("fill-rule")) ? sty.getStringValue() : "nonzero";
+        fillRule = fillRuleStrn.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO;
+
+        buildPath();
+    }
+    
+    protected void buildPath()
+    {
+        float[] points = XMLParseUtil.parseFloatList(pointsStrn);
+        path = new GeneralPath(fillRule, points.length / 2);
+
+        path.moveTo(points[0], points[1]);
+        for (int i = 2; i < points.length; i += 2)
+        {
+            path.lineTo(points[i], points[i + 1]);
+        }
+        path.closePath();
+    }
+    
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, path);
+        finishLayer(g);
+    }
+
+
+    public Shape getShape()
+    {
+        return shapeToParent(path);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(path.getBounds2D()));
+    }
+
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getStyle(sty.setName("fill-rule")))
+        {
+            int newVal = sty.getStringValue().equals("evenodd") 
+                ? GeneralPath.WIND_EVEN_ODD 
+                : GeneralPath.WIND_NON_ZERO;
+            if (newVal != fillRule)
+            {
+                fillRule = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("points")))
+        {
+            String newVal = sty.getStringValue();
+            if (!newVal.equals(pointsStrn))
+            {
+                pointsStrn = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        
+        if (shapeChange)
+        {
+            build();
+//            buildPath();
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Polyline.java
===================================================================
--- trunk/src/com/kitfox/svg/Polyline.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Polyline.java	(revision 4256)
@@ -0,0 +1,153 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.geom.*;
+import java.awt.*;
+
+import com.kitfox.svg.xml.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Polyline extends ShapeElement {
+
+    int fillRule = GeneralPath.WIND_NON_ZERO;
+    String pointsStrn = "";
+//    float[] points = null;
+    GeneralPath path;
+
+    /** Creates a new instance of Rect */
+    public Polyline() {
+    }
+
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+
+        points = XMLParseUtil.parseFloatList(attrs.getValue("points"));
+
+        build();
+    }
+    */
+
+    public void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("points"))) pointsStrn = sty.getStringValue();
+        
+        String fillRuleStrn = getStyle(sty.setName("fill-rule")) ? sty.getStringValue() : "nonzero";
+        fillRule = fillRuleStrn.equals("evenodd") ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO;
+
+        buildPath();
+    }
+    
+    protected void buildPath()
+    {
+        float[] points = XMLParseUtil.parseFloatList(pointsStrn);
+        path = new GeneralPath(fillRule, points.length / 2);
+
+        path.moveTo(points[0], points[1]);
+        for (int i = 2; i < points.length; i += 2)
+        {
+            path.lineTo(points[i], points[i + 1]);
+        }
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, path);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(path);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(path.getBounds2D()));
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getStyle(sty.setName("fill-rule")))
+        {
+            int newVal = sty.getStringValue().equals("evenodd") 
+                ? GeneralPath.WIND_EVEN_ODD 
+                : GeneralPath.WIND_NON_ZERO;
+            if (newVal != fillRule)
+            {
+                fillRule = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("points")))
+        {
+            String newVal = sty.getStringValue();
+            if (!newVal.equals(pointsStrn))
+            {
+                pointsStrn = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        
+        if (shapeChange)
+        {
+            build();
+//            buildPath();
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/RadialGradient.java
===================================================================
--- trunk/src/com/kitfox/svg/RadialGradient.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/RadialGradient.java	(revision 4256)
@@ -0,0 +1,222 @@
+/*
+ * RadialGradient.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:55 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.geom.*;
+import java.awt.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+//import org.apache.batik.ext.awt.*;
+import com.kitfox.svg.batik.*;
+
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class RadialGradient extends Gradient {
+
+    float cx = 0.5f;
+    float cy = 0.5f;
+    float fx = 0.5f;
+    float fy = 0.5f;
+    float r = 0.5f;
+
+    /** Creates a new instance of RadialGradient */
+    public RadialGradient() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String cx = attrs.getValue("cx");
+        String cy = attrs.getValue("cy");
+        String fx = attrs.getValue("fx");
+        String fy = attrs.getValue("fy");
+        String r = attrs.getValue("r");
+
+        if (cx != null) this.cx = (float)XMLParseUtil.parseRatio(cx);
+        if (cy != null) this.cy = (float)XMLParseUtil.parseRatio(cy);
+        if (fx != null) this.fx = (float)XMLParseUtil.parseRatio(fx);
+        if (fy != null) this.fy = (float)XMLParseUtil.parseRatio(fy);
+        if (r != null) this.r = (float)XMLParseUtil.parseRatio(r);
+    }
+    */
+
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        super.loaderEndElement(helper);
+        
+        build();
+    }
+     */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("cx"))) cx = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("cy"))) cy = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("fx"))) fx = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("fy"))) fy = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("r"))) r = sty.getFloatValueWithUnits();
+    }
+    
+    public Paint getPaint(Rectangle2D bounds, AffineTransform xform)
+    {
+        com.kitfox.svg.batik.MultipleGradientPaint.CycleMethodEnum method;
+        switch (spreadMethod)
+        {
+            default:
+            case SM_PAD:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.NO_CYCLE;
+                break;
+            case SM_REPEAT:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.REPEAT;
+                break;
+            case SM_REFLECT:
+                method = com.kitfox.svg.batik.MultipleGradientPaint.REFLECT;
+                break;
+        }
+
+        com.kitfox.svg.batik.RadialGradientPaint paint;
+
+        if (gradientUnits == GU_USER_SPACE_ON_USE)
+        {
+            paint = new com.kitfox.svg.batik.RadialGradientPaint(
+                new Point2D.Float(cx, cy),
+                r,
+                new Point2D.Float(fx, fy),
+                getStopFractions(),
+                getStopColors(),
+                method,
+                com.kitfox.svg.batik.MultipleGradientPaint.SRGB,
+                gradientTransform);
+        }
+        else
+        {
+            AffineTransform viewXform = new AffineTransform();
+            viewXform.translate(bounds.getX(), bounds.getY());
+            viewXform.scale(bounds.getWidth(), bounds.getHeight());
+
+            viewXform.concatenate(gradientTransform);
+
+            paint = new com.kitfox.svg.batik.RadialGradientPaint(
+                new Point2D.Float(cx, cy),
+                r,
+                new Point2D.Float(fx, fy),
+                getStopFractions(),
+                getStopColors(),
+                method,
+                com.kitfox.svg.batik.MultipleGradientPaint.SRGB,
+                viewXform);
+        }
+
+        return paint;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("cx")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != cx)
+            {
+                cx = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("cy")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != cy)
+            {
+                cy = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("fx")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != fx)
+            {
+                fx = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("fy")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != fy)
+            {
+                fy = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("r")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != r)
+            {
+                r = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        return changeState;
+    }
+}
Index: trunk/src/com/kitfox/svg/Rect.java
===================================================================
--- trunk/src/com/kitfox/svg/Rect.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Rect.java	(revision 4256)
@@ -0,0 +1,268 @@
+/*
+ * Rect.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:25 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Rect extends ShapeElement {
+
+    float x = 0f;
+    float y = 0f;
+    float width = 0f;
+    float height = 0f;
+    float rx = 0f;
+    float ry = 0f;
+
+    RectangularShape rect;
+
+    /** Creates a new instance of Rect */
+    public Rect() {
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException    
+    {
+        out.writeFloat(x);
+        out.writeFloat(y);
+        out.writeFloat(width);
+        out.writeFloat(height);
+        out.writeFloat(rx);
+        out.writeFloat(ry);
+    }
+    
+    private void readObject(ObjectInputStream in) throws IOException
+    {
+        x = in.readFloat();
+        y = in.readFloat();
+        width = in.readFloat();
+        height = in.readFloat();
+        rx = in.readFloat();
+        ry = in.readFloat();
+        
+        if (rx == 0f && ry == 0f)
+        {
+            rect = new Rectangle2D.Float(x, y, width, height);
+        }
+        else
+        {
+            rect = new RoundRectangle2D.Float(x, y, width, height, rx * 2, ry * 2);
+        }
+    }
+    
+    /*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String x = attrs.getValue("x");
+        String y = attrs.getValue("y");
+        String width = attrs.getValue("width");
+        String height = attrs.getValue("height");
+        String rx = attrs.getValue("rx");
+        String ry = attrs.getValue("ry");
+
+        if (rx == null) rx = ry;
+        if (ry == null) ry = rx;
+
+        this.x = XMLParseUtil.parseFloat(x);
+        this.y = XMLParseUtil.parseFloat(y);
+        this.width = XMLParseUtil.parseFloat(width);
+        this.height = XMLParseUtil.parseFloat(height);
+        if (rx != null)
+        {
+            this.rx = XMLParseUtil.parseFloat(rx);
+            this.ry = XMLParseUtil.parseFloat(ry);
+        }
+
+        build();
+//        setBounds(this.x, this.y, this.width, this.height);
+    }
+*/
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+//        SVGElement parent = this.getParent();
+//        if (parent instanceof RenderableElement)
+//        {
+//            RenderableElement re = (RenderableElement)parent;
+//            Rectangle2D bounds = re.getBoundingBox();
+//            bounds = null;
+//        }
+        
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("width"))) width = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("height"))) height = sty.getFloatValueWithUnits();
+
+        boolean rxSet = false;
+        if (getPres(sty.setName("rx"))) { rx = sty.getFloatValueWithUnits(); rxSet = true; }
+        
+        boolean rySet = false;
+        if (getPres(sty.setName("ry"))) { ry = sty.getFloatValueWithUnits(); rySet = true; }
+        
+        if (!rxSet) rx = ry;
+        if (!rySet) ry = rx;
+
+        
+        if (rx == 0f && ry == 0f)
+        {
+            rect = new Rectangle2D.Float(x, y, width, height);
+        }
+        else
+        {
+            rect = new RoundRectangle2D.Float(x, y, width, height, rx * 2, ry * 2);
+        }
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, rect);
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        return shapeToParent(rect);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(rect.getBounds2D()));
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("width")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != width)
+            {
+                width = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("height")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != height)
+            {
+                height = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("rx")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != rx)
+            {
+                rx = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("ry")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != ry)
+            {
+                ry = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (shapeChange)
+        {
+            build();
+//            if (rx == 0f && ry == 0f)
+//            {
+//                rect = new Rectangle2D.Float(x, y, width, height);
+//            }
+//            else
+//            {
+//                rect = new RoundRectangle2D.Float(x, y, width, height, rx * 2, ry * 2);
+//            }
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/RenderableElement.java
===================================================================
--- trunk/src/com/kitfox/svg/RenderableElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/RenderableElement.java	(revision 4256)
@@ -0,0 +1,167 @@
+/*
+ * BoundedElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 9:00 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.net.URI;
+import java.util.List;
+
+
+
+/**
+ * Maintains bounding box for this element
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class RenderableElement extends TransformableElement 
+{
+
+    AffineTransform cachedXform = null;
+    Shape cachedClip = null;
+    
+    public static final int VECTOR_EFFECT_NONE = 0;
+    public static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1;
+    int vectorEffect;
+
+    /** Creates a new instance of BoundedElement */
+    public RenderableElement() {
+    }
+
+    public RenderableElement(String id, SVGElement parent)
+    {
+        super(id, parent);
+    }
+
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("vector-effect")))
+        {
+            if ("non-scaling-stroke".equals(sty.getStringValue()))
+            {
+                vectorEffect = VECTOR_EFFECT_NON_SCALING_STROKE;
+            }
+            else
+            {
+                vectorEffect = VECTOR_EFFECT_NONE;
+            }
+        }
+        else
+        {
+            vectorEffect = VECTOR_EFFECT_NONE;
+        }
+    }
+    
+    abstract public void render(Graphics2D g) throws SVGException;
+    
+    abstract void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException;
+    
+    abstract void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException;
+    
+    abstract public Rectangle2D getBoundingBox() throws SVGException;
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+        super.loaderStartElement(helper, attrs, parent);
+    }
+*/
+    /**
+     * Pushes transform stack, transforms to local coordinates and sets up
+     * clipping mask.
+     */
+    protected void beginLayer(Graphics2D g) throws SVGException
+    {
+        if (xform != null)
+        {
+            cachedXform = g.getTransform();
+            g.transform(xform);
+        }
+
+        StyleAttribute styleAttrib = new StyleAttribute();
+        
+        //Get clipping path
+//        StyleAttribute styleAttrib = getStyle("clip-path", false);
+        Shape clipPath = null;
+        int clipPathUnits = ClipPath.CP_USER_SPACE_ON_USE;
+        if (getStyle(styleAttrib.setName("clip-path")))
+        {
+            URI uri = styleAttrib.getURIValue(getXMLBase());
+            if (uri != null)
+            {
+                ClipPath ele = (ClipPath)diagram.getUniverse().getElement(uri);
+                clipPath = ele.getClipPathShape();
+                clipPathUnits = ele.getClipPathUnits();
+            }
+        }
+
+        //Return if we're out of clipping range
+        if (clipPath != null)
+        {
+            if (clipPathUnits == ClipPath.CP_OBJECT_BOUNDING_BOX && (this instanceof ShapeElement))
+            {
+                Rectangle2D rect = ((ShapeElement)this).getBoundingBox();
+                AffineTransform at = new AffineTransform();
+                at.scale(rect.getWidth(), rect.getHeight());
+                clipPath = at.createTransformedShape(clipPath);
+            }
+
+            cachedClip = g.getClip();
+            Area newClip = new Area(cachedClip);
+            newClip.intersect(new Area(clipPath));
+            g.setClip(newClip);
+        }
+    }
+
+    /**
+     * Restores transform and clipping values to the way they were before
+     * this layer was drawn.
+     */
+    protected void finishLayer(Graphics2D g)
+    {
+        if (cachedClip != null)
+        {
+            g.setClip(cachedClip);
+        }
+
+        if (cachedXform != null)
+        {
+            g.setTransform(cachedXform);
+        }
+    }
+
+}
Index: trunk/src/com/kitfox/svg/SVGCache.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGCache.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGCache.java	(revision 4256)
@@ -0,0 +1,28 @@
+/*
+ * SVGUniverseSingleton.java
+ *
+ * Created on April 2, 2005, 1:54 AM
+ */
+
+package com.kitfox.svg;
+
+/**
+ * A convienience singleton for allowing all classes to access a common SVG universe.
+ *
+ * @author kitfox
+ */
+public class SVGCache
+{
+    private static final SVGUniverse svgUniverse = new SVGUniverse();
+    
+    /** Creates a new instance of SVGUniverseSingleton */
+    private SVGCache()
+    {
+    }
+
+    public static SVGUniverse getSVGUniverse()
+    {
+        return svgUniverse;
+    }
+    
+}
Index: trunk/src/com/kitfox/svg/SVGDiagram.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGDiagram.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGDiagram.java	(revision 4256)
@@ -0,0 +1,245 @@
+/*
+ * SVGDiagram.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 5:04 PM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * Top level structure in an SVG tree.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGDiagram implements Serializable
+{
+    public static final long serialVersionUID = 0;
+    
+    //Indexes elements within this SVG diagram
+    final HashMap idMap = new HashMap();
+
+    SVGRoot root;
+    final SVGUniverse universe;
+
+    /**
+     * This is used by the SVGRoot to determine the width of the 
+     */
+    private Rectangle deviceViewport = new Rectangle(100, 100);
+
+    /**
+     * If true, no attempt will be made to discard geometry based on it being
+     * out of bounds.  This trades potentially drawing many out of bounds
+     * shapes with having to recalculate bounding boxes every animation iteration.
+     */
+    protected boolean ignoreClipHeuristic = false;
+
+    /**
+     * URL which uniquely identifies this document
+     */
+//    final URI docRoot;
+
+    /**
+     * URI that uniquely identifies this document.  Also used to resolve
+     * relative urls.  Default base for document.
+     */
+    final URI xmlBase;
+
+    /** Creates a new instance of SVGDiagram */
+    public SVGDiagram(URI xmlBase, SVGUniverse universe)
+    {
+        this.universe = universe;
+//        this.docRoot = docRoot;
+        this.xmlBase = xmlBase;
+    }
+
+    /**
+     * Draws this diagram to the passed graphics context
+     */
+    public void render(Graphics2D g) throws SVGException
+    {
+        root.render(g);
+    }
+    
+    /**
+     * Searches thorough the scene graph for all RenderableElements that have
+     * shapes that contain the passed point.
+     * 
+     * For every shape which contains the pick point, a List containing the
+     * path to the node is added to the return list.  That is, the result of
+     * SVGElement.getPath() is added for each entry.
+     *
+     * @return the passed in list
+     */
+    public List pick(Point2D point, List retVec) throws SVGException
+    {
+        return pick(point, false, retVec);
+    }
+    
+    public List pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
+    {
+        if (retVec == null)
+        {
+            retVec = new ArrayList();
+        }
+        
+        root.pick(point, boundingBox, retVec);
+        
+        return retVec;
+    }
+
+    public List pick(Rectangle2D pickArea, List retVec) throws SVGException
+    {
+        return pick(pickArea, false, retVec);
+    }
+    
+    public List pick(Rectangle2D pickArea, boolean boundingBox, List retVec) throws SVGException
+    {
+        if (retVec == null)
+        {
+            retVec = new ArrayList();
+        }
+        
+        root.pick(pickArea, new AffineTransform(), boundingBox, retVec);
+        
+        return retVec;
+    }
+
+    public SVGUniverse getUniverse()
+    {
+        return universe;
+    }
+
+    public URI getXMLBase()
+    {
+        return xmlBase;
+    }
+
+//    public URL getDocRoot()
+//    {
+//        return docRoot;
+//    }
+
+    public float getWidth()
+    {
+        if (root == null) return 0;
+        return root.getDeviceWidth();
+    }
+    
+    public float getHeight()
+    {
+        if (root == null) return 0;
+        return root.getDeviceHeight();
+    }
+    
+    /**
+     * Returns the viewing rectangle of this diagram in device coordinates.
+     */
+    public Rectangle2D getViewRect(Rectangle2D rect)
+    {
+        if (root != null) return root.getDeviceRect(rect);
+        return rect;
+    }
+
+    public Rectangle2D getViewRect()
+    {
+        return getViewRect(new Rectangle2D.Double());
+    }
+
+    public SVGElement getElement(String name)
+    {
+        return (SVGElement)idMap.get(name);
+    }
+
+    public void setElement(String name, SVGElement node)
+    {
+        idMap.put(name, node);
+    }
+
+    public void removeElement(String name)
+    {
+        idMap.remove(name);
+    }
+
+    public SVGRoot getRoot()
+    {
+        return root;
+    }
+
+    public void setRoot(SVGRoot root)
+    {
+        this.root = root;
+    }
+
+    public boolean ignoringClipHeuristic() { return ignoreClipHeuristic; }
+
+    public void setIgnoringClipHeuristic(boolean ignoreClipHeuristic) { this.ignoreClipHeuristic = ignoreClipHeuristic; }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     */
+    public void updateTime(double curTime) throws SVGException
+    {
+        if (root == null) return;
+        root.updateTime(curTime);
+    }
+
+    public Rectangle getDeviceViewport()
+    {
+        return deviceViewport;
+    }
+
+    /**
+     * Sets the dimensions of the device being rendered into.  This is used by
+     * SVGRoot when its x, y, width or height parameters are specified as
+     * percentages.
+     */
+    public void setDeviceViewport(Rectangle deviceViewport)
+    {
+        this.deviceViewport.setBounds(deviceViewport);
+        if (root != null)
+        {
+            try
+            {
+                root.build();
+            } catch (SVGException ex)
+            {
+                ex.printStackTrace();
+            }
+        }
+    }
+}
Index: trunk/src/com/kitfox/svg/SVGDisplayPanel.form
===================================================================
--- trunk/src/com/kitfox/svg/SVGDisplayPanel.form	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGDisplayPanel.form	(revision 4256)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Events>
+    <EventHandler event="componentResized" listener="java.awt.event.ComponentListener" parameters="java.awt.event.ComponentEvent" handler="formComponentResized"/>
+  </Events>
+  <AuxValues>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+</Form>
Index: trunk/src/com/kitfox/svg/SVGDisplayPanel.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGDisplayPanel.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGDisplayPanel.java	(revision 4256)
@@ -0,0 +1,195 @@
+/*
+ * SVGDisplayPanel.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 20, 2004, 12:29 PM
+ */
+
+package com.kitfox.svg;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGDisplayPanel extends javax.swing.JPanel implements Scrollable
+{
+    public static final long serialVersionUID = 1;
+    
+    SVGDiagram diagram = null;
+    float scale = 1f;
+    Color bgColor = null;
+
+    /** Creates new form SVGDisplayPanel */
+    public SVGDisplayPanel()
+    {
+        initComponents();
+    }
+
+    public SVGDiagram getDiagram()
+    {
+        return diagram;
+    }
+    
+    public void setDiagram(SVGDiagram diagram)
+    {
+        this.diagram = diagram;
+        diagram.setDeviceViewport(getBounds());
+        
+        setDimension();
+    }
+
+    public void setScale(float scale)
+    {
+        this.scale = scale;
+        setDimension();
+    }
+
+    public void setBgColor(Color col)
+    {
+        bgColor = col;
+    }
+
+    private void setDimension()
+    {
+        if (diagram == null)
+        {
+            setPreferredSize(new Dimension(1, 1));
+            revalidate();
+            return;
+        }
+
+        final Rectangle2D.Float rect = new Rectangle2D.Float();
+        diagram.getViewRect(rect);
+
+        int w = (int)(rect.width * scale);
+        int h = (int)(rect.height * scale);
+
+        setPreferredSize(new Dimension(w, h));
+        revalidate();
+    }
+
+    /**
+     * Update this image to reflect the passed time
+     */
+    public void updateTime(double curTime) throws SVGException
+    {
+        if (diagram == null) return;
+        
+        diagram.updateTime(curTime);
+    }
+    
+    public void paintComponent(Graphics gg)
+    {
+        Graphics2D g = (Graphics2D)gg;
+
+        if (bgColor != null)
+        {
+            Dimension dim = getSize();
+            g.setColor(bgColor);
+            g.fillRect(0, 0, dim.width, dim.height);
+        }
+
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        if (diagram != null) 
+        {
+            try
+            {
+                diagram.render(g);
+            }
+            catch (SVGException e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /** This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the Form Editor.
+     */
+    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+    private void initComponents()
+    {
+
+        setLayout(new java.awt.BorderLayout());
+
+        addComponentListener(new java.awt.event.ComponentAdapter()
+        {
+            public void componentResized(java.awt.event.ComponentEvent evt)
+            {
+                formComponentResized(evt);
+            }
+        });
+
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void formComponentResized(java.awt.event.ComponentEvent evt)//GEN-FIRST:event_formComponentResized
+    {//GEN-HEADEREND:event_formComponentResized
+        if (diagram != null)
+        {
+            diagram.setDeviceViewport(getBounds());
+            setDimension();
+        }
+
+    }//GEN-LAST:event_formComponentResized
+
+    public Dimension getPreferredScrollableViewportSize()
+    {
+        return getPreferredSize();
+    }
+
+    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
+    {
+        if (orientation == SwingConstants.HORIZONTAL)
+        {
+            return visibleRect.width;
+        }
+        else return visibleRect.height;
+    }
+
+    public boolean getScrollableTracksViewportHeight()
+    {
+        return false;
+    }
+
+    public boolean getScrollableTracksViewportWidth()
+    {
+        return false;
+    }
+
+    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
+    {
+        return getScrollableBlockIncrement(visibleRect, orientation, direction) / 16;
+    }
+
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    // End of variables declaration//GEN-END:variables
+
+}
Index: trunk/src/com/kitfox/svg/SVGElement.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGElement.java	(revision 4256)
@@ -0,0 +1,753 @@
+/*
+ * SVGElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:59 AM
+ */
+
+package com.kitfox.svg;
+
+import java.util.*;
+import java.util.regex.*;
+import java.net.*;
+import java.awt.geom.*;
+
+import org.xml.sax.*;
+import com.kitfox.svg.pathcmd.*;
+import com.kitfox.svg.xml.*;
+import java.io.Serializable;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class SVGElement implements Serializable
+{
+    public static final long serialVersionUID = 0;
+    
+    public static final String SVG_NS = "http://www.w3.org/2000/svg";
+    
+    protected SVGElement parent = null;
+    
+    protected final ArrayList children = new ArrayList();
+    
+    protected String id = null;
+    /**
+     * CSS class.  Used for applying style sheet information.
+     */
+    protected String cssClass = null;
+    
+    /**
+     * Styles defined for this elemnt via the <b>style</b> attribute.
+     */
+    protected final HashMap inlineStyles = new HashMap();
+    
+    /**
+     * Presentation attributes set for this element.  Ie, any attribute other
+     * than the <b>style</b> attribute.
+     */
+    protected final HashMap presAttribs = new HashMap();
+    
+    /**
+     * A list of presentation attributes to not include in the presentation
+     * attribute set.
+     */
+    protected static final Set ignorePresAttrib;
+    static
+    {
+        HashSet set = new HashSet();
+//        set.add("id");
+//        set.add("class");
+//        set.add("style");
+//        set.add("xml:base");
+        
+        ignorePresAttrib = Collections.unmodifiableSet(set);
+    }
+    
+    /**
+     * This element may override the URI we resolve against with an
+     * xml:base attribute.  If so, a copy is placed here.  Otherwise, we defer
+     * to our parent for the reolution base
+     */
+    protected URI xmlBase = null;
+    
+    /**
+     * The diagram this element belongs to
+     */
+    protected SVGDiagram diagram;
+    /**
+     * Link to the universe we reside in
+     */
+//    protected SVGUniverse universe;
+    
+    boolean dirty = true;
+
+//    public static final Matcher adobeId = Pattern.compile("(.*)_1_").matcher("");
+//    static final String fpNumRe = "[-+]?((\\d+)|(\\d*\\.\\d+))([-+]?[eE]\\d+)?";
+    
+    /** Creates a new instance of SVGElement */
+    public SVGElement()
+    {
+        this(null, null, null);
+    }
+    
+    public SVGElement(String id, SVGElement parent)
+    {
+        this(id, null, parent);
+    }
+    
+    public SVGElement(String id, String cssClass, SVGElement parent)
+    {
+        this.id = id;
+        this.cssClass = cssClass;
+        this.parent = parent;
+    }
+    
+    public SVGElement getParent()
+    {
+        return parent;
+    }
+    
+    void setParent(SVGElement parent)
+    {
+        this.parent = parent;
+    }
+    
+    /**
+     * @return an ordered list of nodes from the root of the tree to this node
+     */
+    public List getPath(List retVec)
+    {
+        if (retVec == null) retVec = new ArrayList();
+        
+        if (parent != null)
+        {
+            parent.getPath(retVec);
+        }
+        retVec.add(this);
+        
+        return retVec;
+    }
+    
+    /**
+     * @param retVec - A list to add all children to.  If null, a new list is
+     * created and children of this group are added.
+     *
+     * @return The list containing the children of this group
+     */
+    public List getChildren(List retVec)
+    {
+        if (retVec == null) retVec = new ArrayList();
+        
+        retVec.addAll(children);
+        
+        return retVec;
+    }
+    
+    /**
+     * @param id - Id of svg element to return
+     * @return the child of the given id, or null if no such child exists.
+     */
+    public SVGElement getChild(String id)
+    {
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            String eleId = ele.getId();
+            if (eleId != null && eleId.equals(id)) return ele;
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Searches children for given element.  If found, returns index of child.
+     * Otherwise returns -1.
+     */
+    public int indexOfChild(SVGElement child)
+    {
+        return children.indexOf(child);
+    }
+    
+    /**
+     * Swaps 2 elements in children.
+     * @i index of first
+     * @j index of second
+     *
+     * @return true if successful, false otherwise
+     */
+    public void swapChildren(int i, int j) throws SVGException
+    {
+        if ((children == null) || (i < 0) || (i >= children.size()) || (j < 0) || (j >= children.size()))
+        {
+            return;
+        }
+        
+        Object temp = children.get(i);
+        children.set(i, children.get(j));
+        children.set(j, temp);
+        build();
+    }
+    
+    /**
+     * Called during SAX load process to notify that this tag has begun the process
+     * of being loaded
+     * @param attrs - Attributes of this tag
+     * @param helper - An object passed to all SVG elements involved in this build
+     * process to aid in sharing information.
+     */
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+    {
+        //Set identification info
+        this.parent = parent;
+        this.diagram = helper.diagram;
+        
+        this.id = attrs.getValue("id");
+        if (this.id != null && !this.id.equals(""))
+        {
+            diagram.setElement(this.id, this);
+        }
+        
+        String className = attrs.getValue("class");
+        this.cssClass = (className == null || className.equals("")) ? null : className;
+        //docRoot = helper.docRoot;
+        //universe = helper.universe;
+        
+        //Parse style string, if any
+        String style = attrs.getValue("style");
+        if (style != null)
+        {
+            HashMap map = XMLParseUtil.parseStyle(style, inlineStyles);
+        }
+        
+        String base = attrs.getValue("xml:base");
+        if (base != null && !base.equals(""))
+        {
+            try
+            {
+                xmlBase = new URI(base);
+            }
+            catch (Exception e)
+            {
+                throw new SAXException(e);
+            }
+        }
+        
+        //Place all other attributes into the presentation attribute list
+        int numAttrs = attrs.getLength();
+        for (int i = 0; i < numAttrs; i++)
+        {
+            String name = attrs.getQName(i);
+            if (ignorePresAttrib.contains(name)) continue;
+            String value = attrs.getValue(i);
+            
+            presAttribs.put(name, new StyleAttribute(name, value));
+        }
+    }
+    
+    /**
+     * @return a set of Strings that corespond to CSS attributes on this element
+     */
+    public Set getInlineAttributes()
+    {
+        return inlineStyles.keySet();
+    }
+    
+    /**
+     * @return a set of Strings that corespond to XML attributes on this element
+     */
+    public Set getPresentationAttributes()
+    {
+        return presAttribs.keySet();
+    }
+    
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        children.add(child);
+        child.parent = this;
+        child.setDiagram(diagram);
+    }
+    
+    private void setDiagram(SVGDiagram diagram)
+    {
+        this.diagram = diagram;
+        diagram.setElement(id, this);
+        for (Iterator it = children.iterator(); it.hasNext();)
+        {
+            SVGElement ele = (SVGElement)it.next();
+            ele.setDiagram(diagram);
+        }
+    }
+    
+    public void removeChild(SVGElement child) throws SVGElementException
+    {
+        if (!children.contains(child))
+        {
+            throw new SVGElementException(this, "Element does not contain child " + child);
+        }
+        
+        children.remove(child);
+    }
+    
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+    }
+    
+    /**
+     * Called to indicate that this tag and the tags it contains have been completely
+     * processed, and that it should finish any load processes.
+     */
+    public void loaderEndElement(SVGLoaderHelper helper) throws SVGParseException
+    {
+        try
+        {
+            build();
+        }
+        catch (SVGException se)
+        {
+            throw new SVGParseException(se);
+        }
+    }
+    
+    /**
+     * Called by internal processes to rebuild the geometry of this node
+     * from it's presentation attributes, style attributes and animated tracks.
+     */
+    protected void build() throws SVGException
+    {
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("id")))
+        {
+            String newId = sty.getStringValue();
+            if (!newId.equals(id))
+            {
+                diagram.removeElement(id);
+                id = newId;
+                diagram.setElement(this.id, this);
+            }
+        }
+        if (getPres(sty.setName("class"))) cssClass = sty.getStringValue();
+        if (getPres(sty.setName("xml:base"))) xmlBase = sty.getURIValue();
+    }
+    
+    public URI getXMLBase()
+    {
+        return xmlBase != null ? xmlBase :
+            (parent != null ? parent.getXMLBase() : diagram.getXMLBase());
+    }
+
+    /**
+     * @return the id assigned to this node.  Null if no id explicitly set.
+     */
+    public String getId()
+    {
+        return id; 
+    }
+    
+    
+    LinkedList contexts = new LinkedList();
+    
+    /**
+     * Hack to allow nodes to temporarily change their parents.  The Use tag will
+     * need this so it can alter the attributes that a particular node uses.
+     */
+    protected void pushParentContext(SVGElement context)
+    {
+        contexts.addLast(context);
+    }
+
+    protected SVGElement popParentContext()
+    {
+        return (SVGElement)contexts.removeLast();
+    }
+    
+    protected SVGElement getParentContext()
+    {
+        return contexts.isEmpty() ? null : (SVGElement)contexts.getLast();
+    }
+    
+    /*
+     * Returns the named style attribute.  Checks for inline styles first, then
+     * internal and extranal style sheets, and finally checks for presentation
+     * attributes.
+     * @param styleName - Name of attribute to return
+     * @param recursive - If true and this object does not contain the
+     * named style attribute, checks attributes of parents abck to root until
+     * one found.
+     */
+    public boolean getStyle(StyleAttribute attrib) throws SVGException
+    {
+        return getStyle(attrib, true);
+    }
+    
+    /**
+     * Copies the current style into the passed style attribute.  Checks for
+     * inline styles first, then internal and extranal style sheets, and
+     * finally checks for presentation attributes.  Recursively checks parents.
+     * @param attrib - Attribute to write style data to.  Must have it's name
+     * set to the name of the style being queried.
+     * @param recursive - If true and this object does not contain the
+     * named style attribute, checks attributes of parents abck to root until
+     * one found.
+     */
+    public boolean getStyle(StyleAttribute attrib, boolean recursive) throws SVGException
+    {
+        String styName = attrib.getName();
+        
+        //Check for local inline styles
+        StyleAttribute styAttr = (StyleAttribute)inlineStyles.get(styName);
+        
+        attrib.setStringValue(styAttr == null ? "" : styAttr.getStringValue());
+        
+        //Return if we've found a non animated style
+        if (styAttr != null) return true;
+        
+        
+        
+        //Implement style sheet lookup later
+        
+        //Check for presentation attribute
+        StyleAttribute presAttr = (StyleAttribute)presAttribs.get(styName);
+        
+        attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue());
+        
+        //Return if we've found a presentation attribute instead
+        if (presAttr != null) return true;
+        
+        
+        //If we're recursive, check parents
+        if (recursive)
+        {
+            SVGElement parentContext = getParentContext();
+            if (parentContext != null) return parentContext.getStyle(attrib, true);
+            if (parent != null) return parent.getStyle(attrib, true);
+        }
+        
+        //Unsuccessful reading style attribute
+        return false;
+    }
+    
+    /**
+     * @return the raw style value of this attribute.  Does not take the 
+     * presentation value or animation into consideration.  Used by animations 
+     * to determine the base to animate from.
+     */
+    public StyleAttribute getStyleAbsolute(String styName)
+    {
+        //Check for local inline styles
+        return (StyleAttribute)inlineStyles.get(styName);
+    }
+    
+    /**
+     * Copies the presentation attribute into the passed one.
+     * @return - True if attribute was read successfully
+     */
+    public boolean getPres(StyleAttribute attrib) throws SVGException
+    {
+        String presName = attrib.getName();
+        
+        //Make sure we have a coresponding presentation attribute
+        StyleAttribute presAttr = (StyleAttribute)presAttribs.get(presName);
+        
+        //Copy presentation value directly
+        attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue());
+        
+        //Return if we found presentation attribute
+        if (presAttr != null) return true;
+        
+        return false;
+    }
+    
+    /**
+     * @return the raw presentation value of this attribute.  Ignores any 
+     * modifications applied by style attributes or animation.  Used by 
+     * animations to determine the starting point to animate from
+     */
+    public StyleAttribute getPresAbsolute(String styName)
+    {
+        //Check for local inline styles
+        return (StyleAttribute)presAttribs.get(styName);
+    }
+    
+    static protected AffineTransform parseTransform(String val) throws SVGException
+    {
+        final Matcher matchExpression = Pattern.compile("\\w+\\([^)]*\\)").matcher("");
+        
+        AffineTransform retXform = new AffineTransform();
+        
+        matchExpression.reset(val);
+        while (matchExpression.find())
+        {
+            retXform.concatenate(parseSingleTransform(matchExpression.group()));
+        }
+        
+        return retXform;
+    }
+    
+    static public AffineTransform parseSingleTransform(String val) throws SVGException
+    {
+        final Matcher matchWord = Pattern.compile("[-.\\w]+").matcher("");
+        
+        AffineTransform retXform = new AffineTransform();
+        
+        matchWord.reset(val);
+        if (!matchWord.find())
+        {
+            //Return identity transformation if no data present (eg, empty string)
+            return retXform;
+        }
+        
+        String function = matchWord.group().toLowerCase();
+        
+        LinkedList termList = new LinkedList();
+        while (matchWord.find())
+        {
+            termList.add(matchWord.group());
+        }
+        
+        
+        double[] terms = new double[termList.size()];
+        Iterator it = termList.iterator();
+        int count = 0;
+        while (it.hasNext())
+        {
+            terms[count++] = XMLParseUtil.parseDouble((String)it.next());
+        }
+        
+        //Calculate transformation
+        if (function.equals("matrix"))
+        {
+            retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]);
+        }
+        else if (function.equals("translate"))
+        {
+            retXform.setToTranslation(terms[0], terms[1]);
+        }
+        else if (function.equals("scale"))
+        {
+            if (terms.length > 1)
+                retXform.setToScale(terms[0], terms[1]);
+            else
+                retXform.setToScale(terms[0], terms[0]);
+        }
+        else if (function.equals("rotate"))
+        {
+            if (terms.length > 2)
+                retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]);
+            else
+                retXform.setToRotation(Math.toRadians(terms[0]));
+        }
+        else if (function.equals("skewx"))
+        {
+            retXform.setToShear(Math.toRadians(terms[0]), 0.0);
+        }
+        else if (function.equals("skewy"))
+        {
+            retXform.setToShear(0.0, Math.toRadians(terms[0]));
+        }
+        else
+        {
+            throw new SVGException("Unknown transform type");
+        }
+        
+        return retXform;
+    }
+    
+    static protected float nextFloat(LinkedList l)
+    {
+        String s = (String)l.removeFirst();
+        return Float.parseFloat(s);
+    }
+    
+    static protected PathCommand[] parsePathList(String list)
+    {
+        final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(list);
+        
+        //Tokenize
+        LinkedList tokens = new LinkedList();
+        while (matchPathCmd.find())
+        {
+            tokens.addLast(matchPathCmd.group());
+        }
+        
+        
+        boolean defaultRelative = false;
+        LinkedList cmdList = new LinkedList();
+        char curCmd = 'Z';
+        while (tokens.size() != 0)
+        {
+            String curToken = (String)tokens.removeFirst();
+            char initChar = curToken.charAt(0);
+            if ((initChar >= 'A' && initChar <='Z') || (initChar >= 'a' && initChar <='z'))
+            {
+                curCmd = initChar;
+            }
+            else
+            {
+                tokens.addFirst(curToken);
+            }
+            
+            PathCommand cmd = null;
+            
+            switch (curCmd)
+            {
+                case 'M':
+                    cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens));
+                    curCmd = 'L';
+                    break;
+                case 'm':
+                    cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens));
+                    curCmd = 'l';
+                    break;
+                case 'L':
+                    cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'l':
+                    cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'H':
+                    cmd = new Horizontal(false, nextFloat(tokens));
+                    break;
+                case 'h':
+                    cmd = new Horizontal(true, nextFloat(tokens));
+                    break;
+                case 'V':
+                    cmd = new Vertical(false, nextFloat(tokens));
+                    break;
+                case 'v':
+                    cmd = new Vertical(true, nextFloat(tokens));
+                    break;
+                case 'A':
+                    cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens),
+                            nextFloat(tokens) == 1f, nextFloat(tokens) == 1f,
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'a':
+                    cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens),
+                            nextFloat(tokens) == 1f, nextFloat(tokens) == 1f,
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'Q':
+                    cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'q':
+                    cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'T':
+                    cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 't':
+                    cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'C':
+                    cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'c':
+                    cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'S':
+                    cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 's':
+                    cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens),
+                            nextFloat(tokens), nextFloat(tokens));
+                    break;
+                case 'Z':
+                case 'z':
+                    cmd = new Terminal();
+                    break;
+                default:
+                    throw new RuntimeException("Invalid path element");
+            }
+            
+            cmdList.add(cmd);
+            defaultRelative = cmd.isRelative;
+        }
+        
+        PathCommand[] retArr = new PathCommand[cmdList.size()];
+        cmdList.toArray(retArr);
+        return retArr;
+    }
+    
+    static protected GeneralPath buildPath(String text, int windingRule)
+    {
+        PathCommand[] commands = parsePathList(text);
+        
+        int numKnots = 2;
+        for (int i = 0; i < commands.length; i++)
+        {
+            numKnots += commands[i].getNumKnotsAdded();
+        }
+        
+        
+        GeneralPath path = new GeneralPath(windingRule, numKnots);
+        
+        BuildHistory hist = new BuildHistory();
+        
+        for (int i = 0; i < commands.length; i++)
+        {
+            PathCommand cmd = commands[i];
+            cmd.appendPath(path, hist);
+        }
+        
+        return path;
+    }
+    
+    
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    abstract public boolean updateTime(double curTime) throws SVGException;
+
+    public int getNumChildren()
+    {
+        return children.size();
+    }
+
+    public SVGElement getChild(int i)
+    {
+        return (SVGElement)children.get(i);
+    }
+    
+}
Index: trunk/src/com/kitfox/svg/SVGElementException.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGElementException.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGElementException.java	(revision 4256)
@@ -0,0 +1,56 @@
+/*
+ * SVGException.java
+ *
+ * Created on May 12, 2005, 11:32 PM
+ *
+ * To change this template, choose Tools | Options and locate the template under
+ * the Source Creation and Management node. Right-click the template and choose
+ * Open. You can then make changes to the template in the Source Editor.
+ */
+
+package com.kitfox.svg;
+
+/**
+ *
+ * @author kitfox
+ */
+public class SVGElementException extends SVGException
+{
+    public static final long serialVersionUID = 0;
+    
+    private final SVGElement element;
+    
+    /**
+     * Creates a new instance of <code>SVGException</code> without detail message.
+     */
+    public SVGElementException(SVGElement element)
+    {
+        this(element, null, null);
+    }
+    
+    
+    /**
+     * Constructs an instance of <code>SVGException</code> with the specified detail message.
+     * @param msg the detail message.
+     */
+    public SVGElementException(SVGElement element, String msg)
+    {
+        this(element, msg, null);
+    }
+    
+    public SVGElementException(SVGElement element, String msg, Throwable cause)
+    {
+        super(msg, cause);
+        this.element = element;
+    }
+    
+    public SVGElementException(SVGElement element, Throwable cause)
+    {
+        this(element, null, cause);
+    }
+
+    public SVGElement getElement()
+    {
+        return element;
+    }
+}
Index: trunk/src/com/kitfox/svg/SVGException.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGException.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGException.java	(revision 4256)
@@ -0,0 +1,47 @@
+/*
+ * SVGException.java
+ *
+ * Created on May 12, 2005, 11:32 PM
+ *
+ * To change this template, choose Tools | Options and locate the template under
+ * the Source Creation and Management node. Right-click the template and choose
+ * Open. You can then make changes to the template in the Source Editor.
+ */
+
+package com.kitfox.svg;
+
+/**
+ *
+ * @author kitfox
+ */
+public class SVGException extends java.lang.Exception
+{
+    public static final long serialVersionUID = 0;
+    
+    /**
+     * Creates a new instance of <code>SVGException</code> without detail message.
+     */
+    public SVGException()
+    {
+    }
+    
+    
+    /**
+     * Constructs an instance of <code>SVGException</code> with the specified detail message.
+     * @param msg the detail message.
+     */
+    public SVGException(String msg)
+    {
+        super(msg);
+    }
+    
+    public SVGException(String msg, Throwable cause)
+    {
+        super(msg, cause);
+    }
+    
+    public SVGException(Throwable cause)
+    {
+        super(cause);
+    }
+}
Index: trunk/src/com/kitfox/svg/SVGLoader.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGLoader.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGLoader.java	(revision 4256)
@@ -0,0 +1,262 @@
+/*
+ * SVGLoader.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 5:09 PM
+ */
+
+package com.kitfox.svg;
+
+
+import java.util.*;
+import java.net.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGLoader extends DefaultHandler
+{
+    final HashMap nodeClasses = new HashMap();
+    //final HashMap attribClasses = new HashMap();
+    final LinkedList buildStack = new LinkedList();
+
+    final HashSet ignoreClasses = new HashSet();
+
+    final SVGLoaderHelper helper;
+
+    /**
+     * The diagram that represents the base of this SVG document we're loading.
+     * Will be augmented to include node indexing info and other useful stuff.
+     */
+    final SVGDiagram diagram;
+
+//    SVGElement loadRoot;
+
+    //Used to keep track of document elements that are not part of the SVG namespace
+    int skipNonSVGTagDepth = 0;
+    int indent = 0;
+
+    final boolean verbose;
+    
+    /** Creates a new instance of SVGLoader */
+    public SVGLoader(URI xmlBase, SVGUniverse universe)
+    {
+        this(xmlBase, universe, false);
+    }
+    
+    public SVGLoader(URI xmlBase, SVGUniverse universe, boolean verbose)
+    {
+        this.verbose = verbose;
+        
+        diagram = new SVGDiagram(xmlBase, universe);
+
+        //Compile a list of important builder classes
+        nodeClasses.put("a", A.class);
+        nodeClasses.put("circle", Circle.class);
+        nodeClasses.put("clippath", ClipPath.class);
+        nodeClasses.put("defs", Defs.class);
+        nodeClasses.put("desc", Desc.class);
+        nodeClasses.put("ellipse", Ellipse.class);
+        nodeClasses.put("filter", Filter.class);
+        nodeClasses.put("font", Font.class);
+        nodeClasses.put("font-face", FontFace.class);
+        nodeClasses.put("g", Group.class);
+        nodeClasses.put("glyph", Glyph.class);
+        nodeClasses.put("hkern", Hkern.class);
+        nodeClasses.put("image", ImageSVG.class);
+        nodeClasses.put("line", Line.class);
+        nodeClasses.put("lineargradient", LinearGradient.class);
+        nodeClasses.put("marker", Marker.class);
+        nodeClasses.put("metadata", Metadata.class);
+        nodeClasses.put("missing-glyph", MissingGlyph.class);
+        nodeClasses.put("path", Path.class);
+        nodeClasses.put("pattern", PatternSVG.class);
+        nodeClasses.put("polygon", Polygon.class);
+        nodeClasses.put("polyline", Polyline.class);
+        nodeClasses.put("radialgradient", RadialGradient.class);
+        nodeClasses.put("rect", Rect.class);
+        nodeClasses.put("shape", ShapeElement.class);
+        nodeClasses.put("stop", Stop.class);
+        nodeClasses.put("style", Style.class);
+        nodeClasses.put("svg", SVGRoot.class);
+        nodeClasses.put("symbol", Symbol.class);
+        nodeClasses.put("text", Text.class);
+        nodeClasses.put("title", Title.class);
+        nodeClasses.put("tspan", Tspan.class);
+        nodeClasses.put("use", Use.class);
+
+        ignoreClasses.add("midpointstop");
+
+        //attribClasses.put("clip-path", StyleUrl.class);
+        //attribClasses.put("color", StyleColor.class);
+
+        helper = new SVGLoaderHelper(xmlBase, universe, diagram);
+    }
+
+    private String printIndent(int indent, String indentStrn)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < indent; i++)
+        {
+            sb.append(indentStrn);
+        }
+        return sb.toString();
+    }
+    
+    public void startDocument() throws SAXException
+    {
+//        System.err.println("Start doc");
+
+//        buildStack.clear();
+    }
+
+    public void endDocument() throws SAXException
+    {
+//        System.err.println("End doc");
+    }
+
+    public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) throws SAXException
+    {
+        if (verbose)
+        {
+            System.err.println(printIndent(indent, " ") + "Starting parse of tag " + sName+ ": " + namespaceURI);
+        }
+        indent++;
+        
+        if (skipNonSVGTagDepth != 0 || (!namespaceURI.equals("") && !namespaceURI.equals(SVGElement.SVG_NS)))
+        {
+            skipNonSVGTagDepth++;
+            return;
+        }
+        
+        sName = sName.toLowerCase();
+
+//javax.swing.JOptionPane.showMessageDialog(null, sName);
+
+        Object obj = nodeClasses.get(sName);
+        if (obj == null)
+        {
+            if (!ignoreClasses.contains(sName))
+            {
+                if (verbose)
+                {
+                    System.err.println("SVGLoader: Could not identify tag '" + sName + "'");
+                }
+            }
+            return;
+        }
+
+//Debug info tag depth
+//for (int i = 0; i < buildStack.size(); i++) System.err.print(" ");
+//System.err.println("+" + sName);
+
+        try {
+            Class cls = (Class)obj;
+            SVGElement svgEle = (SVGElement)cls.newInstance();
+
+            SVGElement parent = null;
+            if (buildStack.size() != 0) parent = (SVGElement)buildStack.getLast();
+            svgEle.loaderStartElement(helper, attrs, parent);
+
+            buildStack.addLast(svgEle);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            throw new SAXException(e);
+        }
+
+    }
+
+    public void endElement(String namespaceURI, String sName, String qName)
+        throws SAXException
+    {
+        indent--;
+        if (verbose)
+        {
+            System.err.println(printIndent(indent, " ") + "Ending parse of tag " + sName+ ": " + namespaceURI);
+        }
+        
+        if (skipNonSVGTagDepth != 0)
+        {
+            skipNonSVGTagDepth--;
+            return;
+        }
+        
+        sName = sName.toLowerCase();
+
+        Object obj = nodeClasses.get(sName);
+        if (obj == null) return;
+
+//Debug info tag depth
+//for (int i = 0; i < buildStack.size(); i++) System.err.print(" ");
+//System.err.println("-" + sName);
+
+        try {
+            SVGElement svgEle = (SVGElement)buildStack.removeLast();
+
+            svgEle.loaderEndElement(helper);
+
+            SVGElement parent = null;
+            if (buildStack.size() != 0) parent = (SVGElement)buildStack.getLast();
+            //else loadRoot = (SVGElement)svgEle;
+
+            if (parent != null) parent.loaderAddChild(helper, svgEle);
+            else diagram.setRoot((SVGRoot)svgEle);
+
+        }
+        catch (Exception e)
+        {
+e.printStackTrace();
+            throw new SAXException(e);
+        }
+    }
+
+    public void characters(char buf[], int offset, int len)
+        throws SAXException
+    {
+        if (skipNonSVGTagDepth != 0)
+        {
+            return;
+        }
+
+        if (buildStack.size() != 0)
+        {
+            SVGElement parent = (SVGElement)buildStack.getLast();
+            String s = new String(buf, offset, len);
+            parent.loaderAddText(helper, s);
+        }
+    }
+
+    public void processingInstruction(String target, String data)
+        throws SAXException
+    {
+        //Check for external style sheet
+    }
+    
+//    public SVGElement getLoadRoot() { return loadRoot; }
+    public SVGDiagram getLoadedDiagram() { return diagram; }
+}
Index: trunk/src/com/kitfox/svg/SVGLoaderHelper.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGLoaderHelper.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGLoaderHelper.java	(revision 4256)
@@ -0,0 +1,71 @@
+/*
+ * SVGLoaderHelper.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 5:37 PM
+ */
+
+package com.kitfox.svg;
+
+import java.net.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGLoaderHelper
+{
+    /** This is the URL that this document is being loaded from */
+//    public final URL docRoot;
+//    public final URI docRoot;
+
+    /** This is the universe of all currently loaded SVG documents */
+    public final SVGUniverse universe;
+
+    /** This is the diagram which the load process is currently loading */
+    public final SVGDiagram diagram;
+
+    public final URI xmlBase;
+
+    /** Creates a new instance of SVGLoaderHelper */
+    public SVGLoaderHelper(URI xmlBase, SVGUniverse universe, SVGDiagram diagram)
+    {
+        /*
+        URI docURI = null;
+        try
+        {
+            docURI = new URI(docRoot.toString());
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+         */
+        
+        this.xmlBase = xmlBase;
+//        this.docRoot = docURI;
+        this.universe = universe;
+        this.diagram = diagram;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/SVGParseException.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGParseException.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGParseException.java	(revision 4256)
@@ -0,0 +1,47 @@
+/*
+ * SVGException.java
+ *
+ * Created on May 12, 2005, 11:32 PM
+ *
+ * To change this template, choose Tools | Options and locate the template under
+ * the Source Creation and Management node. Right-click the template and choose
+ * Open. You can then make changes to the template in the Source Editor.
+ */
+
+package com.kitfox.svg;
+
+/**
+ *
+ * @author kitfox
+ */
+public class SVGParseException extends java.lang.Exception
+{
+    public static final long serialVersionUID = 0;
+    
+    /**
+     * Creates a new instance of <code>SVGException</code> without detail message.
+     */
+    public SVGParseException()
+    {
+    }
+    
+    
+    /**
+     * Constructs an instance of <code>SVGException</code> with the specified detail message.
+     * @param msg the detail message.
+     */
+    public SVGParseException(String msg)
+    {
+        super(msg);
+    }
+    
+    public SVGParseException(String msg, Throwable cause)
+    {
+        super(msg, cause);
+    }
+    
+    public SVGParseException(Throwable cause)
+    {
+        super(cause);
+    }
+}
Index: trunk/src/com/kitfox/svg/SVGRoot.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGRoot.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGRoot.java	(revision 4256)
@@ -0,0 +1,395 @@
+/*
+ * SVGRoot.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 5:33 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.NumberWithUnits;
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.geom.*;
+import java.awt.*;
+
+/**
+ * The root element of an SVG tree.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGRoot extends Group
+{
+    NumberWithUnits x;
+    NumberWithUnits y;
+    NumberWithUnits width;
+    NumberWithUnits height;
+
+    
+//    final Rectangle2D.Float viewBox = new Rectangle2D.Float();
+    Rectangle2D.Float viewBox = null;
+
+    public static final int PA_X_NONE = 0;
+    public static final int PA_X_MIN = 1;
+    public static final int PA_X_MID = 2;
+    public static final int PA_X_MAX = 3;
+
+    public static final int PA_Y_NONE = 0;
+    public static final int PA_Y_MIN = 1;
+    public static final int PA_Y_MID = 2;
+    public static final int PA_Y_MAX = 3;
+
+    public static final int PS_MEET = 0;
+    public static final int PS_SLICE = 1;
+
+    int parSpecifier = PS_MEET;
+    int parAlignX = PA_X_MID;
+    int parAlignY = PA_Y_MID;
+
+    final AffineTransform viewXform = new AffineTransform();
+    final Rectangle2D.Float clipRect = new Rectangle2D.Float();
+
+    /** Creates a new instance of SVGRoot */
+    public SVGRoot()
+    {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        x = XMLParseUtil.parseNumberWithUnits(attrs.getValue("x"));
+        y = XMLParseUtil.parseNumberWithUnits(attrs.getValue("y"));
+        width = XMLParseUtil.parseNumberWithUnits(attrs.getValue("width"));
+        height = XMLParseUtil.parseNumberWithUnits(attrs.getValue("height"));
+
+        String viewBox = attrs.getValue("viewBox");
+        float[] coords = XMLParseUtil.parseFloatList(viewBox);
+
+        if (coords == null)
+        {
+            //this.viewBox.setRect(0, 0, width.getValue(), height.getValue());
+            this.viewBox = null;
+        }
+        else
+        {
+            this.viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
+        }
+
+        String par = attrs.getValue("preserveAspectRatio");
+        if (par != null)
+        {
+            String[] parList = XMLParseUtil.parseStringList(par);
+
+            if (parList[0].equals("none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
+            else if (parList[0].equals("xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
+            else if (parList[0].equals("xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
+            else if (parList[0].equals("xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
+
+            if (parList[1].equals("meet")) parSpecifier = PS_MEET;
+            else if (parList[1].equals("slice")) parSpecifier = PS_SLICE;
+        }
+
+        build();
+    }
+*/
+    
+    public void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x"))) x = sty.getNumberWithUnits();
+        
+        if (getPres(sty.setName("y"))) y = sty.getNumberWithUnits();
+        
+        if (getPres(sty.setName("width"))) width = sty.getNumberWithUnits();
+        
+        if (getPres(sty.setName("height"))) height = sty.getNumberWithUnits();
+        
+        if (getPres(sty.setName("viewBox"))) 
+        {
+            float[] coords = sty.getFloatList();
+            viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
+        }
+        
+        if (getPres(sty.setName("preserveAspectRatio")))
+        {
+            String preserve = sty.getStringValue();
+            
+            if (contains(preserve, "none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
+            else if (contains(preserve, "xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
+            else if (contains(preserve, "xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
+            else if (contains(preserve, "xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
+            else if (contains(preserve, "xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
+            else if (contains(preserve, "xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
+            else if (contains(preserve, "xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
+            else if (contains(preserve, "xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
+            else if (contains(preserve, "xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
+            else if (contains(preserve, "xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
+
+            if (contains(preserve, "meet")) parSpecifier = PS_MEET;
+            else if (contains(preserve, "slice")) parSpecifier = PS_SLICE;
+            
+            /*
+            String[] parList = sty.getStringList();
+
+            if (parList[0].equals("none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
+            else if (parList[0].equals("xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
+            else if (parList[0].equals("xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
+            else if (parList[0].equals("xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
+            else if (parList[0].equals("xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
+            else if (parList[0].equals("xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
+
+            if (parList[1].equals("meet")) parSpecifier = PS_MEET;
+            else if (parList[1].equals("slice")) parSpecifier = PS_SLICE;
+             */
+        }
+        
+        prepareViewport();
+    }
+    
+    private boolean contains(String text, String find) 
+    {
+        return (text.indexOf(find) != -1);
+    }
+    
+    protected void prepareViewport()
+    {
+        Rectangle deviceViewport = diagram.getDeviceViewport();
+        
+        Rectangle2D defaultBounds;
+        try
+        {
+            defaultBounds = getBoundingBox();
+        } catch (SVGException ex)
+        {
+            defaultBounds= new Rectangle2D.Float();
+        }
+        
+        //Determine destination rectangle
+        float xx, yy, ww, hh;
+        if (width != null)
+        {
+            xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue());
+            if (width.getUnits() == NumberWithUnits.UT_PERCENT)
+            {
+                ww = width.getValue() * deviceViewport.width;
+            }
+            else
+            {
+                ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue());
+            }
+//            setAttribute("x", AnimationElement.AT_XML, "" + xx);
+//            setAttribute("width", AnimationElement.AT_XML, "" + ww);
+        }
+        else if (viewBox != null)
+        {
+            xx = (float)viewBox.x;
+            ww = (float)viewBox.width;
+            width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
+            x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
+        }
+        else
+        {
+            //Estimate size from scene bounding box
+            xx = (float)defaultBounds.getX();
+            ww = (float)defaultBounds.getWidth();
+            width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
+            x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
+        }
+        
+        if (height != null)
+        {
+            yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue());
+            if (height.getUnits() == NumberWithUnits.UT_PERCENT)
+            {
+                hh = height.getValue() * deviceViewport.height;
+            }
+            else
+            {
+                hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue());
+            }
+        }
+        else if (viewBox != null)
+        {
+            yy = (float)viewBox.y;
+            hh = (float)viewBox.height;
+            height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
+            y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
+        }
+        else
+        {
+            //Estimate size from scene bounding box
+            yy = (float)defaultBounds.getY();
+            hh = (float)defaultBounds.getHeight();
+            height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
+            y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
+        }
+
+        clipRect.setRect(xx, yy, ww, hh);
+
+        if (viewBox == null)
+        {
+            viewXform.setToIdentity();
+        }
+        else
+        {
+            viewXform.setToTranslation(clipRect.x, clipRect.y);
+            viewXform.scale(clipRect.width, clipRect.height);
+            viewXform.scale(1 / viewBox.width, 1 / viewBox.height);
+            viewXform.translate(-viewBox.x, -viewBox.y);
+        }
+
+        
+        //For now, treat all preserveAspectRatio as 'none'
+//        viewXform.setToTranslation(viewBox.x, viewBox.y);
+//        viewXform.scale(clipRect.width / viewBox.width, clipRect.height / viewBox.height);
+//        viewXform.translate(-clipRect.x, -clipRect.y);
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        prepareViewport();
+        
+        AffineTransform cachedXform = g.getTransform();
+        g.transform(viewXform);
+        
+        super.render(g);
+        
+        g.setTransform(cachedXform);
+    }
+
+    public Shape getShape()
+    {
+        Shape shape = super.getShape();
+        return viewXform.createTransformedShape(shape);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        Rectangle2D bbox = super.getBoundingBox();
+        return viewXform.createTransformedShape(bbox).getBounds2D();
+    }
+    
+    public float getDeviceWidth()
+    {
+        return clipRect.width;
+    }
+    
+    public float getDeviceHeight()
+    {
+        return clipRect.height;
+    }
+    
+    public Rectangle2D getDeviceRect(Rectangle2D rect)
+    {
+        rect.setRect(clipRect);
+        return rect;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        boolean changeState = super.updateTime(curTime);
+        
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            NumberWithUnits newVal = sty.getNumberWithUnits();
+            if (!newVal.equals(x))
+            {
+                x = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y")))
+        {
+            NumberWithUnits newVal = sty.getNumberWithUnits();
+            if (!newVal.equals(y))
+            {
+                y = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("width")))
+        {
+            NumberWithUnits newVal = sty.getNumberWithUnits();
+            if (!newVal.equals(width))
+            {
+                width = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("height")))
+        {
+            NumberWithUnits newVal = sty.getNumberWithUnits();
+            if (!newVal.equals(height))
+            {
+                height = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("viewBox"))) 
+        {
+            float[] coords = sty.getFloatList();
+            Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
+            if (!newViewBox.equals(viewBox))
+            {
+                viewBox = newViewBox;
+                shapeChange = true;
+            }
+        }
+
+        if (shapeChange)
+        {
+            build();
+        }
+
+        return changeState || shapeChange;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/SVGUniverse.java
===================================================================
--- trunk/src/com/kitfox/svg/SVGUniverse.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/SVGUniverse.java	(revision 4256)
@@ -0,0 +1,657 @@
+/*
+ * SVGUniverse.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 11:43 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.app.beans.SVGIcon;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.zip.GZIPInputStream;
+import javax.imageio.ImageIO;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Many SVG files can be loaded at one time.  These files will quite likely
+ * need to reference one another.  The SVG universe provides a container for
+ * all these files and the means for them to relate to each other.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGUniverse implements Serializable
+{
+    public static final long serialVersionUID = 0;
+    
+    transient private PropertyChangeSupport changes = new PropertyChangeSupport(this);
+    
+    /**
+     * Maps document URIs to their loaded SVG diagrams.  Note that URIs for
+     * documents loaded from URLs will reflect their URLs and URIs for documents
+     * initiated from streams will have the scheme <i>svgSalamander</i>.
+     */
+    final HashMap loadedDocs = new HashMap();
+    
+    final HashMap loadedFonts = new HashMap();
+    
+    final HashMap loadedImages = new HashMap();
+    
+    public static final String INPUTSTREAM_SCHEME = "svgSalamander";
+    
+    /**
+     * Current time in this universe.  Used for resolving attributes that
+     * are influenced by track information.  Time is in milliseconds.  Time
+     * 0 coresponds to the time of 0 in each member diagram.
+     */
+    protected double curTime = 0.0;
+    
+    private boolean verbose = false;
+
+    //Cache reader for efficiency
+    XMLReader cachedReader;
+    
+    /** Creates a new instance of SVGUniverse */
+    public SVGUniverse()
+    {
+    }
+    
+    public void addPropertyChangeListener(PropertyChangeListener l)
+    {
+        changes.addPropertyChangeListener(l);
+    }
+    
+    public void removePropertyChangeListener(PropertyChangeListener l)
+    {
+        changes.removePropertyChangeListener(l);
+    }
+    
+    /**
+     * Release all loaded SVG document from memory
+     */
+    public void clear()
+    {
+        loadedDocs.clear();
+        loadedFonts.clear();
+        loadedImages.clear();
+    }
+    
+    /**
+     * Returns the current animation time in milliseconds.
+     */
+    public double getCurTime()
+    { 
+        return curTime; 
+    }
+    
+    public void setCurTime(double curTime)
+    {
+        double oldTime = this.curTime;
+        this.curTime = curTime; 
+        changes.firePropertyChange("curTime", new Double(oldTime), new Double(curTime));
+    }
+    
+    /**
+     * Updates all time influenced style and presentation attributes in all SVG
+     * documents in this universe.
+     */
+    public void updateTime() throws SVGException
+    {
+        for (Iterator it = loadedDocs.values().iterator(); it.hasNext();)
+        {
+            SVGDiagram dia = (SVGDiagram)it.next();
+            dia.updateTime(curTime);
+        }
+    }
+    
+    /**
+     * Called by the Font element to let the universe know that a font has been
+     * loaded and is available.
+     */
+    void registerFont(Font font)
+    {
+        loadedFonts.put(font.getFontFace().getFontFamily(), font);
+    }
+    
+    public Font getDefaultFont()
+    {
+        for (Iterator it = loadedFonts.values().iterator(); it.hasNext();)
+        {
+            return (Font)it.next();
+        }
+        return null;
+    }
+    
+    public Font getFont(String fontName)
+    {
+        return (Font)loadedFonts.get(fontName);
+    }
+
+    URL registerImage(URI imageURI)
+    {
+        String scheme = imageURI.getScheme();
+        if (scheme.equals("data"))
+        {
+            String path = imageURI.getRawSchemeSpecificPart();
+            int idx = path.indexOf(';');
+            String mime = path.substring(0, idx);
+            String content = path.substring(idx + 1);
+
+            if (content.startsWith("base64"))
+            {
+                content = content.substring(6);
+                try {
+                    byte[] buf = new sun.misc.BASE64Decoder().decodeBuffer(content);
+                    ByteArrayInputStream bais = new ByteArrayInputStream(buf);
+                    BufferedImage img = ImageIO.read(bais);
+
+                    URL url;
+                    int urlIdx = 0;
+                    while (true)
+                    {
+                        url = new URL("inlineImage", "localhost", "img" + urlIdx);
+                        if (!loadedImages.containsKey(url))
+                        {
+                            break;
+                        }
+                        urlIdx++;
+                    }
+
+                    SoftReference ref = new SoftReference(img);
+                    loadedImages.put(url, ref);
+
+                    return url;
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                }
+            }
+            return null;
+        }
+        else
+        {
+            try {
+                URL url = imageURI.toURL();
+                registerImage(url);
+                return url;
+            } catch (MalformedURLException ex) {
+                ex.printStackTrace();
+            }
+            return null;
+        }
+    }
+
+    void registerImage(URL imageURL)
+    {
+        if (loadedImages.containsKey(imageURL)) return;
+        
+        SoftReference ref;
+        try
+        {
+            String fileName = imageURL.getFile();
+            if (".svg".equals(fileName.substring(fileName.length() - 4).toLowerCase()))
+            {
+                SVGIcon icon = new SVGIcon();
+                icon.setSvgURI(imageURL.toURI());
+                
+                BufferedImage img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+                Graphics2D g = img.createGraphics();
+                icon.paintIcon(null, g, 0, 0);
+                g.dispose();
+                ref = new SoftReference(img);
+            }
+            else
+            {
+                BufferedImage img = ImageIO.read(imageURL);
+                ref = new SoftReference(img);
+            }
+            loadedImages.put(imageURL, ref);
+        }
+        catch (Exception e)
+        {
+            System.err.println("Could not load image: " + imageURL);
+            e.printStackTrace();
+        }
+    }
+    
+    BufferedImage getImage(URL imageURL)
+    {
+        SoftReference ref = (SoftReference)loadedImages.get(imageURL);
+        if (ref == null) return null;
+        
+        BufferedImage img = (BufferedImage)ref.get();
+        //If image was cleared from memory, reload it
+        if (img == null)
+        {
+            try
+            {
+                img = ImageIO.read(imageURL);
+            }
+            catch (Exception e)
+            { e.printStackTrace(); }
+            ref = new SoftReference(img);
+            loadedImages.put(imageURL, ref);
+        }
+        
+        return img;
+    }
+    
+    /**
+     * Returns the element of the document at the given URI.  If the document
+     * is not already loaded, it will be.
+     */
+    public SVGElement getElement(URI path)
+    {
+        return getElement(path, true);
+    }
+    
+    public SVGElement getElement(URL path)
+    {
+        try
+        {
+            URI uri = new URI(path.toString());
+            return getElement(uri, true);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+    
+    /**
+     * Looks up a href within our universe.  If the href refers to a document that
+     * is not loaded, it will be loaded.  The URL #target will then be checked
+     * against the SVG diagram's index and the coresponding element returned.
+     * If there is no coresponding index, null is returned.
+     */
+    public SVGElement getElement(URI path, boolean loadIfAbsent)
+    {
+        try
+        {
+            //Strip fragment from URI
+            URI xmlBase = new URI(path.getScheme(), path.getSchemeSpecificPart(), null);
+            
+            SVGDiagram dia = (SVGDiagram)loadedDocs.get(xmlBase);
+            if (dia == null && loadIfAbsent)
+            {
+//System.err.println("SVGUnivserse: " + xmlBase.toString());
+//javax.swing.JOptionPane.showMessageDialog(null, xmlBase.toString());
+                URL url = xmlBase.toURL();
+                
+                loadSVG(url, false);
+                dia = (SVGDiagram)loadedDocs.get(xmlBase);
+                if (dia == null) return null;
+            }
+            
+            String fragment = path.getFragment();
+            return fragment == null ? dia.getRoot() : dia.getElement(fragment);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    
+    public SVGDiagram getDiagram(URI xmlBase)
+    {
+        return getDiagram(xmlBase, true);
+    }
+    
+    /**
+     * Returns the diagram that has been loaded from this root.  If diagram is
+     * not already loaded, returns null.
+     */
+    public SVGDiagram getDiagram(URI xmlBase, boolean loadIfAbsent)
+    {
+        if (xmlBase == null) return null;
+
+        SVGDiagram dia = (SVGDiagram)loadedDocs.get(xmlBase);
+        if (dia != null || !loadIfAbsent) return dia;
+        
+        //Load missing diagram
+        try
+        {
+            URL url;
+            if ("jar".equals(xmlBase.getScheme()) && xmlBase.getPath() != null && !xmlBase.getPath().contains("!/"))
+            {
+                //Workaround for resources stored in jars loaded by Webstart.
+                //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6753651
+                url = SVGUniverse.class.getResource("xmlBase.getPath()");
+            }
+            else
+            {
+                url = xmlBase.toURL();
+            }
+
+            
+            loadSVG(url, false);
+            dia = (SVGDiagram)loadedDocs.get(xmlBase);
+            return dia;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Wraps input stream in a BufferedInputStream.  If it is detected that this
+     * input stream is GZIPped, also wraps in a GZIPInputStream for inflation.
+     * 
+     * @param is Raw input stream
+     * @return Uncompressed stream of SVG data
+     * @throws java.io.IOException
+     */
+    private InputStream createDocumentInputStream(InputStream is) throws IOException
+    {
+        BufferedInputStream bin = new BufferedInputStream(is);
+        bin.mark(2);
+        int b0 = bin.read();
+        int b1 = bin.read();
+        bin.reset();
+
+        //Check for gzip magic number
+        if ((b1 << 8 | b0) == GZIPInputStream.GZIP_MAGIC)
+        {
+            GZIPInputStream iis = new GZIPInputStream(bin);
+            return iis;
+        }
+        else
+        {
+            //Plain text
+            return bin;
+        }
+    }
+    
+    public URI loadSVG(URL docRoot)
+    {
+        return loadSVG(docRoot, false);
+    }
+    
+    /**
+     * Loads an SVG file and all the files it references from the URL provided.
+     * If a referenced file already exists in the SVG universe, it is not
+     * reloaded.
+     * @param docRoot - URL to the location where this SVG file can be found.
+     * @param forceLoad - if true, ignore cached diagram and reload
+     * @return - The URI that refers to the loaded document
+     */
+    public URI loadSVG(URL docRoot, boolean forceLoad)
+    {
+        try
+        {
+            URI uri = new URI(docRoot.toString());
+            if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
+            
+            InputStream is = docRoot.openStream();
+            return loadSVG(uri, new InputSource(createDocumentInputStream(is)));
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+        
+        return null;
+    }
+    
+    
+    public URI loadSVG(InputStream is, String name) throws IOException
+    {
+        return loadSVG(is, name, false);
+    }
+    
+    public URI loadSVG(InputStream is, String name, boolean forceLoad) throws IOException
+    {
+        URI uri = getStreamBuiltURI(name);
+        if (uri == null) return null;
+        if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
+        
+        return loadSVG(uri, new InputSource(createDocumentInputStream(is)));
+    }
+    
+    public URI loadSVG(Reader reader, String name)
+    {
+        return loadSVG(reader, name, false);
+    }
+    
+    /**
+     * This routine allows you to create SVG documents from data streams that
+     * may not necessarily have a URL to load from.  Since every SVG document
+     * must be identified by a unique URL, Salamander provides a method to
+     * fake this for streams by defining it's own protocol - svgSalamander -
+     * for SVG documents without a formal URL.
+     *
+     * @param reader - A stream containing a valid SVG document
+     * @param name - <p>A unique name for this document.  It will be used to
+     * construct a unique URI to refer to this document and perform resolution
+     * with relative URIs within this document.</p>
+     * <p>For example, a name of "/myScene" will produce the URI
+     * svgSalamander:/myScene.  "/maps/canada/toronto" will produce
+     * svgSalamander:/maps/canada/toronto.  If this second document then
+     * contained the href "../uk/london", it would resolve by default to
+     * svgSalamander:/maps/uk/london.  That is, SVG Salamander defines the
+     * URI scheme svgSalamander for it's own internal use and uses it
+     * for uniquely identfying documents loaded by stream.</p>
+     * <p>If you need to link to documents outside of this scheme, you can
+     * either supply full hrefs (eg, href="url(http://www.kitfox.com/index.html)")
+     * or put the xml:base attribute in a tag to change the defaultbase
+     * URIs are resolved against</p>
+     * <p>If a name does not start with the character '/', it will be automatically
+     * prefixed to it.</p>
+     * @param forceLoad - if true, ignore cached diagram and reload
+     *
+     * @return - The URI that refers to the loaded document
+     */
+    public URI loadSVG(Reader reader, String name, boolean forceLoad)
+    {
+//System.err.println(url.toString());
+        //Synthesize URI for this stream
+        URI uri = getStreamBuiltURI(name);
+        if (uri == null) return null;
+        if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
+        
+        return loadSVG(uri, new InputSource(reader));
+    }
+    
+    /**
+     * Synthesize a URI for an SVGDiagram constructed from a stream.
+     * @param name - Name given the document constructed from a stream.
+     */
+    public URI getStreamBuiltURI(String name)
+    {
+        if (name == null || name.length() == 0) return null;
+        
+        if (name.charAt(0) != '/') name = '/' + name;
+        
+        try
+        {
+            //Dummy URL for SVG documents built from image streams
+            return new URI(INPUTSTREAM_SCHEME, name, null);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    
+    private XMLReader getXMLReaderCached() throws SAXException
+    {
+        if (cachedReader == null)
+        {
+            cachedReader = XMLReaderFactory.createXMLReader();
+        }
+        return cachedReader;
+    }
+    
+//    protected URI loadSVG(URI xmlBase, InputStream is)
+    protected URI loadSVG(URI xmlBase, InputSource is)
+    {
+        // Use an instance of ourselves as the SAX event handler
+        SVGLoader handler = new SVGLoader(xmlBase, this, verbose);
+        
+        //Place this docment in the universe before it is completely loaded
+        // so that the load process can refer to references within it's current
+        // document
+//System.err.println("SVGUniverse: loading dia " + xmlBase);
+        loadedDocs.put(xmlBase, handler.getLoadedDiagram());
+        
+        // Use the default (non-validating) parser
+//        SAXParserFactory factory = SAXParserFactory.newInstance();
+//        factory.setValidating(false);
+//        factory.setNamespaceAware(true);
+        
+        try
+        {
+            // Parse the input
+            XMLReader reader = getXMLReaderCached();
+            reader.setEntityResolver(
+                new EntityResolver()
+                {
+                    public InputSource resolveEntity(String publicId, String systemId)
+                    {
+                        //Ignore all DTDs
+                        return new InputSource(new ByteArrayInputStream(new byte[0]));
+                    }
+                }
+            );
+            reader.setContentHandler(handler);
+            reader.parse(is);
+            
+//            SAXParser saxParser = factory.newSAXParser();
+//            saxParser.parse(new InputSource(new BufferedReader(is)), handler);
+            return xmlBase;
+        }
+        catch (SAXParseException sex)
+        {
+            System.err.println("Error processing " + xmlBase);
+            System.err.println(sex.getMessage());
+            
+            loadedDocs.remove(xmlBase);
+            return null;
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+        
+        return null;
+    }
+    
+    public static void main(String argv[])
+    {
+        try
+        {
+            URL url = new URL("svgSalamander", "localhost", -1, "abc.svg",
+                    new URLStreamHandler()
+            {
+                protected URLConnection openConnection(URL u)
+                {
+                    return null;
+                }
+            }
+            );
+//            URL url2 = new URL("svgSalamander", "localhost", -1, "abc.svg");
+            
+            //Investigate URI resolution
+            URI uriA, uriB, uriC, uriD, uriE;
+            
+            uriA = new URI("svgSalamander", "/names/mySpecialName", null);
+//            uriA = new URI("http://www.kitfox.com/salamander");
+//            uriA = new URI("svgSalamander://mySpecialName/grape");
+            System.err.println(uriA.toString());
+            System.err.println(uriA.getScheme());
+            
+            uriB = uriA.resolve("#begin");
+            System.err.println(uriB.toString());
+            
+            uriC = uriA.resolve("tree#boing");
+            System.err.println(uriC.toString());
+            
+            uriC = uriA.resolve("../tree#boing");
+            System.err.println(uriC.toString());
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean isVerbose()
+    {
+        return verbose;
+    }
+
+    public void setVerbose(boolean verbose)
+    {
+        this.verbose = verbose;
+    }
+    
+    /**
+     * Uses serialization to duplicate this universe.
+     */
+    public SVGUniverse duplicate() throws IOException, ClassNotFoundException
+    {
+        ByteArrayOutputStream bs = new ByteArrayOutputStream();
+        ObjectOutputStream os = new ObjectOutputStream(bs);
+        os.writeObject(this);
+        os.close();
+
+        ByteArrayInputStream bin = new ByteArrayInputStream(bs.toByteArray());
+        ObjectInputStream is = new ObjectInputStream(bin);
+        SVGUniverse universe = (SVGUniverse)is.readObject();
+        is.close();
+
+        return universe;
+    }
+}
Index: trunk/src/com/kitfox/svg/ShapeElement.java
===================================================================
--- trunk/src/com/kitfox/svg/ShapeElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/ShapeElement.java	(revision 4256)
@@ -0,0 +1,382 @@
+/*
+ * ShapeElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 5:21 PM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.Marker.MarkerLayout;
+import com.kitfox.svg.Marker.MarkerPos;
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+
+
+/**
+ * Parent of shape objects
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class ShapeElement extends RenderableElement 
+{
+
+    /**
+     * This is necessary to get text elements to render the stroke the correct
+     * width.  It is an alternative to producing new font glyph sets at different
+     * sizes.
+     */
+    protected float strokeWidthScalar = 1f;
+
+    /** Creates a new instance of ShapeElement */
+    public ShapeElement() {
+    }
+
+    abstract public void render(java.awt.Graphics2D g) throws SVGException;
+
+    /*
+    protected void setStrokeWidthScalar(float strokeWidthScalar)
+    {
+        this.strokeWidthScalar = strokeWidthScalar;
+    }
+     */
+
+    void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
+    {
+        StyleAttribute styleAttrib = new StyleAttribute();
+//        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
+        if ((boundingBox ? getBoundingBox() : getShape()).contains(point))
+        {
+            retVec.add(getPath(null));
+        }
+    }
+
+    void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
+    {
+        StyleAttribute styleAttrib = new StyleAttribute();
+//        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
+        if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea))
+        {
+            retVec.add(getPath(null));
+        }
+    }
+
+    protected void renderShape(Graphics2D g, Shape shape) throws SVGException
+    {
+//g.setColor(Color.green);
+
+        StyleAttribute styleAttrib = new StyleAttribute();
+        
+        //Don't process if not visible
+        if (getStyle(styleAttrib.setName("visibility")))
+        {
+            if (!styleAttrib.getStringValue().equals("visible")) return;
+        }
+
+        if (getStyle(styleAttrib.setName("display")))
+        {
+            if (styleAttrib.getStringValue().equals("none")) return;
+        }
+
+        //None, solid color, gradient, pattern
+        Paint fillPaint = Color.black;  //Default to black.  Must be explicitly set to none for no fill.
+        if (getStyle(styleAttrib.setName("fill")))
+        {
+            if (styleAttrib.getStringValue().equals("none")) fillPaint = null;
+            else
+            {
+                fillPaint = styleAttrib.getColorValue();
+                if (fillPaint == null)
+                {
+                    URI uri = styleAttrib.getURIValue(getXMLBase());
+                    if (uri != null)
+                    {
+                        Rectangle2D bounds = shape.getBounds2D();
+                        AffineTransform xform = g.getTransform();
+
+                        SVGElement ele = diagram.getUniverse().getElement(uri);
+                        fillPaint = ((FillElement)ele).getPaint(bounds, xform);
+                    }
+                }
+            }
+        }
+
+        //Default opacity
+        float opacity = 1f;
+        if (getStyle(styleAttrib.setName("opacity")))
+        {
+            opacity = styleAttrib.getRatioValue();
+        }
+        
+        float fillOpacity = opacity;
+        if (getStyle(styleAttrib.setName("fill-opacity")))
+        {
+            fillOpacity *= styleAttrib.getRatioValue();
+        }
+
+
+        Paint strokePaint = null;  //Default is to stroke with none
+        if (getStyle(styleAttrib.setName("stroke")))
+        {
+            if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
+            else
+            {
+                strokePaint = styleAttrib.getColorValue();
+                if (strokePaint == null)
+                {
+                    URI uri = styleAttrib.getURIValue(getXMLBase());
+                    if (uri != null)
+                    {
+                        Rectangle2D bounds = shape.getBounds2D();
+                        AffineTransform xform = g.getTransform();
+
+                        SVGElement ele = diagram.getUniverse().getElement(uri);
+                        strokePaint = ((FillElement)ele).getPaint(bounds, xform);
+                    }
+                }
+            }
+        }
+
+        float[] strokeDashArray = null;
+        if (getStyle(styleAttrib.setName("stroke-dasharray")))
+        {
+            strokeDashArray = styleAttrib.getFloatList();
+            if (strokeDashArray.length == 0) strokeDashArray = null;
+        }
+
+        float strokeDashOffset = 0f;
+        if (getStyle(styleAttrib.setName("stroke-dashoffset")))
+        {
+            strokeDashOffset = styleAttrib.getFloatValueWithUnits();
+        }
+
+        int strokeLinecap = BasicStroke.CAP_BUTT;
+        if (getStyle(styleAttrib.setName("stroke-linecap")))
+        {
+            String val = styleAttrib.getStringValue();
+            if (val.equals("round"))
+            {
+                strokeLinecap = BasicStroke.CAP_ROUND;
+            }
+            else if (val.equals("square"))
+            {
+                strokeLinecap = BasicStroke.CAP_SQUARE;
+            }
+        }
+
+        int strokeLinejoin = BasicStroke.JOIN_MITER;
+        if (getStyle(styleAttrib.setName("stroke-linejoin")))
+        {
+            String val = styleAttrib.getStringValue();
+            if (val.equals("round"))
+            {
+                strokeLinejoin = BasicStroke.JOIN_ROUND;
+            }
+            else if (val.equals("bevel"))
+            {
+                strokeLinejoin = BasicStroke.JOIN_BEVEL;
+            }
+        }
+
+        float strokeMiterLimit = 4f;
+        if (getStyle(styleAttrib.setName("stroke-miterlimit")))
+        {
+            strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
+        }
+
+        float strokeOpacity = opacity;
+        if (getStyle(styleAttrib.setName("stroke-opacity")))
+        {
+            strokeOpacity *= styleAttrib.getRatioValue();
+        }
+
+        float strokeWidth = 1f;
+        if (getStyle(styleAttrib.setName("stroke-width")))
+        {
+            strokeWidth = styleAttrib.getFloatValueWithUnits();
+        }
+//        if (strokeWidthScalar != 1f)
+//        {
+            strokeWidth *= strokeWidthScalar;
+//        }
+
+        Marker markerStart = null;
+        if (getStyle(styleAttrib.setName("marker-start")))
+        {
+            if (!styleAttrib.getStringValue().equals("none"))
+            {
+                URI uri = styleAttrib.getURIValue(getXMLBase());
+                markerStart = (Marker)diagram.getUniverse().getElement(uri);
+            }
+        }
+
+        Marker markerMid = null;
+        if (getStyle(styleAttrib.setName("marker-mid")))
+        {
+            if (!styleAttrib.getStringValue().equals("none"))
+            {
+                URI uri = styleAttrib.getURIValue(getXMLBase());
+                markerMid = (Marker)diagram.getUniverse().getElement(uri);
+            }
+        }
+
+        Marker markerEnd = null;
+        if (getStyle(styleAttrib.setName("marker-end")))
+        {
+            if (!styleAttrib.getStringValue().equals("none"))
+            {
+                URI uri = styleAttrib.getURIValue(getXMLBase());
+                markerEnd = (Marker)diagram.getUniverse().getElement(uri);
+            }
+        }
+
+
+        //Draw the shape
+        if (fillPaint != null && fillOpacity != 0f)
+        {
+            if (fillOpacity <= 0)
+            {
+                //Do nothing
+            }
+            else if (fillOpacity < 1f)
+            {
+                Composite cachedComposite = g.getComposite();
+                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
+
+                g.setPaint(fillPaint);
+                g.fill(shape);
+            
+                g.setComposite(cachedComposite);
+            }
+            else
+            {
+                g.setPaint(fillPaint);
+                g.fill(shape);
+            }
+        }
+
+
+        if (strokePaint != null && strokeOpacity != 0f)
+        {
+            BasicStroke stroke;
+            if (strokeDashArray == null)
+            {
+                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
+            }
+            else
+            {
+                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
+            }
+
+            Shape strokeShape = stroke.createStrokedShape(shape);
+
+            if (strokeOpacity <= 0)
+            {
+                //Do nothing
+            }
+            else if (strokeOpacity < 1f)
+            {
+                Composite cachedComposite = g.getComposite();
+                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
+
+                g.setPaint(strokePaint);
+                g.fill(strokeShape);
+
+                g.setComposite(cachedComposite);
+            }
+            else
+            {
+                g.setPaint(strokePaint);
+                g.fill(strokeShape);
+            }
+        }
+
+        if (markerStart != null || markerMid != null || markerEnd != null)
+        {
+            MarkerLayout layout = new MarkerLayout();
+            layout.layout(shape);
+            
+            ArrayList list = layout.getMarkerList();
+            for (int i = 0; i < list.size(); ++i)
+            {
+                MarkerPos pos = (MarkerPos)list.get(i);
+
+                switch (pos.type)
+                {
+                    case Marker.MARKER_START:
+                        if (markerStart != null)
+                        {
+                            markerStart.render(g, pos, strokeWidth);
+                        }
+                        break;
+                    case Marker.MARKER_MID:
+                        if (markerMid != null)
+                        {
+                            markerMid.render(g, pos, strokeWidth);
+                        }
+                        break;
+                    case Marker.MARKER_END:
+                        if (markerEnd != null)
+                        {
+                            markerEnd.render(g, pos, strokeWidth);
+                        }
+                        break;
+                }
+            }
+        }
+    }
+    
+    abstract public Shape getShape();
+
+    protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
+    {
+        StyleAttribute styleAttrib = new StyleAttribute();
+        if (!getStyle(styleAttrib.setName("stroke"))) return rect;
+
+        double strokeWidth = 1;
+        if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
+
+        rect.setRect(
+            rect.getX() - strokeWidth / 2,
+            rect.getY() - strokeWidth / 2,
+            rect.getWidth() + strokeWidth,
+            rect.getHeight() + strokeWidth);
+
+        return rect;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/Stop.java
===================================================================
--- trunk/src/com/kitfox/svg/Stop.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Stop.java	(revision 4256)
@@ -0,0 +1,129 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Stop extends SVGElement {
+
+    float offset = 0f;
+    float opacity = 1f;
+    Color color = Color.black;
+
+    /** Creates a new instance of Stop */
+    public Stop() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String offset = attrs.getValue("offset");
+        this.offset = (float)XMLParseUtil.parseRatio(offset);
+
+        buildStop();
+    }
+    */
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("offset")))
+        {
+            offset = sty.getFloatValue();
+            String units = sty.getUnits();
+            if (units != null && units.equals("%")) offset /= 100f;
+            if (offset > 1) offset = 1;
+            if (offset < 0) offset = 0;
+        }
+        
+        if (getStyle(sty.setName("stop-color"))) color = sty.getColorValue();
+
+        if (getStyle(sty.setName("stop-opacity"))) opacity = sty.getRatioValue();
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("offset")))
+        {
+            float newVal = sty.getFloatValue();
+            if (newVal != offset)
+            {
+                offset = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("stop-color")))
+        {
+            Color newVal = sty.getColorValue();
+            if (newVal != color)
+            {
+                color = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("stop-opacity")))
+        {
+            float newVal = sty.getFloatValue();
+            if (newVal != opacity)
+            {
+                opacity = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        return shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Style.java
===================================================================
--- trunk/src/com/kitfox/svg/Style.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Style.java	(revision 4256)
@@ -0,0 +1,83 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 19, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+
+/**
+ * Holds title textual information within tree
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Style extends SVGElement {
+
+    //Should be set to "text/css"
+    String type;
+    StringBuffer text = new StringBuffer();
+
+    /** Creates a new instance of Stop */
+    public Style() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        this.type = attrs.getValue("type");
+    }
+*/
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+        this.text.append(text);
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("type"))) type = sty.getStringValue();
+    }
+    
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Style sheet doesn't change
+        return false;
+    }    
+    
+}
Index: trunk/src/com/kitfox/svg/Symbol.java
===================================================================
--- trunk/src/com/kitfox/svg/Symbol.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Symbol.java	(revision 4256)
@@ -0,0 +1,155 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+import com.kitfox.svg.xml.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Symbol extends Group
+{
+    AffineTransform viewXform;
+    Rectangle2D viewBox;
+
+    /** Creates a new instance of Stop */
+    public Symbol() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String viewBoxStrn = attrs.getValue("viewBox");
+        if (viewBoxStrn != null)
+        {
+            float[] dim = XMLParseUtil.parseFloatList(viewBoxStrn);
+            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
+        }
+    }
+*/
+    /*
+    public void loaderEndElement(SVGLoaderHelper helper)
+    {
+        if (viewBox == null)
+        {
+            viewBox = super.getBoundingBox();
+        }
+
+        //Transform pattern onto unit square
+        viewXform = new AffineTransform();
+        viewXform.scale(1.0 / viewBox.getWidth(), 1.0 / viewBox.getHeight());
+        viewXform.translate(-viewBox.getX(), -viewBox.getY());
+    }
+*/
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+//        sty = getPres("unicode");
+//        if (sty != null) unicode = sty.getStringValue();
+
+
+        if (getPres(sty.setName("viewBox")))
+        {
+            float[] dim = sty.getFloatList();
+            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
+        }
+        
+        if (viewBox == null)
+        {
+//            viewBox = super.getBoundingBox();
+            viewBox = new Rectangle(0, 0, 1, 1);
+        }
+
+        //Transform pattern onto unit square
+        viewXform = new AffineTransform();
+        viewXform.scale(1.0 / viewBox.getWidth(), 1.0 / viewBox.getHeight());
+        viewXform.translate(-viewBox.getX(), -viewBox.getY());
+    }
+
+    protected boolean outsideClip(Graphics2D g) throws SVGException
+    {
+        g.getClipBounds(clipBounds);
+        Rectangle2D rect = super.getBoundingBox();
+        if (rect.intersects(clipBounds))
+        {
+            return false;
+        }
+
+        return true;
+
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        AffineTransform oldXform = g.getTransform();
+        g.transform(viewXform);
+
+        super.render(g);
+
+        g.setTransform(oldXform);
+    }
+
+    public Shape getShape()
+    {
+        Shape shape = super.getShape();
+        return viewXform.createTransformedShape(shape);
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        Rectangle2D rect = super.getBoundingBox();
+        return viewXform.createTransformedShape(rect).getBounds2D();
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+        
+        //View box properties do not change
+        
+        return changeState;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/Text.java
===================================================================
--- trunk/src/com/kitfox/svg/Text.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Text.java	(revision 4256)
@@ -0,0 +1,540 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.font.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.regex.*;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Text extends ShapeElement
+{
+    
+    float x = 0;
+    float y = 0;
+    AffineTransform transform = null;
+    
+    String fontFamily;
+    float fontSize;
+    
+    //List of strings and tspans containing the content of this node
+    LinkedList content = new LinkedList();
+    
+    Shape textShape;
+    
+    public static final int TXAN_START = 0;
+    public static final int TXAN_MIDDLE = 1;
+    public static final int TXAN_END = 2;
+    int textAnchor = TXAN_START;
+    
+    public static final int TXST_NORMAL = 0;
+    public static final int TXST_ITALIC = 1;
+    public static final int TXST_OBLIQUE = 2;
+    int fontStyle;
+    
+    public static final int TXWE_NORMAL = 0;
+    public static final int TXWE_BOLD = 1;
+    public static final int TXWE_BOLDER = 2;
+    public static final int TXWE_LIGHTER = 3;
+    public static final int TXWE_100 = 4;
+    public static final int TXWE_200 = 5;
+    public static final int TXWE_300 = 6;
+    public static final int TXWE_400 = 7;
+    public static final int TXWE_500 = 8;
+    public static final int TXWE_600 = 9;
+    public static final int TXWE_700 = 10;
+    public static final int TXWE_800 = 11;
+    public static final int TXWE_900 = 12;
+    int fontWeight;
+    
+    /** Creates a new instance of Stop */
+    public Text()
+    {
+    }
+    
+    public void appendText(String text)
+    {
+        content.addLast(text);
+    }
+    
+    public void appendTspan(Tspan tspan) throws SVGElementException
+    {
+        super.loaderAddChild(null, tspan);
+        content.addLast(tspan);
+//        tspan.setParent(this);
+    }
+    
+    /**
+     * Discard cached information
+     */
+    public void rebuild() throws SVGException
+    {
+        build();
+    }
+    
+    public java.util.List getContent()
+    {
+        return content;
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+                //Load style string
+        super.loaderStartElement(helper, attrs, parent);
+ 
+        String x = attrs.getValue("x");
+        String y = attrs.getValue("y");
+        //String transform = attrs.getValue("transform");
+ 
+        this.x = XMLParseUtil.parseFloat(x);
+        this.y = XMLParseUtil.parseFloat(y);
+    }
+ */
+    /**
+     * Called after the start element but before the end element to indicate
+     * each child tag that has been processed
+     */
+    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
+    {
+        super.loaderAddChild(helper, child);
+        
+        content.addLast(child);
+    }
+    
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+        Matcher matchWs = Pattern.compile("\\s*").matcher(text);
+        if (!matchWs.matches()) content.addLast(text);
+    }
+    
+    public void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+        
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+        
+        if (getStyle(sty.setName("font-family"))) fontFamily = sty.getStringValue();
+        else fontFamily = "Sans Serif";
+        
+        if (getStyle(sty.setName("font-size"))) fontSize = sty.getFloatValueWithUnits();
+        else fontSize = 12f;
+        
+        if (getStyle(sty.setName("font-style")))
+        {
+            String s = sty.getStringValue();
+            if ("normal".equals(s))
+            {
+                fontStyle = TXST_NORMAL;
+            }
+            else if ("italic".equals(s))
+            {
+                fontStyle = TXST_ITALIC;
+            }
+            else if ("oblique".equals(s))
+            {
+                fontStyle = TXST_OBLIQUE;
+            }
+        }
+        else fontStyle = TXST_NORMAL;
+        
+        if (getStyle(sty.setName("font-weight")))
+        {
+            String s = sty.getStringValue();
+            if ("normal".equals(s))
+            {
+                fontWeight = TXWE_NORMAL;
+            }
+            else if ("bold".equals(s))
+            {
+                fontWeight = TXWE_BOLD;
+            }
+        }
+        else fontWeight = TXWE_BOLD;
+        
+        if (getStyle(sty.setName("text-anchor")))
+        {
+            String s = sty.getStringValue();
+            if (s.equals("middle")) textAnchor = TXAN_MIDDLE;
+            else if (s.equals("end")) textAnchor = TXAN_END;
+            else textAnchor = TXAN_START;
+        }
+        else textAnchor = TXAN_START;
+        
+        //text anchor
+        //text-decoration
+        //text-rendering
+        
+        buildFont();
+    }
+    
+    protected void buildFont() throws SVGException
+    {
+        int style;
+        switch (fontStyle)
+        {
+            case TXST_ITALIC:
+                style = java.awt.Font.ITALIC;
+                break;
+            default:
+                style = java.awt.Font.PLAIN;
+                break;
+        }
+
+        int weight;
+        switch (fontWeight)
+        {
+            case TXWE_BOLD:
+            case TXWE_BOLDER:
+                weight = java.awt.Font.BOLD;
+                break;
+            default:
+                weight = java.awt.Font.PLAIN;
+                break;
+        }
+            
+        //Get font
+        Font font = diagram.getUniverse().getFont(fontFamily);
+        if (font == null)
+        {
+//            System.err.println("Could not load font");
+            
+            java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int)fontSize);
+            buildSysFont(sysFont);
+            return;
+        }
+        
+//        font = new java.awt.Font(font.getFamily(), style | weight, font.getSize());
+        
+//        Area textArea = new Area();
+        GeneralPath textPath = new GeneralPath();
+        textShape = textPath;
+        
+        float cursorX = x, cursorY = y;
+        
+        FontFace fontFace = font.getFontFace();
+        //int unitsPerEm = fontFace.getUnitsPerEm();
+        int ascent = fontFace.getAscent();
+        float fontScale = fontSize / (float)ascent;
+        
+//        AffineTransform oldXform = g.getTransform();
+        AffineTransform xform = new AffineTransform();
+        
+        for (Iterator it = content.iterator(); it.hasNext();)
+        {
+            Object obj = it.next();
+            
+            if (obj instanceof String)
+            {
+                String text = (String)obj;
+                
+                strokeWidthScalar = 1f / fontScale;
+                
+                for (int i = 0; i < text.length(); i++)
+                {
+                    xform.setToIdentity();
+                    xform.setToTranslation(cursorX, cursorY);
+                    xform.scale(fontScale, fontScale);
+//                    g.transform(xform);
+                    
+                    String unicode = text.substring(i, i + 1);
+                    MissingGlyph glyph = font.getGlyph(unicode);
+                    
+                    Shape path = glyph.getPath();
+                    if (path != null)
+                    {
+                        path = xform.createTransformedShape(path);
+                        textPath.append(path, false);
+                    }
+//                    else glyph.render(g);
+                    
+                    cursorX += fontScale * glyph.getHorizAdvX();
+                    
+//                    g.setTransform(oldXform);
+                }
+                
+                strokeWidthScalar = 1f;
+            }
+            else if (obj instanceof Tspan)
+            {
+                Tspan tspan = (Tspan)obj;
+                
+                xform.setToIdentity();
+                xform.setToTranslation(cursorX, cursorY);
+                xform.scale(fontScale, fontScale);
+//                tspan.setCursorX(cursorX);
+//                tspan.setCursorY(cursorY);
+                
+                Shape tspanShape = tspan.getShape();
+                tspanShape = xform.createTransformedShape(tspanShape);
+                textPath.append(tspanShape, false);
+//                tspan.render(g);
+//                cursorX = tspan.getCursorX();
+//                cursorY = tspan.getCursorY();
+            }
+            
+        }
+        
+        switch (textAnchor)
+        {
+            case TXAN_MIDDLE:
+            {
+                AffineTransform at = new AffineTransform();
+                at.translate(-textPath.getBounds2D().getWidth() / 2, 0);
+                textPath.transform(at);
+                break;
+            }
+            case TXAN_END:
+            {
+                AffineTransform at = new AffineTransform();
+                at.translate(-textPath.getBounds2D().getWidth(), 0);
+                textPath.transform(at);
+                break;
+            }
+        }
+    }
+    
+    private void buildSysFont(java.awt.Font font) throws SVGException
+    {
+        GeneralPath textPath = new GeneralPath();
+        textShape = textPath;
+        
+        float cursorX = x, cursorY = y;
+        
+//        FontMetrics fm = g.getFontMetrics(font);
+        FontRenderContext frc = new FontRenderContext(null, true, true);
+        
+//        FontFace fontFace = font.getFontFace();
+        //int unitsPerEm = fontFace.getUnitsPerEm();
+//        int ascent = fm.getAscent();
+//        float fontScale = fontSize / (float)ascent;
+        
+//        AffineTransform oldXform = g.getTransform();
+        AffineTransform xform = new AffineTransform();
+        
+        for (Iterator it = content.iterator(); it.hasNext();)
+        {
+            Object obj = it.next();
+            
+            if (obj instanceof String)
+            {
+                String text = (String)obj;
+                
+                Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
+                textPath.append(textShape, false);
+//                renderShape(g, textShape);
+//                g.drawString(text, cursorX, cursorY);
+                
+                Rectangle2D rect = font.getStringBounds(text, frc);
+                cursorX += (float)rect.getWidth();
+            }
+            else if (obj instanceof Tspan)
+            {
+                /*
+                Tspan tspan = (Tspan)obj;
+                 
+                xform.setToIdentity();
+                xform.setToTranslation(cursorX, cursorY);
+                 
+                Shape tspanShape = tspan.getShape();
+                tspanShape = xform.createTransformedShape(tspanShape);
+                textArea.add(new Area(tspanShape));
+                 
+                cursorX += tspanShape.getBounds2D().getWidth();
+                 */
+                
+                
+                Tspan tspan = (Tspan)obj;
+                tspan.setCursorX(cursorX);
+                tspan.setCursorY(cursorY);
+                tspan.addShape(textPath);
+                cursorX = tspan.getCursorX();
+                cursorY = tspan.getCursorY();
+                
+            }
+        }
+        
+        switch (textAnchor)
+        {
+            case TXAN_MIDDLE:
+            {
+                AffineTransform at = new AffineTransform();
+                at.translate(-textPath.getBounds2D().getWidth() / 2, 0);
+                textPath.transform(at);
+                break;
+            }
+            case TXAN_END:
+            {
+                AffineTransform at = new AffineTransform();
+                at.translate(-textPath.getBounds2D().getWidth(), 0);
+                textPath.transform(at);
+                break;
+            }
+        }
+    }
+    
+    
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+        renderShape(g, textShape);
+        finishLayer(g);
+    }
+    
+    public Shape getShape()
+    {
+        return shapeToParent(textShape);
+    }
+    
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        return boundsToParent(includeStrokeInBounds(textShape.getBounds2D()));
+    }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+        
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("font-family")))
+        {
+            String newVal = sty.getStringValue();
+            if (!newVal.equals(fontFamily))
+            {
+                fontFamily = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("font-size")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != fontSize)
+            {
+                fontSize = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        
+        if (getStyle(sty.setName("font-style")))
+        {
+            String s = sty.getStringValue();
+            int newVal = fontStyle;
+            if ("normal".equals(s))
+            {
+                newVal = TXST_NORMAL;
+            }
+            else if ("italic".equals(s))
+            {
+                newVal = TXST_ITALIC;
+            }
+            else if ("oblique".equals(s))
+            {
+                newVal = TXST_OBLIQUE;
+            }
+            if (newVal != fontStyle)
+            {
+                fontStyle = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getStyle(sty.setName("font-weight")))
+        {
+            String s = sty.getStringValue();
+            int newVal = fontWeight;
+            if ("normal".equals(s))
+            {
+                newVal = TXWE_NORMAL;
+            }
+            else if ("bold".equals(s))
+            {
+                newVal = TXWE_BOLD;
+            }
+            if (newVal != fontWeight)
+            {
+                fontWeight = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (shapeChange)
+        {
+            build();
+//            buildFont();
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/Title.java
===================================================================
--- trunk/src/com/kitfox/svg/Title.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Title.java	(revision 4256)
@@ -0,0 +1,65 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 19, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+/**
+ * Holds title textual information within tree
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Title extends SVGElement {
+
+    StringBuffer text = new StringBuffer();
+
+    /** Creates a new instance of Stop */
+    public Title() {
+    }
+
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+        this.text.append(text);
+    }
+
+    public String getText() { return text.toString(); }
+    
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Title does not change
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/TransformableElement.java
===================================================================
--- trunk/src/com/kitfox/svg/TransformableElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/TransformableElement.java	(revision 4256)
@@ -0,0 +1,116 @@
+/*
+ * BoundedElement.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 9:00 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+
+
+/**
+ * Maintains bounding box for this element
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TransformableElement extends SVGElement 
+{
+
+    AffineTransform xform = null;
+//    AffineTransform invXform = null;
+
+    /** Creates a new instance of BoundedElement */
+    public TransformableElement() {
+    }
+
+    public TransformableElement(String id, SVGElement parent)
+    {
+        super(id, parent);
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String transform = attrs.getValue("transform");
+        if (transform != null)
+        {
+            xform = parseTransform(transform);
+        }
+    }
+*/
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("transform")))
+        {
+            xform = parseTransform(sty.getStringValue());
+        }
+    }
+    
+    protected Shape shapeToParent(Shape shape)
+    {
+        if (xform == null) return shape;
+        return xform.createTransformedShape(shape);
+    }
+
+    protected Rectangle2D boundsToParent(Rectangle2D rect)
+    {
+        if (xform == null) return rect;
+        return xform.createTransformedShape(rect).getBounds2D();
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("transform")))
+        {
+            AffineTransform newXform = parseTransform(sty.getStringValue());
+            if (!newXform.equals(xform))
+            {
+                xform = newXform;
+                return true;
+            }
+        }
+        
+        return false;
+    }
+}
Index: trunk/src/com/kitfox/svg/Tspan.java
===================================================================
--- trunk/src/com/kitfox/svg/Tspan.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Tspan.java	(revision 4256)
@@ -0,0 +1,380 @@
+/*
+ * Stop.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:56 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.font.*;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Tspan extends ShapeElement {
+
+    float[] x = null;
+    float[] y = null;
+    float[] dx = null;
+    float[] dy = null;
+    float[] rotate = null;
+
+    private String text = "";
+
+    float cursorX;
+    float cursorY;
+
+//    Shape tspanShape;
+
+    /** Creates a new instance of Stop */
+    public Tspan() {
+    }
+
+    public float getCursorX() { return cursorX; }
+    public float getCursorY() { return cursorY; }
+
+    public void setCursorX(float cursorX)
+    {
+        this.cursorX = cursorX;
+    }
+
+    public void setCursorY(float cursorY)
+    {
+        this.cursorY = cursorY;
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String x = attrs.getValue("x");
+        String y = attrs.getValue("y");
+        String dx = attrs.getValue("dx");
+        String dy = attrs.getValue("dy");
+        String rotate = attrs.getValue("rotate");
+
+        if (x != null) this.x = XMLParseUtil.parseFloatList(x);
+        if (y != null) this.y = XMLParseUtil.parseFloatList(y);
+        if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx);
+        if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy);
+        if (rotate != null)
+        {
+            this.rotate = XMLParseUtil.parseFloatList(rotate);
+            for (int i = 0; i < this.rotate.length; i++)
+                this.rotate[i] = (float)Math.toRadians(this.rotate[i]);
+        }
+    }
+    */
+
+    /**
+     * Called during load process to add text scanned within a tag
+     */
+    public void loaderAddText(SVGLoaderHelper helper, String text)
+    {
+        this.text += text;
+    }
+
+    
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatList();
+
+        if (getPres(sty.setName("y"))) y = sty.getFloatList();
+
+        if (getPres(sty.setName("dx"))) dx = sty.getFloatList();
+
+        if (getPres(sty.setName("dy"))) dy = sty.getFloatList();
+
+        if (getPres(sty.setName("rotate")))
+        {
+            rotate = sty.getFloatList();
+            for (int i = 0; i < this.rotate.length; i++)
+            {
+                rotate[i] = (float)Math.toRadians(this.rotate[i]);
+            }
+                
+        }
+    }
+    
+    public void addShape(GeneralPath addShape) throws SVGException
+    {
+        if (x != null)
+        {
+            cursorX = x[0];
+            cursorY = y[0];
+        }
+        else if (dx != null)
+        {
+            cursorX += dx[0];
+            cursorY += dy[0];
+        }
+
+        StyleAttribute sty = new StyleAttribute();
+        
+        String fontFamily = null;
+        if (getStyle(sty.setName("font-family")))
+        {
+            fontFamily = sty.getStringValue();
+        }
+
+
+        float fontSize = 12f;
+        if (getStyle(sty.setName("font-size")))
+        {
+            fontSize = sty.getFloatValueWithUnits();
+        }
+
+        //Get font
+        Font font = diagram.getUniverse().getFont(fontFamily);
+        if (font == null)
+        {
+            addShapeSysFont(addShape, font, fontFamily, fontSize);
+            return;
+        }
+
+        FontFace fontFace = font.getFontFace();
+        int ascent = fontFace.getAscent();
+        float fontScale = fontSize / (float)ascent;
+
+        AffineTransform xform = new AffineTransform();
+
+        strokeWidthScalar = 1f / fontScale;
+
+        int posPtr = 1;
+
+        for (int i = 0; i < text.length(); i++)
+        {
+            xform.setToIdentity();
+            xform.setToTranslation(cursorX, cursorY);
+            xform.scale(fontScale, fontScale);
+            if (rotate != null) xform.rotate(rotate[posPtr]);
+
+            String unicode = text.substring(i, i + 1);
+            MissingGlyph glyph = font.getGlyph(unicode);
+
+            Shape path = glyph.getPath();
+            if (path != null)
+            {
+                path = xform.createTransformedShape(path);
+                addShape.append(path, false);
+            }
+
+            if (x != null && posPtr < x.length)
+            {
+                cursorX = x[posPtr];
+                cursorY = y[posPtr++];
+            }
+            else if (dx != null && posPtr < dx.length)
+            {
+                cursorX += dx[posPtr];
+                cursorY += dy[posPtr++];
+            }
+
+            cursorX += fontScale * glyph.getHorizAdvX();
+        }
+
+        strokeWidthScalar = 1f;
+    }
+
+    private void addShapeSysFont(GeneralPath addShape, Font font, String fontFamily, float fontSize)
+    {
+        java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int)fontSize);
+
+        FontRenderContext frc = new FontRenderContext(null, true, true);
+        GlyphVector textVector = sysFont.createGlyphVector(frc, text);
+
+        AffineTransform xform = new AffineTransform();
+
+        int posPtr = 1;
+        for (int i = 0; i < text.length(); i++)
+        {
+            xform.setToIdentity();
+            xform.setToTranslation(cursorX, cursorY);
+            if (rotate != null) xform.rotate(rotate[Math.min(i, rotate.length - 1)]);
+
+            String unicode = text.substring(i, i + 1);
+            Shape glyphOutline = textVector.getGlyphOutline(i);
+            GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(i);
+
+            glyphOutline = xform.createTransformedShape(glyphOutline);
+            addShape.append(glyphOutline, false);
+
+            if (x != null && posPtr < x.length)
+            {
+                cursorX = x[posPtr];
+                cursorY = y[posPtr++];
+            }
+            else if (dx != null && posPtr < dx.length)
+            {
+                cursorX += dx[posPtr];
+                cursorY += dy[posPtr++];
+            }
+        }
+    }
+
+    public void render(Graphics2D g) throws SVGException
+    {
+        if (x != null)
+        {
+            cursorX = x[0];
+            cursorY = y[0];
+        }
+        else if (dx != null)
+        {
+            cursorX += dx[0];
+            cursorY += dy[0];
+        }
+
+        StyleAttribute sty = new StyleAttribute();
+        
+        String fontFamily = null;
+        if (getPres(sty.setName("font-family")))
+        {
+            fontFamily = sty.getStringValue();
+        }
+
+
+        float fontSize = 12f;
+        if (getPres(sty.setName("font-size")))
+        {
+            fontSize = sty.getFloatValueWithUnits();
+        }
+
+        //Get font
+        Font font = diagram.getUniverse().getFont(fontFamily);
+        if (font == null)
+        {
+            System.err.println("Could not load font");
+            java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int)fontSize);
+            renderSysFont(g, sysFont);
+            return;
+        }
+
+
+        FontFace fontFace = font.getFontFace();
+        int ascent = fontFace.getAscent();
+        float fontScale = fontSize / (float)ascent;
+
+        AffineTransform oldXform = g.getTransform();
+        AffineTransform xform = new AffineTransform();
+
+        strokeWidthScalar = 1f / fontScale;
+
+        int posPtr = 1;
+
+        for (int i = 0; i < text.length(); i++)
+        {
+            xform.setToTranslation(cursorX, cursorY);
+            xform.scale(fontScale, fontScale);
+            g.transform(xform);
+
+            String unicode = text.substring(i, i + 1);
+            MissingGlyph glyph = font.getGlyph(unicode);
+
+            Shape path = glyph.getPath();
+            if (path != null)
+            {
+                renderShape(g, path);
+            }
+            else glyph.render(g);
+
+            if (x != null && posPtr < x.length)
+            {
+                cursorX = x[posPtr];
+                cursorY = y[posPtr++];
+            }
+            else if (dx != null && posPtr < dx.length)
+            {
+                cursorX += dx[posPtr];
+                cursorY += dy[posPtr++];
+            }
+
+            cursorX += fontScale * glyph.getHorizAdvX();
+
+            g.setTransform(oldXform);
+        }
+
+        strokeWidthScalar = 1f;
+    }
+
+    protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException
+    {
+        int posPtr = 1;
+        FontRenderContext frc = g.getFontRenderContext();
+
+        Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
+        renderShape(g, textShape);
+        Rectangle2D rect = font.getStringBounds(text, frc);
+        cursorX += (float)rect.getWidth();
+    }
+
+    public Shape getShape()
+    {
+        return null;
+        //return shapeToParent(tspanShape);
+    }
+
+    public Rectangle2D getBoundingBox()
+    {
+        return null;
+        //return boundsToParent(tspanShape.getBounds2D());
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+        //Tspan does not change
+        return false;
+    }
+
+    public String getText()
+    {
+        return text;
+    }
+
+    public void setText(String text)
+    {
+        this.text = text;
+    }
+}
Index: trunk/src/com/kitfox/svg/Use.java
===================================================================
--- trunk/src/com/kitfox/svg/Use.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/Use.java	(revision 4256)
@@ -0,0 +1,255 @@
+/*
+ * LinearGradient.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 1:54 AM
+ */
+
+package com.kitfox.svg;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.net.URI;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Use extends ShapeElement {
+
+    float x = 0f;
+    float y = 0f;
+    float width = 1f;
+    float height = 1f;
+
+    SVGElement href = null;
+
+    AffineTransform refXform;
+
+    /** Creates a new instance of LinearGradient */
+    public Use() {
+    }
+/*
+    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
+    {
+		//Load style string
+        super.loaderStartElement(helper, attrs, parent);
+
+        String x = attrs.getValue("x");
+        String y = attrs.getValue("y");
+        String width = attrs.getValue("width");
+        String height = attrs.getValue("height");
+        String href = attrs.getValue("xlink:href");
+
+        if (x != null) this.x = (float)XMLParseUtil.parseRatio(x);
+        if (y != null) this.y = (float)XMLParseUtil.parseRatio(y);
+        if (width != null) this.width = (float)XMLParseUtil.parseRatio(width);
+        if (height != null) this.height = (float)XMLParseUtil.parseRatio(height);
+
+
+        if (href != null)
+        {
+            try {
+                URI src = getXMLBase().resolve(href);
+                this.href = helper.universe.getElement(src);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        //Determine use offset/scale
+        refXform = new AffineTransform();
+        refXform.translate(this.x, this.y);
+        refXform.scale(this.width, this.height);
+    }
+*/
+    protected void build() throws SVGException
+    {
+        super.build();
+        
+        StyleAttribute sty = new StyleAttribute();
+        
+        if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("width"))) width = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("height"))) height = sty.getFloatValueWithUnits();
+
+        if (getPres(sty.setName("xlink:href")))
+        {
+            URI src = sty.getURIValue(getXMLBase());
+            href = diagram.getUniverse().getElement(src);
+        }
+        
+        //Determine use offset/scale
+        refXform = new AffineTransform();
+        refXform.translate(this.x, this.y);
+    }
+    
+    public void render(Graphics2D g) throws SVGException
+    {
+        beginLayer(g);
+
+        //AffineTransform oldXform = g.getTransform();
+        AffineTransform oldXform = g.getTransform();
+        g.transform(refXform);
+
+        if (href == null || !(href instanceof RenderableElement)) return;
+
+        RenderableElement rendEle = (RenderableElement)href;
+        rendEle.pushParentContext(this);
+        rendEle.render(g);
+        rendEle.popParentContext();
+
+        g.setTransform(oldXform);
+
+        finishLayer(g);
+    }
+
+    public Shape getShape()
+    {
+        if (href instanceof ShapeElement)
+        {
+            Shape shape = ((ShapeElement)href).getShape();
+            shape = refXform.createTransformedShape(shape);
+            shape = shapeToParent(shape);
+            return shape;
+        }
+
+        return null;
+    }
+
+    public Rectangle2D getBoundingBox() throws SVGException
+    {
+        if (href instanceof ShapeElement)
+        {
+            ShapeElement shapeEle = (ShapeElement)href;
+            shapeEle.pushParentContext(this);
+            Rectangle2D bounds = shapeEle.getBoundingBox();
+            shapeEle.popParentContext();
+            
+            bounds = refXform.createTransformedShape(bounds).getBounds2D();
+            bounds = boundsToParent(bounds);
+
+            return bounds;
+        }
+
+        return null;
+    }
+
+    /**
+     * Updates all attributes in this diagram associated with a time event.
+     * Ie, all attributes with track information.
+     * @return - true if this node has changed state as a result of the time
+     * update
+     */
+    public boolean updateTime(double curTime) throws SVGException
+    {
+//        if (trackManager.getNumTracks() == 0) return false;
+        boolean changeState = super.updateTime(curTime);
+
+        //Get current values for parameters
+        StyleAttribute sty = new StyleAttribute();
+        boolean shapeChange = false;
+        
+        if (getPres(sty.setName("x")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != x)
+            {
+                x = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("y")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != y)
+            {
+                y = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("width")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != width)
+            {
+                width = newVal;
+                shapeChange = true;
+            }
+        }
+
+        if (getPres(sty.setName("height")))
+        {
+            float newVal = sty.getFloatValueWithUnits();
+            if (newVal != height)
+            {
+                height = newVal;
+                shapeChange = true;
+            }
+        }
+        
+        if (getPres(sty.setName("xlink:href")))
+        {
+            URI src = sty.getURIValue(getXMLBase());
+            SVGElement newVal = diagram.getUniverse().getElement(src);
+            if (newVal != href)
+            {
+                href = newVal;
+                shapeChange = true;
+            }
+        }
+/*
+        if (getPres(sty.setName("xlink:href")))
+        {
+            URI src = sty.getURIValue(getXMLBase());
+            href = diagram.getUniverse().getElement(src);
+        }
+        
+        //Determine use offset/scale
+        refXform = new AffineTransform();
+        refXform.translate(this.x, this.y);
+        refXform.scale(this.width, this.height);
+*/        
+        if (shapeChange)
+        {
+            build();
+            //Determine use offset/scale
+//            refXform.setToTranslation(this.x, this.y);
+//            refXform.scale(this.width, this.height);
+//            return true;
+        }
+        
+        return changeState || shapeChange;
+    }
+}
Index: trunk/src/com/kitfox/svg/app/beans/SVGIcon.java
===================================================================
--- trunk/src/com/kitfox/svg/app/beans/SVGIcon.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/app/beans/SVGIcon.java	(revision 4256)
@@ -0,0 +1,385 @@
+/*
+ * SVGIcon.java
+ *
+ * Created on April 21, 2005, 10:45 AM
+ */
+
+package com.kitfox.svg.app.beans;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.net.*;
+import java.beans.*;
+
+import com.kitfox.svg.*;
+
+/**
+ *
+ * @author kitfox
+ */
+public class SVGIcon implements Icon
+{
+    public static final long serialVersionUID = 1;
+    
+    private PropertyChangeSupport changes = new PropertyChangeSupport(this);
+    
+    SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
+    public static final int INTERP_NEAREST_NEIGHBOR = 0;
+    public static final int INTERP_BILINEAR = 1;
+    public static final int INTERP_BICUBIC = 2;
+    
+    private boolean antiAlias;
+    private int interpolation = INTERP_NEAREST_NEIGHBOR;
+    private boolean clipToViewbox;
+    
+//    private String svgPath;
+    URI svgURI;
+    
+    private boolean scaleToFit;
+    AffineTransform scaleXform = new AffineTransform();
+    
+//    Dimension preferredSize = new Dimension(100, 100);
+    Dimension preferredSize;
+    
+    /** Creates a new instance of SVGIcon */
+    public SVGIcon()
+    {
+    }
+    
+    public void addPropertyChangeListener(PropertyChangeListener p)
+    {
+        changes.addPropertyChangeListener(p);
+    }
+    
+    public void removePropertyChangeListener(PropertyChangeListener p)
+    {
+        changes.removePropertyChangeListener(p);
+    }
+    
+    /**
+     * @return height of this icon
+     */
+    public int getIconHeight()
+    {
+        if (scaleToFit && preferredSize != null)
+        {
+            return preferredSize.height;
+        }
+        
+        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+        if (diagram == null) return 0;
+        return (int)diagram.getHeight();
+    }
+    
+    /**
+     * @return width of this icon
+     */
+    public int getIconWidth()
+    {
+        if (scaleToFit && preferredSize != null)
+        {
+            return preferredSize.width;
+        }
+        
+        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+        if (diagram == null) return 0;
+        return (int)diagram.getWidth();
+    }
+    
+    /**
+     * Draws the icon to the specified component.
+     * @param comp - Component to draw icon to.  This is ignored by SVGIcon, and can be set to null; only gg is used for drawing the icon
+     * @param gg - Graphics context to render SVG content to
+     * @param x - X coordinate to draw icon
+     * @param y - Y coordinate to draw icon
+     */
+    public void paintIcon(Component comp, Graphics gg, int x, int y)
+    {
+        Graphics2D g = (Graphics2D)gg;
+        
+        Object oldAliasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
+        
+        Object oldInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
+        switch (interpolation)
+        {
+            case INTERP_NEAREST_NEIGHBOR:
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+                break;
+            case INTERP_BILINEAR:
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                break;
+            case INTERP_BICUBIC:
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+                break;
+        }
+        
+        
+        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+        if (diagram == null) return;
+        
+        g.translate(x, y);
+        diagram.setIgnoringClipHeuristic(!clipToViewbox);
+        if (clipToViewbox)
+        {
+            g.setClip(new Rectangle2D.Float(0, 0, diagram.getWidth(), diagram.getHeight()));
+        }
+        
+        
+        
+        if (!scaleToFit)
+        {
+            try
+            {
+                diagram.render(g);
+                g.translate(-x, -y);
+                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+        
+        final int width = getIconWidth();
+        final int height = getIconHeight();
+//        int width = getWidth();
+//        int height = getHeight();
+        
+        if (width == 0 || height == 0)
+        {
+            return;
+        }
+        
+//        if (width == 0 || height == 0)
+//        {
+//           //Chances are we're rendering offscreen
+//            Dimension dim = getSize();
+//            width = dim.width;
+//            height = dim.height;
+//            return;
+//        }
+        
+//        g.setClip(0, 0, width, height);
+        
+        
+        final Rectangle2D.Double rect = new Rectangle2D.Double();
+        diagram.getViewRect(rect);
+        
+        scaleXform.setToScale(width / rect.width, height / rect.height);
+        
+        AffineTransform oldXform = g.getTransform();
+        g.transform(scaleXform);
+        
+        try
+        {
+            diagram.render(g);
+        }
+        catch (SVGException e)
+        {
+            throw new RuntimeException(e);
+        }
+        
+        g.setTransform(oldXform);
+        
+        
+        g.translate(-x, -y);
+        
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
+        if (oldInterpolationHint != null) g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldInterpolationHint);
+    }
+    
+    /**
+     * @return the universe this icon draws it's SVGDiagrams from
+     */
+    public SVGUniverse getSvgUniverse()
+    {
+        return svgUniverse;
+    }
+    
+    public void setSvgUniverse(SVGUniverse svgUniverse)
+    {
+        SVGUniverse old = this.svgUniverse;
+        this.svgUniverse = svgUniverse;
+        changes.firePropertyChange("svgUniverse", old, svgUniverse);
+    }
+    
+    /**
+     * @return the uni of the document being displayed by this icon
+     */
+    public URI getSvgURI()
+    {
+        return svgURI;
+    }
+    
+    /**
+     * Loads an SVG document from a URI.
+     * @param svgURI - URI to load document from
+     */
+    public void setSvgURI(URI svgURI)
+    {
+        URI old = this.svgURI;
+        this.svgURI = svgURI;
+        
+        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+        if (diagram != null)
+        {
+            Dimension size = getPreferredSize();
+            if (size == null)
+            {
+                size = new Dimension((int)diagram.getRoot().getDeviceWidth(), (int)diagram.getRoot().getDeviceHeight());
+            }
+            diagram.setDeviceViewport(new Rectangle(0, 0, size.width, size.height));
+        }
+        
+        changes.firePropertyChange("svgURI", old, svgURI);
+    }
+    
+    /**
+     * Loads an SVG document from the classpath.  This function is equivilant to
+     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
+     * @param resourcePath - resource to load
+     */
+    public void setSvgResourcePath(String resourcePath)
+    {
+        URI old = this.svgURI;
+        
+        try
+        {
+            svgURI = new URI(getClass().getResource(resourcePath).toString());
+            changes.firePropertyChange("svgURI", old, svgURI);
+            
+            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+            if (diagram != null)
+            {
+                diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
+            }
+            
+        }
+        catch (Exception e)
+        {
+            svgURI = old;
+        }
+    }
+    
+    /**
+     * If this SVG document has a viewbox, if scaleToFit is set, will scale the viewbox to match the
+     * preferred size of this icon
+     */
+    public boolean isScaleToFit()
+    {
+        return scaleToFit;
+    }
+    
+    public void setScaleToFit(boolean scaleToFit)
+    {
+        boolean old = this.scaleToFit;
+        this.scaleToFit = scaleToFit;
+        changes.firePropertyChange("scaleToFit", old, scaleToFit);
+    }
+    
+    public Dimension getPreferredSize()
+    {
+        if (preferredSize == null)
+        {
+            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+            if (diagram != null)
+            {
+                //preferredSize = new Dimension((int)diagram.getWidth(), (int)diagram.getHeight());
+                setPreferredSize(new Dimension((int)diagram.getWidth(), (int)diagram.getHeight()));
+            }
+        }
+        
+        return new Dimension(preferredSize);
+    }
+    
+    public void setPreferredSize(Dimension preferredSize)
+    {
+        Dimension old = this.preferredSize;
+        this.preferredSize = preferredSize;
+        
+        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+        if (diagram != null)
+        {
+            diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
+        }
+        
+        changes.firePropertyChange("preferredSize", old, preferredSize);
+    }
+    
+    
+    /**
+     * @return true if antiAliasing is turned on.
+     * @deprecated
+     */
+    public boolean getUseAntiAlias()
+    {
+        return getAntiAlias();
+    }
+    
+    /**
+     * @param antiAlias true to use antiAliasing.
+     * @deprecated
+     */
+    public void setUseAntiAlias(boolean antiAlias)
+    {
+        setAntiAlias(antiAlias);
+    }
+    
+    /**
+     * @return true if antiAliasing is turned on.
+     */
+    public boolean getAntiAlias()
+    {
+        return antiAlias;
+    }
+    
+    /**
+     * @param antiAlias true to use antiAliasing.
+     */
+    public void setAntiAlias(boolean antiAlias)
+    {
+        boolean old = this.antiAlias;
+        this.antiAlias = antiAlias;
+        changes.firePropertyChange("antiAlias", old, antiAlias);
+    }
+    
+    /**
+     * @return interpolation used in rescaling images
+     */
+    public int getInterpolation()
+    {
+        return interpolation;
+    }
+    
+    /**
+     * @param interpolation Interpolation value used in rescaling images.
+     * Should be one of
+     *    INTERP_NEAREST_NEIGHBOR - Fastest, one pixel resampling, poor quality
+     *    INTERP_BILINEAR - four pixel resampling
+     *    INTERP_BICUBIC - Slowest, nine pixel resampling, best quality
+     */
+    public void setInterpolation(int interpolation)
+    {
+        int old = this.interpolation;
+        this.interpolation = interpolation;
+        changes.firePropertyChange("interpolation", old, interpolation);
+    }
+    
+    /**
+     * clipToViewbox will set a clip box equivilant to the SVG's viewbox before
+     * rendering.
+     */
+    public boolean isClipToViewbox()
+    {
+        return clipToViewbox;
+    }
+    
+    public void setClipToViewbox(boolean clipToViewbox)
+    {
+        this.clipToViewbox = clipToViewbox;
+    }
+    
+}
Index: trunk/src/com/kitfox/svg/app/data/Handler.java
===================================================================
--- trunk/src/com/kitfox/svg/app/data/Handler.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/app/data/Handler.java	(revision 4256)
@@ -0,0 +1,76 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.kitfox.svg.app.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Handler extends URLStreamHandler
+{
+    class Connection extends URLConnection
+    {
+        String mime;
+        byte[] buf;
+
+        public Connection(URL url)
+        {
+            super(url);
+
+            String path = url.getPath();
+            int idx = path.indexOf(';');
+            mime = path.substring(0, idx);
+            String content = path.substring(idx + 1);
+
+            if (content.startsWith("base64,"))
+            {
+                content = content.substring(7);
+                try {
+                    buf = new sun.misc.BASE64Decoder().decodeBuffer(content);
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                }
+            }
+        }
+        
+        public void connect() throws IOException
+        {
+        }
+
+        public String getHeaderField(String name)
+        {
+            if ("content-type".equals(name))
+            {
+                return mime;
+            }
+
+            return super.getHeaderField(name);
+        }
+
+        public InputStream getInputStream() throws IOException
+        {
+            return new ByteArrayInputStream(buf);
+        }
+
+//        public Object getContent() throws IOException
+//        {
+//            BufferedImage img = ImageIO.read(getInputStream());
+//        }
+    }
+
+    protected URLConnection openConnection(URL u) throws IOException
+    {
+        return new Connection(u);
+    }
+
+}
Index: trunk/src/com/kitfox/svg/batik/GraphicsUtil.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/GraphicsUtil.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/GraphicsUtil.java	(revision 4256)
@@ -0,0 +1,382 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+import java.awt.image.DataBufferUShort;
+import java.awt.image.DirectColorModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+import java.awt.image.renderable.RenderContext;
+import java.awt.image.renderable.RenderableImage;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+/**
+ *
+ * @author  kitfox
+ */
+public class GraphicsUtil
+{
+    
+    /** Creates a new instance of GraphicsUtil */
+    public GraphicsUtil()
+    {
+    }
+    
+    /**
+     * Create a new ColorModel with it's alpha premultiplied state matching
+     * newAlphaPreMult.
+     * @param cm The ColorModel to change the alpha premult state of.
+     * @param newAlphaPreMult The new state of alpha premult.
+     * @return   A new colorModel that has isAlphaPremultiplied()
+     *           equal to newAlphaPreMult.
+     */
+    public static ColorModel coerceColorModel(ColorModel cm, boolean newAlphaPreMult)
+    {
+        if (cm.isAlphaPremultiplied() == newAlphaPreMult)
+            return cm;
+        
+        // Easiest way to build proper colormodel for new Alpha state...
+        // Eventually this should switch on known ColorModel types and
+        // only fall back on this hack when the CM type is unknown.
+        WritableRaster wr = cm.createCompatibleWritableRaster(1,1);
+        return cm.coerceData(wr, newAlphaPreMult);
+    }
+    
+    /**
+     * Coerces data within a bufferedImage to match newAlphaPreMult,
+     * Note that this can not change the colormodel of bi so you
+     *
+     * @param wr The raster to change the state of.
+     * @param cm The colormodel currently associated with data in wr.
+     * @param newAlphaPreMult The desired state of alpha Premult for raster.
+     * @return A new colormodel that matches newAlphaPreMult.
+     */
+    public static ColorModel coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult)
+    {
+        
+        // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() +
+        //                    " Out: " + newAlphaPreMult);
+        if (cm.hasAlpha()== false)
+            // Nothing to do no alpha channel
+            return cm;
+        
+        if (cm.isAlphaPremultiplied() == newAlphaPreMult)
+            // nothing to do alpha state matches...
+            return cm;
+        
+        // System.out.println("CoerceData: " + wr.getSampleModel());
+        
+        int [] pixel = null;
+        int    bands = wr.getNumBands();
+        float  norm;
+        if (newAlphaPreMult)
+        {
+            if (is_BYTE_COMP_Data(wr.getSampleModel()))
+                mult_BYTE_COMP_Data(wr);
+            else if (is_INT_PACK_Data(wr.getSampleModel(), true))
+                mult_INT_PACK_Data(wr);
+            else
+            {
+                norm = 1f/255f;
+                int x0, x1, y0, y1, a, b;
+                float alpha;
+                x0 = wr.getMinX();
+                x1 = x0+wr.getWidth();
+                y0 = wr.getMinY();
+                y1 = y0+wr.getHeight();
+                for (int y=y0; y<y1; y++)
+                    for (int x=x0; x<x1; x++)
+                    {
+                        pixel = wr.getPixel(x,y,pixel);
+                        a = pixel[bands-1];
+                        if ((a >= 0) && (a < 255))
+                        {
+                            alpha = a*norm;
+                            for (b=0; b<bands-1; b++)
+                                pixel[b] = (int)(pixel[b]*alpha+0.5f);
+                            wr.setPixel(x,y,pixel);
+                        }
+                    }
+            }
+        } else
+        {
+            if (is_BYTE_COMP_Data(wr.getSampleModel()))
+                divide_BYTE_COMP_Data(wr);
+            else if (is_INT_PACK_Data(wr.getSampleModel(), true))
+                divide_INT_PACK_Data(wr);
+            else
+            {
+                int x0, x1, y0, y1, a, b;
+                float ialpha;
+                x0 = wr.getMinX();
+                x1 = x0+wr.getWidth();
+                y0 = wr.getMinY();
+                y1 = y0+wr.getHeight();
+                for (int y=y0; y<y1; y++)
+                    for (int x=x0; x<x1; x++)
+                    {
+                        pixel = wr.getPixel(x,y,pixel);
+                        a = pixel[bands-1];
+                        if ((a > 0) && (a < 255))
+                        {
+                            ialpha = 255/(float)a;
+                            for (b=0; b<bands-1; b++)
+                                pixel[b] = (int)(pixel[b]*ialpha+0.5f);
+                            wr.setPixel(x,y,pixel);
+                        }
+                    }
+            }
+        }
+        
+        return coerceColorModel(cm, newAlphaPreMult);
+    }
+    
+    
+    public static boolean is_INT_PACK_Data(SampleModel sm,
+    boolean requireAlpha)
+    {
+        // Check ColorModel is of type DirectColorModel
+        if(!(sm instanceof SinglePixelPackedSampleModel)) return false;
+        
+        // Check transfer type
+        if(sm.getDataType() != DataBuffer.TYPE_INT)       return false;
+        
+        SinglePixelPackedSampleModel sppsm;
+        sppsm = (SinglePixelPackedSampleModel)sm;
+        
+        int [] masks = sppsm.getBitMasks();
+        if (masks.length == 3)
+        {
+            if (requireAlpha) return false;
+        } else if (masks.length != 4)
+            return false;
+        
+        if(masks[0] != 0x00ff0000) return false;
+        if(masks[1] != 0x0000ff00) return false;
+        if(masks[2] != 0x000000ff) return false;
+        if ((masks.length == 4) &&
+        (masks[3] != 0xff000000)) return false;
+        
+        return true;
+    }
+    
+    protected static void mult_INT_PACK_Data(WritableRaster wr)
+    {
+        // System.out.println("Multiply Int: " + wr);
+        
+        SinglePixelPackedSampleModel sppsm;
+        sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
+        
+        final int width = wr.getWidth();
+        
+        final int scanStride = sppsm.getScanlineStride();
+        DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
+        final int base
+        = (db.getOffset() +
+        sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
+        wr.getMinY()-wr.getSampleModelTranslateY()));
+        int n=0;
+        // Access the pixel data array
+        final int pixels[] = db.getBankData()[0];
+        for (int y=0; y<wr.getHeight(); y++)
+        {
+            int sp = base + y*scanStride;
+            final int end = sp + width;
+            while (sp < end)
+            {
+                int pixel = pixels[sp];
+                int a = pixel>>>24;
+                if ((a>=0) && (a<255))
+                {
+                    pixels[sp] = ((a << 24) |
+                    ((((pixel&0xFF0000)*a)>>8)&0xFF0000) |
+                    ((((pixel&0x00FF00)*a)>>8)&0x00FF00) |
+                    ((((pixel&0x0000FF)*a)>>8)&0x0000FF));
+                }
+                sp++;
+            }
+        }
+    }
+    
+    protected static void divide_INT_PACK_Data(WritableRaster wr)
+    {
+        // System.out.println("Divide Int");
+        
+        SinglePixelPackedSampleModel sppsm;
+        sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
+        
+        final int width = wr.getWidth();
+        
+        final int scanStride = sppsm.getScanlineStride();
+        DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
+        final int base
+        = (db.getOffset() +
+        sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
+        wr.getMinY()-wr.getSampleModelTranslateY()));
+        int pixel, a, aFP, n=0;
+        // Access the pixel data array
+        final int pixels[] = db.getBankData()[0];
+        for (int y=0; y<wr.getHeight(); y++)
+        {
+            int sp = base + y*scanStride;
+            final int end = sp + width;
+            while (sp < end)
+            {
+                pixel = pixels[sp];
+                a = pixel>>>24;
+                if (a<=0)
+                {
+                    pixels[sp] = 0x00FFFFFF;
+                }
+                else if (a<255)
+                {
+                    aFP = (0x00FF0000/a);
+                    pixels[sp] =
+                    ((a << 24) |
+                    (((((pixel&0xFF0000)>>16)*aFP)&0xFF0000)    ) |
+                    (((((pixel&0x00FF00)>>8) *aFP)&0xFF0000)>>8 ) |
+                    (((((pixel&0x0000FF))    *aFP)&0xFF0000)>>16));
+                }
+                sp++;
+            }
+        }
+    }
+    
+    public static boolean is_BYTE_COMP_Data(SampleModel sm)
+    {
+        // Check ColorModel is of type DirectColorModel
+        if(!(sm instanceof ComponentSampleModel))    return false;
+        
+        // Check transfer type
+        if(sm.getDataType() != DataBuffer.TYPE_BYTE) return false;
+        
+        return true;
+    }
+    
+    protected static void mult_BYTE_COMP_Data(WritableRaster wr)
+    {
+        // System.out.println("Multiply Int: " + wr);
+        
+        ComponentSampleModel csm;
+        csm = (ComponentSampleModel)wr.getSampleModel();
+        
+        final int width = wr.getWidth();
+        
+        final int scanStride = csm.getScanlineStride();
+        final int pixStride  = csm.getPixelStride();
+        final int [] bandOff = csm.getBandOffsets();
+        
+        DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
+        final int base
+        = (db.getOffset() +
+        csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
+        wr.getMinY()-wr.getSampleModelTranslateY()));
+        
+        
+        int a=0;
+        int aOff = bandOff[bandOff.length-1];
+        int bands = bandOff.length-1;
+        int b, i;
+        
+        // Access the pixel data array
+        final byte pixels[] = db.getBankData()[0];
+        for (int y=0; y<wr.getHeight(); y++)
+        {
+            int sp = base + y*scanStride;
+            final int end = sp + width*pixStride;
+            while (sp < end)
+            {
+                a = pixels[sp+aOff]&0xFF;
+                if (a!=0xFF)
+                    for (b=0; b<bands; b++)
+                    {
+                        i = sp+bandOff[b];
+                        pixels[i] = (byte)(((pixels[i]&0xFF)*a)>>8);
+                    }
+                sp+=pixStride;
+            }
+        }
+    }
+    
+    protected static void divide_BYTE_COMP_Data(WritableRaster wr)
+    {
+        // System.out.println("Multiply Int: " + wr);
+        
+        ComponentSampleModel csm;
+        csm = (ComponentSampleModel)wr.getSampleModel();
+        
+        final int width = wr.getWidth();
+        
+        final int scanStride = csm.getScanlineStride();
+        final int pixStride  = csm.getPixelStride();
+        final int [] bandOff = csm.getBandOffsets();
+        
+        DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
+        final int base
+        = (db.getOffset() +
+        csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
+        wr.getMinY()-wr.getSampleModelTranslateY()));
+        
+        
+        int a=0;
+        int aOff = bandOff[bandOff.length-1];
+        int bands = bandOff.length-1;
+        int b, i;
+        // Access the pixel data array
+        final byte pixels[] = db.getBankData()[0];
+        for (int y=0; y<wr.getHeight(); y++)
+        {
+            int sp = base + y*scanStride;
+            final int end = sp + width*pixStride;
+            while (sp < end)
+            {
+                a = pixels[sp+aOff]&0xFF;
+                if (a==0)
+                {
+                    for (b=0; b<bands; b++)
+                        pixels[sp+bandOff[b]] = (byte)0xFF;
+                } else if (a<255)
+                {
+                    int aFP = (0x00FF0000/a);
+                    for (b=0; b<bands; b++)
+                    {
+                        i = sp+bandOff[b];
+                        pixels[i] = (byte)(((pixels[i]&0xFF)*aFP)>>>16);
+                    }
+                }
+                sp+=pixStride;
+            }
+        }
+    }
+    
+    
+}
Index: trunk/src/com/kitfox/svg/batik/LinearGradientPaint.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/LinearGradientPaint.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/LinearGradientPaint.java	(revision 4256)
@@ -0,0 +1,354 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+
+/**
+ * The <code>LinearGradientPaint</code> class provides a way to fill
+ * a {@link java.awt.Shape} with a linear color gradient pattern.  The user may
+ * specify 2 or more gradient colors, and this paint will provide an
+ * interpolation between each color.  The user also specifies start and end
+ * points which define where in user space the color gradient should begin 
+ * and end.
+ * <p>
+ * The user must provide an array of floats specifying how to distribute the
+ * colors along the gradient.  These values should range from 0.0 to 1.0 and 
+ * act like keyframes along the gradient (they mark where the gradient should 
+ * be exactly a particular color).
+ * <p>
+ * For example:
+ * <br>
+ * <code>
+ * <p>
+ * Point2D start = new Point2D.Float(0, 0);<br>
+ * Point2D end = new Point2D.Float(100,100);<br>
+ * float[] dist = {0.0, 0.2, 1.0};<br>
+ * Color[] colors = {Color.red, Color.white, Color.blue};<br>
+ * LinearGradientPaint p = new LinearGradientPaint(start, end, dist, colors);
+ * </code>
+ *<p>
+ * This code will create a LinearGradientPaint which interpolates between 
+ * red and white for the first 20% of the gradient and between white and blue 
+ * for the remaining 80%.
+ *
+ * <p> In the event that the user does not set the first keyframe value equal
+ * to 0 and the last keyframe value equal to 1, keyframes will be created at
+ * these positions and the first and last colors will be replicated there.
+ * So, if a user specifies the following arrays to construct a gradient:<br>
+ * {Color.blue, Color.red}, {.3, .7}<br>
+ * this will be converted to a gradient with the following keyframes:
+ * {Color.blue, Color.blue, Color.red, Color.red}, {0, .3, .7, 1}
+ *
+ * <p>
+ * The user may also select what action the LinearGradientPaint should take
+ * when filling color outside the start and end points. If no cycle method is
+ * specified, NO_CYCLE will be chosen by default, so the endpoint colors 
+ * will be used to fill the remaining area.  
+ *
+ * <p> The following image demonstrates the options NO_CYCLE and REFLECT.
+ *
+ * <p>
+ * <img src = "cyclic.jpg">
+ *
+ * <p> The colorSpace parameter allows the user to specify in which colorspace
+ *  the interpolation should be performed, default sRGB or linearized RGB.
+ *  
+ *
+ * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: LinearGradientPaint.java,v 1.2 2004/09/27 09:27:27 kitfox Exp $
+ * @see java.awt.Paint
+ * @see java.awt.Graphics2D#setPaint
+ *
+ */
+
+public final class LinearGradientPaint extends MultipleGradientPaint {
+
+    /** Gradient start and end points. */
+    private Point2D start, end;   
+       
+    /**<p>
+     * Constructs an <code>LinearGradientPaint</code> with the default 
+     * NO_CYCLE repeating method and SRGB colorspace.
+     *
+     * @param startX the x coordinate of the gradient axis start point 
+     * in user space
+     *
+     * @param startY the y coordinate of the gradient axis start point 
+     * in user space
+     *
+     * @param endX the x coordinate of the gradient axis end point 
+     * in user space
+     *
+     * @param endY the y coordinate of the gradient axis end point 
+     * in user space
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *     
+     *
+     * @throws IllegalArgumentException if start and end points are the 
+     * same points, or if fractions.length != colors.length, or if colors 
+     * is less than 2 in size.
+     *
+     */
+    public LinearGradientPaint(float startX, float startY, 
+                               float endX, float endY, 
+                               float[] fractions, Color[] colors) {
+
+        this(new Point2D.Float(startX, startY),
+             new Point2D.Float(endX, endY), 
+             fractions, 
+             colors,
+             NO_CYCLE,
+             SRGB);
+    }
+
+    /**<p>
+     * Constructs an <code>LinearGradientPaint</code> with default SRGB 
+     * colorspace.
+     *
+     * @param startX the x coordinate of the gradient axis start point 
+     * in user space
+     *
+     * @param startY the y coordinate of the gradient axis start point 
+     * in user space
+     *
+     * @param endX the x coordinate of the gradient axis end point 
+     * in user space
+     * 
+     * @param endY the y coordinate of the gradient axis end point 
+     * in user space
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @throws IllegalArgumentException if start and end points are the 
+     * same points, or if fractions.length != colors.length, or if colors 
+     * is less than 2 in size.
+     *
+     */
+    public LinearGradientPaint(float startX, float startY, 
+                               float endX, float endY, 
+                               float[] fractions, Color[] colors, 
+                               CycleMethodEnum cycleMethod) {
+        this(new Point2D.Float(startX, startY), 
+             new Point2D.Float(endX, endY), 
+             fractions, 
+             colors,
+             cycleMethod,
+             SRGB);
+    }
+
+    /**<p>
+     * Constructs a <code>LinearGradientPaint</code> with the default 
+     * NO_CYCLE repeating method and SRGB colorspace.
+     *
+     * @param start the gradient axis start <code>Point</code> in user space
+     *
+     * @param end the gradient axis end <code>Point</code> in user space
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *
+     * @throws NullPointerException if one of the points is null
+     *
+     * @throws IllegalArgumentException if start and end points are the 
+     * same points, or if fractions.length != colors.length, or if colors 
+     * is less than 2 in size.
+     *
+     */
+    public LinearGradientPaint(Point2D start, Point2D end, float[] fractions,
+                               Color[] colors) {
+
+        this(start, end, fractions, colors, NO_CYCLE, SRGB);
+    }
+    
+    /**<p>
+     * Constructs a <code>LinearGradientPaint</code>.
+     *
+     * @param start the gradient axis start <code>Point</code> in user space
+     *
+     * @param end the gradient axis end <code>Point</code> in user space
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @param colorSpace which colorspace to use for interpolation, 
+     * either SRGB or LINEAR_RGB
+     *   
+     * @throws NullPointerException if one of the points is null
+     *
+     * @throws IllegalArgumentException if start and end points are the 
+     * same points, or if fractions.length != colors.length, or if colors 
+     * is less than 2 in size.
+     *
+     */
+    public LinearGradientPaint(Point2D start, Point2D end, float[] fractions,
+                               Color[] colors, 
+                               CycleMethodEnum cycleMethod, 
+                               ColorSpaceEnum colorSpace) {
+	
+        this(start, end, fractions, colors, cycleMethod, colorSpace, 
+             new AffineTransform());
+	
+    }
+    
+    /**<p>
+     * Constructs a <code>LinearGradientPaint</code>.
+     *
+     * @param start the gradient axis start <code>Point</code> in user space
+     *
+     * @param end the gradient axis end <code>Point</code> in user space
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @param colorSpace which colorspace to use for interpolation, 
+     * either SRGB or LINEAR_RGB
+     *
+     * @param gradientTransform transform to apply to the gradient
+     *     
+     * @throws NullPointerException if one of the points is null, 
+     * or gradientTransform is null
+     *
+     * @throws IllegalArgumentException if start and end points are the 
+     * same points, or if fractions.length != colors.length, or if colors 
+     * is less than 2 in size.
+     *
+     */
+    public LinearGradientPaint(Point2D start, Point2D end, float[] fractions,
+                               Color[] colors,
+                               CycleMethodEnum cycleMethod, 
+                               ColorSpaceEnum colorSpace, 
+                               AffineTransform gradientTransform) {
+        super(fractions, colors, cycleMethod, colorSpace, gradientTransform);
+
+        //
+        // Check input parameters
+        //	
+        if (start == null || end == null) {
+            throw new NullPointerException("Start and end points must be" +
+                                           "non-null");
+        }
+
+        if (start.equals(end)) {
+            throw new IllegalArgumentException("Start point cannot equal" +
+                                               "endpoint");
+        }
+
+        //copy the points...
+        this.start = (Point2D)start.clone();
+
+        this.end = (Point2D)end.clone();
+	
+    }
+    
+    /**
+     * Creates and returns a PaintContext used to generate the color pattern,
+     * for use by the internal rendering engine.
+     *
+     * @param cm {@link ColorModel} that receives
+     * the <code>Paint</code> data. This is used only as a hint.
+     *
+     * @param deviceBounds the device space bounding box of the 
+     * graphics primitive being rendered
+     *
+     * @param userBounds the user space bounding box of the 
+     * graphics primitive being rendered
+     *
+     * @param transform the {@link AffineTransform} from user
+     * space into device space
+     *
+     * @param hints the hints that the context object uses to choose
+     * between rendering alternatives
+     *
+     * @return the {@link PaintContext} that generates color patterns.
+     *
+     * @see PaintContext
+     */
+    public PaintContext createContext(ColorModel cm,
+                                      Rectangle deviceBounds,
+                                      Rectangle2D userBounds,
+                                      AffineTransform transform,
+                                      RenderingHints hints) {
+
+        // Can't modify the transform passed in...
+        transform = new AffineTransform(transform);
+        //incorporate the gradient transform
+        transform.concatenate(gradientTransform); 
+
+        try {
+            return new LinearGradientPaintContext(cm, 
+                                                  deviceBounds,
+                                                  userBounds, 
+                                                  transform,
+                                                  hints,
+                                                  start, 
+                                                  end,
+                                                  fractions,
+                                                  this.getColors(),
+                                                  cycleMethod,
+                                                  colorSpace);
+        }
+	
+        catch(NoninvertibleTransformException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("transform should be" + 
+                                               "invertible");
+        }
+    }
+    
+    /**
+     * Returns a copy of the start point of the gradient axis
+     * @return a {@link Point2D} object that is a copy of the point
+     * that anchors the first color of this 
+     * <code>LinearGradientPaint</code>.  
+     */
+    public Point2D getStartPoint() {
+        return new Point2D.Double(start.getX(), start.getY());
+    }
+    
+    /** Returns a copy of the end point of the gradient axis
+     * @return a {@link Point2D} object that is a copy of the point
+     * that anchors the last color of this 
+     * <code>LinearGradientPaint</code>.  
+     */
+    public Point2D getEndPoint() {
+        return new Point2D.Double(end.getX(), end.getY());
+    }
+        
+}
+
+
Index: trunk/src/com/kitfox/svg/batik/LinearGradientPaintContext.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/LinearGradientPaintContext.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/LinearGradientPaintContext.java	(revision 4256)
@@ -0,0 +1,529 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+
+/**
+ * Provides the actual implementation for the LinearGradientPaint
+ * This is where the pixel processing is done.
+ * 
+ * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: LinearGradientPaintContext.java,v 1.2 2007/02/04 01:28:05 kitfox Exp $
+ * @see java.awt.PaintContext
+ * @see java.awt.Paint
+ * @see java.awt.GradientPaint
+ */
+final class LinearGradientPaintContext extends MultipleGradientPaintContext {
+    
+    /**
+     * The following invariants are used to process the gradient value from 
+     * a device space coordinate, (X, Y):
+     * g(X, Y) = dgdX*X + dgdY*Y + gc
+     */
+    private float dgdX, dgdY, gc, pixSz;    
+           
+    private static final int DEFAULT_IMPL = 1;
+    private static final int ANTI_ALIAS_IMPL  = 3;
+
+    private int fillMethod;
+
+    /** 
+     * Constructor for LinearGradientPaintContext.
+     *
+     *  @param cm {@link ColorModel} that receives
+     *  the <code>Paint</code> data. This is used only as a hint.
+     *
+     *  @param deviceBounds the device space bounding box of the 
+     *  graphics primitive being rendered
+     *
+     *  @param userBounds the user space bounding box of the 
+     *  graphics primitive being rendered
+     * 
+     *  @param t the {@link AffineTransform} from user
+     *  space into device space (gradientTransform should be 
+     *  concatenated with this)
+     *
+     *  @param hints the hints that the context object uses to choose
+     *  between rendering alternatives
+     *
+     *  @param start gradient start point, in user space
+     *
+     *  @param end gradient end point, in user space
+     *
+     *  @param fractions the fractions specifying the gradient distribution
+     *
+     *  @param colors the gradient colors
+     *
+     *  @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     *  @param colorSpace which colorspace to use for interpolation, 
+     *  either SRGB or LINEAR_RGB
+     *
+     */
+    public LinearGradientPaintContext(ColorModel cm,
+                                      Rectangle deviceBounds,
+                                      Rectangle2D userBounds,
+                                      AffineTransform t,
+                                      RenderingHints hints,
+                                      Point2D dStart,
+                                      Point2D dEnd,
+                                      float[] fractions,
+                                      Color[] colors, 
+                                      MultipleGradientPaint.CycleMethodEnum 
+                                      cycleMethod,
+                                      MultipleGradientPaint.ColorSpaceEnum 
+                                      colorSpace)
+        throws NoninvertibleTransformException
+    {	
+        super(cm, deviceBounds, userBounds, t, hints, fractions, 
+              colors, cycleMethod, colorSpace);
+        
+        // Use single precision floating points
+        Point2D.Float start = new Point2D.Float((float)dStart.getX(),
+                                                (float)dStart.getY());
+        Point2D.Float end = new Point2D.Float((float)dEnd.getX(),
+                                              (float)dEnd.getY());
+        
+        // A given point in the raster should take on the same color as its
+        // projection onto the gradient vector.
+        // Thus, we want the projection of the current position vector
+        // onto the gradient vector, then normalized with respect to the
+        // length of the gradient vector, giving a value which can be mapped into
+        // the range 0-1.
+        // projection = currentVector dot gradientVector / length(gradientVector)
+        // normalized = projection / length(gradientVector)
+
+        float dx = end.x - start.x; // change in x from start to end
+        float dy = end.y - start.y; // change in y from start to end
+        float dSq = dx*dx + dy*dy; // total distance squared
+	
+        //avoid repeated calculations by doing these divides once.
+        float constX = dx/dSq;
+        float constY = dy/dSq;
+	
+        //incremental change along gradient for +x
+        dgdX = a00*constX + a10*constY;
+        //incremental change along gradient for +y
+        dgdY = a01*constX + a11*constY;
+        
+        float dgdXAbs = Math.abs(dgdX);
+        float dgdYAbs = Math.abs(dgdY);
+        if (dgdXAbs > dgdYAbs)  pixSz = dgdXAbs;
+        else                    pixSz = dgdYAbs;
+
+        //constant, incorporates the translation components from the matrix
+        gc = (a02-start.x)*constX + (a12-start.y)*constY;	       	
+
+        Object colorRend = hints == null ? RenderingHints.VALUE_COLOR_RENDER_SPEED : hints.get(RenderingHints.KEY_COLOR_RENDERING);
+        Object rend      = hints == null ? RenderingHints.VALUE_RENDER_SPEED : hints.get(RenderingHints.KEY_RENDERING);
+
+        fillMethod = DEFAULT_IMPL;
+
+        if ((cycleMethod == MultipleGradientPaint.REPEAT) ||
+            hasDiscontinuity) {
+            if (rend      == RenderingHints.VALUE_RENDER_QUALITY)
+                fillMethod = ANTI_ALIAS_IMPL;
+            // ColorRend overrides rend.
+            if (colorRend == RenderingHints.VALUE_COLOR_RENDER_SPEED)
+                fillMethod = DEFAULT_IMPL;
+            else if (colorRend == RenderingHints.VALUE_COLOR_RENDER_QUALITY)
+                fillMethod = ANTI_ALIAS_IMPL;
+        } 
+    }
+
+    protected void fillHardNoCycle(int[] pixels, int off, int adjust, 
+                              int x, int y, int w, int h) {
+
+        //constant which can be pulled out of the inner loop
+        final float initConst = (dgdX*x) + gc;
+
+        for(int i=0; i<h; i++) { //for every row
+            //initialize current value to be start.
+            float g = initConst + dgdY*(y+i); 
+            final int rowLimit = off+w;  // end of row iteration
+
+            if (dgdX == 0) {
+                // System.out.println("In fillHard: " + g);
+                final int val;
+                if (g <= 0) 
+                    val = gradientUnderflow;
+                else if (g >= 1)
+                    val = gradientOverflow;
+                else {
+                    // Could be a binary search...
+                    int gradIdx = 0;
+                    while (gradIdx < gradientsLength-1) {
+                        if (g < fractions[gradIdx+1])
+                            break;
+                        gradIdx++;
+                    }
+                    float delta = (g-fractions[gradIdx]);
+                    float idx  = ((delta*GRADIENT_SIZE_INDEX)
+                                  /normalizedIntervals[gradIdx])+0.5f;
+                    val = gradients[gradIdx][(int)idx];
+                }
+
+                while (off < rowLimit) {
+                    pixels[off++] = val;
+                }
+            } else {
+                // System.out.println("In fillHard2: " + g);
+                int gradSteps;
+                int preGradSteps;
+                final int preVal, postVal;
+                if (dgdX >= 0) {
+                    gradSteps    = (int)         ((1-g)/dgdX);
+                    preGradSteps = (int)Math.ceil((0-g)/dgdX);
+                    preVal  = gradientUnderflow;
+                    postVal = gradientOverflow;
+                } else { // dgdX < 0
+                    gradSteps    = (int)         ((0-g)/dgdX);
+                    preGradSteps = (int)Math.ceil((1-g)/dgdX);
+                    preVal  = gradientOverflow;
+                    postVal = gradientUnderflow;
+                }
+
+                if (gradSteps > w) 
+                    gradSteps = w;
+
+                final int gradLimit    = off + gradSteps;
+                if (preGradSteps > 0) {
+                    if (preGradSteps > w)
+                        preGradSteps = w;
+                    final int preGradLimit = off + preGradSteps;
+
+                    while (off < preGradLimit) {
+                        pixels[off++] = preVal;
+                    }
+                    g += dgdX*preGradSteps;
+                }
+                        
+                if (dgdX > 0) {
+                    // Could be a binary search...
+                    int gradIdx = 0;
+                    while (gradIdx < gradientsLength-1) {
+                        if (g < fractions[gradIdx+1])
+                            break;
+                        gradIdx++;
+                    }
+                    
+                    while (off < gradLimit) {
+                        float delta = (g-fractions[gradIdx]);
+                        final int [] grad = gradients[gradIdx];
+
+                        int steps = 
+                            (int)Math.ceil((fractions[gradIdx+1]-g)/dgdX);
+                        int subGradLimit = off + steps;
+                        if (subGradLimit > gradLimit)
+                            subGradLimit = gradLimit;
+
+                        int idx  = (int)(((delta*GRADIENT_SIZE_INDEX)
+                                          /normalizedIntervals[gradIdx])
+                                         *(1<<16)) + (1<<15);
+                        int step = (int)(((dgdX*GRADIENT_SIZE_INDEX)
+                                          /normalizedIntervals[gradIdx])
+                                         *(1<<16));
+                        while (off < subGradLimit) {
+                            pixels[off++] = grad[idx>>16];
+                            idx += step;
+                        }
+                        g+=dgdX*steps;
+                        gradIdx++;
+                    }
+                } else {
+                    // Could be a binary search...
+                    int gradIdx = gradientsLength-1;
+                    while (gradIdx > 0) {
+                        if (g > fractions[gradIdx])
+                            break;
+                        gradIdx--;
+                    }
+                    
+                    while (off < gradLimit) {
+                        float delta = (g-fractions[gradIdx]);
+                        final int [] grad = gradients[gradIdx];
+
+                        int steps        = (int)Math.ceil(delta/-dgdX);
+                        int subGradLimit = off + steps;
+                        if (subGradLimit > gradLimit)
+                            subGradLimit = gradLimit;
+
+                        int idx  = (int)(((delta*GRADIENT_SIZE_INDEX)
+                                          /normalizedIntervals[gradIdx])
+                                         *(1<<16)) + (1<<15);
+                        int step = (int)(((dgdX*GRADIENT_SIZE_INDEX)
+                                          /normalizedIntervals[gradIdx])
+                                         *(1<<16));
+                        while (off < subGradLimit) {
+                            pixels[off++] = grad[idx>>16];
+                            idx += step;
+                        }
+                        g+=dgdX*steps;
+                        gradIdx--;
+                    }
+                }
+
+                while (off < rowLimit) {
+                    pixels[off++] = postVal;
+                }
+            }
+            off += adjust; //change in off from row to row
+        }
+    }
+
+    protected void fillSimpleNoCycle(int[] pixels, int off, int adjust, 
+                                int x, int y, int w, int h) {
+        //constant which can be pulled out of the inner loop
+        final float initConst = (dgdX*x) + gc;
+        final float      step = dgdX*fastGradientArraySize;
+        final int      fpStep = (int)(step*(1<<16));  // fix point step
+
+        final int [] grad = gradient;
+
+        for(int i=0; i<h; i++){ //for every row
+            //initialize current value to be start.
+            float g = initConst + dgdY*(y+i); 
+            g *= fastGradientArraySize;
+            g += 0.5; // rounding factor...
+
+            final int rowLimit = off+w;  // end of row iteration
+
+            if (dgdX == 0) {
+                // System.out.println("In fillSimpleNC: " + g);
+                final int val;
+                if (g<=0) 
+                    val = gradientUnderflow;
+                else if (g>=fastGradientArraySize) 
+                    val = gradientOverflow;
+                else 
+                    val = grad[(int)g];
+                while (off < rowLimit) {
+                    pixels[off++] = val;
+                }
+            } else {
+                // System.out.println("In fillSimpleNC2: " + g);
+                int gradSteps;
+                int preGradSteps;
+                final int preVal, postVal;
+                if (dgdX > 0) {
+                    gradSteps = (int)((fastGradientArraySize-g)/step);
+                    preGradSteps = (int)Math.ceil(0-g/step);
+                    preVal  = gradientUnderflow;
+                    postVal = gradientOverflow;
+
+                } else { // dgdX < 0
+                    gradSteps    = (int)((0-g)/step);
+                    preGradSteps = 
+                        (int)Math.ceil((fastGradientArraySize-g)/step);
+                    preVal  = gradientOverflow;
+                    postVal = gradientUnderflow;
+                }
+
+                if (gradSteps > w) 
+                    gradSteps = w;
+                final int gradLimit    = off + gradSteps;
+
+                if (preGradSteps > 0) {
+                    if (preGradSteps > w)
+                        preGradSteps = w;
+                    final int preGradLimit = off + preGradSteps;
+
+                    while (off < preGradLimit) {
+                        pixels[off++] = preVal;
+                    }
+                    g += step*preGradSteps;
+                }
+                        
+                int fpG = (int)(g*(1<<16));
+                while (off < gradLimit) {
+                    pixels[off++] = grad[fpG>>16];
+                    fpG += fpStep;
+                }
+                        
+                while (off < rowLimit) {
+                    pixels[off++] = postVal;
+                }
+            }
+            off += adjust; //change in off from row to row
+        }
+    }
+    
+    protected void fillSimpleRepeat(int[] pixels, int off, int adjust, 
+                               int x, int y, int w, int h) {
+
+        final float initConst = (dgdX*x) + gc;
+
+        // Limit step to fractional part of
+        // fastGradientArraySize (the non fractional part has
+        // no affect anyways, and would mess up lots of stuff
+        // below).
+        float step = (dgdX - (int)dgdX)*fastGradientArraySize;
+
+                // Make it a Positive step (a small negative step is
+                // the same as a positive step slightly less than
+                // fastGradientArraySize.
+        if (step < 0) 
+            step += fastGradientArraySize;
+
+        final int [] grad = gradient;
+
+        for(int i=0; i<h; i++) { //for every row
+            //initialize current value to be start.
+            float g = initConst + dgdY*(y+i); 
+
+            // now Limited between -1 and 1.
+            g = g-(int)g;
+            // put in the positive side.
+            if (g < 0)
+                g += 1;
+                        
+            // scale for gradient array... 
+            g *= fastGradientArraySize;
+            g += 0.5; // rounding factor
+            final int rowLimit = off+w;  // end of row iteration
+            while (off < rowLimit) {
+                int idx = (int)g;
+                if (idx >= fastGradientArraySize) {
+                    g   -= fastGradientArraySize;
+                    idx -= fastGradientArraySize; 
+                }
+                pixels[off++] = grad[idx];
+                g += step;
+            }
+
+            off += adjust; //change in off from row to row
+        }
+    }
+
+
+    protected void fillSimpleReflect(int[] pixels, int off, int adjust, 
+                                int x, int y, int w, int h) {
+        final float initConst = (dgdX*x) + gc;
+
+        final int [] grad = gradient;
+
+        for (int i=0; i<h; i++) { //for every row
+            //initialize current value to be start.
+            float g = initConst + dgdY*(y+i); 
+
+            // now limited g to -2<->2
+            g = g - 2*((int)(g/2.0f));
+
+            float step = dgdX;
+            // Pull it into the positive half
+            if (g < 0) {
+                g = -g; //take absolute value
+                step = - step;  // Change direction..
+            }
+
+            // Now do the same for dgdX. This is safe because
+            // any step that is a multiple of 2.0 has no
+            // affect, hence we can remove it which the first
+            // part does.  The second part simply adds 2.0
+            // (which has no affect due to the cylcle) to move
+            // all negative step values into the positive
+            // side.
+            step = step - 2*((int)step/2.0f);
+            if (step < 0) 
+                step += 2.0;
+            final int reflectMax = 2*fastGradientArraySize;
+
+            // Scale for gradient array.
+            g    *= fastGradientArraySize;
+            g    += 0.5;
+            step *= fastGradientArraySize;
+            final int rowLimit = off+w;  // end of row iteration
+            while (off < rowLimit) {
+                int idx = (int)g;
+                if (idx >= reflectMax) {
+                    g   -= reflectMax;
+                    idx -= reflectMax;
+                }
+
+                if (idx <= fastGradientArraySize)
+                    pixels[off++] = grad[idx];
+                else
+                    pixels[off++] = grad[reflectMax-idx];
+                g+= step;
+            }
+
+            off += adjust; //change in off from row to row
+        }
+    }
+        
+    /**
+     * Return a Raster containing the colors generated for the graphics
+     * operation.  This is where the area is filled with colors distributed
+     * linearly.
+     *
+     * @param x,y,w,h The area in device space for which colors are
+     * generated.
+     *
+     */
+    protected void fillRaster(int[] pixels, int off, int adjust, 
+                              int x, int y, int w, int h) {
+	
+        //constant which can be pulled out of the inner loop
+        final float initConst = (dgdX*x) + gc;
+
+        if (fillMethod == ANTI_ALIAS_IMPL) {
+            //initialize current value to be start.
+            for(int i=0; i<h; i++){ //for every row
+                float g = initConst + dgdY*(y+i);
+                
+                final int rowLimit = off+w;  // end of row iteration
+                while(off < rowLimit){ //for every pixel in this row.
+                    //get the color
+                    pixels[off++] = indexGradientAntiAlias(g, pixSz); 
+                    g += dgdX; //incremental change in g
+                }
+                off += adjust; //change in off from row to row
+            }
+        }
+        else if (!isSimpleLookup) {
+            if (cycleMethod == MultipleGradientPaint.NO_CYCLE) {
+                fillHardNoCycle(pixels, off, adjust, x, y, w, h);
+            }
+            else {
+                //initialize current value to be start.
+                for(int i=0; i<h; i++){ //for every row
+                    float g = initConst + dgdY*(y+i); 
+                
+                    final int rowLimit = off+w;  // end of row iteration
+                    while(off < rowLimit){ //for every pixel in this row.
+                        //get the color
+                        pixels[off++] = indexIntoGradientsArrays(g); 
+                        g += dgdX; //incremental change in g
+                    }
+                    off += adjust; //change in off from row to row
+                }
+            }
+        } else {
+            // Simple implementations: just scale index by array size
+            
+            if (cycleMethod == MultipleGradientPaint.NO_CYCLE)
+                fillSimpleNoCycle(pixels, off, adjust, x, y, w, h);
+            else if (cycleMethod == MultipleGradientPaint.REPEAT)
+                fillSimpleRepeat(pixels, off, adjust, x, y, w, h);
+            else //cycleMethod == MultipleGradientPaint.REFLECT
+                fillSimpleReflect(pixels, off, adjust, x, y, w, h);
+        }
+    }
+    
+    
+}
Index: trunk/src/com/kitfox/svg/batik/MultipleGradientPaint.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/MultipleGradientPaint.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/MultipleGradientPaint.java	(revision 4256)
@@ -0,0 +1,236 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.Paint;
+import java.awt.geom.AffineTransform;
+
+/** This is the superclass for Paints which use a multiple color
+ * gradient to fill in their raster.  It provides storage for variables and
+ * enumerated values common to LinearGradientPaint and RadialGradientPaint.
+ *
+ *
+ * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: MultipleGradientPaint.java,v 1.2 2004/09/27 09:27:27 kitfox Exp $
+ *
+ */
+
+public abstract class MultipleGradientPaint implements Paint {
+
+    /** Transparency. */
+    protected int transparency;
+
+    /** Gradient keyframe values in the range 0 to 1. */
+    protected float[] fractions;
+
+    /** Gradient colors. */
+    protected Color[] colors;
+
+    /** Transform to apply to gradient. */
+    protected AffineTransform gradientTransform;
+
+    /** The method to use when painting out of the gradient bounds. */
+    protected CycleMethodEnum cycleMethod;
+
+    /** The colorSpace in which to perform the interpolation. */
+    protected ColorSpaceEnum colorSpace;
+
+    /** Inner class to allow for typesafe enumerated ColorSpace values. */
+    public static class ColorSpaceEnum {
+    }
+
+    /** Inner class to allow for typesafe enumerated CycleMethod values. */
+    public static class CycleMethodEnum {
+    }
+
+    /** Indicates (if the gradient starts or ends inside the target region)
+     *  to use the terminal colors to fill the remaining area. (default)
+     */
+    public static final CycleMethodEnum NO_CYCLE = new CycleMethodEnum();
+
+    /** Indicates (if the gradient starts or ends inside the target region),
+     *  to cycle the gradient colors start-to-end, end-to-start to fill the
+     *  remaining area.
+     */
+    public static final CycleMethodEnum REFLECT = new CycleMethodEnum();
+
+    /** Indicates (if the gradient starts or ends inside the target region),
+     *  to cycle the gradient colors start-to-end, start-to-end to fill the
+     *  remaining area.
+     */
+    public static final CycleMethodEnum REPEAT = new CycleMethodEnum();
+
+    /** Indicates that the color interpolation should occur in sRGB space.
+     *  (default)
+     */
+    public static final ColorSpaceEnum SRGB = new ColorSpaceEnum();
+
+    /** Indicates that the color interpolation should occur in linearized
+     *  RGB space.
+     */
+    public static final ColorSpaceEnum LINEAR_RGB = new ColorSpaceEnum();
+
+
+     /**
+     * Superclass constructor, typical user should never have to call this.
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors corresponding to each fractional value
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @param colorSpace which colorspace to use for interpolation,
+     * either SRGB or LINEAR_RGB
+     *
+     * @param gradientTransform transform to apply to the gradient
+     *
+     * @throws NullPointerException if arrays are null, or
+     * gradientTransform is null
+     *
+     * @throws IllegalArgumentException if fractions.length != colors.length,
+     * or if colors is less than 2 in size, or if an enumerated value is bad.
+     *
+     * @see java.awt.PaintContext
+     */
+    public MultipleGradientPaint(float[] fractions,
+                                 Color[] colors,
+                                 CycleMethodEnum cycleMethod,
+                                 ColorSpaceEnum colorSpace,
+                                 AffineTransform gradientTransform) {
+
+        if (fractions == null) {
+            throw new IllegalArgumentException("Fractions array cannot be " +
+                                               "null");
+        }
+
+        if (colors == null) {
+            throw new IllegalArgumentException("Colors array cannot be null");
+        }
+
+        if (fractions.length != colors.length) {
+            throw new IllegalArgumentException("Colors and fractions must " +
+                                               "have equal size");
+        }
+
+        if (colors.length < 2) {
+            throw new IllegalArgumentException("User must specify at least " +
+                                               "2 colors");
+        }
+
+        if ((colorSpace != LINEAR_RGB) &&
+            (colorSpace != SRGB)) {
+            throw new IllegalArgumentException("Invalid colorspace for " +
+                                               "interpolation.");
+        }
+
+        if ((cycleMethod != NO_CYCLE) &&
+            (cycleMethod != REFLECT) &&
+            (cycleMethod != REPEAT)) {
+            throw new IllegalArgumentException("Invalid cycle method.");
+        }
+
+        if (gradientTransform == null) {
+            throw new IllegalArgumentException("Gradient transform cannot be "+
+                                               "null.");
+        }
+
+        //copy the fractions array
+        this.fractions = new float[fractions.length];
+        System.arraycopy(fractions, 0, this.fractions, 0, fractions.length);
+
+        //copy the colors array
+        this.colors = new Color[colors.length];
+        System.arraycopy(colors, 0, this.colors, 0, colors.length);
+
+        //copy some flags
+        this.colorSpace = colorSpace;
+        this.cycleMethod = cycleMethod;
+
+        //copy the gradient transform
+        this.gradientTransform = (AffineTransform)gradientTransform.clone();
+
+        // Process transparency
+        boolean opaque = true;
+        for(int i=0; i<colors.length; i++){
+            opaque = opaque && (colors[i].getAlpha()==0xff);
+        }
+
+        if(opaque) {
+            transparency = OPAQUE;
+        }
+
+        else {
+            transparency = TRANSLUCENT;
+        }
+    }
+
+    /**
+     * Returns a copy of the array of colors used by this gradient.
+     * @return a copy of the array of colors used by this gradient
+     *
+     */
+    public Color[] getColors() {
+        Color colors[] = new Color[this.colors.length];
+        System.arraycopy(this.colors, 0, colors, 0, this.colors.length);
+        return colors;
+    }
+
+    /**
+     * Returns a copy of the array of floats used by this gradient
+     * to calculate color distribution.
+     * @return a copy of the array of floats used by this gradient to
+     * calculate color distribution
+     *
+     */
+    public float[] getFractions() {
+        float fractions[] = new float[this.fractions.length];
+        System.arraycopy(this.fractions, 0, fractions, 0, this.fractions.length);
+        return fractions;
+    }
+
+    /**
+     * Returns the transparency mode for this LinearGradientPaint.
+     * @return an integer value representing this LinearGradientPaint object's
+     * transparency mode.
+     * @see java.awt.Transparency
+     */
+    public int getTransparency() {
+        return transparency;
+    }
+
+    /**
+     * Returns the enumerated type which specifies cycling behavior.
+     * @return the enumerated type which specifies cycling behavior
+     */
+    public CycleMethodEnum getCycleMethod() {
+        return cycleMethod;
+    }
+
+    /**
+     * Returns the enumerated type which specifies color space for
+     * interpolation.
+     * @return the enumerated type which specifies color space for
+     * interpolation
+     */
+    public ColorSpaceEnum getColorSpace() {
+        return colorSpace;
+    }
+
+    /**
+     * Returns a copy of the transform applied to the gradient.
+     * @return a copy of the transform applied to the gradient.
+     */
+    public AffineTransform getTransform() {
+        return (AffineTransform)gradientTransform.clone();
+    }
+}
Index: trunk/src/com/kitfox/svg/batik/MultipleGradientPaintContext.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/MultipleGradientPaintContext.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/MultipleGradientPaintContext.java	(revision 4256)
@@ -0,0 +1,1421 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DirectColorModel;
+import java.awt.image.Raster;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+import java.lang.ref.WeakReference;
+
+//import org.apache.batik.ext.awt.image.GraphicsUtil;
+
+/** This is the superclass for all PaintContexts which use a multiple color
+ * gradient to fill in their raster. It provides the actual color interpolation
+ * functionality.  Subclasses only have to deal with using the gradient to fill
+ * pixels in a raster.
+ *
+ * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: MultipleGradientPaintContext.java,v 1.1 2004/09/06 19:35:39 kitfox Exp $
+ *
+ */
+abstract class MultipleGradientPaintContext implements PaintContext {
+
+    protected final static boolean DEBUG = false;
+
+    /**
+     * The color model data is generated in (always un premult).
+     */
+    protected ColorModel dataModel;
+    /**
+     * PaintContext's output ColorModel ARGB if colors are not all
+     * opaque, RGB otherwise.  Linear and premult are matched to
+     * output ColorModel.
+     */
+    protected ColorModel model;
+
+    /** Color model used if gradient colors are all opaque */
+    private static ColorModel lrgbmodel_NA = new DirectColorModel
+        (ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
+         24, 0xff0000, 0xFF00, 0xFF, 0x0,
+         false, DataBuffer.TYPE_INT);
+
+    private static ColorModel srgbmodel_NA = new DirectColorModel
+        (ColorSpace.getInstance(ColorSpace.CS_sRGB),
+         24, 0xff0000, 0xFF00, 0xFF, 0x0,
+         false, DataBuffer.TYPE_INT);
+
+    /** Color model used if some gradient colors are transparent */
+    private static ColorModel lrgbmodel_A = new DirectColorModel
+        (ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
+         32, 0xff0000, 0xFF00, 0xFF, 0xFF000000,
+         false, DataBuffer.TYPE_INT);
+
+    private static ColorModel srgbmodel_A = new DirectColorModel
+        (ColorSpace.getInstance(ColorSpace.CS_sRGB),
+         32, 0xff0000, 0xFF00, 0xFF, 0xFF000000,
+         false, DataBuffer.TYPE_INT);
+
+     /** The cached colorModel */
+    protected static ColorModel cachedModel;
+
+    /** The cached raster, which is reusable among instances */
+    protected static WeakReference cached;
+
+    /** Raster is reused whenever possible */
+    protected WritableRaster saved;
+
+    /** The method to use when painting out of the gradient bounds. */
+    protected MultipleGradientPaint.CycleMethodEnum cycleMethod;
+
+    /** The colorSpace in which to perform the interpolation */
+    protected MultipleGradientPaint.ColorSpaceEnum colorSpace;
+
+    /** Elements of the inverse transform matrix. */
+    protected float a00, a01, a10, a11, a02, a12;
+
+    /** This boolean specifies wether we are in simple lookup mode, where an
+     * input value between 0 and 1 may be used to directly index into a single
+     * array of gradient colors.  If this boolean value is false, then we have
+     * to use a 2-step process where we have to determine which gradient array
+     * we fall into, then determine the index into that array.
+     */
+    protected boolean isSimpleLookup = true;
+
+    /** This boolean indicates if the gradient appears to have sudden
+     *  discontinuities in it, this may be because of multiple stops
+     *  at the same location or use of the REPEATE mode.  
+     */
+    protected boolean hasDiscontinuity = false;
+
+    /** Size of gradients array for scaling the 0-1 index when looking up
+     *  colors the fast way.  */
+    protected int fastGradientArraySize;
+
+    /**
+     * Array which contains the interpolated color values for each interval,
+     * used by calculateSingleArrayGradient().  It is protected for possible
+     * direct access by subclasses.
+     */
+    protected int[] gradient;
+
+    /** Array of gradient arrays, one array for each interval.  Used by
+     *  calculateMultipleArrayGradient().
+     */
+    protected int[][] gradients;
+
+    /** This holds the blend of all colors in the gradient.
+     *  we use this at extreamly low resolutions to ensure we
+     *  get a decent blend of the colors.
+     */
+    protected int gradientAverage;
+
+    /** This holds the color to use when we are off the bottom of the
+     * gradient */
+    protected int gradientUnderflow;
+
+    /** This holds the color to use when we are off the top of the
+     * gradient */
+    protected int gradientOverflow;
+
+    /** Length of the 2D slow lookup gradients array. */
+    protected int gradientsLength;
+
+    /** Normalized intervals array */
+    protected float[] normalizedIntervals;
+
+    /** fractions array */
+    protected float[] fractions;
+
+    /** Used to determine if gradient colors are all opaque */
+    private int transparencyTest;
+
+    /** Colorspace conversion lookup tables */
+    private static final int SRGBtoLinearRGB[] = new int[256];
+    private static final int LinearRGBtoSRGB[] = new int[256];
+
+    //build the tables
+    static{
+        for (int k = 0; k < 256; k++) {
+            SRGBtoLinearRGB[k] = convertSRGBtoLinearRGB(k);
+            LinearRGBtoSRGB[k] = convertLinearRGBtoSRGB(k);
+        }
+    }
+
+    /** Constant number of max colors between any 2 arbitrary colors.
+     * Used for creating and indexing gradients arrays.
+     */
+    protected static final int GRADIENT_SIZE = 256;
+    protected static final int GRADIENT_SIZE_INDEX = GRADIENT_SIZE -1;
+
+    /** Maximum length of the fast single-array.  If the estimated array size
+     * is greater than this, switch over to the slow lookup method.
+     * No particular reason for choosing this number, but it seems to provide
+     * satisfactory performance for the common case (fast lookup).
+     */
+    private static final int MAX_GRADIENT_ARRAY_SIZE = 5000;
+
+   /** Constructor for superclass. Does some initialization, but leaves most
+    * of the heavy-duty math for calculateGradient(), so the subclass may do
+    * some other manipulation beforehand if necessary.  This is not possible
+    * if this computation is done in the superclass constructor which always
+    * gets called first.
+    **/
+    public MultipleGradientPaintContext(ColorModel cm,
+                                        Rectangle deviceBounds,
+                                        Rectangle2D userBounds,
+                                        AffineTransform t,
+                                        RenderingHints hints,
+                                        float[] fractions,
+                                        Color[] colors,
+                                        MultipleGradientPaint.CycleMethodEnum
+                                        cycleMethod,
+                                        MultipleGradientPaint.ColorSpaceEnum
+                                        colorSpace)
+        throws NoninvertibleTransformException
+    {
+        //We have to deal with the cases where the 1st gradient stop is not
+        //equal to 0 and/or the last gradient stop is not equal to 1.
+        //In both cases, create a new point and replicate the previous
+        //extreme point's color.
+
+        boolean fixFirst = false;
+        boolean fixLast = false;
+        int len = fractions.length;
+
+        //if the first gradient stop is not equal to zero, fix this condition
+        if (fractions[0] != 0f) {
+            fixFirst = true;
+            len++;
+        }
+
+        //if the last gradient stop is not equal to one, fix this condition
+        if (fractions[fractions.length - 1] != 1f) {
+            fixLast = true;
+            len++;
+        }
+        
+        for (int i=0; i<fractions.length-1; i++)
+            if (fractions[i] == fractions[i+1])
+                len--;
+
+        this.fractions      = new float[len];
+        Color [] loColors   = new Color[len-1];
+        Color [] hiColors   = new Color[len-1];
+        normalizedIntervals = new float[len-1];
+
+        gradientUnderflow = colors[0].getRGB();
+        gradientOverflow  = colors[colors.length-1].getRGB();
+
+        int idx = 0;
+        if (fixFirst) {
+            this.fractions[0] = 0;
+            loColors[0] = colors[0];
+            hiColors[0] = colors[0];
+            normalizedIntervals[0] = fractions[0];
+            idx++;
+        }
+
+        for (int i=0; i<fractions.length-1; i++) {
+            if (fractions[i] == fractions[i+1]) {
+                // System.out.println("EQ Fracts");
+                if (!colors[i].equals(colors[i+1])) {
+                    hasDiscontinuity = true;
+                }
+                continue;
+            }
+            this.fractions[idx] = fractions[i];
+            loColors[idx] = colors[i];
+            hiColors[idx] = colors[i+1];
+            normalizedIntervals[idx] = fractions[i+1]-fractions[i];
+            idx++;
+        }
+            
+        this.fractions[idx] = fractions[fractions.length-1];
+
+        if (fixLast) {
+            loColors[idx] = hiColors[idx] = colors[colors.length-1];
+            normalizedIntervals[idx] = 1-fractions[fractions.length-1];
+            idx++;
+            this.fractions[idx] = 1;
+        }
+
+        // The inverse transform is needed to from device to user space.
+        // Get all the components of the inverse transform matrix.
+        AffineTransform tInv = t.createInverse();
+
+        double m[] = new double[6];
+        tInv.getMatrix(m);
+        a00 = (float)m[0];
+        a10 = (float)m[1];
+        a01 = (float)m[2];
+        a11 = (float)m[3];
+        a02 = (float)m[4];
+        a12 = (float)m[5];
+
+        //copy some flags
+        this.cycleMethod = cycleMethod;
+        this.colorSpace = colorSpace;
+
+        // Setup an example Model, we may refine it later.
+        if (cm.getColorSpace() == lrgbmodel_A.getColorSpace())
+            dataModel = lrgbmodel_A;
+        else if (cm.getColorSpace() == srgbmodel_A.getColorSpace())
+            dataModel = srgbmodel_A;
+        else
+            throw new IllegalArgumentException
+                ("Unsupported ColorSpace for interpolation");
+
+        calculateGradientFractions(loColors, hiColors);
+
+        model = GraphicsUtil.coerceColorModel(dataModel,
+                                              cm.isAlphaPremultiplied());
+    }
+
+
+    /** This function is the meat of this class.  It calculates an array of
+     * gradient colors based on an array of fractions and color values at those
+     * fractions.
+     */
+    protected final void calculateGradientFractions
+        (Color []loColors, Color []hiColors) {
+
+        //if interpolation should occur in Linear RGB space, convert the
+        //colors using the lookup table
+        if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
+            for (int i = 0; i < loColors.length; i++) {
+                loColors[i] = 
+                    new Color(SRGBtoLinearRGB[loColors[i].getRed()],
+                              SRGBtoLinearRGB[loColors[i].getGreen()],
+                              SRGBtoLinearRGB[loColors[i].getBlue()],
+                              loColors[i].getAlpha());
+                
+                hiColors[i] = 
+                    new Color(SRGBtoLinearRGB[hiColors[i].getRed()],
+                              SRGBtoLinearRGB[hiColors[i].getGreen()],
+                              SRGBtoLinearRGB[hiColors[i].getBlue()],
+                              hiColors[i].getAlpha());
+            }
+        }
+
+        //initialize to be fully opaque for ANDing with colors
+        transparencyTest = 0xff000000;
+
+        //array of interpolation arrays
+        gradients = new int[fractions.length - 1][];
+        gradientsLength = gradients.length;
+
+        // Find smallest interval
+        int n = normalizedIntervals.length;
+
+        float Imin = 1;
+
+        for(int i = 0; i < n; i++) {
+            Imin = (Imin > normalizedIntervals[i]) ?
+                normalizedIntervals[i] : Imin;
+        }
+
+        //estimate the size of the entire gradients array.
+        //This is to prevent a tiny interval from causing the size of array to
+        //explode.  If the estimated size is too large, break to using
+        //seperate arrays for each interval, and using an indexing scheme at
+        //look-up time.
+        int estimatedSize = 0;
+
+        if (Imin == 0) {
+            estimatedSize = Integer.MAX_VALUE;
+            hasDiscontinuity = true;
+        } else {
+            for (int i = 0; i < normalizedIntervals.length; i++) {
+                estimatedSize += (normalizedIntervals[i]/Imin) * GRADIENT_SIZE;
+            }
+        }
+
+
+        if (estimatedSize > MAX_GRADIENT_ARRAY_SIZE) {
+            //slow method
+            calculateMultipleArrayGradient(loColors, hiColors);
+            if ((cycleMethod == MultipleGradientPaint.REPEAT) &&
+                (gradients[0][0] != 
+                 gradients[gradients.length-1][GRADIENT_SIZE_INDEX]))
+                hasDiscontinuity = true;
+        } else {
+            //fast method
+            calculateSingleArrayGradient(loColors, hiColors, Imin);
+            if ((cycleMethod == MultipleGradientPaint.REPEAT) &&
+                (gradient[0] != gradient[fastGradientArraySize]))
+                hasDiscontinuity = true;
+        }
+
+        // Use the most 'economical' model (no alpha).
+        if((transparencyTest >>> 24) == 0xff) {
+            if (dataModel.getColorSpace() == lrgbmodel_NA.getColorSpace())
+                dataModel = lrgbmodel_NA;
+            else if (dataModel.getColorSpace() == srgbmodel_NA.getColorSpace())
+                dataModel = srgbmodel_NA;
+            model = dataModel;
+        }
+    }
+
+
+    /**
+     * FAST LOOKUP METHOD
+     *
+     * This method calculates the gradient color values and places them in a
+     * single int array, gradient[].  It does this by allocating space for
+     * each interval based on its size relative to the smallest interval in
+     * the array.  The smallest interval is allocated 255 interpolated values
+     * (the maximum number of unique in-between colors in a 24 bit color
+     * system), and all other intervals are allocated
+     * size = (255 * the ratio of their size to the smallest interval).
+     *
+     * This scheme expedites a speedy retrieval because the colors are
+     * distributed along the array according to their user-specified
+     * distribution.  All that is needed is a relative index from 0 to 1.
+     *
+     * The only problem with this method is that the possibility exists for
+     * the array size to balloon in the case where there is a
+     * disproportionately small gradient interval.  In this case the other
+     * intervals will be allocated huge space, but much of that data is
+     * redundant.  We thus need to use the space conserving scheme below.
+     *
+     * @param Imin the size of the smallest interval
+     *
+     */
+    private void calculateSingleArrayGradient
+        (Color [] loColors, Color [] hiColors, float Imin) {
+
+        //set the flag so we know later it is a non-simple lookup
+        isSimpleLookup = true;
+
+        int rgb1; //2 colors to interpolate
+        int rgb2;
+
+        int gradientsTot = 1; //the eventual size of the single array
+
+        // These are fixed point 8.16 (start with 0.5)
+        int aveA = 0x008000;
+        int aveR = 0x008000;
+        int aveG = 0x008000;
+        int aveB = 0x008000;
+
+        //for every interval (transition between 2 colors)
+        for(int i=0; i < gradients.length; i++){
+
+            //create an array whose size is based on the ratio to the
+            //smallest interval.
+            int nGradients = (int)((normalizedIntervals[i]/Imin)*255f);
+            gradientsTot += nGradients;
+            gradients[i] = new int[nGradients];
+
+            //the the 2 colors (keyframes) to interpolate between
+            rgb1 = loColors[i].getRGB();
+            rgb2 = hiColors[i].getRGB();
+
+            //fill this array with the colors in between rgb1 and rgb2
+            interpolate(rgb1, rgb2, gradients[i]);
+
+            // Calculate Average of two colors...
+            int argb = gradients[i][GRADIENT_SIZE/2];
+            float norm = normalizedIntervals[i];
+            aveA += (int)(((argb>> 8)&0xFF0000)*norm);
+            aveR += (int)(((argb    )&0xFF0000)*norm);
+            aveG += (int)(((argb<< 8)&0xFF0000)*norm);
+            aveB += (int)(((argb<<16)&0xFF0000)*norm);
+
+            //if the colors are opaque, transparency should still be 0xff000000
+            transparencyTest &= rgb1;
+            transparencyTest &= rgb2;
+        }
+
+        gradientAverage = (((aveA & 0xFF0000)<< 8) |
+                           ((aveR & 0xFF0000)    ) |
+                           ((aveG & 0xFF0000)>> 8) |
+                           ((aveB & 0xFF0000)>>16));
+
+        // Put all gradients in a single array
+        gradient = new int[gradientsTot];
+        int curOffset = 0;
+        for(int i = 0; i < gradients.length; i++){
+            System.arraycopy(gradients[i], 0, gradient,
+                             curOffset, gradients[i].length);
+            curOffset += gradients[i].length;
+        }
+        gradient[gradient.length-1] = hiColors[hiColors.length-1].getRGB();
+
+        //if interpolation occurred in Linear RGB space, convert the
+        //gradients back to SRGB using the lookup table
+        if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
+            if (dataModel.getColorSpace() ==
+                ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
+                for (int i = 0; i < gradient.length; i++) {
+                    gradient[i] =
+                        convertEntireColorLinearRGBtoSRGB(gradient[i]);
+                }
+                gradientAverage = 
+                    convertEntireColorLinearRGBtoSRGB(gradientAverage);
+            }
+        } else {
+            if (dataModel.getColorSpace() ==
+                ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) {
+                for (int i = 0; i < gradient.length; i++) {
+                    gradient[i] =
+                        convertEntireColorSRGBtoLinearRGB(gradient[i]);
+                }
+                gradientAverage = 
+                    convertEntireColorSRGBtoLinearRGB(gradientAverage);
+            }
+        }
+
+        fastGradientArraySize = gradient.length - 1;
+    }
+
+
+    /**
+     * SLOW LOOKUP METHOD
+     *
+     * This method calculates the gradient color values for each interval and
+     * places each into its own 255 size array.  The arrays are stored in
+     * gradients[][].  (255 is used because this is the maximum number of
+     * unique colors between 2 arbitrary colors in a 24 bit color system)
+     *
+     * This method uses the minimum amount of space (only 255 * number of
+     * intervals), but it aggravates the lookup procedure, because now we
+     * have to find out which interval to select, then calculate the index
+     * within that interval.  This causes a significant performance hit,
+     * because it requires this calculation be done for every point in
+     * the rendering loop.
+     *
+     * For those of you who are interested, this is a classic example of the
+     * time-space tradeoff.
+     *
+     */
+    private void calculateMultipleArrayGradient
+        (Color [] loColors, Color [] hiColors) {
+
+        //set the flag so we know later it is a non-simple lookup
+        isSimpleLookup = false;
+
+        int rgb1; //2 colors to interpolate
+        int rgb2;
+
+        // These are fixed point 8.16 (start with 0.5)
+        int aveA = 0x008000;
+        int aveR = 0x008000;
+        int aveG = 0x008000;
+        int aveB = 0x008000;
+
+        //for every interval (transition between 2 colors)
+        for(int i=0; i < gradients.length; i++){
+
+            // This interval will never actually be used (zero size)
+            if (normalizedIntervals[i] == 0)
+                continue;
+
+            //create an array of the maximum theoretical size for each interval
+            gradients[i] = new int[GRADIENT_SIZE];
+
+            //get the the 2 colors
+            rgb1 = loColors[i].getRGB();
+            rgb2 = hiColors[i].getRGB();
+
+            //fill this array with the colors in between rgb1 and rgb2
+            interpolate(rgb1, rgb2, gradients[i]);
+
+            // Calculate Average of two colors...
+            int argb = gradients[i][GRADIENT_SIZE/2];
+            float norm = normalizedIntervals[i];
+            aveA += (int)(((argb>> 8)&0xFF0000)*norm);
+            aveR += (int)(((argb    )&0xFF0000)*norm);
+            aveG += (int)(((argb<< 8)&0xFF0000)*norm);
+            aveB += (int)(((argb<<16)&0xFF0000)*norm);
+
+            //if the colors are opaque, transparency should still be 0xff000000
+            transparencyTest &= rgb1;
+            transparencyTest &= rgb2;
+        }
+
+        gradientAverage = (((aveA & 0xFF0000)<< 8) |
+                           ((aveR & 0xFF0000)    ) |
+                           ((aveG & 0xFF0000)>> 8) |
+                           ((aveB & 0xFF0000)>>16));
+
+        //if interpolation occurred in Linear RGB space, convert the
+        //gradients back to SRGB using the lookup table
+        if (colorSpace == LinearGradientPaint.LINEAR_RGB) {
+            if (dataModel.getColorSpace() ==
+                ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
+                for (int j = 0; j < gradients.length; j++) {
+                    for (int i = 0; i < gradients[j].length; i++) {
+                        gradients[j][i] =
+                            convertEntireColorLinearRGBtoSRGB(gradients[j][i]);
+                    }
+                }
+                gradientAverage = 
+                    convertEntireColorLinearRGBtoSRGB(gradientAverage);
+            }
+        } else {
+            if (dataModel.getColorSpace() ==
+                ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) {
+                for (int j = 0; j < gradients.length; j++) {
+                    for (int i = 0; i < gradients[j].length; i++) {
+                        gradients[j][i] =
+                            convertEntireColorSRGBtoLinearRGB(gradients[j][i]);
+                    }
+                }
+                gradientAverage = 
+                    convertEntireColorSRGBtoLinearRGB(gradientAverage);
+            }
+        }
+    }
+
+    /** Yet another helper function.  This one linearly interpolates between
+     * 2 colors, filling up the output array.
+     *
+     * @param rgb1 the start color
+     * @param rgb2 the end color
+     * @param output the output array of colors... assuming this is not null.
+     *
+     */
+    private void interpolate(int rgb1, int rgb2, int[] output) {
+
+        int a1, r1, g1, b1, da, dr, dg, db; //color components
+
+        //step between interpolated values.
+        float stepSize = 1/(float)output.length;
+
+        //extract color components from packed integer
+        a1 = (rgb1 >> 24) & 0xff;
+        r1 = (rgb1 >> 16) & 0xff;
+        g1 = (rgb1 >>  8) & 0xff;
+        b1 = (rgb1      ) & 0xff;
+        //calculate the total change in alpha, red, green, blue
+        da = ((rgb2 >> 24) & 0xff) - a1;
+        dr = ((rgb2 >> 16) & 0xff) - r1;
+        dg = ((rgb2 >>  8) & 0xff) - g1;
+        db = ((rgb2      ) & 0xff) - b1;
+
+        //for each step in the interval calculate the in-between color by
+        //multiplying the normalized current position by the total color change
+        //(.5 is added to prevent truncation round-off error)
+        for (int i = 0; i < output.length; i++) {
+            output[i] =
+                (((int) ((a1 + i * da * stepSize) + .5) << 24)) |
+                (((int) ((r1 + i * dr * stepSize) + .5) << 16)) |
+                (((int) ((g1 + i * dg * stepSize) + .5) <<  8)) |
+                (((int) ((b1 + i * db * stepSize) + .5)      ));
+        }
+    }
+
+
+    /** Yet another helper function.  This one extracts the color components
+     * of an integer RGB triple, converts them from LinearRGB to SRGB, then
+     * recompacts them into an int.
+     */
+    private int convertEntireColorLinearRGBtoSRGB(int rgb) {
+
+        int a1, r1, g1, b1; //color components
+
+        //extract red, green, blue components
+        a1 = (rgb >> 24) & 0xff;
+        r1 = (rgb >> 16) & 0xff;
+        g1 = (rgb >> 8) & 0xff;
+        b1 = rgb & 0xff;
+
+        //use the lookup table
+        r1 =  LinearRGBtoSRGB[r1];
+        g1 =  LinearRGBtoSRGB[g1];
+        b1 =  LinearRGBtoSRGB[b1];
+
+        //re-compact the components
+        return ((a1 << 24) |
+                (r1 << 16) |
+                (g1 << 8) |
+                b1);
+    }
+
+    /** Yet another helper function.  This one extracts the color components
+     * of an integer RGB triple, converts them from LinearRGB to SRGB, then
+     * recompacts them into an int.
+     */
+    private int convertEntireColorSRGBtoLinearRGB(int rgb) {
+
+        int a1, r1, g1, b1; //color components
+
+        //extract red, green, blue components
+        a1 = (rgb >> 24) & 0xff;
+        r1 = (rgb >> 16) & 0xff;
+        g1 = (rgb >> 8) & 0xff;
+        b1 = rgb & 0xff;
+
+        //use the lookup table
+        r1 =  SRGBtoLinearRGB[r1];
+        g1 =  SRGBtoLinearRGB[g1];
+        b1 =  SRGBtoLinearRGB[b1];
+
+        //re-compact the components
+        return ((a1 << 24) |
+                (r1 << 16) |
+                (g1 << 8) |
+                b1);
+    }
+
+
+    /** Helper function to index into the gradients array.  This is necessary
+     * because each interval has an array of colors with uniform size 255.
+     * However, the color intervals are not necessarily of uniform length, so
+     * a conversion is required.
+     *
+     * @param position the unmanipulated position.  want to map this into the
+     * range 0 to 1
+     *
+     * @returns integer color to display
+     *
+     */
+    protected final int indexIntoGradientsArrays(float position) {
+
+        //first, manipulate position value depending on the cycle method.
+
+        if (cycleMethod == MultipleGradientPaint.NO_CYCLE) {
+
+            if (position >= 1) { //upper bound is 1
+                return gradientOverflow;
+            }
+
+            else if (position <= 0) { //lower bound is 0
+                return gradientUnderflow;
+            }
+        }
+
+        else if (cycleMethod == MultipleGradientPaint.REPEAT) {
+            //get the fractional part
+            //(modulo behavior discards integer component)
+            position = position - (int)position;
+
+            //position now be between -1 and 1
+
+            if (position < 0) {
+                position = position + 1; //force it to be in the range 0-1
+            }
+
+            int w=0, c1=0, c2=0;
+            if (isSimpleLookup) {
+              position *= gradient.length;
+              int idx1 = (int)(position);
+              if (idx1+1 < gradient.length)
+                return gradient[idx1];
+
+              w = (int)((position-idx1)*(1<<16));
+              c1 = gradient[idx1];
+              c2 = gradient[0];
+            } else {
+              //for all the gradient interval arrays
+              for (int i = 0; i < gradientsLength; i++) {
+
+                if (position < fractions[i+1]) { //this is the array we want
+
+                  float delta = position - fractions[i];
+                  
+                  delta = ((delta / normalizedIntervals[i]) * GRADIENT_SIZE);
+                  //this is the interval we want.
+                  int index = (int)delta;
+                  if ((index+1<gradients[i].length) ||
+                      (i+1 < gradientsLength))
+                    return gradients[i][index];
+
+                  w  = (int)((delta-index)*(1<<16));
+                  c1 = gradients[i][index];
+                  c2 = gradients[0][0];
+                  break;
+                }
+              }
+            }
+
+            return 
+              ((((  (  (c1>>  8)           &0xFF0000)+
+                    ((((c2>>>24)     )-((c1>>>24)     ))*w))&0xFF0000)<< 8) |
+               
+               (((  (  (c1     )           &0xFF0000)+
+                    ((((c2>> 16)&0xFF)-((c1>> 16)&0xFF))*w))&0xFF0000)    ) |
+                    
+               (((  (  (c1<<  8)           &0xFF0000)+
+                    ((((c2>>  8)&0xFF)-((c1>>  8)&0xFF))*w))&0xFF0000)>> 8) |
+               
+               (((  (  (c1<< 16)           &0xFF0000)+
+                    ((((c2     )&0xFF)-((c1     )&0xFF))*w))&0xFF0000)>>16));
+
+            // return c1 +
+            //   ((( ((((c2>>>24)     )-((c1>>>24)     ))*w)&0xFF0000)<< 8) |
+            //    (( ((((c2>> 16)&0xFF)-((c1>> 16)&0xFF))*w)&0xFF0000)    ) |
+            //    (( ((((c2>>  8)&0xFF)-((c1>>  8)&0xFF))*w)&0xFF0000)>> 8) |
+            //    (( ((((c2     )&0xFF)-((c1     )&0xFF))*w)&0xFF0000)>>16));
+        }
+
+        else {  //cycleMethod == MultipleGradientPaint.REFLECT
+
+            if (position < 0) {
+                position = -position; //take absolute value
+            }
+
+            int part = (int)position; //take the integer part
+
+            position = position - part; //get the fractional part
+
+            if ((part & 0x00000001) == 1) { //if integer part is odd
+                position = 1 - position; //want the reflected color instead
+            }
+        }
+
+        //now, get the color based on this 0-1 position:
+
+        if (isSimpleLookup) { //easy to compute: just scale index by array size
+            return gradient[(int)(position * fastGradientArraySize)];
+        }
+
+        else { //more complicated computation, to save space
+
+            //for all the gradient interval arrays
+            for (int i = 0; i < gradientsLength; i++) {
+
+                if (position < fractions[i+1]) { //this is the array we want
+
+                    float delta = position - fractions[i];
+
+                    //this is the interval we want.
+                    int index = (int)((delta / normalizedIntervals[i])
+                                      * (GRADIENT_SIZE_INDEX));
+
+                    return gradients[i][index];
+                }
+            }
+
+        }
+
+        return gradientOverflow;
+    }
+
+
+    /** Helper function to index into the gradients array.  This is necessary
+     * because each interval has an array of colors with uniform size 255.
+     * However, the color intervals are not necessarily of uniform length, so
+     * a conversion is required.  This version also does anti-aliasing by
+     * averaging the gradient over position+/-(sz/2).
+     *
+     * @param position the unmanipulated position.  want to map this into the
+     * range 0 to 1
+     * @param sz the size in gradient space to average.
+     *
+     * @returns ARGB integer color to display
+     */
+    protected final int indexGradientAntiAlias(float position, float sz) {
+        //first, manipulate position value depending on the cycle method.
+        if (cycleMethod == MultipleGradientPaint.NO_CYCLE) {
+            if (DEBUG) System.out.println("NO_CYCLE");
+            float p1 = position-(sz/2);
+            float p2 = position+(sz/2);
+
+            if (p1 >= 1) 
+                return gradientOverflow;
+
+            if (p2 <= 0) 
+                return gradientUnderflow;
+
+            int interior;
+            float top_weight=0, bottom_weight=0, frac;
+            if (p2 >= 1) {
+                top_weight = (p2-1)/sz;
+                if (p1 <= 0) {
+                    bottom_weight = -p1/sz;
+                    frac=1;
+                    interior = gradientAverage;
+                } else {
+                    frac=1-p1;
+                    interior = getAntiAlias(p1, true, 1, false, 1-p1, 1);
+                }
+            } else if (p1 <= 0) {
+                bottom_weight = -p1/sz;
+                frac = p2;
+                interior = getAntiAlias(0, true, p2, false, p2, 1);
+            } else
+                return getAntiAlias(p1, true, p2, false, sz, 1);
+            
+            int norm = (int)((1<<16)*frac/sz);
+            int pA = (((interior>>>20)&0xFF0)*norm)>>16;
+            int pR = (((interior>> 12)&0xFF0)*norm)>>16;
+            int pG = (((interior>>  4)&0xFF0)*norm)>>16;
+            int pB = (((interior<<  4)&0xFF0)*norm)>>16;
+
+            if (bottom_weight != 0) {
+                int bPix = gradientUnderflow;
+                // System.out.println("ave: " + gradientAverage);
+                norm = (int)((1<<16)*bottom_weight);
+                pA += (((bPix>>>20) & 0xFF0)*norm)>>16;
+                pR += (((bPix>> 12) & 0xFF0)*norm)>>16;
+                pG += (((bPix>>  4) & 0xFF0)*norm)>>16;
+                pB += (((bPix<<  4) & 0xFF0)*norm)>>16;
+            }
+
+            if (top_weight != 0) {
+                int tPix = gradientOverflow;
+
+                norm = (int)((1<<16)*top_weight);
+                pA += (((tPix>>>20) & 0xFF0)*norm)>>16;
+                pR += (((tPix>> 12) & 0xFF0)*norm)>>16;
+                pG += (((tPix>>  4) & 0xFF0)*norm)>>16;
+                pB += (((tPix<<  4) & 0xFF0)*norm)>>16;
+            }
+
+            return (((pA&0xFF0)<<20)  |
+                    ((pR&0xFF0)<<12)  |
+                    ((pG&0xFF0)<< 4)  |
+                    ((pB&0xFF0)>> 4));
+        }
+
+        // See how many times we are going to "wrap around" the gradient,
+        // array.
+        int intSz = (int)sz;
+        
+        float weight = 1f;
+        if (intSz != 0) {
+            // We need to make sure that sz is < 1.0 otherwise 
+            // p1 and p2 my pass each other which will cause no end of
+            // trouble.
+            sz -= intSz;
+            weight = sz/(intSz+sz);
+            if (weight < 0.1)
+                // The part of the color from the location will be swamped
+                // by the averaged part of the gradient so just use the
+                // average color for the gradient.
+                return gradientAverage;
+        }
+            
+        // So close to full gradient just use the average value...
+        if (sz > 0.99)
+            return gradientAverage;
+            
+            // Go up and down from position by 1/2 sz.
+        float p1 = position-(sz/2);
+        float p2 = position+(sz/2);
+        if (DEBUG) System.out.println("P1: " + p1 + " P2: " + p2);
+
+        // These indicate the direction to go from p1 and p2 when
+        // averaging...
+        boolean p1_up=true;
+        boolean p2_up=false;
+
+        if (cycleMethod == MultipleGradientPaint.REPEAT) {
+            if (DEBUG) System.out.println("REPEAT");
+
+            // Get positions between -1 and 1
+            p1=p1-(int)p1;
+            p2=p2-(int)p2;
+
+            // force to be in rage 0-1.
+            if (p1 <0) p1 += 1;
+            if (p2 <0) p2 += 1;
+        }
+
+        else {  //cycleMethod == MultipleGradientPaint.REFLECT
+            if (DEBUG) System.out.println("REFLECT");
+
+            //take absolute values
+            // Note when we reflect we change sense of p1/2_up.
+            if (p2 < 0) {
+                p1 = -p1; p1_up = !p1_up;
+                p2 = -p2; p2_up = !p2_up;
+            } else if (p1 < 0) { 
+                p1 = -p1; p1_up = !p1_up; 
+            }
+
+            int part1, part2;
+            part1 = (int)p1;   // take the integer part
+            p1   = p1 - part1; // get the fractional part
+
+            part2 = (int)p2;   // take the integer part
+            p2   = p2 - part2; // get the fractional part
+
+            // if integer part is odd we want the reflected color instead.
+            // Note when we reflect we change sense of p1/2_up.
+            if ((part1 & 0x01) == 1) {
+                p1 = 1-p1;
+                p1_up = !p1_up;
+            }
+
+            if ((part2 & 0x01) == 1) {
+                p2 = 1-p2;
+                p2_up = !p2_up;
+            }
+
+            // Check if in the end they just got switched around.
+            // this commonly happens if they both end up negative.
+            if ((p1 > p2) && !p1_up && p2_up) {
+                float t = p1;
+                p1 = p2; 
+                p2 = t;
+                p1_up = true;
+                p2_up = false;
+            }
+        }
+
+        return getAntiAlias(p1, p1_up, p2, p2_up, sz, weight);
+    }
+
+
+    private final int getAntiAlias(float p1, boolean p1_up,
+                                   float p2, boolean p2_up,
+                                   float sz, float weight) {
+
+        // Until the last set of ops these are 28.4 fixed point values.
+        int ach=0, rch=0, gch=0, bch=0;
+        if (isSimpleLookup) {
+            p1 *= fastGradientArraySize;
+            p2 *= fastGradientArraySize;
+
+            int idx1 = (int)p1;
+            int idx2 = (int)p2;
+
+            int i, pix;
+
+            if (p1_up && !p2_up && (idx1 <= idx2)) {
+
+                if (idx1 == idx2)
+                    return gradient[idx1];
+
+                // Sum between idx1 and idx2.
+                for (i=idx1+1; i<idx2; i++) {
+                    pix  = gradient[i];
+                    ach += ((pix>>>20)&0xFF0);
+                    rch += ((pix>>>12)&0xFF0);
+                    gch += ((pix>>> 4)&0xFF0);
+                    bch += ((pix<<  4)&0xFF0);
+                }
+            } else {
+                // Do the bulk of the work, all the whole gradient entries
+                // for idx1 and idx2.
+                if (p1_up) {
+                    for (i=idx1+1; i<fastGradientArraySize; i++) {
+                        pix  = gradient[i];
+                        ach += ((pix>>>20)&0xFF0);
+                        rch += ((pix>>>12)&0xFF0);
+                        gch += ((pix>>> 4)&0xFF0);
+                        bch += ((pix<<  4)&0xFF0);
+                    }
+                } else {
+                    for (i=0; i<idx1; i++) {
+                        pix  = gradient[i];
+                        ach += ((pix>>>20)&0xFF0);
+                        rch += ((pix>>>12)&0xFF0);
+                        gch += ((pix>>> 4)&0xFF0);
+                        bch += ((pix<<  4)&0xFF0);
+                    }
+                }
+
+                if (p2_up) {
+                    for (i=idx2+1; i<fastGradientArraySize; i++) {
+                        pix  = gradient[i];
+                        ach += ((pix>>>20)&0xFF0);
+                        rch += ((pix>>>12)&0xFF0);
+                        gch += ((pix>>> 4)&0xFF0);
+                        bch += ((pix<<  4)&0xFF0);
+                    }
+                } else {
+                    for (i=0; i<idx2; i++) {
+                        pix  = gradient[i];
+                        ach += ((pix>>>20)&0xFF0);
+                        rch += ((pix>>>12)&0xFF0);
+                        gch += ((pix>>> 4)&0xFF0);
+                        bch += ((pix<<  4)&0xFF0);
+                    }
+                }
+            }
+
+            int norm, isz;
+
+            // Normalize the summation so far...
+            isz = (int)((1<<16)/(sz*fastGradientArraySize));
+            ach = (ach*isz)>>16;
+            rch = (rch*isz)>>16;
+            gch = (gch*isz)>>16;
+            bch = (bch*isz)>>16;
+
+            // Clean up with the partial buckets at each end.
+            if (p1_up) norm = (int)((1-(p1-idx1))*isz);
+            else       norm = (int)(   (p1-idx1) *isz);
+            pix = gradient[idx1];
+            ach += (((pix>>>20)&0xFF0) *norm)>>16;
+            rch += (((pix>>>12)&0xFF0) *norm)>>16;
+            gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+            bch += (((pix<<  4)&0xFF0) *norm)>>16;
+
+            if (p2_up) norm = (int)((1-(p2-idx2))*isz);
+            else       norm = (int)(   (p2-idx2) *isz);
+            pix = gradient[idx2];
+            ach += (((pix>>>20)&0xFF0) *norm)>>16;
+            rch += (((pix>>>12)&0xFF0) *norm)>>16;
+            gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+            bch += (((pix<<  4)&0xFF0) *norm)>>16;
+
+            // Round and drop the 4bits frac.
+            ach = (ach+0x08)>>4;
+            rch = (rch+0x08)>>4;
+            gch = (gch+0x08)>>4;
+            bch = (bch+0x08)>>4;
+
+        } else {
+            int idx1=0, idx2=0;
+            int i1=-1, i2=-1;
+            float f1=0, f2=0;
+            // Find which gradient interval our points fall into.
+            for (int i = 0; i < gradientsLength; i++) {
+                if ((p1 < fractions[i+1]) && (i1 == -1)) { 
+                    //this is the array we want
+                    i1 = i;
+                    f1 = p1 - fractions[i];
+
+                    f1 = ((f1/normalizedIntervals[i])
+                             *GRADIENT_SIZE_INDEX);
+                    //this is the  interval we want.
+                    idx1 = (int)f1;
+                    if (i2 != -1) break;
+                }
+                if ((p2 < fractions[i+1]) && (i2 == -1)) { 
+                    //this is the array we want
+                    i2 = i;
+                    f2 = p2 - fractions[i];
+                    
+                    f2 = ((f2/normalizedIntervals[i])
+                             *GRADIENT_SIZE_INDEX);
+                    //this is the interval we want.
+                    idx2 = (int)f2;
+                    if (i1 != -1) break;
+                }
+            }
+
+            if (i1 == -1) {
+                i1 = gradients.length - 1;
+                f1 = idx1 = GRADIENT_SIZE_INDEX;
+            }
+
+            if (i2 == -1) {
+                i2 = gradients.length - 1;
+                f2 = idx2 = GRADIENT_SIZE_INDEX;
+            }
+
+            if (DEBUG) System.out.println("I1: " + i1 + " Idx1: " + idx1 +
+                                          " I2: " + i2 + " Idx2: " + idx2); 
+
+            // Simple case within one gradient array (so the average
+            // of the two idx gives us the true average of colors).
+            if ((i1 == i2) && (idx1 <= idx2) && p1_up && !p2_up)
+                return gradients[i1][(idx1+idx2+1)>>1];
+
+            // i1 != i2
+
+            int pix, norm;
+            int base = (int)((1<<16)/sz);
+            if ((i1 < i2) && p1_up && !p2_up) {
+                norm = (int)((base
+                              *normalizedIntervals[i1]
+                              *(GRADIENT_SIZE_INDEX-f1))
+                             /GRADIENT_SIZE_INDEX);
+                pix  = gradients[i1][(idx1+GRADIENT_SIZE)>>1];
+                ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                bch += (((pix<<  4)&0xFF0) *norm)>>16;
+
+                for (int i=i1+1; i<i2; i++) {
+                    norm = (int)(base*normalizedIntervals[i]);
+                    pix  = gradients[i][GRADIENT_SIZE>>1];
+                  
+                    ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                    rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                    gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                    bch += (((pix<<  4)&0xFF0) *norm)>>16;
+                }
+
+                norm = (int)((base*normalizedIntervals[i2]*f2)
+                             /GRADIENT_SIZE_INDEX);
+                pix  = gradients[i2][(idx2+1)>>1];
+                ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                bch += (((pix<<  4)&0xFF0) *norm)>>16;
+            } else {
+                if (p1_up) {
+                    norm = (int)((base
+                                  *normalizedIntervals[i1]
+                                  *(GRADIENT_SIZE_INDEX-f1))
+                                 /GRADIENT_SIZE_INDEX);
+                    pix  = gradients[i1][(idx1+GRADIENT_SIZE)>>1];
+                } else {
+                    norm = (int)((base*normalizedIntervals[i1]*f1)
+                                 /GRADIENT_SIZE_INDEX);
+                    pix  = gradients[i1][(idx1+1)>>1];
+                }
+                ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                bch += (((pix<<  4)&0xFF0) *norm)>>16;
+
+                if (p2_up) {
+                    norm = (int)((base
+                                  *normalizedIntervals[i2]
+                                  *(GRADIENT_SIZE_INDEX-f2))
+                                 /GRADIENT_SIZE_INDEX);
+                    pix  =  gradients[i2][(idx2+GRADIENT_SIZE)>>1];
+                } else {
+                    norm = (int)((base*normalizedIntervals[i2]*f2)
+                                 /GRADIENT_SIZE_INDEX);
+                    pix  = gradients[i2][(idx2+1)>>1];
+                }
+                ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                bch += (((pix<<  4)&0xFF0) *norm)>>16;
+
+                if (p1_up) {
+                    for (int i=i1+1; i<gradientsLength; i++) {
+                        norm = (int)(base*normalizedIntervals[i]);
+                        pix  = gradients[i][GRADIENT_SIZE>>1];
+
+                        ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                        rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                        gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                        bch += (((pix<<  4)&0xFF0) *norm)>>16;
+                    }
+                } else {
+                    for (int i=0; i<i1; i++) {
+                        norm = (int)(base*normalizedIntervals[i]);
+                        pix  = gradients[i][GRADIENT_SIZE>>1];
+                  
+                        ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                        rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                        gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                        bch += (((pix<<  4)&0xFF0) *norm)>>16;
+                    }
+                }
+
+                if (p2_up) {
+                    for (int i=i2+1; i<gradientsLength; i++) {
+                        norm = (int)(base*normalizedIntervals[i]);
+                        pix  = gradients[i][GRADIENT_SIZE>>1];
+
+                        ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                        rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                        gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                        bch += (((pix<<  4)&0xFF0) *norm)>>16;
+                    }
+                } else {
+                    for (int i=0; i<i2; i++) {
+                        norm = (int)(base*normalizedIntervals[i]);
+                        pix  = gradients[i][GRADIENT_SIZE>>1];
+
+                        ach += (((pix>>>20)&0xFF0) *norm)>>16;
+                        rch += (((pix>>>12)&0xFF0) *norm)>>16;
+                        gch += (((pix>>> 4)&0xFF0) *norm)>>16;
+                        bch += (((pix<<  4)&0xFF0) *norm)>>16;
+                    }
+                }
+
+            }
+            ach = (ach+0x08)>>4;
+            rch = (rch+0x08)>>4;
+            gch = (gch+0x08)>>4;
+            bch = (bch+0x08)>>4;
+            if (DEBUG) System.out.println("Pix: [" + ach + ", " + rch + 
+                                          ", " + gch + ", " + bch + "]");
+        }
+
+        if (weight != 1) {
+            // System.out.println("ave: " + gradientAverage);
+            int aveW = (int)((1<<16)*(1-weight));
+            int aveA = ((gradientAverage>>>24) & 0xFF)*aveW;
+            int aveR = ((gradientAverage>> 16) & 0xFF)*aveW;
+            int aveG = ((gradientAverage>>  8) & 0xFF)*aveW;
+            int aveB = ((gradientAverage     ) & 0xFF)*aveW;
+
+            int iw = (int)(weight*(1<<16));
+            ach = ((ach*iw)+aveA)>>16;
+            rch = ((rch*iw)+aveR)>>16;
+            gch = ((gch*iw)+aveG)>>16;
+            bch = ((bch*iw)+aveB)>>16;
+        }
+              
+        return ((ach<<24) | (rch<<16) | (gch<<8) | bch);
+    }
+
+
+    /** Helper function to convert a color component in sRGB space to linear
+     * RGB space.  Used to build a static lookup table.
+     */
+    private static int convertSRGBtoLinearRGB(int color) {
+
+        float input, output;
+
+        input = ((float) color) / 255.0f;
+        if (input <= 0.04045f) {
+            output = input / 12.92f;
+        }
+        else {
+            output = (float) Math.pow((input + 0.055) / 1.055, 2.4);
+        }
+        int o = Math.round(output * 255.0f);
+
+        return o;
+    }
+
+     /** Helper function to convert a color component in linear RGB space to
+      *  SRGB space. Used to build a static lookup table.
+      */
+    private static int convertLinearRGBtoSRGB(int color) {
+
+        float input, output;
+
+        input = ((float) color) / 255.0f;
+
+        if (input <= 0.0031308) {
+            output = input * 12.92f;
+        }
+        else {
+            output = (1.055f *
+                ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f;
+        }
+
+        int o = Math.round(output * 255.0f);
+
+        return o;
+    }
+
+
+    /** Superclass getRaster... */
+    public final Raster getRaster(int x, int y, int w, int h) {
+        if (w == 0 || h == 0) {
+            return null;
+        }
+
+        //
+        // If working raster is big enough, reuse it. Otherwise,
+        // build a large enough new one.
+        //
+        WritableRaster raster = saved;
+        if (raster == null || raster.getWidth() < w || raster.getHeight() < h)
+            {
+                raster = getCachedRaster(dataModel, w, h);
+                saved = raster;
+            }
+
+        // Access raster internal int array. Because we use a DirectColorModel,
+        // we know the DataBuffer is of type DataBufferInt and the SampleModel
+        // is SinglePixelPackedSampleModel.
+        // Adjust for initial offset in DataBuffer and also for the scanline
+        // stride.
+        //
+        DataBufferInt rasterDB = (DataBufferInt)raster.getDataBuffer();
+        int[] pixels = rasterDB.getBankData()[0];
+        int off = rasterDB.getOffset();
+        int scanlineStride = ((SinglePixelPackedSampleModel)
+                              raster.getSampleModel()).getScanlineStride();
+        int adjust = scanlineStride - w;
+
+        fillRaster(pixels, off, adjust, x, y, w, h); //delegate to subclass.
+
+        GraphicsUtil.coerceData(raster, dataModel,
+                                model.isAlphaPremultiplied());
+
+
+        return raster;
+    }
+
+    /** Subclasses should implement this. */
+    protected abstract void fillRaster(int pixels[], int off, int adjust,
+                                       int x, int y, int w, int h);
+
+
+    /** Took this cacheRaster code from GradientPaint. It appears to recycle
+     * rasters for use by any other instance, as long as they are sufficiently
+     * large.
+     */
+    protected final
+    static synchronized WritableRaster getCachedRaster
+        (ColorModel cm, int w, int h) {
+        if (cm == cachedModel) {
+            if (cached != null) {
+                WritableRaster ras = (WritableRaster) cached.get();
+                if (ras != null &&
+                    ras.getWidth() >= w &&
+                    ras.getHeight() >= h)
+                    {
+                        cached = null;
+                        return ras;
+                    }
+            }
+        }
+        // Don't create rediculously small rasters...
+        if (w<32) w=32;
+        if (h<32) h=32;
+        return cm.createCompatibleWritableRaster(w, h);
+    }
+
+    /** Took this cacheRaster code from GradientPaint. It appears to recycle
+     * rasters for use by any other instance, as long as they are sufficiently
+     * large.
+     */
+    protected final
+    static synchronized void putCachedRaster(ColorModel cm,
+                                             WritableRaster ras) {
+        if (cached != null) {
+            WritableRaster cras = (WritableRaster) cached.get();
+            if (cras != null) {
+                int cw = cras.getWidth();
+                int ch = cras.getHeight();
+                int iw = ras.getWidth();
+                int ih = ras.getHeight();
+                if (cw >= iw && ch >= ih) {
+                    return;
+                }
+                if (cw * ch >= iw * ih) {
+                    return;
+                }
+            }
+        }
+        cachedModel = cm;
+        cached = new WeakReference(ras);
+    }
+
+    /**
+     * Release the resources allocated for the operation.
+     */
+    public final void dispose() {
+        if (saved != null) {
+            putCachedRaster(model, saved);
+            saved = null;
+        }
+    }
+
+    /**
+     * Return the ColorModel of the output.
+     */
+    public final ColorModel getColorModel() {
+        return model;
+    }
+}
+
Index: trunk/src/com/kitfox/svg/batik/RadialGradientPaint.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/RadialGradientPaint.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/RadialGradientPaint.java	(revision 4256)
@@ -0,0 +1,491 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+
+/**
+ * <p>
+ * This class provides a way to fill a shape with a circular radial color 
+ * gradient pattern. The user may specify 2 or more gradient colors, and this 
+ * paint will provide an interpolation between each color.
+ * <p>
+ *
+ * The user must provide an array of floats specifying how to distribute the 
+ * colors along the gradient.  These values should range from 0.0 to 1.0 and 
+ * act like keyframes along the gradient (they mark where the gradient should 
+ * be exactly a particular color).
+ *
+ * <p>
+ * This paint will map the first color of the gradient to a focus point within
+ * the circle, and the last color to the perimeter of the circle, interpolating
+ * smoothly for any inbetween colors specified by the user.  Any line drawn 
+ * from the focus point to the circumference will span the all the gradient 
+ * colors.  By default the focus is set to be the center of the circle.
+ *
+ * <p>
+ * Specifying a focus point outside of the circle's radius will result in the 
+ * focus being set to the intersection point of the focus-center line and the 
+ * perimenter of the circle.
+ * <p>
+ *
+ * Specifying a cycle method allows the user to control the painting behavior 
+ * outside of the bounds of the circle's radius.  See LinearGradientPaint for 
+ * more details.
+ *
+ * <p>
+ * The following code demonstrates typical usage of RadialGradientPaint:
+ * <p>
+ * <code>
+ * Point2D center = new Point2D.Float(0, 0);<br>
+ * float radius = 20;
+ * float[] dist = {0.0, 0.2, 1.0};<br>
+ * Color[] colors = {Color.red, Color.white, Color.blue};<br>
+ * RadialGradientPaint p = new RadialGradientPaint(center, radius, 
+ * dist, colors);
+ * </code>
+ *
+ * <p> In the event that the user does not set the first keyframe value equal
+ * to 0 and the last keyframe value equal to 1, keyframes will be created at
+ * these positions and the first and last colors will be replicated there.
+ * So, if a user specifies the following arrays to construct a gradient:<br>
+ * {Color.blue, Color.red}, {.3, .7}<br>
+ * this will be converted to a gradient with the following keyframes:
+ * {Color.blue, Color.blue, Color.red, Color.red}, {0, .3, .7, 1}
+ *
+ *
+ * <p>
+ * <img src = "radial.jpg">
+ * <p>
+ * This image demonstrates a radial gradient with NO_CYCLE and default focus.
+ * <p>
+ *
+ * <img src = "radial2.jpg">
+ * <p>
+ * This image demonstrates a radial gradient with NO_CYCLE and non-centered 
+ * focus.
+ * <p>
+ * 
+ * <img src = "radial3.jpg">
+ * <p>
+ * This image demonstrates a radial gradient with REFLECT and non-centered 
+ * focus.
+ *
+ * @author  Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: RadialGradientPaint.java,v 1.1 2004/09/06 19:35:39 kitfox Exp $
+ *
+ */
+
+public final class RadialGradientPaint extends MultipleGradientPaint {
+
+    /** Focus point which defines the 0% gradient stop x coordinate. */
+    private Point2D focus;
+
+    /** Center of the circle defining the 100% gradient stop x coordinate. */
+    private Point2D center;
+
+    /** Radius of the outermost circle defining the 100% gradient stop. */
+    private float radius;
+
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>, using the center as the 
+     * focus point.
+     *
+     * @param cx the x coordinate in user space of the center point of the 
+     * circle defining the gradient.  The last color of the gradient is mapped
+     * to the perimeter of this circle
+     *
+     * @param cy the y coordinate in user space of the center point of the 
+     * circle defining the gradient.  The last color of the gradient is mapped
+     * to the perimeter of this circle
+     *
+     * @param radius the radius of the circle defining the extents of the 
+     * color gradient   
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color 
+     * is used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *        
+     *
+     * @throws IllegalArgumentException  
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     *
+     */
+    public RadialGradientPaint(float cx, float cy, float radius,
+                               float[] fractions, Color[] colors) {
+        this(cx, cy,
+             radius,
+             cx, cy,
+             fractions,
+             colors);
+    }    
+    
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>, using the center as the 
+     * focus point.
+     *
+     * @param center the center point, in user space, of the circle defining 
+     * the gradient
+     *
+     * @param radius the radius of the circle defining the extents of the 
+     * color gradient
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color 
+     * is used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *   
+     * @throws NullPointerException if center point is null
+     *
+     * @throws IllegalArgumentException  
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     *
+     */
+    public RadialGradientPaint(Point2D center, float radius,
+                               float[] fractions, Color[] colors) {
+        this(center,
+             radius,
+             center,	    
+             fractions,
+             colors);
+    }
+
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>.
+     *
+     * @param cx the x coordinate in user space of the center point of the 
+     * circle defining the gradient.  The last color of the gradient is mapped
+     * to the perimeter of this circle
+     *
+     * @param cy the y coordinate in user space of the center point of the 
+     * circle defining the gradient.  The last color of the gradient is mapped
+     * to the perimeter of this circle
+     *
+     * @param radius the radius of the circle defining the extents of the 
+     * color gradient
+     *
+     * @param fx the x coordinate of the point in user space to which the 
+     * first color is mapped
+     *
+     * @param fy the y coordinate of the point in user space to which the 
+     * first color is mapped
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color 
+     * is used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *  
+     * @throws IllegalArgumentException  
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     *
+     */
+    public RadialGradientPaint(float cx, float cy, float radius,
+                               float fx, float fy,
+                               float[] fractions, Color[] colors) {
+        this(new Point2D.Float(cx, cy),
+             radius,
+             new Point2D.Float(fx, fy),
+             fractions,
+             colors,
+             NO_CYCLE,
+             SRGB);
+    }
+    
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>.
+     *
+     * @param center the center point, in user space, of the circle defining 
+     * the gradient. The last color of the gradient is mapped to the perimeter
+     * of this circle
+     *
+     * @param radius the radius of the circle defining the extents of the color
+     * gradient
+     *
+     * @param focus the point, in user space, to which the first color is 
+     * mapped    
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color 
+     * is used at the focus point, the last color around the perimeter of the
+     * circle.
+     *   
+     * @throws NullPointerException if one of the points is null
+     *
+     * @throws IllegalArgumentException  
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     */
+    public RadialGradientPaint(Point2D center, float radius,
+                               Point2D focus,
+                               float[] fractions, Color[] colors) {
+        this(center,
+             radius,
+             focus,
+             fractions,
+             colors,
+             NO_CYCLE,
+             SRGB);	
+    }
+    
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>.
+     *
+     * @param center the center point in user space of the circle defining the
+     * gradient. The last color of the gradient is mapped to the perimeter of 
+     * this circle
+     *
+     * @param radius the radius of the circle defining the extents of the color
+     * gradient
+     *
+     * @param focus the point in user space to which the first color is mapped
+     *   
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color is
+     * used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @param colorSpace which colorspace to use for interpolation, 
+     * either SRGB or LINEAR_RGB
+     *   
+     * @throws NullPointerException if one of the points is null
+     *
+     * @throws IllegalArgumentException 
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     */
+    public RadialGradientPaint(Point2D center, float radius,
+                               Point2D focus,
+                               float[] fractions, Color[] colors,
+                               CycleMethodEnum cycleMethod, 
+                               ColorSpaceEnum colorSpace) {
+        this(center,
+             radius,
+             focus,
+             fractions,
+             colors,
+             cycleMethod,
+             colorSpace,
+             new AffineTransform());
+    }
+
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>.
+     *
+     * @param center the center point in user space of the circle defining the
+     * gradient.  The last color of the gradient is mapped to the perimeter of
+     * this circle
+     *
+     * @param radius the radius of the circle defining the extents of the color
+     * gradient. 
+     *
+     * @param focus the point in user space to which the first color is mapped
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color is
+     * used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *
+     * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     * @param colorSpace which colorspace to use for interpolation, 
+     * either SRGB or LINEAR_RGB          
+     *
+     * @param gradientTransform transform to apply to the gradient
+     *
+     * @throws NullPointerException if one of the points is null, 
+     * or gradientTransform is null
+     *
+     * @throws IllegalArgumentException 
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     */
+    public RadialGradientPaint(Point2D center,
+                               float radius,
+                               Point2D focus,
+                               float[] fractions,  Color[] colors,
+                               CycleMethodEnum cycleMethod, 
+                               ColorSpaceEnum colorSpace,
+                               AffineTransform gradientTransform){
+        super(fractions, colors, cycleMethod, colorSpace, gradientTransform);
+
+        // Check input arguments
+        if (center == null) {
+            throw new NullPointerException("Center point should not be null.");
+        }
+	
+        if (focus == null) {
+            throw new NullPointerException("Focus point should not be null.");
+        }
+
+        if (radius <= 0) {
+            throw new IllegalArgumentException("radius should be greater than zero");
+        }
+
+        //copy parameters
+        this.center = (Point2D)center.clone();
+        this.focus = (Point2D)focus.clone();
+        this.radius = radius;
+    }
+    
+    /**
+     * <p>
+     *
+     * Constructs a <code>RadialGradientPaint</code>, the gradient circle is 
+     * defined by a bounding box.
+     *    
+     * @param gradientBounds the bounding box, in user space, of the circle 
+     * defining outermost extent of the gradient.
+     *
+     * @param fractions numbers ranging from 0.0 to 1.0 specifying the 
+     * distribution of colors along the gradient
+     *
+     * @param colors array of colors to use in the gradient. The first color 
+     * is used at the focus point, the last color around the perimeter of the 
+     * circle.
+     *
+     * @throws NullPointerException if the gradientBounds is null
+     *
+     * @throws IllegalArgumentException 
+     *         if fractions.length != colors.length, or if colors is less 
+     *         than 2 in size, or if radius < 0
+     *
+     */    
+    public RadialGradientPaint(Rectangle2D gradientBounds,
+                               float[] fractions,  Color[] colors) {
+
+        //calculate center point and radius based on bounding box coordinates.
+        this((float)gradientBounds.getX() +
+             ( (float)gradientBounds.getWidth() / 2),
+	     
+             (float)gradientBounds.getY() +
+             ( (float)gradientBounds.getWidth() / 2),
+	     
+             (float)gradientBounds.getWidth() / 2, 
+             fractions, colors);
+    }
+
+
+    /** <p>
+     * Creates and returns a PaintContext used to generate the color pattern,
+     * for use by the internal rendering engine.
+     *
+     * @param cm {@link ColorModel} that receives
+     * the <code>Paint</code> data. This is used only as a hint.
+     *
+     * @param deviceBounds the device space bounding box of the 
+     * graphics primitive being rendered
+     *
+     * @param userBounds the user space bounding box of the 
+     * graphics primitive being rendered
+     *
+     * @param transform the {@link AffineTransform} from user
+     * space into device space
+     *
+     * @param hints the hints that the context object uses to choose
+     * between rendering alternatives
+     *
+     * @return the {@link PaintContext} that generates color patterns.
+     *
+     * @throws IllegalArgumentException if the transform is not invertible
+     *
+     * @see PaintContext
+     */
+    public PaintContext createContext(ColorModel cm,
+                                      Rectangle deviceBounds,
+                                      Rectangle2D userBounds,
+                                      AffineTransform transform,
+                                      RenderingHints hints) {
+        // Can't modify the transform passed in...
+        transform = new AffineTransform(transform);
+        // incorporate the gradient transform
+        transform.concatenate(gradientTransform);
+
+        try{
+            return new RadialGradientPaintContext
+                (cm, deviceBounds, userBounds, transform, hints,
+                 (float)center.getX(), (float)center.getY(), radius,
+                 (float)focus.getX(), (float)focus.getY(),
+                 fractions, colors, cycleMethod, colorSpace);       	    
+        }
+	
+        catch(NoninvertibleTransformException e){
+            throw new IllegalArgumentException("transform should be " +
+                                               "invertible");
+        }
+    }
+
+    /**
+     * Returns a copy of the center point of the radial gradient.
+     * @return a {@link Point2D} object that is a copy of the center point     
+     */
+    public Point2D getCenterPoint() {
+        return new Point2D.Double(center.getX(), center.getY());
+    }
+    
+    /** Returns a copy of the end point of the gradient axis.
+     * @return a {@link Point2D} object that is a copy of the focus point     
+     */
+    public Point2D getFocusPoint() {
+        return new Point2D.Double(focus.getX(), focus.getY());
+    }
+
+    /** Returns the radius of the circle defining the radial gradient.
+     * @return the radius of the circle defining the radial gradient
+     */
+    public float getRadius() {
+        return radius;
+    }
+    
+}
+
Index: trunk/src/com/kitfox/svg/batik/RadialGradientPaintContext.java
===================================================================
--- trunk/src/com/kitfox/svg/batik/RadialGradientPaintContext.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/batik/RadialGradientPaintContext.java	(revision 4256)
@@ -0,0 +1,775 @@
+/*****************************************************************************
+ * Copyright (C) The Apache Software Foundation. All rights reserved.        *
+ * ------------------------------------------------------------------------- *
+ * This software is published under the terms of the Apache Software License *
+ * version 1.1, a copy of which has been included with this distribution in  *
+ * the LICENSE file.                                                         *
+ *****************************************************************************/
+
+package com.kitfox.svg.batik;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+
+/**
+ * Provides the actual implementation for the RadialGradientPaint.
+ * This is where the pixel processing is done.  A RadialGradienPaint
+ * only supports circular gradients, but it should be possible to scale
+ * the circle to look approximately elliptical, by means of a
+ * gradient transform passed into the RadialGradientPaint constructor.
+ *
+ * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
+ * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
+ * @version $Id: RadialGradientPaintContext.java,v 1.2 2005/10/12 20:36:55 kitfox Exp $
+ *
+ */
+final class RadialGradientPaintContext extends MultipleGradientPaintContext {  
+    
+    /** True when (focus == center)  */
+    private boolean isSimpleFocus = false;
+
+    /** True when (cycleMethod == NO_CYCLE) */
+    private boolean isNonCyclic = false;
+       
+    /** Radius of the outermost circle defining the 100% gradient stop. */
+    private float radius;   
+    
+    /** Variables representing center and focus points. */
+    private float centerX, centerY, focusX, focusY;     
+
+    /** Radius of the gradient circle squared. */
+    private float radiusSq; 
+        
+    /** Constant part of X, Y user space coordinates. */
+    private float constA, constB;
+       
+    /** This value represents the solution when focusX == X.  It is called
+     * trivial because it is easier to calculate than the general case.
+     */
+    private float trivial;       
+
+    private static final int FIXED_POINT_IMPL = 1;
+    private static final int DEFAULT_IMPL     = 2;
+    private static final int ANTI_ALIAS_IMPL  = 3;
+
+    private int fillMethod;
+    
+    /** Amount for offset when clamping focus. */
+    private static final float SCALEBACK = .97f;
+    
+    /** 
+     * Constructor for RadialGradientPaintContext.
+     *
+     *  @param cm {@link ColorModel} that receives
+     *  the <code>Paint</code> data. This is used only as a hint.
+     *
+     *  @param deviceBounds the device space bounding box of the 
+     *  graphics primitive being rendered
+     *
+     *  @param userBounds the user space bounding box of the 
+     *  graphics primitive being rendered
+     * 
+     *  @param t the {@link AffineTransform} from user
+     *  space into device space (gradientTransform should be 
+     *  concatenated with this)    
+     *
+     *  @param hints the hints that the context object uses to choose
+     *  between rendering alternatives
+     *       
+     *  @param cx the center point in user space of the circle defining 
+     *  the gradient.  The last color of the gradient is mapped to the 
+     *  perimeter of this circle X coordinate
+     *
+     *  @param cy the center point in user space of the circle defining 
+     *  the gradient.  The last color of the gradient is mapped to the 
+     *  perimeter of this circle Y coordinate
+     *     
+     *  @param r the radius of the circle defining the extents of the 
+     *  color gradient
+     *
+     *  @param fx the point in user space to which the first color is mapped
+     *  X coordinate
+     *
+     *  @param fy the point in user space to which the first color is mapped
+     *  Y coordinate
+     *
+     *  @param fractions the fractions specifying the gradient distribution
+     *
+     *  @param colors the gradient colors
+     *
+     *  @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
+     *
+     *  @param colorSpace which colorspace to use for interpolation, 
+     *  either SRGB or LINEAR_RGB
+     *
+     */
+    public RadialGradientPaintContext(ColorModel cm,
+                                      Rectangle deviceBounds,
+                                      Rectangle2D userBounds,
+                                      AffineTransform t,
+                                      RenderingHints hints,
+                                      float cx, float cy,
+                                      float r,
+                                      float fx, float fy,
+                                      float[] fractions,
+                                      Color[] colors,
+                                      MultipleGradientPaint.CycleMethodEnum 
+                                      cycleMethod,
+                                      MultipleGradientPaint.ColorSpaceEnum 
+                                      colorSpace)
+        throws NoninvertibleTransformException
+    {       	
+        super(cm, deviceBounds, userBounds, t, hints, fractions, colors, 
+              cycleMethod, colorSpace);
+
+        //copy some parameters.
+        centerX = cx;
+        centerY = cy;	
+        focusX = fx;
+        focusY = fy;
+        radius = r;
+
+        this.isSimpleFocus = (focusX == centerX) && (focusY == centerY);
+        this.isNonCyclic = (cycleMethod == RadialGradientPaint.NO_CYCLE);
+	
+        //for use in the quadractic equation
+        radiusSq = radius * radius;
+
+        float dX = focusX - centerX;
+        float dY = focusY - centerY;
+
+        double dist = Math.sqrt((dX * dX) + (dY * dY));
+
+        //test if distance from focus to center is greater than the radius
+        if (dist > radius* SCALEBACK) { //clamp focus to radius
+          double angle = Math.atan2(dY, dX);
+			
+          //x = r cos theta, y = r sin theta
+          focusX = (float)(SCALEBACK * radius * Math.cos(angle)) + centerX;
+          
+          focusY = (float)(SCALEBACK * radius * Math.sin(angle)) + centerY;
+        }
+
+        //calculate the solution to be used in the case where X == focusX
+        //in cyclicCircularGradientFillRaster
+        dX = focusX - centerX;
+        trivial = (float)Math.sqrt(radiusSq - (dX * dX));
+
+        // constant parts of X, Y user space coordinates 
+        constA = a02 - centerX;
+        constB = a12 - centerY;
+
+        Object colorRend;
+        Object rend;
+        //hints can be null on Mac OSX
+        if (hints == null)
+        {
+            colorRend = RenderingHints.VALUE_COLOR_RENDER_DEFAULT;
+            rend = RenderingHints.VALUE_RENDER_DEFAULT;
+        }
+        else
+        {
+            colorRend = hints.get(RenderingHints.KEY_COLOR_RENDERING);
+            rend      = hints.get(RenderingHints.KEY_RENDERING);
+        }
+
+        fillMethod = 0;
+
+        if ((rend      == RenderingHints.VALUE_RENDER_QUALITY) ||
+            (colorRend == RenderingHints.VALUE_COLOR_RENDER_QUALITY)) {
+            // System.out.println("AAHints set: " + rend + ", " + colorRend);
+            fillMethod = ANTI_ALIAS_IMPL;
+        }
+
+        if ((rend      == RenderingHints.VALUE_RENDER_SPEED) ||
+            (colorRend == RenderingHints.VALUE_COLOR_RENDER_SPEED)) {
+            // System.out.println("SPHints set: " + rend + ", " + colorRend);
+            fillMethod = DEFAULT_IMPL;
+        }
+
+        // We are in the 'default' case, no hint or hint set to
+        // DEFAULT values...
+        if (fillMethod == 0) {
+            // For now we will always use the 'default' impl if 
+            // one is not specified.
+            fillMethod = DEFAULT_IMPL;
+
+            if (false) {
+                // This could be used for a 'smart' choice in
+                // the default case, if the gradient has obvious
+                // discontinuites use AA, otherwise default
+                if (hasDiscontinuity) {
+                    fillMethod = ANTI_ALIAS_IMPL;
+                } else {
+                    fillMethod = DEFAULT_IMPL;
+                }
+            }
+        }
+
+        if ((fillMethod == DEFAULT_IMPL) &&
+            (isSimpleFocus && isNonCyclic && isSimpleLookup)) {
+            this.calculateFixedPointSqrtLookupTable();
+            fillMethod = FIXED_POINT_IMPL;
+        }
+    }
+    
+    /**
+     * Return a Raster containing the colors generated for the graphics
+     * operation.
+     * @param x,y,w,h The area in device space for which colors are
+     * generated.
+     */
+    protected void fillRaster(int pixels[], int off, int adjust,
+                              int x, int y, int w, int h) {
+        switch(fillMethod) {
+        case FIXED_POINT_IMPL:
+            // System.out.println("Calling FP");
+            fixedPointSimplestCaseNonCyclicFillRaster(pixels, off, adjust, x, 
+                                                      y, w, h);
+            break;
+        case ANTI_ALIAS_IMPL:
+            // System.out.println("Calling AA");
+            antiAliasFillRaster(pixels, off, adjust, x, y, w, h);
+            break;
+        case DEFAULT_IMPL:
+        default:
+            // System.out.println("Calling Default");
+            cyclicCircularGradientFillRaster(pixels, off, adjust, x, y, w, h);
+        }
+    }    
+    
+    /**
+     * This code works in the simplest of cases, where the focus == center 
+     * point, the gradient is noncyclic, and the gradient lookup method is 
+     * fast (single array index, no conversion necessary).
+     *
+     */         
+    private void fixedPointSimplestCaseNonCyclicFillRaster(int pixels[], 
+                                                           int off,
+                                                           int adjust, 
+                                                           int x, int y, 
+                                                           int w, int h) {
+        float iSq=0;  // Square distance index
+        final float indexFactor = fastGradientArraySize / radius;      
+
+        //constant part of X and Y coordinates for the entire raster
+        final float constX = (a00*x) + (a01*y) + constA;
+        final float constY = (a10*x) + (a11*y) + constB;
+        final float deltaX = indexFactor * a00; //incremental change in dX
+        final float deltaY = indexFactor * a10; //incremental change in dY
+        float dX, dY; //the current distance from center
+        final int fixedArraySizeSq=
+            (fastGradientArraySize * fastGradientArraySize);
+        float g, gDelta, gDeltaDelta, temp; //gradient square value
+        int gIndex; // integer number used to index gradient array
+        int iSqInt; // Square distance index       		   
+	
+        int end, j; //indexing variables
+        int indexer = off;//used to index pixels array
+
+        temp        = ((deltaX * deltaX) + (deltaY * deltaY));
+        gDeltaDelta = ((temp * 2));
+
+        if (temp > fixedArraySizeSq) {
+            // This combination of scale and circle radius means
+            // essentially no pixels will be anything but the end
+            // stop color.  This also avoids math problems.
+            final int val = gradientOverflow;
+            for(j = 0; j < h; j++){ //for every row
+                //for every column (inner loop begins here)
+                for (end = indexer+w; indexer < end; indexer++) 
+                    pixels[indexer] = val;
+                indexer += adjust;
+            }
+            return;
+        }
+
+        // For every point in the raster, calculate the color at that point
+        for(j = 0; j < h; j++){ //for every row
+            //x and y (in user space) of the first pixel of this row
+            dX = indexFactor * ((a01*j) + constX);
+            dY = indexFactor * ((a11*j) + constY);	   	   
+
+            // these values below here allow for an incremental calculation
+            // of dX^2 + dY^2 
+
+            //initialize to be equal to distance squared
+            g = (((dY * dY) + (dX * dX)) );
+            gDelta =  (((((deltaY * dY) + (deltaX * dX))* 2) + 
+                        temp));	 
+	    
+            //for every column (inner loop begins here)
+            for (end = indexer+w; indexer < end; indexer++) {	       
+                //determine the distance to the center
+		
+                //since this is a non cyclic fill raster, crop at "1" and 0
+                if (g >= fixedArraySizeSq) {
+                    pixels[indexer] = gradientOverflow;
+                }
+		
+                // This should not happen as gIndex is a square
+                // quantity. Code commented out on purpose, can't underflow.
+                // else if (g < 0) {
+                //    gIndex = 0;		    
+                // }
+		
+                else {
+                    iSq = (g * invSqStepFloat);
+                    
+                    iSqInt = (int)iSq; //chop off fractional part
+                    iSq -= iSqInt;		    
+                    gIndex = sqrtLutFixed[iSqInt];
+                    gIndex += (int)(iSq * (sqrtLutFixed[iSqInt + 1]-gIndex));
+                    pixels[indexer] = gradient[gIndex]; 
+                }
+		
+				
+                //incremental calculation
+                g += gDelta;
+                gDelta += gDeltaDelta;		
+            }	  
+            indexer += adjust;
+        }
+    }
+
+    /** Length of a square distance intervale in the lookup table */
+    private float invSqStepFloat; 
+    
+    /** Used to limit the size of the square root lookup table */
+    private int MAX_PRECISION = 256;
+    
+    /** Square root lookup table */
+    private int sqrtLutFixed[] = new int[MAX_PRECISION];
+    
+    /**
+     * Build square root lookup table
+     */       
+    private void calculateFixedPointSqrtLookupTable() {	      
+        float sqStepFloat;
+        sqStepFloat = ((fastGradientArraySize  * fastGradientArraySize) 
+                       / (MAX_PRECISION - 2));
+	
+        // The last two values are the same so that linear square root 
+        // interpolation can happen on the maximum reachable element in the 
+        // lookup table (precision-2)
+        int i;
+        for (i = 0; i < MAX_PRECISION - 1; i++) {
+            sqrtLutFixed[i] = (int)(Math.sqrt(i*sqStepFloat));
+        }
+        sqrtLutFixed[i] = sqrtLutFixed[i-1];	
+        invSqStepFloat = 1/sqStepFloat;
+    }
+    
+    /** Fill the raster, cycling the gradient colors when a point falls outside
+     *  of the perimeter of the 100% stop circle.          
+     * 
+     *  This calculation first computes the intersection point of the line
+     *  from the focus through the current point in the raster, and the
+     *  perimeter of the gradient circle.
+     * 
+     *  Then it determines the percentage distance of the current point along
+     *  that line (focus is 0%, perimeter is 100%). 
+     *
+     *  Equation of a circle centered at (a,b) with radius r:
+     *  (x-a)^2 + (y-b)^2 = r^2
+     *  Equation of a line with slope m and y-intercept b
+     *  y = mx + b
+     *  replacing y in the cirlce equation and solving using the quadratic
+     *  formula produces the following set of equations.  Constant factors have
+     *  been extracted out of the inner loop.
+     *
+     */   
+    private void cyclicCircularGradientFillRaster(int pixels[], int off, 
+                                                  int adjust, 
+                                                  int x, int y, 
+                                                  int w, int h) {
+        // Constant part of the C factor of the quadratic equation
+        final double constC = 
+            -(radiusSq) + (centerX * centerX) + (centerY * centerY);
+        double A; //coefficient of the quadratic equation (Ax^2 + Bx + C = 0)
+        double B; //coefficient of the quadratic equation
+        double C; //coefficient of the quadratic equation
+        double slope; //slope of the focus-perimeter line
+        double yintcpt; //y-intercept of the focus-perimeter line
+        double solutionX;//intersection with circle X coordinate
+        double solutionY;//intersection with circle Y coordinate       
+       	final float constX = (a00*x) + (a01*y) + a02;//const part of X coord
+        final float constY = (a10*x) + (a11*y) + a12; //const part of Y coord
+       	final float precalc2 = 2 * centerY;//const in inner loop quad. formula
+        final float precalc3 =-2 * centerX;//const in inner loop quad. formula
+        float X; // User space point X coordinate 
+        float Y; // User space point Y coordinate
+        float g;//value between 0 and 1 specifying position in the gradient
+        float det; //determinant of quadratic formula (should always be >0)
+        float currentToFocusSq;//sq distance from the current pt. to focus
+        float intersectToFocusSq;//sq distance from the intersect pt. to focus
+        float deltaXSq; //temp variable for a change in X squared.
+        float deltaYSq; //temp variable for a change in Y squared.
+        int indexer = off; //index variable for pixels array
+        int i, j; //indexing variables for FOR loops
+        int pixInc = w+adjust;//incremental index change for pixels array
+
+        for (j = 0; j < h; j++) { //for every row
+	    
+            X = (a01*j) + constX; //constants from column to column
+            Y = (a11*j) + constY;
+	    
+            //for every column (inner loop begins here)
+            for (i = 0; i < w; i++) {	       			
+	
+                // special case to avoid divide by zero or very near zero
+                if (((X-focusX)>-0.000001) &&
+                    ((X-focusX)< 0.000001)) {		   
+                    solutionX = focusX;
+		    
+                    solutionY = centerY;
+		    
+                    solutionY += (Y > focusY)?trivial:-trivial;
+                }
+		
+                else {    
+		    
+                    //slope of the focus-current line
+                    slope =   (Y - focusY) / (X - focusX);
+		    
+                    yintcpt = Y - (slope * X); //y-intercept of that same line
+		    
+                    //use the quadratic formula to calculate the intersection
+                    //point		  
+                    A = (slope * slope) + 1; 
+		    
+                    B =  precalc3 + (-2 * slope * (centerY - yintcpt));
+		    
+                    C =  constC + (yintcpt* (yintcpt - precalc2));
+		    
+                    det = (float)Math.sqrt((B * B) - ( 4 * A * C));
+		    
+                    solutionX = -B;
+		    
+                    //choose the positive or negative root depending
+                    //on where the X coord lies with respect to the focus.
+                    solutionX += (X < focusX)?-det:det;
+		    
+                    solutionX = solutionX / (2 * A);//divisor
+		    
+                    solutionY = (slope * solutionX) + yintcpt;
+                }	                    	
+
+                //calculate the square of the distance from the current point 
+                //to the focus and the square of the distance from the 
+                //intersection point to the focus. Want the squares so we can
+                //do 1 square root after division instead of 2 before.
+
+                deltaXSq = (float)solutionX - focusX;
+                deltaXSq = deltaXSq * deltaXSq;
+
+                deltaYSq = (float)solutionY - focusY;
+                deltaYSq = deltaYSq * deltaYSq;
+
+                intersectToFocusSq = deltaXSq + deltaYSq;
+
+                deltaXSq = X - focusX;
+                deltaXSq = deltaXSq * deltaXSq;
+
+                deltaYSq = Y - focusY;
+                deltaYSq = deltaYSq * deltaYSq;
+
+                currentToFocusSq = deltaXSq + deltaYSq;
+
+                //want the percentage (0-1) of the current point along the 
+                //focus-circumference line
+                g = (float)Math.sqrt(currentToFocusSq / intersectToFocusSq);
+
+                //Get the color at this point
+                pixels[indexer + i] = indexIntoGradientsArrays(g);
+		
+                X += a00; //incremental change in X, Y
+                Y += a10;	
+            } //end inner loop
+            indexer += pixInc;
+        } //end outer loop
+    }
+
+
+    /** Fill the raster, cycling the gradient colors when a point
+     *  falls outside of the perimeter of the 100% stop circle. Use
+     *  the anti-aliased gradient lookup.
+     *
+     *  This calculation first computes the intersection point of the line
+     *  from the focus through the current point in the raster, and the
+     *  perimeter of the gradient circle.
+     * 
+     *  Then it determines the percentage distance of the current point along
+     *  that line (focus is 0%, perimeter is 100%). 
+     *
+     *  Equation of a circle centered at (a,b) with radius r:
+     *  (x-a)^2 + (y-b)^2 = r^2
+     *  Equation of a line with slope m and y-intercept b
+     *  y = mx + b
+     *  replacing y in the cirlce equation and solving using the quadratic
+     *  formula produces the following set of equations.  Constant factors have
+     *  been extracted out of the inner loop.
+     * */
+    private void antiAliasFillRaster(int pixels[], int off, 
+                                     int adjust, 
+                                     int x, int y, 
+                                     int w, int h) {
+        // Constant part of the C factor of the quadratic equation
+        final double constC = 
+            -(radiusSq) + (centerX * centerX) + (centerY * centerY);
+        //coefficients of the quadratic equation (Ax^2 + Bx + C = 0)
+       	final float precalc2 = 2 * centerY;//const in inner loop quad. formula
+        final float precalc3 =-2 * centerX;//const in inner loop quad. formula
+
+        //const part of X,Y coord (shifted to bottom left corner of pixel.
+       	final float constX = (a00*(x-.5f)) + (a01*(y+.5f)) + a02;
+        final float constY = (a10*(x-.5f)) + (a11*(y+.5f)) + a12;
+        float X; // User space point X coordinate 
+        float Y; // User space point Y coordinate
+        int i, j; //indexing variables for FOR loops
+        int indexer = off-1; //index variable for pixels array
+
+        // Size of a pixel in user space.
+        double pixSzSq = (float)(a00*a00+a01*a01+a10*a10+a11*a11);
+        double [] prevGs = new double[w+1];
+        double deltaXSq, deltaYSq;
+        double solutionX, solutionY;
+        double slope, yintcpt, A, B, C, det;
+        double intersectToFocusSq, currentToFocusSq;
+        double g00, g01, g10, g11;
+
+        // Set X,Y to top left corner of first pixel of first row.
+        X = constX - a01;
+        Y = constY - a11;
+
+        // Calc top row of g's.
+        for (i=0; i <= w; i++) {
+            // special case to avoid divide by zero or very near zero
+            if (((X-focusX)>-0.000001) &&
+                ((X-focusX)< 0.000001)) {		   
+                solutionX = focusX;
+                solutionY = centerY;
+                solutionY += (Y > focusY)?trivial:-trivial;
+            }
+            else { 
+                // Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
+                // Formula line:        Y = Slope*x + Y0;
+                // 
+                // So you substitue line into Circle and apply
+                // Quadradic formula.
+
+
+                //slope of the focus-current line
+                slope =   (Y - focusY) / (X - focusX);
+		    
+                yintcpt = Y - (slope * X); //y-intercept of that same line
+		    
+                //use the quadratic formula to calculate the intersection
+                //point		  
+                A = (slope * slope) + 1; 
+		    
+                B =  precalc3 + (-2 * slope * (centerY - yintcpt));
+		    
+                C =  constC + (yintcpt* (yintcpt - precalc2));
+		    
+                det = Math.sqrt((B * B) - ( 4 * A * C));
+		    
+                solutionX = -B;
+		    
+                //choose the positive or negative root depending
+                //on where the X coord lies with respect to the focus.
+                solutionX += (X < focusX)?-det:det;
+		    
+                solutionX = solutionX / (2 * A);//divisor
+		    
+                solutionY = (slope * solutionX) + yintcpt;
+            }	                    	
+
+            //calculate the square of the distance from the current point 
+            //to the focus and the square of the distance from the 
+            //intersection point to the focus. Want the squares so we can
+            //do 1 square root after division instead of 2 before.
+            deltaXSq = solutionX - focusX;
+            deltaXSq = deltaXSq * deltaXSq;
+        
+            deltaYSq = solutionY - focusY;
+            deltaYSq = deltaYSq * deltaYSq;
+        
+            intersectToFocusSq = deltaXSq + deltaYSq;
+        
+            deltaXSq = X - focusX;
+            deltaXSq = deltaXSq * deltaXSq;
+        
+            deltaYSq = Y - focusY;
+            deltaYSq = deltaYSq * deltaYSq;
+        
+            currentToFocusSq = deltaXSq + deltaYSq;
+        
+            //want the percentage (0-1) of the current point along the 
+            //focus-circumference line
+            prevGs[i] = Math.sqrt(currentToFocusSq / intersectToFocusSq);
+
+            X += a00; //incremental change in X, Y
+            Y += a10;	
+        }
+
+        for (j = 0; j < h; j++) { //for every row
+	    
+            // Set X,Y to bottom edge of pixel row.
+            X = (a01*j) + constX; //constants from row to row
+            Y = (a11*j) + constY;
+
+            g10 = prevGs[0];
+            // special case to avoid divide by zero or very near zero
+            if (((X-focusX)>-0.000001) &&
+                ((X-focusX)< 0.000001)) {		   
+                solutionX = focusX;
+                solutionY = centerY;
+                solutionY += (Y > focusY)?trivial:-trivial;
+            }
+            else { 
+                // Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
+                // Formula line:        Y = Slope*x + Y0;
+                // 
+                // So you substitue line into Circle and apply
+                // Quadradic formula.
+
+
+                //slope of the focus-current line
+                slope =   (Y - focusY) / (X - focusX);
+		    
+                yintcpt = Y - (slope * X); //y-intercept of that same line
+		    
+                //use the quadratic formula to calculate the intersection
+                //point		  
+                A = (slope * slope) + 1; 
+		    
+                B =  precalc3 + (-2 * slope * (centerY - yintcpt));
+		    
+                C =  constC + (yintcpt* (yintcpt - precalc2));
+		    
+                det = Math.sqrt((B * B) - ( 4 * A * C));
+		    
+                solutionX = -B;
+		    
+                //choose the positive or negative root depending
+                //on where the X coord lies with respect to the focus.
+                solutionX += (X < focusX)?-det:det;
+		    
+                solutionX = solutionX / (2 * A);//divisor
+		    
+                solutionY = (slope * solutionX) + yintcpt;
+            }	                    	
+
+            //calculate the square of the distance from the current point 
+            //to the focus and the square of the distance from the 
+            //intersection point to the focus. Want the squares so we can
+            //do 1 square root after division instead of 2 before.
+            deltaXSq = solutionX - focusX;
+            deltaXSq = deltaXSq * deltaXSq;
+        
+            deltaYSq = solutionY - focusY;
+            deltaYSq = deltaYSq * deltaYSq;
+        
+            intersectToFocusSq = deltaXSq + deltaYSq;
+        
+            deltaXSq = X - focusX;
+            deltaXSq = deltaXSq * deltaXSq;
+        
+            deltaYSq = Y - focusY;
+            deltaYSq = deltaYSq * deltaYSq;
+                
+            currentToFocusSq = deltaXSq + deltaYSq;
+            g11 = Math.sqrt(currentToFocusSq / intersectToFocusSq);
+            prevGs[0] = g11;
+            
+            X += a00; //incremental change in X, Y
+            Y += a10;	
+
+            //for every column (inner loop begins here)
+            for (i=1; i <= w; i++) {
+                g00 = g10;
+                g01 = g11;
+                g10 = prevGs[i];
+
+                // special case to avoid divide by zero or very near zero
+                if (((X-focusX)>-0.000001) &&
+                    ((X-focusX)< 0.000001)) {		   
+                    solutionX = focusX;
+                    solutionY = centerY;
+                    solutionY += (Y > focusY)?trivial:-trivial;
+                }
+                else { 
+                    // Formula for Circle: (X-Xc)^2 + (Y-Yc)^2 - R^2 = 0
+                    // Formula line:        Y = Slope*x + Y0;
+                    // 
+                    // So you substitue line into Circle and apply
+                    // Quadradic formula.
+
+
+                    //slope of the focus-current line
+                    slope =   (Y - focusY) / (X - focusX);
+		    
+                    yintcpt = Y - (slope * X); //y-intercept of that same line
+		    
+                    //use the quadratic formula to calculate the intersection
+                    //point		  
+                    A = (slope * slope) + 1; 
+		    
+                    B =  precalc3 + (-2 * slope * (centerY - yintcpt));
+		    
+                    C =  constC + (yintcpt* (yintcpt - precalc2));
+		    
+                    det = Math.sqrt((B * B) - ( 4 * A * C));
+		    
+                    solutionX = -B;
+		    
+                    //choose the positive or negative root depending
+                    //on where the X coord lies with respect to the focus.
+                    solutionX += (X < focusX)?-det:det;
+		    
+                    solutionX = solutionX / (2 * A);//divisor
+		    
+                    solutionY = (slope * solutionX) + yintcpt;
+                }	                    	
+
+                //calculate the square of the distance from the current point 
+                //to the focus and the square of the distance from the 
+                //intersection point to the focus. Want the squares so we can
+                //do 1 square root after division instead of 2 before.
+                deltaXSq = solutionX - focusX;
+                deltaXSq = deltaXSq * deltaXSq;
+        
+                deltaYSq = solutionY - focusY;
+                deltaYSq = deltaYSq * deltaYSq;
+        
+                intersectToFocusSq = deltaXSq + deltaYSq;
+        
+                deltaXSq = X - focusX;
+                deltaXSq = deltaXSq * deltaXSq;
+        
+                deltaYSq = Y - focusY;
+                deltaYSq = deltaYSq * deltaYSq;
+        
+                currentToFocusSq = deltaXSq + deltaYSq;
+                g11 = Math.sqrt(currentToFocusSq / intersectToFocusSq);
+                prevGs[i] = g11;
+
+                //Get the color at this point
+                pixels[indexer+i] = indexGradientAntiAlias
+                    ((float)((g00+g01+g10+g11)/4), 
+                     (float)Math.max(Math.abs(g11-g00),
+                                     Math.abs(g10-g01)));
+
+                X += a00; //incremental change in X, Y
+                Y += a10;	
+            } //end inner loop
+            indexer += (w+adjust);
+        } //end outer loop
+    }
+}
Index: trunk/src/com/kitfox/svg/composite/AdobeComposite.java
===================================================================
--- trunk/src/com/kitfox/svg/composite/AdobeComposite.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/composite/AdobeComposite.java	(revision 4256)
@@ -0,0 +1,70 @@
+/*
+ * AdobeComposite.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on April 1, 2004, 6:40 AM
+ */
+
+package com.kitfox.svg.composite;
+
+import java.awt.*;
+import java.awt.image.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class AdobeComposite implements Composite
+{
+    public static final int CT_NORMAL = 0;
+    public static final int CT_MULTIPLY = 1;
+    public static final int CT_LAST = 2;
+
+    final int compositeType;
+    final float extraAlpha;
+
+    /** Creates a new instance of AdobeComposite */
+    public AdobeComposite(int compositeType, float extraAlpha)
+    {
+        this.compositeType = compositeType;
+        this.extraAlpha = extraAlpha;
+
+        if (compositeType < 0 || compositeType >= CT_LAST)
+        {
+            new Exception("Invalid composite type").printStackTrace();
+        }
+
+        if (extraAlpha < 0f || extraAlpha > 1f)
+        {
+            new Exception("Invalid alpha").printStackTrace();
+        }
+    }
+
+    public int getCompositeType() { return compositeType; }
+
+    public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints)
+    {
+        return new AdobeCompositeContext(compositeType, extraAlpha);
+    }
+
+}
Index: trunk/src/com/kitfox/svg/composite/AdobeCompositeContext.java
===================================================================
--- trunk/src/com/kitfox/svg/composite/AdobeCompositeContext.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/composite/AdobeCompositeContext.java	(revision 4256)
@@ -0,0 +1,97 @@
+/*
+ * AdobeCompositeContext.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on April 1, 2004, 6:41 AM
+ */
+
+package com.kitfox.svg.composite;
+
+import java.awt.*;
+import java.awt.image.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class AdobeCompositeContext implements CompositeContext
+{
+    final int compositeType;
+    final float extraAlpha;
+
+    float[] rgba_src = new float[4];
+    float[] rgba_dstIn = new float[4];
+    float[] rgba_dstOut = new float[4];
+
+    /** Creates a new instance of AdobeCompositeContext */
+    public AdobeCompositeContext(int compositeType, float extraAlpha)
+    {
+        this.compositeType = compositeType;
+        this.extraAlpha = extraAlpha;
+
+        rgba_dstOut[3] = 1f;
+    }
+
+    public void compose(Raster src, Raster dstIn, WritableRaster dstOut)
+    {
+        int width = src.getWidth();
+        int height = src.getHeight();
+
+        for (int j = 0; j < height; j++)
+        {
+            for (int i = 0; i < width; i++)
+            {
+                src.getPixel(i, j, rgba_src);
+                dstIn.getPixel(i, j, rgba_dstIn);
+
+                //Ignore transparent pixels
+                if (rgba_src[3] == 0)
+                {
+//                    dstOut.setPixel(i, j, rgba_dstIn);
+                    continue;
+                }
+
+                float alpha = rgba_src[3];
+
+                switch (compositeType)
+                {
+                    default:
+                    case AdobeComposite.CT_NORMAL:
+                        rgba_dstOut[0] = rgba_src[0] * alpha + rgba_dstIn[0] * (1f - alpha);
+                        rgba_dstOut[1] = rgba_src[1] * alpha + rgba_dstIn[1] * (1f - alpha);
+                        rgba_dstOut[2] = rgba_src[2] * alpha + rgba_dstIn[2] * (1f - alpha);
+                        break;
+                    case AdobeComposite.CT_MULTIPLY:
+                        rgba_dstOut[0] = rgba_src[0] * rgba_dstIn[0] * alpha + rgba_dstIn[0] * (1f - alpha);
+                        rgba_dstOut[1] = rgba_src[1] * rgba_dstIn[1] * alpha + rgba_dstIn[1] * (1f - alpha);
+                        rgba_dstOut[2] = rgba_src[2] * rgba_dstIn[2] * alpha + rgba_dstIn[2] * (1f - alpha);
+                        break;
+                }
+            }
+        }
+    }
+
+    public void dispose() {
+    }
+
+}
Index: trunk/src/com/kitfox/svg/package-info.java
===================================================================
--- trunk/src/com/kitfox/svg/package-info.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/package-info.java	(revision 4256)
@@ -0,0 +1,9 @@
+/**
+ * Provides the nodes of an SVG scene graph.  This graph can be queried, updated
+ * and picked against at runtime.  See the online docs for instructions on
+ * how to use SVGSalamander
+ *
+ * @author <a href="mailto:kitfox@kitfox.com">Mark McKay</a> (C) 2005
+ */
+
+package com.kitfox.svg;
Index: trunk/src/com/kitfox/svg/pathcmd/Arc.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Arc.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Arc.java	(revision 4256)
@@ -0,0 +1,245 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * This is a little used SVG function, as most editors will save curves as 
+ * Beziers.  To reduce the need to rely on the Batik library, this functionallity
+ * is being bypassed for the time being.  In the future, it would be nice to
+ * extend the GeneralPath command to include the arcTo ability provided by Batik.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Arc extends PathCommand 
+{
+
+    public float rx = 0f;
+    public float ry = 0f;
+    public float xAxisRot = 0f;
+    public boolean largeArc = false;
+    public boolean sweep = false;
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public Arc() {
+    }
+
+    public Arc(boolean isRelative, float rx, float ry, float xAxisRot, boolean largeArc, boolean sweep, float x, float y) {
+        super(isRelative);
+        this.rx = rx;
+        this.ry = ry;
+        this.xAxisRot = xAxisRot;
+        this.largeArc = largeArc;
+        this.sweep = sweep;
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        arcTo(path, rx, ry, xAxisRot, largeArc, sweep, x + offx, y + offy, hist.history[0].x, hist.history[0].y);
+//        path.lineTo(x + offx, y + offy);
+        hist.setPoint(x + offx, y + offy);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 6;
+    }
+
+    /**
+     * Adds an elliptical arc, defined by two radii, an angle from the
+     * x-axis, a flag to choose the large arc or not, a flag to
+     * indicate if we increase or decrease the angles and the final
+     * point of the arc.
+     *
+     * @param rx the x radius of the ellipse
+     * @param ry the y radius of the ellipse
+     *
+     * @param angle the angle from the x-axis of the current
+     * coordinate system to the x-axis of the ellipse in degrees.
+     *
+     * @param largeArcFlag the large arc flag. If true the arc
+     * spanning less than or equal to 180 degrees is chosen, otherwise
+     * the arc spanning greater than 180 degrees is chosen
+     *
+     * @param sweepFlag the sweep flag. If true the line joining
+     * center to arc sweeps through decreasing angles otherwise it
+     * sweeps through increasing angles
+     *
+     * @param x the absolute x coordinate of the final point of the arc.
+     * @param y the absolute y coordinate of the final point of the arc.
+     * @param x0 - The absolute x coordinate of the initial point of the arc.
+     * @param y0 - The absolute y coordinate of the initial point of the arc.
+     */
+    public void arcTo(GeneralPath path, float rx, float ry,
+                                   float angle,
+                                   boolean largeArcFlag,
+                                   boolean sweepFlag,
+                                   float x, float y, float x0, float y0) 
+    {
+
+        // Ensure radii are valid
+        if (rx == 0 || ry == 0) {
+            path.lineTo((float) x, (float) y);
+            return;
+        }
+
+        if (x0 == x && y0 == y) {
+            // If the endpoints (x, y) and (x0, y0) are identical, then this
+            // is equivalent to omitting the elliptical arc segment entirely.
+            return;
+        }
+
+        Arc2D arc = computeArc(x0, y0, rx, ry, angle, 
+                               largeArcFlag, sweepFlag, x, y);
+        if (arc == null) return;
+
+        AffineTransform t = AffineTransform.getRotateInstance
+            (Math.toRadians(angle), arc.getCenterX(), arc.getCenterY());
+        Shape s = t.createTransformedShape(arc);
+        path.append(s, true);
+    }
+
+
+    /** 
+     * This constructs an unrotated Arc2D from the SVG specification of an 
+     * Elliptical arc.  To get the final arc you need to apply a rotation
+     * transform such as:
+     * 
+     * AffineTransform.getRotateInstance
+     *     (angle, arc.getX()+arc.getWidth()/2, arc.getY()+arc.getHeight()/2);
+     */
+    public static Arc2D computeArc(double x0, double y0,
+                                   double rx, double ry,
+                                   double angle,
+                                   boolean largeArcFlag,
+                                   boolean sweepFlag,
+                                   double x, double y) {
+        //
+        // Elliptical arc implementation based on the SVG specification notes
+        //
+
+        // Compute the half distance between the current and the final point
+        double dx2 = (x0 - x) / 2.0;
+        double dy2 = (y0 - y) / 2.0;
+        // Convert angle from degrees to radians
+        angle = Math.toRadians(angle % 360.0);
+        double cosAngle = Math.cos(angle);
+        double sinAngle = Math.sin(angle);
+
+        //
+        // Step 1 : Compute (x1, y1)
+        //
+        double x1 = (cosAngle * dx2 + sinAngle * dy2);
+        double y1 = (-sinAngle * dx2 + cosAngle * dy2);
+        // Ensure radii are large enough
+        rx = Math.abs(rx);
+        ry = Math.abs(ry);
+        double Prx = rx * rx;
+        double Pry = ry * ry;
+        double Px1 = x1 * x1;
+        double Py1 = y1 * y1;
+        // check that radii are large enough
+        double radiiCheck = Px1/Prx + Py1/Pry;
+        if (radiiCheck > 1) {
+            rx = Math.sqrt(radiiCheck) * rx;
+            ry = Math.sqrt(radiiCheck) * ry;
+            Prx = rx * rx;
+            Pry = ry * ry;
+        }
+
+        //
+        // Step 2 : Compute (cx1, cy1)
+        //
+        double sign = (largeArcFlag == sweepFlag) ? -1 : 1;
+        double sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
+        sq = (sq < 0) ? 0 : sq;
+        double coef = (sign * Math.sqrt(sq));
+        double cx1 = coef * ((rx * y1) / ry);
+        double cy1 = coef * -((ry * x1) / rx);
+
+        //
+        // Step 3 : Compute (cx, cy) from (cx1, cy1)
+        //
+        double sx2 = (x0 + x) / 2.0;
+        double sy2 = (y0 + y) / 2.0;
+        double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1);
+        double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1);
+
+        //
+        // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
+        //
+        double ux = (x1 - cx1) / rx;
+        double uy = (y1 - cy1) / ry;
+        double vx = (-x1 - cx1) / rx;
+        double vy = (-y1 - cy1) / ry;
+        double p, n;
+        // Compute the angle start
+        n = Math.sqrt((ux * ux) + (uy * uy));
+        p = ux; // (1 * ux) + (0 * uy)
+        sign = (uy < 0) ? -1d : 1d;
+        double angleStart = Math.toDegrees(sign * Math.acos(p / n));
+
+        // Compute the angle extent
+        n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
+        p = ux * vx + uy * vy;
+        sign = (ux * vy - uy * vx < 0) ? -1d : 1d;
+        double angleExtent = Math.toDegrees(sign * Math.acos(p / n));
+        if(!sweepFlag && angleExtent > 0) {
+            angleExtent -= 360f;
+        } else if (sweepFlag && angleExtent < 0) {
+            angleExtent += 360f;
+        }
+        angleExtent %= 360f;
+        angleStart %= 360f;
+
+        //
+        // We can now build the resulting Arc2D in double precision
+        //
+        Arc2D.Double arc = new Arc2D.Double();
+        arc.x = cx - rx;
+        arc.y = cy - ry;
+        arc.width = rx * 2.0;
+        arc.height = ry * 2.0;
+        arc.start = -angleStart;
+        arc.extent = -angleExtent;
+
+        return arc;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/BuildHistory.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/BuildHistory.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/BuildHistory.java	(revision 4256)
@@ -0,0 +1,68 @@
+/*
+ * BuildHistory.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 9:18 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+import java.awt.geom.Point2D;
+
+/**
+ * When building a path from command segments, most need to cache information
+ * (such as the point finished at) for future commands.  This structure allows
+ * that
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class BuildHistory {
+
+//    Point2D.Float[] history = new Point2D.Float[2];
+    Point2D.Float[] history = {new Point2D.Float(), new Point2D.Float()};
+    Point2D.Float start = new Point2D.Float();
+    int length = 0;
+
+    /** Creates a new instance of BuildHistory */
+    public BuildHistory() {
+    }
+
+    public void setPoint(float x, float y)
+    {
+        history[0].setLocation(x, y);
+        length = 1;
+    }
+
+    public void setStart(float x, float y)
+    {
+        start.setLocation(x, y);
+    }
+
+    public void setPointAndKnot(float x, float y, float kx, float ky)
+    {
+        history[0].setLocation(x, y);
+        history[1].setLocation(kx, ky);
+        length = 2;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/Cubic.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Cubic.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Cubic.java	(revision 4256)
@@ -0,0 +1,74 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Cubic extends PathCommand {
+
+    public float k1x = 0f;
+    public float k1y = 0f;
+    public float k2x = 0f;
+    public float k2y = 0f;
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public Cubic() {
+    }
+
+    public Cubic(boolean isRelative, float k1x, float k1y, float k2x, float k2y, float x, float y) {
+        super(isRelative);
+        this.k1x = k1x;
+        this.k1y = k1y;
+        this.k2x = k2x;
+        this.k2y = k2y;
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        path.curveTo(k1x + offx, k1y + offy, k2x + offx, k2y + offy, x + offx, y + offy);
+        hist.setPointAndKnot(x + offx, y + offy, k2x + offx, k2y + offy);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 6;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/CubicSmooth.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/CubicSmooth.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/CubicSmooth.java	(revision 4256)
@@ -0,0 +1,78 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CubicSmooth extends PathCommand {
+
+    public float x = 0f;
+    public float y = 0f;
+    public float k2x = 0f;
+    public float k2y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public CubicSmooth() {
+    }
+
+    public CubicSmooth(boolean isRelative, float k2x, float k2y, float x, float y) {
+        super(isRelative);
+        this.k2x = k2x;
+        this.k2y = k2y;
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        float oldKx = hist.history.length >= 2 ? hist.history[1].x : hist.history[0].x;
+        float oldKy = hist.history.length >= 2 ? hist.history[1].y : hist.history[0].y;
+        float oldX = hist.history[0].x;
+        float oldY = hist.history[0].y;
+        //Calc knot as reflection of old knot
+        float k1x = oldX * 2f - oldKx;
+        float k1y = oldY * 2f - oldKy;
+
+        path.curveTo(k1x, k1y, k2x + offx, k2y + offy, x + offx, y + offy);
+        hist.setPointAndKnot(x + offx, y + offy, k2x + offx, k2y + offy);
+    }
+    
+    public int getNumKnotsAdded()
+    {
+        return 6;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/Horizontal.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Horizontal.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Horizontal.java	(revision 4256)
@@ -0,0 +1,65 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Horizontal extends PathCommand {
+
+    public float x = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public Horizontal() {
+    }
+
+    public Horizontal(boolean isRelative, float x) {
+        super(isRelative);
+        this.x = x;
+    }
+
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = hist.history[0].y;
+
+        path.lineTo(x + offx, offy);
+        hist.setPoint(x + offx, offy);
+    }
+    
+    public int getNumKnotsAdded()
+    {
+        return 2;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/LineTo.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/LineTo.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/LineTo.java	(revision 4256)
@@ -0,0 +1,67 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class LineTo extends PathCommand {
+
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public LineTo() {
+    }
+
+    public LineTo(boolean isRelative, float x, float y) {
+        super(isRelative);
+        this.x = x;
+        this.y = y;
+    }
+
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        path.lineTo(x + offx, y + offy);
+        hist.setPoint(x + offx, y + offy);
+    }
+    
+    public int getNumKnotsAdded()
+    {
+        return 2;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/MoveTo.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/MoveTo.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/MoveTo.java	(revision 4256)
@@ -0,0 +1,67 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class MoveTo extends PathCommand {
+
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public MoveTo() {
+    }
+
+    public MoveTo(boolean isRelative, float x, float y) {
+        super(isRelative);
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        path.moveTo(x + offx, y + offy);
+        hist.setPoint(x + offx, y + offy);
+        hist.setStart(x + offx, y + offy);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 2;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/PathCommand.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/PathCommand.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/PathCommand.java	(revision 4256)
@@ -0,0 +1,56 @@
+/*
+ * PathCommand.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:39 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * This is the element of a path and contains instructions for rendering a
+ * portion of the path
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class PathCommand {
+
+    public boolean isRelative = false;
+
+    /** Creates a new instance of PathCommand */
+    public PathCommand() {
+    }
+
+    public PathCommand(boolean isRelative) {
+        this.isRelative = isRelative;
+    }
+
+//    abstract public void appendPath(ExtendedGeneralPath path, BuildHistory hist);
+    abstract public void appendPath(GeneralPath path, BuildHistory hist);
+
+    abstract public int getNumKnotsAdded();
+}
Index: trunk/src/com/kitfox/svg/pathcmd/PathUtil.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/PathUtil.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/PathUtil.java	(revision 4256)
@@ -0,0 +1,72 @@
+/*
+ * PathUtil.java
+ *
+ * Created on May 10, 2005, 5:56 AM
+ *
+ * To change this template, choose Tools | Options and locate the template under
+ * the Source Creation and Management node. Right-click the template and choose
+ * Open. You can then make changes to the template in the Source Editor.
+ */
+
+package com.kitfox.svg.pathcmd;
+
+import java.awt.geom.*;
+
+/**
+ *
+ * @author kitfox
+ */
+public class PathUtil
+{
+    
+    /** Creates a new instance of PathUtil */
+    public PathUtil()
+    {
+    }
+    
+    /**
+     * Converts a GeneralPath into an SVG representation
+     */
+    public static String buildPathString(GeneralPath path)
+    {
+        float[] coords = new float[6];
+        
+        StringBuffer sb = new StringBuffer();
+        
+        for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next())
+        {
+            int segId = pathIt.currentSegment(coords);
+            
+            switch (segId)
+            {
+                case PathIterator.SEG_CLOSE:
+                {
+                    sb.append(" Z");
+                    break;
+                }
+                case PathIterator.SEG_CUBICTO:
+                {
+                    sb.append(" C " + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " " + coords[4] + " " + coords[5]);
+                    break;
+                }
+                case PathIterator.SEG_LINETO:
+                {
+                    sb.append(" L " + coords[0] + " " + coords[1]);
+                    break;
+                }
+                case PathIterator.SEG_MOVETO:
+                {
+                    sb.append(" M " + coords[0] + " " + coords[1]);
+                    break;
+                }
+                case PathIterator.SEG_QUADTO:
+                {
+                    sb.append(" Q " + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3]);
+                    break;
+                }
+            }
+        }
+        
+        return sb.toString();
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/Quadratic.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Quadratic.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Quadratic.java	(revision 4256)
@@ -0,0 +1,70 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Quadratic extends PathCommand {
+
+    public float kx = 0f;
+    public float ky = 0f;
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public Quadratic() {
+    }
+
+    public Quadratic(boolean isRelative, float kx, float ky, float x, float y) {
+        super(isRelative);
+        this.kx = kx;
+        this.ky = ky;
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        path.quadTo(kx + offx, ky + offy, x + offx, y + offy);
+        hist.setPointAndKnot(x + offx, y + offy, kx + offx, ky + offy);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 4;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/QuadraticSmooth.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/QuadraticSmooth.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/QuadraticSmooth.java	(revision 4256)
@@ -0,0 +1,74 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class QuadraticSmooth extends PathCommand {
+
+    public float x = 0f;
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public QuadraticSmooth() {
+    }
+
+    public QuadraticSmooth(boolean isRelative, float x, float y) {
+        super(isRelative);
+        this.x = x;
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = isRelative ? hist.history[0].x : 0f;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        float oldKx = hist.history.length >= 2 ? hist.history[1].x : hist.history[0].x;
+        float oldKy = hist.history.length >= 2 ? hist.history[1].y : hist.history[0].y;
+        float oldX = hist.history[0].x;
+        float oldY = hist.history[0].y;
+        //Calc knot as reflection of old knot
+        float kx = oldX * 2f - oldKx;
+        float ky = oldY * 2f - oldKy;
+
+        path.quadTo(kx, ky, x + offx, y + offy);
+        hist.setPointAndKnot(x + offx, y + offy, kx, ky);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 4;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/Terminal.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Terminal.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Terminal.java	(revision 4256)
@@ -0,0 +1,57 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * Finishes a path
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Terminal extends PathCommand {
+
+    /** Creates a new instance of MoveTo */
+    public Terminal() {
+    }
+
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        path.closePath();
+        hist.setPoint(hist.start.x, hist.start.y);
+    }
+    
+    public int getNumKnotsAdded()
+    {
+        return 0;
+    }
+}
Index: trunk/src/com/kitfox/svg/pathcmd/Vertical.java
===================================================================
--- trunk/src/com/kitfox/svg/pathcmd/Vertical.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pathcmd/Vertical.java	(revision 4256)
@@ -0,0 +1,64 @@
+/*
+ * MoveTo.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 8:40 PM
+ */
+
+package com.kitfox.svg.pathcmd;
+
+//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
+import java.awt.geom.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Vertical extends PathCommand {
+
+    public float y = 0f;
+
+    /** Creates a new instance of MoveTo */
+    public Vertical() {
+    }
+
+    public Vertical(boolean isRelative, float y) {
+        super(isRelative);
+        this.y = y;
+    }
+
+//    public void appendPath(ExtendedGeneralPath path, BuildHistory hist)
+    public void appendPath(GeneralPath path, BuildHistory hist)
+    {
+        float offx = hist.history[0].x;
+        float offy = isRelative ? hist.history[0].y : 0f;
+
+        path.lineTo(offx, y + offy);
+        hist.setPoint(offx, y + offy);
+    }
+
+    public int getNumKnotsAdded()
+    {
+        return 2;
+    }
+}
Index: trunk/src/com/kitfox/svg/pattern/PatternPaint.java
===================================================================
--- trunk/src/com/kitfox/svg/pattern/PatternPaint.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pattern/PatternPaint.java	(revision 4256)
@@ -0,0 +1,60 @@
+/*
+ * PatternPaint.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on April 1, 2004, 3:37 AM
+ */
+
+package com.kitfox.svg.pattern;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class PatternPaint implements Paint
+{
+    BufferedImage source;  //Image we're rendering from
+    AffineTransform xform;
+
+    /** Creates a new instance of PatternPaint */
+    public PatternPaint(BufferedImage source, AffineTransform xform)
+    {
+        this.source = source;
+        this.xform = xform;
+    }
+
+    public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints)
+    {
+        return new PatternPaintContext(source, deviceBounds, xform, this.xform);
+    }
+
+    public int getTransparency()
+    {
+        return source.getColorModel().getTransparency();
+    }
+
+}
Index: trunk/src/com/kitfox/svg/pattern/PatternPaintContext.java
===================================================================
--- trunk/src/com/kitfox/svg/pattern/PatternPaintContext.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/pattern/PatternPaintContext.java	(revision 4256)
@@ -0,0 +1,120 @@
+/*
+ * PatternPaintContext.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on April 1, 2004, 3:37 AM
+ */
+
+package com.kitfox.svg.pattern;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class PatternPaintContext implements PaintContext
+{
+    BufferedImage source;  //Image we're rendering from
+    Rectangle deviceBounds;  //int size of rectangle we're rendering to
+//    AffineTransform userXform;  //xform from user space to device space
+//    AffineTransform distortXform;  //distortion applied to this pattern
+
+    AffineTransform xform;  //distortion applied to this pattern
+
+    int sourceWidth;
+    int sourceHeight;
+
+    //Raster we use to build tile
+    BufferedImage buf;
+
+    /** Creates a new instance of PatternPaintContext */
+    public PatternPaintContext(BufferedImage source, Rectangle deviceBounds, AffineTransform userXform, AffineTransform distortXform)
+    {
+//System.err.println("Bounds " + deviceBounds);
+        this.source = source;
+        this.deviceBounds = deviceBounds;
+        try {
+//            this.distortXform = distortXform.createInverse();
+//            this.userXform = userXform.createInverse();
+
+//            xform = userXform.createInverse();
+//            xform.concatenate(distortXform.createInverse());
+            xform = distortXform.createInverse();
+            xform.concatenate(userXform.createInverse());
+        }
+        catch (Exception e) { e.printStackTrace(); }
+
+        sourceWidth = source.getWidth();
+        sourceHeight = source.getHeight();
+    }
+
+    public void dispose() {
+    }
+
+    public ColorModel getColorModel() {
+        return source.getColorModel();
+    }
+
+    public Raster getRaster(int x, int y, int w, int h)
+    {
+//System.err.println("" + x + ", " + y + ", " + w + ", " + h);
+        if (buf == null || buf.getWidth() != w || buf.getHeight() != buf.getHeight())
+        {
+            buf = new BufferedImage(w, h, source.getType());
+        }
+
+//        Point2D.Float srcPt = new Point2D.Float(), srcPt2 = new Point2D.Float(), destPt = new Point2D.Float();
+        Point2D.Float srcPt = new Point2D.Float(), destPt = new Point2D.Float();
+        for (int j = 0; j < h; j++)
+        {
+            for (int i = 0; i < w; i++)
+            {
+                destPt.setLocation(i + x, j + y);
+
+                xform.transform(destPt, srcPt);
+
+//                userXform.transform(destPt, srcPt2);
+//                distortXform.transform(srcPt2, srcPt);
+
+                int ii = ((int)srcPt.x) % sourceWidth;
+                if (ii < 0) ii += sourceWidth;
+                int jj = ((int)srcPt.y) % sourceHeight;
+                if (jj < 0) jj += sourceHeight;
+
+                buf.setRGB(i, j, source.getRGB(ii, jj));
+            }
+        }
+
+        return buf.getData();
+    }
+
+    public static void main(String[] argv)
+    {
+        int i = -4;
+        System.err.println("Hello " + (i % 4));
+    }
+
+}
Index: trunk/src/com/kitfox/svg/xml/Base64InputStream.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/Base64InputStream.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/Base64InputStream.java	(revision 4256)
@@ -0,0 +1,82 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.kitfox.svg.xml;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Base64InputStream extends FilterInputStream
+{
+    int buf;  //Cached bytes to read
+    int bufSize;  //Number of bytes waiting to be read from buffer
+    boolean drain = false;  //After set, read no more chunks
+    
+    public Base64InputStream(InputStream in)
+    {
+        super(in);
+    }
+
+    public int read() throws IOException
+    {
+        if (drain && bufSize == 0)
+        {
+            return -1;
+        }
+        
+        if (bufSize == 0)
+        {
+            //Read next chunk into 4 byte buffer
+            int chunk = in.read();
+            if (chunk == -1)
+            {
+                drain = true;
+                return -1;
+            }
+            
+            //get remaining 3 bytes
+            for (int i = 0; i < 3; ++i)
+            {
+                int value = in.read();
+                if (value == -1)
+                {
+                    throw new IOException("Early termination of base64 stream");
+                }
+                chunk = (chunk << 8) | (value & 0xff);
+            }
+
+            //Check for special termination characters
+            if ((chunk & 0xffff) == (((byte)'=' << 8) | (byte)'='))
+            {
+                bufSize = 1;
+                drain = true;
+            }
+            else if ((chunk & 0xff) == (byte)'=')
+            {
+                bufSize = 2;
+                drain = true;
+            }
+            else
+            {
+                bufSize = 3;
+            }
+            
+            //Fill buffer with decoded characters
+            for (int i = 0; i < bufSize + 1; ++i)
+            {
+                buf = (buf << 6) | Base64Util.decodeByte((chunk >> 24) & 0xff);
+                chunk <<= 8;
+            }
+        }
+        
+        //Return nth remaing bte & decrement counter
+        return (buf >> (--bufSize * 8)) & 0xff;
+    } 
+}
Index: trunk/src/com/kitfox/svg/xml/Base64OutputStream.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/Base64OutputStream.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/Base64OutputStream.java	(revision 4256)
@@ -0,0 +1,90 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.kitfox.svg.xml;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Base64OutputStream extends FilterOutputStream
+{
+    int buf;
+    int numBytes;
+    int numChunks;
+    
+    public Base64OutputStream(OutputStream out)
+    {
+        super(out);
+    }
+    
+    public void flush() throws IOException
+    {
+        out.flush();
+    }
+    
+    public void close() throws IOException
+    {
+        switch (numBytes)
+        {
+            case 1:
+                buf <<= 4;
+                out.write(getBase64Byte(1));
+                out.write(getBase64Byte(0));
+                out.write('=');
+                out.write('=');
+                break;
+            case 2:
+                buf <<= 2;
+                out.write(getBase64Byte(2));
+                out.write(getBase64Byte(1));
+                out.write(getBase64Byte(0));
+                out.write('=');
+                break;
+            case 3:
+                out.write(getBase64Byte(3));
+                out.write(getBase64Byte(2));
+                out.write(getBase64Byte(1));
+                out.write(getBase64Byte(0));
+                break;
+            default:
+                assert false;
+        }
+        
+        out.close();
+    }
+    
+    public void write(int b) throws IOException
+    {
+        buf = (buf << 8) | (0xff & b);
+        numBytes++;
+        
+        if (numBytes == 3)
+        {
+            out.write(getBase64Byte(3));
+            out.write(getBase64Byte(2));
+            out.write(getBase64Byte(1));
+            out.write(getBase64Byte(0));
+            
+            numBytes = 0;
+            numChunks++;
+            if (numChunks == 16)
+            {
+//                out.write('\r');
+//                out.write('\n');
+                numChunks = 0;
+            }
+        }
+    }
+    
+    public byte getBase64Byte(int index)
+    {
+        return Base64Util.encodeByte((buf >> (index * 6)) & 0x3f);
+    }
+}
Index: trunk/src/com/kitfox/svg/xml/Base64Util.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/Base64Util.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/Base64Util.java	(revision 4256)
@@ -0,0 +1,32 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.kitfox.svg.xml;
+
+/**
+ *
+ * @author kitfox
+ */
+public class Base64Util 
+{
+    static final byte[] valueToBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes();
+    static final byte[] base64ToValue = new byte[128];
+    static {
+        for (int i = 0; i < valueToBase64.length; ++i)
+        {
+            base64ToValue[valueToBase64[i]] = (byte)i;
+        }
+    }
+    
+    static public byte encodeByte(int value)
+    {
+        return valueToBase64[value];
+    }
+    
+    static public byte decodeByte(int base64Char)
+    {
+        return base64ToValue[base64Char];
+    }
+}
Index: trunk/src/com/kitfox/svg/xml/ColorTable.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/ColorTable.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/ColorTable.java	(revision 4256)
@@ -0,0 +1,283 @@
+/*
+ * ColorTable.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 4:34 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.awt.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class ColorTable 
+{
+
+    static final Map colorTable;
+    static {
+        HashMap table = new HashMap();
+
+	//We really should be interpreting the currentColor keyword as 
+        // a reference to the referring node's color, but this quick hack 
+        // will stop the program from crashing.
+        table.put("currentcolor", new Color(0x0));
+
+        table.put("aliceblue", new Color(0xf0f8ff));
+        table.put("antiquewhite", new Color(0xfaebd7));
+        table.put("aqua", new Color(0x00ffff));
+        table.put("aquamarine", new Color(0x7fffd4));
+        table.put("azure", new Color(0xf0ffff));
+        table.put("beige", new Color(0xf5f5dc));
+        table.put("bisque", new Color(0xffe4c4));
+        table.put("black", new Color(0x000000));
+        table.put("blanchedalmond", new Color(0xffebcd));
+        table.put("blue", new Color(0x0000ff));
+        table.put("blueviolet", new Color(0x8a2be2));
+        table.put("brown", new Color(0xa52a2a));
+        table.put("burlywood", new Color(0xdeb887));
+        table.put("cadetblue", new Color(0x5f9ea0));
+        table.put("chartreuse", new Color(0x7fff00));
+        table.put("chocolate", new Color(0xd2691e));
+        table.put("coral", new Color(0xff7f50));
+        table.put("cornflowerblue", new Color(0x6495ed));
+        table.put("cornsilk", new Color(0xfff8dc));
+        table.put("crimson", new Color(0xdc143c));
+        table.put("cyan", new Color(0x00ffff));
+        table.put("darkblue", new Color(0x00008b));
+        table.put("darkcyan", new Color(0x008b8b));
+        table.put("darkgoldenrod", new Color(0xb8860b));
+        table.put("darkgray", new Color(0xa9a9a9));
+        table.put("darkgreen", new Color(0x006400));
+        table.put("darkkhaki", new Color(0xbdb76b));
+        table.put("darkmagenta", new Color(0x8b008b));
+        table.put("darkolivegreen", new Color(0x556b2f));
+        table.put("darkorange", new Color(0xff8c00));
+        table.put("darkorchid", new Color(0x9932cc));
+        table.put("darkred", new Color(0x8b0000));
+        table.put("darksalmon", new Color(0xe9967a));
+        table.put("darkseagreen", new Color(0x8fbc8f));
+        table.put("darkslateblue", new Color(0x483d8b));
+        table.put("darkslategray", new Color(0x2f4f4f));
+        table.put("darkturquoise", new Color(0x00ced1));
+        table.put("darkviolet", new Color(0x9400d3));
+        table.put("deeppink", new Color(0xff1493));
+        table.put("deepskyblue", new Color(0x00bfff));
+        table.put("dimgray", new Color(0x696969));
+        table.put("dodgerblue", new Color(0x1e90ff));
+        table.put("feldspar", new Color(0xd19275));
+        table.put("firebrick", new Color(0xb22222));
+        table.put("floralwhite", new Color(0xfffaf0));
+        table.put("forestgreen", new Color(0x228b22));
+        table.put("fuchsia", new Color(0xff00ff));
+        table.put("gainsboro", new Color(0xdcdcdc));
+        table.put("ghostwhite", new Color(0xf8f8ff));
+        table.put("gold", new Color(0xffd700));
+        table.put("goldenrod", new Color(0xdaa520));
+        table.put("gray", new Color(0x808080));
+        table.put("green", new Color(0x008000));
+        table.put("greenyellow", new Color(0xadff2f));
+        table.put("honeydew", new Color(0xf0fff0));
+        table.put("hotpink", new Color(0xff69b4));
+        table.put("indianred", new Color(0xcd5c5c));
+        table.put("indigo", new Color(0x4b0082));
+        table.put("ivory", new Color(0xfffff0));
+        table.put("khaki", new Color(0xf0e68c));
+        table.put("lavender", new Color(0xe6e6fa));
+        table.put("lavenderblush", new Color(0xfff0f5));
+        table.put("lawngreen", new Color(0x7cfc00));
+        table.put("lemonchiffon", new Color(0xfffacd));
+        table.put("lightblue", new Color(0xadd8e6));
+        table.put("lightcoral", new Color(0xf08080));
+        table.put("lightcyan", new Color(0xe0ffff));
+        table.put("lightgoldenrodyellow", new Color(0xfafad2));
+        table.put("lightgrey", new Color(0xd3d3d3));
+        table.put("lightgreen", new Color(0x90ee90));
+        table.put("lightpink", new Color(0xffb6c1));
+        table.put("lightsalmon", new Color(0xffa07a));
+        table.put("lightseagreen", new Color(0x20b2aa));
+        table.put("lightskyblue", new Color(0x87cefa));
+        table.put("lightslateblue", new Color(0x8470ff));
+        table.put("lightslategray", new Color(0x778899));
+        table.put("lightsteelblue", new Color(0xb0c4de));
+        table.put("lightyellow", new Color(0xffffe0));
+        table.put("lime", new Color(0x00ff00));
+        table.put("limegreen", new Color(0x32cd32));
+        table.put("linen", new Color(0xfaf0e6));
+        table.put("magenta", new Color(0xff00ff));
+        table.put("maroon", new Color(0x800000));
+        table.put("mediumaquamarine", new Color(0x66cdaa));
+        table.put("mediumblue", new Color(0x0000cd));
+        table.put("mediumorchid", new Color(0xba55d3));
+        table.put("mediumpurple", new Color(0x9370d8));
+        table.put("mediumseagreen", new Color(0x3cb371));
+        table.put("mediumslateblue", new Color(0x7b68ee));
+        table.put("mediumspringgreen", new Color(0x00fa9a));
+        table.put("mediumturquoise", new Color(0x48d1cc));
+        table.put("mediumvioletred", new Color(0xc71585));
+        table.put("midnightblue", new Color(0x191970));
+        table.put("mintcream", new Color(0xf5fffa));
+        table.put("mistyrose", new Color(0xffe4e1));
+        table.put("moccasin", new Color(0xffe4b5));
+        table.put("navajowhite", new Color(0xffdead));
+        table.put("navy", new Color(0x000080));
+        table.put("oldlace", new Color(0xfdf5e6));
+        table.put("olive", new Color(0x808000));
+        table.put("olivedrab", new Color(0x6b8e23));
+        table.put("orange", new Color(0xffa500));
+        table.put("orangered", new Color(0xff4500));
+        table.put("orchid", new Color(0xda70d6));
+        table.put("palegoldenrod", new Color(0xeee8aa));
+        table.put("palegreen", new Color(0x98fb98));
+        table.put("paleturquoise", new Color(0xafeeee));
+        table.put("palevioletred", new Color(0xd87093));
+        table.put("papayawhip", new Color(0xffefd5));
+        table.put("peachpuff", new Color(0xffdab9));
+        table.put("peru", new Color(0xcd853f));
+        table.put("pink", new Color(0xffc0cb));
+        table.put("plum", new Color(0xdda0dd));
+        table.put("powderblue", new Color(0xb0e0e6));
+        table.put("purple", new Color(0x800080));
+        table.put("red", new Color(0xff0000));
+        table.put("rosybrown", new Color(0xbc8f8f));
+        table.put("royalblue", new Color(0x4169e1));
+        table.put("saddlebrown", new Color(0x8b4513));
+        table.put("salmon", new Color(0xfa8072));
+        table.put("sandybrown", new Color(0xf4a460));
+        table.put("seagreen", new Color(0x2e8b57));
+        table.put("seashell", new Color(0xfff5ee));
+        table.put("sienna", new Color(0xa0522d));
+        table.put("silver", new Color(0xc0c0c0));
+        table.put("skyblue", new Color(0x87ceeb));
+        table.put("slateblue", new Color(0x6a5acd));
+        table.put("slategray", new Color(0x708090));
+        table.put("snow", new Color(0xfffafa));
+        table.put("springgreen", new Color(0x00ff7f));
+        table.put("steelblue", new Color(0x4682b4));
+        table.put("tan", new Color(0xd2b48c));
+        table.put("teal", new Color(0x008080));
+        table.put("thistle", new Color(0xd8bfd8));
+        table.put("tomato", new Color(0xff6347));
+        table.put("turquoise", new Color(0x40e0d0));
+        table.put("violet", new Color(0xee82ee));
+        table.put("violetred", new Color(0xd02090));
+        table.put("wheat", new Color(0xf5deb3));
+        table.put("white", new Color(0xffffff));
+        table.put("whitesmoke", new Color(0xf5f5f5));
+        table.put("yellow", new Color(0xffff00));
+        table.put("yellowgreen", new Color(0x9acd32));
+        
+        colorTable = Collections.unmodifiableMap(table);
+    }
+
+    static ColorTable singleton = new ColorTable();
+
+    /** Creates a new instance of ColorTable */
+    protected ColorTable() {
+//        buildColorList();
+    }
+
+    static public ColorTable instance() { return singleton; }
+
+    public Color lookupColor(String name) {
+        Object obj = colorTable.get(name.toLowerCase());
+        if (obj == null) return null;
+
+        return (Color)obj;
+    }
+
+    public static Color parseColor(String val)
+    {
+        Color retVal = null;
+
+        if (val.charAt(0) == '#')
+        {
+            String hexStrn = val.substring(1);
+            
+            if (hexStrn.length() == 3)
+            {
+                hexStrn = "" + hexStrn.charAt(0) + hexStrn.charAt(0) + hexStrn.charAt(1) + hexStrn.charAt(1) + hexStrn.charAt(2) + hexStrn.charAt(2);
+            }
+            int hexVal = parseHex(hexStrn);
+
+            retVal = new Color(hexVal);
+        }
+        else
+        {
+            final String number = "\\s*(((\\d+)(\\.\\d*)?)|(\\.\\d+))(%)?\\s*";
+            final Matcher rgbMatch = Pattern.compile("rgb\\(" + number + "," + number + "," + number + "\\)", Pattern.CASE_INSENSITIVE).matcher("");
+
+            rgbMatch.reset(val);
+            if (rgbMatch.matches())
+            {
+                float rr = Float.parseFloat(rgbMatch.group(1));
+                float gg = Float.parseFloat(rgbMatch.group(7));
+                float bb = Float.parseFloat(rgbMatch.group(13));
+                rr /= "%".equals(rgbMatch.group(6)) ? 100 : 255;
+                gg /= "%".equals(rgbMatch.group(12)) ? 100 : 255;
+                bb /= "%".equals(rgbMatch.group(18)) ? 100 : 255;
+                retVal = new Color(rr, gg, bb);
+            }
+            else
+            {
+                Color lookupCol = ColorTable.instance().lookupColor(val);
+                if (lookupCol != null) retVal = lookupCol;
+            }
+        }
+
+        return retVal;
+    }
+
+    public static int parseHex(String val)
+    {
+        int retVal = 0;
+        
+        for (int i = 0; i < val.length(); i++)
+        {
+            retVal <<= 4;
+            
+            char ch = val.charAt(i);
+            if (ch >= '0' && ch <= '9')
+            {
+                retVal |= ch - '0';
+            }
+            else if (ch >= 'a' && ch <= 'z')
+            {
+                retVal |= ch - 'a' + 10;
+            }
+            else if (ch >= 'A' && ch <= 'Z')
+            {
+                retVal |= ch - 'A' + 10;
+            }
+            else throw new RuntimeException();
+        }
+        
+        return retVal;
+    }
+
+}
Index: trunk/src/com/kitfox/svg/xml/NumberWithUnits.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/NumberWithUnits.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/NumberWithUnits.java	(revision 4256)
@@ -0,0 +1,148 @@
+/*
+ * NumberWithUnits.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 2:43 PM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.io.Serializable;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class NumberWithUnits implements Serializable
+{
+    public static final long serialVersionUID = 0;
+    
+    public static final int UT_UNITLESS = 0;
+    public static final int UT_PX = 1;  //Pixels
+    public static final int UT_CM = 2;  //Centimeters
+    public static final int UT_MM = 3;  //Millimeters
+    public static final int UT_IN = 4;  //Inches
+    public static final int UT_EM = 5;  //Default font height
+    public static final int UT_EX = 6;  //Height of character 'x' in default font
+    public static final int UT_PT = 7;  //Points - 1/72 of an inch
+    public static final int UT_PC = 8;  //Picas - 1/6 of an inch
+    public static final int UT_PERCENT = 9;  //Percent - relative width
+
+    float value = 0f;
+    int unitType = UT_UNITLESS;
+
+    /** Creates a new instance of NumberWithUnits */
+    public NumberWithUnits()
+    {
+    }
+
+    public NumberWithUnits(String value)
+    {
+        set(value);
+    }
+
+    public NumberWithUnits(float value, int unitType)
+    {
+        this.value = value;
+        this.unitType = unitType;
+    }
+
+    public float getValue() { return value; }
+    public int getUnits() { return unitType; }
+
+    public void set(String value)
+    {
+        this.value = XMLParseUtil.findFloat(value);
+        unitType = UT_UNITLESS;
+
+        if (value.indexOf("px") != -1) { unitType = UT_PX; return; }
+        if (value.indexOf("cm") != -1) { unitType = UT_CM; return; }
+        if (value.indexOf("mm") != -1) { unitType = UT_MM; return; }
+        if (value.indexOf("in") != -1) { unitType = UT_IN; return; }
+        if (value.indexOf("em") != -1) { unitType = UT_EM; return; }
+        if (value.indexOf("ex") != -1) { unitType = UT_EX; return; }
+        if (value.indexOf("pt") != -1) { unitType = UT_PT; return; }
+        if (value.indexOf("pc") != -1) { unitType = UT_PC; return; }
+        if (value.indexOf("%") != -1) { unitType = UT_PERCENT; return; }
+    }
+
+    public static String unitsAsString(int unitIdx)
+    {
+        switch (unitIdx)
+        {
+            default:
+                return "";
+            case UT_PX:
+                return "px";
+            case UT_CM:
+                return "cm";
+            case UT_MM:
+                return "mm";
+            case UT_IN:
+                return "in";
+            case UT_EM:
+                return "em";
+            case UT_EX:
+                return "ex";
+            case UT_PT:
+                return "pt";
+            case UT_PC:
+                return "pc";
+            case UT_PERCENT:
+                return "%";
+        }
+    }
+
+    public String toString()
+    {
+        return "" + value + unitsAsString(unitType);
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final NumberWithUnits other = (NumberWithUnits) obj;
+        if (Float.floatToIntBits(this.value) != Float.floatToIntBits(other.value)) {
+            return false;
+        }
+        if (this.unitType != other.unitType) {
+            return false;
+        }
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hash = 5;
+        hash = 37 * hash + Float.floatToIntBits(this.value);
+        hash = 37 * hash + this.unitType;
+        return hash;
+    }
+
+
+}
Index: trunk/src/com/kitfox/svg/xml/ReadableXMLElement.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/ReadableXMLElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/ReadableXMLElement.java	(revision 4256)
@@ -0,0 +1,48 @@
+/*
+ * LoadableObject.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 1, 2003, 1:46 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import org.w3c.dom.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface ReadableXMLElement {
+
+    /**
+     * Initializes this element from the passed DOM tree.
+     * @param root - DOM tree to build from
+     * @param docRoot - URL of the document this DOM tree was created from
+     */
+    public void read(Element root, URL docRoot);
+
+}
Index: trunk/src/com/kitfox/svg/xml/StyleAttribute.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/StyleAttribute.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/StyleAttribute.java	(revision 4256)
@@ -0,0 +1,290 @@
+/*
+ * StyleAttribute.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on January 27, 2004, 2:53 PM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.awt.*;
+import java.net.*;
+import java.io.*;
+import java.util.regex.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class StyleAttribute implements Serializable
+{
+    public static final long serialVersionUID = 0;
+
+    static final Matcher matchUrl = Pattern.compile("\\s*url\\((.*)\\)\\s*").matcher("");
+    static final Matcher matchFpNumUnits = Pattern.compile("\\s*([-+]?((\\d*\\.\\d+)|(\\d+))([-+]?[eE]\\d+)?)\\s*(px|cm|mm|in|pc|pt|em|ex)\\s*").matcher("");
+    
+    String name;
+    String stringValue;
+
+    boolean colorCompatable = false;
+    boolean urlCompatable = false;
+
+    /** Creates a new instance of StyleAttribute */
+    public StyleAttribute()
+    {
+        this(null, null);
+    }
+    
+    public StyleAttribute(String name) 
+    {
+        this.name = name;
+        stringValue = null;
+    }
+
+    public StyleAttribute(String name, String stringValue) 
+    {
+        this.name = name;
+        this.stringValue = stringValue;
+    }
+
+    public String getName() { return name; }
+    public StyleAttribute setName(String name) { this.name = name; return this; }
+    
+    public String getStringValue() { return stringValue; }
+
+    public String[] getStringList() 
+    { 
+        return XMLParseUtil.parseStringList(stringValue);
+    }
+
+    public void setStringValue(String value)
+    {
+        stringValue = value;
+    }
+
+    public boolean getBooleanValue() {
+        return stringValue.toLowerCase().equals("true");
+    }
+
+    public int getIntValue() {
+        return XMLParseUtil.findInt(stringValue);
+    }
+
+    public int[] getIntList() {
+        return XMLParseUtil.parseIntList(stringValue);
+    }
+
+    public double getDoubleValue() {
+        return XMLParseUtil.findDouble(stringValue);
+    }
+
+    public double[] getDoubleList() {
+        return XMLParseUtil.parseDoubleList(stringValue);
+    }
+
+    public float getFloatValue() {
+        return XMLParseUtil.findFloat(stringValue);
+    }
+
+    public float[] getFloatList() {
+        return XMLParseUtil.parseFloatList(stringValue);
+    }
+
+    public float getRatioValue() {
+        return (float)XMLParseUtil.parseRatio(stringValue);
+//        try { return Float.parseFloat(stringValue); }
+//        catch (Exception e) {}
+//        return 0f;
+    }
+
+    public String getUnits() {
+        matchFpNumUnits.reset(stringValue);
+        if (!matchFpNumUnits.matches()) return null;
+        return matchFpNumUnits.group(6);
+    }
+
+    public NumberWithUnits getNumberWithUnits() {
+        return XMLParseUtil.parseNumberWithUnits(stringValue);
+    }
+
+    public float getFloatValueWithUnits()
+    {
+        NumberWithUnits number = getNumberWithUnits();
+        return convertUnitsToPixels(number.getUnits(), number.getValue());
+    }
+    
+    static public float convertUnitsToPixels(int unitType, float value)
+    {
+        if (unitType == NumberWithUnits.UT_UNITLESS || unitType == NumberWithUnits.UT_PERCENT)
+        {
+            return value;
+        }
+        
+        float pixPerInch;
+        try 
+        {
+            pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution();
+        }
+        catch (HeadlessException ex)
+        {
+            //default to 72 dpi
+            pixPerInch = 72;
+        }
+        final float inchesPerCm = .3936f;
+
+        switch (unitType)
+        {
+            case NumberWithUnits.UT_IN:
+                return value * pixPerInch;
+            case NumberWithUnits.UT_CM:
+                return value * inchesPerCm * pixPerInch;
+            case NumberWithUnits.UT_MM:
+                return value * .1f * inchesPerCm * pixPerInch;
+            case NumberWithUnits.UT_PT:
+                return value * (1f / 72f) * pixPerInch;
+            case NumberWithUnits.UT_PC:
+                return value *  (1f / 6f) * pixPerInch;
+        }
+
+        return value;
+    }
+
+    public Color getColorValue()
+    {
+        return ColorTable.parseColor(stringValue);
+    }
+
+    public String parseURLFn()
+    {
+        matchUrl.reset(stringValue);
+        if (!matchUrl.matches()) return null;
+        return matchUrl.group(1);
+    }
+
+    public URL getURLValue(URL docRoot)
+    {
+        String fragment = parseURLFn();
+        if (fragment == null) return null;
+        try {
+            return new URL(docRoot, fragment);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public URL getURLValue(URI docRoot)
+    {
+        String fragment = parseURLFn();
+        if (fragment == null) return null;
+        try {
+            URI ref = docRoot.resolve(fragment);
+            return ref.toURL();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public URI getURIValue()
+    {
+        return getURIValue(null);
+    }
+    
+    /**
+     * Parse this sytle attribute as a URL and return it in URI form resolved
+     * against the passed base.
+     *
+     * @param base - URI to resolve against.  If null, will return value without
+     * attempting to resolve it.
+     */
+    public URI getURIValue(URI base)
+    {
+        try {
+            String fragment = parseURLFn();
+            if (fragment == null) fragment = stringValue.replaceAll("\\s+", "");
+            if (fragment == null) return null;
+            
+            //======================
+            //This gets around a bug in the 1.5.0 JDK
+            if (Pattern.matches("[a-zA-Z]:!\\\\.*", fragment))
+            {
+                File file = new File(fragment);
+                return file.toURI();
+            }
+            //======================
+
+            //[scheme:]scheme-specific-part[#fragment]
+            
+            URI uriFrag = new URI(fragment);
+            if (uriFrag.isAbsolute())
+            {
+                //Has scheme
+                return uriFrag;
+            }
+        
+            if (base == null) return uriFrag;
+        
+            URI relBase = new URI(null, base.getSchemeSpecificPart(), null);
+            URI relUri;
+            if (relBase.isOpaque())
+            {
+                relUri = new URI(null, base.getSchemeSpecificPart(), uriFrag.getFragment());
+            }
+            else
+            {
+                relUri = relBase.resolve(uriFrag);
+            }
+            return new URI(base.getScheme() + ":" + relUri);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    
+    public static void main(String[] args)
+    {
+        try
+        {
+            URI uri = new URI("jar:http://www.kitfox.com/jackal/jackal.jar!/res/doc/about.svg");
+            uri = uri.resolve("#myFragment");
+            
+            System.err.println(uri.toString());
+            
+            uri = new URI("http://www.kitfox.com/jackal/jackal.html");
+            uri = uri.resolve("#myFragment");
+            
+            System.err.println(uri.toString());
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
Index: trunk/src/com/kitfox/svg/xml/WritableXMLElement.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/WritableXMLElement.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/WritableXMLElement.java	(revision 4256)
@@ -0,0 +1,48 @@
+/*
+ * LoadableObject.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on September 1, 2003, 1:46 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import org.w3c.dom.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface WritableXMLElement {
+
+    /**
+     * Initializes this element from the passed DOM tree.
+     * @param root - DOM tree to build from
+     * @param docRoot - URL of the document this DOM tree was created from
+     */
+//    public void write(Element root, URL docRoot);
+
+}
Index: trunk/src/com/kitfox/svg/xml/XMLParseUtil.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/XMLParseUtil.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/XMLParseUtil.java	(revision 4256)
@@ -0,0 +1,821 @@
+/*
+ * XMLParseUtil.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 18, 2004, 1:49 PM
+ */
+
+package com.kitfox.svg.xml;
+
+import org.w3c.dom.*;
+import java.awt.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+import java.lang.reflect.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class XMLParseUtil
+{
+    static final Matcher fpMatch = Pattern.compile("([-+]?((\\d*\\.\\d+)|(\\d+))([eE][+-]?\\d+)?)(\\%|in|cm|mm|pt|pc|px|em|ex)?").matcher("");
+    static final Matcher intMatch = Pattern.compile("[-+]?\\d+").matcher("");
+
+    /** Creates a new instance of XMLParseUtil */
+    private XMLParseUtil()
+    {
+    }
+
+    /**
+     * Scans the tag's children and returns the first text element found
+     */
+    public static String getTagText(Element ele)
+    {
+        NodeList nl = ele.getChildNodes();
+        int size = nl.getLength();
+
+        Node node = null;
+        int i = 0;
+        for (; i < size; i++)
+        {
+            node = nl.item(i);
+            if (node instanceof Text) break;
+        }
+        if (i == size || node == null) return null;
+
+        return ((Text)node).getData();
+    }
+
+    /**
+     * Returns the first node that is a direct child of root with the coresponding
+     * name.  Does not search children of children.
+     */
+    public static Element getFirstChild(Element root, String name)
+    {
+        NodeList nl = root.getChildNodes();
+        int size = nl.getLength();
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (ele.getTagName().equals(name)) return ele;
+        }
+
+        return null;
+    }
+
+    public static String[] parseStringList(String list)
+    {
+//        final Pattern patWs = Pattern.compile("\\s+");
+        final Matcher matchWs = Pattern.compile("[^\\s]+").matcher("");
+        matchWs.reset(list);
+
+        LinkedList matchList = new LinkedList();
+        while (matchWs.find())
+        {
+            matchList.add(matchWs.group());
+        }
+
+        String[] retArr = new String[matchList.size()];
+        return (String[])matchList.toArray(retArr);
+    }
+
+    public static boolean isDouble(String val)
+    {
+        fpMatch.reset(val);
+        return fpMatch.matches();
+    }
+    
+    public static double parseDouble(String val)
+    {
+        /*
+        if (val == null) return 0.0;
+
+        double retVal = 0.0;
+        try
+        { retVal = Double.parseDouble(val); }
+        catch (Exception e)
+        {}
+        return retVal;
+         */
+        return findDouble(val);
+    }
+
+    /**
+     * Searches the given string for the first floating point number it contains,
+     * parses and returns it.
+     */
+    public synchronized static double findDouble(String val)
+    {
+        if (val == null) return 0;
+
+        fpMatch.reset(val);
+        try
+        {
+            if (!fpMatch.find()) return 0;
+        }
+        catch (StringIndexOutOfBoundsException e)
+        {
+            System.err.println("XMLParseUtil: regex parse problem: '" + val + "'");
+            e.printStackTrace();
+        }
+
+        val = fpMatch.group(1);
+        //System.err.println("Parsing " + val);
+
+        double retVal = 0;
+        try
+        { 
+            retVal = Double.parseDouble(val); 
+            
+            float pixPerInch;
+            try {
+                pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution();
+            }
+            catch (NoClassDefFoundError err)
+            {
+                //Default value for headless X servers
+                pixPerInch = 72;
+            }
+            final float inchesPerCm = .3936f;
+            final String units = fpMatch.group(6);
+            
+            if ("%".equals(units)) retVal /= 100;
+            else if ("in".equals(units))
+            {
+                retVal *= pixPerInch;
+            }
+            else if ("cm".equals(units))
+            {
+                retVal *= inchesPerCm * pixPerInch;
+            }
+            else if ("mm".equals(units))
+            {
+                retVal *= inchesPerCm * pixPerInch * .1f;
+            }
+            else if ("pt".equals(units))
+            {
+                retVal *= (1f / 72f) * pixPerInch;
+            }
+            else if ("pc".equals(units))
+            {
+                retVal *= (1f / 6f) * pixPerInch;
+            }
+        }
+        catch (Exception e)
+        {}
+        return retVal;
+    }
+
+    /**
+     * Scans an input string for double values.  For each value found, places
+     * in a list.  This method regards any characters not part of a floating
+     * point value to be seperators.  Thus this will parse whitespace seperated,
+     * comma seperated, and many other separation schemes correctly.
+     */
+    public synchronized static double[] parseDoubleList(String list)
+    {
+        if (list == null) return null;
+
+        fpMatch.reset(list);
+
+        LinkedList doubList = new LinkedList();
+        while (fpMatch.find())
+        {
+            String val = fpMatch.group(1);
+            doubList.add(Double.valueOf(val));
+        }
+
+        double[] retArr = new double[doubList.size()];
+        Iterator it = doubList.iterator();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            retArr[idx++] = ((Double)it.next()).doubleValue();
+        }
+
+        return retArr;
+    }
+
+    public static float parseFloat(String val)
+    {
+        /*
+        if (val == null) return 0f;
+
+        float retVal = 0f;
+        try
+        { retVal = Float.parseFloat(val); }
+        catch (Exception e)
+        {}
+        return retVal;
+         */
+        return findFloat(val);
+    }
+
+    /**
+     * Searches the given string for the first floating point number it contains,
+     * parses and returns it.
+     */
+    public synchronized static float findFloat(String val)
+    {
+        if (val == null) return 0f;
+
+        fpMatch.reset(val);
+        if (!fpMatch.find()) return 0f;
+
+        val = fpMatch.group(1);
+        //System.err.println("Parsing " + val);
+
+        float retVal = 0f;
+        try
+        {
+            retVal = Float.parseFloat(val);
+            String units = fpMatch.group(6);
+            if ("%".equals(units)) retVal /= 100;
+        }
+        catch (Exception e)
+        {}
+        return retVal;
+    }
+
+    public synchronized static float[] parseFloatList(String list)
+    {
+        if (list == null) return null;
+
+        fpMatch.reset(list);
+
+        LinkedList floatList = new LinkedList();
+        while (fpMatch.find())
+        {
+            String val = fpMatch.group(1);
+            floatList.add(Float.valueOf(val));
+        }
+
+        float[] retArr = new float[floatList.size()];
+        Iterator it = floatList.iterator();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            retArr[idx++] = ((Float)it.next()).floatValue();
+        }
+
+        return retArr;
+    }
+
+    public static int parseInt(String val)
+    {
+        if (val == null) return 0;
+
+        int retVal = 0;
+        try
+        { retVal = Integer.parseInt(val); }
+        catch (Exception e)
+        {}
+        return retVal;
+    }
+
+    /**
+     * Searches the given string for the first integer point number it contains,
+     * parses and returns it.
+     */
+    public static int findInt(String val)
+    {
+        if (val == null) return 0;
+
+        intMatch.reset(val);
+        if (!intMatch.find()) return 0;
+
+        val = intMatch.group();
+        //System.err.println("Parsing " + val);
+
+        int retVal = 0;
+        try
+        { retVal = Integer.parseInt(val); }
+        catch (Exception e)
+        {}
+        return retVal;
+    }
+
+    public static int[] parseIntList(String list)
+    {
+        if (list == null) return null;
+
+        intMatch.reset(list);
+
+        LinkedList intList = new LinkedList();
+        while (intMatch.find())
+        {
+            String val = intMatch.group();
+            intList.add(Integer.valueOf(val));
+        }
+
+        int[] retArr = new int[intList.size()];
+        Iterator it = intList.iterator();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            retArr[idx++] = ((Integer)it.next()).intValue();
+        }
+
+        return retArr;
+    }
+/*
+    public static int parseHex(String val)
+    {
+        int retVal = 0;
+        
+        for (int i = 0; i < val.length(); i++)
+        {
+            retVal <<= 4;
+            
+            char ch = val.charAt(i);
+            if (ch >= '0' && ch <= '9')
+            {
+                retVal |= ch - '0';
+            }
+            else if (ch >= 'a' && ch <= 'z')
+            {
+                retVal |= ch - 'a' + 10;
+            }
+            else if (ch >= 'A' && ch <= 'Z')
+            {
+                retVal |= ch - 'A' + 10;
+            }
+            else throw new RuntimeException();
+        }
+        
+        return retVal;
+    }
+*/
+    /**
+     * The input string represents a ratio.  Can either be specified as a
+     * double number on the range of [0.0 1.0] or as a percentage [0% 100%]
+     */
+    public static double parseRatio(String val)
+    {
+        if (val == null || val.equals("")) return 0.0;
+
+        if (val.charAt(val.length() - 1) == '%')
+        {
+            parseDouble(val.substring(0, val.length() - 1));
+        }
+        return parseDouble(val);
+    }
+
+    public static NumberWithUnits parseNumberWithUnits(String val)
+    {
+        if (val == null) return null;
+
+        return new NumberWithUnits(val);
+    }
+/*
+    public static Color parseColor(String val)
+    {
+        Color retVal = null;
+
+        if (val.charAt(0) == '#')
+        {
+            String hexStrn = val.substring(1);
+            
+            if (hexStrn.length() == 3)
+            {
+                hexStrn = "" + hexStrn.charAt(0) + hexStrn.charAt(0) + hexStrn.charAt(1) + hexStrn.charAt(1) + hexStrn.charAt(2) + hexStrn.charAt(2);
+            }
+            int hexVal = parseHex(hexStrn);
+
+            retVal = new Color(hexVal);
+        }
+        else
+        {
+            final Matcher rgbMatch = Pattern.compile("rgb\\((\\d+),(\\d+),(\\d+)\\)", Pattern.CASE_INSENSITIVE).matcher("");
+
+            rgbMatch.reset(val);
+            if (rgbMatch.matches())
+            {
+                int r = Integer.parseInt(rgbMatch.group(1));
+                int g = Integer.parseInt(rgbMatch.group(2));
+                int b = Integer.parseInt(rgbMatch.group(3));
+                retVal = new Color(r, g, b);
+            }
+            else
+            {
+                Color lookupCol = ColorTable.instance().lookupColor(val);
+                if (lookupCol != null) retVal = lookupCol;
+            }
+        }
+
+        return retVal;
+    }
+*/
+    /**
+     * Parses the given attribute of this tag and returns it as a String.
+     */
+    public static String getAttribString(Element ele, String name)
+    {
+        return ele.getAttribute(name);
+    }
+
+    /**
+     * Parses the given attribute of this tag and returns it as an int.
+     */
+    public static int getAttribInt(Element ele, String name)
+    {
+        String sval = ele.getAttribute(name);
+        int val = 0;
+        try { val = Integer.parseInt(sval); } catch (Exception e) {}
+
+        return val;
+    }
+
+    /**
+     * Parses the given attribute of this tag as a hexadecimal encoded string and
+     * returns it as an int
+     */
+    public static int getAttribIntHex(Element ele, String name)
+    {
+        String sval = ele.getAttribute(name);
+        int val = 0;
+        try { val = Integer.parseInt(sval, 16); } catch (Exception e) {}
+
+        return val;
+    }
+
+    /**
+     * Parses the given attribute of this tag and returns it as a float
+     */
+    public static float getAttribFloat(Element ele, String name)
+    {
+        String sval = ele.getAttribute(name);
+        float val = 0.0f;
+        try { val = Float.parseFloat(sval); } catch (Exception e) {}
+
+        return val;
+    }
+
+    /**
+     * Parses the given attribute of this tag and returns it as a double.
+     */
+    public static double getAttribDouble(Element ele, String name)
+    {
+        String sval = ele.getAttribute(name);
+        double val = 0.0;
+        try { val = Double.parseDouble(sval); } catch (Exception e) {}
+
+        return val;
+    }
+
+    /**
+     * Parses the given attribute of this tag and returns it as a boolean.
+     * Essentially compares the lower case textual value to the string "true"
+     */
+    public static boolean getAttribBoolean(Element ele, String name)
+    {
+        String sval = ele.getAttribute(name);
+
+        return sval.toLowerCase().equals("true");
+    }
+
+    public static URL getAttribURL(Element ele, String name, URL docRoot)
+    {
+        String sval = ele.getAttribute(name);
+
+        URL url;
+        try
+        {
+            return new URL(docRoot, sval);
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the first ReadableXMLElement with the given name
+     */
+    public static ReadableXMLElement getElement(Class classType, Element root, String name, URL docRoot)
+    {
+        if (root == null) return null;
+
+        //Do not process if not a LoadableObject
+        if (!ReadableXMLElement.class.isAssignableFrom(classType))
+        {
+            return null;
+        }
+
+        NodeList nl = root.getChildNodes();
+        int size = nl.getLength();
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            ReadableXMLElement newObj = null;
+            try { newObj = (ReadableXMLElement)classType.newInstance(); }
+            catch (Exception e) { e.printStackTrace(); continue; }
+            newObj.read(ele, docRoot);
+
+            if (newObj == null) continue;
+
+            return newObj;
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a HashMap of nodes that are children of root.  All nodes will
+     * be of class classType and have a tag name of 'name'.  'key' is
+     * an attribute of tag 'name' who's string value will be used as the key
+     * in the HashMap
+     */
+    public static HashMap getElementHashMap(Class classType, Element root, String name, String key, URL docRoot)
+    {
+        if (root == null) return null;
+
+        //Do not process if not a LoadableObject
+        if (!ReadableXMLElement.class.isAssignableFrom(classType))
+        {
+            return null;
+        }
+
+        HashMap retMap = new HashMap();
+
+/*
+        Class[] params = {Element.class, URL.class};
+        Method loadMethod = null;
+        try { loadMethod = classType.getMethod("load", params); }
+        catch (Exception e) { e.printStackTrace(); return null; }
+
+ */
+        NodeList nl = root.getChildNodes();
+        int size = nl.getLength();
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            ReadableXMLElement newObj = null;
+            try { newObj = (ReadableXMLElement)classType.newInstance(); }
+            catch (Exception e) { e.printStackTrace(); continue; }
+            newObj.read(ele, docRoot);
+/*
+            Object[] args = {ele, source};
+            Object obj = null;
+            try { obj = loadMethod.invoke(null, args); }
+            catch (Exception e) { e.printStackTrace(); }
+
+ */
+            if (newObj == null) continue;
+
+            String keyVal = getAttribString(ele, key);
+            retMap.put(keyVal, newObj);
+        }
+
+        return retMap;
+    }
+
+    public static HashSet getElementHashSet(Class classType, Element root, String name, URL docRoot)
+    {
+        if (root == null) return null;
+
+        //Do not process if not a LoadableObject
+        if (!ReadableXMLElement.class.isAssignableFrom(classType))
+        {
+            return null;
+        }
+
+        HashSet retSet = new HashSet();
+
+        /*
+        Class[] params = {Element.class, URL.class};
+        Method loadMethod = null;
+        try { loadMethod = classType.getMethod("load", params); }
+        catch (Exception e) { e.printStackTrace(); return null; }
+        */
+
+        NodeList nl = root.getChildNodes();
+        int size = nl.getLength();
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            ReadableXMLElement newObj = null;
+            try { newObj = (ReadableXMLElement)classType.newInstance(); }
+            catch (Exception e) { e.printStackTrace(); continue; }
+            newObj.read(ele, docRoot);
+            /*
+            Object[] args = {ele, source};
+            Object obj = null;
+            try { obj = loadMethod.invoke(null, args); }
+            catch (Exception e) { e.printStackTrace(); }
+             */
+
+            if (newObj == null) continue;
+
+            retSet.add(newObj);
+        }
+
+        return retSet;
+    }
+
+
+    public static LinkedList getElementLinkedList(Class classType, Element root, String name, URL docRoot)
+    {
+        if (root == null) return null;
+
+        //Do not process if not a LoadableObject
+        if (!ReadableXMLElement.class.isAssignableFrom(classType))
+        {
+            return null;
+        }
+
+        NodeList nl = root.getChildNodes();
+        LinkedList elementCache = new LinkedList();
+        int size = nl.getLength();
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            ReadableXMLElement newObj = null;
+            try { newObj = (ReadableXMLElement)classType.newInstance(); }
+            catch (Exception e) { e.printStackTrace(); continue; }
+            newObj.read(ele, docRoot);
+
+            elementCache.addLast(newObj);
+        }
+
+        return elementCache;
+    }
+
+    public static Object[] getElementArray(Class classType, Element root, String name, URL docRoot)
+    {
+        if (root == null) return null;
+
+        //Do not process if not a LoadableObject
+        if (!ReadableXMLElement.class.isAssignableFrom(classType))
+        {
+            return null;
+        }
+
+        LinkedList elementCache = getElementLinkedList(classType, root, name, docRoot);
+
+        Object[] retArr = (Object[])Array.newInstance(classType, elementCache.size());
+        return elementCache.toArray(retArr);
+    }
+
+    /**
+     * Takes a number of tags of name 'name' that are children of 'root', and
+     * looks for attributes of 'attrib' on them.  Converts attributes to an
+     * int and returns in an array.
+     */
+    public static int[] getElementArrayInt(Element root, String name, String attrib)
+    {
+        if (root == null) return null;
+
+        NodeList nl = root.getChildNodes();
+        LinkedList elementCache = new LinkedList();
+        int size = nl.getLength();
+
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            String valS = ele.getAttribute(attrib);
+            int eleVal = 0;
+            try { eleVal = Integer.parseInt(valS); }
+            catch (Exception e) {}
+
+            elementCache.addLast(new Integer(eleVal));
+        }
+
+        int[] retArr = new int[elementCache.size()];
+        Iterator it = elementCache.iterator();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            retArr[idx++] = ((Integer)it.next()).intValue();
+        }
+
+        return retArr;
+    }
+
+    /**
+     * Takes a number of tags of name 'name' that are children of 'root', and
+     * looks for attributes of 'attrib' on them.  Converts attributes to an
+     * int and returns in an array.
+     */
+    public static String[] getElementArrayString(Element root, String name, String attrib)
+    {
+        if (root == null) return null;
+
+        NodeList nl = root.getChildNodes();
+        LinkedList elementCache = new LinkedList();
+        int size = nl.getLength();
+
+        for (int i = 0; i < size; i++)
+        {
+            Node node = nl.item(i);
+            if (!(node instanceof Element)) continue;
+            Element ele = (Element)node;
+            if (!ele.getTagName().equals(name)) continue;
+
+            String valS = ele.getAttribute(attrib);
+
+            elementCache.addLast(valS);
+        }
+
+        String[] retArr = new String[elementCache.size()];
+        Iterator it = elementCache.iterator();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            retArr[idx++] = (String)it.next();
+        }
+
+        return retArr;
+    }
+
+    /**
+     * Takes a CSS style string and retursn a hash of them.
+     * @param styleString - A CSS formatted string of styles.  Eg,
+     *     "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;"
+     */
+    public static HashMap parseStyle(String styleString) {
+        return parseStyle(styleString, new HashMap());
+    }
+
+    /**
+     * Takes a CSS style string and returns a hash of them.
+     * @param styleString - A CSS formatted string of styles.  Eg,
+     *     "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;"
+     * @param map - A map to which these styles will be added
+     */
+    public static HashMap parseStyle(String styleString, HashMap map) {
+        final Pattern patSemi = Pattern.compile(";");
+        final Pattern patColonSpace = Pattern.compile(":");
+
+        //Strips left and right whitespace
+        final Matcher matcherContent = Pattern.compile("\\s*([^\\s](.*[^\\s])?)\\s*").matcher("");
+
+        String[] styles = patSemi.split(styleString);
+
+        for (int i = 0; i < styles.length; i++)
+        {
+            if (styles[i].length() == 0)
+            {
+                continue;
+            }
+
+            String[] vals = patColonSpace.split(styles[i]);
+
+            matcherContent.reset(vals[0]);
+            matcherContent.matches();
+            vals[0] = matcherContent.group(1);
+
+            matcherContent.reset(vals[1]);
+            matcherContent.matches();
+            vals[1] = matcherContent.group(1);
+
+            map.put(vals[0], new StyleAttribute(vals[0], vals[1]));
+        }
+
+        return map;
+    }
+}
Index: trunk/src/com/kitfox/svg/xml/cpx/CPXConsts.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/cpx/CPXConsts.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/cpx/CPXConsts.java	(revision 4256)
@@ -0,0 +1,40 @@
+/*
+ * CPXConst.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 12:51 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface CPXConsts {
+
+    static final byte[] MAGIC_NUMBER = {'C', 'P', 'X', 0};
+
+    static final int XL_PLAIN = 0;
+    static final int XL_ZIP_CRYPT = 1;
+}
Index: trunk/src/com/kitfox/svg/xml/cpx/CPXInputStream.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/cpx/CPXInputStream.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/cpx/CPXInputStream.java	(revision 4256)
@@ -0,0 +1,293 @@
+/*
+ * CPXInputStream.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 10:34 AM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import java.security.*;
+import javax.crypto.*;
+
+/**
+ * This class reads/decodes the CPX file format.  This format is a simple
+ * compression/encryption transformer for XML data.  This stream takes in
+ * encrypted XML and outputs decrypted.  It does this by checking for a magic
+ * number at the start of the stream.  If absent, it treats the stream as
+ * raw XML data and passes it through unaltered.  This is to aid development
+ * in debugging versions, where the XML files will not be in CPX format.
+ *
+ * See http://java.sun.com/developer/technicalArticles/Security/Crypto/
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXInputStream extends FilterInputStream implements CPXConsts {
+
+
+    SecureRandom sec = new SecureRandom();
+
+    Inflater inflater = new Inflater();
+
+    int xlateMode;
+
+    //Keep header bytes in case this stream turns out to be plain text
+    byte[] head = new byte[4];
+    int headSize = 0;
+    int headPtr = 0;
+
+    boolean reachedEOF = false;
+    byte[] inBuffer = new byte[2048];
+    byte[] decryptBuffer = new byte[2048];
+
+    /** Creates a new instance of CPXInputStream */
+    public CPXInputStream(InputStream in) throws IOException {
+        super(in);
+
+        //Determine processing type
+        for (int i = 0; i < 4; i++)
+        {
+            int val = in.read();
+            head[i] = (byte)val;
+            if (val == -1 || head[i] != MAGIC_NUMBER[i])
+            {
+                headSize = i + 1;
+                xlateMode = XL_PLAIN;
+                return;
+            }
+        }
+
+        xlateMode = XL_ZIP_CRYPT;
+    }
+
+    /**
+     * We do not allow marking
+     */
+    public boolean markSupported() { return false; }
+
+    /**
+     * Closes this input stream and releases any system resources
+     * associated with the stream.
+     * This
+     * method simply performs <code>in.close()</code>.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    public void close() throws IOException {
+        reachedEOF = true;
+        in.close();
+    }
+
+    /**
+     * Reads the next byte of data from this input stream. The value
+     * byte is returned as an <code>int</code> in the range
+     * <code>0</code> to <code>255</code>. If no byte is available
+     * because the end of the stream has been reached, the value
+     * <code>-1</code> is returned. This method blocks until input data
+     * is available, the end of the stream is detected, or an exception
+     * is thrown.
+     * <p>
+     * This method
+     * simply performs <code>in.read()</code> and returns the result.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    public int read() throws IOException
+    {
+        final byte[] b = new byte[1];
+        int retVal = read(b, 0, 1);
+        if (retVal == -1) return -1;
+        return b[0];
+    }
+
+    /**
+     * Reads up to <code>byte.length</code> bytes of data from this
+     * input stream into an array of bytes. This method blocks until some
+     * input is available.
+     * <p>
+     * This method simply performs the call
+     * <code>read(b, 0, b.length)</code> and returns
+     * the  result. It is important that it does
+     * <i>not</i> do <code>in.read(b)</code> instead;
+     * certain subclasses of  <code>FilterInputStream</code>
+     * depend on the implementation strategy actually
+     * used.
+     *
+     * @param      b   the buffer into which the data is read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#read(byte[], int, int)
+     */
+    public int read(byte[] b) throws IOException
+    {
+        return read(b, 0, b.length);
+    }
+
+    /**
+     * Reads up to <code>len</code> bytes of data from this input stream
+     * into an array of bytes. This method blocks until some input is
+     * available.
+     * <p>
+     * This method simply performs <code>in.read(b, off, len)</code>
+     * and returns the result.
+     *
+     * @param      b     the buffer into which the data is read.
+     * @param      off   the start offset of the data.
+     * @param      len   the maximum number of bytes read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        if (reachedEOF) return -1;
+
+        if (xlateMode == XL_PLAIN)
+        {
+            int count = 0;
+            //Write header if appropriate
+            while (headPtr < headSize && len > 0)
+            {
+                b[off++] = head[headPtr++];
+                count++;
+                len--;
+            }
+
+            return (len == 0) ? count : count + in.read(b, off, len);
+        }
+
+        //Decrypt and inflate
+        if (inflater.needsInput() && !decryptChunk())
+        {
+            reachedEOF = true;
+
+            //Read remaining bytes
+            int numRead;
+            try {
+                numRead = inflater.inflate(b, off, len);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+                return -1;
+            }
+
+            if (!inflater.finished())
+            {
+                new Exception("Inflation incomplete").printStackTrace();
+            }
+
+            return numRead == 0 ? -1 : numRead;
+        }
+
+        try {
+            return inflater.inflate(b, off, len);
+        }
+        catch (DataFormatException e)
+        {
+            e.printStackTrace();
+            return -1;
+        }
+    }
+
+
+    /**
+     * Call when inflater indicates that it needs more bytes.
+     * @return - true if we decrypted more bytes to deflate, false if we
+     * encountered the end of stream
+     */
+    protected boolean decryptChunk() throws IOException
+    {
+        while (inflater.needsInput())
+        {
+            int numInBytes = in.read(inBuffer);
+            if (numInBytes == -1) return false;
+//            int numDecryptBytes = cipher.update(inBuffer, 0, numInBytes, decryptBuffer);
+//            inflater.setInput(decryptBuffer, 0, numDecryptBytes);
+inflater.setInput(inBuffer, 0, numInBytes);
+        }
+
+        return true;
+    }
+
+    /**
+     * This method returns 1 if we've not reached EOF, 0 if we have.  Programs
+     * should not rely on this to determine the number of bytes that can be
+     * read without blocking.
+     */
+    public int available() { return reachedEOF ? 0 : 1; }
+
+    /**
+     * Skips bytes by reading them into a cached buffer
+     */
+    public long skip(long n) throws IOException
+    {
+        int skipSize = (int)n;
+        if (skipSize > inBuffer.length) skipSize = inBuffer.length;
+        return read(inBuffer, 0, skipSize);
+    }
+
+}
+
+/*
+ import java.security.KeyPairGenerator;
+  import java.security.KeyPair;
+  import java.security.KeyPairGenerator;
+  import java.security.PrivateKey;
+  import java.security.PublicKey;
+  import java.security.SecureRandom;
+  import java.security.Cipher;
+
+  ....
+
+  java.security.Security.addProvider(new cryptix.provider.Cryptix());
+
+  SecureRandom random = new SecureRandom(SecureRandom.getSeed(30));
+  KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
+  keygen.initialize(1024, random);
+  keypair = keygen.generateKeyPair();
+
+  PublicKey  pubkey  = keypair.getPublic();
+  PrivateKey privkey = keypair.getPrivate();
+ */
+
+/*
+ *
+ *Generate key pairs
+KeyPairGenerator keyGen =
+             KeyPairGenerator.getInstance("DSA");
+KeyGen.initialize(1024, new SecureRandom(userSeed));
+KeyPair pair = KeyGen.generateKeyPair();
+ */
Index: trunk/src/com/kitfox/svg/xml/cpx/CPXOutputStream.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/cpx/CPXOutputStream.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/cpx/CPXOutputStream.java	(revision 4256)
@@ -0,0 +1,174 @@
+/*
+ * CPXOutputStream.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 12:50 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.util.zip.*;
+import java.security.*;
+import javax.crypto.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXOutputStream extends FilterOutputStream implements CPXConsts {
+
+    Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
+
+    /** Creates a new instance of CPXOutputStream */
+    public CPXOutputStream(OutputStream os) throws IOException {
+        super(os);
+
+        //Write magic number
+        os.write(MAGIC_NUMBER);
+    }
+
+    /**
+     * Writes the specified <code>byte</code> to this output stream.
+     * <p>
+     * The <code>write</code> method of <code>FilterOutputStream</code>
+     * calls the <code>write</code> method of its underlying output stream,
+     * that is, it performs <tt>out.write(b)</tt>.
+     * <p>
+     * Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
+     *
+     * @param      b   the <code>byte</code>.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void write(int b) throws IOException {
+        final byte[] buf = new byte[1];
+        buf[0] = (byte)b;
+	write(buf, 0, 1);
+    }
+
+    /**
+     * Writes <code>b.length</code> bytes to this output stream.
+     * <p>
+     * The <code>write</code> method of <code>FilterOutputStream</code>
+     * calls its <code>write</code> method of three arguments with the
+     * arguments <code>b</code>, <code>0</code>, and
+     * <code>b.length</code>.
+     * <p>
+     * Note that this method does not call the one-argument
+     * <code>write</code> method of its underlying stream with the single
+     * argument <code>b</code>.
+     *
+     * @param      b   the data to be written.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterOutputStream#write(byte[], int, int)
+     */
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    byte[] deflateBuffer = new byte[2048];
+
+    /**
+     * Writes <code>len</code> bytes from the specified
+     * <code>byte</code> array starting at offset <code>off</code> to
+     * this output stream.
+     * <p>
+     * The <code>write</code> method of <code>FilterOutputStream</code>
+     * calls the <code>write</code> method of one argument on each
+     * <code>byte</code> to output.
+     * <p>
+     * Note that this method does not call the <code>write</code> method
+     * of its underlying input stream with the same arguments. Subclasses
+     * of <code>FilterOutputStream</code> should provide a more efficient
+     * implementation of this method.
+     *
+     * @param      b     the data.
+     * @param      off   the start offset in the data.
+     * @param      len   the number of bytes to write.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterOutputStream#write(int)
+     */
+    public void write(byte b[], int off, int len) throws IOException
+    {
+        deflater.setInput(b, off, len);
+
+        processAllData();
+        /*
+        int numDeflatedBytes;
+        while ((numDeflatedBytes = deflater.deflate(deflateBuffer)) != 0)
+        {
+//            byte[] cipherBuf = cipher.update(deflateBuffer, 0, numDeflatedBytes);
+//            out.write(cipherBytes);
+out.write(deflateBuffer, 0, numDeflatedBytes);
+        }
+        */
+    }
+
+    protected void processAllData() throws IOException
+    {
+        int numDeflatedBytes;
+        while ((numDeflatedBytes = deflater.deflate(deflateBuffer)) != 0)
+        {
+//            byte[] cipherBuf = cipher.update(deflateBuffer, 0, numDeflatedBytes);
+//            out.write(cipherBytes);
+out.write(deflateBuffer, 0, numDeflatedBytes);
+        }
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be written out to the stream.
+     * <p>
+     * The <code>flush</code> method of <code>FilterOutputStream</code>
+     * calls the <code>flush</code> method of its underlying output stream.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterOutputStream#out
+     */
+    public void flush() throws IOException {
+	out.flush();
+    }
+
+    /**
+     * Closes this output stream and releases any system resources
+     * associated with the stream.
+     * <p>
+     * The <code>close</code> method of <code>FilterOutputStream</code>
+     * calls its <code>flush</code> method, and then calls the
+     * <code>close</code> method of its underlying output stream.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterOutputStream#flush()
+     * @see        java.io.FilterOutputStream#out
+     */
+    public void close() throws IOException {
+        deflater.finish();
+        processAllData();
+
+	try {
+	  flush();
+	} catch (IOException ignored) {
+	}
+	out.close();
+    }
+}
Index: trunk/src/com/kitfox/svg/xml/cpx/CPXTest.java
===================================================================
--- trunk/src/com/kitfox/svg/xml/cpx/CPXTest.java	(revision 4256)
+++ trunk/src/com/kitfox/svg/xml/cpx/CPXTest.java	(revision 4256)
@@ -0,0 +1,100 @@
+/*
+ * CPXTest.java
+ *
+ *
+ *  The Salamander Project - 2D and 3D graphics libraries in Java
+ *  Copyright (C) 2004 Mark McKay
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
+ *  projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 2:45 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXTest {
+
+    /** Creates a new instance of CPXTest */
+    public CPXTest() {
+
+//        FileInputStream fin = new FileInputStream();
+        writeTest();
+        readTest();
+    }
+
+    public void writeTest()
+    {
+        try {
+
+            InputStream is = CPXTest.class.getResourceAsStream("/data/readme.txt");
+//System.err.println("Is " + is);
+
+            FileOutputStream fout = new FileOutputStream("C:\\tmp\\cpxFile.cpx");
+            CPXOutputStream cout = new CPXOutputStream(fout);
+
+            byte[] buffer = new byte[1024];
+            int numBytes;
+            while ((numBytes = is.read(buffer)) != -1)
+            {
+                cout.write(buffer, 0, numBytes);
+            }
+            cout.close();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void readTest()
+    {
+        try {
+
+//            InputStream is = CPXTest.class.getResourceAsStream("/rawdata/test/cpx/text.txt");
+//            InputStream is = CPXTest.class.getResourceAsStream("/rawdata/test/cpx/cpxFile.cpx");
+            FileInputStream is = new FileInputStream("C:\\tmp\\cpxFile.cpx");
+            CPXInputStream cin = new CPXInputStream(is);
+
+            BufferedReader br = new BufferedReader(new InputStreamReader(cin));
+            String line;
+            while ((line = br.readLine()) != null)
+            {
+                System.err.println(line);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * @param args the command line arguments
+     */
+    public static void main(String[] args) {
+        new CPXTest();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 4255)
+++ trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 4256)
@@ -3,4 +3,8 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
+
+import com.kitfox.svg.SVGDiagram;
+import com.kitfox.svg.SVGException;
+import com.kitfox.svg.SVGUniverse;
 
 import java.awt.Component;
@@ -20,4 +24,5 @@
 import java.io.InputStream;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.util.Arrays;
@@ -42,4 +47,6 @@
 public class ImageProvider {
 
+    private static SVGUniverse svgUniverse;
+
     /**
      * Position of an overlay icon
@@ -48,4 +55,8 @@
     public static enum OverlayPosition {
         NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST
+    }
+
+    public static enum ImageType {
+        PNG, SVG
     }
 
@@ -71,6 +82,6 @@
      * Return an image from the specified location.
      *
-     * @param subdir The position of the directory, e.g. "layer"
-     * @param name The icons name (without the ending of ".png")
+     * @param subdir The position of the directory, e.g. 'layer'
+     * @param name The icons name (with or without '.png' or '.svg' extension)
      * @return The requested Image.
      */
@@ -86,4 +97,11 @@
     }
 
+    /**
+     * Shortcut for get("", name);
+     */
+    public static ImageIcon get(String name) {
+        return get("", name);
+    }
+
     public static ImageIcon getIfAvailable(String subdir, String name) {
         return getIfAvailable((Collection<String>) null, null, subdir, name);
@@ -111,9 +129,10 @@
      * or something like
      *   dirs.get(i)+"/"+subdir+"/"+name+".png".
-     * @param dirs      Directories to look.
+     * @param dirs      Directories to look (may be null).
      * @param id        An id used for caching. Id is not used for cache if name starts with http://. (URL is unique anyway.)
      * @param subdir    Subdirectory the image lies in.
-     * @param name      The name of the image. If it contains no '.', a png extension is added.
-     * @param archive   A zip file where the image is located.
+     * @param name      The name of the image. If it does not end with '.png' or '.svg',
+     *                  it will try both extensions.
+     * @param archive   A zip file where the image is located (may be null).
      * @param sanitize  If the image should be repainted to a new BufferedImage to work
      *                  around certain issues.
@@ -133,7 +152,17 @@
         if (name == null)
             return null;
+        ImageType type = name.toLowerCase().endsWith("svg") ? ImageType.SVG : ImageType.PNG;
+
         if (name.startsWith("http://")) {
-            return getIfAvailableHttp(name);
-        }
+            String url = name;
+            ImageWrapper iw = cache.get(url);
+            if (iw != null) return iw;
+            iw = getIfAvailableHttp(url, type);
+            if (iw != null) {
+                cache.put(url, iw);
+            }
+            return iw;
+        }
+
         if (subdir == null) {
             subdir = "";
@@ -141,20 +170,38 @@
             subdir += "/";
         }
-        String ext = name.indexOf('.') != -1 ? "" : ".png";
-        String full_name = subdir + name + ext;
-        String cache_name = full_name;
-        /* cache separately */
-        if (dirs != null && dirs.size() > 0) {
-            cache_name = "id:" + id + ":" + full_name;
-            if(archive != null) {
-                cache_name += ":" + archive.getName();
-            }
-        }
-
-        ImageWrapper iw = cache.get(cache_name);
-        if (iw == null) {
+        String[] extensions;
+        if (name.toLowerCase().endsWith(".png") | name.toLowerCase().endsWith(".svg")) {
+            extensions = new String[] { "" };
+        } else {
+            extensions = new String[] { ".png", ".svg"};
+        }
+        for (String ext : extensions) {
+            if (".svg".equals(ext)) {
+                type = ImageType.SVG;
+            } else if (".png".equals(ext)) {
+                type = ImageType.PNG;
+            }
+            
+            String full_name = subdir + name + ext;
+            String cache_name = full_name;
+            /* cache separately */
+            if (dirs != null && dirs.size() > 0) {
+                cache_name = "id:" + id + ":" + full_name;
+                if(archive != null) {
+                    cache_name += ":" + archive.getName();
+                }
+            }
+
+            ImageWrapper iw = cache.get(cache_name);
+            if (iw != null) return iw;
+
             if (archive != null) {
-                iw = getIfAvailableZip(full_name, archive);
-            }
+                iw = getIfAvailableZip(full_name, archive, type);
+                if (iw != null) {
+                    cache.put(cache_name, iw);
+                    return iw;
+                }
+            }
+
             // getImageUrl() does a ton of "stat()" calls and gets expensive
             // and redundant when you have a whole ton of objects. So,
@@ -162,33 +209,36 @@
             // and don't bother to create a URL unless we're actually
             // creating the image.
-            if (iw == null) {
-                URL path = getImageUrl(full_name, dirs);
-                if (path == null)
-                    return null;
-                Image img = Toolkit.getDefaultToolkit().createImage(path);
-                iw = new ImageWrapper(img, false);
-            }
-            cache.put(cache_name, iw);
-        }
-
-        return iw;
-    }
-
-    private static ImageWrapper getIfAvailableHttp(String url) {
-        ImageWrapper iw = cache.get(url);
-        if (iw == null) {
-            try {
-                MirroredInputStream is = new MirroredInputStream(url, new File(Main.pref.getPreferencesDir(),
-                "images").toString());
-                Image img = Toolkit.getDefaultToolkit().createImage(is.getFile().toURI().toURL());
-                iw = new ImageWrapper(img, false);
-                cache.put(url, iw);
-            } catch (IOException e) {
-            }
-        }
-        return iw;
-    }
-
-    private static ImageWrapper getIfAvailableZip(String full_name, File archive) {
+            URL path = getImageUrl(full_name, dirs);
+            if (path == null)
+                continue;
+            iw = getIfAvailableLocalURL(path, type);
+            if (iw != null) {
+                cache.put(cache_name, iw);
+                return iw;
+            }
+        }
+        return null;
+    }
+
+    private static ImageWrapper getIfAvailableHttp(String url, ImageType type) {
+        Image img = null;
+        try {
+            MirroredInputStream is = new MirroredInputStream(url,
+                    new File(Main.pref.getPreferencesDir(), "images").toString());
+            switch (type) {
+                case PNG:
+                    img = Toolkit.getDefaultToolkit().createImage(is.getFile().toURI().toURL());
+                    break;
+                case SVG:
+                    URI uri = getSvgUniverse().loadSVG(is, is.getFile().toURI().toURL().toString());
+                    img = createImageFromSvgUri(uri);
+                    break;
+            }
+        } catch (IOException e) {
+        }
+        return img == null ? null : new ImageWrapper(img, false);
+    }
+
+    private static ImageWrapper getIfAvailableZip(String full_name, File archive, ImageType type) {
         ZipFile zipFile = null;
         Image img = null;
@@ -205,11 +255,19 @@
                 try {
                     is = zipFile.getInputStream(entry);
-                    while(size > 0)
-                    {
-                        int l = is.read(buf, offs, size);
-                        offs += l;
-                        size -= l;
+                    switch (type) {
+                        case PNG:
+                            while(size > 0)
+                            {
+                                int l = is.read(buf, offs, size);
+                                offs += l;
+                                size -= l;
+                            }
+                            img = Toolkit.getDefaultToolkit().createImage(buf);
+                            break;
+                        case SVG:
+                            URI uri = getSvgUniverse().loadSVG(is, full_name);
+                            img = createImageFromSvgUri(uri);
+                            break;
                     }
-                    img = Toolkit.getDefaultToolkit().createImage(buf);
                 } finally {
                     if (is != null) {
@@ -231,6 +289,20 @@
     }
 
+    private static ImageWrapper getIfAvailableLocalURL(URL path, ImageType type) {
+        Image img = null;
+        switch (type) {
+            case PNG:
+                img = Toolkit.getDefaultToolkit().createImage(path);
+                break;
+            case SVG:
+                URI uri = getSvgUniverse().loadSVG(path);
+                img = createImageFromSvgUri(uri);
+                break;
+        }
+        return img == null ? null : new ImageWrapper(img, false);
+    }
+
     private static URL getImageUrl(String path, String name) {
-        if (path.startsWith("resource://")) {
+        if (path != null && path.startsWith("resource://")) {
             String p = path.substring("resource://".length());
             for (ClassLoader source : PluginHandler.getResourceClassLoaders()) {
@@ -280,4 +352,9 @@
         }
 
+        // Absolute path?
+        u = getImageUrl(null, imageName);
+        if (u != null)
+            return u;
+
         // Try plugins and josm classloader
         u = getImageUrl("resource://images/", imageName);
@@ -294,12 +371,6 @@
                 return u;
         }
+
         return null;
-    }
-
-    /**
-     * Shortcut for get("", name);
-     */
-    public static ImageIcon get(String name) {
-        return get("", name);
     }
 
@@ -438,3 +509,26 @@
         return result;
     }
+
+    private static Image createImageFromSvgUri(URI uri) {
+        SVGDiagram dia = getSvgUniverse().getDiagram(uri);
+        int w = (int)dia.getWidth();
+        int h = (int)dia.getHeight();
+        Image img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = ((BufferedImage) img).createGraphics();
+        g.setClip(0, 0, w, h);
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        try {
+            dia.render(g);
+        } catch (SVGException ex) {
+            return null;
+        }
+        return img;
+    }
+
+    private static SVGUniverse getSvgUniverse() {
+        if (svgUniverse == null) {
+            svgUniverse = new SVGUniverse();
+        }
+        return svgUniverse;
+    }
 }
