summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkitfox2007-05-30 01:33:23 +0200
committerkitfox2007-05-30 01:33:23 +0200
commit091a1e0179cb264cc2cab6e3b11ea31045c8536d (patch)
treebd561534d16012c9d398acf32398968e14a19b85
parentreverting to original source tree (diff)
downloadsvg-salamander-core-091a1e0179cb264cc2cab6e3b11ea31045c8536d.tar.gz
svg-salamander-core-091a1e0179cb264cc2cab6e3b11ea31045c8536d.tar.xz
svg-salamander-core-091a1e0179cb264cc2cab6e3b11ea31045c8536d.zip
Restoring SVG Salamander to it's original code base, and updating build scripts.
git-svn-id: https://svn.java.net/svn/svgsalamander~svn/trunk/svg-core@36 7dc7fa77-23fb-e6ad-8e2e-c86bd48ed22b
-rwxr-xr-xbuild.xml53
-rw-r--r--lib/library/ant.jarbin0 -> 999966 bytes
-rwxr-xr-xmanifest.mf2
-rw-r--r--src/main/java/com/kitfox/svg/Circle.java175
-rw-r--r--src/main/java/com/kitfox/svg/ClipPath.java159
-rw-r--r--src/main/java/com/kitfox/svg/Defs.java71
-rw-r--r--src/main/java/com/kitfox/svg/Desc.java58
-rw-r--r--src/main/java/com/kitfox/svg/Ellipse.java191
-rw-r--r--src/main/java/com/kitfox/svg/FeDistantLight.java100
-rw-r--r--src/main/java/com/kitfox/svg/FeLight.java50
-rw-r--r--src/main/java/com/kitfox/svg/FePointLight.java114
-rw-r--r--src/main/java/com/kitfox/svg/FeSpotLight.java177
-rw-r--r--src/main/java/com/kitfox/svg/FillElement.java51
-rw-r--r--src/main/java/com/kitfox/svg/Filter.java229
-rw-r--r--src/main/java/com/kitfox/svg/FilterEffects.java233
-rw-r--r--src/main/java/com/kitfox/svg/Font.java249
-rw-r--r--src/main/java/com/kitfox/svg/FontFace.java215
-rw-r--r--src/main/java/com/kitfox/svg/Glyph.java102
-rw-r--r--src/main/java/com/kitfox/svg/Gradient.java282
-rw-r--r--src/main/java/com/kitfox/svg/Group.java275
-rw-r--r--src/main/java/com/kitfox/svg/ImageSVG.java299
-rw-r--r--src/main/java/com/kitfox/svg/Line.java172
-rw-r--r--src/main/java/com/kitfox/svg/LinearGradient.java210
-rw-r--r--src/main/java/com/kitfox/svg/Metadata.java46
-rw-r--r--src/main/java/com/kitfox/svg/MissingGlyph.java252
-rw-r--r--src/main/java/com/kitfox/svg/Path.java153
-rw-r--r--src/main/java/com/kitfox/svg/PatternSVG.java303
-rw-r--r--src/main/java/com/kitfox/svg/Polygon.java177
-rw-r--r--src/main/java/com/kitfox/svg/Polyline.java155
-rw-r--r--src/main/java/com/kitfox/svg/RadialGradient.java222
-rw-r--r--src/main/java/com/kitfox/svg/Rect.java267
-rw-r--r--src/main/java/com/kitfox/svg/RenderableElement.java161
-rw-r--r--src/main/java/com/kitfox/svg/SVGCache.java28
-rw-r--r--src/main/java/com/kitfox/svg/SVGDiagram.java217
-rw-r--r--src/main/java/com/kitfox/svg/SVGDisplayPanel.form16
-rw-r--r--src/main/java/com/kitfox/svg/SVGDisplayPanel.java194
-rw-r--r--src/main/java/com/kitfox/svg/SVGElement.java853
-rw-r--r--src/main/java/com/kitfox/svg/SVGElementException.java56
-rw-r--r--src/main/java/com/kitfox/svg/SVGException.java47
-rw-r--r--src/main/java/com/kitfox/svg/SVGLoader.java267
-rw-r--r--src/main/java/com/kitfox/svg/SVGLoaderHelper.java79
-rw-r--r--src/main/java/com/kitfox/svg/SVGParseException.java47
-rw-r--r--src/main/java/com/kitfox/svg/SVGRoot.java390
-rw-r--r--src/main/java/com/kitfox/svg/SVGUniverse.java533
-rw-r--r--src/main/java/com/kitfox/svg/ShapeElement.java299
-rw-r--r--src/main/java/com/kitfox/svg/Stop.java129
-rw-r--r--src/main/java/com/kitfox/svg/Style.java83
-rw-r--r--src/main/java/com/kitfox/svg/Symbol.java158
-rw-r--r--src/main/java/com/kitfox/svg/Text.java539
-rw-r--r--src/main/java/com/kitfox/svg/Title.java65
-rw-r--r--src/main/java/com/kitfox/svg/TransformableElement.java117
-rw-r--r--src/main/java/com/kitfox/svg/Tspan.java380
-rw-r--r--src/main/java/com/kitfox/svg/Use.java255
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimTimeParser.jjt316
-rw-r--r--src/main/java/com/kitfox/svg/animation/Animate.java354
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateBase.java92
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateColor.java81
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateColorIface.java38
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateMotion.java234
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateTransform.java225
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimateXform.java52
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimationElement.java336
-rw-r--r--src/main/java/com/kitfox/svg/animation/AnimationTimeEval.java65
-rw-r--r--src/main/java/com/kitfox/svg/animation/Bezier.java201
-rw-r--r--src/main/java/com/kitfox/svg/animation/SetSmil.java55
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeBase.java99
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeCompound.java96
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeDiscrete.java51
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeIndefinite.java48
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeLookup.java75
-rw-r--r--src/main/java/com/kitfox/svg/animation/TimeSum.java60
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackBase.java104
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackColor.java95
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackDouble.java119
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackManager.java153
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackMotion.java128
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackPath.java100
-rw-r--r--src/main/java/com/kitfox/svg/animation/TrackTransform.java112
-rw-r--r--src/main/java/com/kitfox/svg/app/MainFrame.form65
-rw-r--r--src/main/java/com/kitfox/svg/app/MainFrame.java134
-rw-r--r--src/main/java/com/kitfox/svg/app/PlayerDialog.form133
-rw-r--r--src/main/java/com/kitfox/svg/app/PlayerDialog.java289
-rw-r--r--src/main/java/com/kitfox/svg/app/PlayerThread.java129
-rw-r--r--src/main/java/com/kitfox/svg/app/PlayerThreadListener.java37
-rw-r--r--src/main/java/com/kitfox/svg/app/SVGPlayer.form118
-rw-r--r--src/main/java/com/kitfox/svg/app/SVGPlayer.java426
-rw-r--r--src/main/java/com/kitfox/svg/app/SVGViewer.form118
-rw-r--r--src/main/java/com/kitfox/svg/app/SVGViewer.java421
-rw-r--r--src/main/java/com/kitfox/svg/app/VersionDialog.form64
-rw-r--r--src/main/java/com/kitfox/svg/app/VersionDialog.java151
-rw-r--r--src/main/java/com/kitfox/svg/app/ant/SVGToImageAntTask.java251
-rw-r--r--src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.form29
-rw-r--r--src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.java91
-rw-r--r--src/main/java/com/kitfox/svg/app/beans/SVGIcon.java385
-rw-r--r--src/main/java/com/kitfox/svg/app/beans/SVGPanel.form13
-rw-r--r--src/main/java/com/kitfox/svg/app/beans/SVGPanel.java243
-rw-r--r--src/main/java/com/kitfox/svg/batik/GraphicsUtil.java382
-rw-r--r--src/main/java/com/kitfox/svg/batik/LinearGradientPaint.java354
-rw-r--r--src/main/java/com/kitfox/svg/batik/LinearGradientPaintContext.java529
-rw-r--r--src/main/java/com/kitfox/svg/batik/MultipleGradientPaint.java236
-rw-r--r--src/main/java/com/kitfox/svg/batik/MultipleGradientPaintContext.java1421
-rw-r--r--src/main/java/com/kitfox/svg/batik/RadialGradientPaint.java491
-rw-r--r--src/main/java/com/kitfox/svg/batik/RadialGradientPaintContext.java775
-rw-r--r--src/main/java/com/kitfox/svg/composite/AdobeComposite.java70
-rw-r--r--src/main/java/com/kitfox/svg/composite/AdobeCompositeContext.java97
-rw-r--r--src/main/java/com/kitfox/svg/package-info.java9
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Arc.java245
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/BuildHistory.java62
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Cubic.java74
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/CubicSmooth.java78
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Horizontal.java65
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/LineTo.java67
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/MoveTo.java66
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/PathCommand.java56
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/PathUtil.java72
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Quadratic.java70
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/QuadraticSmooth.java74
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Terminal.java56
-rw-r--r--src/main/java/com/kitfox/svg/pathcmd/Vertical.java64
-rw-r--r--src/main/java/com/kitfox/svg/pattern/PatternPaint.java60
-rw-r--r--src/main/java/com/kitfox/svg/pattern/PatternPaintContext.java120
-rw-r--r--src/main/java/com/kitfox/svg/xml/ColorTable.java273
-rw-r--r--src/main/java/com/kitfox/svg/xml/NumberWithUnits.java89
-rw-r--r--src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java48
-rw-r--r--src/main/java/com/kitfox/svg/xml/StyleAttribute.java290
-rw-r--r--src/main/java/com/kitfox/svg/xml/WritableXMLElement.java48
-rw-r--r--src/main/java/com/kitfox/svg/xml/XMLParseUtil.java806
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java40
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java293
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java174
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java100
-rw-r--r--src/main/java/xml/formControls.js27
-rw-r--r--src/main/java/xml/postXform.html13
-rw-r--r--src/main/java/xml/postXform.svg142
-rw-r--r--src/main/java/xml/sampleForm.xml41
-rw-r--r--src/main/res/example/duke.svg8
-rw-r--r--src/main/res/res/help/about/about.html19
-rw-r--r--src/main/res/res/icons/SVGUniverseIcon_16c.pngbin0 -> 440 bytes
-rw-r--r--src/main/res/res/icons/SVGUniverseIcon_32c.pngbin0 -> 703 bytes
-rw-r--r--src/main/res/res/icons/cursor.svg9
-rw-r--r--src/main/res/xml/about.xml3
-rw-r--r--src/main/res/xml/about.xsl50
142 files changed, 24387 insertions, 20 deletions
diff --git a/build.xml b/build.xml
index 467416c..17e83f7 100755
--- a/build.xml
+++ b/build.xml
@@ -29,14 +29,18 @@
<property name="version" value="1"/>
<property name="project.jar" location="${jar.dir}/${project.name}.jar"/>
- <property name="project.debug.mainClass" value="com.kitfox.Main"/>
+ <property name="project.debug.mainClass" value="com.kitfox.svg.app.MainFrame"/>
<property name="project.debug.jvm" location="C:\bin\jdk1.6.0\fastdebug/bin/java"/>
- <property name="project.run.mainClass" value="com.kitfox.Main"/>
+ <property name="project.run.mainClass" value="com.kitfox.svg.app.MainFrame"/>
<property name="project.run.jvm" location="C:\bin\jdk1.6.0\fastdebug/bin/java"/>
<property name="javadoc.dir" location="${build.dir}/javadoc"/>
<property name="javadoc.zip" location="${www.localDir}/doc/javadoc.zip"/>
+ <property name="javacc.home" location="lib/library"/>
+ <property name="animtime.jjtree" location="${src.main.dir}/com/kitfox/svg/animation/AnimTimeParser.jjt"/>
+ <property name="animtime.outdir" location="${gen.main.src.dir}/com/kitfox/svg/animation/parser"/>
+
<path id="lib.include">
<pathelement path="${netbeans.home}/modules/org-jdesktop-layout.jar"/>
<fileset dir="lib/include">
@@ -52,8 +56,9 @@
</path>
<path id="path.sourcepath">
+ <pathelement path="${gen.main.src.dir}"/>
<pathelement path="${src.main.dir}"/>
- <pathelement path="${src.test.dir}"/>
+ <!--pathelement path="${src.test.dir}"/-->
</path>
<path id="path.sourcepath.debug">
@@ -79,9 +84,18 @@
<mkdir dir="${gen.main.res.dir}"/>
</target>
- <target name="compile" depends="init,prepareBuild" description="Compile source">
- <javac debug="true" destdir="${classes.main.dir}" srcdir="${src.main.dir}">
+ <target name="genData" depends="init,prepareBuild" description="Generate parsers from javacc files.">
+ <mkdir dir="${animtime.outdir}"/>
+ <!--jjtree target="${animtime.jjtree}" javacchome="${javacc.home}" outputdirectory="${animtime.outdir}" outputfile="${animtime.outfile}"/-->
+ <jjtree target="${animtime.jjtree}" javacchome="${javacc.home}" outputdirectory="${animtime.outdir}"/>
+ <javacc target="${animtime.outdir}/AnimTimeParser.jj" javacchome="${javacc.home}" outputdirectory="${animtime.outdir}"/>
+ </target>
+
+ <target name="compile" depends="init,prepareBuild,genData" description="Compile source">
+ <javac debug="true" destdir="${classes.main.dir}">
<compilerarg value="-Xlint:all,-unchecked,-serial"/>
+ <src path="${src.main.dir}"/>
+ <src path="${gen.main.src.dir}"/>
<classpath>
<path refid="path.classpath"/>
</classpath>
@@ -117,20 +131,21 @@
</target>
<target name="jar" depends="init,compile">
- <manifest file="${manifest.file}">
- <attribute name="Main-Class" value="${project.run.mainClass}"/>
- <attribute name="Built-By" value="${user.name}"/>
- <section name="common">
- <attribute name="Specification-Title" value="${project.title}"/>
- <attribute name="Specification-Version" value="${version}"/>
- <attribute name="Specification-Vendor" value="Kitfox"/>
- <attribute name="Implementation-Title" value="${project.name}"/>
- <attribute name="Implementation-Version" value="Version ${version}, Date ${TODAY}"/>
- <attribute name="Implementation-Vendor" value="Mark McKay, mark@kitfox.com"/>
- </section>
- </manifest>
-
- <jar compress="true" jarfile="${project.jar}" manifest="${manifest.file}">
+
+ <jar compress="true" jarfile="${project.jar}">
+ <manifest>
+ <attribute name="Main-Class" value="${project.run.mainClass}"/>
+ <attribute name="Built-By" value="${user.name}"/>
+ <section name="common">
+ <attribute name="Specification-Title" value="${project.title}"/>
+ <attribute name="Specification-Version" value="${version}"/>
+ <attribute name="Specification-Vendor" value="Kitfox"/>
+ <attribute name="Implementation-Title" value="${project.name}"/>
+ <attribute name="Implementation-Version" value="Version ${version}, Date ${TODAY}"/>
+ <attribute name="Implementation-Vendor" value="Mark McKay, mark@kitfox.com"/>
+ </section>
+ </manifest>
+
<fileset dir="${classes.main.dir}">
<include name="**/*.class"/>
</fileset>
diff --git a/lib/library/ant.jar b/lib/library/ant.jar
new file mode 100644
index 0000000..3a67607
--- /dev/null
+++ b/lib/library/ant.jar
Binary files differ
diff --git a/manifest.mf b/manifest.mf
index 0d75d09..9960bbc 100755
--- a/manifest.mf
+++ b/manifest.mf
@@ -9,6 +9,6 @@ Specification-Title: SVG Salamander
Specification-Version: 1
Specification-Vendor: Kitfox
Implementation-Title: svg-salamander-core
-Implementation-Version: Version 1, Date April 16 2007
+Implementation-Version: Version 1, Date May 29 2007
Implementation-Vendor: Mark McKay, mark@kitfox.com
diff --git a/src/main/java/com/kitfox/svg/Circle.java b/src/main/java/com/kitfox/svg/Circle.java
new file mode 100644
index 0000000..80daf35
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Circle.java
@@ -0,0 +1,175 @@
+/*
+ * 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 com.kitfox.svg.animation.*;
+import org.xml.sax.*;
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * @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)
+ {
+ circle.setFrame(cx - r, cy - r, r * 2f, r * 2f);
+ return true;
+ }
+
+ return changeState;
+ }
+
+}
+
+
+
diff --git a/src/main/java/com/kitfox/svg/ClipPath.java b/src/main/java/com/kitfox/svg/ClipPath.java
new file mode 100644
index 0000000..4c16f06
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/ClipPath.java
@@ -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.*;
+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 ClipPath extends SVGElement
+{
+
+// final Vector members = new Vector();
+
+ 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;
+ }
+ }
+
+ return shapeChange;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Defs.java b/src/main/java/com/kitfox/svg/Defs.java
new file mode 100644
index 0000000..ed7e615
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Defs.java
@@ -0,0 +1,71 @@
+/*
+ * 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 {
+
+// final Vector members = new Vector();
+
+ /** 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Desc.java b/src/main/java/com/kitfox/svg/Desc.java
new file mode 100644
index 0000000..4eb2c8f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Desc.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Ellipse.java b/src/main/java/com/kitfox/svg/Ellipse.java
new file mode 100644
index 0000000..1047930
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Ellipse.java
@@ -0,0 +1,191 @@
+/*
+ * 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)
+ {
+ ellipse.setFrame(cx - rx, cy - ry, rx * 2f, ry * 2f);
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/FeDistantLight.java b/src/main/java/com/kitfox/svg/FeDistantLight.java
new file mode 100644
index 0000000..a3aff4e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FeDistantLight.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/FeLight.java b/src/main/java/com/kitfox/svg/FeLight.java
new file mode 100644
index 0000000..8cd239c
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FeLight.java
@@ -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() {
+ }
+
+}
+
diff --git a/src/main/java/com/kitfox/svg/FePointLight.java b/src/main/java/com/kitfox/svg/FePointLight.java
new file mode 100644
index 0000000..52e116e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FePointLight.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/FeSpotLight.java b/src/main/java/com/kitfox/svg/FeSpotLight.java
new file mode 100644
index 0000000..f42a817
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FeSpotLight.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/FillElement.java b/src/main/java/com/kitfox/svg/FillElement.java
new file mode 100644
index 0000000..9fef10b
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FillElement.java
@@ -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);
+}
diff --git a/src/main/java/com/kitfox/svg/Filter.java b/src/main/java/com/kitfox/svg/Filter.java
new file mode 100644
index 0000000..0c41f15
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Filter.java
@@ -0,0 +1,229 @@
+/*
+ * 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 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 Vector filterEffects = new Vector();
+
+ /** 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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/FilterEffects.java b/src/main/java/com/kitfox/svg/FilterEffects.java
new file mode 100644
index 0000000..e5c7a91
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FilterEffects.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/Font.java b/src/main/java/com/kitfox/svg/Font.java
new file mode 100644
index 0000000..0896472
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Font.java
@@ -0,0 +1,249 @@
+/*
+ * 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
+
+ //Vector members = null;
+
+ 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;
+ */
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/FontFace.java b/src/main/java/com/kitfox/svg/FontFace.java
new file mode 100644
index 0000000..7b6dcfe
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/FontFace.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Glyph.java b/src/main/java/com/kitfox/svg/Glyph.java
new file mode 100644
index 0000000..528444a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Glyph.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Gradient.java b/src/main/java/com/kitfox/svg/Gradient.java
new file mode 100644
index 0000000..a1336f1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Gradient.java
@@ -0,0 +1,282 @@
+/*
+ * 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 com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+/**
+ * @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
+ Vector stops = new Vector();
+ 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 {
+ URI src = 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) return stopRef.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) return stopRef.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(Gradient 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 src = sty.getURIValue(getXMLBase());
+ Gradient newVal = (Gradient)diagram.getUniverse().getElement(src);
+ if (newVal != 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/Group.java b/src/main/java/com/kitfox/svg/Group.java
new file mode 100644
index 0000000..8446049
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Group.java
@@ -0,0 +1,275 @@
+/*
+ * 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 Group extends ShapeElement {
+
+// final Vector members = new Vector();
+
+ //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, Vector 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, 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/ImageSVG.java b/src/main/java/com/kitfox/svg/ImageSVG.java
new file mode 100644
index 0000000..2d981bc
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/ImageSVG.java
@@ -0,0 +1,299 @@
+/*
+ * 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 java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.util.*;
+import java.net.*;
+
+/**
+ * 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());
+ imageSrc = src.toURL();
+ }
+ }
+ 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, Vector retVec) throws SVGException
+ {
+ /*
+ Point2D xPoint = new Point2D.Double();
+ try
+ {
+ xform.inverseTransform(point, xPoint);
+ }
+ catch (NoninvertibleTransformException ex)
+ {
+ throw new SVGException(ex);
+ }
+ */
+
+// if (bounds.contains(xPoint))
+ if (getBoundingBox().contains(point))
+ {
+ 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)
+ {
+ 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Line.java b/src/main/java/com/kitfox/svg/Line.java
new file mode 100644
index 0000000..f52a9c8
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Line.java
@@ -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 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 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)
+ {
+ line = new Line2D.Float(x1, y1, x2, y2);
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/LinearGradient.java b/src/main/java/com/kitfox/svg/LinearGradient.java
new file mode 100644
index 0000000..06a1503
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/LinearGradient.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Metadata.java b/src/main/java/com/kitfox/svg/Metadata.java
new file mode 100644
index 0000000..554c301
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Metadata.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/MissingGlyph.java b/src/main/java/com/kitfox/svg/MissingGlyph.java
new file mode 100644
index 0000000..bc0cd2c
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/MissingGlyph.java
@@ -0,0 +1,252 @@
+/*
+ * 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 MissingGlyph extends ShapeElement
+{
+ //We may define a path
+// ExtendedGeneralPath path = null;
+ Shape path = null;
+
+ //Alternately, we may have child graphical elements
+// Vector members = null;
+
+ 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);
+
+// if (members == null) members = new Vector();
+// members.add(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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Path.java b/src/main/java/com/kitfox/svg/Path.java
new file mode 100644
index 0000000..85feff3
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Path.java
@@ -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 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)
+ {
+ path = buildPath(d, fillRule);
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/PatternSVG.java b/src/main/java/com/kitfox/svg/PatternSVG.java
new file mode 100644
index 0000000..c1a32e9
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/PatternSVG.java
@@ -0,0 +1,303 @@
+/*
+ * 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;
+
+// final Vector members = new Vector();
+
+ 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;
+ }
+
+
+ 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Polygon.java b/src/main/java/com/kitfox/svg/Polygon.java
new file mode 100644
index 0000000..56f2997
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Polygon.java
@@ -0,0 +1,177 @@
+/*
+ * 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)
+ {
+ buildPath();
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Polyline.java b/src/main/java/com/kitfox/svg/Polyline.java
new file mode 100644
index 0000000..335dd87
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Polyline.java
@@ -0,0 +1,155 @@
+/*
+ * 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 java.util.Vector;
+
+/**
+ * @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)
+ {
+ buildPath();
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/RadialGradient.java b/src/main/java/com/kitfox/svg/RadialGradient.java
new file mode 100644
index 0000000..37b9414
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/RadialGradient.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Rect.java b/src/main/java/com/kitfox/svg/Rect.java
new file mode 100644
index 0000000..fa59372
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Rect.java
@@ -0,0 +1,267 @@
+/*
+ * 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, ry);
+ }
+ }
+
+ /*
+ 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, ry);
+ }
+ }
+
+ 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)
+ {
+ if (rx == 0f && ry == 0f)
+ {
+ rect = new Rectangle2D.Float(x, y, width, height);
+ }
+ else
+ {
+ rect = new RoundRectangle2D.Float(x, y, width, height, rx, ry);
+ }
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/RenderableElement.java b/src/main/java/com/kitfox/svg/RenderableElement.java
new file mode 100644
index 0000000..878a132
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/RenderableElement.java
@@ -0,0 +1,161 @@
+/*
+ * 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.geom.*;
+import java.awt.*;
+import java.net.*;
+import java.util.LinkedList;
+import java.util.Vector;
+
+/**
+ * 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, Vector 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);
+ }
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGCache.java b/src/main/java/com/kitfox/svg/SVGCache.java
new file mode 100644
index 0000000..13fa1ec
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGCache.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGDiagram.java b/src/main/java/com/kitfox/svg/SVGDiagram.java
new file mode 100644
index 0000000..aaab400
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGDiagram.java
@@ -0,0 +1,217 @@
+/*
+ * 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.util.*;
+import java.net.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.io.Serializable;
+
+/**
+ * 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 Vector containing the
+ * path to the node is added to the return vector. That is, the result of
+ * SVGElement.getPath() is added for each entry.
+ *
+ * @return the passed in vector
+ */
+ public Vector pick(Point2D point, Vector retVec) throws SVGException
+ {
+ if (retVec == null)
+ {
+ retVec = new Vector();
+ }
+
+ root.pick(point, 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();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/SVGDisplayPanel.form b/src/main/java/com/kitfox/svg/SVGDisplayPanel.form
new file mode 100644
index 0000000..9392b28
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGDisplayPanel.form
@@ -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>
diff --git a/src/main/java/com/kitfox/svg/SVGDisplayPanel.java b/src/main/java/com/kitfox/svg/SVGDisplayPanel.java
new file mode 100644
index 0000000..beedace
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGDisplayPanel.java
@@ -0,0 +1,194 @@
+/*
+ * 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);
+ 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
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGElement.java b/src/main/java/com/kitfox/svg/SVGElement.java
new file mode 100644
index 0000000..2370906
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGElement.java
@@ -0,0 +1,853 @@
+/*
+ * 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 com.kitfox.svg.xml.StyleAttribute;
+import com.kitfox.svg.xml.XMLParseUtil;
+import java.util.*;
+import java.util.regex.*;
+import java.net.*;
+import java.awt.geom.*;
+
+import org.xml.sax.*;
+import com.kitfox.svg.animation.*;
+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 Vector children = new Vector();
+
+ 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;
+
+ protected final TrackManager trackManager = new TrackManager();
+
+// 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 Vector getPath(Vector retVec)
+ {
+ if (retVec == null) retVec = new Vector();
+
+ if (parent != null)
+ {
+ parent.getPath(retVec);
+ }
+ retVec.add(this);
+
+ return retVec;
+ }
+
+ /**
+ * @param retVec - A vector to add all children to. If null, a new vector is
+ * created and children of this group are added.
+ *
+ * @return The vector containing the children of this group
+ */
+ public Vector getChildren(Vector retVec)
+ {
+ if (retVec == null) retVec = new Vector();
+
+ 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));
+ }
+ }
+
+ public void addAttribute(String name, int attribType, String value) throws SVGElementException
+ {
+ if (hasAttribute(name, attribType)) throw new SVGElementException(this, "Attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + ") already exists");
+
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ inlineStyles.put(name, new StyleAttribute(name, value));
+ return;
+ case AnimationElement.AT_XML:
+ presAttribs.put(name, new StyleAttribute(name, value));
+ return;
+ }
+
+ throw new SVGElementException(this, "Invalid attribute type " + attribType);
+ }
+
+ public boolean hasAttribute(String name, int attribType) throws SVGElementException
+ {
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ return inlineStyles.containsKey(name);
+ case AnimationElement.AT_XML:
+ return presAttribs.containsKey(name);
+ case AnimationElement.AT_AUTO:
+ return inlineStyles.containsKey(name) || presAttribs.containsKey(name);
+ }
+
+ throw new SVGElementException(this, "Invalid attribute type " + attribType);
+ }
+
+ /**
+ * @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);
+
+ //Add info to track if we've scanned animation element
+ if (child instanceof AnimationElement)
+ {
+ trackManager.addTrackElement((AnimationElement)child);
+ }
+ }
+
+ private void setDiagram(SVGDiagram diagram)
+ {
+ this.diagram = diagram;
+ 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);
+ }
+
+
+ public void setAttribute(String name, int attribType, String value) throws SVGElementException
+ {
+ StyleAttribute styAttr;
+
+
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ {
+ styAttr = (StyleAttribute)inlineStyles.get(name);
+ break;
+ }
+ case AnimationElement.AT_XML:
+ {
+ styAttr = (StyleAttribute)presAttribs.get(name);
+ break;
+ }
+ case AnimationElement.AT_AUTO:
+ {
+ styAttr = (StyleAttribute)inlineStyles.get(name);
+
+ if (styAttr == null)
+ {
+ styAttr = (StyleAttribute)presAttribs.get(name);
+ }
+ break;
+ }
+ default:
+ throw new SVGElementException(this, "Invalid attribute type " + attribType);
+ }
+
+ if (styAttr == null)
+ {
+ throw new SVGElementException(this, "Could not find attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + "). Make sure to create attribute before setting it.");
+ }
+
+ //Alter layout for relevant attributes
+ if ("id".equals(styAttr.getStringValue()))
+ {
+ diagram.removeElement(this.id);
+ this.id = name;
+ diagram.setElement(this.id, this);
+ }
+
+ styAttr.setStringValue(value);
+ }
+
+ /**
+ * 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());
+
+ //Evalutate coresponding track, if one exists
+ TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_CSS);
+ if (track != null)
+ {
+ track.getValue(attrib, diagram.getUniverse().getCurTime());
+ return true;
+ }
+
+ //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());
+
+ //Evalutate coresponding track, if one exists
+ track = trackManager.getTrack(styName, AnimationElement.AT_XML);
+ if (track != null)
+ {
+ track.getValue(attrib, diagram.getUniverse().getCurTime());
+ return true;
+ }
+
+ //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());
+
+ //Evalutate coresponding track, if one exists
+ TrackBase track = trackManager.getTrack(presName, AnimationElement.AT_XML);
+ if (track != null)
+ {
+ track.getValue(attrib, diagram.getUniverse().getCurTime());
+ return true;
+ }
+
+ //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));
+ break;
+ case 'm':
+ cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens));
+ 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;
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGElementException.java b/src/main/java/com/kitfox/svg/SVGElementException.java
new file mode 100644
index 0000000..61a4540
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGElementException.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/SVGException.java b/src/main/java/com/kitfox/svg/SVGException.java
new file mode 100644
index 0000000..c985ea1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/SVGLoader.java b/src/main/java/com/kitfox/svg/SVGLoader.java
new file mode 100644
index 0000000..7f81c18
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGLoader.java
@@ -0,0 +1,267 @@
+/*
+ * 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.io.*;
+import java.net.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+
+import com.kitfox.svg.animation.*;
+
+/**
+ * @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("animate", Animate.class);
+ nodeClasses.put("animatecolor", AnimateColor.class);
+ nodeClasses.put("animatemotion", AnimateMotion.class);
+ nodeClasses.put("animatetransform", AnimateTransform.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("image", ImageSVG.class);
+ nodeClasses.put("line", Line.class);
+ nodeClasses.put("lineargradient", LinearGradient.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("set", SetSmil.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))
+ {
+ 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; }
+}
diff --git a/src/main/java/com/kitfox/svg/SVGLoaderHelper.java b/src/main/java/com/kitfox/svg/SVGLoaderHelper.java
new file mode 100644
index 0000000..35e45bb
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGLoaderHelper.java
@@ -0,0 +1,79 @@
+/*
+ * 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.*;
+import java.io.*;
+
+import com.kitfox.svg.animation.parser.*;
+
+/**
+ * @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;
+
+ /**
+ * Animate nodes use this to parse their time strings
+ */
+ public final AnimTimeParser animTimeParser = new AnimTimeParser(new StringReader(""));
+
+ /** 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGParseException.java b/src/main/java/com/kitfox/svg/SVGParseException.java
new file mode 100644
index 0000000..478ecd2
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGParseException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/SVGRoot.java b/src/main/java/com/kitfox/svg/SVGRoot.java
new file mode 100644
index 0000000..7fea53a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGRoot.java
@@ -0,0 +1,390 @@
+/*
+ * 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 != x)
+ {
+ x = newVal;
+ shapeChange = true;
+ }
+ }
+
+ if (getPres(sty.setName("y")))
+ {
+ NumberWithUnits newVal = sty.getNumberWithUnits();
+ if (newVal != y)
+ {
+ y = newVal;
+ shapeChange = true;
+ }
+ }
+
+ if (getPres(sty.setName("width")))
+ {
+ NumberWithUnits newVal = sty.getNumberWithUnits();
+ if (newVal != width)
+ {
+ width = newVal;
+ shapeChange = true;
+ }
+ }
+
+ if (getPres(sty.setName("height")))
+ {
+ NumberWithUnits newVal = sty.getNumberWithUnits();
+ if (newVal != 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;
+ }
+ }
+
+ return changeState;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/SVGUniverse.java b/src/main/java/com/kitfox/svg/SVGUniverse.java
new file mode 100644
index 0000000..48d0f3a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/SVGUniverse.java
@@ -0,0 +1,533 @@
+/*
+ * 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.net.*;
+import java.awt.image.*;
+import javax.imageio.*;
+import java.beans.*;
+import java.lang.ref.*;
+//import java.util.*;
+//import java.util.regex.*;
+
+import java.util.*;
+import java.io.*;
+import org.xml.sax.*;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.parsers.SAXParser;
+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;
+
+ /** 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);
+ }
+
+ 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 = xmlBase.toURL();
+
+ loadSVG(url, false);
+ dia = (SVGDiagram)loadedDocs.get(xmlBase);
+ return dia;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ 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 InputStreamReader(is));
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+
+ return null;
+ }
+
+
+ public URI loadSVG(InputStream is, String name)
+ {
+ return loadSVG(is, name, false);
+ }
+
+ public URI loadSVG(InputStream is, String name, boolean forceLoad)
+ {
+ return loadSVG(new InputStreamReader(is), name, forceLoad);
+ }
+
+ 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, 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;
+ }
+ }
+
+
+ protected URI loadSVG(URI xmlBase, Reader 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 = XMLReaderFactory.createXMLReader();
+ 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(new InputSource(new BufferedReader(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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/ShapeElement.java b/src/main/java/com/kitfox/svg/ShapeElement.java
new file mode 100644
index 0000000..c4e583e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/ShapeElement.java
@@ -0,0 +1,299 @@
+/*
+ * 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.xml.StyleAttribute;
+import java.net.*;
+import java.awt.*;
+import java.awt.geom.*;
+import com.kitfox.svg.xml.*;
+import java.util.Vector;
+
+/**
+ * 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, Vector retVec) throws SVGException
+ {
+ /*
+ Point2D xPoint = new Point2D.Double();
+ try
+ {
+ xform.inverseTransform(point, xPoint);
+ }
+ catch (NoninvertibleTransformException ex)
+ {
+ throw new SVGException(ex);
+ }
+ */
+
+ StyleAttribute styleAttrib = new StyleAttribute();
+ if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
+ {
+ 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")) strokeLinecap = BasicStroke.JOIN_ROUND;
+ else if (val.equals("bevel")) strokeLinecap = 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;
+// }
+
+
+
+ //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);
+ }
+
+ }
+
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/Stop.java b/src/main/java/com/kitfox/svg/Stop.java
new file mode 100644
index 0000000..b3990c6
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Stop.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Style.java b/src/main/java/com/kitfox/svg/Style.java
new file mode 100644
index 0000000..58bbf26
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Style.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/Symbol.java b/src/main/java/com/kitfox/svg/Symbol.java
new file mode 100644
index 0000000..1a9f0b3
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Symbol.java
@@ -0,0 +1,158 @@
+/*
+ * 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.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 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/Text.java b/src/main/java/com/kitfox/svg/Text.java
new file mode 100644
index 0000000..1256419
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Text.java
@@ -0,0 +1,539 @@
+/*
+ * 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)
+ {
+ buildFont();
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Title.java b/src/main/java/com/kitfox/svg/Title.java
new file mode 100644
index 0000000..c056297
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Title.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/TransformableElement.java b/src/main/java/com/kitfox/svg/TransformableElement.java
new file mode 100644
index 0000000..cde6340
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/TransformableElement.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.awt.geom.*;
+import java.awt.*;
+
+/**
+ * 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Tspan.java b/src/main/java/com/kitfox/svg/Tspan.java
new file mode 100644
index 0000000..bffa6b8
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Tspan.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/Use.java b/src/main/java/com/kitfox/svg/Use.java
new file mode 100644
index 0000000..00d05ff
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/Use.java
@@ -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 com.kitfox.svg.xml.*;
+import org.xml.sax.*;
+
+import java.net.*;
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * @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)
+ {
+ //Determine use offset/scale
+ refXform.setToTranslation(this.x, this.y);
+ refXform.scale(this.width, this.height);
+ return true;
+ }
+
+ return changeState;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimTimeParser.jjt b/src/main/java/com/kitfox/svg/animation/AnimTimeParser.jjt
new file mode 100644
index 0000000..3a63b4c
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimTimeParser.jjt
@@ -0,0 +1,316 @@
+/**
+ */
+
+
+options {
+ MULTI=true;
+ STATIC=false;
+}
+
+PARSER_BEGIN(AnimTimeParser)
+
+package com.kitfox.svg.animation.parser;
+
+import java.util.*;
+import java.io.*;
+import com.kitfox.svg.animation.*;
+
+public class AnimTimeParser
+{
+ /**
+ * Test the parser
+ */
+ public static void main(String args[]) throws ParseException
+ {
+// AnimTimeParser parser = new AnimTimeParser(System.in);
+ StringReader reader;
+
+ reader = new StringReader("1:30 + 5ms");
+ AnimTimeParser parser = new AnimTimeParser(reader);
+ TimeBase tc;
+
+ tc = parser.Expr();
+ System.err.println("AnimTimeParser eval to " + tc.evalTime());
+
+ reader = new StringReader("19");
+ parser.ReInit(reader);
+ tc = parser.Expr();
+ System.err.println("AnimTimeParser eval to " + tc.evalTime());
+ }
+
+}
+
+PARSER_END(AnimTimeParser)
+
+
+
+
+/**
+ * Tokens
+ */
+
+SKIP : /* WHITE SPACE */
+{
+ " "
+| "\t"
+| "\n"
+| "\r"
+| "\f"
+}
+
+TOKEN :
+{
+ < #LETTER: [ "a"-"z", "A"-"Z" ] >
+|
+ < #DIGIT: [ "0"-"9"] >
+|
+ < INTEGER: (<DIGIT>)+ >
+|
+ < FLOAT: (["+", "-"])? (((<DIGIT>)* "." (<DIGIT>)+) | ((<DIGIT>)+)) (["E", "e"] (["+", "-"])? (<DIGIT>)+)? >
+|
+ < INDEFINITE: "indefinite" >
+|
+ < MOUSE_OVER: "mouseover" >
+|
+ < WHEN_NOT_ACTIVE: "whenNotActive" >
+|
+ < UNITS: "ms" | "s" | "min" | "h" >
+|
+ < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>|"_"|"-")* >
+
+}
+
+
+
+
+
+
+
+/**
+ * Expression structure
+ */
+
+
+TimeBase Expr() :
+{
+ TimeBase term;
+ Vector list = new Vector();
+}
+{
+ ( term = Sum()
+ {
+ list.add(term);
+ }
+ )?
+ ( LOOKAHEAD(2) ";" term = Sum()
+ {
+ list.add(term);
+ }
+ ) *
+ (";")?
+
+ {
+ switch (list.size())
+ {
+ case 0:
+ return new TimeIndefinite();
+ case 1:
+ return (TimeBase)list.get(0);
+ default:
+ return new TimeCompound(list);
+ }
+ }
+}
+
+TimeBase Sum() :
+{
+ Token t = null;
+ TimeBase t1;
+ TimeBase t2 = null;
+}
+{
+ t1=Term() ( (t="+" | t="-") t2 = Term() )?
+ {
+ if (t2 == null) return t1;
+
+ if (t.image.equals("-"))
+ {
+ return new TimeSum(t1, t2, false);
+ }
+ else
+ {
+ return new TimeSum(t1, t2, true);
+ }
+ }
+}
+
+
+/*
+{
+ TimeBase base;
+ Vector timeList = new Vector();
+ Token t;
+}
+{
+ base=Term()
+ {
+ timeList.add(base);
+ }
+ ( (t="+" | t="-") base=Term()
+ {
+ if (t.image.equals"-")
+ timeList.sub(base);
+ else
+ timeList.add(base);
+ }
+ )*
+ {
+ switch (timeList.size())
+ {
+ case 0:
+ return new TimeIndefinite();
+ case 1:
+ return (TimeBase)timeList.get(0);
+ default:
+ return new TimeCompound(timeList);
+ }
+ }
+
+}
+*/
+
+TimeBase Term() :
+{
+ TimeBase base;
+}
+{
+ base=IndefiniteTime()
+ { return base; }
+ | base=LiteralTime()
+ { return base; }
+ | base=LookupTime()
+ { return base; }
+ | base=EventTime()
+ { return base; }
+}
+
+TimeIndefinite IndefiniteTime() :
+{}
+{
+ <INDEFINITE>
+ {
+ return new TimeIndefinite();
+ }
+}
+
+TimeDiscrete EventTime() :
+{}
+{
+ (<MOUSE_OVER> | <WHEN_NOT_ACTIVE>)
+ {
+ //For now, map all events to the zero time
+ return new TimeDiscrete(0);
+ }
+}
+
+TimeDiscrete LiteralTime() :
+{
+ double t1, t2, t3 = Double.NaN, value;
+ Token t;
+}
+{
+ t1=Number()
+ {
+ value = t1;
+ }
+ (
+
+ (":" t2=Number() (":" t3=Number())?
+ {
+ //Return clock time format (convert to seconds)
+ if (Double.isNaN(t3))
+ {
+ value = t1 * 60 + t2;
+ }
+ else
+ {
+ value = t1 * 3600 + t2 * 60 + t3;
+ }
+ }
+ )
+
+ |
+
+ (t=<UNITS>
+ {
+ //Return units format (convert to seconds)
+ if (t.image.equals("ms")) value = t1 / 1000;
+ if (t.image.equals("min")) value = t1 * 60;
+ if (t.image.equals("h")) value = t1 * 3600;
+ }
+ )
+ )?
+ {
+ return new TimeDiscrete(value);
+ }
+}
+
+
+TimeLookup LookupTime() :
+{
+ double paramNum = 0.0;
+ Token node, event;
+}
+{
+ node=<IDENTIFIER> "." event=<IDENTIFIER> (paramNum=ParamList())?
+ {
+ return new TimeLookup(null, node.image, event.image, "" + paramNum);
+ }
+}
+
+double ParamList() :
+{
+ double num;
+}
+{
+ "(" num=Number() ")"
+ {
+ return num;
+ }
+}
+
+double Number() :
+{
+ Token t;
+}
+{
+ t=<FLOAT>
+ {
+ try { return Double.parseDouble(t.image); }
+ catch (Exception e) { e.printStackTrace(); }
+
+ return 0.0;
+ }
+ | t=<INTEGER>
+ {
+ try { return Double.parseDouble(t.image); }
+ catch (Exception e) { e.printStackTrace(); }
+
+ return 0.0;
+ }
+}
+
+int Integer() :
+{
+ Token t;
+}
+{
+ t=<INTEGER>
+ {
+ try { return Integer.parseInt(t.image); }
+ catch (Exception e) { e.printStackTrace(); }
+
+ return 0;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/animation/Animate.java b/src/main/java/com/kitfox/svg/animation/Animate.java
new file mode 100644
index 0000000..051c526
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/Animate.java
@@ -0,0 +1,354 @@
+/*
+ * Animate.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.XMLParseUtil;
+import org.xml.sax.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * Animate is a really annoying morphic tag that could represent a real value,
+ * a color or a path
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class Animate extends AnimateBase implements AnimateColorIface
+{
+// StyleAttribute retAttrib = new StyleAttribute
+ public static final int DT_REAL = 0;
+ public static final int DT_COLOR = 1;
+ public static final int DT_PATH = 2;
+ int dataType = DT_REAL;
+
+ protected double fromValue = Double.NaN;
+ protected double toValue = Double.NaN;
+ protected double byValue = Double.NaN;
+ protected double[] valuesValue;
+
+ protected Color fromColor = null;
+ protected Color toColor = null;
+
+ protected GeneralPath fromPath = null;
+ protected GeneralPath toPath = null;
+
+ /** Creates a new instance of Animate */
+ public Animate()
+ {
+ }
+
+ public int getDataType()
+ {
+ return dataType;
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ String strn = attrs.getValue("from");
+ if (strn != null)
+ {
+ if (XMLParseUtil.isDouble(strn))
+ {
+ fromValue = XMLParseUtil.parseDouble(strn);
+ }
+// else if (attrs.getValue("attributeName").equals("d"))
+// {
+// fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
+// dataType = DT_PATH;
+// }
+ else
+ {
+ fromColor = ColorTable.parseColor(strn);
+ if (fromColor == null)
+ {
+ //Try path
+ fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
+ dataType = DT_PATH;
+ }
+ else dataType = DT_COLOR;
+ }
+ }
+
+ strn = attrs.getValue("to");
+ if (strn != null)
+ {
+ if (XMLParseUtil.isDouble(strn))
+ {
+ toValue = XMLParseUtil.parseDouble(strn);
+ }
+ else
+ {
+ toColor = ColorTable.parseColor(strn);
+ if (toColor == null)
+ {
+ //Try path
+ toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
+ dataType = DT_PATH;
+ }
+ else dataType = DT_COLOR;
+ }
+ }
+
+ strn = attrs.getValue("by");
+ try
+ {
+ if (strn != null) byValue = XMLParseUtil.parseDouble(strn);
+ } catch (Exception e) {}
+
+ strn = attrs.getValue("values");
+ try
+ {
+ if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn);
+ } catch (Exception e) {}
+ }
+
+ /**
+ * Evaluates this animation element for the passed interpolation time. Interp
+ * must be on [0..1].
+ */
+ public double eval(double interp)
+ {
+ boolean fromExists = !Double.isNaN(fromValue);
+ boolean toExists = !Double.isNaN(toValue);
+ boolean byExists = !Double.isNaN(byValue);
+ boolean valuesExists = valuesValue != null;
+
+ if (valuesExists)
+ {
+ double sp = interp * valuesValue.length;
+ int ip = (int)sp;
+ double fp = sp - ip;
+
+ int i0 = ip;
+ int i1 = ip + 1;
+
+ if (i0 < 0) return valuesValue[0];
+ if (i1 >= valuesValue.length) return valuesValue[valuesValue.length - 1];
+ return valuesValue[i0] * (1 - fp) + valuesValue[i1] * fp;
+ }
+ else if (fromExists && toExists)
+ {
+ return toValue * interp + fromValue * (1.0 - interp);
+ }
+ else if (fromExists && byExists)
+ {
+ return fromValue + byValue * interp;
+ }
+ else if (toExists && byExists)
+ {
+ return toValue - byValue * (1.0 - interp);
+ }
+ else if (byExists)
+ {
+ return byValue * interp;
+ }
+
+ //Should not reach this line
+ throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
+ }
+
+ public Color evalColor(double interp)
+ {
+ if (fromColor == null && toColor != null)
+ {
+ float[] toCol = new float[3];
+ toColor.getColorComponents(toCol);
+ return new Color(toCol[0] * (float)interp,
+ toCol[1] * (float)interp,
+ toCol[2] * (float)interp);
+ }
+ else if (fromColor != null && toColor != null)
+ {
+ float nInterp = 1 - (float)interp;
+
+ float[] fromCol = new float[3];
+ float[] toCol = new float[3];
+ fromColor.getColorComponents(fromCol);
+ toColor.getColorComponents(toCol);
+ return new Color(fromCol[0] * nInterp + toCol[0] * (float)interp,
+ fromCol[1] * nInterp + toCol[1] * (float)interp,
+ fromCol[2] * nInterp + toCol[2] * (float)interp);
+ }
+
+ throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
+ }
+
+ public GeneralPath evalPath(double interp)
+ {
+ if (fromPath == null && toPath != null)
+ {
+ PathIterator itTo = toPath.getPathIterator(new AffineTransform());
+
+ GeneralPath midPath = new GeneralPath();
+ float[] coordsTo = new float[6];
+
+ for (; !itTo.isDone(); itTo.next())
+ {
+ int segTo = itTo.currentSegment(coordsTo);
+
+ switch (segTo)
+ {
+ case PathIterator.SEG_CLOSE:
+ midPath.closePath();
+ break;
+ case PathIterator.SEG_CUBICTO:
+ midPath.curveTo(
+ (float)(coordsTo[0] * interp),
+ (float)(coordsTo[1] * interp),
+ (float)(coordsTo[2] * interp),
+ (float)(coordsTo[3] * interp),
+ (float)(coordsTo[4] * interp),
+ (float)(coordsTo[5] * interp)
+ );
+ break;
+ case PathIterator.SEG_LINETO:
+ midPath.lineTo(
+ (float)(coordsTo[0] * interp),
+ (float)(coordsTo[1] * interp)
+ );
+ break;
+ case PathIterator.SEG_MOVETO:
+ midPath.moveTo(
+ (float)(coordsTo[0] * interp),
+ (float)(coordsTo[1] * interp)
+ );
+ break;
+ case PathIterator.SEG_QUADTO:
+ midPath.quadTo(
+ (float)(coordsTo[0] * interp),
+ (float)(coordsTo[1] * interp),
+ (float)(coordsTo[2] * interp),
+ (float)(coordsTo[3] * interp)
+ );
+ break;
+ }
+ }
+
+ return midPath;
+ }
+ else if (toPath != null)
+ {
+ PathIterator itFrom = fromPath.getPathIterator(new AffineTransform());
+ PathIterator itTo = toPath.getPathIterator(new AffineTransform());
+
+ GeneralPath midPath = new GeneralPath();
+ float[] coordsFrom = new float[6];
+ float[] coordsTo = new float[6];
+
+ for (; !itFrom.isDone(); itFrom.next())
+ {
+ int segFrom = itFrom.currentSegment(coordsFrom);
+ int segTo = itTo.currentSegment(coordsTo);
+
+ if (segFrom != segTo)
+ {
+ throw new RuntimeException("Path shape mismatch");
+ }
+
+ switch (segFrom)
+ {
+ case PathIterator.SEG_CLOSE:
+ midPath.closePath();
+ break;
+ case PathIterator.SEG_CUBICTO:
+ midPath.curveTo(
+ (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp),
+ (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp),
+ (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp),
+ (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp),
+ (float)(coordsFrom[4] * (1 - interp) + coordsTo[4] * interp),
+ (float)(coordsFrom[5] * (1 - interp) + coordsTo[5] * interp)
+ );
+ break;
+ case PathIterator.SEG_LINETO:
+ midPath.lineTo(
+ (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp),
+ (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
+ );
+ break;
+ case PathIterator.SEG_MOVETO:
+ midPath.moveTo(
+ (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp),
+ (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
+ );
+ break;
+ case PathIterator.SEG_QUADTO:
+ midPath.quadTo(
+ (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp),
+ (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp),
+ (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp),
+ (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp)
+ );
+ break;
+ }
+ }
+
+ return midPath;
+ }
+
+ throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
+ }
+
+ /**
+ * If this element is being accumulated, detemine the delta to accumulate by
+ */
+ public double repeatSkipSize(int reps)
+ {
+ boolean fromExists = !Double.isNaN(fromValue);
+ boolean toExists = !Double.isNaN(toValue);
+ boolean byExists = !Double.isNaN(byValue);
+
+ if (fromExists && toExists)
+ {
+ return (toValue - fromValue) * reps;
+ }
+ else if (fromExists && byExists)
+ {
+ return (fromValue + byValue) * reps;
+ }
+ else if (toExists && byExists)
+ {
+ return toValue * reps;
+ }
+ else if (byExists)
+ {
+ return byValue * reps;
+ }
+
+ //Should not reach this line
+ return 0;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateBase.java b/src/main/java/com/kitfox/svg/animation/AnimateBase.java
new file mode 100644
index 0000000..53a6117
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateBase.java
@@ -0,0 +1,92 @@
+/*
+ * Animate.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.io.*;
+import org.xml.sax.*;
+
+import com.kitfox.svg.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class AnimateBase extends AnimationElement
+{
+ protected double repeatCount = Double.NaN;
+ protected TimeBase repeatDur;
+
+ /** Creates a new instance of Animate */
+ public AnimateBase()
+ {
+ }
+
+ public void evalParametric(AnimationTimeEval state, double curTime)
+ {
+ evalParametric(state, curTime, repeatCount, repeatDur == null ? Double.NaN : repeatDur.evalTime());
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ String repeatDurTime = attrs.getValue("repeatDur");
+
+ try
+ {
+ if (repeatDurTime != null)
+ {
+ helper.animTimeParser.ReInit(new StringReader(repeatDurTime));
+ this.repeatDur = helper.animTimeParser.Expr();
+ this.repeatDur.setParentElement(this);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SAXException(e);
+ }
+
+// this.repeatDur = TimeBase.parseTime(repeatDurTime);
+
+ String strn = attrs.getValue("repeatCount");
+ if (strn == null)
+ {
+ repeatCount = 1;
+ }
+ else if ("indefinite".equals(strn))
+ {
+ repeatCount = Double.POSITIVE_INFINITY;
+ }
+ else
+ {
+ try { repeatCount = Double.parseDouble(strn); }
+ catch (Exception e) { repeatCount = Double.NaN; }
+ }
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateColor.java b/src/main/java/com/kitfox/svg/animation/AnimateColor.java
new file mode 100644
index 0000000..a63ecad
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateColor.java
@@ -0,0 +1,81 @@
+/*
+ * Animate.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import org.xml.sax.*;
+import java.awt.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class AnimateColor extends AnimateBase implements AnimateColorIface
+{
+
+ protected Color fromValue;
+ protected Color toValue;
+
+ /** Creates a new instance of Animate */
+ public AnimateColor()
+ {
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ String strn = attrs.getValue("from");
+ fromValue = ColorTable.parseColor(strn);
+
+ strn = attrs.getValue("to");
+ toValue = ColorTable.parseColor(strn);
+ }
+
+
+ /**
+ * Evaluates this animation element for the passed interpolation time. Interp
+ * must be on [0..1].
+ */
+ public Color evalColor(double interp)
+ {
+ int r1 = fromValue.getRed();
+ int g1 = fromValue.getGreen();
+ int b1 = fromValue.getBlue();
+ int r2 = toValue.getRed();
+ int g2 = toValue.getGreen();
+ int b2 = toValue.getBlue();
+ double invInterp = 1.0 - interp;
+
+ return new Color((int)(r1 * invInterp + r2 * interp),
+ (int)(g1 * invInterp + g2 * interp),
+ (int)(b1 * invInterp + b2 * interp));
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateColorIface.java b/src/main/java/com/kitfox/svg/animation/AnimateColorIface.java
new file mode 100644
index 0000000..a9b59d1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateColorIface.java
@@ -0,0 +1,38 @@
+/*
+ * AnimateColorIface.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 16, 2005, 6:24 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.awt.*;
+
+/**
+ *
+ * @author kitfox
+ */
+public interface AnimateColorIface
+{
+ public Color evalColor(double interp);
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateMotion.java b/src/main/java/com/kitfox/svg/animation/AnimateMotion.java
new file mode 100644
index 0000000..c96f45e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateMotion.java
@@ -0,0 +1,234 @@
+/*
+ * Animate.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import org.xml.sax.*;
+import java.util.regex.*;
+import java.awt.geom.*;
+import java.awt.*;
+import java.util.*;
+
+import com.kitfox.svg.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class AnimateMotion extends AnimateXform
+{
+ static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher("");
+
+// protected double fromValue;
+// protected double toValue;
+ GeneralPath path;
+ int rotateType = RT_ANGLE;
+ double rotate; //Static angle to rotate by
+
+ public static final int RT_ANGLE = 0; //Rotate by constant 'rotate' degrees
+ public static final int RT_AUTO = 1; //Rotate to reflect tangent of position on path
+
+ final Vector bezierSegs = new Vector();
+ double curveLength;
+
+ /** Creates a new instance of Animate */
+ public AnimateMotion()
+ {
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ //Motion element implies animating the transform element
+ if (attribName == null)
+ {
+ attribName = "transform";
+ attribType = AT_AUTO;
+ additiveType = AD_SUM;
+ }
+
+
+ //Determine path
+ String from = attrs.getValue("from");
+ String to = attrs.getValue("to");
+ if (from != null && to != null)
+ {
+ Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float();
+
+ matchPoint.reset(from);
+ if (matchPoint.matches())
+ {
+ setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
+ }
+
+ matchPoint.reset(to);
+ if (matchPoint.matches())
+ {
+ setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
+ }
+
+ if (ptFrom != null && ptTo != null)
+ {
+ path = new GeneralPath();
+ path.moveTo(ptFrom.x, ptFrom.y);
+ path.lineTo(ptTo.x, ptTo.y);
+ }
+ }
+
+ String path = attrs.getValue("path");
+ if (path != null)
+ {
+ this.path = buildPath(path, GeneralPath.WIND_NON_ZERO);
+ }
+
+ //Now parse rotation style
+ String rotate = attrs.getValue("rotate");
+ if (rotate != null)
+ {
+ if (rotate.equals("auto"))
+ {
+ this.rotateType = RT_AUTO;
+ }
+ else
+ {
+ try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {}
+ }
+ }
+
+ paramaterizePath();
+ }
+
+ protected static void setPoint(Point2D.Float pt, String x, String y)
+ {
+ try { pt.x = Float.parseFloat(x); } catch (Exception e) {};
+
+ try { pt.y = Float.parseFloat(y); } catch (Exception e) {};
+ }
+
+
+ private void paramaterizePath()
+ {
+ bezierSegs.clear();
+ curveLength = 0;
+
+ double[] coords = new double[6];
+ double sx = 0, sy = 0;
+
+ for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next())
+ {
+ Bezier bezier = null;
+
+ int segType = pathIt.currentSegment(coords);
+
+ switch (segType)
+ {
+ case PathIterator.SEG_LINETO:
+ {
+ bezier = new Bezier(sx, sy, coords, 1);
+ sx = coords[0];
+ sy = coords[1];
+ break;
+ }
+ case PathIterator.SEG_QUADTO:
+ {
+ bezier = new Bezier(sx, sy, coords, 2);
+ sx = coords[2];
+ sy = coords[3];
+ break;
+ }
+ case PathIterator.SEG_CUBICTO:
+ {
+ bezier = new Bezier(sx, sy, coords, 3);
+ sx = coords[4];
+ sy = coords[5];
+ break;
+ }
+ case PathIterator.SEG_MOVETO:
+ {
+ sx = coords[0];
+ sy = coords[1];
+ break;
+ }
+ case PathIterator.SEG_CLOSE:
+ //Do nothing
+ break;
+
+ }
+
+ if (bezier != null)
+ {
+ bezierSegs.add(bezier);
+ curveLength += bezier.getLength();
+ }
+ }
+ }
+
+ /**
+ * Evaluates this animation element for the passed interpolation time. Interp
+ * must be on [0..1].
+ */
+ public AffineTransform eval(AffineTransform xform, double interp)
+ {
+ Point2D.Double point = new Point2D.Double();
+
+ if (interp >= 1)
+ {
+ Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1);
+ last.getFinalPoint(point);
+ xform.setToTranslation(point.x, point.y);
+ return xform;
+ }
+
+ double curLength = curveLength * interp;
+ for (Iterator it = bezierSegs.iterator(); it.hasNext();)
+ {
+ Bezier bez = (Bezier)it.next();
+
+ double bezLength = bez.getLength();
+ if (curLength < bezLength)
+ {
+ double param = curLength / bezLength;
+ bez.eval(param, point);
+ break;
+ }
+
+ curLength -= bezLength;
+ }
+
+ xform.setToTranslation(point.x, point.y);
+
+ return xform;
+ }
+
+ public static void main(String[] argv)
+ {
+ AnimateMotion a = new AnimateMotion();
+ a.path = buildPath("M0 0 L-400, 0", GeneralPath.WIND_NON_ZERO);
+ a.paramaterizePath();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateTransform.java b/src/main/java/com/kitfox/svg/animation/AnimateTransform.java
new file mode 100644
index 0000000..efce6fc
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateTransform.java
@@ -0,0 +1,225 @@
+/*
+ * Animate.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.XMLParseUtil;
+import org.xml.sax.*;
+import java.awt.geom.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+import java.util.regex.Pattern;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class AnimateTransform extends AnimateXform
+{
+// protected AffineTransform fromValue;
+// protected AffineTransform toValue;
+// protected double[] fromValue; //Transform parameters
+// protected double[] toValue;
+ protected double[][] values;
+ protected double[] keyTimes;
+
+ public static final int AT_REPLACE = 0;
+ public static final int AT_SUM = 1;
+
+ protected int additive = AT_REPLACE;
+
+ public static final int TR_TRANSLATE = 0;
+ public static final int TR_ROTATE = 1;
+ public static final int TR_SCALE = 2;
+ public static final int TR_SKEWY = 3;
+ public static final int TR_SKEWX = 4;
+ public static final int TR_INVALID = 5;
+
+ protected int xformType = TR_INVALID;
+
+ /** Creates a new instance of Animate */
+ public AnimateTransform()
+ {
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ //Type of matrix of transform. Should be one of the known names used to
+ // define matrix transforms
+ // valid types: translate, scale, rotate, skewX, skewY
+ // 'matrix' not valid for animation
+ String type = attrs.getValue("type").toLowerCase();
+ if (type.equals("translate")) xformType = TR_TRANSLATE;
+ if (type.equals("rotate")) xformType = TR_ROTATE;
+ if (type.equals("scale")) xformType = TR_SCALE;
+ if (type.equals("skewx")) xformType = TR_SKEWX;
+ if (type.equals("skewy")) xformType = TR_SKEWY;
+
+ String fromStrn = attrs.getValue("from");
+ String toStrn = attrs.getValue("to");
+ if (fromStrn != null && toStrn != null)
+ {
+ //fromValue = parseSingleTransform(type + "(" + strn + ")");
+ double[] fromValue = XMLParseUtil.parseDoubleList(fromStrn);
+ fromValue = validate(fromValue);
+
+ // toValue = parseSingleTransform(type + "(" + strn + ")");
+ double[] toValue = XMLParseUtil.parseDoubleList(toStrn);
+ toValue = validate(toValue);
+
+ values = new double[][]{fromValue, toValue};
+ keyTimes = new double[]{0, 1};
+ }
+
+ String keyTimeStrn = attrs.getValue("keyTimes");
+ String valuesStrn = attrs.getValue("values");
+ if (keyTimeStrn != null && valuesStrn != null)
+ {
+ keyTimes = XMLParseUtil.parseDoubleList(keyTimeStrn);
+
+ String[] valueList = Pattern.compile(";").split(valuesStrn);
+ values = new double[valueList.length][];
+ for (int i = 0; i < valueList.length; i++)
+ {
+ double[] list = XMLParseUtil.parseDoubleList(valueList[i]);
+ values[i] = validate(list);
+ }
+ }
+
+ //Check our additive state
+ String additive = attrs.getValue("additive");
+ if (additive != null)
+ {
+ if (additive.equals("sum")) this.additive = AT_SUM;
+ }
+ }
+
+ /**
+ * Check list size against current xform type and ensure list
+ * is expanded to a standard list size
+ */
+ private double[] validate(double[] paramList)
+ {
+ switch (xformType)
+ {
+ case TR_SCALE:
+ {
+ if (paramList == null)
+ {
+ paramList = new double[]{1, 1};
+ }
+ else if (paramList.length == 1)
+ {
+ paramList = new double[]{paramList[0], paramList[0]};
+
+// double[] tmp = paramList;
+// paramList = new double[2];
+// paramList[0] = paramList[1] = tmp[0];
+ }
+ }
+ }
+
+ return paramList;
+ }
+
+ /**
+ * Evaluates this animation element for the passed interpolation time. Interp
+ * must be on [0..1].
+ */
+ public AffineTransform eval(AffineTransform xform, double interp)
+ {
+ int idx = 0;
+ for (; idx < keyTimes.length - 1; idx++)
+ {
+ if (interp >= keyTimes[idx])
+ {
+ idx--;
+ if (idx < 0) idx = 0;
+ break;
+ }
+ }
+
+ double spanStartTime = keyTimes[idx];
+ double spanEndTime = keyTimes[idx + 1];
+// double span = spanStartTime - spanEndTime;
+
+ interp = (interp - spanStartTime) / (spanEndTime - spanStartTime);
+ double[] fromValue = values[idx];
+ double[] toValue = values[idx + 1];
+
+ switch (xformType)
+ {
+ case TR_TRANSLATE:
+ {
+ double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
+ double y = (1.0 - interp) * fromValue[1] + interp * toValue[1];
+ xform.setToTranslation(x, y);
+ break;
+ }
+ case TR_ROTATE:
+ {
+ double x1 = fromValue.length == 3 ? fromValue[1] : 0;
+ double y1 = fromValue.length == 3 ? fromValue[2] : 0;
+ double x2 = toValue.length == 3 ? toValue[1] : 0;
+ double y2 = toValue.length == 3 ? toValue[2] : 0;
+
+ double theta = (1.0 - interp) * fromValue[0] + interp * toValue[0];
+ double x = (1.0 - interp) * x1 + interp * x2;
+ double y = (1.0 - interp) * y1 + interp * y2;
+ xform.setToRotation(Math.toRadians(theta), x, y);
+ break;
+ }
+ case TR_SCALE:
+ {
+ double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
+ double y = (1.0 - interp) * fromValue[1] + interp * toValue[1];
+ xform.setToScale(x, y);
+ break;
+ }
+ case TR_SKEWX:
+ {
+ double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
+ xform.setToShear(Math.toRadians(x), 0.0);
+ break;
+ }
+ case TR_SKEWY:
+ {
+ double y = (1.0 - interp) * fromValue[0] + interp * toValue[0];
+ xform.setToShear(0.0, Math.toRadians(y));
+ break;
+ }
+ default:
+ xform.setToIdentity();
+ break;
+ }
+
+ return xform;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimateXform.java b/src/main/java/com/kitfox/svg/animation/AnimateXform.java
new file mode 100644
index 0000000..d373d13
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimateXform.java
@@ -0,0 +1,52 @@
+/*
+ * AnimateXform.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 14, 2005, 6:46 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import org.xml.sax.*;
+import java.awt.geom.*;
+
+import com.kitfox.svg.xml.*;
+import com.kitfox.svg.*;
+
+/**
+ *
+ * @author kitfox
+ */
+abstract public class AnimateXform extends AnimateBase
+{
+ public AnimateXform()
+ {
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ super.loaderStartElement(helper, attrs, parent);
+ }
+
+ abstract public AffineTransform eval(AffineTransform xform, double interp);
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimationElement.java b/src/main/java/com/kitfox/svg/animation/AnimationElement.java
new file mode 100644
index 0000000..e1c7cd9
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimationElement.java
@@ -0,0 +1,336 @@
+/*
+ * AnimateEle.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 August 15, 2004, 2:52 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.io.*;
+import org.xml.sax.*;
+
+import com.kitfox.svg.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public abstract class AnimationElement extends SVGElement
+{
+ protected String attribName;
+// protected String attribType;
+ protected int attribType = AT_AUTO;
+
+ public static final int AT_CSS = 0;
+ public static final int AT_XML = 1;
+ public static final int AT_AUTO = 2; //Check CSS first, then XML
+
+ protected TimeBase beginTime;
+ protected TimeBase durTime;
+ protected TimeBase endTime;
+ protected int fillType = FT_AUTO;
+
+ /** <a href="http://www.w3.org/TR/smil20/smil-timing.html#adef-fill">More about the <b>fill</b> attribute</a> */
+ public static final int FT_REMOVE = 0;
+ public static final int FT_FREEZE = 1;
+ public static final int FT_HOLD = 2;
+ public static final int FT_TRANSITION = 3;
+ public static final int FT_AUTO = 4;
+ public static final int FT_DEFAULT = 5;
+
+ /** Additive state of track */
+ public static final int AD_REPLACE = 0;
+ public static final int AD_SUM = 1;
+
+ int additiveType = AD_REPLACE;
+
+ /** Accumlative state */
+ public static final int AC_REPLACE = 0;
+ public static final int AC_SUM = 1;
+
+ int accumulateType = AC_REPLACE;
+
+ /** Creates a new instance of AnimateEle */
+ public AnimationElement()
+ {
+ }
+
+ public static String animationElementToString(int attrValue)
+ {
+ switch (attrValue)
+ {
+ case AT_CSS:
+ return "CSS";
+ case AT_XML:
+ return "XML";
+ case AT_AUTO:
+ return "AUTO";
+ default:
+ throw new RuntimeException("Unknown element type");
+ }
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ attribName = attrs.getValue("attributeName");
+ String attribType = attrs.getValue("attributeType");
+ if (attribType != null)
+ {
+ attribType = attribType.toLowerCase();
+ if (attribType.equals("css")) this.attribType = AT_CSS;
+ else if (attribType.equals("xml")) this.attribType = AT_XML;
+ }
+
+ String beginTime = attrs.getValue("begin");
+ String durTime = attrs.getValue("dur");
+ String endTime = attrs.getValue("end");
+
+ try
+ {
+ if (beginTime != null)
+ {
+ helper.animTimeParser.ReInit(new StringReader(beginTime));
+ this.beginTime = helper.animTimeParser.Expr();
+ this.beginTime.setParentElement(this);
+ }
+
+ if (durTime != null)
+ {
+ helper.animTimeParser.ReInit(new StringReader(durTime));
+ this.durTime = helper.animTimeParser.Expr();
+ this.durTime.setParentElement(this);
+ }
+
+ if (endTime != null)
+ {
+ helper.animTimeParser.ReInit(new StringReader(endTime));
+ this.endTime = helper.animTimeParser.Expr();
+ this.endTime.setParentElement(this);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SAXException(e);
+ }
+
+// this.beginTime = TimeBase.parseTime(beginTime);
+// this.durTime = TimeBase.parseTime(durTime);
+// this.endTime = TimeBase.parseTime(endTime);
+
+ String fill = attrs.getValue("fill");
+
+ if (fill != null)
+ {
+ if (fill.equals("remove")) this.fillType = FT_REMOVE;
+ if (fill.equals("freeze")) this.fillType = FT_FREEZE;
+ if (fill.equals("hold")) this.fillType = FT_HOLD;
+ if (fill.equals("transiton")) this.fillType = FT_TRANSITION;
+ if (fill.equals("auto")) this.fillType = FT_AUTO;
+ if (fill.equals("default")) this.fillType = FT_DEFAULT;
+ }
+
+ String additiveStrn = attrs.getValue("additive");
+
+ if (additiveStrn != null)
+ {
+ if (additiveStrn.equals("replace")) this.additiveType = AD_REPLACE;
+ if (additiveStrn.equals("sum")) this.additiveType = AD_SUM;
+ }
+
+ String accumulateStrn = attrs.getValue("accumulate");
+
+ if (accumulateStrn != null)
+ {
+ if (accumulateStrn.equals("replace")) this.accumulateType = AC_REPLACE;
+ if (accumulateStrn.equals("sum")) this.accumulateType = AC_SUM;
+ }
+ }
+
+ public String getAttribName() { return attribName; }
+ public int getAttribType() { return attribType; }
+ public int getAdditiveType() { return additiveType; }
+ public int getAccumulateType() { return accumulateType; }
+
+ public void evalParametric(AnimationTimeEval state, double curTime)
+ {
+ evalParametric(state, curTime, Double.NaN, Double.NaN);
+ }
+
+ /**
+ * Compares current time to start and end times and determines what degree
+ * of time interpolation this track currently represents. Returns
+ * Float.NaN if this track cannot be evaluated at the passed time (ie,
+ * it is before or past the end of the track, or it depends upon
+ * an unknown event)
+ * @param state - A structure that will be filled with information
+ * regarding the applicability of this animatoin element at the passed
+ * time.
+ * @param curTime - Current time in seconds
+ * @param repeatCount - Optional number of repetitions of length 'dur' to
+ * do. Set to Double.NaN to not consider this in the calculation.
+ * @param repeatDur - Optional amoun tof time to repeat the animaiton.
+ * Set to Double.NaN to not consider this in the calculation.
+ */
+ protected void evalParametric(AnimationTimeEval state, double curTime, double repeatCount, double repeatDur)
+ {
+ double begin = (beginTime == null) ? 0 : beginTime.evalTime();
+ if (Double.isNaN(begin) || begin > curTime)
+ {
+ state.set(Double.NaN, 0);
+ return;
+ }
+
+ double dur = (durTime == null) ? Double.NaN : durTime.evalTime();
+ if (Double.isNaN(dur))
+ {
+ state.set(Double.NaN, 0);
+ return;
+ }
+
+ //Determine end point of this animation
+ double end = (endTime == null) ? Double.NaN : endTime.evalTime();
+ double repeat;
+// if (Double.isNaN(repeatDur))
+// {
+// repeatDur = dur;
+// }
+ if (Double.isNaN(repeatCount) && Double.isNaN(repeatDur))
+ {
+ repeat = Double.NaN;
+ }
+ else
+ {
+ repeat = Math.min(
+ Double.isNaN(repeatCount) ? Double.POSITIVE_INFINITY : dur * repeatCount,
+ Double.isNaN(repeatDur) ? Double.POSITIVE_INFINITY : repeatDur);
+ }
+ if (Double.isNaN(repeat) && Double.isNaN(end))
+ {
+ //If neither and end point nor a repeat is specified, end point is
+ // implied by duration.
+ end = begin + dur;
+ }
+
+ double finishTime;
+ if (Double.isNaN(end))
+ {
+ finishTime = begin + repeat;
+ }
+ else if (Double.isNaN(repeat))
+ {
+ finishTime = end;
+ }
+ else
+ {
+ finishTime = Math.min(end, repeat);
+ }
+
+ double evalTime = Math.min(curTime, finishTime);
+// if (curTime > finishTime) evalTime = finishTime;
+
+
+// double evalTime = curTime;
+
+// boolean pastEnd = curTime > evalTime;
+
+// if (!Double.isNaN(end) && curTime > end) { pastEnd = true; evalTime = Math.min(evalTime, end); }
+// if (!Double.isNaN(repeat) && curTime > repeat) { pastEnd = true; evalTime = Math.min(evalTime, repeat); }
+
+ double ratio = (evalTime - begin) / dur;
+ int rep = (int)ratio;
+ double interp = ratio - rep;
+
+ //Adjust for roundoff
+ if (interp < 0.00001) interp = 0;
+
+// state.set(interp, rep);
+// if (!pastEnd)
+// {
+// state.set(interp, rep, false);
+// return;
+// }
+
+ //If we are still within the clip, return value
+ if (curTime == evalTime)
+ {
+ state.set(interp, rep);
+ return;
+ }
+
+ //We are past end of clip. Determine to clamp or ignore.
+ switch (fillType)
+ {
+ default:
+ case FT_REMOVE:
+ case FT_AUTO:
+ case FT_DEFAULT:
+ state.set(Double.NaN, rep);
+ return;
+ case FT_FREEZE:
+ case FT_HOLD:
+ case FT_TRANSITION:
+ state.set(interp == 0 ? 1 : interp, rep);
+ return;
+ }
+
+ }
+
+ double evalStartTime()
+ {
+ return beginTime == null ? Double.NaN : beginTime.evalTime();
+ }
+
+ double evalDurTime()
+ {
+ return durTime == null ? Double.NaN : durTime.evalTime();
+ }
+
+ /**
+ * Evaluates the ending time of this element. Returns 0 if not specified.
+ *
+ * @see hasEndTime
+ */
+ double evalEndTime()
+ {
+ return endTime == null ? Double.NaN : endTime.evalTime();
+ }
+
+ /**
+ * Checks to see if an end time has been specified for this element.
+ */
+ boolean hasEndTime() { return endTime != 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)
+ {
+ //Animation elements to not change with time
+ return false;
+ }}
diff --git a/src/main/java/com/kitfox/svg/animation/AnimationTimeEval.java b/src/main/java/com/kitfox/svg/animation/AnimationTimeEval.java
new file mode 100644
index 0000000..ee79c8a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/AnimationTimeEval.java
@@ -0,0 +1,65 @@
+/*
+ * AnimateTimeEval.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 21, 2004, 1:31 PM
+ */
+
+package com.kitfox.svg.animation;
+
+/**
+ *
+ * @author kitfox
+ */
+public class AnimationTimeEval
+{
+ /**
+ * Value on [0..1] representing the interpolation value of queried animation
+ * element, or Double.NaN if element does not provide a valid evalutaion
+ */
+ public double interp;
+
+ /**
+ * Number of completed repetitions
+ */
+ public int rep;
+
+ /**
+ * True if this evaluation is in a frozen state; ie, past the end of the
+ * track and held in the "freeze" state.
+ */
+// public boolean pastEnd;
+
+ /** Creates a new instance of AnimateTimeEval */
+ public AnimationTimeEval()
+ {
+ }
+
+// public void set(double interp, int rep, boolean pastEnd)
+ public void set(double interp, int rep)
+ {
+ this.interp = interp;
+ this.rep = rep;
+// this.pastEnd = pastEnd;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/Bezier.java b/src/main/java/com/kitfox/svg/animation/Bezier.java
new file mode 100644
index 0000000..1fae8c1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/Bezier.java
@@ -0,0 +1,201 @@
+/*
+ * Bezier.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 14, 2005, 4:08 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.awt.geom.*;
+
+/**
+ * http://mathworld.wolfram.com/BezierCurve.html
+ * @author kitfox
+ */
+public class Bezier
+{
+ double length;
+ double[] coord;
+
+ public Bezier(double sx, double sy, double[] coords, int numCoords)
+ {
+ setCoords(sx, sy, coords, numCoords);
+ }
+
+ public void setCoords(double sx, double sy, double[] coords, int numCoords)
+ {
+ coord = new double[numCoords * 2 + 2];
+ coord[0] = sx;
+ coord[1] = sy;
+ for (int i = 0; i < numCoords; i++)
+ {
+ coord[i * 2 + 2] = coords[i * 2];
+ coord[i * 2 + 3] = coords[i * 2 + 1];
+ }
+
+ calcLength();
+ }
+
+ /**
+ * Retuns aproximation of the length of the bezier
+ */
+ public double getLength()
+ {
+ return length;
+ }
+
+ private void calcLength()
+ {
+ length = 0;
+ for (int i = 2; i < coord.length; i += 2)
+ {
+ length += lineLength(coord[i - 2], coord[i - 1], coord[i], coord[i + 1]);
+ }
+ }
+
+ private double lineLength(double x1, double y1, double x2, double y2)
+ {
+ double dx = x2 - x1, dy = y2 - y1;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ public Point2D.Double getFinalPoint(Point2D.Double point)
+ {
+ point.x = coord[coord.length - 2];
+ point.y = coord[coord.length - 1];
+ return point;
+ }
+
+ public Point2D.Double eval(double param, Point2D.Double point)
+ {
+ point.x = 0;
+ point.y = 0;
+ int numKnots = coord.length / 2;
+
+ for (int i = 0; i < numKnots; i++)
+ {
+ double scale = bernstein(numKnots - 1, i, param);
+ point.x += coord[i * 2] * scale;
+ point.y += coord[i * 2 + 1] * scale;
+ }
+
+ return point;
+ }
+
+ /**
+ * Calculates the bernstein polynomial for evaluating parametric bezier
+ * @param numKnots - one less than number of knots in this curve hull
+ * @param knotNo - knot we are evaluating Bernstein for
+ * @param param - Parametric value we are evaluating at
+ */
+ private double bernstein(int numKnots, int knotNo, double param)
+ {
+ double iParam = 1 - param;
+ //Faster evaluation for easy cases:
+ switch (numKnots)
+ {
+ case 0:
+ return 1;
+ case 1:
+ {
+ switch (knotNo)
+ {
+ case 0:
+ return iParam;
+ case 1:
+ return param;
+ }
+ break;
+ }
+ case 2:
+ {
+ switch (knotNo)
+ {
+ case 0:
+ return iParam * iParam;
+ case 1:
+ return 2 * iParam * param;
+ case 2:
+ return param * param;
+ }
+ break;
+ }
+ case 3:
+ {
+ switch (knotNo)
+ {
+ case 0:
+ return iParam * iParam * iParam;
+ case 1:
+ return 3 * iParam * iParam * param;
+ case 2:
+ return 3 * iParam * param * param;
+ case 3:
+ return param * param * param;
+ }
+ break;
+ }
+ }
+
+ //If this bezier has more than four points, calculate bernstein the hard way
+ double retVal = 1;
+ for (int i = 0; i < knotNo; i++)
+ {
+ retVal *= param;
+ }
+ for (int i = 0; i < numKnots - knotNo; i++)
+ {
+ retVal *= iParam;
+ }
+ retVal *= choose(numKnots, knotNo);
+
+ return retVal;
+ }
+
+
+
+ private int choose(int num, int denom)
+ {
+ int denom2 = num - denom;
+ if (denom < denom2)
+ {
+ int tmp = denom;
+ denom = denom2;
+ denom2 = tmp;
+ }
+
+ int prod = 1;
+ for (int i = num; i > denom; i--)
+ {
+ prod *= num;
+ }
+
+ for (int i = 2; i <= denom2; i++)
+ {
+ prod /= i;
+ }
+
+ return prod;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/SetSmil.java b/src/main/java/com/kitfox/svg/animation/SetSmil.java
new file mode 100644
index 0000000..3ada7c9
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/SetSmil.java
@@ -0,0 +1,55 @@
+/*
+ * Set.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 August 15, 2004, 2:51 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import org.xml.sax.*;
+
+import com.kitfox.svg.*;
+
+/**
+ * Set is used to set a textual value; most likely for a style element.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SetSmil extends AnimationElement
+{
+ String toValue;
+
+ /** Creates a new instance of Set */
+ public SetSmil()
+ {
+ }
+
+ public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
+ {
+ //Load style string
+ super.loaderStartElement(helper, attrs, parent);
+
+ toValue = attrs.getValue("to");
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeBase.java b/src/main/java/com/kitfox/svg/animation/TimeBase.java
new file mode 100644
index 0000000..2f2f189
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeBase.java
@@ -0,0 +1,99 @@
+/*
+ * TimeBase.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 August 15, 2004, 3:31 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.util.regex.*;
+
+/**
+ * SVG has a complicated way of specifying time. Potentially, a time could
+ * be represened as a summation of discrete times and times of other animation
+ * events. This provides a root for the many elements we will need to define
+ * time.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class TimeBase
+{
+ static final Matcher matchIndefinite = Pattern.compile("\\s*indefinite\\s*").matcher("");
+ static final Matcher matchUnitTime = Pattern.compile("\\s*([-+]?((\\d*\\.\\d+)|(\\d+))([-+]?[eE]\\d+)?)\\s*(h|min|s|ms)?\\s*").matcher("");
+
+ /*
+ public static TimeBase parseTime(String text)
+ {
+ if (text == null) return null;
+
+ if (text.indexOf('+') == -1)
+ {
+ return parseTimeComponent(text);
+ }
+
+ return new TimeCompound(text);
+ }
+ */
+
+ protected static TimeBase parseTimeComponent(String text)
+ {
+ matchIndefinite.reset(text);
+ if (matchIndefinite.matches()) return new TimeIndefinite();
+
+ matchUnitTime.reset(text);
+ if (matchUnitTime.matches())
+ {
+ String val = matchUnitTime.group(1);
+ String units = matchUnitTime.group(6);
+
+ double time = 0;
+ try { time = Double.parseDouble(val); }
+ catch (Exception e) {}
+
+ if (units.equals("ms")) time *= .001;
+ else if (units.equals("min")) time *= 60;
+ else if (units.equals("h")) time *= 3600;
+
+ return new TimeDiscrete(time);
+ }
+
+ return null;
+ }
+
+ /**
+ * Calculates the (greater than or equal to 0) time in seconds this
+ * time represents. If the time cannot be determined, returns
+ * Double.NaN. If this represents an infinte amount of time, returns
+ * Double.POSITIVE_INFINITY.
+ */
+ abstract public double evalTime();
+
+ /**
+ * Some time elements need to refer to the animation element that contains
+ * them to evaluate correctly
+ */
+ public void setParentElement(AnimationElement ele)
+ {
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeCompound.java b/src/main/java/com/kitfox/svg/animation/TimeCompound.java
new file mode 100644
index 0000000..0545fc6
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeCompound.java
@@ -0,0 +1,96 @@
+/*
+ * TimeDiscrete.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 August 15, 2004, 3:33 AM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * This represents a summation of other time elements. It is used for complex
+ * timing events with offsets.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TimeCompound extends TimeBase
+{
+ static final Pattern patPlus = Pattern.compile("\\+");
+
+ /**
+ * This is a list of times. This element's time is calculated as the greatest
+ * member that is less than the current time.
+ */
+ final List componentTimes;
+
+ private AnimationElement parent;
+
+ /** Creates a new instance of TimeDiscrete */
+ public TimeCompound(Vector timeBases)
+ {
+ componentTimes = Collections.unmodifiableList(timeBases);
+ }
+
+ /*
+ public TimeCompound(String text)
+ {
+ String[] vals = patPlus.split(text);
+
+ Vector times = new Vector(vals.length);
+
+ for (int i = 0; i < vals.length; i++)
+ {
+ times.set(i, parseTimeComponent(vals[i]));
+ }
+
+ this(times);
+ }*/
+
+ public double evalTime()
+ {
+ double agg = 0.0;
+
+ for (Iterator it = componentTimes.iterator(); it.hasNext();)
+ {
+ TimeBase timeEle = (TimeBase)it.next();
+ double time = timeEle.evalTime();
+ agg += time;
+ }
+
+ return agg;
+ }
+
+ public void setParentElement(AnimationElement ele)
+ {
+ this.parent = ele;
+
+ for (Iterator it = componentTimes.iterator(); it.hasNext();)
+ {
+ TimeBase timeEle = (TimeBase)it.next();
+ timeEle.setParentElement(ele);
+ }
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeDiscrete.java b/src/main/java/com/kitfox/svg/animation/TimeDiscrete.java
new file mode 100644
index 0000000..dab7dc1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeDiscrete.java
@@ -0,0 +1,51 @@
+/*
+ * TimeDiscrete.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 August 15, 2004, 3:33 AM
+ */
+
+package com.kitfox.svg.animation;
+
+/**
+ * This is a time that represents a specific number of milliseconds
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TimeDiscrete extends TimeBase
+{
+ //Milliseconds of delay
+ double secs;
+
+ /** Creates a new instance of TimeDiscrete */
+ public TimeDiscrete(double secs)
+ {
+ this.secs = secs;
+ }
+
+ public double evalTime()
+ {
+ return secs;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeIndefinite.java b/src/main/java/com/kitfox/svg/animation/TimeIndefinite.java
new file mode 100644
index 0000000..b3859f1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeIndefinite.java
@@ -0,0 +1,48 @@
+/*
+ * TimeDiscrete.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 August 15, 2004, 3:33 AM
+ */
+
+package com.kitfox.svg.animation;
+
+/**
+ * This represents the indefinite (infinite) amount of time.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TimeIndefinite extends TimeBase
+{
+
+ /** Creates a new instance of TimeDiscrete */
+ public TimeIndefinite()
+ {
+ }
+
+ public double evalTime()
+ {
+ return Double.POSITIVE_INFINITY;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeLookup.java b/src/main/java/com/kitfox/svg/animation/TimeLookup.java
new file mode 100644
index 0000000..e6120bd
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeLookup.java
@@ -0,0 +1,75 @@
+/*
+ * TimeDiscrete.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 August 15, 2004, 3:33 AM
+ */
+
+package com.kitfox.svg.animation;
+
+/**
+ * This is a time that represents a specific number of milliseconds
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TimeLookup extends TimeBase
+{
+ /**
+ * This time can only be resolved in relation to it's parent
+ */
+ private AnimationElement parent;
+
+ /**
+ * Node this lookup acts upon
+ */
+ String node;
+
+ /**
+ * Event to evalutae on this node
+ */
+ String event;
+
+ /**
+ * Optional parameter used by some events
+ */
+ String paramList;
+
+ /** Creates a new instance of TimeDiscrete */
+ public TimeLookup(AnimationElement parent, String node, String event, String paramList)
+ {
+ this.parent = parent;
+ this.node = node;
+ this.event = event;
+ this.paramList = paramList;
+ }
+
+ public double evalTime()
+ {
+ return 0.0;
+ }
+
+ public void setParentElement(AnimationElement ele)
+ {
+ parent = ele;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TimeSum.java b/src/main/java/com/kitfox/svg/animation/TimeSum.java
new file mode 100644
index 0000000..82c98fe
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TimeSum.java
@@ -0,0 +1,60 @@
+/*
+ * TimeDiscrete.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 August 15, 2004, 3:33 AM
+ */
+
+package com.kitfox.svg.animation;
+
+/**
+ * This is a time that represents a specific number of milliseconds
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TimeSum extends TimeBase
+{
+ //Milliseconds of delay
+ TimeBase t1;
+ TimeBase t2;
+ boolean add;
+
+ /** Creates a new instance of TimeDiscrete */
+ public TimeSum(TimeBase t1, TimeBase t2, boolean add)
+ {
+ this.t1 = t1;
+ this.t2 = t2;
+ this.add = add;
+ }
+
+ public double evalTime()
+ {
+ return add ? t1.evalTime() + t2.evalTime() : t1.evalTime() - t2.evalTime();
+ }
+
+ public void setParentElement(AnimationElement ele)
+ {
+ t1.setParentElement(ele);
+ t2.setParentElement(ele);
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackBase.java b/src/main/java/com/kitfox/svg/animation/TrackBase.java
new file mode 100644
index 0000000..a38866f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackBase.java
@@ -0,0 +1,104 @@
+/*
+ * TrackManager.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 August 15, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.util.*;
+
+import com.kitfox.svg.xml.*;
+import com.kitfox.svg.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+abstract public class TrackBase
+{
+ protected final String attribName;
+ protected final int attribType; //AnimationElement.AT_*
+
+ /** Element we're animating */
+ protected final SVGElement parent;
+
+ //It doesn't make sense to sort this, since some events will depend on
+ // other events - in many cases, there will be no meaningful sorted order.
+ final Vector animEvents = new Vector();
+
+ /** Creates a new instance of TrackManager */
+// public TrackBase(SVGElement parent)
+// {
+// this(parent, "", AnimationElement.AT_AUTO);
+// }
+
+ /**
+ * Creates a track that would be valid for the name and type of element
+ * passed in. Does not actually add this elemnt to the track.
+ */
+ public TrackBase(SVGElement parent, AnimationElement ele) throws SVGElementException
+ {
+ this(parent, ele.getAttribName(), ele.getAttribType());
+ }
+
+ public TrackBase(SVGElement parent, String attribName, int attribType) throws SVGElementException
+ {
+ this.parent = parent;
+ this.attribName = attribName;
+ this.attribType = attribType;
+
+ //Make sure parent has an attribute we will write to
+ if (attribType == AnimationElement.AT_AUTO
+ && !parent.hasAttribute(attribName, AnimationElement.AT_CSS)
+ && !parent.hasAttribute(attribName, AnimationElement.AT_XML))
+ {
+ parent.addAttribute(attribName, AnimationElement.AT_CSS, "");
+ }
+ else if (!parent.hasAttribute(attribName, attribType))
+ {
+ parent.addAttribute(attribName, attribType, "");
+ }
+ }
+
+ public String getAttribName() { return attribName; }
+ public int getAttribType() { return attribType; }
+
+ public void addElement(AnimationElement ele)
+ {
+ animEvents.add(ele);
+ }
+
+ /**
+ * Returns a StyleAttribute representing the value of this track at the
+ * passed time. If this track does not apply, returns null.
+ * @return - True if successful, false if a value could not be obtained
+ */
+ abstract public boolean getValue(StyleAttribute attrib, double curTime) throws SVGException;
+
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackColor.java b/src/main/java/com/kitfox/svg/animation/TrackColor.java
new file mode 100644
index 0000000..97a8f3c
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackColor.java
@@ -0,0 +1,95 @@
+/*
+ * TrackManager.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 21, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.util.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackColor extends TrackBase
+{
+
+ public TrackColor(AnimationElement ele) throws SVGElementException
+ {
+ super(ele.getParent(), ele);
+ }
+
+ public boolean getValue(StyleAttribute attrib, double curTime)
+ {
+ Color col = getValue(curTime);
+ if (col == null) return false;
+
+ attrib.setStringValue("#" + Integer.toHexString(col.getRGB()));
+ return true;
+ }
+
+ public Color getValue(double curTime)
+ {
+ Color retVal = null;
+ AnimationTimeEval state = new AnimationTimeEval();
+
+ for (Iterator it = animEvents.iterator(); it.hasNext();)
+ {
+ AnimateBase ele = (AnimateBase)it.next();
+ AnimateColorIface eleColor = (AnimateColorIface)ele;
+ ele.evalParametric(state, curTime);
+
+ //Reject value if it is in the invalid state
+ if (Double.isNaN(state.interp)) continue;
+
+ if (retVal == null)
+ {
+ retVal = eleColor.evalColor(state.interp);
+ continue;
+ }
+
+ Color curCol = eleColor.evalColor(state.interp);
+ switch (ele.getAdditiveType())
+ {
+ case AnimationElement.AD_REPLACE:
+ retVal = curCol;
+ break;
+ case AnimationElement.AD_SUM:
+ retVal = new Color(curCol.getRed() + retVal.getRed(), curCol.getGreen() + retVal.getGreen(), curCol.getBlue() + retVal.getBlue());
+ break;
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackDouble.java b/src/main/java/com/kitfox/svg/animation/TrackDouble.java
new file mode 100644
index 0000000..211cb2f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackDouble.java
@@ -0,0 +1,119 @@
+/*
+ * TrackManager.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 August 15, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.util.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackDouble extends TrackBase
+{
+ public TrackDouble(AnimationElement ele) throws SVGElementException
+ {
+ super(ele.getParent(), ele);
+ }
+
+ public boolean getValue(StyleAttribute attrib, double curTime)
+ {
+ double val = getValue(curTime);
+ if (Double.isNaN(val)) return false;
+
+ attrib.setStringValue("" + val);
+ return true;
+ }
+
+ public double getValue(double curTime)
+ {
+ double retVal = Double.NaN;
+
+ StyleAttribute attr = null;
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ attr = parent.getStyleAbsolute(attribName);
+ retVal = attr.getDoubleValue();
+ break;
+ case AnimationElement.AT_XML:
+ attr = parent.getPresAbsolute(attribName);
+ retVal = attr.getDoubleValue();
+ break;
+ case AnimationElement.AT_AUTO:
+ attr = parent.getStyleAbsolute(attribName);
+ if (attr == null) attr = parent.getPresAbsolute(attribName);
+ retVal = attr.getDoubleValue();
+ break;
+ }
+
+
+
+ AnimationTimeEval state = new AnimationTimeEval();
+// boolean pastEnd = true;
+
+ for (Iterator it = animEvents.iterator(); it.hasNext();)
+ {
+ Animate ele = (Animate)it.next();
+ ele.evalParametric(state, curTime);
+
+ //Go to next element if this one does not affect processing
+ if (Double.isNaN(state.interp)) continue;
+
+ switch (ele.getAdditiveType())
+ {
+ case AnimationElement.AD_SUM:
+ retVal += ele.eval(state.interp);
+ break;
+ case AnimationElement.AD_REPLACE:
+ retVal = ele.eval(state.interp);
+ break;
+ }
+
+ //Evalutae accumulation if applicable
+ if (state.rep > 0)
+ {
+ switch (ele.getAccumulateType())
+ {
+ case AnimationElement.AC_SUM:
+ retVal += ele.repeatSkipSize(state.rep);
+ break;
+ }
+
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackManager.java b/src/main/java/com/kitfox/svg/animation/TrackManager.java
new file mode 100644
index 0000000..b8e6305
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackManager.java
@@ -0,0 +1,153 @@
+/*
+ * TrackManager.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 August 15, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import java.util.*;
+
+import com.kitfox.svg.*;
+import java.io.Serializable;
+
+/**
+ * Every element contains tracks, which manage the animation. There is one track
+ * for every parameter with animation, and each track in turn is composed of
+ * many events.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackManager implements Serializable
+{
+ public static final long serialVersionUID = 0;
+
+ static class TrackKey
+ {
+ String name;
+ int type;
+
+ TrackKey(AnimationElement base)
+ {
+ this(base.getAttribName(), base.getAttribType());
+ }
+
+ TrackKey(String name, int type)
+ {
+ this.name = name;
+ this.type = type;
+ }
+
+ public int hashCode() { return name.hashCode() ^ type; }
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof TrackKey)) return false;
+ TrackKey key = (TrackKey)obj;
+ return key.type == type && key.name.equals(name);
+ }
+ }
+
+ HashMap tracks = new HashMap();
+
+ /** Creates a new instance of TrackManager */
+ public TrackManager()
+ {
+ }
+
+ /**
+ * Adds a new animation element to this track
+ */
+ public void addTrackElement(AnimationElement element) throws SVGElementException
+ {
+ TrackKey key = new TrackKey(element);
+
+ TrackBase track = (TrackBase)tracks.get(key);
+
+ if (track == null)
+ {
+ //Create a track for this element
+ if (element instanceof Animate)
+ {
+ switch (((Animate)element).getDataType())
+ {
+ case Animate.DT_REAL:
+ track = new TrackDouble(element);
+ break;
+ case Animate.DT_COLOR:
+ track = new TrackColor(element);
+ break;
+ case Animate.DT_PATH:
+ track = new TrackPath(element);
+ break;
+ default:
+ throw new RuntimeException("");
+ }
+ }
+ else if (element instanceof AnimateColor)
+ {
+ track = new TrackColor(element);
+ }
+ else if (element instanceof AnimateTransform || element instanceof AnimateMotion)
+ {
+ track = new TrackTransform(element);
+ }
+
+ tracks.put(key, track);
+ }
+
+ track.addElement(element);
+ }
+
+ public TrackBase getTrack(String name, int type)
+ {
+ //Handle AUTO, which will match either CSS or XML (in that order)
+ if (type == AnimationElement.AT_AUTO)
+ {
+ TrackBase t = getTrack(name, AnimationElement.AT_CSS);
+ if (t != null) return t;
+ t = getTrack(name, AnimationElement.AT_XML);
+ if (t != null) return t;
+ return null;
+ }
+
+ //Get requested attribute
+ TrackKey key = new TrackKey(name, type);
+ TrackBase t = (TrackBase)tracks.get(key);
+ if (t != null) return t;
+
+ //If that didn't exist, see if one exists of type AUTO
+ key = new TrackKey(name, AnimationElement.AT_AUTO);
+ return (TrackBase)tracks.get(key);
+ }
+
+ public int getNumTracks()
+ {
+ return tracks.size();
+ }
+
+ public Iterator iterator()
+ {
+ return tracks.values().iterator();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackMotion.java b/src/main/java/com/kitfox/svg/animation/TrackMotion.java
new file mode 100644
index 0000000..6cb8578
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackMotion.java
@@ -0,0 +1,128 @@
+/*
+ * TrackManager.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 21, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackMotion extends TrackBase
+{
+ public TrackMotion(AnimationElement ele) throws SVGElementException
+ {
+ //The motion element implies a CSS attribute of transform
+// super(ele.getParent(), "transform", AnimationElement.AT_CSS);
+ super(ele.getParent(), ele);
+ }
+
+ public boolean getValue(StyleAttribute attrib, double curTime) throws SVGException
+ {
+ AffineTransform retVal = new AffineTransform();
+ retVal = getValue(retVal, curTime);
+// AffineTransform val = getValue(curTime);
+// if (val == null) return false;
+
+ double[] mat = new double[6];
+ retVal.getMatrix(mat);
+ attrib.setStringValue("matrix(" + mat[0] + " " + mat[1] + " " + mat[2] + " " + mat[3] + " " + mat[4] + " " + mat[5] + ")");
+ return true;
+ }
+
+ public AffineTransform getValue(AffineTransform retVal, double curTime) throws SVGException
+ {
+ //Init transform with default state
+ StyleAttribute attr = null;
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ attr = parent.getStyleAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ case AnimationElement.AT_XML:
+ attr = parent.getPresAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ case AnimationElement.AT_AUTO:
+ attr = parent.getStyleAbsolute(attribName);
+ if (attr == null) attr = parent.getPresAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ }
+
+
+ //Update transform with time based information
+ AnimationTimeEval state = new AnimationTimeEval();
+ AffineTransform xform = new AffineTransform();
+// boolean pastEnd = true;
+
+ for (Iterator it = animEvents.iterator(); it.hasNext();)
+ {
+ AnimateMotion ele = (AnimateMotion)it.next();
+ ele.evalParametric(state, curTime);
+
+ //Go to next element if this one does not affect processing
+ if (Double.isNaN(state.interp)) continue;
+
+ switch (ele.getAdditiveType())
+ {
+ case AnimationElement.AD_SUM:
+ retVal.concatenate(ele.eval(xform, state.interp));
+ break;
+ case AnimationElement.AD_REPLACE:
+ retVal.setTransform(ele.eval(xform, state.interp));
+ break;
+ }
+
+ //Evaluate accumulation if applicable
+/*
+ if (state.rep > 0)
+ {
+ switch (ele.getAccumulateType())
+ {
+ case AnimationElement.AC_SUM:
+ retVal += ele.repeatSkipSize(state.rep);
+ break;
+ }
+
+ }
+*/
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackPath.java b/src/main/java/com/kitfox/svg/animation/TrackPath.java
new file mode 100644
index 0000000..e2822f6
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackPath.java
@@ -0,0 +1,100 @@
+/*
+ * TrackManager.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 21, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.pathcmd.*;
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackPath extends TrackBase
+{
+
+ public TrackPath(AnimationElement ele) throws SVGElementException
+ {
+ super(ele.getParent(), ele);
+ }
+
+ public boolean getValue(StyleAttribute attrib, double curTime)
+ {
+ GeneralPath path = getValue(curTime);
+ if (path == null) return false;
+
+ attrib.setStringValue(PathUtil.buildPathString(path));
+ return true;
+ }
+
+ public GeneralPath getValue(double curTime)
+ {
+ GeneralPath retVal = null;
+ AnimationTimeEval state = new AnimationTimeEval();
+
+ for (Iterator it = animEvents.iterator(); it.hasNext();)
+ {
+ AnimateBase ele = (AnimateBase)it.next();
+ Animate eleAnim = (Animate)ele;
+ ele.evalParametric(state, curTime);
+
+ //Reject value if it is in the invalid state
+ if (Double.isNaN(state.interp)) continue;
+
+ if (retVal == null)
+ {
+ retVal = eleAnim.evalPath(state.interp);
+ continue;
+ }
+
+ GeneralPath curPath = eleAnim.evalPath(state.interp);
+ switch (ele.getAdditiveType())
+ {
+ case AnimationElement.AD_REPLACE:
+ retVal = curPath;
+ break;
+ case AnimationElement.AD_SUM:
+ throw new RuntimeException("Not implemented");
+// retVal = new Color(curCol.getRed() + retVal.getRed(), curCol.getGreen() + retVal.getGreen(), curCol.getBlue() + retVal.getBlue());
+// break;
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/animation/TrackTransform.java b/src/main/java/com/kitfox/svg/animation/TrackTransform.java
new file mode 100644
index 0000000..d925923
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/animation/TrackTransform.java
@@ -0,0 +1,112 @@
+/*
+ * TrackManager.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 21, 2004, 11:34 PM
+ */
+
+package com.kitfox.svg.animation;
+
+import com.kitfox.svg.xml.StyleAttribute;
+import java.awt.geom.*;
+import java.util.*;
+
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.*;
+
+/**
+ * A track holds the animation events for a single parameter of a single SVG
+ * element. It also contains the default value for the element, should the
+ * user want to see the 'unanimated' value.
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class TrackTransform extends TrackBase
+{
+ public TrackTransform(AnimationElement ele) throws SVGElementException
+ {
+ super(ele.getParent(), ele);
+ }
+
+ public boolean getValue(StyleAttribute attrib, double curTime) throws SVGException
+ {
+ AffineTransform retVal = new AffineTransform();
+ retVal = getValue(retVal, curTime);
+// AffineTransform val = getValue(curTime);
+// if (val == null) return false;
+
+ double[] mat = new double[6];
+ retVal.getMatrix(mat);
+ attrib.setStringValue("matrix(" + mat[0] + " " + mat[1] + " " + mat[2] + " " + mat[3] + " " + mat[4] + " " + mat[5] + ")");
+ return true;
+ }
+
+ public AffineTransform getValue(AffineTransform retVal, double curTime) throws SVGException
+ {
+ //Init transform with default state
+ StyleAttribute attr = null;
+ switch (attribType)
+ {
+ case AnimationElement.AT_CSS:
+ attr = parent.getStyleAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ case AnimationElement.AT_XML:
+ attr = parent.getPresAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ case AnimationElement.AT_AUTO:
+ attr = parent.getStyleAbsolute(attribName);
+ if (attr == null) attr = parent.getPresAbsolute(attribName);
+ retVal.setTransform(SVGElement.parseSingleTransform(attr.getStringValue()));
+ break;
+ }
+
+
+ //Update transform with time based information
+ AnimationTimeEval state = new AnimationTimeEval();
+ AffineTransform xform = new AffineTransform();
+
+ for (Iterator it = animEvents.iterator(); it.hasNext();)
+ {
+ AnimateXform ele = (AnimateXform)it.next();
+ ele.evalParametric(state, curTime);
+
+ //Go to next element if this one does not affect processing
+ if (Double.isNaN(state.interp)) continue;
+
+ switch (ele.getAdditiveType())
+ {
+ case AnimationElement.AD_SUM:
+ retVal.concatenate(ele.eval(xform, state.interp));
+ break;
+ case AnimationElement.AD_REPLACE:
+ retVal.setTransform(ele.eval(xform, state.interp));
+ break;
+ }
+
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/app/MainFrame.form b/src/main/java/com/kitfox/svg/app/MainFrame.form
new file mode 100644
index 0000000..e285b77
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/MainFrame.form
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <Properties>
+ <Property name="title" type="java.lang.String" value="SVG Salamander - Application Launcher"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <Events>
+ <EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="exitForm"/>
+ </Events>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
+ <Property name="axis" type="int" value="1"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="bn_svgViewer">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="SVG Viewer (No animation)"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_svgViewerActionPerformed"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="bn_svgViewer1">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="SVG Player (Animation)"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_svgViewer1ActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="South"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="bn_quit">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Quit"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_quitActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/MainFrame.java b/src/main/java/com/kitfox/svg/app/MainFrame.java
new file mode 100644
index 0000000..596ec05
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/MainFrame.java
@@ -0,0 +1,134 @@
+/*
+ * MainFrame.java
+ *
+ * Created on September 6, 2004, 1:19 AM
+ */
+
+package com.kitfox.svg.app;
+
+/**
+ *
+ * @author kitfox
+ */
+public class MainFrame extends javax.swing.JFrame
+{
+ public static final long serialVersionUID = 1;
+
+ /** Creates new form MainFrame */
+ public MainFrame()
+ {
+ initComponents();
+ }
+
+ /** 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.
+ */
+ private void initComponents()//GEN-BEGIN:initComponents
+ {
+ jPanel1 = new javax.swing.JPanel();
+ bn_svgViewer = new javax.swing.JButton();
+ bn_svgViewer1 = new javax.swing.JButton();
+ jPanel2 = new javax.swing.JPanel();
+ bn_quit = new javax.swing.JButton();
+
+ setTitle("SVG Salamander - Application Launcher");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosing(java.awt.event.WindowEvent evt)
+ {
+ exitForm(evt);
+ }
+ });
+
+ jPanel1.setLayout(new javax.swing.BoxLayout(jPanel1, javax.swing.BoxLayout.Y_AXIS));
+
+ bn_svgViewer.setText("SVG Viewer (No animation)");
+ bn_svgViewer.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_svgViewerActionPerformed(evt);
+ }
+ });
+
+ jPanel1.add(bn_svgViewer);
+
+ bn_svgViewer1.setText("SVG Player (Animation)");
+ bn_svgViewer1.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_svgViewer1ActionPerformed(evt);
+ }
+ });
+
+ jPanel1.add(bn_svgViewer1);
+
+ getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER);
+
+ bn_quit.setText("Quit");
+ bn_quit.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_quitActionPerformed(evt);
+ }
+ });
+
+ jPanel2.add(bn_quit);
+
+ getContentPane().add(jPanel2, java.awt.BorderLayout.SOUTH);
+
+ pack();
+ }//GEN-END:initComponents
+
+ private void bn_svgViewer1ActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_svgViewer1ActionPerformed
+ {//GEN-HEADEREND:event_bn_svgViewer1ActionPerformed
+ SVGPlayer.main(null);
+
+ close();
+ }//GEN-LAST:event_bn_svgViewer1ActionPerformed
+
+ private void bn_svgViewerActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_svgViewerActionPerformed
+ {//GEN-HEADEREND:event_bn_svgViewerActionPerformed
+ SVGViewer.main(null);
+
+ close();
+ }//GEN-LAST:event_bn_svgViewerActionPerformed
+
+ private void bn_quitActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_quitActionPerformed
+ {//GEN-HEADEREND:event_bn_quitActionPerformed
+ exitForm(null);
+ }//GEN-LAST:event_bn_quitActionPerformed
+
+ /** Exit the Application */
+ private void exitForm(java.awt.event.WindowEvent evt)//GEN-FIRST:event_exitForm
+ {
+ System.exit(0);
+ }//GEN-LAST:event_exitForm
+
+ private void close()
+ {
+ this.setVisible(false);
+ this.dispose();
+ }
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[])
+ {
+ new MainFrame().setVisible(true);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton bn_quit;
+ private javax.swing.JButton bn_svgViewer;
+ private javax.swing.JButton bn_svgViewer1;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/PlayerDialog.form b/src/main/java/com/kitfox/svg/app/PlayerDialog.form
new file mode 100644
index 0000000..628bbf8
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/PlayerDialog.form
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="2"/>
+ <Property name="title" type="java.lang.String" value="Player"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <Events>
+ <EventHandler event="windowClosed" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="formWindowClosed"/>
+ </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"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="North"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="bn_playBack">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="&lt;"/>
+ <Property name="toolTipText" type="java.lang.String" value="Play backwards"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_playBackActionPerformed"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="bn_stop">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="||"/>
+ <Property name="toolTipText" type="java.lang.String" value="Stop playback"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_stopActionPerformed"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="bn_playFwd">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="&gt;"/>
+ <Property name="toolTipText" type="java.lang.String" value="Play Forwards"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_playFwdActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
+ <Property name="axis" type="int" value="1"/>
+ </Layout>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel3">
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel1">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Cur Time"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="text_curTime">
+ <Properties>
+ <Property name="horizontalAlignment" type="int" value="2"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[100, 21]"/>
+ </Property>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="text_curTimeActionPerformed"/>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="text_curTimeFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="bn_time0">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Time 0"/>
+ <Property name="toolTipText" type="java.lang.String" value="Reset time to first frame"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_time0ActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel4">
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel2">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Frames Per Second"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="text_timeStep">
+ <Properties>
+ <Property name="horizontalAlignment" type="int" value="4"/>
+ <Property name="text" type="java.lang.String" value="60"/>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[100, 21]"/>
+ </Property>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="text_timeStepActionPerformed"/>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="text_timeStepFocusLost"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/PlayerDialog.java b/src/main/java/com/kitfox/svg/app/PlayerDialog.java
new file mode 100644
index 0000000..6d663f0
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/PlayerDialog.java
@@ -0,0 +1,289 @@
+/*
+ * PlayerDialog.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 28, 2004, 9:56 PM
+ */
+
+package com.kitfox.svg.app;
+
+/**
+ *
+ * @author kitfox
+ */
+public class PlayerDialog extends javax.swing.JDialog implements PlayerThreadListener
+{
+ public static final long serialVersionUID = 1;
+
+ PlayerThread thread;
+
+ final SVGPlayer parent;
+
+ /** Creates new form PlayerDialog */
+ public PlayerDialog(SVGPlayer parent)
+ {
+ super(parent, false);
+ initComponents();
+
+ this.parent = parent;
+
+ thread = new PlayerThread();
+ thread.addListener(this);
+
+ text_timeStepActionPerformed(null);
+ }
+
+ public void updateTime(double curTime, double timeStep, int playState)
+ {
+ if (playState == PlayerThread.PS_STOP) return;
+
+ text_curTime.setText("" + (float)curTime);
+ parent.updateTime(curTime);
+// text_timeStep.setText("" + (int)(1.0 / timeStep));
+ }
+
+ /** 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()
+ {
+ jPanel1 = new javax.swing.JPanel();
+ bn_playBack = new javax.swing.JButton();
+ bn_stop = new javax.swing.JButton();
+ bn_playFwd = new javax.swing.JButton();
+ jPanel2 = new javax.swing.JPanel();
+ jPanel3 = new javax.swing.JPanel();
+ jLabel1 = new javax.swing.JLabel();
+ text_curTime = new javax.swing.JTextField();
+ bn_time0 = new javax.swing.JButton();
+ jPanel4 = new javax.swing.JPanel();
+ jLabel2 = new javax.swing.JLabel();
+ text_timeStep = new javax.swing.JTextField();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle("Player");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosed(java.awt.event.WindowEvent evt)
+ {
+ formWindowClosed(evt);
+ }
+ });
+
+ bn_playBack.setText("<");
+ bn_playBack.setToolTipText("Play backwards");
+ bn_playBack.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_playBackActionPerformed(evt);
+ }
+ });
+
+ jPanel1.add(bn_playBack);
+
+ bn_stop.setText("||");
+ bn_stop.setToolTipText("Stop playback");
+ bn_stop.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_stopActionPerformed(evt);
+ }
+ });
+
+ jPanel1.add(bn_stop);
+
+ bn_playFwd.setText(">");
+ bn_playFwd.setToolTipText("Play Forwards");
+ bn_playFwd.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_playFwdActionPerformed(evt);
+ }
+ });
+
+ jPanel1.add(bn_playFwd);
+
+ getContentPane().add(jPanel1, java.awt.BorderLayout.NORTH);
+
+ jPanel2.setLayout(new javax.swing.BoxLayout(jPanel2, javax.swing.BoxLayout.Y_AXIS));
+
+ jLabel1.setText("Cur Time");
+ jPanel3.add(jLabel1);
+
+ text_curTime.setHorizontalAlignment(javax.swing.JTextField.LEFT);
+ text_curTime.setText("0");
+ text_curTime.setPreferredSize(new java.awt.Dimension(100, 21));
+ text_curTime.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ text_curTimeActionPerformed(evt);
+ }
+ });
+ text_curTime.addFocusListener(new java.awt.event.FocusAdapter()
+ {
+ public void focusLost(java.awt.event.FocusEvent evt)
+ {
+ text_curTimeFocusLost(evt);
+ }
+ });
+
+ jPanel3.add(text_curTime);
+
+ bn_time0.setText("Time 0");
+ bn_time0.setToolTipText("Reset time to first frame");
+ bn_time0.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_time0ActionPerformed(evt);
+ }
+ });
+
+ jPanel3.add(bn_time0);
+
+ jPanel2.add(jPanel3);
+
+ jLabel2.setText("Frames Per Second");
+ jPanel4.add(jLabel2);
+
+ text_timeStep.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
+ text_timeStep.setText("60");
+ text_timeStep.setPreferredSize(new java.awt.Dimension(100, 21));
+ text_timeStep.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ text_timeStepActionPerformed(evt);
+ }
+ });
+ text_timeStep.addFocusListener(new java.awt.event.FocusAdapter()
+ {
+ public void focusLost(java.awt.event.FocusEvent evt)
+ {
+ text_timeStepFocusLost(evt);
+ }
+ });
+
+ jPanel4.add(text_timeStep);
+
+ jPanel2.add(jPanel4);
+
+ getContentPane().add(jPanel2, java.awt.BorderLayout.CENTER);
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void bn_time0ActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_time0ActionPerformed
+ {//GEN-HEADEREND:event_bn_time0ActionPerformed
+ thread.setCurTime(0);
+ }//GEN-LAST:event_bn_time0ActionPerformed
+
+ private void bn_playFwdActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_playFwdActionPerformed
+ {//GEN-HEADEREND:event_bn_playFwdActionPerformed
+ thread.setPlayState(PlayerThread.PS_PLAY_FWD);
+ }//GEN-LAST:event_bn_playFwdActionPerformed
+
+ private void bn_stopActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_stopActionPerformed
+ {//GEN-HEADEREND:event_bn_stopActionPerformed
+ thread.setPlayState(PlayerThread.PS_STOP);
+ }//GEN-LAST:event_bn_stopActionPerformed
+
+ private void bn_playBackActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_playBackActionPerformed
+ {//GEN-HEADEREND:event_bn_playBackActionPerformed
+ thread.setPlayState(PlayerThread.PS_PLAY_BACK);
+ }//GEN-LAST:event_bn_playBackActionPerformed
+
+ private void formWindowClosed(java.awt.event.WindowEvent evt)//GEN-FIRST:event_formWindowClosed
+ {//GEN-HEADEREND:event_formWindowClosed
+// thread.exit();
+ }//GEN-LAST:event_formWindowClosed
+
+ private void text_timeStepFocusLost(java.awt.event.FocusEvent evt)//GEN-FIRST:event_text_timeStepFocusLost
+ {//GEN-HEADEREND:event_text_timeStepFocusLost
+ text_timeStepActionPerformed(null);
+ }//GEN-LAST:event_text_timeStepFocusLost
+
+ private void text_timeStepActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_text_timeStepActionPerformed
+ {//GEN-HEADEREND:event_text_timeStepActionPerformed
+ try
+ {
+ int val = Integer.parseInt(text_timeStep.getText());
+ thread.setTimeStep(1.0 / val);
+ }
+ catch (Exception e)
+ {
+ }
+
+ double d = thread.getTimeStep();
+ String newStrn = "" + (int)(1f / d);
+ if (newStrn.equals(text_timeStep.getText())) return;
+ text_timeStep.setText(newStrn);
+
+// text_timeStepActionPerformed(null);
+ }//GEN-LAST:event_text_timeStepActionPerformed
+
+ private void text_curTimeActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_text_curTimeActionPerformed
+ {//GEN-HEADEREND:event_text_curTimeActionPerformed
+ try
+ {
+ double val = Double.parseDouble(text_curTime.getText());
+ thread.setCurTime(val);
+ }
+ catch (Exception e)
+ {
+ }
+
+ double d = thread.getCurTime();
+ text_curTime.setText("" + (float)d);
+
+ text_timeStepActionPerformed(null);
+ }//GEN-LAST:event_text_curTimeActionPerformed
+
+ private void text_curTimeFocusLost(java.awt.event.FocusEvent evt)//GEN-FIRST:event_text_curTimeFocusLost
+ {//GEN-HEADEREND:event_text_curTimeFocusLost
+ text_curTimeActionPerformed(null);
+ }//GEN-LAST:event_text_curTimeFocusLost
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton bn_playBack;
+ private javax.swing.JButton bn_playFwd;
+ private javax.swing.JButton bn_stop;
+ private javax.swing.JButton bn_time0;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JPanel jPanel4;
+ private javax.swing.JTextField text_curTime;
+ private javax.swing.JTextField text_timeStep;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/PlayerThread.java b/src/main/java/com/kitfox/svg/app/PlayerThread.java
new file mode 100644
index 0000000..90a3a28
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/PlayerThread.java
@@ -0,0 +1,129 @@
+/*
+ * PlayerThread.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 28, 2004, 10:07 PM
+ */
+
+
+package com.kitfox.svg.app;
+
+import java.util.*;
+
+/**
+ *
+ * @author kitfox
+ */
+public class PlayerThread implements Runnable
+{
+ HashSet listeners = new HashSet();
+
+ double curTime = 0;
+ double timeStep = .2;
+
+ public static final int PS_STOP = 0;
+ public static final int PS_PLAY_FWD = 1;
+ public static final int PS_PLAY_BACK = 2;
+
+ int playState = PS_STOP;
+
+ Thread thread;
+
+ /** Creates a new instance of PlayerThread */
+ public PlayerThread()
+ {
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ public void run()
+ {
+ while (thread != null)
+ {
+ synchronized (this)
+ {
+ switch (playState)
+ {
+ case PS_PLAY_FWD:
+ curTime += timeStep;
+ break;
+ case PS_PLAY_BACK:
+ curTime -= timeStep;
+ if (curTime < 0) curTime = 0;
+ break;
+ default:
+ case PS_STOP:
+ break;
+ }
+
+ fireTimeUpdateEvent();
+ }
+
+ try
+ {
+ Thread.sleep((long)(timeStep * 1000));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void exit() { thread = null; }
+ public synchronized void addListener(PlayerThreadListener listener)
+ {
+ listeners.add(listener);
+ }
+
+ public synchronized double getCurTime() { return curTime; }
+
+ public synchronized void setCurTime(double time)
+ {
+ curTime = time;
+ }
+
+ public synchronized double getTimeStep() { return timeStep; }
+
+ public synchronized void setTimeStep(double time)
+ {
+ timeStep = time;
+ if (timeStep < .01) timeStep = .01;
+ }
+
+ public synchronized int getPlayState() { return playState; }
+
+ public synchronized void setPlayState(int playState)
+ {
+ this.playState = playState;
+ }
+
+ private void fireTimeUpdateEvent()
+ {
+ for (Iterator it = listeners.iterator(); it.hasNext();)
+ {
+ PlayerThreadListener listener = (PlayerThreadListener)it.next();
+ listener.updateTime(curTime, timeStep, playState);
+ }
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/app/PlayerThreadListener.java b/src/main/java/com/kitfox/svg/app/PlayerThreadListener.java
new file mode 100644
index 0000000..b6438a0
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/PlayerThreadListener.java
@@ -0,0 +1,37 @@
+/*
+ * PlayerThreadListener.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 28, 2004, 10:15 PM
+ */
+
+package com.kitfox.svg.app;
+
+/**
+ *
+ * @author kitfox
+ */
+public interface PlayerThreadListener
+{
+ public void updateTime(double curTime, double timeStep, int playState);
+}
diff --git a/src/main/java/com/kitfox/svg/app/SVGPlayer.form b/src/main/java/com/kitfox/svg/app/SVGPlayer.form
new file mode 100644
index 0000000..f98c2be
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/SVGPlayer.form
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <NonVisualComponents>
+ <Menu class="javax.swing.JMenuBar" name="jMenuBar1">
+ <SubComponents>
+ <Menu class="javax.swing.JMenu" name="menu_file">
+ <Properties>
+ <Property name="mnemonic" type="int" value="102"/>
+ <Property name="text" type="java.lang.String" value="File"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_loadFile">
+ <Properties>
+ <Property name="mnemonic" type="int" value="108"/>
+ <Property name="text" type="java.lang.String" value="Load File..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_loadFileActionPerformed"/>
+ </Events>
+ </MenuItem>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_loadUrl">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Load URL..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_loadUrlActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ <Menu class="javax.swing.JMenu" name="menu_window">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Window"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_player">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Player"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_playerActionPerformed"/>
+ </Events>
+ </MenuItem>
+ <MenuItem class="javax.swing.JSeparator" name="jSeparator2">
+ </MenuItem>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_800x600">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="800 x 600"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_800x600ActionPerformed"/>
+ </Events>
+ </MenuItem>
+ <MenuItem class="javax.swing.JCheckBoxMenuItem" name="CheckBoxMenuItem_anonInputStream">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Anonymous Input Stream"/>
+ </Properties>
+ </MenuItem>
+ <MenuItem class="javax.swing.JCheckBoxMenuItem" name="cmCheck_verbose">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Verbose"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cmCheck_verboseActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ <Menu class="javax.swing.JMenu" name="menu_help">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Help"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_about">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="About..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_aboutActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ </SubComponents>
+ </Menu>
+ </NonVisualComponents>
+ <Properties>
+ <Property name="title" type="java.lang.String" value="SVG Player - Salamander Project"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="menuBar" type="java.lang.String" value="jMenuBar1"/>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <Events>
+ <EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="exitForm"/>
+ </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"/>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="scrollPane_svgArea">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/SVGPlayer.java b/src/main/java/com/kitfox/svg/app/SVGPlayer.java
new file mode 100644
index 0000000..100a3e8
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/SVGPlayer.java
@@ -0,0 +1,426 @@
+/*
+ * SVGViewer.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 3, 2004, 5:28 PM
+ */
+
+package com.kitfox.svg.app;
+
+import java.net.*;
+import java.awt.*;
+import java.io.*;
+import java.util.regex.*;
+
+import javax.swing.*;
+
+import com.kitfox.svg.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.security.AccessControlException;
+import java.util.Vector;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGPlayer extends javax.swing.JFrame
+{
+ public static final long serialVersionUID = 1;
+
+ SVGDisplayPanel svgDisplayPanel = new SVGDisplayPanel();
+
+ final PlayerDialog playerDialog;
+
+ SVGUniverse universe;
+
+ /** FileChooser for running in trusted environments */
+ final JFileChooser fileChooser;
+ {
+// fileChooser = new JFileChooser(new File("."));
+ JFileChooser fc = null;
+ try
+ {
+ fc = new JFileChooser();
+ fc.setFileFilter(
+ new javax.swing.filechooser.FileFilter() {
+ final Matcher matchLevelFile = Pattern.compile(".*\\.svg").matcher("");
+
+ public boolean accept(File file)
+ {
+ if (file.isDirectory()) return true;
+
+ matchLevelFile.reset(file.getName());
+ return matchLevelFile.matches();
+ }
+
+ public String getDescription() { return "SVG file (*.svg)"; }
+ }
+ );
+ }
+ catch (AccessControlException ex)
+ {
+ //Do not create file chooser if webstart refuses permissions
+ }
+ fileChooser = fc;
+ }
+
+ /** Backup file service for opening files in WebStart situations */
+ /*
+ final FileOpenService fileOpenService;
+ {
+ try
+ {
+ fileOpenService = (FileOpenService)ServiceManager.lookup("javax.jnlp.FileOpenService");
+ }
+ catch (UnavailableServiceException e)
+ {
+ fileOpenService = null;
+ }
+ }
+ */
+
+ /** Creates new form SVGViewer */
+ public SVGPlayer() {
+ initComponents();
+
+ setSize(800, 600);
+
+ svgDisplayPanel.setBgColor(Color.white);
+ svgDisplayPanel.addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent evt)
+ {
+ SVGDiagram diagram = svgDisplayPanel.getDiagram();
+ if (diagram == null) return;
+
+ System.out.println("Picking at cursor (" + evt.getX() + ", " + evt.getY() + ")");
+ try
+ {
+ Vector paths = diagram.pick(new Point2D.Float(evt.getX(), evt.getY()), null);
+ for (int i = 0; i < paths.size(); i++)
+ {
+ Vector path = (Vector)paths.get(i);
+ System.out.println(pathToString(path));
+ }
+ }
+ catch (SVGException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ }
+ );
+
+ svgDisplayPanel.setPreferredSize(getSize());
+ scrollPane_svgArea.setViewportView(svgDisplayPanel);
+
+ playerDialog = new PlayerDialog(this);
+ }
+
+ private String pathToString(Vector path)
+ {
+ if (path.size() == 0) return "";
+
+ StringBuffer sb = new StringBuffer();
+ sb.append(path.get(0));
+ for (int i = 1; i < path.size(); i++)
+ {
+ sb.append("/");
+ sb.append(((SVGElement)path.get(i)).getId());
+ }
+ return sb.toString();
+ }
+
+ public void updateTime(double curTime)
+ {
+ try
+ {
+ if (universe != null)
+ {
+ universe.setCurTime(curTime);
+ universe.updateTime();
+ // svgDisplayPanel.updateTime(curTime);
+ repaint();
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void loadURL(URL url)
+ {
+ boolean verbose = cmCheck_verbose.isSelected();
+
+ universe = new SVGUniverse();
+ universe.setVerbose(verbose);
+ SVGDiagram diagram = null;
+
+ if (!CheckBoxMenuItem_anonInputStream.isSelected())
+ {
+ //Load from a disk with a valid URL
+ URI uri = universe.loadSVG(url);
+
+ if (verbose) System.err.println(uri.toString());
+
+ diagram = universe.getDiagram(uri);
+ }
+ else
+ {
+ //Load from a stream with no particular valid URL
+ try
+ {
+ InputStream is = url.openStream();
+ URI uri = universe.loadSVG(is, "defaultName");
+
+ if (verbose) System.err.println(uri.toString());
+
+ diagram = universe.getDiagram(uri);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ svgDisplayPanel.setDiagram(diagram);
+ repaint();
+ }
+
+ /** 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()
+ {
+ scrollPane_svgArea = new javax.swing.JScrollPane();
+ jMenuBar1 = new javax.swing.JMenuBar();
+ menu_file = new javax.swing.JMenu();
+ cm_loadFile = new javax.swing.JMenuItem();
+ cm_loadUrl = new javax.swing.JMenuItem();
+ menu_window = new javax.swing.JMenu();
+ cm_player = new javax.swing.JMenuItem();
+ jSeparator2 = new javax.swing.JSeparator();
+ cm_800x600 = new javax.swing.JMenuItem();
+ CheckBoxMenuItem_anonInputStream = new javax.swing.JCheckBoxMenuItem();
+ cmCheck_verbose = new javax.swing.JCheckBoxMenuItem();
+ menu_help = new javax.swing.JMenu();
+ cm_about = new javax.swing.JMenuItem();
+
+ setTitle("SVG Player - Salamander Project");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosing(java.awt.event.WindowEvent evt)
+ {
+ exitForm(evt);
+ }
+ });
+
+ getContentPane().add(scrollPane_svgArea, java.awt.BorderLayout.CENTER);
+
+ menu_file.setMnemonic('f');
+ menu_file.setText("File");
+ cm_loadFile.setMnemonic('l');
+ cm_loadFile.setText("Load File...");
+ cm_loadFile.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_loadFileActionPerformed(evt);
+ }
+ });
+
+ menu_file.add(cm_loadFile);
+
+ cm_loadUrl.setText("Load URL...");
+ cm_loadUrl.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_loadUrlActionPerformed(evt);
+ }
+ });
+
+ menu_file.add(cm_loadUrl);
+
+ jMenuBar1.add(menu_file);
+
+ menu_window.setText("Window");
+ cm_player.setText("Player");
+ cm_player.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_playerActionPerformed(evt);
+ }
+ });
+
+ menu_window.add(cm_player);
+
+ menu_window.add(jSeparator2);
+
+ cm_800x600.setText("800 x 600");
+ cm_800x600.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_800x600ActionPerformed(evt);
+ }
+ });
+
+ menu_window.add(cm_800x600);
+
+ CheckBoxMenuItem_anonInputStream.setText("Anonymous Input Stream");
+ menu_window.add(CheckBoxMenuItem_anonInputStream);
+
+ cmCheck_verbose.setText("Verbose");
+ cmCheck_verbose.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cmCheck_verboseActionPerformed(evt);
+ }
+ });
+
+ menu_window.add(cmCheck_verbose);
+
+ jMenuBar1.add(menu_window);
+
+ menu_help.setText("Help");
+ cm_about.setText("About...");
+ cm_about.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_aboutActionPerformed(evt);
+ }
+ });
+
+ menu_help.add(cm_about);
+
+ jMenuBar1.add(menu_help);
+
+ setJMenuBar(jMenuBar1);
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void cm_loadUrlActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_loadUrlActionPerformed
+ {//GEN-HEADEREND:event_cm_loadUrlActionPerformed
+ String urlStrn = JOptionPane.showInputDialog(this, "Enter URL of SVG file");
+ if (urlStrn == null) return;
+
+ try
+ {
+ URL url = new URL(urlStrn);
+ loadURL(url);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }//GEN-LAST:event_cm_loadUrlActionPerformed
+
+ private void cmCheck_verboseActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cmCheck_verboseActionPerformed
+ {//GEN-HEADEREND:event_cmCheck_verboseActionPerformed
+// TODO add your handling code here:
+ }//GEN-LAST:event_cmCheck_verboseActionPerformed
+
+ private void cm_playerActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_playerActionPerformed
+ {//GEN-HEADEREND:event_cm_playerActionPerformed
+ playerDialog.setVisible(true);
+ }//GEN-LAST:event_cm_playerActionPerformed
+
+ private void cm_aboutActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_aboutActionPerformed
+ {//GEN-HEADEREND:event_cm_aboutActionPerformed
+ VersionDialog dia = new VersionDialog(this, true, cmCheck_verbose.isSelected());
+ dia.setVisible(true);
+// JOptionPane.showMessageDialog(this, "Salamander SVG - Created by Mark McKay\nhttp://www.kitfox.com");
+ }//GEN-LAST:event_cm_aboutActionPerformed
+
+ private void cm_800x600ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cm_800x600ActionPerformed
+ setSize(800, 600);
+ }//GEN-LAST:event_cm_800x600ActionPerformed
+
+ private void cm_loadFileActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_loadFileActionPerformed
+ {//GEN-HEADEREND:event_cm_loadFileActionPerformed
+ boolean verbose = cmCheck_verbose.isSelected();
+
+ try
+ {
+ int retVal = fileChooser.showOpenDialog(this);
+ if (retVal == JFileChooser.APPROVE_OPTION)
+ {
+ File chosenFile = fileChooser.getSelectedFile();
+
+ URL url = chosenFile.toURI().toURL();
+
+ loadURL(url);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }//GEN-LAST:event_cm_loadFileActionPerformed
+
+ /** Exit the Application */
+ private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
+ System.exit(0);
+ }//GEN-LAST:event_exitForm
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ new SVGPlayer().setVisible(true);
+ }
+
+ public void updateTime(double curTime, double timeStep, int playState)
+ {
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JCheckBoxMenuItem CheckBoxMenuItem_anonInputStream;
+ private javax.swing.JCheckBoxMenuItem cmCheck_verbose;
+ private javax.swing.JMenuItem cm_800x600;
+ private javax.swing.JMenuItem cm_about;
+ private javax.swing.JMenuItem cm_loadFile;
+ private javax.swing.JMenuItem cm_loadUrl;
+ private javax.swing.JMenuItem cm_player;
+ private javax.swing.JMenuBar jMenuBar1;
+ private javax.swing.JSeparator jSeparator2;
+ private javax.swing.JMenu menu_file;
+ private javax.swing.JMenu menu_help;
+ private javax.swing.JMenu menu_window;
+ private javax.swing.JScrollPane scrollPane_svgArea;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/SVGViewer.form b/src/main/java/com/kitfox/svg/app/SVGViewer.form
new file mode 100644
index 0000000..d6a5f7d
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/SVGViewer.form
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <NonVisualComponents>
+ <Menu class="javax.swing.JMenuBar" name="jMenuBar1">
+ <SubComponents>
+ <Menu class="javax.swing.JMenu" name="menu_file">
+ <Properties>
+ <Property name="mnemonic" type="int" value="102"/>
+ <Property name="text" type="java.lang.String" value="File"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_loadFile">
+ <Properties>
+ <Property name="mnemonic" type="int" value="108"/>
+ <Property name="text" type="java.lang.String" value="Load File..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_loadFileActionPerformed"/>
+ </Events>
+ </MenuItem>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_loadUrl">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Load URL..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_loadUrlActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ <Menu class="javax.swing.JMenu" name="menu_window">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Window"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_800x600">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="800 x 600"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_800x600ActionPerformed"/>
+ </Events>
+ </MenuItem>
+ <MenuItem class="javax.swing.JCheckBoxMenuItem" name="CheckBoxMenuItem_anonInputStream">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Anonymous Input Stream"/>
+ </Properties>
+ </MenuItem>
+ <MenuItem class="javax.swing.JCheckBoxMenuItem" name="cmCheck_verbose">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Verbose"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cmCheck_verboseActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ <Menu class="javax.swing.JMenu" name="menu_help">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Help"/>
+ </Properties>
+ <SubComponents>
+ <MenuItem class="javax.swing.JMenuItem" name="cm_about">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="About..."/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cm_aboutActionPerformed"/>
+ </Events>
+ </MenuItem>
+ </SubComponents>
+ </Menu>
+ </SubComponents>
+ </Menu>
+ </NonVisualComponents>
+ <Properties>
+ <Property name="title" type="java.lang.String" value="SVG Viewer - Salamander Project"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="menuBar" type="java.lang.String" value="jMenuBar1"/>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <Events>
+ <EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="exitForm"/>
+ </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"/>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="scrollPane_svgArea">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="panel_svgArea">
+ <Events>
+ <EventHandler event="mousePressed" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="panel_svgAreaMousePressed"/>
+ <EventHandler event="mouseReleased" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="panel_svgAreaMouseReleased"/>
+ </Events>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/SVGViewer.java b/src/main/java/com/kitfox/svg/app/SVGViewer.java
new file mode 100644
index 0000000..ea63b7f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/SVGViewer.java
@@ -0,0 +1,421 @@
+/*
+ * SVGViewer.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 3, 2004, 5:28 PM
+ */
+
+package com.kitfox.svg.app;
+
+import java.net.*;
+import java.awt.*;
+import java.io.*;
+import java.util.regex.*;
+import javax.swing.*;
+
+//import javax.jnlp.*;
+
+import com.kitfox.svg.*;
+import java.security.AccessControlException;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class SVGViewer extends javax.swing.JFrame
+{
+ public static final long serialVersionUID = 1;
+
+ SVGDisplayPanel svgDisplayPanel = new SVGDisplayPanel();
+
+ /** FileChooser for running in trusted environments */
+ final JFileChooser fileChooser;
+ {
+// fileChooser = new JFileChooser(new File("."));
+ JFileChooser fc = null;
+ try
+ {
+ fc = new JFileChooser();
+ fc.setFileFilter(
+ new javax.swing.filechooser.FileFilter() {
+ final Matcher matchLevelFile = Pattern.compile(".*\\.svg").matcher("");
+
+ public boolean accept(File file)
+ {
+ if (file.isDirectory()) return true;
+
+ matchLevelFile.reset(file.getName());
+ return matchLevelFile.matches();
+ }
+
+ public String getDescription() { return "SVG file (*.svg)"; }
+ }
+ );
+ }
+ catch (AccessControlException ex)
+ {
+ //Do not create file chooser if webstart refuses permissions
+ }
+ fileChooser = fc;
+ }
+
+ /** Backup file service for opening files in WebStart situations */
+ /*
+ final FileOpenService fileOpenService;
+ {
+ try
+ {
+ fileOpenService = (FileOpenService)ServiceManager.lookup("javax.jnlp.FileOpenService");
+ }
+ catch (UnavailableServiceException e)
+ {
+ fileOpenService = null;
+ }
+ }
+ */
+
+ /** Creates new form SVGViewer */
+ public SVGViewer() {
+ initComponents();
+
+ setSize(800, 600);
+
+ svgDisplayPanel.setBgColor(Color.white);
+
+ svgDisplayPanel.setPreferredSize(getSize());
+ panel_svgArea.add(svgDisplayPanel, BorderLayout.CENTER);
+// scrollPane_svgArea.setViewportView(svgDisplayPanel);
+ }
+
+ private void loadURL(URL url)
+ {
+ boolean verbose = cmCheck_verbose.isSelected();
+
+// SVGUniverse universe = new SVGUniverse();
+ SVGUniverse universe = SVGCache.getSVGUniverse();
+ SVGDiagram diagram = null;
+ URI uri;
+
+ if (!CheckBoxMenuItem_anonInputStream.isSelected())
+ {
+ //Load from a disk with a valid URL
+ uri = universe.loadSVG(url);
+
+ if (verbose) System.err.println("Loading document " + uri.toString());
+
+ diagram = universe.getDiagram(uri);
+ }
+ else
+ {
+ //Load from a stream with no particular valid URL
+ try
+ {
+ InputStream is = url.openStream();
+ uri = universe.loadSVG(is, "defaultName");
+
+ if (verbose) System.err.println("Loading document " + uri.toString());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return;
+ }
+ }
+/*
+ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ObjectOutputStream os = new ObjectOutputStream(bs);
+os.writeObject(universe);
+os.close();
+
+ByteArrayInputStream bin = new ByteArrayInputStream(bs.toByteArray());
+ObjectInputStream is = new ObjectInputStream(bin);
+universe = (SVGUniverse)is.readObject();
+is.close();
+*/
+
+ diagram = universe.getDiagram(uri);
+
+ svgDisplayPanel.setDiagram(diagram);
+ repaint();
+ }
+
+ /** 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()
+ {
+ scrollPane_svgArea = new javax.swing.JScrollPane();
+ panel_svgArea = new javax.swing.JPanel();
+ jMenuBar1 = new javax.swing.JMenuBar();
+ menu_file = new javax.swing.JMenu();
+ cm_loadFile = new javax.swing.JMenuItem();
+ cm_loadUrl = new javax.swing.JMenuItem();
+ menu_window = new javax.swing.JMenu();
+ cm_800x600 = new javax.swing.JMenuItem();
+ CheckBoxMenuItem_anonInputStream = new javax.swing.JCheckBoxMenuItem();
+ cmCheck_verbose = new javax.swing.JCheckBoxMenuItem();
+ menu_help = new javax.swing.JMenu();
+ cm_about = new javax.swing.JMenuItem();
+
+ setTitle("SVG Viewer - Salamander Project");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosing(java.awt.event.WindowEvent evt)
+ {
+ exitForm(evt);
+ }
+ });
+
+ panel_svgArea.setLayout(new java.awt.BorderLayout());
+
+ panel_svgArea.addMouseListener(new java.awt.event.MouseAdapter()
+ {
+ public void mousePressed(java.awt.event.MouseEvent evt)
+ {
+ panel_svgAreaMousePressed(evt);
+ }
+ public void mouseReleased(java.awt.event.MouseEvent evt)
+ {
+ panel_svgAreaMouseReleased(evt);
+ }
+ });
+
+ scrollPane_svgArea.setViewportView(panel_svgArea);
+
+ getContentPane().add(scrollPane_svgArea, java.awt.BorderLayout.CENTER);
+
+ menu_file.setMnemonic('f');
+ menu_file.setText("File");
+ cm_loadFile.setMnemonic('l');
+ cm_loadFile.setText("Load File...");
+ cm_loadFile.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_loadFileActionPerformed(evt);
+ }
+ });
+
+ menu_file.add(cm_loadFile);
+
+ cm_loadUrl.setText("Load URL...");
+ cm_loadUrl.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_loadUrlActionPerformed(evt);
+ }
+ });
+
+ menu_file.add(cm_loadUrl);
+
+ jMenuBar1.add(menu_file);
+
+ menu_window.setText("Window");
+ cm_800x600.setText("800 x 600");
+ cm_800x600.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_800x600ActionPerformed(evt);
+ }
+ });
+
+ menu_window.add(cm_800x600);
+
+ CheckBoxMenuItem_anonInputStream.setText("Anonymous Input Stream");
+ menu_window.add(CheckBoxMenuItem_anonInputStream);
+
+ cmCheck_verbose.setText("Verbose");
+ cmCheck_verbose.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cmCheck_verboseActionPerformed(evt);
+ }
+ });
+
+ menu_window.add(cmCheck_verbose);
+
+ jMenuBar1.add(menu_window);
+
+ menu_help.setText("Help");
+ cm_about.setText("About...");
+ cm_about.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ cm_aboutActionPerformed(evt);
+ }
+ });
+
+ menu_help.add(cm_about);
+
+ jMenuBar1.add(menu_help);
+
+ setJMenuBar(jMenuBar1);
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void cm_loadUrlActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_loadUrlActionPerformed
+ {//GEN-HEADEREND:event_cm_loadUrlActionPerformed
+ String urlStrn = JOptionPane.showInputDialog(this, "Enter URL of SVG file");
+ if (urlStrn == null) return;
+
+ try
+ {
+ URL url = new URL(urlStrn);
+ loadURL(url);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }//GEN-LAST:event_cm_loadUrlActionPerformed
+
+ private void panel_svgAreaMouseReleased(java.awt.event.MouseEvent evt)//GEN-FIRST:event_panel_svgAreaMouseReleased
+ {//GEN-HEADEREND:event_panel_svgAreaMouseReleased
+ SVGDiagram diagram = svgDisplayPanel.getDiagram();
+ Vector pickedElements;
+ try
+ {
+ pickedElements = diagram.pick(new Point(evt.getX(), evt.getY()), null);
+ }
+ catch (SVGException ex)
+ {
+ ex.printStackTrace();
+ return;
+ }
+
+ System.out.println("Pick results:");
+ for (Iterator it = pickedElements.iterator(); it.hasNext();)
+ {
+ Vector path = (Vector)it.next();
+
+ System.out.print(" Path: ");
+
+ for (Iterator it2 = path.iterator(); it2.hasNext();)
+ {
+ SVGElement ele = (SVGElement)it2.next();
+
+ System.out.print("" + ele.getId() + "(" + ele.getClass().getName() + ") ");
+ }
+ System.out.println();
+ }
+ }//GEN-LAST:event_panel_svgAreaMouseReleased
+
+ private void panel_svgAreaMousePressed(java.awt.event.MouseEvent evt)//GEN-FIRST:event_panel_svgAreaMousePressed
+ {//GEN-HEADEREND:event_panel_svgAreaMousePressed
+
+ }//GEN-LAST:event_panel_svgAreaMousePressed
+
+ private void cmCheck_verboseActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cmCheck_verboseActionPerformed
+ {//GEN-HEADEREND:event_cmCheck_verboseActionPerformed
+ SVGCache.getSVGUniverse().setVerbose(cmCheck_verbose.isSelected());
+ }//GEN-LAST:event_cmCheck_verboseActionPerformed
+
+ private void cm_aboutActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_aboutActionPerformed
+ {//GEN-HEADEREND:event_cm_aboutActionPerformed
+ //JOptionPane.showMessageDialog(this, "Salamander SVG - Created by Mark McKay\nhttp://www.kitfox.com");
+ VersionDialog dlg = new VersionDialog(this, true, cmCheck_verbose.isSelected());
+ dlg.setVisible(true);
+ }//GEN-LAST:event_cm_aboutActionPerformed
+
+ private void cm_800x600ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cm_800x600ActionPerformed
+ setSize(800, 600);
+ }//GEN-LAST:event_cm_800x600ActionPerformed
+
+ private void cm_loadFileActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cm_loadFileActionPerformed
+ {//GEN-HEADEREND:event_cm_loadFileActionPerformed
+ try
+ {
+ int retVal = fileChooser.showOpenDialog(this);
+ if (retVal == JFileChooser.APPROVE_OPTION)
+ {
+ File chosenFile = fileChooser.getSelectedFile();
+
+ URL url = chosenFile.toURI().toURL();
+
+ loadURL(url);
+ }
+ }
+ /*
+ catch (IOException ioe)
+ {
+ try
+ {
+ //We may be in a WebStart app. Try again with a FileOpenService
+ FileContents fc = fileOpenService.openFileDialog(null, new String[]{"svg"});
+ InputStream is = fc.getInputStream();
+ String name = fc.getName();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ */
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }//GEN-LAST:event_cm_loadFileActionPerformed
+
+ /** Exit the Application */
+ private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
+// setVisible(false);
+// dispose();
+ System.exit(0);
+ }//GEN-LAST:event_exitForm
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ new SVGViewer().setVisible(true);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JCheckBoxMenuItem CheckBoxMenuItem_anonInputStream;
+ private javax.swing.JCheckBoxMenuItem cmCheck_verbose;
+ private javax.swing.JMenuItem cm_800x600;
+ private javax.swing.JMenuItem cm_about;
+ private javax.swing.JMenuItem cm_loadFile;
+ private javax.swing.JMenuItem cm_loadUrl;
+ private javax.swing.JMenuBar jMenuBar1;
+ private javax.swing.JMenu menu_file;
+ private javax.swing.JMenu menu_help;
+ private javax.swing.JMenu menu_window;
+ private javax.swing.JPanel panel_svgArea;
+ private javax.swing.JScrollPane scrollPane_svgArea;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/VersionDialog.form b/src/main/java/com/kitfox/svg/app/VersionDialog.form
new file mode 100644
index 0000000..a20b953
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/VersionDialog.form
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="2"/>
+ <Property name="title" type="java.lang.String" value="About SVG Salamander"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <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"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextPane" name="textpane_text">
+ <Properties>
+ <Property name="editable" type="boolean" value="false"/>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[400, 300]"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="South"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="bn_close">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Close"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bn_closeActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/VersionDialog.java b/src/main/java/com/kitfox/svg/app/VersionDialog.java
new file mode 100644
index 0000000..4c416d1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/VersionDialog.java
@@ -0,0 +1,151 @@
+/*
+ * VersionDialog.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 13, 2005, 7:23 AM
+ */
+
+package com.kitfox.svg.app;
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.event.*;
+import javax.swing.*;
+import javax.swing.text.html.*;
+
+
+/**
+ *
+ * @author kitfox
+ */
+public class VersionDialog extends javax.swing.JDialog
+{
+ public static final long serialVersionUID = 1;
+
+ final boolean verbose;
+
+ /** Creates new form VersionDialog */
+ public VersionDialog(java.awt.Frame parent, boolean modal, boolean verbose)
+ {
+ super(parent, modal);
+ initComponents();
+
+ this.verbose = verbose;
+
+ textpane_text.setContentType("text/html");
+
+ StringBuffer sb = new StringBuffer();
+ try
+ {
+ URL url = getClass().getResource("/res/help/about/about.html");
+ if (verbose)
+ {
+ System.err.println("" + getClass() + " trying to load about html " + url);
+ }
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
+ while (true)
+ {
+ String line = reader.readLine();
+ if (line == null) break;
+ sb.append(line);
+ }
+
+ textpane_text.setText(sb.toString());
+ }
+ catch (Exception 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()
+ {
+ jPanel1 = new javax.swing.JPanel();
+ textpane_text = new javax.swing.JTextPane();
+ jPanel2 = new javax.swing.JPanel();
+ bn_close = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle("About SVG Salamander");
+ jPanel1.setLayout(new java.awt.BorderLayout());
+
+ textpane_text.setEditable(false);
+ textpane_text.setPreferredSize(new java.awt.Dimension(400, 300));
+ jPanel1.add(textpane_text, java.awt.BorderLayout.CENTER);
+
+ getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER);
+
+ bn_close.setText("Close");
+ bn_close.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ bn_closeActionPerformed(evt);
+ }
+ });
+
+ jPanel2.add(bn_close);
+
+ getContentPane().add(jPanel2, java.awt.BorderLayout.SOUTH);
+
+ pack();
+ }
+ // </editor-fold>//GEN-END:initComponents
+
+ private void bn_closeActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_bn_closeActionPerformed
+ {//GEN-HEADEREND:event_bn_closeActionPerformed
+ setVisible(false);
+ dispose();
+ }//GEN-LAST:event_bn_closeActionPerformed
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[])
+ {
+ java.awt.EventQueue.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ new VersionDialog(new javax.swing.JFrame(), true, true).setVisible(true);
+ }
+ });
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton bn_close;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JTextPane textpane_text;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/ant/SVGToImageAntTask.java b/src/main/java/com/kitfox/svg/app/ant/SVGToImageAntTask.java
new file mode 100644
index 0000000..f4cd85a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/ant/SVGToImageAntTask.java
@@ -0,0 +1,251 @@
+/*
+ * IndexLoadObjectsAntTask.java
+ *
+ * Created on January 22, 2005, 10:30 AM
+ */
+
+package com.kitfox.svg.app.ant;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.util.*;
+import java.util.regex.*;
+import java.io.*;
+import javax.imageio.*;
+
+//import com.kitfox.util.*;
+//import com.kitfox.util.indexedObject.*;
+
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.types.*;
+
+import com.kitfox.svg.app.beans.*;
+import com.kitfox.svg.*;
+import com.kitfox.svg.xml.ColorTable;
+
+/**
+ * <p>Translates a group of SVG files into images.</p>
+ *
+ * <p>Parameters:</p>
+ * <p><ul>
+ * <li/>destDir - If present, specifices a directory to write SVG files to. Otherwise
+ * writes images to directory SVG file was found in
+ * verbose - If true, prints processing information to the console
+ * <li/>format - File format for output images. The java core javax.imageio.ImageIO
+ * class is used for creating images, so format strings will depend on what
+ * files your system is configured to handle. By default, "gif", "jpg" and "png"
+ * files are guaranteed to be present. If omitted, "png" is used by default.
+ * <li/>backgroundColor - Optional background color. Color can be specified as a standard
+ * HTML color. That is, as the name of a standard color such as "blue" or
+ * "limegreen", using the # notaion as in #ff00ff for magenta, or in rgb format
+ * listing the components as in rgb(255, 192, 192) for pink. If omitted,
+ * background is transparent.
+ * <li/>antiAlias - If set, shapes are drawn using antialiasing. Defaults to true.
+ * <li/>interpolation - String describing image interpolation alrogithm. Can
+ * be one of "nearest neighbor", "bilinear" or "bicubic". Defaults to "bicubic".
+ * <li/>width - If greater than 0, determines the width of the written image. Otherwise,
+ * the width is obtained from the SVG document. Defaults to -1;
+ * <li/>height - If greater than 0, determines the height of the written image. Otherwise,
+ * the height is obtained from the SVG document. Defaults to -1.
+ * <li/>sizeToFit - If true and the width and height of the output image differ
+ * from that of the SVG image, the valid area of the SVG image will be resized
+ * to fit the specified size.
+ * <li/>verbose - IF true, prints out diagnostic infromation about processing.
+ * Defaults to false.
+ * </ul></p>
+ *
+ * Example:
+ * &lt;SVGToImage destDir="${index.java}" format="jpg" verbose="true"&gt;
+ * &lt;fileset dir="${dir1}"&gt;
+ * &lt;include name="*.svg"/&gt;
+ * &lt;/fileset&gt;
+ * &lt;fileset dir="${dir2}"&gt;
+ * &lt;include name="*.svg"/&gt;
+ * &lt;/fileset&gt;
+ * &lt;/SVGToImage&gt;
+ *
+ *
+ *
+ * @author kitfox
+ */
+public class SVGToImageAntTask extends Task
+{
+ private Vector filesets = new Vector();
+ boolean verbose = false;
+ File destDir;
+ private String format = "png";
+ Color backgroundColor = null;
+ int width = -1;
+ int height = -1;
+ boolean antiAlias = true;
+ String interpolation = "bicubic";
+ boolean clipToViewBox = false;
+ boolean sizeToFit = true;
+
+ /** Creates a new instance of IndexLoadObjectsAntTask */
+ public SVGToImageAntTask()
+ {
+ }
+
+
+ public String getFormat()
+ {
+ return format;
+ }
+
+ public void setFormat(String format)
+ {
+ this.format = format;
+ }
+
+ public void setBackgroundColor(String bgColor)
+ {
+ this.backgroundColor = ColorTable.parseColor(bgColor);
+ }
+
+ public void setHeight(int height)
+ {
+ this.height = height;
+ }
+
+ public void setWidth(int width)
+ {
+ this.width = width;
+ }
+
+ public void setAntiAlias(boolean antiAlias)
+ {
+ this.antiAlias = antiAlias;
+ }
+
+ public void setInterpolation(String interpolation)
+ {
+ this.interpolation = interpolation;
+ }
+
+ public void setSizeToFit(boolean sizeToFit)
+ {
+ this.sizeToFit = sizeToFit;
+ }
+
+ public void setClipToViewBox(boolean clipToViewBox)
+ {
+ this.clipToViewBox = clipToViewBox;
+ }
+
+ public void setVerbose(boolean verbose)
+ {
+ this.verbose = verbose;
+ }
+
+ public void setDestDir(File destDir)
+ {
+ this.destDir = destDir;
+ }
+
+ /**
+ * Adds a set of files.
+ */
+ public void addFileset(FileSet set)
+ {
+ filesets.add(set);
+ }
+
+
+
+ public void execute()
+ {
+ if (verbose) log("Building SVG images");
+
+ for (Iterator it = filesets.iterator(); it.hasNext();)
+ {
+ FileSet fs = (FileSet)it.next();
+ FileScanner scanner = fs.getDirectoryScanner(getProject());
+ String[] files = scanner.getIncludedFiles();
+
+ try
+ {
+ File basedir = scanner.getBasedir();
+
+ if (verbose) log("Scaning " + basedir);
+
+ for (int i = 0; i < files.length; i++)
+ {
+//System.out.println("File " + files[i]);
+//System.out.println("BaseDir " + basedir);
+ translate(basedir, files[i]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new BuildException(e);
+ }
+ }
+ }
+
+ private void translate(File baseDir, String shortName) throws BuildException
+ {
+ File source = new File(baseDir, shortName);
+
+ if (verbose) log("Reading file: " + source);
+
+ Matcher matchName = Pattern.compile("(.*)\\.svg", Pattern.CASE_INSENSITIVE).matcher(shortName);
+ if (matchName.matches())
+ {
+ shortName = matchName.group(1);
+ }
+ shortName += "." + format;
+
+ SVGIcon icon = new SVGIcon();
+ icon.setSvgURI(source.toURI());
+ icon.setAntiAlias(antiAlias);
+ if (interpolation.equals("nearest neighbor"))
+ {
+ icon.setInterpolation(SVGIcon.INTERP_NEAREST_NEIGHBOR);
+ }
+ else if (interpolation.equals("bilinear"))
+ {
+ icon.setInterpolation(SVGIcon.INTERP_BILINEAR);
+ }
+ else if (interpolation.equals("bicubic"))
+ {
+ icon.setInterpolation(SVGIcon.INTERP_BICUBIC);
+ }
+
+ int iconWidth = width > 0 ? width : icon.getIconWidth();
+ int iconHeight = height > 0 ? height : icon.getIconHeight();
+ icon.setClipToViewbox(clipToViewBox);
+ icon.setPreferredSize(new Dimension(iconWidth, iconHeight));
+ icon.setScaleToFit(sizeToFit);
+ BufferedImage image = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = image.createGraphics();
+
+ if (backgroundColor != null)
+ {
+ g.setColor(backgroundColor);
+ g.fillRect(0, 0, iconWidth, iconHeight);
+ }
+
+ g.setClip(0, 0, iconWidth, iconHeight);
+// g.fillRect(10, 10, 100, 100);
+ icon.paintIcon(null, g, 0, 0);
+ g.dispose();
+
+ File outFile = destDir == null ? new File(baseDir, shortName) : new File(destDir, shortName);
+ if (verbose) log("Writing file: " + outFile);
+
+ try
+ {
+ ImageIO.write(image, format, outFile);
+ }
+ catch (IOException e)
+ {
+ log("Error writing image: " + e.getMessage());
+ throw new BuildException(e);
+ }
+
+
+ SVGCache.getSVGUniverse().clear();
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.form b/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.form
new file mode 100644
index 0000000..81fa33a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.form
@@ -0,0 +1,29 @@
+<?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"/>
+ <EventHandler event="componentShown" listener="java.awt.event.ComponentListener" parameters="java.awt.event.ComponentEvent" handler="formComponentShown"/>
+ </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"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
+ <Property name="useNullLayout" type="boolean" value="true"/>
+ </Layout>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
+ <AbsoluteConstraints x="80" y="90" width="280" height="160"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.java b/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.java
new file mode 100644
index 0000000..be947db
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.java
@@ -0,0 +1,91 @@
+/*
+ * ProportionalLayoutPanel.java
+ *
+ * Created on May 7, 2005, 4:15 AM
+ */
+
+package com.kitfox.svg.app.beans;
+
+import java.awt.*;
+import java.util.*;
+import javax.swing.*;
+
+/**
+ * Panel based on the null layout. Allows editing with absolute layout. When
+ * instanced, records layout dimensions of all subcomponents. Then, if the
+ * panel is ever resized, scales all children to fit new size.
+ *
+ * @author kitfox
+ */
+public class ProportionalLayoutPanel extends javax.swing.JPanel
+{
+ public static final long serialVersionUID = 1;
+
+ //Margins to leave on sides of panel, expressed in fractions [0 1]
+ float topMargin;
+ float bottomMargin;
+ float leftMargin;
+ float rightMargin;
+
+ /** Creates new form ProportionalLayoutPanel */
+ public ProportionalLayoutPanel()
+ {
+ initComponents();
+ }
+
+ public void addNotify()
+ {
+ super.addNotify();
+
+ Rectangle rect = this.getBounds();
+ JOptionPane.showMessageDialog(this, "" + rect);
+ }
+
+
+ /** 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()
+ {
+ jPanel1 = new javax.swing.JPanel();
+
+ setLayout(null);
+
+ addComponentListener(new java.awt.event.ComponentAdapter()
+ {
+ public void componentResized(java.awt.event.ComponentEvent evt)
+ {
+ formComponentResized(evt);
+ }
+ public void componentShown(java.awt.event.ComponentEvent evt)
+ {
+ formComponentShown(evt);
+ }
+ });
+
+ add(jPanel1);
+ jPanel1.setBounds(80, 90, 280, 160);
+
+ }
+ // </editor-fold>//GEN-END:initComponents
+
+ private void formComponentShown(java.awt.event.ComponentEvent evt)//GEN-FIRST:event_formComponentShown
+ {//GEN-HEADEREND:event_formComponentShown
+ JOptionPane.showMessageDialog(this, "" + getWidth() + ", " + getHeight());
+
+ }//GEN-LAST:event_formComponentShown
+
+ private void formComponentResized(java.awt.event.ComponentEvent evt)//GEN-FIRST:event_formComponentResized
+ {//GEN-HEADEREND:event_formComponentResized
+// TODO add your handling code here:
+ }//GEN-LAST:event_formComponentResized
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel jPanel1;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/beans/SVGIcon.java b/src/main/java/com/kitfox/svg/app/beans/SVGIcon.java
new file mode 100644
index 0000000..acb5bb5
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/beans/SVGIcon.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/app/beans/SVGPanel.form b/src/main/java/com/kitfox/svg/app/beans/SVGPanel.form
new file mode 100644
index 0000000..b404a39
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/beans/SVGPanel.form
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <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>
diff --git a/src/main/java/com/kitfox/svg/app/beans/SVGPanel.java b/src/main/java/com/kitfox/svg/app/beans/SVGPanel.java
new file mode 100644
index 0000000..396bd9b
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/app/beans/SVGPanel.java
@@ -0,0 +1,243 @@
+/*
+ * SVGIcon.java
+ *
+ * Created on April 21, 2005, 10:43 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 SVGPanel extends JPanel
+{
+ public static final long serialVersionUID = 1;
+
+
+ SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
+
+ private boolean antiAlias;
+
+// private String svgPath;
+ URI svgURI;
+
+ private boolean scaleToFit;
+ AffineTransform scaleXform = new AffineTransform();
+
+ /** Creates new form SVGIcon */
+ public SVGPanel()
+ {
+ initComponents();
+ }
+
+ public int getSVGHeight()
+ {
+ if (scaleToFit) return getPreferredSize().height;
+
+ SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+ if (diagram == null) return 0;
+ return (int)diagram.getHeight();
+ }
+
+ public int getSVGWidth()
+ {
+ if (scaleToFit) return getPreferredSize().width;
+
+ SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+ if (diagram == null) return 0;
+ return (int)diagram.getWidth();
+ }
+
+// Draw the icon at the specified location.
+ public void paintComponent(Graphics gg)
+ {
+ super.paintComponent(gg);
+
+ 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);
+
+
+ SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
+ if (diagram == null) return;
+
+ if (!scaleToFit)
+ {
+ try
+ {
+ diagram.render(g);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
+ }
+ catch (SVGException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+
+ Dimension dim = getSize();
+ final int width = dim.width;
+ final int height = dim.height;
+// int width = getWidth();
+// int height = getHeight();
+
+// 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, dim.width, dim.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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
+ }
+
+ public SVGUniverse getSvgUniverse()
+ {
+ return svgUniverse;
+ }
+
+ public void setSvgUniverse(SVGUniverse svgUniverse)
+ {
+ SVGUniverse old = this.svgUniverse;
+ this.svgUniverse = svgUniverse;
+ firePropertyChange("svgUniverse", old, svgUniverse);
+ }
+
+ public URI getSvgURI()
+ {
+ return svgURI;
+ }
+
+ public void setSvgURI(URI svgURI)
+ {
+ URI old = this.svgURI;
+ this.svgURI = svgURI;
+ firePropertyChange("svgURI", old, svgURI);
+ }
+
+ /**
+ * Most resources your component will want to access will be resources on your classpath.
+ * This method will interpret the passed string as a path in the classpath and use
+ * Class.getResource() to determine the URI of the SVG.
+ */
+ public void setSvgResourcePath(String resourcePath) throws SVGException
+ {
+ URI old = this.svgURI;
+
+ try
+ {
+ svgURI = new URI(getClass().getResource(resourcePath).toString());
+//System.err.println("SVGPanel: new URI " + svgURI + " from path " + resourcePath);
+
+ firePropertyChange("svgURI", old, svgURI);
+
+ repaint();
+ }
+ catch (Exception e)
+ {
+ throw new SVGException("Could not resolve path " + resourcePath, e);
+// svgURI = old;
+ }
+ }
+
+ public boolean isScaleToFit()
+ {
+ return scaleToFit;
+ }
+
+ public void setScaleToFit(boolean scaleToFit)
+ {
+ boolean old = this.scaleToFit;
+ this.scaleToFit = scaleToFit;
+ firePropertyChange("scaleToFit", old, scaleToFit);
+ }
+
+ /**
+ * @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;
+ firePropertyChange("antiAlias", old, antiAlias);
+ }
+
+ /** 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());
+
+ }
+ // </editor-fold>//GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/com/kitfox/svg/batik/GraphicsUtil.java b/src/main/java/com/kitfox/svg/batik/GraphicsUtil.java
new file mode 100644
index 0000000..b52148b
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/GraphicsUtil.java
@@ -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;
+ }
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/kitfox/svg/batik/LinearGradientPaint.java b/src/main/java/com/kitfox/svg/batik/LinearGradientPaint.java
new file mode 100644
index 0000000..50ff7cc
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/LinearGradientPaint.java
@@ -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());
+ }
+
+}
+
+
diff --git a/src/main/java/com/kitfox/svg/batik/LinearGradientPaintContext.java b/src/main/java/com/kitfox/svg/batik/LinearGradientPaintContext.java
new file mode 100644
index 0000000..c06d557
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/LinearGradientPaintContext.java
@@ -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);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/kitfox/svg/batik/MultipleGradientPaint.java b/src/main/java/com/kitfox/svg/batik/MultipleGradientPaint.java
new file mode 100644
index 0000000..9cd8c11
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/MultipleGradientPaint.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/batik/MultipleGradientPaintContext.java b/src/main/java/com/kitfox/svg/batik/MultipleGradientPaintContext.java
new file mode 100644
index 0000000..85f3273
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/MultipleGradientPaintContext.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/kitfox/svg/batik/RadialGradientPaint.java b/src/main/java/com/kitfox/svg/batik/RadialGradientPaint.java
new file mode 100644
index 0000000..f5f629b
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/RadialGradientPaint.java
@@ -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;
+ }
+
+}
+
diff --git a/src/main/java/com/kitfox/svg/batik/RadialGradientPaintContext.java b/src/main/java/com/kitfox/svg/batik/RadialGradientPaintContext.java
new file mode 100644
index 0000000..5b097fd
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/batik/RadialGradientPaintContext.java
@@ -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
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/composite/AdobeComposite.java b/src/main/java/com/kitfox/svg/composite/AdobeComposite.java
new file mode 100644
index 0000000..00cd929
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/composite/AdobeComposite.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/composite/AdobeCompositeContext.java b/src/main/java/com/kitfox/svg/composite/AdobeCompositeContext.java
new file mode 100644
index 0000000..86185a2
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/composite/AdobeCompositeContext.java
@@ -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() {
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/package-info.java b/src/main/java/com/kitfox/svg/package-info.java
new file mode 100644
index 0000000..ebced40
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/package-info.java
@@ -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; \ No newline at end of file
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Arc.java b/src/main/java/com/kitfox/svg/pathcmd/Arc.java
new file mode 100644
index 0000000..c48d149
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Arc.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/BuildHistory.java b/src/main/java/com/kitfox/svg/pathcmd/BuildHistory.java
new file mode 100644
index 0000000..d16c11a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/BuildHistory.java
@@ -0,0 +1,62 @@
+/*
+ * 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()};
+ 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 setPointAndKnot(float x, float y, float kx, float ky)
+ {
+ history[0].setLocation(x, y);
+ history[1].setLocation(kx, ky);
+ length = 2;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Cubic.java b/src/main/java/com/kitfox/svg/pathcmd/Cubic.java
new file mode 100644
index 0000000..f61ec4f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Cubic.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/CubicSmooth.java b/src/main/java/com/kitfox/svg/pathcmd/CubicSmooth.java
new file mode 100644
index 0000000..a54b9e4
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/CubicSmooth.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Horizontal.java b/src/main/java/com/kitfox/svg/pathcmd/Horizontal.java
new file mode 100644
index 0000000..5aa6c37
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Horizontal.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/LineTo.java b/src/main/java/com/kitfox/svg/pathcmd/LineTo.java
new file mode 100644
index 0000000..2c76508
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/LineTo.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/MoveTo.java b/src/main/java/com/kitfox/svg/pathcmd/MoveTo.java
new file mode 100644
index 0000000..7339607
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/MoveTo.java
@@ -0,0 +1,66 @@
+/*
+ * 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);
+ }
+
+ public int getNumKnotsAdded()
+ {
+ return 2;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/PathCommand.java b/src/main/java/com/kitfox/svg/pathcmd/PathCommand.java
new file mode 100644
index 0000000..33faca1
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/PathCommand.java
@@ -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();
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/PathUtil.java b/src/main/java/com/kitfox/svg/pathcmd/PathUtil.java
new file mode 100644
index 0000000..afd562d
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/PathUtil.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Quadratic.java b/src/main/java/com/kitfox/svg/pathcmd/Quadratic.java
new file mode 100644
index 0000000..34ace18
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Quadratic.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/QuadraticSmooth.java b/src/main/java/com/kitfox/svg/pathcmd/QuadraticSmooth.java
new file mode 100644
index 0000000..2bbc6bf
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/QuadraticSmooth.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Terminal.java b/src/main/java/com/kitfox/svg/pathcmd/Terminal.java
new file mode 100644
index 0000000..a9d1c9f
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Terminal.java
@@ -0,0 +1,56 @@
+/*
+ * 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();
+ }
+
+ public int getNumKnotsAdded()
+ {
+ return 0;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pathcmd/Vertical.java b/src/main/java/com/kitfox/svg/pathcmd/Vertical.java
new file mode 100644
index 0000000..ca7bda4
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pathcmd/Vertical.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/pattern/PatternPaint.java b/src/main/java/com/kitfox/svg/pattern/PatternPaint.java
new file mode 100644
index 0000000..30864a9
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pattern/PatternPaint.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/pattern/PatternPaintContext.java b/src/main/java/com/kitfox/svg/pattern/PatternPaintContext.java
new file mode 100644
index 0000000..d7e4c2e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/pattern/PatternPaintContext.java
@@ -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));
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/ColorTable.java b/src/main/java/com/kitfox/svg/xml/ColorTable.java
new file mode 100644
index 0000000..f3db3b2
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/ColorTable.java
@@ -0,0 +1,273 @@
+/*
+ * 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();
+ 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 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;
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java b/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java
new file mode 100644
index 0000000..77bb895
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java
@@ -0,0 +1,89 @@
+/*
+ * 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; }
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java b/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java
new file mode 100644
index 0000000..c7416ba
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java
@@ -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);
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/StyleAttribute.java b/src/main/java/com/kitfox/svg/xml/StyleAttribute.java
new file mode 100644
index 0000000..70cf71a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/StyleAttribute.java
@@ -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;
+ 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();
+ }
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java b/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java
new file mode 100644
index 0000000..09bf00e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java
@@ -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);
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java b/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java
new file mode 100644
index 0000000..b9cbec0
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java
@@ -0,0 +1,806 @@
+/*
+ * 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)?").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 = (float)Toolkit.getDefaultToolkit().getScreenResolution();
+ 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);
+ if (fpMatch.group(6).equals("%")) 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 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;"
+ * @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++) {
+ 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;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java
new file mode 100644
index 0000000..6cbcaba
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java
@@ -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;
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java
new file mode 100644
index 0000000..1e6ee36
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java
@@ -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();
+ */ \ No newline at end of file
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java
new file mode 100644
index 0000000..f129cfb
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java
new file mode 100644
index 0000000..1b6954a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/xml/formControls.js b/src/main/java/xml/formControls.js
new file mode 100644
index 0000000..7c951c2
--- /dev/null
+++ b/src/main/java/xml/formControls.js
@@ -0,0 +1,27 @@
+
+/**
+ * Layout a form control. Must be called to initialize and whenever the
+ * form's dimensions change.
+ */
+function pack()
+{
+}
+
+function moveLabel(name, x, y, width, height)
+{
+ var clipRect = svgDocument.getElementById(name + "-clip-rect");
+ clipRect.setAttribute("x", x);
+ clipRect.setAttribute("y", y);
+ clipRect.setAttribute("width", width);
+ clipRect.setAttribute("height", height);
+
+ var rect = svgDocument.getElementById(name + "-rect");
+ rect.setAttribute("x", x);
+ rect.setAttribute("y", y);
+ rect.setAttribute("width", width);
+ rect.setAttribute("height", height);
+
+ var text = svgDocument.getElementById(name + "-text");
+ rect.setAttribute("x", x);
+ rect.setAttribute("y", y);
+} \ No newline at end of file
diff --git a/src/main/java/xml/postXform.html b/src/main/java/xml/postXform.html
new file mode 100644
index 0000000..970f622
--- /dev/null
+++ b/src/main/java/xml/postXform.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <!--script type="text/ecmascript" src="formControls.js"/-->
+ <!--script type="javascript" src="formControls.js"/-->
+ </head>
+ <body>
+ <h3>SVG image</h3>
+
+ <p><object data="postXform.svg" width="500" height="500" type="image/svg-xml">
+ <embed src="postXform.svg" width="500" height="500" type="image/svg-xml" />
+ </object></p>
+ </body>
+</html> \ No newline at end of file
diff --git a/src/main/java/xml/postXform.svg b/src/main/java/xml/postXform.svg
new file mode 100644
index 0000000..6a360ea
--- /dev/null
+++ b/src/main/java/xml/postXform.svg
@@ -0,0 +1,142 @@
+<!--
+<svg onload="moveLabel('label1', 50, 50, 200, 24)">
+ <script type="text/ecmascript" src="formControls.js"/>
+ -->
+<!--svg onload="moveLabel('label1', 50, 50, 200, 24)"-->
+<!--svg onload="zzz.doIt(); m_label1.moveLabel(50, 50, 200, 24)"-->
+<svg onload="m_label1.setSize(200, 24);m_label1.setLocation(50, 50)">
+<!--svg-->
+
+
+ <script type="text/javascript">
+ <![CDATA[
+
+/**
+ * Fake inheritance in JS
+ */
+Object.prototype.inheritFrom=inheritFrom;
+Object.prototype.callSuper=callSuper;
+
+function inheritFrom(ancestor)
+{
+ this.ancestor=ancestor;
+ for (x in ancestor)
+ {
+ if (!(this [x])) this[x] = ancestor[x];
+ }
+}
+
+function callSuper(func)
+{
+ if (!this.ancestor) return;
+
+ var old = eval(this[func]);
+ this[func] = this.ancestor[func];
+ var temp = arguments;
+ var callString = (arguments.length == 1) ? "this[func]()" : "this[func](";
+
+ for (x=1; x<temp.length; x++)
+ {
+ callString += "temp[" + x + "]";
+ callString += (x == temp.length - 1) ? ")" : ",";
+ }
+
+ eval(callString);
+ this[func] = old;
+}
+
+
+
+function aaa()
+{
+ this.doIt = function() { alert("aaa"); }
+}
+
+function bbb()
+{
+ this.inheritFrom(new aaa());
+ this.doIt = function() { alert("bbb"); }
+}
+
+var zzz = new bbb();
+
+
+var m_label1 = new label("label1");
+
+/**
+ * Layout a form control. Must be called to initialize and whenever the
+ * form''s dimensions change.
+ */
+function pack()
+{
+}
+
+function container(name, x, y, width, height)
+{
+ this.name = name;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+
+ this.getWidth = function() { return this.width; }
+ this.getHeight = function() { return this.height; }
+ this.getX = function() { return this.x; }
+ this.getY = function() { return this.y; }
+
+// this.setWidth = function(width) { this.width = width; }
+// this.setHeight = function(height) { this.height = height; }
+// this.setX = function(x) { this.x = x; }
+// this.setY = function(y) { this.y = y; }
+
+ this.setSize = function(width, height)
+ {
+ this.width = width;
+ this.height = height;
+
+ var rect = svgDocument.getElementById(this.name + "-rect");
+ rect.setAttribute("width", width);
+ rect.setAttribute("height", height);
+ }
+
+ this.setLocation = function(x, y)
+ {
+ this.x = x;
+ this.y = y;
+
+ var rect = svgDocument.getElementById(this.name + "-rect");
+ rect.setAttribute("x", x);
+ rect.setAttribute("y", y);
+ }
+}
+
+
+function label(name)
+{
+ this.inheritFrom(new container(name));
+
+ this.setLocation = function(x, y)
+ {
+ this.callSuper("setLocation", x, y);
+
+ var text = svgDocument.getElementById(this.name + "-text");
+ text.setAttribute("x", x);
+ text.setAttribute("y", y + this.getHeight());
+ }
+
+//http://www.javascriptkit.com/javatutors/object4.shtml
+}
+
+ // ]]>
+ </script>
+
+
+ <g name="label1">
+ <clipPath id="label1-clip">
+ <rect id="label1-rect" x="20" y="50" width="100" height="24" style="fill:lightGray;stroke:black;"/>
+ </clipPath>
+ <use xlink:href="#label1-rect"/>
+ <text id="label1-text" x="30" y="10" style="fill:black; font-size: 20;clip-path:url(#label1-clip)">Hello good day</text>
+ <!--text id="label1-text" x="30" y="10" style="fill:black; font-size: 20;">Hello good day</text-->
+ </g>
+</svg> \ No newline at end of file
diff --git a/src/main/java/xml/sampleForm.xml b/src/main/java/xml/sampleForm.xml
new file mode 100644
index 0000000..3e8479a
--- /dev/null
+++ b/src/main/java/xml/sampleForm.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Document : sampleForm.xml
+ Created on : October 21, 2004, 7:21 PM
+ Author : kitfox
+ Description:
+ Purpose of the document follows.
+-->
+
+<form x="0" y="0" width="640" height="480">
+ <radio-group name="stoplight"/>
+
+ <panel>
+ <border-layout>
+ <north>
+ <label text="My Window"/>
+ </north>
+ <center>
+ <panel>
+ <box-layout axis="y-axis">
+ <panel>
+ <flow-layout>
+ <label text="Press this button: "/>
+ <button text="Click Me!" onClick="alert('Hello there')"/>
+ </flow-layout>
+ </panel>
+ <panel>
+ <flow-layout>
+ <radio-button name="red" button-group="stoplight" text="red" selected="true"/>
+ <radio-button name="yellow" button-group="stoplight" text="yellow"/>
+ <radio-button name="green" button-group="stoplight" text="green"/>
+ </flow-layout>
+ </panel>
+ </box-layout>
+ </panel>
+ </center>
+ </border-layout>
+ </panel>
+
+</form>
diff --git a/src/main/res/example/duke.svg b/src/main/res/example/duke.svg
new file mode 100644
index 0000000..d13b507
--- /dev/null
+++ b/src/main/res/example/duke.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="100" height="100">
+ <g transform="scale(1,1)">
+ <polygon points="0 100 50 0 100 100" fill="black"/>
+ <polygon points="0 100 25 50 75 50 100 100" fill="white"/>
+ <circle cx="50" cy="50" r="10" fill="red"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/src/main/res/res/help/about/about.html b/src/main/res/res/help/about/about.html
new file mode 100644
index 0000000..8923f65
--- /dev/null
+++ b/src/main/res/res/help/about/about.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C/DTD HTML 1.0 Transitional//EN">
+<html xmlns:java="http://xml.apache.org/xslt/java">
+<head>
+<META http-equiv="Content-Type" content="text/html">
+<title>About SVG Salamander</title>
+</head>
+<body>
+<div style="text-align: center;">
+<h3>SVG Salamander</h3>
+ Created by Mark McKay<br>
+ Copyright 2005<br>
+<br>
+ http://svgsalamander.dev.java.net<br>
+ http://www.kitfox.com<br>
+<br>
+ Last built: 2007, April, 18 02:09<br>
+</div>
+</body>
+</html>
diff --git a/src/main/res/res/icons/SVGUniverseIcon_16c.png b/src/main/res/res/icons/SVGUniverseIcon_16c.png
new file mode 100644
index 0000000..53e1091
--- /dev/null
+++ b/src/main/res/res/icons/SVGUniverseIcon_16c.png
Binary files differ
diff --git a/src/main/res/res/icons/SVGUniverseIcon_32c.png b/src/main/res/res/icons/SVGUniverseIcon_32c.png
new file mode 100644
index 0000000..05f34c8
--- /dev/null
+++ b/src/main/res/res/icons/SVGUniverseIcon_32c.png
Binary files differ
diff --git a/src/main/res/res/icons/cursor.svg b/src/main/res/res/icons/cursor.svg
new file mode 100644
index 0000000..1b74599
--- /dev/null
+++ b/src/main/res/res/icons/cursor.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+ width="26" height="31" viewBox="-0.079 -0.029 26 31" xml:space="preserve">
+ <defs>
+ </defs>
+ <g>
+ <polygon style="fill:#FFFFFF;stroke:#000000;" points="0.672,29.262 0.505,0.887 24.505,15.324 "/>
+ </g>
+</svg>
diff --git a/src/main/res/xml/about.xml b/src/main/res/xml/about.xml
new file mode 100644
index 0000000..1ec0798
--- /dev/null
+++ b/src/main/res/xml/about.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<about>
+</about> \ No newline at end of file
diff --git a/src/main/res/xml/about.xsl b/src/main/res/xml/about.xsl
new file mode 100644
index 0000000..7b4b938
--- /dev/null
+++ b/src/main/res/xml/about.xsl
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+ Document : about.xsl
+ Created on : October 18, 2005, 5:16 PM
+ Author : kitfox
+ Description:
+ Purpose of transformation follows.
+-->
+
+<xsl:stylesheet
+xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+xmlns:java="http://xml.apache.org/xslt/java"
+version="1.0">
+ <xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C/DTD HTML 1.0 Transitional//EN"/>
+ <!--xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C/DTD HTML 1.0 Transitional//EN"/-->
+
+ <!-- TODO customize transformation rules
+ syntax recommendation http://www.w3.org/TR/xslt
+ -->
+ <xsl:template match="/">
+
+<xsl:variable name="dateformat" select="'yyyy, MMMMM, d hh:mm'"/>
+<xsl:variable name="formatter" select="java:java.text.SimpleDateFormat.new($dateformat)"/>
+<xsl:variable name="date" select="java:java.util.Date.new()"/>
+
+
+<html>
+ <head>
+ <title>About SVG Salamander</title>
+ </head>
+ <body>
+
+ <div style="text-align: center;">
+ <h3>SVG Salamander</h3>
+ Created by Mark McKay<br/>
+ Copyright 2005<br/>
+ <br/>
+ http://svgsalamander.dev.java.net<br/>
+ http://www.kitfox.com<br/>
+ <br/>
+ Last built: <xsl:value-of select="java:format($formatter, $date)"/><br/>
+ </div>
+
+ </body>
+</html>
+
+ </xsl:template>
+
+</xsl:stylesheet>