com.hfg.svg.AbstractSvgNode 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 java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.hfg.css.CSSDeclaration;
import com.hfg.css.CSSProperty;
import com.hfg.css.CssUtil;
import com.hfg.graphics.Graphics2DState;
import com.hfg.graphics.GraphicsUtil;
import com.hfg.graphics.units.GfxSize;
import com.hfg.graphics.units.GfxUnits;
import com.hfg.html.attribute.HTMLColor;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLAttribute;
import com.hfg.xml.XMLNamespace;
import com.hfg.xml.XMLTag;
import com.hfg.xml.XMLizable;
//------------------------------------------------------------------------------
/**
* SVG (Scalable Vector Graphics) tag base class.
*
* @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]
//------------------------------------------------------------------------------
public abstract class AbstractSvgNode extends XMLTag implements SvgNode
{
private static final Pattern FILL_PATTERN = Pattern.compile("\\bfill\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern OPACITY_PATTERN = Pattern.compile("\\bopacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern FILL_OPACITY_PATTERN = Pattern.compile("\\bfill-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern STROKE_PATTERN = Pattern.compile("\\bstroke\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern STROKE_WIDTH_PATTERN = Pattern.compile("\\bstroke-width\\s*:\\s*([^;\\s]+?)(:?px)?", Pattern.CASE_INSENSITIVE);
private static final Pattern STROKE_OPACITY_PATTERN = Pattern.compile("\\bstroke-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern TRANSFORM_PATTERN = Pattern.compile("\\btransform\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE);
//---------------------------------------------------------------------------
public AbstractSvgNode(String inTagName)
{
super(inTagName);
setNamespace(XMLNamespace.SVG);
}
//---------------------------------------------------------------------------
protected void initFromXMLTag(XMLTag inXMLTag)
{
inXMLTag.verifyTagName(getTagName());
if (CollectionUtil.hasValues(inXMLTag.getAttributes()))
{
for (XMLAttribute attr : inXMLTag.getAttributes())
{
setAttribute(attr.clone());
}
}
List subtagsAndContent = inXMLTag.getContentPlusSubtagList();
if (CollectionUtil.hasValues(subtagsAndContent))
{
for (Object object : subtagsAndContent)
{
if (object instanceof XMLTag)
{
addSubtag(SVG.constructFromXMLTag((XMLTag) object));
}
else
{
addContent((String)object);
}
}
}
}
//---------------------------------------------------------------------------
public SvgNode setId(String inValue)
{
setAttribute(SvgAttr.id, inValue);
return this;
}
//--------------------------------------------------------------------------
public SvgNode addStyle(String inValue)
{
CssUtil.addStyle(this, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setStyle(String inValue)
{
setAttribute(SvgAttr.style, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setTransform(String inValue)
{
setAttribute(SvgAttr.transform, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setFilter(String inValue)
{
if (! inValue.startsWith("url("))
{
inValue = "url(#" + inValue + ")";
}
setAttribute(SvgAttr.CLASS, inValue);
return this;
}
//--------------------------------------------------------------------------
public SvgNode setClass(String inValue)
{
setAttribute(SvgAttr.CLASS, inValue);
return this;
}
//--------------------------------------------------------------------------
public SvgNode addClass(String inValue)
{
String oldValue = getAttributeValue(SvgAttr.CLASS);
if (oldValue != null)
{
inValue = oldValue + " " + inValue;
}
setAttribute(SvgAttr.CLASS, inValue);
return this;
}
//--------------------------------------------------------------------------
/**
Not called getClass() for obvious reasons.
*/
public String getClassAttribute()
{
return getAttributeValue(SvgAttr.CLASS);
}
//---------------------------------------------------------------------------
public SvgNode setOnMouseOver(String inValue)
{
setAttribute(SvgAttr.onmouseover, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setOnMouseOut(String inValue)
{
setAttribute(SvgAttr.onmouseout, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setOnMouseDown(String inValue)
{
setAttribute(SvgAttr.onmousedown, inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgNode setOnClick(String inValue)
{
setAttribute(SvgAttr.onclick, inValue);
return this;
}
//--------------------------------------------------------------------------
public void draw(Graphics2D g2)
{
// Save settings
Graphics2DState origState = new Graphics2DState(g2);
applyTransforms(g2);
Composite composite = getG2Composite();
if (composite != null)
{
g2.setComposite(composite);
}
Font locallyAdjustedFont = getAdjustedFont(origState.getFont());
if (locallyAdjustedFont != null)
{
g2.setFont(locallyAdjustedFont);
}
drawSubnodes(g2);
// Restore settings
origState.applyTo(g2);
}
//---------------------------------------------------------------------------
public String getCssTransform()
{
String transform = null;
String style = getAttributeValue(SvgAttr.style);
if (StringUtil.isSet(style))
{
List cssDeclarations = CSSDeclaration.parse(style);
for (CSSDeclaration cssDeclaration : cssDeclarations)
{
if (cssDeclaration.getProperty().equals(CSSProperty.transform))
{
transform = cssDeclaration.getValue();
break;
}
}
}
return transform;
}
//---------------------------------------------------------------------------
/**
Specifies the upper left corner of the bounding rectangle.
*/
public SvgNode setPosition(Point2D inValue)
{
Rectangle bbox = getBoundsBox();
StringBuilderPlus transform = new StringBuilderPlus(getCssTransform()).setDelimiter(" ");
transform.delimitedAppend("translate(" + (inValue.getX() - bbox.getX()) + ", " + (inValue.getY() - bbox.getY()) + ")");
setTransform(transform.toString());
return this;
}
//---------------------------------------------------------------------------
public Rectangle getBoundsBox()
{
Integer minX = null;
String xString = getAttributeValue(SvgAttr.x);
if (StringUtil.isSet(xString))
{
if (xString.endsWith("%"))
{
// TODO: How to handle percents?
}
else
{
minX = Integer.parseInt(xString);
}
}
Integer minY = null;
String yString = getAttributeValue(SvgAttr.y);
if (StringUtil.isSet(yString))
{
if (yString.endsWith("%"))
{
// TODO: How to handle percents?
}
else
{
minY = Integer.parseInt(yString);
}
}
Integer maxX = null;
Integer maxY = null;
for (XMLizable node : getSubtags())
{
if (node instanceof SvgNode)
{
Rectangle rect = ((SvgNode)node).getBoundsBox();
if (rect != null)
{
if (null == minX || rect.getX() < minX) minX = (int) rect.getX();
if (null == minY || rect.getY() < minY) minY = (int) rect.getY();
if (null == maxX || rect.getMaxX() > maxX) maxX = (int) rect.getMaxX();
if (null == maxY || rect.getMaxY() > maxY) maxY = (int) rect.getMaxY();
}
}
}
Rectangle boundsBox = null;
if (minX != null
&& minY != null
&& maxX != null
&& maxY != null)
{
boundsBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
adjustBoundsForTransform(boundsBox);
}
return boundsBox;
}
//---------------------------------------------------------------------------
public Point getCenterPoint()
{
Rectangle bbox = getBoundsBox();
return new Point((int) (bbox.getX() + (bbox.getWidth() / 2f)),
(int) (bbox.getY() + (bbox.getHeight()) / 2f));
}
//---------------------------------------------------------------------------
protected void rangeCheckOpacityValue(float inValue)
{
if (inValue > 1 || inValue < 0)
{
throw new RuntimeException("Illegal opacity value: " + inValue + "! Must be between 0 and 1 (inclusive).");
}
}
//---------------------------------------------------------------------------
// TODO: Font style and weight
protected Font getAdjustedFont(Font inOrigFont)
{
Font font = null;
if (hasAttribute(SvgAttr.style))
{
List declarations = CSSDeclaration.parse(getAttributeValue(SvgAttr.style));
for (CSSDeclaration declaration : declarations)
{
if (declaration.getProperty().equals(CSSProperty.font_size))
{
if (null == font)
{
font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
}
GfxSize size = GfxSize.allocate(declaration.getValue(), GfxUnits.pixels);
font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points));
}
else if (declaration.getProperty().equals(CSSProperty.font_family))
{
if (null == font)
{
font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
}
font = new Font(declaration.getValue(), font.getStyle(), font.getSize());
}
}
}
if (hasAttribute(SvgAttr.fontSize))
{
if (null == font)
{
font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
}
GfxSize size = GfxSize.allocate(getAttributeValue(SvgAttr.fontSize), GfxUnits.pixels);
font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points));
}
if (hasAttribute(SvgAttr.fontFamily))
{
if (null == font)
{
font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
}
font = new Font(getAttributeValue(SvgAttr.fontFamily), font.getStyle(), font.getSize());
}
return font;
}
//--------------------------------------------------------------------------
protected void applyTransforms(Graphics2D g2)
{
if (getAttributeValue(SvgAttr.transform) != null)
{
applyTransform(g2, getAttributeValue(SvgAttr.transform));
}
if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = TRANSFORM_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
applyTransform(g2, m.group(1));
}
}
}
//---------------------------------------------------------------------------
protected void applyTransform(Graphics2D g2, String inTransformAttributeValue)
{
// Ex: "rotate(-90.0 118 9) translate(-33.0 3.0 )"
if (StringUtil.isSet(inTransformAttributeValue))
{
String transformValue = inTransformAttributeValue.trim();
int index;
while ((index = transformValue.indexOf("(")) > 0)
{
String functionName = transformValue.substring(0, index).trim();
int endArgIndex = transformValue.indexOf(")");
String[] functionArgs = transformValue.substring(index + 1, endArgIndex).split("\\s*[,\\s]\\s*");
for (int i = 0; i < functionArgs.length; i++)
{
if (functionArgs[i].endsWith("px"))
{
functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 2);
}
else if (functionArgs[i].endsWith("deg"))
{
functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 3);
}
}
switch (functionName)
{
case "rotate":
// The first arg is the rotation angle in degrees. The optional second and third args are the anchor point for rotation.
// If no anchor point is specified, used the center point of the bounds box.
Rectangle contentRectangle = getBoundsBox();
double anchorPointX = (3 == functionArgs.length ? Double.parseDouble(functionArgs[1]) : contentRectangle.getX() + (contentRectangle.getWidth() / 2));
double anchorPointY = (3 == functionArgs.length ? Double.parseDouble(functionArgs[2]) : contentRectangle.getY() + (contentRectangle.getHeight() / 2));
AffineTransform rotation = new AffineTransform();
rotation.rotate(Math.toRadians(Double.parseDouble(functionArgs[0])), anchorPointX, anchorPointY);
g2.transform(rotation);
break;
case "translate":
AffineTransform translation = new AffineTransform();
// The y value is optional. Use zero if it was not specified
double yTranslation = (functionArgs.length > 1 ? Double.parseDouble(functionArgs[1]) : 0);
translation.translate(Double.parseDouble(functionArgs[0]), yTranslation);
g2.transform(translation);
break;
case "translateX":
translation = new AffineTransform();
translation.translate(Double.parseDouble(functionArgs[0]), 0);
g2.transform(translation);
break;
case "translateY":
translation = new AffineTransform();
translation.translate(0, Double.parseDouble(functionArgs[0]));
g2.transform(translation);
break;
default:
System.err.println(StringUtil.singleQuote(functionName) + " is not a currently supported transform function for conversion to Graphics2D!");
}
if (endArgIndex < transformValue.length() - 1)
{
transformValue = transformValue.substring(endArgIndex + 1);
}
else
{
break;
}
}
}
}
//--------------------------------------------------------------------------
protected void adjustBoundsForTransform(Rectangle inBoundsBox)
{
if (hasAttribute(SvgAttr.transform))
{
String value = getAttributeValue(SvgAttr.transform);
Matcher m = SvgAttr.TRANSLATE_PATTERN.matcher(value);
if (m.find())
{
int translateX = (int) Float.parseFloat(m.group(1));
int translateY = (int) Float.parseFloat(m.group(2));
inBoundsBox.translate(translateX, translateY);
}
else
{
m = SvgAttr.ROTATE_PATTERN.matcher(value);
if (m.find())
{
Point2D centerOfRotation;
float angle = Float.parseFloat(m.group(1));
if (m.group(2) != null)
{
// A center of rotation was specified
centerOfRotation = new Point2D.Float(Float.parseFloat(m.group(2)),
Float.parseFloat(m.group(3)));
}
else
{
// Default the center of rotation to the center of the bounds box
centerOfRotation = new Point2D.Double(inBoundsBox.getX() + (inBoundsBox.getWidth() / 2),
inBoundsBox.getY() + (inBoundsBox.getHeight() / 2));
}
Rectangle adjustedBounds = GraphicsUtil.rotate(inBoundsBox, angle, centerOfRotation).getBounds();
inBoundsBox.setBounds(adjustedBounds);
}
}
}
}
//--------------------------------------------------------------------------
protected void drawSubnodes(Graphics2D g2)
{
if (CollectionUtil.hasValues(getSubtags()))
{
for (XMLizable node : getSubtags())
{
if (node instanceof AbstractSvgNode)
{
((AbstractSvgNode)node).draw(g2);
}
}
}
}
//--------------------------------------------------------------------------
protected Composite getG2Composite()
{
String opacityString = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.opacity)))
{
if (! getAttributeValue(SvgAttr.opacity).equals(SvgAttr.Value.none))
{
opacityString = getAttributeValue(SvgAttr.opacity);
}
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
opacityString = m.group(1);
}
}
Composite composite = null;
if (StringUtil.isSet(opacityString))
{
composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Float.parseFloat(opacityString.trim()));
}
return composite;
}
//--------------------------------------------------------------------------
protected Paint getG2Paint()
{
String colorString = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.fill)))
{
if (! getAttributeValue(SvgAttr.fill).equals(SvgAttr.Value.none))
{
colorString = getAttributeValue(SvgAttr.fill);
}
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = FILL_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
colorString = m.group(1);
}
}
Color color = null;
if (StringUtil.isSet(colorString))
{
if (colorString.startsWith("#"))
{
color = Color.decode(colorString);
}
else
{
color = Color.getColor(colorString);
}
}
String opacityString = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.fillOpacity)))
{
if (! getAttributeValue(SvgAttr.fillOpacity).equals(SvgAttr.Value.none))
{
opacityString = getAttributeValue(SvgAttr.fillOpacity);
}
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = FILL_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
opacityString = m.group(1);
}
}
if (color != null
&& StringUtil.isSet(opacityString))
{
color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString));
}
return color;
}
//--------------------------------------------------------------------------
protected Stroke getG2Stroke()
{
Stroke stroke = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeWidth)))
{
stroke = new BasicStroke(Integer.parseInt(getAttributeValue(SvgAttr.strokeWidth)));
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = STROKE_WIDTH_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
stroke = new BasicStroke(Integer.parseInt(m.group(1)));
}
}
return stroke;
}
//--------------------------------------------------------------------------
protected Color getG2StrokeColor()
{
String colorString = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.stroke)))
{
colorString = getAttributeValue(SvgAttr.stroke);
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = STROKE_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
colorString = m.group(1);
}
}
Color color = null;
if (StringUtil.isSet(colorString))
{
if (colorString.startsWith("#"))
{
try
{
color = Color.decode(colorString);
}
catch (Exception e)
{
throw new RuntimeException("Problem decoding color string " + StringUtil.singleQuote(colorString) + "!");
}
}
else
{
color = HTMLColor.valueOf(colorString);
}
}
String opacityString = null;
if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeOpacity)))
{
if (! getAttributeValue(SvgAttr.strokeOpacity).equals(SvgAttr.Value.none))
{
opacityString = getAttributeValue(SvgAttr.strokeOpacity);
}
}
else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
{
Matcher m = STROKE_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
if (m.find())
{
opacityString = m.group(1);
}
}
if (StringUtil.isSet(opacityString))
{
if (null == color)
{
color = Color.BLACK;
}
color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString));
}
return color;
}
}