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 6002)
@@ -1,40 +1,71 @@
 /*
- * SVGElement.java
+ * SVG Salamander
+ * Copyright (c) 2004, Mark McKay
+ * All rights reserved.
  *
+ * Redistribution and use in source and binary forms, with or 
+ * without modification, are permitted provided that the following
+ * conditions are met:
  *
- *  The Salamander Project - 2D and 3D graphics libraries in Java
- *  Copyright (C) 2004 Mark McKay
+ *   - Redistributions of source code must retain the above 
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer.
+ *   - Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials 
+ *     provided with the distribution.
  *
- *  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
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE. 
+ * 
+ * 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 com.kitfox.svg.pathcmd.Arc;
+import com.kitfox.svg.pathcmd.BuildHistory;
+import com.kitfox.svg.pathcmd.Cubic;
+import com.kitfox.svg.pathcmd.CubicSmooth;
+import com.kitfox.svg.pathcmd.Horizontal;
+import com.kitfox.svg.pathcmd.LineTo;
+import com.kitfox.svg.pathcmd.MoveTo;
+import com.kitfox.svg.pathcmd.PathCommand;
+import com.kitfox.svg.pathcmd.Quadratic;
+import com.kitfox.svg.pathcmd.QuadraticSmooth;
+import com.kitfox.svg.pathcmd.Terminal;
+import com.kitfox.svg.pathcmd.Vertical;
+import com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.StyleSheet;
+import com.kitfox.svg.xml.XMLParseUtil;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
 import java.io.Serializable;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
 
 /**
@@ -44,29 +75,23 @@
 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.
+     * 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
+    /**
+     * 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
@@ -74,4 +99,5 @@
      */
     protected static final Set ignorePresAttrib;
+
     static
     {
@@ -81,40 +107,32 @@
 //        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
+    /**
+     * 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 */
+    /**
+     * 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)
     {
@@ -123,15 +141,17 @@
         this.parent = parent;
     }
-    
+
+    abstract public String getTagName();
+
     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
@@ -139,6 +159,9 @@
     public List getPath(List retVec)
     {
-        if (retVec == null) retVec = new ArrayList();
-        
+        if (retVec == null)
+        {
+            retVec = new ArrayList();
+        }
+
         if (parent != null)
         {
@@ -146,10 +169,10 @@
         }
         retVec.add(this);
-        
+
         return retVec;
     }
-    
-    /**
-     * @param retVec - A list to add all children to.  If null, a new list is
+
+    /**
+     * @param retVec - A list to add all children to. If null, a new list is
      * created and children of this group are added.
      *
@@ -158,11 +181,14 @@
     public List getChildren(List retVec)
     {
-        if (retVec == null) retVec = new ArrayList();
-        
+        if (retVec == null)
+        {
+            retVec = new ArrayList();
+        }
+
         retVec.addAll(children);
-        
+
         return retVec;
     }
-    
+
     /**
      * @param id - Id of svg element to return
@@ -173,14 +199,17 @@
         for (Iterator it = children.iterator(); it.hasNext();)
         {
-            SVGElement ele = (SVGElement)it.next();
+            SVGElement ele = (SVGElement) it.next();
             String eleId = ele.getId();
-            if (eleId != null && eleId.equals(id)) return ele;
-        }
-        
+            if (eleId != null && eleId.equals(id))
+            {
+                return ele;
+            }
+        }
+
         return null;
     }
-    
-    /**
-     * Searches children for given element.  If found, returns index of child.
+
+    /**
+     * Searches children for given element. If found, returns index of child.
      * Otherwise returns -1.
      */
@@ -189,7 +218,8 @@
         return children.indexOf(child);
     }
-    
+
     /**
      * Swaps 2 elements in children.
+     *
      * @i index of first
      * @j index of second
@@ -203,5 +233,5 @@
             return;
         }
-        
+
         Object temp = children.get(i);
         children.set(i, children.get(j));
@@ -209,11 +239,12 @@
         build();
     }
-    
-    /**
-     * Called during SAX load process to notify that this tag has begun the process
-     * of being loaded
+
+    /**
+     * 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.
+     * @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
@@ -222,5 +253,5 @@
         this.parent = parent;
         this.diagram = helper.diagram;
-        
+
         this.id = attrs.getValue("id");
         if (this.id != null && !this.id.equals(""))
@@ -228,10 +259,10 @@
             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");
@@ -240,5 +271,5 @@
             HashMap map = XMLParseUtil.parseStyle(style, inlineStyles);
         }
-        
+
         String base = attrs.getValue("xml:base");
         if (base != null && !base.equals(""))
@@ -247,11 +278,10 @@
             {
                 xmlBase = new URI(base);
-            }
-            catch (Exception e)
+            } catch (Exception e)
             {
                 throw new SAXException(e);
             }
         }
-        
+
         //Place all other attributes into the presentation attribute list
         int numAttrs = attrs.getLength();
@@ -259,11 +289,14 @@
         {
             String name = attrs.getQName(i);
-            if (ignorePresAttrib.contains(name)) continue;
+            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
@@ -273,5 +306,5 @@
         return inlineStyles.keySet();
     }
-    
+
     /**
      * @return a set of Strings that corespond to XML attributes on this element
@@ -281,5 +314,5 @@
         return presAttribs.keySet();
     }
-    
+
     /**
      * Called after the start element but before the end element to indicate
@@ -292,6 +325,6 @@
         child.setDiagram(diagram);
     }
-    
-    private void setDiagram(SVGDiagram diagram)
+
+    protected void setDiagram(SVGDiagram diagram)
     {
         this.diagram = diagram;
@@ -299,9 +332,9 @@
         for (Iterator it = children.iterator(); it.hasNext();)
         {
-            SVGElement ele = (SVGElement)it.next();
+            SVGElement ele = (SVGElement) it.next();
             ele.setDiagram(diagram);
         }
     }
-    
+
     public void removeChild(SVGElement child) throws SVGElementException
     {
@@ -310,8 +343,8 @@
             throw new SVGElementException(this, "Element does not contain child " + child);
         }
-        
+
         children.remove(child);
     }
-    
+
     /**
      * Called during load process to add text scanned within a tag
@@ -320,29 +353,29 @@
     {
     }
-    
-    /**
-     * Called to indicate that this tag and the tags it contains have been completely
-     * processed, and that it should finish any load processes.
+
+    /**
+     * 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.
+//        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")))
         {
@@ -355,27 +388,38 @@
             }
         }
-        if (getPres(sty.setName("class"))) cssClass = sty.getStringValue();
-        if (getPres(sty.setName("xml:base"))) xmlBase = sty.getURIValue();
-    }
-    
+        if (getPres(sty.setName("class")))
+        {
+            cssClass = sty.getStringValue();
+        }
+        if (getPres(sty.setName("xml:base")))
+        {
+            xmlBase = sty.getURIValue();
+        }
+
+        //Build children
+        for (int i = 0; i < children.size(); ++i)
+        {
+            SVGElement ele = (SVGElement) children.get(i);
+            ele.build();
+        }
+    }
+
     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.
+        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; 
-    }
-    
-    
+        return id;
+    }
     LinkedList contexts = new LinkedList();
-    
-    /**
-     * Hack to allow nodes to temporarily change their parents.  The Use tag will
+
+    /**
+     * 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.
      */
@@ -387,12 +431,17 @@
     protected SVGElement popParentContext()
     {
-        return (SVGElement)contexts.removeLast();
-    }
-    
+        return (SVGElement) contexts.removeLast();
+    }
+
     protected SVGElement getParentContext()
     {
-        return contexts.isEmpty() ? null : (SVGElement)contexts.getLast();
-    }
-    
+        return contexts.isEmpty() ? null : (SVGElement) contexts.getLast();
+    }
+
+    public SVGRoot getRoot()
+    {
+        return parent == null ? null : parent.getRoot();
+    }
+
     /*
      * Returns the named style attribute.  Checks for inline styles first, then
@@ -408,65 +457,86 @@
         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.
+
+    /**
+     * 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 back 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
-        
+        if (styAttr != null)
+        {
+            return true;
+        }
+
+
         //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 (presAttr != null)
+        {
+            return true;
+        }
+
+        //Check for style sheet
+        SVGRoot root = getRoot();
+        if (root != null)
+        {
+            StyleSheet ss = root.getStyleSheet();
+            if (ss != null)
+            {
+                return ss.getStyle(attrib, getTagName(), cssClass);
+            }
+        }
+
         //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);
-        }
-        
+            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.
+
+    /**
+     * @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);
-    }
-    
+        return (StyleAttribute) inlineStyles.get(styName);
+    }
+
     /**
      * Copies the presentation attribute into the passed one.
+     *
      * @return - True if attribute was read successfully
      */
@@ -474,20 +544,23 @@
     {
         String presName = attrib.getName();
-        
+
         //Make sure we have a coresponding presentation attribute
-        StyleAttribute presAttr = (StyleAttribute)presAttribs.get(presName);
-        
+        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;
-        
+        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 
+
+    /**
+     * @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
      */
@@ -495,13 +568,13 @@
     {
         //Check for local inline styles
-        return (StyleAttribute)presAttribs.get(styName);
-    }
-    
+        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())
@@ -509,14 +582,14 @@
             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())
@@ -525,7 +598,7 @@
             return retXform;
         }
-        
+
         String function = matchWord.group().toLowerCase();
-        
+
         LinkedList termList = new LinkedList();
         while (matchWord.find())
@@ -533,6 +606,6 @@
             termList.add(matchWord.group());
         }
-        
-        
+
+
         double[] terms = new double[termList.size()];
         Iterator it = termList.iterator();
@@ -540,56 +613,62 @@
         while (it.hasNext())
         {
-            terms[count++] = XMLParseUtil.parseDouble((String)it.next());
-        }
-        
+            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"))
+        } else if (function.equals("translate"))
+        {
+            if (terms.length == 1)
+            {
+                retXform.setToTranslation(terms[0], 0);
+            } else
+            {
+                retXform.setToTranslation(terms[0], terms[1]);
+            }
+        } else if (function.equals("scale"))
         {
             if (terms.length > 1)
+            {
                 retXform.setToScale(terms[0], terms[1]);
-            else
+            } else
+            {
                 retXform.setToScale(terms[0], terms[0]);
-        }
-        else if (function.equals("rotate"))
+            }
+        } else if (function.equals("rotate"))
         {
             if (terms.length > 2)
+            {
                 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]);
-            else
+            } else
+            {
                 retXform.setToRotation(Math.toRadians(terms[0]));
-        }
-        else if (function.equals("skewx"))
+            }
+        } else if (function.equals("skewx"))
         {
             retXform.setToShear(Math.toRadians(terms[0]), 0.0);
-        }
-        else if (function.equals("skewy"))
+        } else if (function.equals("skewy"))
         {
             retXform.setToShear(0.0, Math.toRadians(terms[0]));
-        }
-        else
+        } else
         {
             throw new SVGException("Unknown transform type");
         }
-        
+
         return retXform;
     }
-    
+
     static protected float nextFloat(LinkedList l)
     {
-        String s = (String)l.removeFirst();
+        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();
@@ -598,6 +677,6 @@
             tokens.addLast(matchPathCmd.group());
         }
-        
-        
+
+
         boolean defaultRelative = false;
         LinkedList cmdList = new LinkedList();
@@ -605,17 +684,16 @@
         while (tokens.size() != 0)
         {
-            String curToken = (String)tokens.removeFirst();
+            String curToken = (String) tokens.removeFirst();
             char initChar = curToken.charAt(0);
-            if ((initChar >= 'A' && initChar <='Z') || (initChar >= 'a' && initChar <='z'))
+            if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z'))
             {
                 curCmd = initChar;
-            }
-            else
+            } else
             {
                 tokens.addFirst(curToken);
             }
-            
+
             PathCommand cmd = null;
-            
+
             switch (curCmd)
             {
@@ -648,21 +726,21 @@
                 case 'A':
                     cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens),
-                            nextFloat(tokens),
-                            nextFloat(tokens) == 1f, nextFloat(tokens) == 1f,
-                            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));
+                        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));
+                        nextFloat(tokens), nextFloat(tokens));
                     break;
                 case 'q':
                     cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens),
-                            nextFloat(tokens), nextFloat(tokens));
+                        nextFloat(tokens), nextFloat(tokens));
                     break;
                 case 'T':
@@ -674,19 +752,19 @@
                 case 'C':
                     cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens),
-                            nextFloat(tokens), nextFloat(tokens),
-                            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));
+                        nextFloat(tokens), nextFloat(tokens),
+                        nextFloat(tokens), nextFloat(tokens));
                     break;
                 case 'S':
                     cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens),
-                            nextFloat(tokens), nextFloat(tokens));
+                        nextFloat(tokens), nextFloat(tokens));
                     break;
                 case 's':
                     cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens),
-                            nextFloat(tokens), nextFloat(tokens));
+                        nextFloat(tokens), nextFloat(tokens));
                     break;
                 case 'Z':
@@ -697,18 +775,18 @@
                     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++)
@@ -716,10 +794,10 @@
             numKnots += commands[i].getNumKnotsAdded();
         }
-        
-        
+
+
         GeneralPath path = new GeneralPath(windingRule, numKnots);
-        
+
         BuildHistory hist = new BuildHistory();
-        
+
         for (int i = 0; i < commands.length; i++)
         {
@@ -727,13 +805,12 @@
             cmd.appendPath(path, hist);
         }
-        
+
         return path;
     }
-    
-    
-    
-    /**
-     * Updates all attributes in this diagram associated with a time event.
-     * Ie, all attributes with track information.
+
+    /**
+     * 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
@@ -748,6 +825,10 @@
     public SVGElement getChild(int i)
     {
-        return (SVGElement)children.get(i);
-    }
-    
+        return (SVGElement) children.get(i);
+    }
+
+    public double lerp(double t0, double t1, double alpha)
+    {
+        return (1 - alpha) * t0 + alpha * t1;
+    }
 }
