com.hfg.svg.SVG 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.io.Writer;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.hfg.css.CSS;
import com.hfg.css.CssUtil;
import com.hfg.graphics.units.GfxSize;
import com.hfg.graphics.units.GfxUnits;
import com.hfg.svg.filtereffect.*;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.mime.MimeType;
import com.hfg.xml.XMLAttribute;
import com.hfg.xml.XMLNamespace;
import com.hfg.util.StringUtil;
import com.hfg.xml.XMLNamespaceSet;
import com.hfg.xml.XMLTag;
import com.hfg.xml.XMLizable;
//------------------------------------------------------------------------------
/**
* Object representation of an SVG (Scalable Vector Graphics) 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]
//------------------------------------------------------------------------------
public class SVG extends AbstractSvgNode
{
private SvgMetadata mMetadata;
private XMLTag mStyleTag;
private static Map sTagToClassMap = new HashMap<>();
private static final Pattern sMeasurementPattern = Pattern.compile("(\\-?[\\d\\.]+)(\\w+)?");
private static final Pattern sTrailingZeroPattern = Pattern.compile("(\\.?0+)$");
private static int sNumSigFigsForCoordinates = 7;
// SVG tag name constants
public static final String anchor = "a";
public static final String circle = "circle";
public static final String defs = "defs";
public static final String desc = "desc";
public static final String ellipse = "ellipse";
public static final String feBlend = "feBlend";
public static final String feColorMatrix = "feColorMatrix";
public static final String feComponentTransfer = "feComponentTransfer";
public static final String feComposite = "feComposite";
public static final String feConvolveMatrix = "feConvolveMatrix";
public static final String feDiffuseLighting = "feDiffuseLighting";
public static final String feDisplacementMap = "feDisplacementMap";
public static final String feDistantLight = "feDistantLight";
public static final String feFlood = "feFlood";
public static final String feFuncA = "feFuncA";
public static final String feFuncB = "feFuncB";
public static final String feFuncG = "feFuncG";
public static final String feFuncR = "feFuncR";
public static final String feGaussianBlur = "feGaussianBlur";
public static final String feImage = "feImage";
public static final String feMerge = "feMerge";
public static final String feMergeNode = "feMergeNode";
public static final String feMorphology = "feMorphology";
public static final String feOffset = "feOffset";
public static final String fePointLight = "fePointLight";
public static final String feSpecularLighting = "feSpecularLighting";
public static final String feSpotLight = "feSpotLight";
public static final String feTile = "feTile";
public static final String feTurbulence = "feTurbulence";
public static final String foreignObject = "foreignObject";
public static final String filter = "filter";
public static final String group = "g";
public static final String line = "line";
public static final String marker = "marker";
public static final String metadata = "metadata";
public static final String path = "path";
public static final String polygon = "polygon";
public static final String rect = "rect";
public static final String script = "script";
public static final String style = "style";
public static final String svg = "svg";
public static final String switch_ = "switch";
public static final String symbol = "symbol";
public static final String text = "text";
public static final String title = "title";
public static final String tspan = "tspan";
public static final String use = "use";
static
{
sTagToClassMap.put(anchor, SvgLink.class);
sTagToClassMap.put(circle, SvgCircle.class);
sTagToClassMap.put(defs, SvgDefs.class);
sTagToClassMap.put(desc, SvgDesc.class);
sTagToClassMap.put(ellipse, SvgEllipse.class);
sTagToClassMap.put(feBlend, SvgFeBlend.class);
sTagToClassMap.put(feColorMatrix, SvgFeColorMatrix.class);
sTagToClassMap.put(feComponentTransfer, SvgFeComponentTransfer.class);
sTagToClassMap.put(feComposite, SvgFeComposite.class);
sTagToClassMap.put(feConvolveMatrix, SvgFeConvolveMatrix.class);
sTagToClassMap.put(feDiffuseLighting, SvgFeDiffuseLighting.class);
sTagToClassMap.put(feDisplacementMap, SvgFeDisplacementMap.class);
sTagToClassMap.put(feDistantLight, SvgFeDistantLight.class);
sTagToClassMap.put(feFlood, SvgFeFlood.class);
sTagToClassMap.put(feFuncA, SvgFeFuncA.class);
sTagToClassMap.put(feFuncB, SvgFeFuncB.class);
sTagToClassMap.put(feFuncG, SvgFeFuncG.class);
sTagToClassMap.put(feFuncR, SvgFeFuncR.class);
sTagToClassMap.put(feGaussianBlur, SvgFeGaussianBlur.class);
sTagToClassMap.put(feImage, SvgFeImage.class);
sTagToClassMap.put(feMerge, SvgFeMerge.class);
sTagToClassMap.put(feMergeNode, SvgFeMergeNode.class);
sTagToClassMap.put(feMorphology, SvgFeMorphology.class);
sTagToClassMap.put(feOffset, SvgFeOffset.class);
sTagToClassMap.put(fePointLight, SvgFePointLight.class);
sTagToClassMap.put(feSpecularLighting, SvgFeSpecularLighting.class);
sTagToClassMap.put(feSpotLight, SvgFeSpotLight.class);
sTagToClassMap.put(feTile, SvgFeTile.class);
sTagToClassMap.put(feTurbulence, SvgFeTurbulence.class);
sTagToClassMap.put(filter, SvgFilter.class);
sTagToClassMap.put(group, SvgGroup.class);
sTagToClassMap.put(line, SvgLine.class);
sTagToClassMap.put(marker, SvgMarker.class);
sTagToClassMap.put(metadata, SvgMetadata.class);
sTagToClassMap.put(path, SvgPath.class);
sTagToClassMap.put(polygon, SvgPolygon.class);
sTagToClassMap.put(rect, SvgRect.class);
sTagToClassMap.put(script, SvgScript.class);
sTagToClassMap.put(symbol, SvgSymbol.class);
sTagToClassMap.put(text, SvgText.class);
sTagToClassMap.put(title, SvgTitle.class);
sTagToClassMap.put(tspan, SvgTSpan.class);
}
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//---------------------------------------------------------------------------
public SVG()
{
super(svg);
setDefaultXMLNamespaceDeclaration(XMLNamespace.SVG);
// setAttribute("xmlns:" + XMLNamespace.SVG.getPrefix(), XMLNamespace.SVG.getURI());
setAttribute("xmlns:" + XMLNamespace.XLINK.getPrefix(), XMLNamespace.XLINK.getURI());
}
//---------------------------------------------------------------------------
public SVG(XMLTag inXMLTag)
{
this();
inXMLTag.verifyTagName(getTagName());
if (CollectionUtil.hasValues(inXMLTag.getAttributes()))
{
for (XMLAttribute attr : inXMLTag.getAttributes())
{
setAttribute(attr.clone());
}
}
java.util.List subtags = inXMLTag.getSubtags();
if (CollectionUtil.hasValues(subtags))
{
for (XMLTag subtag : subtags)
{
addSubtag(SVG.constructFromXMLTag(subtag));
}
}
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//---------------------------------------------------------------------------
public static int getNumSigFigsForCoordinates()
{
return sNumSigFigsForCoordinates;
}
//---------------------------------------------------------------------------
public static void setNumSigFigsForCoordinates(int inValue)
{
sNumSigFigsForCoordinates = inValue;
}
//---------------------------------------------------------------------------
/**
* Method used by SVG classes to impose the globally specified number of
* significant figures on coordinate values.
* @param inValue the number to be sig. fig. adjusted
* @return the sig. fig. adjusted number
*/
public static String formatCoordinate(float inValue)
{
return (0 == inValue ? "0" : trimTrailingZeros(String.format("%." + sNumSigFigsForCoordinates + "G", inValue)));
}
//---------------------------------------------------------------------------
/**
* Method used by SVG classes to impose the globally specified number of
* significant figures on coordinate values.
* @param inValue the number to be sig. fig. adjusted
* @return the sig. fig. adjusted number
*/
public static String formatCoordinate(double inValue)
{
return (0 == inValue ? "0" : trimTrailingZeros(String.format("%." + sNumSigFigsForCoordinates + "G", inValue)));
}
//---------------------------------------------------------------------------
public static SvgNode constructFromXMLTag(XMLTag inXMLTag)
{
Class clazz = sTagToClassMap.get(inXMLTag.getTagName());
if (null == clazz)
{
throw new SvgException("The tag " + StringUtil.singleQuote(inXMLTag.getTagName()) + " could not be mapped to an SVG class!");
}
SvgNode svgNode;
try
{
Constructor constructor = clazz.getConstructor(XMLTag.class);
svgNode = (SvgNode) constructor.newInstance(inXMLTag);
}
catch (NoSuchMethodException e)
{
throw new SvgException("The class " + StringUtil.singleQuote(clazz) + " needs a constructor that takes an XMLTag.", e);
}
catch (Exception e)
{
throw new SvgException("Problem during invocation of class " + StringUtil.singleQuote(clazz) + "'s constructor!", e);
}
return svgNode;
}
//---------------------------------------------------------------------------
public SVG setFont(Font inFont)
{
addStyle("font-family: " + inFont.getName());
addStyle("font-size:" + inFont.getSize() + "pt;");
return this;
}
//---------------------------------------------------------------------------
public SVG setOpacity(Float inValue)
{
if (inValue != null)
{
setAttribute(SvgAttr.opacity, String.format("%.2f", inValue));
}
else
{
removeAttribute(SvgAttr.opacity);
}
return this;
}
//--------------------------------------------------------------------------
/**
Adds the specified text to a 'style' block.
@param inStyle CSS style text for inclusion via a 'style' tag
*/
public SVG addStyleTag(CSS inStyle)
{
return addStyleTag(inStyle.toString());
}
//--------------------------------------------------------------------------
/**
Adds the specified text to a 'style' block.
@param inStyle CSS style text for inclusion via a 'style' tag
*/
public SVG addStyleTag(String inStyle)
{
if (null == mStyleTag)
{
mStyleTag = new XMLTag(style);
mStyleTag.setAttribute(SvgAttr.type, MimeType.TEXT_CSS);
addSubtag(mStyleTag);
}
mStyleTag.addContentWithoutEscaping(inStyle);
return this;
}
//--------------------------------------------------------------------------
@Override
public SVG addStyle(String inValue)
{
CssUtil.addStyle(this, inValue);
return this;
}
//---------------------------------------------------------------------------
@Override
public SVG setStyle(String inValue)
{
setAttribute(SvgAttr.style, inValue);
return this;
}
//---------------------------------------------------------------------------
public SVG setWidth(int inValue)
{
setAttribute(SvgAttr.width, inValue);
return this;
}
//---------------------------------------------------------------------------
public int getWidth()
{
String widthString = getAttributeValue(SvgAttr.width);
if (! StringUtil.isSet(widthString))
{
// Is a viewBox specified?
Rectangle viewBox = getViewBox();
if (viewBox != null)
{
widthString = viewBox.getWidth() + "px";
}
else
{
setDimensionsToContentBoundsBox();
widthString = getAttributeValue(SvgAttr.width);
}
}
Matcher m = sMeasurementPattern.matcher(widthString);
if (m.matches())
{
widthString = m.group(1);
}
return (int) Float.parseFloat(widthString);
}
//---------------------------------------------------------------------------
public SVG setHeight(int inValue)
{
setAttribute(SvgAttr.height, inValue);
return this;
}
//---------------------------------------------------------------------------
public int getHeight()
{
String heightString = getAttributeValue(SvgAttr.height);
if (! StringUtil.isSet(heightString))
{
// Is a viewBox specified?
Rectangle viewBox = getViewBox();
if (viewBox != null)
{
heightString = viewBox.getHeight() + "px";
}
else
{
setDimensionsToContentBoundsBox();
heightString = getAttributeValue(SvgAttr.height);
}
}
Matcher m = sMeasurementPattern.matcher(heightString);
if (m.matches())
{
heightString = m.group(1);
}
return (int) Float.parseFloat(heightString);
}
//---------------------------------------------------------------------------
public SVG setViewBox(Rectangle inValue)
{
setAttribute(SvgAttr.viewBox,
String.format("%d %d %d %d", (int)inValue.getMinX(), (int)inValue.getMinY(),
(int)inValue.getMaxX(), (int)inValue.getMaxY()));
return this;
}
//---------------------------------------------------------------------------
public Rectangle getViewBox()
{
Rectangle rect = null;
String stringValue = getAttributeValue(SvgAttr.viewBox);
if (StringUtil.isSet(stringValue))
{
String[] pieces = stringValue.split("\\s+");
int x = Integer.parseInt(pieces[0]);
int y = Integer.parseInt(pieces[1]);
rect = new Rectangle(x, y, (int) Float.parseFloat(pieces[2]) - x, (int) Float.parseFloat(pieces[3]) - y);
}
return rect;
}
//---------------------------------------------------------------------------
@Override
public synchronized void toXML(Writer inWriter)
{
if (! hasAttribute(SvgAttr.height)) setDimensionsToContentBoundsBox();
super.toXML(inWriter);
}
//---------------------------------------------------------------------------
@Override
protected void toXML(Writer inWriter, XMLNamespaceSet inDeclaredNamespaces)
{
if (! hasAttribute(SvgAttr.height)) setDimensionsToContentBoundsBox();
super.toXML(inWriter, inDeclaredNamespaces);
}
//---------------------------------------------------------------------------
@Override
public synchronized void toIndentedXML(Writer inWriter, int inInitialIndentLevel, int inIndentSize,
XMLNamespaceSet inDeclaredNamespaces)
{
if (! hasAttribute(SvgAttr.height)) setDimensionsToContentBoundsBox();
super.toIndentedXML(inWriter, inInitialIndentLevel, inIndentSize, inDeclaredNamespaces);
}
//---------------------------------------------------------------------------
public SVG setDimensionsToContentBoundsBox()
{
Rectangle contentRect = getContentBoundsBox();
setWidth((int) contentRect.getWidth());
setHeight((int) contentRect.getHeight());
// Don't think we need to do this be default
// setViewBox(contentRect);
return this;
}
//---------------------------------------------------------------------------
@Override
public Rectangle getBoundsBox()
{
int minX = 0;
int minY = 0;
int maxX = minX + (getAttributeValue(SvgAttr.width) != null ? GfxSize.allocate(getAttributeValue(SvgAttr.width), GfxUnits.pixels).toInt(GfxUnits.pixels) : 0);
int maxY = minY + (getAttributeValue(SvgAttr.height) != null ? GfxSize.allocate(getAttributeValue(SvgAttr.height), GfxUnits.pixels).toInt(GfxUnits.pixels) : 0);
Rectangle rect = getContentBoundsBox();
if (rect != null)
{
if (rect.getX() < minX) minX = (int) rect.getX();
if (rect.getY() < minY) minY = (int) rect.getY();
if (rect.getMaxX() > maxX) maxX = (int) rect.getMaxX();
if (rect.getMaxY() > maxY) maxY = (int) rect.getMaxY();
}
Rectangle boundsBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
adjustBoundsForTransform(boundsBox);
return boundsBox;
}
//---------------------------------------------------------------------------
public Rectangle getContentBoundsBox()
{
int minX = 0;
int minY = 0;
int maxX = 0;
int maxY = 0;
for (XMLizable node : getSubtags())
{
if (node instanceof SvgNode)
{
Rectangle rect = ((SvgNode)node).getBoundsBox();
if (rect != null)
{
if (rect.getX() < minX) minX = (int) rect.getX();
if (rect.getY() < minY) minY = (int) rect.getY();
if (rect.getMaxX() > maxX) maxX = (int) rect.getMaxX();
if (rect.getMaxY() > maxY) maxY = (int) rect.getMaxY();
}
}
}
Rectangle boundsBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
adjustBoundsForTransform(boundsBox);
return boundsBox;
}
//---------------------------------------------------------------------------
public SvgGroup addGroup()
{
SvgGroup group = new SvgGroup();
addSubtag(group);
return group;
}
//---------------------------------------------------------------------------
public SvgMetadata getMetadata()
{
if (null == mMetadata)
{
// Check if it has been added via addSubtag()...
mMetadata = getOptionalSubtagByName(SVG.metadata);
if (null == mMetadata)
{
mMetadata = new SvgMetadata();
addSubtag(0, mMetadata);
}
}
return mMetadata;
}
//---------------------------------------------------------------------------
public SvgDefs addDefs()
{
SvgDefs defs = new SvgDefs();
addSubtag(defs);
return defs;
}
//---------------------------------------------------------------------------
public SvgLine addLine(Point inStart, Point inEnd)
{
SvgLine line = new SvgLine(inStart, inEnd);
addSubtag(line);
return line;
}
//---------------------------------------------------------------------------
public SvgPath addPath()
{
SvgPath path = new SvgPath();
addSubtag(path);
return path;
}
//---------------------------------------------------------------------------
public SvgEllipse addEllipse()
{
SvgEllipse ellipse = new SvgEllipse();
addSubtag(ellipse);
return ellipse;
}
//---------------------------------------------------------------------------
public SvgPolygon addPolygon()
{
SvgPolygon polygon = new SvgPolygon();
addSubtag(polygon);
return polygon;
}
//---------------------------------------------------------------------------
public SvgPath addPath(String inPathData)
{
SvgPath path = new SvgPath(inPathData);
addSubtag(path);
return path;
}
//---------------------------------------------------------------------------
public SvgRect addRect(Rectangle inRect)
{
SvgRect rect = new SvgRect(inRect);
addSubtag(rect);
return rect;
}
//---------------------------------------------------------------------------
public SvgText addText(String inText, Font inFont, Point inLocation)
{
SvgText text = new SvgText(inText, inFont, inLocation);
addSubtag(text);
return text;
}
//---------------------------------------------------------------------------
@Override
public void draw(Graphics2D g2)
{
// Save settings
Paint origPaint = g2.getPaint();
Stroke origStroke = g2.getStroke();
Color origBackground = g2.getBackground();
// Paint the background
Rectangle canvas = new Rectangle(0, 0, getWidth(), getHeight());
g2.setPaint(Color.WHITE);
g2.fill(canvas);
g2.setPaint(Color.BLACK);
drawSubnodes(g2);
// Restore settings
g2.setPaint(origPaint);
g2.setStroke(origStroke);
g2.setBackground(origBackground);
}
//---------------------------------------------------------------------------
public void scale(float inProportionalScalingFactor)
{
setHeight((int) (getHeight() * inProportionalScalingFactor));
setWidth((int) (getWidth() * inProportionalScalingFactor));
// Wrap all contents in a scaling transform
SvgGroup scalingGroup = new SvgGroup().setTransform("scale(" + formatCoordinate(inProportionalScalingFactor) + ")");
scalingGroup.setSubtags(getSubtags());
List subtags = new ArrayList<>();
subtags.add(scalingGroup);
setSubtags(subtags);
}
//---------------------------------------------------------------------------
private static String trimTrailingZeros(String inValue)
{
Matcher m = sTrailingZeroPattern.matcher(inValue);
return (m.find() ? inValue.substring(0, inValue.length() - m.group(1).length()) : inValue);
}
}