Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix ArrayIndexOutOfBoundsException with compact SVG arc notation #1282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
hxrshxz wants to merge 2 commits into processing:main
base: main
Choose a base branch
Loading
from hxrshxz:svg
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
View file Open in desktop
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]);
Comment on lines +985 to +988
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the significance of the 5, 6 and 7 index offsets for pathTokens?S Specifically pathTokens[i+5] etc

}
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) == '.')));
Comment on lines +1111 to +1118
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense but could use some comments to describe what is going on

}


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
View file Open in desktop
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);
}
}
}

AltStyle によって変換されたページ (->オリジナル) /