se.europeanspallationsource.xaos.ui.control.svg.SVGContentBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xaos.ui Show documentation
Show all versions of xaos.ui Show documentation
JavaFX-based portion of the XAOS framework, containing the JavaFX-based
controls and tools suitable for other projects too.
The newest version!
/*
* Copyright 2018 European Spallation Source ERIC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.europeanspallationsource.xaos.ui.control.svg;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.logging.Logger;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Paint;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.SVGPath;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.lang3.StringUtils;
import se.europeanspallationsource.xaos.core.util.LogUtils;
import static java.util.logging.Level.WARNING;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_FILL;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_ID;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_OPACITY;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_STROKE;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_STROKE_LINECAP;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_STROKE_LINEJOIN;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_STROKE_MITERLIMIT;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_STROKE_WIDTH;
import static se.europeanspallationsource.xaos.ui.control.svg.SVGAttributesStackFrame.ATTR_TRANSFORM;
/**
* Utility that parses the SVG stream and build a corresponding {@link SVG}
* object.
*
* @author [email protected]
* @see SVGLoader
*/
class SVGContentBuilder {
private static final Logger LOGGER = Logger.getLogger(SVGContentBuilder.class.getName());
private static final String TYPE_KEY = "type";
private final Deque attributesStack = new ArrayDeque<>(4);
private final Map gradients;
private final Map qnames = new TreeMap<>();
private final SVG root;
private final InputStream stream;
private StringBuilder styleBuilder = null;
private final Map styles = new TreeMap<>();
private final URL url;
SVGContentBuilder( URL url ) {
this.url = url;
this.root = new SVG();
this.gradients = new HashMap<>(1);
this.stream = null;
}
SVGContentBuilder( InputStream stream ) {
this.url = null;
this.root = new SVG();
this.gradients = new HashMap<>(1);
this.stream = stream;
}
SVG build() throws IOException, XMLStreamException {
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty("javax.xml.stream.isValidating", false);
factory.setProperty("javax.xml.stream.isNamespaceAware", false);
factory.setProperty("javax.xml.stream.supportDTD", false);
try ( BufferedInputStream bufferedStream = new BufferedInputStream(stream != null ? stream : url.openStream()) ) {
XMLEventReader reader = factory.createXMLEventReader(bufferedStream);
build(reader, root);
reader.close();
}
return root;
}
/**
* Return the string value of the named attribute inside the given
* {@code element}.
*
* @param attributeName The name of the attribute whose value must be returned.
* @param element The {@link StartElement} possibly containing the
* attribute whose name is given.
* @return The found value or {@code null};
*/
String getAttributeValue( String attributeName, StartElement element ) {
Attribute attribute = element.getAttributeByName(getQName(attributeName));
return ( attribute != null ) ? StringUtils.trimToNull(attribute.getValue()) : null;
}
@SuppressWarnings( "ReturnOfCollectionOrArrayField" )
Map getQNames() {
return qnames;
}
@SuppressWarnings( "ReturnOfCollectionOrArrayField" )
Map getStyles() {
return styles;
}
private void applyStyles( Node node, SVGAttributesStackFrame stackFrame ) {
if ( node instanceof Shape ) {
Shape shape = (Shape) node;
stackFrame.consumeAttribute(ATTR_FILL, this::toPaint, f -> shape.setFill(f));
stackFrame.consumeAttribute(ATTR_STROKE, this::toPaint, s -> shape.setStroke(s));
stackFrame.consumeAttribute(ATTR_STROKE_WIDTH, Double::parseDouble, w -> shape.setStrokeWidth(w));
stackFrame.consumeAttribute(ATTR_STROKE_MITERLIMIT, Double::parseDouble, l -> shape.setStrokeMiterLimit(l));
stackFrame.consumeAttribute(ATTR_STROKE_LINECAP, this::toStrokeLineCap, c -> shape.setStrokeLineCap(c));
stackFrame.consumeAttribute(ATTR_STROKE_LINEJOIN, this::toStrokeLineJoin, j -> shape.setStrokeLineJoin(j));
}
stackFrame.consumeAttribute(ATTR_OPACITY, Double::parseDouble, o -> node.setOpacity(o));
stackFrame.consumeAttribute(ATTR_TRANSFORM, this::toTransform, t -> node.getTransforms().add(t));
}
private void build( XMLEventReader reader, Group group ) throws IOException, XMLStreamException {
while ( reader.hasNext() ) {
XMLEvent event = reader.nextEvent();
if ( event.isStartElement() ) {
Node node = null;
StartElement element = (StartElement) event;
String type = element.getName().toString();
switch ( type ) {
case "circle":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildCircle(element);
break;
case "ellipse":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildEllipse(element);
break;
case "g":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildGroup(reader);
break;
case "image":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildImage(element);
break;
case "line":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildLine(element);
break;
case "linearGradient":
buildLinearGradient(reader, element);
break;
case "path":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildPath(element);
break;
case "polygon":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildPolygon(element);
break;
case "polyline":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildPolyline(element);
break;
case "radialGradient":
buildRadialGradient(reader, element);
break;
case "rect":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildRect(element);
break;
case "style":
styleBuilder = new StringBuilder(256);
break;
case "svg": {
SVGAttributesStackFrame stackFrame = new SVGAttributesStackFrame(this);
attributesStack.push(stackFrame);
stackFrame.populate(element);
node = buildGroup(reader);
break;
}
case "text":
attributesStack.push(attributesStack.peek().deriveAndPopulate(element));
node = buildText(reader, element);
break;
default:
LogUtils.log(LOGGER, WARNING, "Non Support Element: {0}", element);
break;
}
if ( node != null ) {
SVGAttributesStackFrame stackFrame = attributesStack.peek();
String id = stackFrame.get(ATTR_ID);
if ( id != null ) {
node.setId(id);
root.putNode(id, node);
}
node.getProperties().put(TYPE_KEY, type);
applyStyles(node, stackFrame);
group.getChildren().add(node);
}
} else if ( event.isEndElement() ) {
EndElement element = (EndElement) event;
switch ( element.getName().toString() ) {
case "circle":
case "ellipse":
case "image":
case "line":
case "path":
case "polygon":
case "polyline":
case "rect":
case "text":
attributesStack.pop();
break;
case "g":
attributesStack.pop();
return;
case "style":
populateStyles(styleBuilder.toString());
styleBuilder = null;
break;
case "svg":
return;
case "linearGradient":
case "radialGradient":
default:
break;
}
} else if ( event.isCharacters() ) {
if ( styleBuilder != null ) {
styleBuilder.append(( (Characters) event ).getData());
}
}
}
}
private Shape buildCircle( StartElement element ) {
String cxAttribute = getAttributeValue("cx", element);
String cyAttribute = getAttributeValue("cy", element);
String rAttribute = getAttributeValue("r", element);
try {
return new Circle(
( cxAttribute != null ) ? Double.parseDouble(cxAttribute) : 0.0,
( cyAttribute != null ) ? Double.parseDouble(cyAttribute) : 0.0,
( rAttribute != null ) ? Double.parseDouble(rAttribute) : 0.0
);
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A circle's attribute cannot be parsed to double: cx [{0}], cy [{1}], r [{2}].",
( cxAttribute != null ) ? cxAttribute : "-",
( cyAttribute != null ) ? cyAttribute : "-",
( rAttribute != null ) ? rAttribute : "-"
);
return null;
}
}
private Shape buildEllipse( StartElement element ) {
String cxAttribute = getAttributeValue("cx", element);
String cyAttribute = getAttributeValue("cy", element);
String rxAttribute = getAttributeValue("rx", element);
String ryAttribute = getAttributeValue("ry", element);
try {
return new Ellipse(
( cxAttribute != null ) ? Double.parseDouble(cxAttribute) : 0.0,
( cyAttribute != null ) ? Double.parseDouble(cyAttribute) : 0.0,
( rxAttribute != null && !"auto".equals(rxAttribute) ) ? Double.parseDouble(rxAttribute) : 0.0,
( ryAttribute != null && !"auto".equals(ryAttribute) ) ? Double.parseDouble(ryAttribute) : 0.0
);
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"An ellipse's attribute cannot be parsed to double: cx [{0}], cy [{1}], rx [{2}], ry [{3}].",
( cxAttribute != null ) ? cxAttribute : "-",
( cyAttribute != null ) ? cyAttribute : "-",
( rxAttribute != null ) ? rxAttribute : "-",
( ryAttribute != null ) ? ryAttribute : "-"
);
return null;
}
}
private Group buildGroup( XMLEventReader reader ) throws IOException, XMLStreamException {
Group group = new Group();
build(reader, group);
return group;
}
private ImageView buildImage( StartElement element ) {
String hrefAttribute = getAttributeValue("href", element);
if ( hrefAttribute != null ) {
URL imageUrl = null;
try {
imageUrl = new URL(hrefAttribute);
} catch ( MalformedURLException ex1 ) {
try {
imageUrl = new URL(url, hrefAttribute);
} catch ( MalformedURLException ex2 ) {
LogUtils.log(
LOGGER,
WARNING,
"Image's href attribute is not a valid URL [{0}].",
hrefAttribute
);
}
}
if ( imageUrl != null ) {
String xAttribute = getAttributeValue("x", element);
String yAttribute = getAttributeValue("y", element);
String widthAttribute = getAttributeValue("width", element);
String heightAttribute = getAttributeValue("height", element);
try {
Image image = new Image(
imageUrl.toString(),
( widthAttribute != null ) ? Double.parseDouble(widthAttribute) : 0.0,
( heightAttribute != null ) ? Double.parseDouble(heightAttribute) : 0.0,
true,
true
);
ImageView imageView = new ImageView(image);
imageView.setLayoutX(( xAttribute != null ) ? Double.parseDouble(xAttribute) : 0.0);
imageView.setLayoutY(( yAttribute != null ) ? Double.parseDouble(yAttribute) : 0.0);
return imageView;
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"An image's attribute cannot be parsed to double: x [{0}], y [{1}], width [{2}], height [{3}].",
( xAttribute != null ) ? xAttribute : "-",
( yAttribute != null ) ? yAttribute : "-",
( widthAttribute != null ) ? widthAttribute : "-",
( heightAttribute != null ) ? heightAttribute : "-"
);
}
}
} else {
LogUtils.log(LOGGER, WARNING, "An image's attribute is null: href.");
}
return null;
}
private Shape buildLine( StartElement element ) {
String x1Attribute = getAttributeValue("x1", element);
String y1Attribute = getAttributeValue("y1", element);
String x2Attribute = getAttributeValue("x2", element);
String y2Attribute = getAttributeValue("y2", element);
try {
return new Line(
( x1Attribute != null ) ? Double.parseDouble(x1Attribute) : 0.0,
( y1Attribute != null ) ? Double.parseDouble(y1Attribute) : 0.0,
( x2Attribute != null ) ? Double.parseDouble(x2Attribute) : 0.0,
( y2Attribute != null ) ? Double.parseDouble(y2Attribute) : 0.0
);
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A line's attribute cannot be parsed to double: x1 [{0}], y1 [{1}], x2 [{2}], y2 [{3}].",
( x1Attribute != null ) ? x1Attribute : "-",
( y1Attribute != null ) ? y1Attribute : "-",
( x2Attribute != null ) ? x2Attribute : "-",
( y2Attribute != null ) ? y2Attribute : "-"
);
}
return null;
}
private void buildLinearGradient( XMLEventReader reader, StartElement element )
throws IOException, XMLStreamException {
String id = null;
double x1 = Double.NaN;
double y1 = Double.NaN;
double x2 = Double.NaN;
double y2 = Double.NaN;
Transform transform = null;
@SuppressWarnings( "unchecked" )
Iterator it = element.getAttributes();
while ( it.hasNext() ) {
Attribute attribute = it.next();
switch ( attribute.getName().getLocalPart() ) {
case "id":
id = attribute.getValue();
break;
case "gradientUnits":
if ( !"userSpaceOnUse".equals(attribute.getValue()) ) {
LogUtils.log(LOGGER, WARNING, "LinearGradient supports only userSpaceOnUse: {0}", element);
return;
}
break;
case "x1":
try {
x1 = Double.parseDouble(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "LinearGradient's x1 attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "y1":
try {
y1 = Double.parseDouble(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "LinearGradient's y1 attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "x2":
try {
x2 = Double.parseDouble(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "LinearGradient's x2 attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "y2":
try {
y2 = Double.parseDouble(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "LinearGradient's y2 attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "gradientTransform":
transform = toTransform(attribute.getValue());
break;
default:
LogUtils.log(LOGGER, WARNING, "LinearGradient doesn''t supports: {0}:{1}", attribute, element);
break;
}
}
if ( id != null && x1 != Double.NaN && y1 != Double.NaN && x2 != Double.NaN && y2 != Double.NaN ) {
List stops = buildStops(reader, "linearGradient");
if ( transform != null && transform instanceof Affine ) {
double x1d = x1;
double y1d = y1;
double x2d = x2;
double y2d = y2;
Affine affine = (Affine) transform;
x1 = x1d * affine.getMxx() + y1d * affine.getMxy() + affine.getTx();
y1 = x1d * affine.getMyx() + y1d * affine.getMyy() + affine.getTy();
x2 = x2d * affine.getMxx() + y2d * affine.getMxy() + affine.getTx();
y2 = x2d * affine.getMyx() + y2d * affine.getMyy() + affine.getTy();
}
gradients.put(id, new LinearGradient(x1, y1, x2, y2, false, CycleMethod.NO_CYCLE, stops));
}
}
private Shape buildPath( StartElement element ) {
String dAttribute = getAttributeValue("d", element);
if ( dAttribute != null ) {
SVGPath path = new SVGPath();
path.setContent(dAttribute);
return path;
} else {
LogUtils.log(LOGGER, WARNING, "A path's attribute is null: d.");
}
return null;
}
private Shape buildPolygon( StartElement element ) {
String pointsAttribute = getAttributeValue("points", element);
if ( pointsAttribute != null ) {
try {
Polygon polygon = new Polygon();
StringTokenizer tokenizer = new StringTokenizer(pointsAttribute, " ");
while ( tokenizer.hasMoreTokens() ) {
String point = tokenizer.nextToken();
StringTokenizer tokenizer2 = new StringTokenizer(point, ",");
Double x = Double.valueOf(tokenizer2.nextToken());
Double y = Double.valueOf(tokenizer2.nextToken());
polygon.getPoints().add(x);
polygon.getPoints().add(y);
}
return polygon;
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A polygon's point coordinate cannot be parsed to double [{0}].",
pointsAttribute
);
}
} else {
LogUtils.log(LOGGER, WARNING, "A polygon's attribute is null: points.");
}
return null;
}
private Shape buildPolyline( StartElement element ) {
String pointsAttribute = getAttributeValue("points", element);
if ( pointsAttribute != null ) {
try {
Polyline polyline = new Polyline();
StringTokenizer tokenizer = new StringTokenizer(pointsAttribute, " ");
while ( tokenizer.hasMoreTokens() ) {
String points = tokenizer.nextToken();
StringTokenizer tokenizer2 = new StringTokenizer(points, ",");
double x = Double.parseDouble(tokenizer2.nextToken());
double y = Double.parseDouble(tokenizer2.nextToken());
polyline.getPoints().add(x);
polyline.getPoints().add(y);
}
return polyline;
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A polyline's point coordinate cannot be parsed to double [{0}].",
pointsAttribute
);
}
} else {
LogUtils.log(LOGGER, WARNING, "A polyline's attribute is null: points.");
}
return null;
}
private void buildRadialGradient( XMLEventReader reader, StartElement element )
throws IOException, XMLStreamException {
String id = null;
Double fx = null;
Double fy = null;
Double cx = null;
Double cy = null;
Double r = null;
Transform transform = null;
@SuppressWarnings( "unchecked" )
Iterator it = element.getAttributes();
while ( it.hasNext() ) {
Attribute attribute = it.next();
switch ( attribute.getName().getLocalPart() ) {
case "id":
id = attribute.getValue();
break;
case "gradientUnits":
if ( !"userSpaceOnUse".equals(attribute.getValue()) ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient supports only userSpaceOnUse: {0}", element);
return;
}
break;
case "fx":
try {
fx = Double.valueOf(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient's fx attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "fy":
try {
fy = Double.valueOf(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient's fy attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "cx":
try {
cx = Double.valueOf(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient's cx attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "cy":
try {
cy = Double.valueOf(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient's cy attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "r":
try {
r = Double.valueOf(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(LOGGER, WARNING, "RadialGradient's r attribute cannot be parsed to double [{0}].", attribute.getValue());
}
break;
case "gradientTransform":
transform = toTransform(attribute.getValue());
break;
default:
LogUtils.log(LOGGER, WARNING, "RadialGradient doesn''t supports: {0}", element);
break;
}
}
if ( id != null && cx != null && cy != null && r != null ) {
List stops = buildStops(reader, "radialGradient");
double fDistance = 0.0;
double fAngle = 0.0;
if ( transform != null && transform instanceof Affine ) {
double tempCx = cx;
double tempCy = cy;
double tempR = r;
Affine affine = (Affine) transform;
cx = tempCx * affine.getMxx() + tempCy * affine.getMxy() + affine.getTx();
cy = tempCx * affine.getMyx() + tempCy * affine.getMyy() + affine.getTy();
r = Math.sqrt(tempR * affine.getMxx() * tempR * affine.getMxx() + tempR * affine.getMyx() * tempR * affine.getMyx());
if ( fx != null && fy != null ) {
double tempFx = fx;
double tempFy = fy;
fx = tempFx * affine.getMxx() + tempFy * affine.getMxy() + affine.getTx();
fy = tempFx * affine.getMyx() + tempFy * affine.getMyy() + affine.getTy();
} else {
fAngle = Math.asin(affine.getMyx()) * 180.0 / Math.PI;
fDistance = Math.sqrt(( cx - tempCx ) * ( cx - tempCx ) + ( cy - tempCy ) * ( cy - tempCy ));
}
}
if ( fx != null && fy != null ) {
fDistance = Math.sqrt(( fx - cx ) * ( fx - cx ) + ( fy - cy ) * ( fy - cy )) / r;
fAngle = Math.atan2(cy - fy, cx - fx) * 180.0 / Math.PI;
}
gradients.put(id, new RadialGradient(fAngle, fDistance, cx, cy, r, false, CycleMethod.NO_CYCLE, stops));
}
}
private Shape buildRect( StartElement element ) {
String xAttribute = getAttributeValue("x", element);
String yAttribute = getAttributeValue("y", element);
String widthAttribute = getAttributeValue("width", element);
String heightAttribute = getAttributeValue("height", element);
String rxAttribute = getAttributeValue("rx", element);
String ryAttribute = getAttributeValue("ry", element);
try {
Rectangle rectangle = new Rectangle(
( xAttribute != null ) ? Double.parseDouble(xAttribute) : 0.0,
( yAttribute != null ) ? Double.parseDouble(yAttribute) : 0.0,
( widthAttribute != null ) ? Double.parseDouble(widthAttribute) : 0.0,
( heightAttribute != null ) ? Double.parseDouble(heightAttribute) : 0.0
);
rectangle.setArcWidth(
( rxAttribute != null && !"auto".equals(rxAttribute) ) ? Double.parseDouble(rxAttribute) : 0.0
);
rectangle.setArcHeight(
( ryAttribute != null && !"auto".equals(ryAttribute) ) ? Double.parseDouble(ryAttribute) : 0.0
);
return rectangle;
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A rect's attribute cannot be parsed to double: x [{0}], y [{1}], width [{2}], height [{3}], rx [{4}], ry [{5}].",
( xAttribute != null ) ? xAttribute : "-",
( yAttribute != null ) ? yAttribute : "-",
( widthAttribute != null ) ? widthAttribute : "-",
( heightAttribute != null ) ? heightAttribute : "-",
( rxAttribute != null ) ? rxAttribute : "-",
( ryAttribute != null ) ? ryAttribute : "-"
);
return null;
}
}
private List buildStops( XMLEventReader reader, String kindOfGradient )
throws XMLStreamException {
List stops = new ArrayList<>(4);
XMLEvent event = reader.nextEvent();
while ( !event.isEndElement() || !event.asEndElement().getName().getLocalPart().equals(kindOfGradient) ) {
if ( event.isStartElement() ) {
StartElement element = event.asStartElement();
if ( element.getName().getLocalPart().equals("stop") ) {
double offset = Double.NaN;
String color = null;
double opacity = 1.0;
@SuppressWarnings( "unchecked" )
Iterator it = element.getAttributes();
while ( it.hasNext() ) {
Attribute attribute = it.next();
switch ( attribute.getName().getLocalPart() ) {
case "offset":
try {
offset = Double.parseDouble(attribute.getValue());
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"LinearGradient's stop offset attribute cannot be parsed to double [{0}].",
attribute.getValue()
);
}
break;
case "style": {
String style = attribute.getValue();
StringTokenizer tokenizer = new StringTokenizer(style, ";");
while ( tokenizer.hasMoreTokens() ) {
String item = tokenizer.nextToken().trim();
if ( item.startsWith("stop-color") ) {
color = item.substring(11);
} else if ( item.startsWith("stop-opacity") ) {
try {
opacity = Double.parseDouble(item.substring(13));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"LinearGradient's stop style opacity attribute cannot be parsed to double [{0}].",
attribute.getValue()
);
}
} else {
LogUtils.log(
LOGGER,
WARNING,
"LinearGradient's stop doesn''t supports: {0} [{1}] ''{2}''",
attribute,
element,
item
);
}
}
}
break;
default:
LogUtils.log(
LOGGER,
WARNING,
"LinearGradient's stop doesn''t supports: {0} [{1}]",
attribute,
element
);
break;
}
}
if ( offset != Double.NaN && color != null ) {
stops.add(new Stop(offset, Color.web(color, opacity)));
}
} else {
LogUtils.log(LOGGER, WARNING, "LinearGradient doesn''t supports: {0}", element);
}
}
event = reader.nextEvent();
}
return stops;
}
private Shape buildText( XMLEventReader reader, StartElement element )
throws XMLStreamException {
Font font = null;
String fontFamilyAttribute = getAttributeValue("font-family", element);
String fontSizeAttribute = getAttributeValue("font-size", element);
if ( fontFamilyAttribute != null && fontSizeAttribute != null ) {
try {
font = Font.font(
fontFamilyAttribute.replace("'", ""),
Double.parseDouble(fontSizeAttribute)
);
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"A text's attribute cannot be parsed to double: font-size [{0}].",
fontSizeAttribute
);
}
}
XMLEvent event = reader.nextEvent();
if ( event.isCharacters() ) {
Text text = new Text(( (Characters) event ).getData());
if ( font != null ) {
text.setFont(font);
}
return text;
} else {
throw new XMLStreamException("Illegal Element: " + event);
}
}
private QName getQName( String name ) {
QName qName = qnames.get(name);
if ( qName == null ) {
qName = new QName(name);
qnames.put(name, qName);
}
return qName;
}
/**
* Parse the content of a style element populating the static map of styles,
* later on used to populate the current frame.
*
* @param styleElement The string content of a style element.
*/
private void populateStyles( String styleElement ) {
String content = StringUtils.normalizeSpace(styleElement);
// @import rule not currently supported: skip it.
final String IMPORT = "@import";
while ( content.contains(IMPORT) ) {
int start = content.indexOf(IMPORT);
int end = content.indexOf(';', start + IMPORT.length());
if ( end == -1 ) {
content = "";
} else {
content = content.substring(0, start) + content.substring(1 + end);
}
}
// Get the classes and store them;
String[] classes = content.split("\\}");
for ( String clazz : classes ) {
String[] nameValues = StringUtils.stripAll(clazz.split("\\{"));
if ( nameValues.length == 2 ) {
styles.put(nameValues[0], nameValues[1]);
}
}
}
private Paint toPaint( String value ) {
Paint paint;
if ( !"none".equals(value) ) {
if ( value.startsWith("url(#") ) {
paint = gradients.get(value.substring(5, value.length() - 1));
} else {
paint = Color.web(value);
}
} else {
paint = Color.TRANSPARENT;
}
return paint;
}
private StrokeLineCap toStrokeLineCap( String value ) {
StrokeLineCap linecap = StrokeLineCap.BUTT;
if ( StringUtils.isNotBlank(value) ) {
switch ( value ) {
case "round":
linecap = StrokeLineCap.ROUND;
break;
case "square":
linecap = StrokeLineCap.SQUARE;
break;
case "butt":
break;
default:
LogUtils.log(
LOGGER,
WARNING,
"Unsupported stroke-linecap [{0}]: ''butt'' used instead.",
value
);
break;
}
}
return linecap;
}
private StrokeLineJoin toStrokeLineJoin( String value ) {
StrokeLineJoin linejoin = StrokeLineJoin.MITER;
if ( StringUtils.isNotBlank(value) ) {
switch ( value ) {
case "bevel":
linejoin = StrokeLineJoin.BEVEL;
break;
case "round":
linejoin = StrokeLineJoin.ROUND;
break;
case "miter":
break;
default:
LogUtils.log(
LOGGER,
WARNING,
"Unsupported stroke-linejoin [{0}]: ''miter'' used instead.",
value
);
break;
}
}
return linejoin;
}
private Transform toTransform( String value ) {
Transform transform = new Translate();
StringTokenizer tokenizer = new StringTokenizer(value, ")");
while ( tokenizer.hasMoreTokens() ) {
String transformTxt = tokenizer.nextToken();
if ( transformTxt.startsWith("translate(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
String[] tokens = StringUtils.split(transformTxt, ", ");
if ( tokens.length == 1 || tokens.length == 2 ) {
try {
double ty = 0.0;
double tx = Double.parseDouble(tokens[0]);
if ( tokens.length == 2 ) {
ty = Double.parseDouble(tokens[2]);
}
transform = transform.createConcatenation(Transform.translate(tx, ty));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''translate'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else {
LogUtils.log(
LOGGER,
WARNING,
"''translate'' transform doesn't contain the right number of elements [{0}].",
transformTxt
);
}
} else if ( transformTxt.startsWith("scale(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
String[] tokens = StringUtils.split(transformTxt, ", ");
if ( tokens.length == 1 || tokens.length == 2 ) {
try {
double sx = Double.parseDouble(tokens[0]);
double sy = sx;
if ( tokens.length == 2 ) {
sy = Double.parseDouble(tokens[2]);
}
transform = transform.createConcatenation(Transform.scale(sx, sy));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''scale'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else {
LogUtils.log(
LOGGER,
WARNING,
"''scale'' transform doesn't contain the right number of elements [{0}].",
transformTxt
);
}
} else if ( transformTxt.startsWith("rotate(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
String[] tokens = StringUtils.split(transformTxt, ", ");
if ( tokens.length == 1 || tokens.length == 3 ) {
try {
double cx = 0.0;
double cy = 0.0;
double angle = Double.parseDouble(tokens[0]);
if ( tokens.length == 3 ) {
cx = Double.parseDouble(tokens[1]);
cy = Double.parseDouble(tokens[2]);
}
transform = transform.createConcatenation(Transform.rotate(angle, cx, cy));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''rotate'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else {
LogUtils.log(
LOGGER,
WARNING,
"''rotate'' transform doesn't contain the right number of elements [{0}].",
transformTxt
);
}
} else if ( transformTxt.startsWith("skewX(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
try {
double angle = Double.parseDouble(transformTxt);
transform = transform.createConcatenation(Transform.shear(Math.tan(Math.toRadians(angle)), 0.0));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''skewX'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else if ( transformTxt.startsWith("skewY(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
try {
double angle = Double.parseDouble(transformTxt);
transform = transform.createConcatenation(Transform.shear(0.0, Math.tan(Math.toRadians(angle))));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''skewY'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else if ( transformTxt.startsWith("matrix(") ) {
transformTxt = transformTxt.substring(1 + transformTxt.indexOf('('));
String[] tokens = StringUtils.split(transformTxt, ", ");
if ( tokens.length == 6 ) {
try {
double mxx = Double.parseDouble(tokens[0]);
double myx = Double.parseDouble(tokens[1]);
double mxy = Double.parseDouble(tokens[2]);
double myy = Double.parseDouble(tokens[3]);
double tx = Double.parseDouble(tokens[4]);
double ty = Double.parseDouble(tokens[5]);
transform = transform.createConcatenation(Transform.affine(mxx, myx, mxy, myy, tx, ty));
} catch ( NumberFormatException ex ) {
LogUtils.log(
LOGGER,
WARNING,
"''matrix'' transform contains value not parsed to double [{0}].",
transformTxt
);
}
} else {
LogUtils.log(
LOGGER,
WARNING,
"''matrix'' transform doesn't contain the right number of elements [{0}].",
transformTxt
);
}
}
}
return transform;
}
}