All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.svg.SvgPath Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.svg;

import com.hfg.css.CSS;
import com.hfg.css.CSSDeclaration;
import com.hfg.graphics.Graphics2DState;
import com.hfg.svg.path.SvgPathCmd;
import com.hfg.svg.path.SvgPathCurveToCmd;
import com.hfg.svg.path.SvgPathQuadCurveToCmd;
import com.hfg.svg.path.SvgPathSmoothCurveToCmd;
import com.hfg.svg.path.SvgPathSmoothQuadCurveToCmd;
import com.hfg.graphics.ColorUtil;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLTag;

import java.awt.*;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

//------------------------------------------------------------------------------
/**
 * Object representation of an SVG (Scalable Vector Graphics) path tag.
 *
 * @author J. Alex Taylor, hairyfatguy.com
 */
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// From http://www.w3.org/TR/SVG/paths.html
//
// Superfluous white space and separators such as commas can be eliminated
// (e.g., "M 100 100 L 200 200" contains unnecessary spaces and could be expressed more compactly as "M100 100L200 200").
// The command letter can be eliminated on subsequent commands if the same command is used multiple times in a row
// (e.g., you can drop the second "L" in "M 100 200 L 200 100 L -100 -200" and use "M 100 200 L 200 100 -100 -200" instead).

public class SvgPath extends AbstractSvgNode implements SvgNode
{
//   private static Color  DEFAULT_FILL = HTMLColor.BLACK;

   private List mPathCmds;

   //###########################################################################
   // CONSTRUCTORS
   //###########################################################################

   //---------------------------------------------------------------------------
   public SvgPath()
   {
      super(SVG.path);
//      setFill(DEFAULT_FILL);
   }

   //---------------------------------------------------------------------------
   public SvgPath(String inPathData)
   {
      this();
      setData(inPathData);
   }

   //---------------------------------------------------------------------------
   public SvgPath(XMLTag inXMLTag)
   {
      super(SVG.path);  // Don't call this() or we will pick up the default fill color
      initFromXMLTag(inXMLTag);
   }

   //###########################################################################
   // PUBLIC METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   @Override
   public SvgPath setId(String inValue)
   {
      return (SvgPath) super.setId(inValue);
   }

   //---------------------------------------------------------------------------
   public SvgPath setFill(Color inColor)
   {
      setAttribute(SvgAttr.fill, inColor != null ? "#" + ColorUtil.colorToHex(inColor) : SvgAttr.Value.none);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgPath setStroke(Color inColor)
   {
      setAttribute(SvgAttr.stroke, inColor != null ? "#" + ColorUtil.colorToHex(inColor) : SvgAttr.Value.none);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgPath setStrokeWidth(int inValue)
   {
      setAttribute(SvgAttr.strokeWidth, inValue);
      return this;
   }

   //--------------------------------------------------------------------------
   @Override
   public SvgPath addStyle(CharSequence inValue)
   {
      return (SvgPath) super.addStyle(inValue);
   }

   //---------------------------------------------------------------------------
   @Override
   public SvgPath setStyle(CharSequence inValue)
   {
      return (SvgPath) super.setStyle(inValue);
   }

   //---------------------------------------------------------------------------
   @Override
   public SvgPath setTransform(String inValue)
   {
      return (SvgPath) super.setTransform(inValue);
   }

   //---------------------------------------------------------------------------
   public SvgPath setOnMouseOver(String inValue)
   {
      setAttribute(SvgAttr.onmouseover, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgPath setOnMouseOut(String inValue)
   {
      setAttribute(SvgAttr.onmouseout, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgPath setOnMouseDown(String inValue)
   {
      setAttribute(SvgAttr.onmousedown, inValue);
      return this;
   }



   //---------------------------------------------------------------------------
   public SvgPath setData(String inValue)
   {
      setAttribute(SvgAttr.d, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public String getData()
   {
      return getAttributeValue(SvgAttr.d);
   }

   //--------------------------------------------------------------------------
   @Override
   public String toString()
   {
      return toXML();
   }

   //---------------------------------------------------------------------------
   public SvgPath addPathCommand(SvgPathCmd inValue)
   {
      if (inValue != null)
      {
         if (null == mPathCmds)
         {
            mPathCmds = new ArrayList<>(20);
         }

         mPathCmds.add(inValue);

         setData(StringUtil.join(mPathCmds, " "));
      }

      return this;
   }

   //---------------------------------------------------------------------------
   public List getPathCommands()
   {
      if (null == mPathCmds
            && StringUtil.isSet(getData()))
      {
         mPathCmds = parsePathData();
      }

      return mPathCmds;
   }

   //---------------------------------------------------------------------------
   // Path data could look like 'M 100 100 L 300 100 L 200 300 z'
   // or like
   @Override
   public Rectangle getBoundsBox()
   {
      Rectangle boundsBox = null;

      List pathCmds = getPathCommands();
      if (CollectionUtil.hasValues(pathCmds))
      {
         boundsBox = generateGeneralPath().getBounds();
      }

      adjustBoundsForTransform(boundsBox);

      return boundsBox;
   }

   //--------------------------------------------------------------------------
   @Override
   public void draw(Graphics2D g2, CSS inCSS)
   {
      // Save settings
      Graphics2DState origState = new Graphics2DState(g2);

      List cssDeclarations = getCSSDeclarations(inCSS);

      GeneralPath path = generateGeneralPath();

      // Fill the path
      Paint paint = getG2Paint(cssDeclarations);
      if (paint != null)
      {
         g2.setPaint(paint);
         g2.fill(path);
      }

      // Set the stroke
      BasicStroke stroke = getG2Stroke(cssDeclarations);
      if (stroke != null)
      {
         g2.setStroke(stroke);
      }

      if (stroke.getLineWidth() > 0
          || paint != null)
      {
         // Set the stroke color
         paint = getG2StrokeColor(cssDeclarations);
         if (null == paint) paint = Color.BLACK;
         g2.setPaint(paint);

         applyTransforms(g2);

         g2.draw(path);
      }

      // Restore settings
      origState.applyTo(g2);
   }

   //--------------------------------------------------------------------------
   /**
    Generates the path for a curly brace between two specified points.
    * @param inPoint1 "from" point
    * @param inPoint2 "to" point
    * @param inWidth  width of the curly brace
    * @return the SVG path for the curly brace
    */
   public static SvgPath generateCurlyBracket(Point2D inPoint1, Point2D inPoint2, int inWidth)
   {
      // Calculate unit vector
      double dx = inPoint1.getX() - inPoint2.getX();
      double dy = inPoint1.getY() - inPoint2.getY();
      double len = Math.sqrt(dx * dx + dy * dy);
      dx = dx / len;
      dy = dy / len;

      double q = 0.5;

      // Calculate path control points
      double qx1 = inPoint1.getX() + q * inWidth * dy;
      double qy1 = inPoint1.getY() - q * inWidth * dx;
      double qx2 = (inPoint1.getX() - .25*len * dx) + (1-q) * inWidth * dy;
      double qy2 = (inPoint1.getY() - .25*len * dy) - (1-q) * inWidth * dx;
      double tx1 = (inPoint1.getX() -  .5*len * dx) + inWidth * dy;
      double ty1 = (inPoint1.getY() -  .5*len * dy) - inWidth * dx;
      double qx3 = inPoint2.getX() + q * inWidth * dy;
      double qy3 = inPoint2.getY() - q * inWidth * dx;
      double qx4 = (inPoint1.getX() - .75 * len * dx) + (1-q) * inWidth * dy;
      double qy4 = (inPoint1.getY() - .75 * len * dy) - (1-q) * inWidth * dx;


      return new SvgPath("M " + (int) inPoint1.getX() + " " + (int) inPoint1.getY() +
               		    " Q " + (int) qx1 + " " + (int) qy1 + " " + (int) qx2 + " " + (int) qy2 +
                		    " T " + (int) tx1 + " " + (int) ty1 +
                	       " M " + (int) inPoint2.getX() + " " + (int) inPoint2.getY() +
                		    " Q " + (int) qx3 + " " + (int) qy3 + " " + (int) qx4 + " " + (int) qy4 +
                		    " T " + (int) tx1 + " " + (int) ty1 ).setFill(null).setStroke(Color.BLACK);
   }

   //###########################################################################
   // PROTECTED METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   protected GeneralPath generateGeneralPath()
   {
      int numSteps = 0;
      for (SvgPathCmd cmd : getPathCommands())
      {
         numSteps += cmd.getNumSteps();
      }

      GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, numSteps);

      Point2D.Float lastPoint = null;
      SvgPathCmd prevCmd = null;
      for (SvgPathCmd cmd : getPathCommands())
      {
         cmd.setStartingPoint(lastPoint);
         // The smooth (shorthand) curve cmds need extra info from the previous cmd
         if (cmd instanceof SvgPathSmoothCurveToCmd
               && prevCmd != null
               && prevCmd instanceof SvgPathCurveToCmd)
         {
            ((SvgPathSmoothCurveToCmd)cmd).setPrevCmdSecondControlPoint(((SvgPathCurveToCmd)prevCmd).getSecondControlPoint());
         }
         else if (cmd instanceof SvgPathSmoothQuadCurveToCmd
                  && prevCmd != null
                  && prevCmd instanceof SvgPathQuadCurveToCmd)
         {
            ((SvgPathSmoothQuadCurveToCmd)cmd).setPrevCmdControlPoint(((SvgPathQuadCurveToCmd) prevCmd).getControlPoint());
         }

         lastPoint = cmd.draw(path);
         prevCmd = cmd;
      }

      return path;
   }

   //###########################################################################
   // PRIVATE METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   private List parsePathData()
   {
      List commands = new ArrayList<>();

      SvgPathCmd currentCmd = null;
      StringBuilder numBuffer = new StringBuilder();
      List rawNumbers = new ArrayList<>(25);
      String pathData = getData();
      for (int i = 0; i < pathData.length(); i++)
      {
         char theChar = pathData.charAt(i);

         if (Character.isLetter(theChar)
             && theChar != 'e')
         {
            if (currentCmd != null)
            {
               if (numBuffer.length() > 0)
               {
                  rawNumbers.add(Float.parseFloat(numBuffer.toString()));
                  numBuffer.setLength(0);
               }

               currentCmd.setRawNumbers(rawNumbers);
               rawNumbers.clear();
            }
            currentCmd = SvgPathCmd.allocate(theChar);
            if (null == currentCmd)
            {
               throw new RuntimeException(StringUtil.singleQuote(theChar) + " is not a recognized SVG path command!");
            }

            commands.add(currentCmd);
         }
         else if (theChar == ','
                  || Character.isWhitespace(theChar))
         {
            if (numBuffer.length() > 0)
            {
               rawNumbers.add(Float.parseFloat(numBuffer.toString()));
               numBuffer.setLength(0);
            }
         }
         else
         {
            if (theChar == '-'
                && numBuffer.length() > 0
                && numBuffer.charAt(numBuffer.length() - 1) != 'e')
            {
               rawNumbers.add(Float.parseFloat(numBuffer.toString()));
               numBuffer.setLength(0);
            }

            numBuffer.append(theChar);
         }
      }

      if (numBuffer.length() > 0)
      {
         rawNumbers.add(Float.parseFloat(numBuffer.toString()));
      }

      if (CollectionUtil.hasValues(rawNumbers))
      {
         currentCmd.setRawNumbers(rawNumbers);
      }

      return commands;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy