/*
* Copyright (c) 2013 by Gerrit Grunwald
*
* 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 eu.hansolo.enzo.common;
import javafx.geometry.Bounds;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.HLineTo;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.QuadCurve;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.SVGPath;
import javafx.scene.shape.Shape;
import javafx.scene.shape.VLineTo;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
/**
* Created by
* User: hansolo
* Date: 02.04.13
* Time: 12:49
*/
public class ShapeConverter {
private static final double KAPPA = 0.5522847498307935;
public static String shapeToSvgString(final Shape SHAPE) {
final StringBuilder fxPath = new StringBuilder();
if (Line.class.equals(SHAPE.getClass())) {
fxPath.append(convertLine((Line) SHAPE));
} else if (Arc.class.equals(SHAPE.getClass())) {
fxPath.append(convertArc((Arc) SHAPE));
} else if (QuadCurve.class.equals(SHAPE.getClass())) {
fxPath.append(convertQuadCurve((QuadCurve) SHAPE));
} else if (CubicCurve.class.equals(SHAPE.getClass())) {
fxPath.append(convertCubicCurve((CubicCurve) SHAPE));
} else if (Rectangle.class.equals(SHAPE.getClass())) {
fxPath.append(convertRectangle((Rectangle) SHAPE));
} else if (Circle.class.equals(SHAPE.getClass())) {
fxPath.append(convertCircle((Circle) SHAPE));
} else if (Ellipse.class.equals(SHAPE.getClass())) {
fxPath.append(convertEllipse((Ellipse) SHAPE));
} else if (Text.class.equals(SHAPE.getClass())) {
Path path = (Path)(Shape.subtract(SHAPE, new Rectangle(0, 0)));
fxPath.append(convertPath(path));
} else if (Path.class.equals(SHAPE.getClass())) {
fxPath.append(convertPath((Path) SHAPE));
} else if (Polygon.class.equals(SHAPE.getClass())) {
fxPath.append(convertPolygon((Polygon) SHAPE));
} else if (Polyline.class.equals(SHAPE.getClass())) {
fxPath.append(convertPolyline((Polyline) SHAPE));
} else if (SVGPath.class.equals(SHAPE.getClass())) {
fxPath.append(((SVGPath) SHAPE).getContent());
}
return fxPath.toString();
}
public static SVGPath shapeToSvgPath(final Shape SHAPE) {
SVGPath svgPath = new SVGPath();
svgPath.setContent(shapeToSvgString(SHAPE));
return svgPath;
}
public static Path svgPathToPath(final SVGPath SVG_PATH) {
String data = SVG_PATH.getContent();
data = data.replaceAll("/(,)/", "/ /");
data = data.replaceAll("/([A-Za-z])/", "/ $1 /"); // wrap single characters in blanks
StringTokenizer tokenizer = new StringTokenizer(data, " ");
List<String> pathList = new ArrayList<>();
while(tokenizer.hasMoreElements()) {
pathList.add(tokenizer.nextElement().toString());
}
PathReader pathReader = new PathReader(pathList);
return processPath(pathList, pathReader);
}
public static String convertLine(final Line LINE) {
final StringBuilder fxPath = new StringBuilder();
fxPath.append("M ").append(LINE.getStartX()).append(" ").append(LINE.getStartY()).append(" ")
.append("L ").append(LINE.getEndX()).append(" ").append(LINE.getEndY());
return fxPath.toString();
}
public static String convertArc(final Arc ARC) {
StringBuilder fxPath = new StringBuilder();
double centerX = ARC.getCenterX();
double centerY = ARC.getCenterY();
double radiusX = ARC.getRadiusX();
double radiusY = ARC.getRadiusY();
double startAngle = Math.toRadians(-ARC.getStartAngle());
double endAngle = Math.toRadians(-ARC.getStartAngle() - ARC.getLength());
double length = ARC.getLength();
double startX = radiusX * Math.cos(startAngle);
double startY = radiusY * Math.sin(startAngle);
double endX = centerX + radiusX * Math.cos(endAngle);
double endY = centerY + radiusY * Math.sin(endAngle);
int xAxisRot = 0;
int largeArc = (length > 180) ? 1 : 0;
int sweep = (length < 0) ? 1 : 0;
fxPath.append("M ").append(centerX).append(" ").append(centerY).append(" ");
if (ArcType.ROUND == ARC.getType()) {
fxPath.append("l ").append(startX).append(" ").append(startY).append(" ");
}
fxPath.append("A ").append(radiusX).append(" ").append(radiusY).append(" ")
.append(xAxisRot).append(" ").append(largeArc).append(" ").append(sweep).append(" ")
.append(endX).append(" ").append(endY).append(" ");
if (ArcType.CHORD == ARC.getType() || ArcType.ROUND == ARC.getType()) {
fxPath.append("Z");
}
return fxPath.toString();
}
public static String convertQuadCurve(final QuadCurve QUAD_CURVE) {
final StringBuilder fxPath = new StringBuilder();
fxPath.append("M ").append(QUAD_CURVE.getStartX()).append(" ").append(QUAD_CURVE.getStartY()).append(" ")
.append("Q ").append(QUAD_CURVE.getControlX()).append(" ").append(QUAD_CURVE.getControlY())
.append(QUAD_CURVE.getEndX()).append(" ").append(QUAD_CURVE.getEndY());
return fxPath.toString();
}
public static String convertCubicCurve(final CubicCurve CUBIC_CURVE) {
final StringBuilder fxPath = new StringBuilder();
fxPath.append("M ").append(CUBIC_CURVE.getStartX()).append(" ").append(CUBIC_CURVE.getStartY()).append(" ")
.append("C ").append(CUBIC_CURVE.getControlX1()).append(" ").append(CUBIC_CURVE.getControlY1()).append(" ")
.append(CUBIC_CURVE.getControlX2()).append(" ").append(CUBIC_CURVE.getControlY2()).append(" ")
.append(CUBIC_CURVE.getEndX()).append(" ").append(CUBIC_CURVE.getEndY());
return fxPath.toString();
}
public static String convertRectangle(final Rectangle RECTANGLE) {
final StringBuilder fxPath = new StringBuilder();
final Bounds bounds = RECTANGLE.getBoundsInLocal();
if (Double.compare(RECTANGLE.getArcWidth(), 0.0) == 0 && Double.compare(RECTANGLE.getArcHeight(), 0.0) == 0) {
fxPath.append("M ").append(bounds.getMinX()).append(" ").append(bounds.getMinY()).append(" ")
.append("H ").append(bounds.getMaxX()).append(" ")
.append("V ").append(bounds.getMaxY()).append(" ")
.append("H ").append(bounds.getMinX()).append(" ")
.append("V ").append(bounds.getMinY()).append(" ")
.append("Z");
} else {
double x = bounds.getMinX();
double y = bounds.getMinY();
double width = bounds.getWidth();
double height = bounds.getHeight();
double arcWidth = RECTANGLE.getArcWidth();
double arcHeight = RECTANGLE.getArcHeight();
double r = x + width;
double b = y + height;
fxPath.append("M ").append(x + arcWidth).append(" ").append(y).append(" ")
.append("L ").append(r - arcWidth).append(" ").append(y).append(" ")
.append("Q ").append(r).append(" ").append(y).append(" ").append(r).append(" ").append(y + arcHeight).append(" ")
.append("L ").append(r).append(" ").append(y + height - arcHeight).append(" ")
.append("Q ").append(r).append(" ").append(b).append(" ").append(r - arcWidth).append(" ").append(b).append(" ")
.append("L ").append(x + arcWidth).append(" ").append(b).append(" ")
.append("Q ").append(x).append(" ").append(b).append(" ").append(x).append(" ").append(b - arcHeight).append(" ")
.append("L ").append(x).append(" ").append(y + arcHeight).append(" ")
.append("Q ").append(x).append(" ").append(y).append(" ").append(x + arcWidth).append(" ").append(y).append(" ")
.append("Z");
}
return fxPath.toString();
}
public static String convertCircle(final Circle CIRCLE) {
final StringBuilder fxPath = new StringBuilder();
final double CENTER_X = CIRCLE.getCenterX() == 0 ? CIRCLE.getRadius() : CIRCLE.getCenterX();
final double CENTER_Y = CIRCLE.getCenterY() == 0 ? CIRCLE.getRadius() : CIRCLE.getCenterY();
final double RADIUS = CIRCLE.getRadius();
final double CONTROL_DISTANCE = RADIUS * KAPPA;
// Move to first point
fxPath.append("M ").append(CENTER_X).append(" ").append(CENTER_Y - RADIUS).append(" ");
// 1. quadrant
fxPath.append("C ").append(CENTER_X + CONTROL_DISTANCE).append(" ").append(CENTER_Y - RADIUS).append(" ")
.append(CENTER_X + RADIUS).append(" ").append(CENTER_Y - CONTROL_DISTANCE).append(" ")
.append(CENTER_X + RADIUS).append(" ").append(CENTER_Y).append(" ");
// 2. quadrant
fxPath.append("C ").append(CENTER_X + RADIUS).append(" ").append(CENTER_Y + CONTROL_DISTANCE).append(" ")
.append(CENTER_X + CONTROL_DISTANCE).append(" ").append(CENTER_Y + RADIUS).append(" ")
.append(CENTER_X).append(" ").append(CENTER_Y + RADIUS).append(" ");
// 3. quadrant
fxPath.append("C ").append(CENTER_X - CONTROL_DISTANCE).append(" ").append(CENTER_Y + RADIUS).append(" ")
.append(CENTER_X - RADIUS).append(" ").append(CENTER_Y + CONTROL_DISTANCE).append(" ")
.append(CENTER_X - RADIUS).append(" ").append(CENTER_Y).append(" ");
// 4. quadrant
fxPath.append("C ").append(CENTER_X - RADIUS).append(" ").append(CENTER_Y - CONTROL_DISTANCE).append(" ")
.append(CENTER_X - CONTROL_DISTANCE).append(" ").append(CENTER_Y - RADIUS).append(" ")
.append(CENTER_X).append(" ").append(CENTER_Y - RADIUS).append(" ");
// Close path
fxPath.append("Z");
return fxPath.toString();
}
public static String convertEllipse(final Ellipse ELLIPSE) {
final StringBuilder fxPath = new StringBuilder();
final double CENTER_X = ELLIPSE.getCenterX() == 0 ? ELLIPSE.getRadiusX() : ELLIPSE.getCenterX();
final double CENTER_Y = ELLIPSE.getCenterY() == 0 ? ELLIPSE.getRadiusY() : ELLIPSE.getCenterY();
final double RADIUS_X = ELLIPSE.getRadiusX();
final double RADIUS_Y = ELLIPSE.getRadiusY();
final double CONTROL_DISTANCE_X = RADIUS_X * KAPPA;
final double CONTROL_DISTANCE_Y = RADIUS_Y * KAPPA;
// Move to first point
fxPath.append("M ").append(CENTER_X).append(" ").append(CENTER_Y - RADIUS_Y).append(" ");
// 1. quadrant
fxPath.append("C ").append(CENTER_X + CONTROL_DISTANCE_X).append(" ").append(CENTER_Y - RADIUS_Y).append(" ")
.append(CENTER_X + RADIUS_X).append(" ").append(CENTER_Y - CONTROL_DISTANCE_Y).append(" ")
.append(CENTER_X + RADIUS_X).append(" ").append(CENTER_Y).append(" ");
// 2. quadrant
fxPath.append("C ").append(CENTER_X + RADIUS_X).append(" ").append(CENTER_Y + CONTROL_DISTANCE_Y).append(" ")
.append(CENTER_X + CONTROL_DISTANCE_X).append(" ").append(CENTER_Y + RADIUS_Y).append(" ")
.append(CENTER_X).append(" ").append(CENTER_Y + RADIUS_Y).append(" ");
// 3. quadrant
fxPath.append("C ").append(CENTER_X - CONTROL_DISTANCE_X).append(" ").append(CENTER_Y + RADIUS_Y).append(" ")
.append(CENTER_X - RADIUS_X).append(" ").append(CENTER_Y + CONTROL_DISTANCE_Y).append(" ")
.append(CENTER_X - RADIUS_X).append(" ").append(CENTER_Y).append(" ");
// 4. quadrant
fxPath.append("C ").append(CENTER_X - RADIUS_X).append(" ").append(CENTER_Y - CONTROL_DISTANCE_Y).append(" ")
.append(CENTER_X - CONTROL_DISTANCE_X).append(" ").append(CENTER_Y - RADIUS_Y).append(" ")
.append(CENTER_X).append(" ").append(CENTER_Y - RADIUS_Y).append(" ");
// Close path
fxPath.append("Z");
return fxPath.toString();
}
public static String convertPath(final Path PATH) {
final StringBuilder fxPath = new StringBuilder();
for (PathElement element : PATH.getElements()) {
if (MoveTo.class.equals(element.getClass())) {
fxPath.append("M ")
.append(((MoveTo) element).getX()).append(" ")
.append(((MoveTo) element).getY()).append(" ");
} else if (LineTo.class.equals(element.getClass())) {
fxPath.append("L ")
.append(((LineTo) element).getX()).append(" ")
.append(((LineTo) element).getY()).append(" ");
} else if (CubicCurveTo.class.equals(element.getClass())) {
fxPath.append("C ")
.append(((CubicCurveTo) element).getControlX1()).append(" ")
.append(((CubicCurveTo) element).getControlY1()).append(" ")
.append(((CubicCurveTo) element).getControlX2()).append(" ")
.append(((CubicCurveTo) element).getControlY2()).append(" ")
.append(((CubicCurveTo) element).getX()).append(" ")
.append(((CubicCurveTo) element).getY()).append(" ");
} else if (QuadCurveTo.class.equals(element.getClass())) {
fxPath.append("Q ")
.append(((QuadCurveTo) element).getControlX()).append(" ")
.append(((QuadCurveTo) element).getControlY()).append(" ")
.append(((QuadCurveTo) element).getX()).append(" ")
.append(((QuadCurveTo) element).getY()).append(" ");
} else if (ArcTo.class.equals(element.getClass())) {
fxPath.append("A ")
.append(((ArcTo) element).getX()).append(" ")
.append(((ArcTo) element).getY()).append(" ")
.append(((ArcTo) element).getRadiusX()).append(" ")
.append(((ArcTo) element).getRadiusY()).append(" ");
} else if (HLineTo.class.equals(element.getClass())) {
fxPath.append("H ")
.append(((HLineTo) element).getX()).append(" ");
} else if (VLineTo.class.equals(element.getClass())) {
fxPath.append("V ")
.append(((VLineTo) element).getY()).append(" ");
} else if (ClosePath.class.equals(element.getClass())) {
fxPath.append("Z");
}
}
return fxPath.toString();
}
public static String convertPolygon(final Polygon POLYGON) {
final StringBuilder fxPath = new StringBuilder();
final int size = POLYGON.getPoints().size();
if (size % 2 == 0) {
List<Double> coordinates = POLYGON.getPoints();
for (int i = 0 ; i < size ; i += 2) {
fxPath.append(i == 0 ? "M " : "L ")
.append(coordinates.get(i)).append(" ").append(coordinates.get(i + 1)).append(" ");
}
fxPath.append("Z");
}
return fxPath.toString();
}
public static String convertPolyline(final Polyline POLYLINE) {
final StringBuilder fxPath = new StringBuilder();
final int size = POLYLINE.getPoints().size();
if (size % 2 == 0) {
List<Double> coordinates = POLYLINE.getPoints();
for (int i = 0 ; i < size ; i += 2) {
fxPath.append(i == 0 ? "M " : "L ")
.append(coordinates.get(i)).append(" ").append(coordinates.get(i + 1)).append(" ");
}
}
return fxPath.toString();
}
private static Path processPath(final List<String> PATH_LIST, final PathReader READER) {
final Path PATH = new Path();
PATH.setFillRule(FillRule.EVEN_ODD);
while (!PATH_LIST.isEmpty()) {
if ("M".equals(READER.read())) {
PATH.getElements().add(new MoveTo(READER.nextX(), READER.nextY()));
} else if ("L".equals(READER.read())) {
PATH.getElements().add(new LineTo(READER.nextX(), READER.nextY()));
} else if ("C".equals(READER.read())) {
PATH.getElements().add(new CubicCurveTo(READER.nextX(), READER.nextY(), READER.nextX(), READER.nextY(), READER.nextX(), READER.nextY()));
} else if ("Q".equals(READER.read())) {
PATH.getElements().add(new QuadCurveTo(READER.nextX(), READER.nextY(), READER.nextX(), READER.nextY()));
} else if ("H".equals(READER.read())) {
PATH.getElements().add(new HLineTo(READER.nextX()));
} else if ("L".equals(READER.read())) {
PATH.getElements().add(new VLineTo(READER.nextY()));
} else if ("A".equals(READER.read())) {
PATH.getElements().add(new ArcTo(READER.nextX(), READER.nextY(), 0, READER.nextX(), READER.nextY(), false, false));
} else if ("Z".equals(READER.read())) {
PATH.getElements().add(new ClosePath());
}
}
return PATH;
}
private static class PathReader {
protected List<String> path;
PathReader(final List<String> PATH) {
path = PATH;
}
String read() {
return path.remove(0);
}
double nextX() {
return Double.parseDouble(read());
}
double nextY() {
return Double.parseDouble(read());
}
}
}