diff --git a/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java b/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
index 3cb0f25fb8..784c6d87f6 100644
--- a/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
+++ b/src/main/java/net/rptools/maptool/client/functions/ShapeFunctions.java
@@ -18,6 +18,10 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.client.functions.json.JSONMacroFunctions;
+import net.rptools.maptool.client.swing.MapToolEventQueue;
+import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer;
import net.rptools.maptool.language.I18N;
import net.rptools.maptool.model.GUID;
import net.rptools.maptool.model.Zone;
@@ -42,7 +46,20 @@
import java.util.*;
import java.util.function.BiFunction;
+/**
+ * These functions are for creating shapes and performing a few operations on them. "arc",
+ * "cubiccurve", "ellipse", "line", "polygon", "quadcurve", "rectangle", "roundrectangle" are the
+ * basic Java shapes. "Path" and "SVGPath" use the ExtendedGeneralPath from JavaFX. "SVGPath" is
+ * parsed from SVG to EGP with Batik.
+ *
+ *
The shapes are cached as a ShapeDrawable so they are assigned a GUID, name, and anti-alilasing
+ * flag. Until they are drawn they do not exist outside the cache. Once drawn they can be
+ * manipulated with existing drawing functions.
+ *
+ *
The cache can be cleared by calling shape.clearAll()
+ */
public class ShapeFunctions extends AbstractFunction {
+ protected static final @Nonnull Map CACHED_SHAPES;
private static final ShapeFunctions instance = new ShapeFunctions();
private static final AWTPathProducer AWT_PATH_PRODUCER = new AWTPathProducer();
private static final PathParser PATH_PARSER = new PathParser();
@@ -54,7 +71,6 @@ public class ShapeFunctions extends AbstractFunction {
private static final String UNSUPPORTED_OPERATION = "macro.function.general.unsupportedOperation";
private static final String WRONG_NUMBER_OF_ARGUMENTS_FOR_OPERATION =
"macro.function.general.wrongNumberArgumentsForOperation";
- protected static final @Nonnull Map CACHED_SHAPES;
static {
PATH_PARSER.setPathHandler(AWT_PATH_PRODUCER);
@@ -66,9 +82,12 @@ private ShapeFunctions() {
0,
UNLIMITED_PARAMETERS,
"shape.areaAdd",
+ "shape.areaExclusiveOr",
+ "shape.areaIntersect",
"shape.areaSubtract",
"shape.clearAll",
"shape.combinePaths",
+ "shape.copy",
"shape.create",
"shape.delete",
"shape.draw",
@@ -86,14 +105,20 @@ public Object childEvaluate(
Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
if (functionName.equalsIgnoreCase("shape.areaAdd")) {
- return areaBoolean(true, parser, resolver, functionName, parameters);
+ return areaBoolean("add", parser, resolver, functionName, parameters);
+ } else if (functionName.equalsIgnoreCase("shape.areaExclusiveOr")) {
+ return areaBoolean("exclusiveOr", parser, resolver, functionName, parameters);
+ } else if (functionName.equalsIgnoreCase("shape.areaIntersect")) {
+ return areaBoolean("intersect", parser, resolver, functionName, parameters);
} else if (functionName.equalsIgnoreCase("shape.areaSubtract")) {
- return areaBoolean(false, parser, resolver, functionName, parameters);
+ return areaBoolean("subtract", parser, resolver, functionName, parameters);
} else if (functionName.equalsIgnoreCase("shape.clearAll")) {
CACHED_SHAPES.clear();
return true;
} else if (functionName.equalsIgnoreCase("shape.combinePaths")) {
return combinePaths(parser, resolver, functionName, parameters);
+ } else if (functionName.equalsIgnoreCase("shape.copy")) {
+ return copyShape(parser, resolver, functionName, parameters);
} else if (functionName.equalsIgnoreCase("shape.create")) {
return createShape(parser, resolver, functionName, parameters);
} else if (functionName.equalsIgnoreCase("shape.delete")) {
@@ -112,116 +137,112 @@ public Object childEvaluate(
}
}
- private Object[] getLeadParameters(String functionName, List parameters)
+ private Object areaBoolean(
+ String operation,
+ Parser parser,
+ VariableResolver resolver,
+ String functionName,
+ List parameters)
throws ParserException {
- Object[] results = new Object[4];
- results[0] = new GUID();
+ FunctionUtil.checkNumberParam(functionName, parameters, 5, -1);
+ /*
+ name, layer, anti-aliasing, shape names...
+ */
+ Object[] leadParams = getLeadParameters(functionName, parameters);
+ GUID guid = (GUID) leadParams[0];
+ String name = leadParams[1].toString();
+ Zone.Layer layer = (Zone.Layer) leadParams[2];
+ boolean aa = (boolean) leadParams[3];
- if (parameters.getFirst().toString().isEmpty()) {
- results[1] = results[0].toString();
- } else {
- results[1] = FunctionUtil.paramAsString(functionName, parameters, 0, false);
- }
- String layerName = "";
- if (!parameters.get(1).toString().isEmpty()) {
- layerName = FunctionUtil.paramAsString(functionName, parameters, 1, false);
+ List areas = new ArrayList<>(parameters.size() - 3);
+ for (int i = 3; i < parameters.size(); i++) {
+ String shapeName = FunctionUtil.paramAsString(functionName, parameters, i, false);
+ if (CACHED_SHAPES.containsKey(shapeName)) {
+ areas.add(new Area(CACHED_SHAPES.get(shapeName).getShape()));
+ } else {
+ throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
+ }
}
- Zone.Layer layer = null;
- if (!layerName.isEmpty()) {
- try {
- layer = Zone.Layer.valueOf(layerName.toUpperCase());
- } catch (IllegalArgumentException iae) {
- throw new ParserException(I18N.getText(UNKNOWN_LAYER, layerName, functionName));
+
+ Area area = areas.getFirst();
+ for (int i = 1; i < areas.size(); i++) {
+ switch (operation) {
+ case "add" -> area.add(areas.get(i));
+ case "subtract" -> area.subtract(areas.get(i));
+ case "intersect" -> area.intersect(areas.get(i));
+ case "exclusiveOr" -> area.exclusiveOr(areas.get(i));
+ default -> throw new ParserException("#");
}
}
- results[2] = layer;
- boolean aa = true;
- // TODO: Preference check
- if (!parameters.get(2).toString().isEmpty()) {
- aa = FunctionUtil.paramAsString(functionName, parameters, 2, true).equals("1");
+ ShapeDrawable sd = new ShapeDrawable(guid, area, aa);
+ sd.setName(name);
+ if (layer != null) {
+ sd.setLayer(layer);
}
- results[3] = aa;
- return results;
+ CACHED_SHAPES.put(name, sd);
+ return name;
}
- private Object getProperties(
+ private Object combinePaths(
Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
+ FunctionUtil.checkNumberParam(functionName, parameters, 5, -1);
/*
- name, delimiter
+ name, layer, anti-aliasing, connect, shape names...
*/
- String name = FunctionUtil.paramAsString(functionName, parameters, 0, false);
- String delimiter =
- parameters.size() < 2 ? ";" : FunctionUtil.paramAsString(functionName, parameters, 1, false);
- if (!CACHED_SHAPES.containsKey(name)) {
- return false;
+ Object[] leadParams = getLeadParameters(functionName, parameters);
+ GUID guid = (GUID) leadParams[0];
+ String name = leadParams[1].toString();
+ Zone.Layer layer = (Zone.Layer) leadParams[2];
+ boolean aa = (boolean) leadParams[3];
+ boolean connect = FunctionUtil.paramAsBoolean(functionName, parameters, 3, true);
+
+ List shapes = new ArrayList<>(parameters.size() - 4);
+ for (int i = 4; i < parameters.size(); i++) {
+ String shapeName = FunctionUtil.paramAsString(functionName, parameters, i, false);
+ if (CACHED_SHAPES.containsKey(shapeName)) {
+ shapes.add(CACHED_SHAPES.get(shapeName).getShape());
+ } else {
+ throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
+ }
}
- ShapeDrawable sd = CACHED_SHAPES.get(name);
- Shape shape = sd.getShape();
- List segments = new ArrayList<>();
- PathIterator pi = shape.getPathIterator(null);
- double[] coords = new double[6];
- while (!pi.isDone()) {
- int seg = pi.currentSegment(coords);
- segments.add(
- String.format(
- "%d,%f,%f,%f,%f,%f,%f",
- seg, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]));
- pi.next();
+
+ Path2D path2D = new Path2D.Float(shapes.getFirst());
+ for (int i = 1; i < shapes.size(); i++) {
+ path2D.append(shapes.get(i), connect);
}
- String joinedSegments;
- if (delimiter.equalsIgnoreCase("json")) {
- JsonObject jo = new JsonObject();
- jo.addProperty("name", name);
- jo.addProperty("shapeType", sd.getShapeTypeName());
- jo.addProperty("layer", sd.getLayer().toString());
- jo.addProperty("id", sd.getId().toString());
- jo.addProperty("antiAliasing", sd.getUseAntiAliasing());
- JsonObject bounds = new JsonObject();
- bounds.addProperty("width", sd.getBounds().width);
- bounds.addProperty("height", sd.getBounds().height);
- jo.add("bounds", bounds);
- JsonArray segArray = new JsonArray(segments.size());
- for(String seg: segments){
- JsonArray segContents = new JsonArray();
- Arrays.stream(seg.split(",")).toList().forEach(segContents::add);
- segArray.add(segContents);
- }
- jo.add("segments", segArray);
- return jo;
- } else {
- StringBuilder sb = new StringBuilder();
- sb.append(String.format("%s=%s%s", "name", name, delimiter));
- sb.append(String.format("%s=%s%s", "shapeType", sd.getShapeTypeName(), delimiter));
- sb.append(String.format("%s=%s%s", "layer", sd.getLayer(), delimiter));
- sb.append(String.format("%s=%s%s", "id", sd.getId().toString(), delimiter));
- sb.append(String.format("%s=%s%s", "antiAliasing", sd.getUseAntiAliasing(), delimiter));
- sb.append(
- String.format(
- "%s=\"width=%d%sheight=%d\"%s",
- "bounds", sd.getBounds().width, delimiter, sd.getBounds().height, delimiter));
- joinedSegments = "\"" + String.join("\",\"", segments) + "\"";
- sb.append(String.format("%s=%s,", "segments", joinedSegments));
- return sb.toString();
+ ShapeDrawable sd = new ShapeDrawable(guid, path2D, aa);
+ sd.setName(name);
+ if (layer != null) {
+ sd.setLayer(layer);
}
+ CACHED_SHAPES.put(name, sd);
+ return name;
}
- private Object drawShape(
+ private Object copyShape(
Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
/*
- name, map name
+ copy name, shape name
*/
- String shapeName = FunctionUtil.paramAsString(functionName, parameters, 0, false);
- String mapName = FunctionUtil.paramAsString(functionName, parameters, 1, true);
+ FunctionUtil.checkNumberParam(functionName, parameters, 2, 2);
+ String shapeName = FunctionUtil.paramAsString(functionName, parameters, 1, false);
if (!CACHED_SHAPES.containsKey(shapeName)) {
throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
}
- DrawnElement drawnElement = new DrawnElement(CACHED_SHAPES.get(shapeName), Pen.DEFAULT);
- FunctionUtil.getZoneRenderer(functionName, mapName).getZone().addDrawable(drawnElement);
- return drawnElement.getDrawable().getId();
+ String copyName = FunctionUtil.paramAsString(functionName, parameters, 0, false);
+ ShapeDrawable shapeDrawable = new ShapeDrawable(CACHED_SHAPES.get(shapeName));
+ shapeDrawable.setId(new GUID());
+ if (!copyName.equalsIgnoreCase("")) {
+ shapeDrawable.setName(copyName);
+ } else {
+ shapeDrawable.setName(shapeDrawable.getId().toString());
+ }
+ CACHED_SHAPES.put(shapeDrawable.getName(), shapeDrawable);
+ return shapeDrawable.getName();
}
private Object createShape(
@@ -256,7 +277,7 @@ private Object createShape(
};
if (parameters.size() > 5) {
- shape = transform(shape, functionName, parameters, 4);
+ shape = transform(shape, functionName, parameters, 5);
}
ShapeDrawable sd = new ShapeDrawable(guid, shape, aa);
sd.setName(name);
@@ -268,7 +289,7 @@ private Object createShape(
return name;
}
- private Object deleteShape(
+ private boolean deleteShape(
Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
String name = FunctionUtil.paramAsString(functionName, parameters, 0, false);
@@ -279,87 +300,129 @@ private Object deleteShape(
return false;
}
- private Object combinePaths(
+ private Object drawShape(
Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
- FunctionUtil.checkNumberParam(functionName, parameters, 5, -1);
/*
- name, layer, anti-aliasing, connect, shape names...
+ name, map name
*/
- Object[] leadParams = getLeadParameters(functionName, parameters);
- GUID guid = (GUID) leadParams[0];
- String name = leadParams[1].toString();
- Zone.Layer layer = (Zone.Layer) leadParams[2];
- boolean aa = (boolean) leadParams[3];
- boolean connect = FunctionUtil.paramAsBoolean(functionName, parameters, 3, true);
-
- List shapes = new ArrayList<>(parameters.size() - 4);
- for (int i = 4; i < parameters.size(); i++) {
- String shapeName = FunctionUtil.paramAsString(functionName, parameters, i, false);
- if (CACHED_SHAPES.containsKey(shapeName)) {
- shapes.add(CACHED_SHAPES.get(shapeName).getShape());
- } else {
- throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
- }
- }
-
- Path2D path2D = new Path2D.Float(shapes.getFirst());
- for (int i = 1; i < shapes.size(); i++) {
- path2D.append(shapes.get(i), connect);
+ FunctionUtil.checkNumberParam(functionName, parameters, 2, 3);
+ String shapeName = FunctionUtil.paramAsString(functionName, parameters, 0, false);
+ String mapName = FunctionUtil.paramAsString(functionName, parameters, 1, true);
+ if (!CACHED_SHAPES.containsKey(shapeName)) {
+ throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
}
-
- ShapeDrawable sd = new ShapeDrawable(guid, path2D, aa);
- sd.setName(name);
- if (layer != null) {
- sd.setLayer(layer);
+ ShapeDrawable shapeDrawable = CACHED_SHAPES.get(shapeName);
+ Rectangle bounds = shapeDrawable.getBounds();
+ if (bounds.width > 0
+ && bounds.height > 0
+ && bounds.width < 10000
+ && bounds.height < 10000
+ && (double) Math.min(bounds.width, bounds.height) / Math.max(bounds.width, bounds.height)
+ > 0.005) {
+ Pen pen = new Pen(Pen.DEFAULT);
+ if (parameters.size() > 2) {
+ JsonObject penObject =
+ FunctionUtil.jsonWithLowerCaseKeys(
+ FunctionUtil.paramFromStrPropOrJsonAsJsonObject(functionName, parameters, 2));
+
+ if (penObject.keySet().contains("foreground")) {
+ String fg = penObject.get("foreground").getAsString();
+ if (fg.equalsIgnoreCase("transparent") || fg.equalsIgnoreCase("")) {
+ pen.setForegroundMode(Pen.MODE_TRANSPARENT);
+ } else {
+ pen.setForegroundMode(Pen.MODE_SOLID);
+ pen.setPaint(FunctionUtil.getPaintFromString(fg));
+ }
+ }
+ if (penObject.keySet().contains("background")) {
+ String bg = penObject.get("background").getAsString();
+ if (bg.equalsIgnoreCase("transparent") || bg.equalsIgnoreCase("")) {
+ pen.setBackgroundMode(Pen.MODE_TRANSPARENT);
+ } else {
+ pen.setBackgroundMode(Pen.MODE_SOLID);
+ pen.setBackgroundPaint(FunctionUtil.getPaintFromString(bg));
+ }
+ }
+ if (penObject.keySet().contains("width")) {
+ pen.setThickness(penObject.get("width").getAsFloat());
+ }
+ if (penObject.keySet().contains("squarecap")) {
+ pen.setSquareCap(penObject.get("squarecap").getAsBoolean());
+ }
+ if (penObject.keySet().contains("opacity")) {
+ float opacity = penObject.get("opacity").getAsFloat();
+ if (opacity <= 1f) {
+ pen.setOpacity(opacity);
+ } else if (opacity <= 255f) {
+ pen.setOpacity(opacity / 255f);
+ }
+ }
+ if (penObject.keySet().contains("eraser")) {
+ pen.setEraser(penObject.get("eraser").getAsBoolean());
+ }
+ }
+ DrawnElement drawnElement = new DrawnElement(CACHED_SHAPES.get(shapeName), pen);
+ drawnElement.setPen(pen);
+ ZoneRenderer zoneRenderer = FunctionUtil.getZoneRenderer(functionName, mapName);
+ MapToolEventQueue.invokeLater(
+ () -> {
+ zoneRenderer.getZone().addDrawable(drawnElement);
+ MapTool.getFrame().updateDrawTree();
+ MapTool.getFrame().refresh();
+ });
+
+ return drawnElement.getDrawable().getId();
+ } else {
+ return false;
}
- CACHED_SHAPES.put(name, sd);
- return name;
}
- private Object areaBoolean(
- boolean add,
- Parser parser,
- VariableResolver resolver,
- String functionName,
- List parameters)
+ private Object getProperties(
+ Parser parser, VariableResolver resolver, String functionName, List parameters)
throws ParserException {
- FunctionUtil.checkNumberParam(functionName, parameters, 5, -1);
/*
- name, layer, anti-aliasing, shape names...
+ name, delimiter
*/
- Object[] leadParams = getLeadParameters(functionName, parameters);
- GUID guid = (GUID) leadParams[0];
- String name = leadParams[1].toString();
- Zone.Layer layer = (Zone.Layer) leadParams[2];
- boolean aa = (boolean) leadParams[3];
-
- List shapes = new ArrayList<>(parameters.size() - 3);
- for (int i = 4; i < parameters.size(); i++) {
- String shapeName = FunctionUtil.paramAsString(functionName, parameters, i, false);
- if (CACHED_SHAPES.containsKey(shapeName)) {
- shapes.add(CACHED_SHAPES.get(shapeName).getShape());
- } else {
- throw new ParserException(I18N.getText(OBJECT_NOT_FOUND, functionName, shapeName));
- }
+ String name = FunctionUtil.paramAsString(functionName, parameters, 0, false);
+ String delimiter =
+ parameters.size() < 2
+ ? ";"
+ : FunctionUtil.paramAsString(functionName, parameters, 1, false);
+ if (!CACHED_SHAPES.containsKey(name)) {
+ return false;
}
-
- Area area = new Area(shapes.getFirst());
- for (int i = 1; i < shapes.size(); i++) {
- if (add) {
- area.add(new Area(shapes.get(i)));
- } else {
- area.subtract(new Area(shapes.get(i)));
- }
+ ShapeDrawable sd = CACHED_SHAPES.get(name);
+ Shape shape = sd.getShape();
+ List segments = new ArrayList<>();
+ PathIterator pi = shape.getPathIterator(null);
+ double[] coords = new double[7];
+ while (!pi.isDone()) {
+ int seg = pi.currentSegment(coords);
+ segments.add(
+ String.format(
+ "%d,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f",
+ seg, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6]));
+ pi.next();
}
+ StringBuilder stringBuilder = new StringBuilder(sd.toString());
+ stringBuilder.append("segments=").append(String.join(",", segments)).append(";");
- ShapeDrawable sd = new ShapeDrawable(guid, area, aa);
- sd.setName(name);
- if (layer != null) {
- sd.setLayer(layer);
+ if (delimiter.equalsIgnoreCase("json")) {
+ JsonObject jsonObject =
+ JSONMacroFunctions.getInstance()
+ .getJsonObjectFunctions()
+ .fromStrProp(stringBuilder.toString(), ";");
+ jsonObject.add(
+ "segments",
+ JSONMacroFunctions.getInstance()
+ .getJsonArrayFunctions()
+ .fromStringList(String.join("##", segments), "##"));
+ return jsonObject;
+ } else {
+ stringBuilder.append("segments=\"").append(String.join("\",\"", segments)).append("\";");
+ return stringBuilder.toString();
}
- CACHED_SHAPES.put(name, sd);
- return name;
}
private Object shapeList(
@@ -397,71 +460,37 @@ private Object transformShape(
return name;
}
- private Shape transform(
- Shape shape, String functionName, List parameters, int indexOfTransforms)
+ private Object[] getLeadParameters(String functionName, List parameters)
throws ParserException {
- JsonArray transforms =
- (JsonArray) FunctionUtil.paramConvertedToJson(functionName, parameters, indexOfTransforms);
- BiFunction nextStringIndex =
- ((jsonElements, integer) -> {
- int i;
- for (i = integer; i < jsonElements.size(); i++) {
- try {
- jsonElements.get(integer).getAsDouble();
- } catch (NumberFormatException nfe) {
- break;
- }
- }
- return i;
- });
+ Object[] results = new Object[4];
+ results[0] = new GUID();
- int i = 0;
- while (i < transforms.size()) {
- String type = transforms.get(i).getAsString();
- List args = new ArrayList<>();
- i++;
- while (i < nextStringIndex.apply(transforms, i)) {
- args.add(transforms.get(i).getAsDouble());
- i++;
- }
- AffineTransform at = new AffineTransform();
+ if (parameters.getFirst().toString().isEmpty()) {
+ results[1] = results[0].toString();
+ } else {
+ results[1] = FunctionUtil.paramAsString(functionName, parameters, 0, false);
+ }
+ String layerName = "";
+ if (!parameters.get(1).toString().isEmpty()) {
+ layerName = FunctionUtil.paramAsString(functionName, parameters, 1, false);
+ }
+ Zone.Layer layer = null;
+ if (!layerName.isEmpty()) {
try {
- switch (type) {
- case "matrix" ->
- at.setTransform(
- args.get(0), args.get(1), args.get(2), args.get(3), args.get(4), args.get(5));
- case "rotate" -> {
- if (args.size() == 1) {
- at.setToRotation(args.getFirst());
- } else {
- at.setToRotation(args.get(0), args.get(1), args.get(2));
- }
- }
- case "scale" -> at.setToScale(args.get(0), args.get(1));
- case "shear" -> at.setToShear(args.get(0), args.get(1));
- case "translate" -> at.setToTranslation(args.get(0), args.get(1));
- default ->
- throw new ParserException(I18N.getText(UNSUPPORTED_OPERATION, functionName, 5, type));
- }
- } catch (IndexOutOfBoundsException oob) {
- throw new ParserException(
- I18N.getText(
- WRONG_NUMBER_OF_ARGUMENTS_FOR_OPERATION,
- functionName,
- 5,
- type,
- switch (type) {
- case "matrix" -> 6;
- case "rotate" -> "1 or 3";
- case "scale", "shear", "translate" -> 2;
- default ->
- throw new ParserException(
- I18N.getText(UNSUPPORTED_OPERATION, functionName, 5, type));
- }));
+ layer = Zone.Layer.valueOf(layerName.toUpperCase());
+ } catch (IllegalArgumentException iae) {
+ throw new ParserException(I18N.getText(UNKNOWN_LAYER, layerName, functionName));
}
- shape = at.createTransformedShape(shape);
}
- return shape;
+ results[2] = layer;
+ boolean aa = true;
+ // TODO: Preference check
+
+ if (!parameters.get(2).toString().isEmpty()) {
+ aa = FunctionUtil.paramAsString(functionName, parameters, 2, true).equals("1");
+ }
+ results[3] = aa;
+ return results;
}
private Shape arc(String functionName, List parameters) throws ParserException {
@@ -542,17 +571,17 @@ private Shape path(String functionName, List parameters) throws ParserEx
switch (segment.get(0).getAsString()) {
case "wind", "w", "-1" -> path.setWindingRule(segment.get(1).getAsInt());
case "close", "z", "4" -> path.closePath();
- case "move", "m", "0" ->
+ case "moveto", "moveTo", "move", "m", "0" ->
path.moveTo(segment.get(1).getAsFloat(), segment.get(2).getAsFloat());
- case "line", "l", "1" ->
+ case "line", "lineto", "lineTo", "l", "1" ->
path.lineTo(segment.get(1).getAsFloat(), segment.get(2).getAsFloat());
- case "quad", "q", "2" ->
+ case "quad", "quadto", "quadTo", "q", "2" ->
path.quadTo(
segment.get(1).getAsFloat(),
segment.get(2).getAsFloat(),
segment.get(3).getAsFloat(),
segment.get(4).getAsFloat());
- case "cubic", "c", "3" ->
+ case "cubic", "cubicto", "cubicTo", "c", "3" ->
path.curveTo(
segment.get(1).getAsFloat(),
segment.get(2).getAsFloat(),
@@ -560,7 +589,7 @@ private Shape path(String functionName, List parameters) throws ParserEx
segment.get(4).getAsFloat(),
segment.get(5).getAsFloat(),
segment.get(6).getAsFloat());
- case "arc", "a", "4321" ->
+ case "arc", "arcto", "arcTo", "a", "4321" ->
path.arcTo(
segment.get(1).getAsFloat(),
segment.get(2).getAsFloat(),
@@ -650,11 +679,89 @@ private Shape svgPath(String functionName, List parameters) throws Parse
String pathString = FunctionUtil.paramAsString(functionName, parameters, 4, false);
try {
PATH_PARSER.parse(pathString);
- final Path2D path = new Path2D.Float(AWT_PATH_PRODUCER.getShape());
+ final Path2D path = new Path2D.Double(AWT_PATH_PRODUCER.getShape());
path.trimToSize();
return path;
} catch (ParseException pe) {
throw new ParserException(I18N.getText(UNABLE_TO_PARSE, functionName, 5));
}
}
+
+ private Shape transform(
+ Shape shape, String functionName, List parameters, int transformIndex)
+ throws ParserException {
+ JsonArray transforms = new JsonArray();
+ String transformsString =
+ FunctionUtil.paramAsString(functionName, parameters, transformIndex, true)
+ .replaceAll("\\s", "");
+ if (transformsString.contains("[")) {
+ transforms =
+ (JsonArray) FunctionUtil.paramConvertedToJson(functionName, parameters, transformIndex);
+ } else {
+ for (String s : transformsString.split(",")) {
+ transforms.add(s);
+ }
+ }
+
+ BiFunction nextStringIndex =
+ ((jsonElements, integer) -> {
+ int i;
+ for (i = integer; i < jsonElements.size(); i++) {
+ try {
+ jsonElements.get(integer).getAsDouble();
+ } catch (NumberFormatException nfe) {
+ break;
+ }
+ }
+ return i;
+ });
+
+ int i = 0;
+ while (i < transforms.size()) {
+ String type = transforms.get(i).getAsString().toLowerCase();
+ List args = new ArrayList<>();
+ i++;
+ while (i < nextStringIndex.apply(transforms, i)) {
+ args.add(transforms.get(i).getAsDouble());
+ i++;
+ }
+ AffineTransform at = new AffineTransform();
+ try {
+ switch (type) {
+ case "matrix" ->
+ at.setTransform(
+ args.get(0), args.get(1), args.get(2), args.get(3), args.get(4), args.get(5));
+ case "rotate" -> {
+ if (args.size() == 1) {
+ at.setToRotation(args.getFirst());
+ } else {
+ at.setToRotation(args.get(0), args.get(1), args.get(2));
+ }
+ }
+ case "scale" -> at.setToScale(args.get(0), args.get(1));
+ case "shear" -> at.setToShear(args.get(0), args.get(1));
+ case "translate" -> at.setToTranslation(args.get(0), args.get(1));
+ default ->
+ throw new ParserException(I18N.getText(UNSUPPORTED_OPERATION, functionName, 5, type));
+ }
+ } catch (IndexOutOfBoundsException oob) {
+ throw new ParserException(
+ I18N.getText(
+ WRONG_NUMBER_OF_ARGUMENTS_FOR_OPERATION,
+ functionName,
+ 5,
+ type,
+ switch (type) {
+ case "matrix" -> 6;
+ case "rotate" -> "1 or 3";
+ case "scale", "shear", "translate" -> 2;
+ default ->
+ throw new ParserException(
+ I18N.getText(UNSUPPORTED_OPERATION, functionName, 5, type));
+ }));
+ }
+ shape = at.createTransformedShape(shape);
+ }
+ return shape;
+ }
}