Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 75 additions & 10 deletions core/src/processing/core/PShapeSVG.java
Original file line number Diff line number Diff line change
Expand Up @@ -961,14 +961,36 @@ 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';
if (token4.length() > 2) {
endX = PApplet.parseFloat(token4.substring(2));
endY = PApplet.parseFloat(pathTokens[i + 5]);
tokenOffset = -2;
} else {
endX = PApplet.parseFloat(pathTokens[i + 5]);
endY = PApplet.parseFloat(pathTokens[i + 6]);
tokenOffset = -1;
}
} else {
fa = PApplet.parseFloat(token4) != 0;
fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
endX = PApplet.parseFloat(pathTokens[i + 6]);
endY = PApplet.parseFloat(pathTokens[i + 7]);
}
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
cx = endX;
cy = endY;
i += 8;
i += 8 + tokenOffset;
prevCurve = true;
}
break;
Expand All @@ -978,14 +1000,34 @@ 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';
if (token4.length() > 2) {
endX = cx + PApplet.parseFloat(token4.substring(2));
endY = cy + PApplet.parseFloat(pathTokens[i + 5]);
tokenOffset = -2;
} else {
endX = cx + PApplet.parseFloat(pathTokens[i + 5]);
endY = cy + PApplet.parseFloat(pathTokens[i + 6]);
tokenOffset = -1;
}
} else {
fa = PApplet.parseFloat(token4) != 0;
fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
endX = cx + PApplet.parseFloat(pathTokens[i + 6]);
endY = cy + PApplet.parseFloat(pathTokens[i + 7]);
}
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
cx = endX;
cy = endY;
i += 8;
i += 8 + tokenOffset;
prevCurve = true;
}
break;
Expand Down Expand Up @@ -1054,6 +1096,29 @@ 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 &&
(token.charAt(0) == '0' || token.charAt(0) == '1') &&
(token.charAt(1) == '0' || token.charAt(1) == '1') &&
(token.length() == 2 ||
(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);
Expand Down
110 changes: 110 additions & 0 deletions core/test/processing/core/PShapeSVGPathTest.java
Original file line number Diff line number Diff line change
@@ -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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.0\" viewBox=\"0 0 29 29\">" +
"<path d=\"m0 6 3-2 15 4 7-7a2 2 0 013 3l-7 7 4 15-2 3-7-13-5 5v4l-2 2-2-5-5-2 2-2h4l5-5z\"/>" +
"</svg>";

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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.0\" viewBox=\"0 0 29 29\">" +
"<path d=\"m 0,5.9994379 2.9997,-1.9998 14.9985,3.9996 6.9993,-6.99930004 a 2.1211082,2.1211082 0 0 1 2.9997,2.99970004 l -6.9993,6.9993001 3.9996,14.9985 -1.9998,2.9997 -6.9993,-12.9987 -4.9995,4.9995 v 3.9996 l -1.9998,1.9998 -1.9998,-4.9995 -4.9995,-1.9998 1.9998,-1.9998 h 3.9996 l 4.9995,-4.9995 z\"/>" +
"</svg>";

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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">" +
"<path d=\"M10 10 A30 30 0 013 50\"/></svg>";

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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">" +
"<path d=\"M10 10 A30 30 0 0110 50\"/></svg>";

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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">" +
"<path d=\"M10 10 A30 30 0 0 1 10 50\"/></svg>";

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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">" +
"<path d=\"M50 50 a20 20 0 01-10 20\"/></svg>";

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);
}
}
}