package org.andengine.extension.svg.util;
import java.util.LinkedList;
import java.util.Queue;
import org.andengine.extension.svg.adt.SVGPaint;
import org.andengine.extension.svg.adt.SVGProperties;
import org.andengine.extension.svg.util.constants.ISVGConstants;
import org.andengine.util.math.MathUtils;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.RectF;
import android.util.FloatMath;
/**
* Parses a single SVG path and returns it as a <code>android.graphics.Path</code> object.
* An example path is <code>M250,150L150,350L350,350Z</code>, which draws a triangle.
*
* @see <a href="http://www.w3.org/TR/SVG/paths.html">Specification</a>.
*
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 17:16:39 - 21.05.2011
*/
public class SVGPathParser implements ISVGConstants {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private String mString;
private int mLength;
private int mPosition;
private char mCurrentChar;
private final PathParserHelper mPathParserHelper = new PathParserHelper();
private Path mPath;
private Character mCommand = null;
private int mCommandStart = 0;
private final Queue<Float> mCommandParameters = new LinkedList<Float>();
private float mSubPathStartX;
private float mSubPathStartY;
private float mLastX;
private float mLastY;
private float mLastCubicBezierX2;
private float mLastCubicBezierY2;
private float mLastQuadraticBezierX2;
private float mLastQuadraticBezierY2;
private final RectF mArcRect = new RectF();
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
public void parse(final SVGProperties pSVGProperties, final Canvas pCanvas, final SVGPaint pSVGPaint) {
final Path path = this.parse(pSVGProperties);
final boolean fill = pSVGPaint.setFill(pSVGProperties);
if (fill) {
pCanvas.drawPath(path, pSVGPaint.getPaint());
}
final boolean stroke = pSVGPaint.setStroke(pSVGProperties);
if (stroke) {
pCanvas.drawPath(path, pSVGPaint.getPaint());
}
if(fill || stroke) {
pSVGPaint.ensureComputedBoundsInclude(path);
}
}
/**
* Uppercase rules are absolute positions, lowercase are relative.
* Types of path rules:
* <p/>
* <ol>
* <li>M/m - (x y)+ - Move to (without drawing)
* <li>Z/z - (no params) - Close path (back to starting point)
* <li>L/l - (x y)+ - Line to
* <li>H/h - x+ - Horizontal ine to
* <li>V/v - y+ - Vertical line to
* <li>C/c - (x1 y1 x2 y2 x y)+ - Cubic bezier to
* <li>S/s - (x2 y2 x y)+ - Smooth cubic bezier to (shorthand that assumes the x2, y2 from previous C/S is the x1, y1 of this bezier)
* <li>Q/q - (x1 y1 x y)+ - Quadratic bezier to
* <li>T/t - (x y)+ - Smooth quadratic bezier to (assumes previous control point is "reflection" of last one w.r.t. to current point)
* <li>A/a - ... - Arc to</li>
* </ol>
* <p/>
* Numbers are separate by whitespace, comma or nothing at all (!) if they are self-delimiting, (ie. begin with a - sign)
*/
private Path parse(final SVGProperties pSVGProperties) {
final String pathString = pSVGProperties.getStringProperty(ATTRIBUTE_PATHDATA);
if(pathString == null) {
return null;
}
this.mString = pathString.trim();
this.mLastX = 0;
this.mLastY = 0;
this.mLastCubicBezierX2 = 0;
this.mLastCubicBezierY2 = 0;
this.mCommand = null;
this.mCommandParameters.clear();
this.mPath = new Path();
if(this.mString.length() == 0) {
return this.mPath;
}
final String fillrule = pSVGProperties.getStringProperty(ATTRIBUTE_FILLRULE);
if(fillrule != null) {
if(ATTRIBUTE_FILLRULE_VALUE_EVENODD.equals(fillrule)) {
this.mPath.setFillType(FillType.EVEN_ODD);
} else {
this.mPath.setFillType(FillType.WINDING);
}
/*
* TODO Check against:
* http://www.w3.org/TR/SVG/images/painting/fillrule-nonzero.svg / http://www.w3.org/TR/SVG/images/painting/fillrule-nonzero.png
* http://www.w3.org/TR/SVG/images/painting/fillrule-evenodd.svg / http://www.w3.org/TR/SVG/images/painting/fillrule-evenodd.png
*/
}
this.mCurrentChar = this.mString.charAt(0);
this.mPosition = 0;
this.mLength = this.mString.length();
while (this.mPosition < this.mLength) {
try {
this.mPathParserHelper.skipWhitespace();
if (Character.isLetter(this.mCurrentChar) && (this.mCurrentChar != 'e') && (this.mCurrentChar != 'E')) {
this.processCommand();
this.mCommand = this.mCurrentChar;
this.mCommandStart = this.mPosition;
this.mPathParserHelper.advance();
} else {
final float parameter = this.mPathParserHelper.nextFloat();
this.mCommandParameters.add(parameter);
}
} catch(final Throwable t) {
throw new IllegalArgumentException("Error parsing: '" + this.mString.substring(this.mCommandStart, this.mPosition) + "'. Command: '" + this.mCommand + "'. Parameters: '" + this.mCommandParameters.size() + "'.", t);
}
}
this.processCommand();
return this.mPath;
}
private void processCommand() {
if (this.mCommand != null) {
// Process command
this.generatePathElement();
this.mCommandParameters.clear();
}
}
private void generatePathElement() {
boolean wasCubicBezierCurve = false;
boolean wasQuadraticBezierCurve = false;
switch (this.mCommand) { // TODO Extract to constants
case 'm':
this.generateMove(false);
break;
case 'M':
this.generateMove(true);
break;
case 'l':
this.generateLine(false);
break;
case 'L':
this.generateLine(true);
break;
case 'h':
this.generateHorizontalLine(false);
break;
case 'H':
this.generateHorizontalLine(true);
break;
case 'v':
this.generateVerticalLine(false);
break;
case 'V':
this.generateVerticalLine(true);
break;
case 'c':
this.generateCubicBezierCurve(false);
wasCubicBezierCurve = true;
break;
case 'C':
this.generateCubicBezierCurve(true);
wasCubicBezierCurve = true;
break;
case 's':
this.generateSmoothCubicBezierCurve(false);
wasCubicBezierCurve = true;
break;
case 'S':
this.generateSmoothCubicBezierCurve(true);
wasCubicBezierCurve = true;
break;
case 'q':
this.generateQuadraticBezierCurve(false);
wasQuadraticBezierCurve = true;
break;
case 'Q':
this.generateQuadraticBezierCurve(true);
wasQuadraticBezierCurve = true;
break;
case 't':
this.generateSmoothQuadraticBezierCurve(false);
wasQuadraticBezierCurve = true;
break;
case 'T':
this.generateSmoothQuadraticBezierCurve(true);
wasQuadraticBezierCurve = true;
break;
case 'a':
this.generateArc(false);
break;
case 'A':
this.generateArc(true);
break;
case 'z':
case 'Z':
this.generateClose();
break;
default:
throw new RuntimeException("Unexpected SVG command: " + this.mCommand);
}
if (!wasCubicBezierCurve) {
this.mLastCubicBezierX2 = this.mLastX;
this.mLastCubicBezierY2 = this.mLastY;
}
if (!wasQuadraticBezierCurve) {
this.mLastQuadraticBezierX2 = this.mLastX;
this.mLastQuadraticBezierY2 = this.mLastY;
}
}
private void assertParameterCountMinimum(final int pParameterCount) {
if (this.mCommandParameters.size() < pParameterCount) {
throw new RuntimeException("Incorrect parameter count: '" + this.mCommandParameters.size() + "'. Expected at least: '" + pParameterCount + "'.");
}
}
private void assertParameterCount(final int pParameterCount) {
if (this.mCommandParameters.size() != pParameterCount) {
throw new RuntimeException("Incorrect parameter count: '" + this.mCommandParameters.size() + "'. Expected: '" + pParameterCount + "'.");
}
}
private void generateMove(final boolean pAbsolute) {
this.assertParameterCountMinimum(2);
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
/** Moves the line from mLastX,mLastY to x,y. */
if (pAbsolute) {
this.mPath.moveTo(x, y);
this.mLastX = x;
this.mLastY = y;
} else {
this.mPath.rMoveTo(x, y);
this.mLastX += x;
this.mLastY += y;
}
this.mSubPathStartX = this.mLastX;
this.mSubPathStartY = this.mLastY;
if(this.mCommandParameters.size() >= 2) {
this.generateLine(pAbsolute);
}
}
private void generateLine(final boolean pAbsolute) {
this.assertParameterCountMinimum(2);
/** Draws a line from mLastX,mLastY to x,y. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 2) {
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
this.mPath.lineTo(x, y);
this.mLastX = x;
this.mLastY = y;
}
} else {
while(this.mCommandParameters.size() >= 2) {
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
this.mPath.rLineTo(x, y);
this.mLastX += x;
this.mLastY += y;
}
}
}
private void generateHorizontalLine(final boolean pAbsolute) {
this.assertParameterCountMinimum(1);
/** Draws a horizontal line to the point defined by mLastY and x. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 1) {
final float x = this.mCommandParameters.poll();
this.mPath.lineTo(x, this.mLastY);
this.mLastX = x;
}
} else {
while(this.mCommandParameters.size() >= 1) {
final float x = this.mCommandParameters.poll();
this.mPath.rLineTo(x, 0);
this.mLastX += x;
}
}
}
private void generateVerticalLine(final boolean pAbsolute) {
this.assertParameterCountMinimum(1);
/** Draws a vertical line to the point defined by mLastX and y. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 1) {
final float y = this.mCommandParameters.poll();
this.mPath.lineTo(this.mLastX, y);
this.mLastY = y;
}
} else {
while(this.mCommandParameters.size() >= 1) {
final float y = this.mCommandParameters.poll();
this.mPath.rLineTo(0, y);
this.mLastY += y;
}
}
}
private void generateCubicBezierCurve(final boolean pAbsolute) {
this.assertParameterCountMinimum(6);
/** Draws a cubic bezier curve from current pen point to x,y.
* x1,y1 and x2,y2 are start and end control points of the curve. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 6) {
final float x1 = this.mCommandParameters.poll();
final float y1 = this.mCommandParameters.poll();
final float x2 = this.mCommandParameters.poll();
final float y2 = this.mCommandParameters.poll();
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
this.mPath.cubicTo(x1, y1, x2, y2, x, y);
this.mLastCubicBezierX2 = x2;
this.mLastCubicBezierY2 = y2;
this.mLastX = x;
this.mLastY = y;
}
} else {
while(this.mCommandParameters.size() >= 6) {
final float x1 = this.mCommandParameters.poll() + this.mLastX;
final float y1 = this.mCommandParameters.poll() + this.mLastY;
final float x2 = this.mCommandParameters.poll() + this.mLastX;
final float y2 = this.mCommandParameters.poll() + this.mLastY;
final float x = this.mCommandParameters.poll() + this.mLastX;
final float y = this.mCommandParameters.poll() + this.mLastY;
this.mPath.cubicTo(x1, y1, x2, y2, x, y);
this.mLastCubicBezierX2 = x2;
this.mLastCubicBezierY2 = y2;
this.mLastX = x;
this.mLastY = y;
}
}
}
private void generateSmoothCubicBezierCurve(final boolean pAbsolute) {
this.assertParameterCountMinimum(4);
/** Draws a cubic bezier curve from the last point to x,y.
* x2,y2 is the end control point.
* The start control point is is assumed to be the same as
* the end control point of the previous curve. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 4) {
final float x1 = 2 * this.mLastX - this.mLastCubicBezierX2;
final float y1 = 2 * this.mLastY - this.mLastCubicBezierY2;
final float x2 = this.mCommandParameters.poll();
final float y2 = this.mCommandParameters.poll();
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
this.mPath.cubicTo(x1, y1, x2, y2, x, y);
this.mLastCubicBezierX2 = x2;
this.mLastCubicBezierY2 = y2;
this.mLastX = x;
this.mLastY = y;
}
} else {
while(this.mCommandParameters.size() >= 4) {
final float x1 = 2 * this.mLastX - this.mLastCubicBezierX2;
final float y1 = 2 * this.mLastY - this.mLastCubicBezierY2;
final float x2 = this.mCommandParameters.poll() + this.mLastX;
final float y2 = this.mCommandParameters.poll() + this.mLastY;
final float x = this.mCommandParameters.poll() + this.mLastX;
final float y = this.mCommandParameters.poll() + this.mLastY;
this.mPath.cubicTo(x1, y1, x2, y2, x, y);
this.mLastCubicBezierX2 = x2;
this.mLastCubicBezierY2 = y2;
this.mLastX = x;
this.mLastY = y;
}
}
}
private void generateQuadraticBezierCurve(final boolean pAbsolute) {
this.assertParameterCountMinimum(4);
/** Draws a quadratic bezier curve from mLastX,mLastY x,y. x1,y1 is the control point.. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 4) {
final float x1 = this.mCommandParameters.poll();
final float y1 = this.mCommandParameters.poll();
final float x2 = this.mCommandParameters.poll();
final float y2 = this.mCommandParameters.poll();
this.mPath.quadTo(x1, y1, x2, y2);
this.mLastQuadraticBezierX2 = x2;
this.mLastQuadraticBezierY2 = y2;
this.mLastX = x2;
this.mLastY = y2;
}
} else {
while(this.mCommandParameters.size() >= 4) {
final float x1 = this.mCommandParameters.poll() + this.mLastX;
final float y1 = this.mCommandParameters.poll() + this.mLastY;
final float x2 = this.mCommandParameters.poll() + this.mLastX;
final float y2 = this.mCommandParameters.poll() + this.mLastY;
this.mPath.quadTo(x1, y1, x2, y2);
this.mLastQuadraticBezierX2 = x2;
this.mLastQuadraticBezierY2 = y2;
this.mLastX = x2;
this.mLastY = y2;
}
}
}
private void generateSmoothQuadraticBezierCurve(final boolean pAbsolute) {
this.assertParameterCountMinimum(2);
/** Draws a quadratic bezier curve from mLastX,mLastY to x,y.
* The control point is assumed to be the same as the last control point used. */
if(pAbsolute) {
while(this.mCommandParameters.size() >= 2) {
final float x1 = 2 * this.mLastX - this.mLastQuadraticBezierX2;
final float y1 = 2 * this.mLastY - this.mLastQuadraticBezierY2;
final float x2 = this.mCommandParameters.poll();
final float y2 = this.mCommandParameters.poll();
this.mPath.quadTo(x1, y1, x2, y2);
this.mLastQuadraticBezierX2 = x2;
this.mLastQuadraticBezierY2 = y2;
this.mLastX = x2;
this.mLastY = y2;
}
} else {
while(this.mCommandParameters.size() >= 2) {
final float x1 = 2 * this.mLastX - this.mLastQuadraticBezierX2;
final float y1 = 2 * this.mLastY - this.mLastQuadraticBezierY2;
final float x2 = this.mCommandParameters.poll() + this.mLastX;
final float y2 = this.mCommandParameters.poll() + this.mLastY;
this.mPath.quadTo(x1, y1, x2, y2);
this.mLastQuadraticBezierX2 = x2;
this.mLastQuadraticBezierY2 = y2;
this.mLastX = x2;
this.mLastY = y2;
}
}
}
private void generateArc(final boolean pAbsolute) {
this.assertParameterCountMinimum(7);
if(pAbsolute) {
while(this.mCommandParameters.size() >= 7) {
final float rx = this.mCommandParameters.poll();
final float ry = this.mCommandParameters.poll();
final float theta = this.mCommandParameters.poll();
final boolean largeArcFlag = this.mCommandParameters.poll().intValue() == 1;
final boolean sweepFlag = this.mCommandParameters.poll().intValue() == 1;
final float x = this.mCommandParameters.poll();
final float y = this.mCommandParameters.poll();
this.generateArc(rx, ry, theta, largeArcFlag, sweepFlag, x, y);
this.mLastX = x;
this.mLastY = y;
}
} else {
while(this.mCommandParameters.size() >= 7) {
final float rx = this.mCommandParameters.poll();
final float ry = this.mCommandParameters.poll();
final float theta = this.mCommandParameters.poll();
final boolean largeArcFlag = this.mCommandParameters.poll().intValue() == 1;
final boolean sweepFlag = this.mCommandParameters.poll().intValue() == 1;
final float x = this.mCommandParameters.poll() + this.mLastX;
final float y = this.mCommandParameters.poll() + this.mLastY;
this.generateArc(rx, ry, theta, largeArcFlag, sweepFlag, x, y);
this.mLastX = x;
this.mLastY = y;
}
}
}
/**
* Based on: org.apache.batik.ext.awt.geom.ExtendedGeneralPath.computeArc(...)
* @see <a href="http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter">http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter</a>
*/
private void generateArc(final float rx, final float ry, final float pTheta, final boolean pLargeArcFlag, final boolean pSweepFlag, final float pX, final float pY) {
/* Compute the half distance between the current and the end point. */
final float dx = (this.mLastX - pX) * 0.5f;
final float dy = (this.mLastY - pY) * 0.5f;
/* Convert theta to radians. */
final float thetaRad = MathUtils.degToRad(pTheta % 360f);
final float cosAngle = FloatMath.cos(thetaRad);
final float sinAngle = FloatMath.sin(thetaRad);
/* Step 1 : Compute (x1, y1) */
final float x1 = (cosAngle * dx + sinAngle * dy);
final float y1 = (-sinAngle * dx + cosAngle * dy);
/* Ensure radii are large enough. */
float radiusX = Math.abs(rx);
float radiusY = Math.abs(ry);
float Prx = radiusX * radiusX;
float Pry = radiusY * radiusY;
final float Px1 = x1 * x1;
final float Py1 = y1 * y1;
/* Check that radii are large enough. */
final float radiiCheck = Px1/Prx + Py1/Pry;
if (radiiCheck > 1) {
radiusX = FloatMath.sqrt(radiiCheck) * radiusX;
radiusY = FloatMath.sqrt(radiiCheck) * radiusY;
Prx = radiusX * radiusX;
Pry = radiusY * radiusY;
}
/* Step 2 : Compute (cx_dash, cy_dash) */
float sign = (pLargeArcFlag == pSweepFlag) ? -1 : 1;
float sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
sq = (sq < 0) ? 0 : sq;
final float coef = sign * FloatMath.sqrt(sq);
final float cx_dash = coef * ((radiusX * y1) / radiusY);
final float cy_dash = coef * -((radiusY * x1) / radiusX);
//- Step 3 : Compute (cx, cy) from (cx_dash, cy_dash) */
final float cx = ((this.mLastX + pX) * 0.5f) + (cosAngle * cx_dash - sinAngle * cy_dash);
final float cy = ((this.mLastY + pY) * 0.5f) + (sinAngle * cx_dash + cosAngle * cy_dash);
/* Step 4 : Compute the angleStart (angle1) and the sweepAngle (dangle). */
final float ux = (x1 - cx_dash) / radiusX;
final float uy = (y1 - cy_dash) / radiusY;
final float vx = (-x1 - cx_dash) / radiusX;
final float vy = (-y1 - cy_dash) / radiusY;
/* Compute the startAngle. */
float p = ux; // (1 * ux) + (0 * uy)
float n = FloatMath.sqrt((ux * ux) + (uy * uy));
sign = (uy < 0) ? -1f : 1f;
float startAngle = MathUtils.radToDeg(sign * (float)Math.acos(p / n));
/* Compute the sweepAngle. */
n = FloatMath.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
p = ux * vx + uy * vy;
sign = (ux * vy - uy * vx < 0) ? -1f : 1f;
float sweepAngle = MathUtils.radToDeg(sign * (float)Math.acos(p / n));
if(!pSweepFlag && sweepAngle > 0) {
sweepAngle -= 360f;
} else if (pSweepFlag && sweepAngle < 0) {
sweepAngle += 360f;
}
sweepAngle %= 360f;
startAngle %= 360f;
/* Generate bounding rect. */
final float left = cx - radiusX;
final float top = cy - radiusY;
final float right = cx + radiusX;
final float bottom = cy + radiusY;
this.mArcRect.set(left, top, right, bottom);
/* Append the arc to the path. */
this.mPath.arcTo(this.mArcRect, startAngle, sweepAngle);
}
private void generateClose() {
this.assertParameterCount(0);
this.mPath.close();
this.mLastX = this.mSubPathStartX;
this.mLastY = this.mSubPathStartY;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
/**
* Parses numbers from SVG text. Based on the Batik Number Parser (Apache 2 License).
*
* @author Apache Software Foundation
* @author Larva Labs LLC
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
*/
public class PathParserHelper {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
private char read() {
if (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
SVGPathParser.this.mPosition++;
}
if (SVGPathParser.this.mPosition == SVGPathParser.this.mLength) {
return '\0';
} else {
return SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition);
}
}
public void skipWhitespace() {
while (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
if (Character.isWhitespace(SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition))) {
this.advance();
} else {
break;
}
}
}
public void skipNumberSeparator() {
while (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
final char c = SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition);
switch (c) {
case ' ':
case ',':
case '\n':
case '\t':
this.advance();
break;
default:
return;
}
}
}
public void advance() {
SVGPathParser.this.mCurrentChar = this.read();
}
/**
* Parses the content of the buffer and converts it to a float.
*/
private float parseFloat() {
int mantissa = 0;
int mantissaDigit = 0;
boolean mantPosition = true;
boolean mantissaRead = false;
int exp = 0;
int expDig = 0;
int expAdj = 0;
boolean expPos = true;
switch (SVGPathParser.this.mCurrentChar) {
case '-':
mantPosition = false;
case '+':
SVGPathParser.this.mCurrentChar = this.read();
}
m1: switch (SVGPathParser.this.mCurrentChar) {
default:
return Float.NaN;
case '.':
break;
case '0':
mantissaRead = true;
l: for (;;) {
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break l;
case '.': case 'e': case 'E':
break m1;
default:
return 0.0f;
case '0':
}
}
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
mantissaRead = true;
l: for (;;) {
if (mantissaDigit < 9) {
mantissaDigit++;
mantissa = mantissa * 10 + (SVGPathParser.this.mCurrentChar - '0');
} else {
expAdj++;
}
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
default:
break l;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
}
}
}
if (SVGPathParser.this.mCurrentChar == '.') {
SVGPathParser.this.mCurrentChar = this.read();
m2: switch (SVGPathParser.this.mCurrentChar) {
default:
case 'e': case 'E':
if (!mantissaRead) {
throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
}
break;
case '0':
if (mantissaDigit == 0) {
l: for (;;) {
SVGPathParser.this.mCurrentChar = this.read();
expAdj--;
switch (SVGPathParser.this.mCurrentChar) {
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break l;
default:
if (!mantissaRead) {
return 0.0f;
}
break m2;
case '0':
}
}
}
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
l: for (;;) {
if (mantissaDigit < 9) {
mantissaDigit++;
mantissa = mantissa * 10 + (SVGPathParser.this.mCurrentChar - '0');
expAdj--;
}
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
default:
break l;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
}
}
}
}
switch (SVGPathParser.this.mCurrentChar) {
case 'e': case 'E':
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
default:
throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
case '-':
expPos = false;
case '+':
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
default:
throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
}
en: switch (SVGPathParser.this.mCurrentChar) {
case '0':
l: for (;;) {
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break l;
default:
break en;
case '0':
}
}
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
l: for (;;) {
if (expDig < 3) {
expDig++;
exp = exp * 10 + (SVGPathParser.this.mCurrentChar - '0');
}
SVGPathParser.this.mCurrentChar = this.read();
switch (SVGPathParser.this.mCurrentChar) {
default:
break l;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
}
}
}
default:
}
if (!expPos) {
exp = -exp;
}
exp += expAdj;
if (!mantPosition) {
mantissa = -mantissa;
}
return this.buildFloat(mantissa, exp);
}
public float nextFloat() {
this.skipWhitespace();
final float f = this.parseFloat();
this.skipNumberSeparator();
return f;
}
public float buildFloat(int pMantissa, final int pExponent) {
if (pExponent < -125 || pMantissa == 0) {
return 0.0f;
}
if (pExponent >= 128) {
return (pMantissa > 0)
? Float.POSITIVE_INFINITY
: Float.NEGATIVE_INFINITY;
}
if (pExponent == 0) {
return pMantissa;
}
if (pMantissa >= (1 << 26)) {
pMantissa++; // round up trailing bits if they will be dropped.
}
return (float) ((pExponent > 0) ? pMantissa * org.andengine.extension.svg.util.constants.MathUtils.POWERS_OF_10[pExponent] : pMantissa / org.andengine.extension.svg.util.constants.MathUtils.POWERS_OF_10[-pExponent]);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
}