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
107 changes: 97 additions & 10 deletions core/src/processing/core/PShapeSVG.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
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);
}
}
}
Loading