From 091a1e0179cb264cc2cab6e3b11ea31045c8536d Mon Sep 17 00:00:00 2001 From: kitfox Date: Tue, 29 May 2007 23:33:23 +0000 Subject: 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 --- build.xml | 53 +- lib/library/ant.jar | Bin 0 -> 999966 bytes manifest.mf | 2 +- src/main/java/com/kitfox/svg/Circle.java | 175 +++ src/main/java/com/kitfox/svg/ClipPath.java | 159 +++ src/main/java/com/kitfox/svg/Defs.java | 71 + src/main/java/com/kitfox/svg/Desc.java | 58 + src/main/java/com/kitfox/svg/Ellipse.java | 191 +++ src/main/java/com/kitfox/svg/FeDistantLight.java | 100 ++ src/main/java/com/kitfox/svg/FeLight.java | 50 + src/main/java/com/kitfox/svg/FePointLight.java | 114 ++ src/main/java/com/kitfox/svg/FeSpotLight.java | 177 +++ src/main/java/com/kitfox/svg/FillElement.java | 51 + src/main/java/com/kitfox/svg/Filter.java | 229 ++++ src/main/java/com/kitfox/svg/FilterEffects.java | 233 ++++ src/main/java/com/kitfox/svg/Font.java | 249 ++++ src/main/java/com/kitfox/svg/FontFace.java | 215 +++ src/main/java/com/kitfox/svg/Glyph.java | 102 ++ src/main/java/com/kitfox/svg/Gradient.java | 282 ++++ src/main/java/com/kitfox/svg/Group.java | 275 ++++ src/main/java/com/kitfox/svg/ImageSVG.java | 299 ++++ src/main/java/com/kitfox/svg/Line.java | 172 +++ src/main/java/com/kitfox/svg/LinearGradient.java | 210 +++ src/main/java/com/kitfox/svg/Metadata.java | 46 + src/main/java/com/kitfox/svg/MissingGlyph.java | 252 ++++ src/main/java/com/kitfox/svg/Path.java | 153 +++ src/main/java/com/kitfox/svg/PatternSVG.java | 303 +++++ src/main/java/com/kitfox/svg/Polygon.java | 177 +++ src/main/java/com/kitfox/svg/Polyline.java | 155 +++ src/main/java/com/kitfox/svg/RadialGradient.java | 222 +++ src/main/java/com/kitfox/svg/Rect.java | 267 ++++ .../java/com/kitfox/svg/RenderableElement.java | 161 +++ src/main/java/com/kitfox/svg/SVGCache.java | 28 + src/main/java/com/kitfox/svg/SVGDiagram.java | 217 +++ src/main/java/com/kitfox/svg/SVGDisplayPanel.form | 16 + src/main/java/com/kitfox/svg/SVGDisplayPanel.java | 194 +++ src/main/java/com/kitfox/svg/SVGElement.java | 853 ++++++++++++ .../java/com/kitfox/svg/SVGElementException.java | 56 + src/main/java/com/kitfox/svg/SVGException.java | 47 + src/main/java/com/kitfox/svg/SVGLoader.java | 267 ++++ src/main/java/com/kitfox/svg/SVGLoaderHelper.java | 79 ++ .../java/com/kitfox/svg/SVGParseException.java | 47 + src/main/java/com/kitfox/svg/SVGRoot.java | 390 ++++++ src/main/java/com/kitfox/svg/SVGUniverse.java | 533 ++++++++ src/main/java/com/kitfox/svg/ShapeElement.java | 299 ++++ src/main/java/com/kitfox/svg/Stop.java | 129 ++ src/main/java/com/kitfox/svg/Style.java | 83 ++ src/main/java/com/kitfox/svg/Symbol.java | 158 +++ src/main/java/com/kitfox/svg/Text.java | 539 ++++++++ src/main/java/com/kitfox/svg/Title.java | 65 + .../java/com/kitfox/svg/TransformableElement.java | 117 ++ src/main/java/com/kitfox/svg/Tspan.java | 380 ++++++ src/main/java/com/kitfox/svg/Use.java | 255 ++++ .../com/kitfox/svg/animation/AnimTimeParser.jjt | 316 +++++ .../java/com/kitfox/svg/animation/Animate.java | 354 +++++ .../java/com/kitfox/svg/animation/AnimateBase.java | 92 ++ .../com/kitfox/svg/animation/AnimateColor.java | 81 ++ .../kitfox/svg/animation/AnimateColorIface.java | 38 + .../com/kitfox/svg/animation/AnimateMotion.java | 234 ++++ .../com/kitfox/svg/animation/AnimateTransform.java | 225 ++++ .../com/kitfox/svg/animation/AnimateXform.java | 52 + .../com/kitfox/svg/animation/AnimationElement.java | 336 +++++ .../kitfox/svg/animation/AnimationTimeEval.java | 65 + src/main/java/com/kitfox/svg/animation/Bezier.java | 201 +++ .../java/com/kitfox/svg/animation/SetSmil.java | 55 + .../java/com/kitfox/svg/animation/TimeBase.java | 99 ++ .../com/kitfox/svg/animation/TimeCompound.java | 96 ++ .../com/kitfox/svg/animation/TimeDiscrete.java | 51 + .../com/kitfox/svg/animation/TimeIndefinite.java | 48 + .../java/com/kitfox/svg/animation/TimeLookup.java | 75 ++ .../java/com/kitfox/svg/animation/TimeSum.java | 60 + .../java/com/kitfox/svg/animation/TrackBase.java | 104 ++ .../java/com/kitfox/svg/animation/TrackColor.java | 95 ++ .../java/com/kitfox/svg/animation/TrackDouble.java | 119 ++ .../com/kitfox/svg/animation/TrackManager.java | 153 +++ .../java/com/kitfox/svg/animation/TrackMotion.java | 128 ++ .../java/com/kitfox/svg/animation/TrackPath.java | 100 ++ .../com/kitfox/svg/animation/TrackTransform.java | 112 ++ src/main/java/com/kitfox/svg/app/MainFrame.form | 65 + src/main/java/com/kitfox/svg/app/MainFrame.java | 134 ++ src/main/java/com/kitfox/svg/app/PlayerDialog.form | 133 ++ src/main/java/com/kitfox/svg/app/PlayerDialog.java | 289 ++++ src/main/java/com/kitfox/svg/app/PlayerThread.java | 129 ++ .../com/kitfox/svg/app/PlayerThreadListener.java | 37 + src/main/java/com/kitfox/svg/app/SVGPlayer.form | 118 ++ src/main/java/com/kitfox/svg/app/SVGPlayer.java | 426 ++++++ src/main/java/com/kitfox/svg/app/SVGViewer.form | 118 ++ src/main/java/com/kitfox/svg/app/SVGViewer.java | 421 ++++++ .../java/com/kitfox/svg/app/VersionDialog.form | 64 + .../java/com/kitfox/svg/app/VersionDialog.java | 151 +++ .../com/kitfox/svg/app/ant/SVGToImageAntTask.java | 251 ++++ .../svg/app/beans/ProportionalLayoutPanel.form | 29 + .../svg/app/beans/ProportionalLayoutPanel.java | 91 ++ .../java/com/kitfox/svg/app/beans/SVGIcon.java | 385 ++++++ .../java/com/kitfox/svg/app/beans/SVGPanel.form | 13 + .../java/com/kitfox/svg/app/beans/SVGPanel.java | 243 ++++ .../java/com/kitfox/svg/batik/GraphicsUtil.java | 382 ++++++ .../com/kitfox/svg/batik/LinearGradientPaint.java | 354 +++++ .../svg/batik/LinearGradientPaintContext.java | 529 ++++++++ .../kitfox/svg/batik/MultipleGradientPaint.java | 236 ++++ .../svg/batik/MultipleGradientPaintContext.java | 1421 ++++++++++++++++++++ .../com/kitfox/svg/batik/RadialGradientPaint.java | 491 +++++++ .../svg/batik/RadialGradientPaintContext.java | 775 +++++++++++ .../com/kitfox/svg/composite/AdobeComposite.java | 70 + .../svg/composite/AdobeCompositeContext.java | 97 ++ src/main/java/com/kitfox/svg/package-info.java | 9 + src/main/java/com/kitfox/svg/pathcmd/Arc.java | 245 ++++ .../java/com/kitfox/svg/pathcmd/BuildHistory.java | 62 + src/main/java/com/kitfox/svg/pathcmd/Cubic.java | 74 + .../java/com/kitfox/svg/pathcmd/CubicSmooth.java | 78 ++ .../java/com/kitfox/svg/pathcmd/Horizontal.java | 65 + src/main/java/com/kitfox/svg/pathcmd/LineTo.java | 67 + src/main/java/com/kitfox/svg/pathcmd/MoveTo.java | 66 + .../java/com/kitfox/svg/pathcmd/PathCommand.java | 56 + src/main/java/com/kitfox/svg/pathcmd/PathUtil.java | 72 + .../java/com/kitfox/svg/pathcmd/Quadratic.java | 70 + .../com/kitfox/svg/pathcmd/QuadraticSmooth.java | 74 + src/main/java/com/kitfox/svg/pathcmd/Terminal.java | 56 + src/main/java/com/kitfox/svg/pathcmd/Vertical.java | 64 + .../java/com/kitfox/svg/pattern/PatternPaint.java | 60 + .../kitfox/svg/pattern/PatternPaintContext.java | 120 ++ src/main/java/com/kitfox/svg/xml/ColorTable.java | 273 ++++ .../java/com/kitfox/svg/xml/NumberWithUnits.java | 89 ++ .../com/kitfox/svg/xml/ReadableXMLElement.java | 48 + .../java/com/kitfox/svg/xml/StyleAttribute.java | 290 ++++ .../com/kitfox/svg/xml/WritableXMLElement.java | 48 + src/main/java/com/kitfox/svg/xml/XMLParseUtil.java | 806 +++++++++++ .../java/com/kitfox/svg/xml/cpx/CPXConsts.java | 40 + .../com/kitfox/svg/xml/cpx/CPXInputStream.java | 293 ++++ .../com/kitfox/svg/xml/cpx/CPXOutputStream.java | 174 +++ src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java | 100 ++ src/main/java/xml/formControls.js | 27 + src/main/java/xml/postXform.html | 13 + src/main/java/xml/postXform.svg | 142 ++ src/main/java/xml/sampleForm.xml | 41 + src/main/res/example/duke.svg | 8 + src/main/res/res/help/about/about.html | 19 + src/main/res/res/icons/SVGUniverseIcon_16c.png | Bin 0 -> 440 bytes src/main/res/res/icons/SVGUniverseIcon_32c.png | Bin 0 -> 703 bytes src/main/res/res/icons/cursor.svg | 9 + src/main/res/xml/about.xml | 3 + src/main/res/xml/about.xsl | 50 + 142 files changed, 24387 insertions(+), 20 deletions(-) create mode 100644 lib/library/ant.jar create mode 100644 src/main/java/com/kitfox/svg/Circle.java create mode 100644 src/main/java/com/kitfox/svg/ClipPath.java create mode 100644 src/main/java/com/kitfox/svg/Defs.java create mode 100644 src/main/java/com/kitfox/svg/Desc.java create mode 100644 src/main/java/com/kitfox/svg/Ellipse.java create mode 100644 src/main/java/com/kitfox/svg/FeDistantLight.java create mode 100644 src/main/java/com/kitfox/svg/FeLight.java create mode 100644 src/main/java/com/kitfox/svg/FePointLight.java create mode 100644 src/main/java/com/kitfox/svg/FeSpotLight.java create mode 100644 src/main/java/com/kitfox/svg/FillElement.java create mode 100644 src/main/java/com/kitfox/svg/Filter.java create mode 100644 src/main/java/com/kitfox/svg/FilterEffects.java create mode 100644 src/main/java/com/kitfox/svg/Font.java create mode 100644 src/main/java/com/kitfox/svg/FontFace.java create mode 100644 src/main/java/com/kitfox/svg/Glyph.java create mode 100644 src/main/java/com/kitfox/svg/Gradient.java create mode 100644 src/main/java/com/kitfox/svg/Group.java create mode 100644 src/main/java/com/kitfox/svg/ImageSVG.java create mode 100644 src/main/java/com/kitfox/svg/Line.java create mode 100644 src/main/java/com/kitfox/svg/LinearGradient.java create mode 100644 src/main/java/com/kitfox/svg/Metadata.java create mode 100644 src/main/java/com/kitfox/svg/MissingGlyph.java create mode 100644 src/main/java/com/kitfox/svg/Path.java create mode 100644 src/main/java/com/kitfox/svg/PatternSVG.java create mode 100644 src/main/java/com/kitfox/svg/Polygon.java create mode 100644 src/main/java/com/kitfox/svg/Polyline.java create mode 100644 src/main/java/com/kitfox/svg/RadialGradient.java create mode 100644 src/main/java/com/kitfox/svg/Rect.java create mode 100644 src/main/java/com/kitfox/svg/RenderableElement.java create mode 100644 src/main/java/com/kitfox/svg/SVGCache.java create mode 100644 src/main/java/com/kitfox/svg/SVGDiagram.java create mode 100644 src/main/java/com/kitfox/svg/SVGDisplayPanel.form create mode 100644 src/main/java/com/kitfox/svg/SVGDisplayPanel.java create mode 100644 src/main/java/com/kitfox/svg/SVGElement.java create mode 100644 src/main/java/com/kitfox/svg/SVGElementException.java create mode 100644 src/main/java/com/kitfox/svg/SVGException.java create mode 100644 src/main/java/com/kitfox/svg/SVGLoader.java create mode 100644 src/main/java/com/kitfox/svg/SVGLoaderHelper.java create mode 100644 src/main/java/com/kitfox/svg/SVGParseException.java create mode 100644 src/main/java/com/kitfox/svg/SVGRoot.java create mode 100644 src/main/java/com/kitfox/svg/SVGUniverse.java create mode 100644 src/main/java/com/kitfox/svg/ShapeElement.java create mode 100644 src/main/java/com/kitfox/svg/Stop.java create mode 100644 src/main/java/com/kitfox/svg/Style.java create mode 100644 src/main/java/com/kitfox/svg/Symbol.java create mode 100644 src/main/java/com/kitfox/svg/Text.java create mode 100644 src/main/java/com/kitfox/svg/Title.java create mode 100644 src/main/java/com/kitfox/svg/TransformableElement.java create mode 100644 src/main/java/com/kitfox/svg/Tspan.java create mode 100644 src/main/java/com/kitfox/svg/Use.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimTimeParser.jjt create mode 100644 src/main/java/com/kitfox/svg/animation/Animate.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateBase.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateColor.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateColorIface.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateMotion.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateTransform.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimateXform.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimationElement.java create mode 100644 src/main/java/com/kitfox/svg/animation/AnimationTimeEval.java create mode 100644 src/main/java/com/kitfox/svg/animation/Bezier.java create mode 100644 src/main/java/com/kitfox/svg/animation/SetSmil.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeBase.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeCompound.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeDiscrete.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeIndefinite.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeLookup.java create mode 100644 src/main/java/com/kitfox/svg/animation/TimeSum.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackBase.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackColor.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackDouble.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackManager.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackMotion.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackPath.java create mode 100644 src/main/java/com/kitfox/svg/animation/TrackTransform.java create mode 100644 src/main/java/com/kitfox/svg/app/MainFrame.form create mode 100644 src/main/java/com/kitfox/svg/app/MainFrame.java create mode 100644 src/main/java/com/kitfox/svg/app/PlayerDialog.form create mode 100644 src/main/java/com/kitfox/svg/app/PlayerDialog.java create mode 100644 src/main/java/com/kitfox/svg/app/PlayerThread.java create mode 100644 src/main/java/com/kitfox/svg/app/PlayerThreadListener.java create mode 100644 src/main/java/com/kitfox/svg/app/SVGPlayer.form create mode 100644 src/main/java/com/kitfox/svg/app/SVGPlayer.java create mode 100644 src/main/java/com/kitfox/svg/app/SVGViewer.form create mode 100644 src/main/java/com/kitfox/svg/app/SVGViewer.java create mode 100644 src/main/java/com/kitfox/svg/app/VersionDialog.form create mode 100644 src/main/java/com/kitfox/svg/app/VersionDialog.java create mode 100644 src/main/java/com/kitfox/svg/app/ant/SVGToImageAntTask.java create mode 100644 src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.form create mode 100644 src/main/java/com/kitfox/svg/app/beans/ProportionalLayoutPanel.java create mode 100644 src/main/java/com/kitfox/svg/app/beans/SVGIcon.java create mode 100644 src/main/java/com/kitfox/svg/app/beans/SVGPanel.form create mode 100644 src/main/java/com/kitfox/svg/app/beans/SVGPanel.java create mode 100644 src/main/java/com/kitfox/svg/batik/GraphicsUtil.java create mode 100644 src/main/java/com/kitfox/svg/batik/LinearGradientPaint.java create mode 100644 src/main/java/com/kitfox/svg/batik/LinearGradientPaintContext.java create mode 100644 src/main/java/com/kitfox/svg/batik/MultipleGradientPaint.java create mode 100644 src/main/java/com/kitfox/svg/batik/MultipleGradientPaintContext.java create mode 100644 src/main/java/com/kitfox/svg/batik/RadialGradientPaint.java create mode 100644 src/main/java/com/kitfox/svg/batik/RadialGradientPaintContext.java create mode 100644 src/main/java/com/kitfox/svg/composite/AdobeComposite.java create mode 100644 src/main/java/com/kitfox/svg/composite/AdobeCompositeContext.java create mode 100644 src/main/java/com/kitfox/svg/package-info.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Arc.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/BuildHistory.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Cubic.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/CubicSmooth.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Horizontal.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/LineTo.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/MoveTo.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/PathCommand.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/PathUtil.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Quadratic.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/QuadraticSmooth.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Terminal.java create mode 100644 src/main/java/com/kitfox/svg/pathcmd/Vertical.java create mode 100644 src/main/java/com/kitfox/svg/pattern/PatternPaint.java create mode 100644 src/main/java/com/kitfox/svg/pattern/PatternPaintContext.java create mode 100644 src/main/java/com/kitfox/svg/xml/ColorTable.java create mode 100644 src/main/java/com/kitfox/svg/xml/NumberWithUnits.java create mode 100644 src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java create mode 100644 src/main/java/com/kitfox/svg/xml/StyleAttribute.java create mode 100644 src/main/java/com/kitfox/svg/xml/WritableXMLElement.java create mode 100644 src/main/java/com/kitfox/svg/xml/XMLParseUtil.java create mode 100644 src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java create mode 100644 src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java create mode 100644 src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java create mode 100644 src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java create mode 100644 src/main/java/xml/formControls.js create mode 100644 src/main/java/xml/postXform.html create mode 100644 src/main/java/xml/postXform.svg create mode 100644 src/main/java/xml/sampleForm.xml create mode 100644 src/main/res/example/duke.svg create mode 100644 src/main/res/res/help/about/about.html create mode 100644 src/main/res/res/icons/SVGUniverseIcon_16c.png create mode 100644 src/main/res/res/icons/SVGUniverseIcon_32c.png create mode 100644 src/main/res/res/icons/cursor.svg create mode 100644 src/main/res/xml/about.xml create mode 100644 src/main/res/xml/about.xsl diff --git a/build.xml b/build.xml index 467416c..17e83f7 100755 --- a/build.xml +++ b/build.xml @@ -29,14 +29,18 @@ - + - + + + + + @@ -52,8 +56,9 @@ + - + @@ -79,9 +84,18 @@ - - + + + + + + + + + + + @@ -117,20 +131,21 @@ - - - -
- - - - - - -
-
- - + + + + + +
+ + + + + + +
+
+ diff --git a/lib/library/ant.jar b/lib/library/ant.jar new file mode 100644 index 0000000..3a67607 Binary files /dev/null and b/lib/library/ant.jar 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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 @@ + + +
+ + + + + + + + + + + + + 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 Mark McKay + */ +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. + */ + // //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); + } + }); + + }// //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 Mark McKay + */ +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 style attribute. + */ + protected final HashMap inlineStyles = new HashMap(); + + /** + * Presentation attributes set for this element. Ie, any attribute other + * than the style 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 SVGException without detail message. + */ + public SVGElementException(SVGElement element) + { + this(element, null, null); + } + + + /** + * Constructs an instance of SVGException 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 SVGException without detail message. + */ + public SVGException() + { + } + + + /** + * Constructs an instance of SVGException 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 Mark McKay + */ +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 Mark McKay + */ +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 SVGException without detail message. + */ + public SVGParseException() + { + } + + + /** + * Constructs an instance of SVGException 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 Mark McKay + */ +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 Mark McKay + */ +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 svgSalamander. + */ + 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 -

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.

+ *

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.

+ *

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

+ *

If a name does not start with the character '/', it will be automatically + * prefixed to it.

+ * @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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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: ()+ > +| + < FLOAT: (["+", "-"])? ((()* "." ()+) | (()+)) (["E", "e"] (["+", "-"])? ()+)? > +| + < INDEFINITE: "indefinite" > +| + < MOUSE_OVER: "mouseover" > +| + < WHEN_NOT_ACTIVE: "whenNotActive" > +| + < UNITS: "ms" | "s" | "min" | "h" > +| + < IDENTIFIER: (||"_"|"-")* > + +} + + + + + + + +/** + * 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() : +{} +{ + + { + return new TimeIndefinite(); + } +} + +TimeDiscrete EventTime() : +{} +{ + ( | ) + { + //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= + { + //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= "." event= (paramNum=ParamList())? + { + return new TimeLookup(null, node.image, event.image, "" + paramNum); + } +} + +double ParamList() : +{ + double num; +} +{ + "(" num=Number() ")" + { + return num; + } +} + +double Number() : +{ + Token t; +} +{ + t= + { + try { return Double.parseDouble(t.image); } + catch (Exception e) { e.printStackTrace(); } + + return 0.0; + } + | t= + { + try { return Double.parseDouble(t.image); } + catch (Exception e) { e.printStackTrace(); } + + return 0.0; + } +} + +int Integer() : +{ + Token t; +} +{ + t= + { + 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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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; + + /** More about the fill attribute */ + 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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + */ + // //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(); + }// //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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 Mark McKay + */ +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. + */ + // //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(); + }// //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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 Mark McKay + */ +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. + */ + // //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(); + }// //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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + */ + // //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(); + } + // //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; + +/** + *

Translates a group of SVG files into images.

+ * + *

Parameters:

+ *

    + *
  • 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 + *
  • 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. + *
  • 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. + *
  • antiAlias - If set, shapes are drawn using antialiasing. Defaults to true. + *
  • interpolation - String describing image interpolation alrogithm. Can + * be one of "nearest neighbor", "bilinear" or "bicubic". Defaults to "bicubic". + *
  • width - If greater than 0, determines the width of the written image. Otherwise, + * the width is obtained from the SVG document. Defaults to -1; + *
  • height - If greater than 0, determines the height of the written image. Otherwise, + * the height is obtained from the SVG document. Defaults to -1. + *
  • 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. + *
  • verbose - IF true, prints out diagnostic infromation about processing. + * Defaults to false. + *

+ * + * Example: + * <SVGToImage destDir="${index.java}" format="jpg" verbose="true"> + * <fileset dir="${dir1}"> + * <include name="*.svg"/> + * </fileset> + * <fileset dir="${dir2}"> + * <include name="*.svg"/> + * </fileset> + * </SVGToImage> + * + * + * + * @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 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
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. + */ + // //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); + + } + // //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 @@ + + +
+ + + + + + + + + + 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. + */ + // //GEN-BEGIN:initComponents + private void initComponents() + { + + setLayout(new java.awt.BorderLayout()); + + } + // //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= 0) && (a < 255)) + { + alpha = a*norm; + for (b=0; b 0) && (a < 255)) + { + ialpha = 255/(float)a; + for (b=0; b>>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>>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>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>>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 LinearGradientPaint 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. + *

+ * 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). + *

+ * For example: + *
+ * + *

+ * Point2D start = new Point2D.Float(0, 0);
+ * Point2D end = new Point2D.Float(100,100);
+ * float[] dist = {0.0, 0.2, 1.0};
+ * Color[] colors = {Color.red, Color.white, Color.blue};
+ * LinearGradientPaint p = new LinearGradientPaint(start, end, dist, colors); + *
+ *

+ * 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%. + * + *

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:
+ * {Color.blue, Color.red}, {.3, .7}
+ * this will be converted to a gradient with the following keyframes: + * {Color.blue, Color.blue, Color.red, Color.red}, {0, .3, .7, 1} + * + *

+ * 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. + * + *

The following image demonstrates the options NO_CYCLE and REFLECT. + * + *

+ * + * + *

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 Vincent Hardy + * @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; + + /**

+ * Constructs an LinearGradientPaint 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); + } + + /**

+ * Constructs an LinearGradientPaint 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); + } + + /**

+ * Constructs a LinearGradientPaint with the default + * NO_CYCLE repeating method and SRGB colorspace. + * + * @param start the gradient axis start Point in user space + * + * @param end 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 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); + } + + /**

+ * Constructs a LinearGradientPaint. + * + * @param start the gradient axis start Point in user space + * + * @param end 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 + * + * @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()); + + } + + /**

+ * Constructs a LinearGradientPaint. + * + * @param start the gradient axis start Point in user space + * + * @param end 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 + * + * @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 Paint 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 + * LinearGradientPaint. + */ + 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 + * LinearGradientPaint. + */ + 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 Vincent Hardy + * @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 Paint 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= 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=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= 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; i2 + 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; iVincent Hardy + * @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; iVincent Hardy + * @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 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> 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>>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>>20)&0xFF0); + rch += ((pix>>>12)&0xFF0); + gch += ((pix>>> 4)&0xFF0); + bch += ((pix<< 4)&0xFF0); + } + } else { + for (i=0; i>>20)&0xFF0); + rch += ((pix>>>12)&0xFF0); + gch += ((pix>>> 4)&0xFF0); + bch += ((pix<< 4)&0xFF0); + } + } + + if (p2_up) { + for (i=idx2+1; i>>20)&0xFF0); + rch += ((pix>>>12)&0xFF0); + gch += ((pix>>> 4)&0xFF0); + bch += ((pix<< 4)&0xFF0); + } + } else { + for (i=0; i>>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>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>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>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>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>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; + +/** + *

+ * 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. + *

+ * + * 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). + * + *

+ * 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. + * + *

+ * 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. + *

+ * + * 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. + * + *

+ * The following code demonstrates typical usage of RadialGradientPaint: + *

+ * + * Point2D center = new Point2D.Float(0, 0);
+ * float radius = 20; + * float[] dist = {0.0, 0.2, 1.0};
+ * Color[] colors = {Color.red, Color.white, Color.blue};
+ * RadialGradientPaint p = new RadialGradientPaint(center, radius, + * dist, colors); + *
+ * + *

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:
+ * {Color.blue, Color.red}, {.3, .7}
+ * this will be converted to a gradient with the following keyframes: + * {Color.blue, Color.blue, Color.red, Color.red}, {0, .3, .7, 1} + * + * + *

+ * + *

+ * This image demonstrates a radial gradient with NO_CYCLE and default focus. + *

+ * + * + *

+ * This image demonstrates a radial gradient with NO_CYCLE and non-centered + * focus. + *

+ * + * + *

+ * This image demonstrates a radial gradient with REFLECT and non-centered + * focus. + * + * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans + * @author Vincent Hardy + * @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; + + /** + *

+ * + * Constructs a RadialGradientPaint, 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); + } + + /** + *

+ * + * Constructs a RadialGradientPaint, 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); + } + + /** + *

+ * + * Constructs a RadialGradientPaint. + * + * @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); + } + + /** + *

+ * + * Constructs a RadialGradientPaint. + * + * @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); + } + + /** + *

+ * + * Constructs a RadialGradientPaint. + * + * @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()); + } + + /** + *

+ * + * Constructs a RadialGradientPaint. + * + * @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; + } + + /** + *

+ * + * Constructs a RadialGradientPaint, 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); + } + + + /**

+ * 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 Paint 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 Vincent Hardy + * @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 Paint 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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay (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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 Mark McKay + */ +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 in.close(). + * + * @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 int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs in.read() and returns the result. + * + * @return the next byte of data, or -1 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 byte.length bytes of data from this + * input stream into an array of bytes. This method blocks until some + * input is available. + *

+ * This method simply performs the call + * read(b, 0, b.length) and returns + * the result. It is important that it does + * not do in.read(b) instead; + * certain subclasses of FilterInputStream + * 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 + * -1 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 len bytes of data from this input stream + * into an array of bytes. This method blocks until some input is + * available. + *

+ * This method simply performs in.read(b, off, len) + * 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 + * -1 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 Mark McKay + */ +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 byte to this output stream. + *

+ * The write method of FilterOutputStream + * calls the write method of its underlying output stream, + * that is, it performs out.write(b). + *

+ * Implements the abstract write method of OutputStream. + * + * @param b the byte. + * @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 b.length bytes to this output stream. + *

+ * The write method of FilterOutputStream + * calls its write method of three arguments with the + * arguments b, 0, and + * b.length. + *

+ * Note that this method does not call the one-argument + * write method of its underlying stream with the single + * argument b. + * + * @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 len bytes from the specified + * byte array starting at offset off to + * this output stream. + *

+ * The write method of FilterOutputStream + * calls the write method of one argument on each + * byte to output. + *

+ * Note that this method does not call the write method + * of its underlying input stream with the same arguments. Subclasses + * of FilterOutputStream 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. + *

+ * The flush method of FilterOutputStream + * calls the flush 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. + *

+ * The close method of FilterOutputStream + * calls its flush method, and then calls the + * close 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 Mark McKay + */ +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 @@ + + + + + + +

SVG image

+ +

+ +

+ + \ 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 @@ + + + + + + + + + + + + + + + + Hello good day + + + \ 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 @@ + + + + +
+ + + + + + +
+ + + + +
+
+
+ + 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 @@ + + + + + + + + \ 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 @@ + + + + +About SVG Salamander + + +
+

SVG Salamander

+ Created by Mark McKay
+ Copyright 2005
+
+ http://svgsalamander.dev.java.net
+ http://www.kitfox.com
+
+ Last built: 2007, April, 18 02:09
+
+ + 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 Binary files /dev/null and b/src/main/res/res/icons/SVGUniverseIcon_16c.png 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 Binary files /dev/null and b/src/main/res/res/icons/SVGUniverseIcon_32c.png 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 @@ + + + + + + + + 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 @@ + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + About SVG Salamander + + + +
+

SVG Salamander

+ Created by Mark McKay
+ Copyright 2005
+
+ http://svgsalamander.dev.java.net
+ http://www.kitfox.com
+
+ Last built:
+
+ + + + +
+ +
-- cgit v1.2.3-55-g7522