Skip to content

Commit 8f37ee9

Browse files
authored
Merge pull request #873 from Junology/main
Refactor SVG path parser to accept scientific notation
2 parents 162554f + 4d71168 commit 8f37ee9

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

core/src/processing/core/PShapeSVG.java

+70-34
Original file line numberDiff line numberDiff line change
@@ -514,53 +514,89 @@ protected void parsePath() {
514514
char[] pathDataChars = pathData.toCharArray();
515515

516516
StringBuilder pathBuffer = new StringBuilder();
517-
boolean lastSeparate = false;
518-
boolean isOnDecimal = false;
517+
518+
// The states of the lexical sanner
519+
enum LexState {
520+
AFTER_CMD,// Just after a command (i.e. a single alphabet)
521+
NEUTRAL, // Neutral state, waiting for a number expression or a command
522+
INTEGER, // On a sequence of digits possibly led by the '-' sign
523+
DECIMAL, // On a digit sequence following the decimal point '.'
524+
EXP_HEAD, // On the head of the exponent part of a scientific notation; the '-' sign or a digit
525+
EXP_TAIL, // On the integer expression in the exponent part
526+
}
527+
LexState lexState = LexState.NEUTRAL;
519528

520529
for (int i = 0; i < pathDataChars.length; i++) {
521530
char c = pathDataChars[i];
522-
boolean separate = false;
523-
524-
if (c == 'M' || c == 'm' ||
525-
c == 'L' || c == 'l' ||
526-
c == 'H' || c == 'h' ||
527-
c == 'V' || c == 'v' ||
528-
c == 'C' || c == 'c' || // beziers
529-
c == 'S' || c == 's' ||
530-
c == 'Q' || c == 'q' || // quadratic beziers
531-
c == 'T' || c == 't' ||
532-
c == 'A' || c == 'a' || // elliptical arc
533-
c == 'Z' || c == 'z' || // closepath
534-
c == ',') {
535-
separate = true;
536-
if (i != 0) {
531+
532+
// Put a separator after a command.
533+
if (lexState == LexState.AFTER_CMD) {
534+
pathBuffer.append("|");
535+
lexState = LexState.NEUTRAL;
536+
}
537+
538+
if (c >= '0' && c <= '9') {
539+
// If it is a head of a number representation, enter the 'inside' of the digit sequence.
540+
if (lexState == LexState.NEUTRAL) {
541+
lexState = LexState.INTEGER;
542+
}
543+
else if (lexState == LexState.EXP_HEAD) {
544+
lexState = LexState.EXP_TAIL;
545+
}
546+
pathBuffer.append(c);
547+
continue;
548+
}
549+
550+
if (c == '-') {
551+
if (lexState == LexState.NEUTRAL) {
552+
// In neutral state, enter 'digit sequence'.
553+
lexState = LexState.INTEGER;
554+
}
555+
else if (lexState == LexState.EXP_HEAD) {
556+
// In the begining of an exponent, enter 'exponent digit sequence'.
557+
lexState = LexState.EXP_TAIL;
558+
}
559+
else {
560+
// Otherwise, begin a new number representation.
537561
pathBuffer.append("|");
562+
lexState = LexState.INTEGER;
538563
}
564+
pathBuffer.append("-");
565+
continue;
539566
}
540-
if (c == 'Z' || c == 'z') {
541-
separate = false;
567+
568+
if (c == '.') {
569+
if (lexState == LexState.DECIMAL || lexState == LexState.EXP_HEAD || lexState == LexState.EXP_TAIL) {
570+
// Begin a new decimal number unless it is in a neutral state or after a digit sequence
571+
pathBuffer.append("|");
572+
}
573+
pathBuffer.append(".");
574+
lexState = LexState.DECIMAL;
575+
continue;
542576
}
543-
if (c == '.' && !isOnDecimal) {
544-
isOnDecimal = true;
577+
578+
if (c == 'e' || c == 'E') {
579+
// Found 'e' or 'E', enter the 'exponent' state immediately.
580+
pathBuffer.append("e");
581+
lexState = LexState.EXP_HEAD;
582+
continue;
545583
}
546-
else if (isOnDecimal && (c < '0' || c > '9')) {
584+
585+
// The following are executed for non-numeral elements
586+
587+
if (lexState != LexState.NEUTRAL) {
547588
pathBuffer.append("|");
548-
isOnDecimal = c == '.';
549-
}
550-
if (c == '-' && !lastSeparate) {
551-
// allow for 'e' notation in numbers, e.g. 2.10e-9
552-
// https://download.processing.org/bugzilla/1408.html
553-
if (i == 0 || pathDataChars[i-1] != 'e') {
554-
pathBuffer.append("|");
555-
}
589+
lexState = LexState.NEUTRAL;
556590
}
591+
557592
if (c != ',') {
558-
pathBuffer.append(c); //"" + pathDataBuffer.charAt(i));
593+
pathBuffer.append(c);
559594
}
560-
if (separate && c != ',' && c != '-') {
561-
pathBuffer.append("|");
595+
596+
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
597+
// Every alphabet character except for 'e' and 'E' are considered as a command.
598+
lexState = LexState.AFTER_CMD;
562599
}
563-
lastSeparate = separate;
564600
}
565601

566602
// use whitespace constant to get rid of extra spaces and CR or LF

core/test/processing/core/PShapeSVGTest.java

+28-4
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,47 @@
1010

1111
public class PShapeSVGTest {
1212

13-
private static final String TEST_CONTENT = "<svg><g><path d=\"L 0,3.1.4.1\"/></g></svg>";
13+
private static final String[] TEST_CONTENT = {
14+
"<svg><g><path d=\"L 0,3.1.4.1\"/></g></svg>",
15+
"<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>"
16+
};
17+
private static final int[] TEST_NVERTEX = {2, 8};
18+
private static final String TEST_EXPONENT =
19+
"<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>";
1420

1521
@Test
1622
public void testDecimals() {
1723
try {
18-
XML xml = XML.parse(TEST_CONTENT);
24+
for (int i = 0; i < TEST_CONTENT.length; ++i) {
25+
XML xml = XML.parse(TEST_CONTENT[i]);
26+
PShapeSVG shape = new PShapeSVG(xml);
27+
PShape[] children = shape.getChildren();
28+
Assert.assertEquals(1, children.length);
29+
PShape[] grandchildren = children[0].getChildren();
30+
Assert.assertEquals(1, grandchildren.length);
31+
Assert.assertEquals(0, grandchildren[0].getChildCount());
32+
Assert.assertEquals(TEST_NVERTEX[i], grandchildren[0].getVertexCount());
33+
}
34+
}
35+
catch (Exception e) {
36+
Assert.fail("Encountered exception " + e);
37+
}
38+
}
39+
40+
@Test
41+
public void testExponent() {
42+
try {
43+
XML xml = XML.parse(TEST_EXPONENT);
1944
PShapeSVG shape = new PShapeSVG(xml);
2045
PShape[] children = shape.getChildren();
2146
Assert.assertEquals(1, children.length);
2247
PShape[] grandchildren = children[0].getChildren();
2348
Assert.assertEquals(1, grandchildren.length);
2449
Assert.assertEquals(0, grandchildren[0].getChildCount());
25-
Assert.assertEquals(2, grandchildren[0].getVertexCount());
50+
Assert.assertEquals(8, grandchildren[0].getVertexCount());
2651
}
2752
catch (Exception e) {
2853
Assert.fail("Encountered exception " + e);
2954
}
3055
}
31-
3256
}

0 commit comments

Comments
 (0)