diff --git a/core/src/processing/core/PShapeSVG.java b/core/src/processing/core/PShapeSVG.java
index f8aa3400fb..7e00b9a05b 100644
--- a/core/src/processing/core/PShapeSVG.java
+++ b/core/src/processing/core/PShapeSVG.java
@@ -961,14 +961,47 @@ else if (lexState == LexState.EXP_HEAD) {
float rx = PApplet.parseFloat(pathTokens[i + 1]);
float ry = PApplet.parseFloat(pathTokens[i + 2]);
float angle = PApplet.parseFloat(pathTokens[i + 3]);
- boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
- boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
- float endX = PApplet.parseFloat(pathTokens[i + 6]);
- float endY = PApplet.parseFloat(pathTokens[i + 7]);
+ // In compact arc notation, flags and coordinates may be concatenated.
+ // e.g. "013" is parsed as large-arc=0, sweep=1, x=3
+ String token4 = pathTokens[i + 4];
+ boolean fa;
+ boolean fs;
+ float endX;
+ float endY;
+ int tokenOffset = 0;
+ if (isCompactArcNotation(token4)) {
+ fa = token4.charAt(0) == '1';
+ fs = token4.charAt(1) == '1';
+ // Case: flags and x-coordinate are concatenated (e.g. "01100")
+ // token4 contains flags + x, so y is at i+5.
+ // We consume 2 fewer tokens than standard (8-2=6).
+ if (token4.length() > 2) {
+ endX = PApplet.parseFloat(token4.substring(2));
+ endY = PApplet.parseFloat(pathTokens[i + 5]);
+ tokenOffset = -2;
+ } else {
+ // Case: flags are concatenated but separated from x (e.g. "01 100")
+ // token4 is flags, x is at i+5, y is at i+6.
+ // We consume 1 fewer token than standard (8-1=7).
+ endX = PApplet.parseFloat(pathTokens[i + 5]);
+ endY = PApplet.parseFloat(pathTokens[i + 6]);
+ tokenOffset = -1;
+ }
+ } else {
+ // Standard notation: flags and coordinates are separate tokens.
+ // The 'A' command takes 7 arguments:
+ // rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y
+ // Here, we've already parsed rx (i+1), ry (i+2), and angle (i+3).
+ // token4 (i+4) is the large-arc-flag.
+ fa = PApplet.parseFloat(token4) != 0;
+ fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; // sweep-flag
+ endX = PApplet.parseFloat(pathTokens[i + 6]); // x
+ endY = PApplet.parseFloat(pathTokens[i + 7]); // y
+ }
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
cx = endX;
cy = endY;
- i += 8;
+ i += 8 + tokenOffset;
prevCurve = true;
}
break;
@@ -978,14 +1011,41 @@ else if (lexState == LexState.EXP_HEAD) {
float rx = PApplet.parseFloat(pathTokens[i + 1]);
float ry = PApplet.parseFloat(pathTokens[i + 2]);
float angle = PApplet.parseFloat(pathTokens[i + 3]);
- boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
- boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
- float endX = cx + PApplet.parseFloat(pathTokens[i + 6]);
- float endY = cy + PApplet.parseFloat(pathTokens[i + 7]);
+ String token4 = pathTokens[i + 4];
+ boolean fa;
+ boolean fs;
+ float endX;
+ float endY;
+ int tokenOffset = 0;
+ if (isCompactArcNotation(token4)) {
+ fa = token4.charAt(0) == '1';
+ fs = token4.charAt(1) == '1';
+ // Case: flags and x-coordinate are concatenated
+ if (token4.length() > 2) {
+ endX = cx + PApplet.parseFloat(token4.substring(2));
+ endY = cy + PApplet.parseFloat(pathTokens[i + 5]);
+ tokenOffset = -2;
+ } else {
+ // Case: flags are concatenated but separated from x
+ endX = cx + PApplet.parseFloat(pathTokens[i + 5]);
+ endY = cy + PApplet.parseFloat(pathTokens[i + 6]);
+ tokenOffset = -1;
+ }
+ } else {
+ // Standard notation: flags and coordinates are separate tokens.
+ // The 'a' command takes 7 arguments:
+ // rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y
+ // Here, we've already parsed rx (i+1), ry (i+2), and angle (i+3).
+ // token4 (i+4) is the large-arc-flag.
+ fa = PApplet.parseFloat(token4) != 0;
+ fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; // sweep-flag
+ endX = cx + PApplet.parseFloat(pathTokens[i + 6]); // x
+ endY = cy + PApplet.parseFloat(pathTokens[i + 7]); // y
+ }
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
cx = endX;
cy = endY;
- i += 8;
+ i += 8 + tokenOffset;
prevCurve = true;
}
break;
@@ -1054,6 +1114,33 @@ private void parsePathMoveto(float px, float py) {
}
+ /**
+ * Checks if a token represents compact arc notation where flags and coordinates
+ * are concatenated (e.g., "013" for large-arc=0, sweep=1, x=3).
+ *
+ * @param token the token to check
+ * @return true if the token is in compact arc notation format
+ */
+ private boolean isCompactArcNotation(String token) {
+ if (token == null) {
+ return false;
+ }
+ return token.length() > 1 &&
+ // First two characters must be '0' or '1' (flags)
+ (token.charAt(0) == '0' || token.charAt(0) == '1') &&
+ (token.charAt(1) == '0' || token.charAt(1) == '1') &&
+ // Either it's just the flags (length 2),
+ (token.length() == 2 ||
+ // Or the flags are followed by the start of a number coordinate
+ // (digit, sign, or decimal point)
+ (token.length() > 2 && (
+ Character.isDigit(token.charAt(2)) ||
+ token.charAt(2) == '+' ||
+ token.charAt(2) == '-' ||
+ token.charAt(2) == '.')));
+ }
+
+
private void parsePathLineto(float px, float py) {
parsePathCode(VERTEX);
parsePathVertex(px, py);
diff --git a/core/test/processing/core/PShapeSVGPathTest.java b/core/test/processing/core/PShapeSVGPathTest.java
new file mode 100644
index 0000000000..298fb085de
--- /dev/null
+++ b/core/test/processing/core/PShapeSVGPathTest.java
@@ -0,0 +1,110 @@
+package processing.core;
+
+import org.junit.Assert;
+import org.junit.Test;
+import processing.data.XML;
+
+public class PShapeSVGPathTest {
+
+ @Test
+ public void testCompactPathNotation() {
+ String svgContent = "";
+
+ try {
+ XML xml = XML.parse(svgContent);
+ PShapeSVG shape = new PShapeSVG(xml);
+ Assert.assertNotNull(shape);
+ Assert.assertTrue(shape.getChildCount() > 0);
+
+ PShape path = shape.getChild(0);
+ Assert.assertNotNull(path);
+ Assert.assertTrue(path.getVertexCount() > 5);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+ }
+
+ @Test
+ public void testWorkingPathNotation() {
+ // Test the working SVG (with explicit decimal points)
+ String svgContent = "";
+
+ try {
+ XML xml = XML.parse(svgContent);
+ PShapeSVG shape = new PShapeSVG(xml);
+ Assert.assertNotNull(shape);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+ }
+
+ @Test
+ public void testCompactArcNotationVariations() {
+ String svgContent1 = "";
+
+ try {
+ XML xml = XML.parse(svgContent1);
+ PShapeSVG shape = new PShapeSVG(xml);
+ PShape path = shape.getChild(0);
+ int vertexCount = path.getVertexCount();
+ PVector lastVertex = path.getVertex(vertexCount - 1);
+ Assert.assertEquals(3.0f, lastVertex.x, 0.0001f);
+ Assert.assertEquals(50.0f, lastVertex.y, 0.0001f);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+
+ String svgContent2 = "";
+
+ try {
+ XML xml = XML.parse(svgContent2);
+ PShapeSVG shape = new PShapeSVG(xml);
+ PShape path = shape.getChild(0);
+ int vertexCount = path.getVertexCount();
+ PVector lastVertex = path.getVertex(vertexCount - 1);
+ Assert.assertEquals(10.0f, lastVertex.x, 0.0001f);
+ Assert.assertEquals(50.0f, lastVertex.y, 0.0001f);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+
+ String svgContent3 = "";
+
+ try {
+ XML xml = XML.parse(svgContent3);
+ PShapeSVG shape = new PShapeSVG(xml);
+ PShape path = shape.getChild(0);
+ int vertexCount = path.getVertexCount();
+ PVector lastVertex = path.getVertex(vertexCount - 1);
+ Assert.assertEquals(10.0f, lastVertex.x, 0.0001f);
+ Assert.assertEquals(50.0f, lastVertex.y, 0.0001f);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+ }
+
+ @Test
+ public void testCompactArcWithNegativeCoordinates() {
+ String svgContent = "";
+
+ try {
+ XML xml = XML.parse(svgContent);
+ PShapeSVG shape = new PShapeSVG(xml);
+ PShape path = shape.getChild(0);
+ int vertexCount = path.getVertexCount();
+ PVector lastVertex = path.getVertex(vertexCount - 1);
+ Assert.assertEquals(40.0f, lastVertex.x, 0.0001f);
+ Assert.assertEquals(70.0f, lastVertex.y, 0.0001f);
+ } catch (Exception e) {
+ Assert.fail("Encountered exception " + e);
+ }
+ }
+}