diff --git a/core/src/processing/core/PShapeSVG.java b/core/src/processing/core/PShapeSVG.java index f8aa3400fb..29d2dde10d 100644 --- a/core/src/processing/core/PShapeSVG.java +++ b/core/src/processing/core/PShapeSVG.java @@ -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; @@ -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) == '.'))); + } + + private void parsePathLineto(float px, float py) { parsePathCode(VERTEX); parsePathVertex(px, py); diff --git a/core/test/processing/core/PShapeSVGPathTest.java b/core/test/processing/core/PShapeSVGPathTest.java new file mode 100644 index 0000000000..298fb085de --- /dev/null +++ b/core/test/processing/core/PShapeSVGPathTest.java @@ -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 = "" + + "" + + ""; + + 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 = "" + + "" + + ""; + + 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 = "" + + ""; + + 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 = "" + + ""; + + 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 = "" + + ""; + + 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 = "" + + ""; + + 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); + } + } +}