Refactor SVG path parser

This commit is contained in:
Junology
2024-12-10 04:30:07 +09:00
parent 66387bfe9d
commit faa6535dd5
2 changed files with 101 additions and 44 deletions
+68 -35
View File
@@ -514,53 +514,86 @@ public class PShapeSVG extends PShape {
char[] pathDataChars = pathData.toCharArray();
StringBuilder pathBuffer = new StringBuilder();
boolean lastSeparate = false;
boolean isOnDecimal = false;
/*
* The state of the lexer:
* -1: just after the command (i.e. a single alphabet)
* 0: neutral state
* 1: on a digit sequence for integer representation
* 2: on a decimal
* 3: on a digit or a sign in exponent in scientific notation, e.g. 3.14e-2)
* 4: on a digit sequence in exponent
*/
int lexState = 0;
for (int i = 0; i < pathDataChars.length; i++) {
char c = pathDataChars[i];
boolean separate = false;
if (c == 'M' || c == 'm' ||
c == 'L' || c == 'l' ||
c == 'H' || c == 'h' ||
c == 'V' || c == 'v' ||
c == 'C' || c == 'c' || // beziers
c == 'S' || c == 's' ||
c == 'Q' || c == 'q' || // quadratic beziers
c == 'T' || c == 't' ||
c == 'A' || c == 'a' || // elliptical arc
c == 'Z' || c == 'z' || // closepath
c == ',') {
separate = true;
if (i != 0) {
pathBuffer.append("|");
}
}
if (c == 'Z' || c == 'z') {
separate = false;
}
if (c == '.' && !isOnDecimal) {
isOnDecimal = true;
}
else if (isOnDecimal && (c < '0' || c > '9')) {
// Put a separator after a command.
if (lexState == -1) {
pathBuffer.append("|");
isOnDecimal = c == '.';
lexState = 0;
}
if (c == '-' && !lastSeparate) {
// allow for 'e' notation in numbers, e.g. 2.10e-9
// https://download.processing.org/bugzilla/1408.html
if (i == 0 || pathDataChars[i-1] != 'e') {
if (c >= '0' && c <= '9') {
if (lexState == 0 || lexState == 3) {
// If it is a head of a number representation, enter the 'inside' of the digit sequence.
++lexState;
}
pathBuffer.append(c);
continue;
}
if (c == '-') {
if (lexState == 0) {
// In neutral state, enter 'digit sequence'.
lexState = 1;
}
else if (lexState == 3) {
// In the begining of an exponent, enter 'exponent digit sequence'.
lexState = 4;
}
else {
// Otherwise, begin a new number representation.
pathBuffer.append("|");
lexState = 1;
}
pathBuffer.append("-");
continue;
}
if (c == '.') {
if (lexState >= 2) {
// Begin a new decimal number unless it is in a neutral state or after a digit sequence
pathBuffer.append("|");
}
pathBuffer.append(".");
lexState = 2;
continue;
}
if (c == 'e' || c == 'E') {
// Found 'e' or 'E', enter the 'exponent' state immediately.
pathBuffer.append("e");
lexState = 3;
continue;
}
// The following are executed for non-numeral elements
if (lexState != 0) {
pathBuffer.append("|");
lexState = 0;
}
if (c != ',') {
pathBuffer.append(c); //"" + pathDataBuffer.charAt(i));
pathBuffer.append(c);
}
if (separate && c != ',' && c != '-') {
pathBuffer.append("|");
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
// Every alphabet character except for 'e' and 'E' are considered as a command.
lexState = -1;
}
lastSeparate = separate;
}
// use whitespace constant to get rid of extra spaces and CR or LF
+33 -9
View File
@@ -10,23 +10,47 @@ import java.awt.*;
public class PShapeSVGTest {
private static final String TEST_CONTENT = "<svg><g><path d=\"L 0,3.1.4.1\"/></g></svg>";
private static final String[] TEST_CONTENT = {
"<svg><g><path d=\"L 0,3.1.4.1\"/></g></svg>",
"<svg><g><path d=\"m-13.6 4.69-1.35 0.78h-0.00034l1.33 2.27 1.33-0.77a6.48 6.47 43.14 0 1-1.31-2.27z\"/></g></svg>"
};
private static final int[] TEST_NVERTEX = {2, 8};
private static final String TEST_EXPONENT =
"<svg><g><path d=\"m-1.36E1 469e-2-1.35 0.78h-3.4e-4l1.33 2.27 1.33-0.77a6.48 6.47 43.14 0 1-1.31-2.27z\"/></g></svg>";
@Test
public void testDecimals() {
try {
XML xml = XML.parse(TEST_CONTENT);
PShapeSVG shape = new PShapeSVG(xml);
PShape[] children = shape.getChildren();
Assert.assertEquals(1, children.length);
PShape[] grandchildren = children[0].getChildren();
Assert.assertEquals(1, grandchildren.length);
Assert.assertEquals(0, grandchildren[0].getChildCount());
Assert.assertEquals(2, grandchildren[0].getVertexCount());
for (int i = 0; i < TEST_CONTENT.length; ++i) {
XML xml = XML.parse(TEST_CONTENT[i]);
PShapeSVG shape = new PShapeSVG(xml);
PShape[] children = shape.getChildren();
Assert.assertEquals(1, children.length);
PShape[] grandchildren = children[0].getChildren();
Assert.assertEquals(1, grandchildren.length);
Assert.assertEquals(0, grandchildren[0].getChildCount());
Assert.assertEquals(TEST_NVERTEX[i], grandchildren[0].getVertexCount());
}
}
catch (Exception e) {
Assert.fail("Encountered exception " + e);
}
}
@Test
public void testExponent() {
try {
XML xml = XML.parse(TEST_EXPONENT);
PShapeSVG shape = new PShapeSVG(xml);
PShape[] children = shape.getChildren();
Assert.assertEquals(1, children.length);
PShape[] grandchildren = children[0].getChildren();
Assert.assertEquals(1, grandchildren.length);
Assert.assertEquals(0, grandchildren[0].getChildCount());
Assert.assertEquals(8, grandchildren[0].getVertexCount());
}
catch (Exception e) {
Assert.fail("Encountered exception " + e);
}
}
}