-
-
Couldn't load subscription status.
- Fork 102
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
| parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY); | ||
| cx = endX; | ||
| cy = endY; | ||
| i += 8; | ||
| i += 8 + tokenOffset; | ||
| prevCurve = true; | ||
| } | ||
| break; | ||
|
|
@@ -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; | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
|
|
||
|
|
||
hxrshxz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private void parsePathLineto(float px, float py) { | ||
| parsePathCode(VERTEX); | ||
| parsePathVertex(px, py); | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
| } |