com.hfg.svg.SvgPath Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.svg;
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
//###########################################################################
//---------------------------------------------------------------------------
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(String inValue)
{
return (SvgPath) super.addStyle(inValue);
}
//---------------------------------------------------------------------------
@Override
public SvgPath setStyle(String 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)
{
// Save settings
Graphics2DState origState = new Graphics2DState(g2);
GeneralPath path = generateGeneralPath();
// Fill the path
Paint paint = getG2Paint();
if (paint != null)
{
g2.setPaint(paint);
g2.fill(path);
}
// Set the stroke
Stroke stroke = getG2Stroke();
if (stroke != null)
{
g2.setStroke(stroke);
}
// Set the stroke color
paint = getG2StrokeColor();
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);
}
//###########################################################################
// PRIVATE METHODS
//###########################################################################
//---------------------------------------------------------------------------
private 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 List parsePathData()
{
List commands = new ArrayList<>();
SvgPathCmd currentCmd = null;
StringBuilder numBuffer = new StringBuilder();
List rawNumbers = new ArrayList<>();
String pathData = getData();
for (int i = 0; i < pathData.length(); i++)
{
char theChar = pathData.charAt(i);
if (Character.isLetter(theChar))
{
if (currentCmd != null)
{
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
{
numBuffer.append(theChar);
}
}
if (numBuffer.length() > 0)
{
rawNumbers.add(Float.parseFloat(numBuffer.toString()));
}
if (CollectionUtil.hasValues(rawNumbers))
{
currentCmd.setRawNumbers(rawNumbers);
}
return commands;
}
}