Skip to content

Commit 89201ea

Browse files
committed
Enhance PShapeSVG to support compact arc notation and add corresponding unit tests
1 parent 5dc52be commit 89201ea

File tree

2 files changed

+154
-10
lines changed

2 files changed

+154
-10
lines changed

core/src/processing/core/PShapeSVG.java

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -961,14 +961,36 @@ else if (lexState == LexState.EXP_HEAD) {
961961
float rx = PApplet.parseFloat(pathTokens[i + 1]);
962962
float ry = PApplet.parseFloat(pathTokens[i + 2]);
963963
float angle = PApplet.parseFloat(pathTokens[i + 3]);
964-
boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
965-
boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
966-
float endX = PApplet.parseFloat(pathTokens[i + 6]);
967-
float endY = PApplet.parseFloat(pathTokens[i + 7]);
964+
// In compact arc notation, flags and coordinates may be concatenated.
965+
// e.g. "013" is parsed as large-arc=0, sweep=1, x=3
966+
String token4 = pathTokens[i + 4];
967+
boolean fa;
968+
boolean fs;
969+
float endX;
970+
float endY;
971+
int tokenOffset = 0;
972+
if (isCompactArcNotation(token4)) {
973+
fa = token4.charAt(0) == '1';
974+
fs = token4.charAt(1) == '1';
975+
if (token4.length() > 2) {
976+
endX = PApplet.parseFloat(token4.substring(2));
977+
endY = PApplet.parseFloat(pathTokens[i + 5]);
978+
tokenOffset = -2;
979+
} else {
980+
endX = PApplet.parseFloat(pathTokens[i + 5]);
981+
endY = PApplet.parseFloat(pathTokens[i + 6]);
982+
tokenOffset = -1;
983+
}
984+
} else {
985+
fa = PApplet.parseFloat(token4) != 0;
986+
fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
987+
endX = PApplet.parseFloat(pathTokens[i + 6]);
988+
endY = PApplet.parseFloat(pathTokens[i + 7]);
989+
}
968990
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
969991
cx = endX;
970992
cy = endY;
971-
i += 8;
993+
i += 8 + tokenOffset;
972994
prevCurve = true;
973995
}
974996
break;
@@ -978,14 +1000,34 @@ else if (lexState == LexState.EXP_HEAD) {
9781000
float rx = PApplet.parseFloat(pathTokens[i + 1]);
9791001
float ry = PApplet.parseFloat(pathTokens[i + 2]);
9801002
float angle = PApplet.parseFloat(pathTokens[i + 3]);
981-
boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
982-
boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
983-
float endX = cx + PApplet.parseFloat(pathTokens[i + 6]);
984-
float endY = cy + PApplet.parseFloat(pathTokens[i + 7]);
1003+
String token4 = pathTokens[i + 4];
1004+
boolean fa;
1005+
boolean fs;
1006+
float endX;
1007+
float endY;
1008+
int tokenOffset = 0;
1009+
if (isCompactArcNotation(token4)) {
1010+
fa = token4.charAt(0) == '1';
1011+
fs = token4.charAt(1) == '1';
1012+
if (token4.length() > 2) {
1013+
endX = cx + PApplet.parseFloat(token4.substring(2));
1014+
endY = cy + PApplet.parseFloat(pathTokens[i + 5]);
1015+
tokenOffset = -2;
1016+
} else {
1017+
endX = cx + PApplet.parseFloat(pathTokens[i + 5]);
1018+
endY = cy + PApplet.parseFloat(pathTokens[i + 6]);
1019+
tokenOffset = -1;
1020+
}
1021+
} else {
1022+
fa = PApplet.parseFloat(token4) != 0;
1023+
fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
1024+
endX = cx + PApplet.parseFloat(pathTokens[i + 6]);
1025+
endY = cy + PApplet.parseFloat(pathTokens[i + 7]);
1026+
}
9851027
parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
9861028
cx = endX;
9871029
cy = endY;
988-
i += 8;
1030+
i += 8 + tokenOffset;
9891031
prevCurve = true;
9901032
}
9911033
break;
@@ -1054,6 +1096,29 @@ private void parsePathMoveto(float px, float py) {
10541096
}
10551097

10561098

1099+
/**
1100+
* Checks if a token represents compact arc notation where flags and coordinates
1101+
* are concatenated (e.g., "013" for large-arc=0, sweep=1, x=3).
1102+
*
1103+
* @param token the token to check
1104+
* @return true if the token is in compact arc notation format
1105+
*/
1106+
private boolean isCompactArcNotation(String token) {
1107+
if (token == null) {
1108+
return false;
1109+
}
1110+
return token.length() > 1 &&
1111+
(token.charAt(0) == '0' || token.charAt(0) == '1') &&
1112+
(token.charAt(1) == '0' || token.charAt(1) == '1') &&
1113+
(token.length() == 2 ||
1114+
(token.length() > 2 && (
1115+
Character.isDigit(token.charAt(2)) ||
1116+
token.charAt(2) == '+' ||
1117+
token.charAt(2) == '-' ||
1118+
token.charAt(2) == '.')));
1119+
}
1120+
1121+
10571122
private void parsePathLineto(float px, float py) {
10581123
parsePathCode(VERTEX);
10591124
parsePathVertex(px, py);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package processing.core;
2+
3+
import org.junit.Assert;
4+
import org.junit.Test;
5+
import processing.data.XML;
6+
7+
public class PShapeSVGPathTest {
8+
9+
@Test
10+
public void testCompactPathNotation() {
11+
// Test the failing SVG from issue #1244
12+
String svgContent = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.0\" viewBox=\"0 0 29 29\">" +
13+
"<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\"/>" +
14+
"</svg>";
15+
16+
try {
17+
XML xml = XML.parse(svgContent);
18+
PShapeSVG shape = new PShapeSVG(xml);
19+
Assert.assertNotNull(shape);
20+
} catch (Exception e) {
21+
Assert.fail("Encountered exception " + e);
22+
}
23+
}
24+
25+
@Test
26+
public void testWorkingPathNotation() {
27+
// Test the working SVG (with explicit decimal points)
28+
String svgContent = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.0\" viewBox=\"0 0 29 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\"/>" +
30+
"</svg>";
31+
32+
try {
33+
XML xml = XML.parse(svgContent);
34+
PShapeSVG shape = new PShapeSVG(xml);
35+
Assert.assertNotNull(shape);
36+
} catch (Exception e) {
37+
Assert.fail("Encountered exception " + e);
38+
}
39+
}
40+
41+
@Test
42+
public void testCompactArcNotationVariations() {
43+
// Test various compact arc notations
44+
String[] testCases = {
45+
// Flags only concatenated (e.g., "01")
46+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M10 10 A30 30 0 0110 50\"/></svg>",
47+
// Flags and coordinate concatenated (e.g., "013")
48+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M10 10 A30 30 0 013 50\"/></svg>",
49+
// Standard notation (should still work)
50+
"<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>"
51+
};
52+
53+
try {
54+
for (String svgContent : testCases) {
55+
XML xml = XML.parse(svgContent);
56+
PShapeSVG shape = new PShapeSVG(xml);
57+
Assert.assertNotNull(shape);
58+
}
59+
} catch (Exception e) {
60+
Assert.fail("Encountered exception " + e);
61+
}
62+
}
63+
64+
@Test
65+
public void testCompactArcWithNegativeCoordinates() {
66+
// Test compact arc notation with negative coordinates
67+
String svgContent = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">" +
68+
"<path d=\"M50 50 a20 20 0 01-10 20\"/>" +
69+
"</svg>";
70+
71+
try {
72+
XML xml = XML.parse(svgContent);
73+
PShapeSVG shape = new PShapeSVG(xml);
74+
Assert.assertNotNull(shape);
75+
} catch (Exception e) {
76+
Assert.fail("Encountered exception " + e);
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)