package org.andengine.extension.svg; import java.util.Stack; import org.andengine.extension.svg.adt.ISVGColorMapper; import org.andengine.extension.svg.adt.SVGGradient; import org.andengine.extension.svg.adt.SVGGradient.SVGGradientStop; import org.andengine.extension.svg.adt.SVGGroup; import org.andengine.extension.svg.adt.SVGPaint; import org.andengine.extension.svg.adt.SVGProperties; import org.andengine.extension.svg.adt.filter.SVGFilter; import org.andengine.extension.svg.adt.filter.element.ISVGFilterElement; import org.andengine.extension.svg.util.SAXHelper; import org.andengine.extension.svg.util.SVGCircleParser; import org.andengine.extension.svg.util.SVGEllipseParser; import org.andengine.extension.svg.util.SVGLineParser; import org.andengine.extension.svg.util.SVGPathParser; import org.andengine.extension.svg.util.SVGPolygonParser; import org.andengine.extension.svg.util.SVGPolylineParser; import org.andengine.extension.svg.util.SVGRectParser; import org.andengine.extension.svg.util.SVGTransformParser; import org.andengine.extension.svg.util.constants.ISVGConstants; import org.andengine.util.debug.Debug; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Picture; import android.graphics.RectF; import android.util.FloatMath; /** * @author Larva Labs, LLC * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 16:50:02 - 21.05.2011 */ public class SVGHandler extends DefaultHandler implements ISVGConstants { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private Canvas mCanvas; private final Picture mPicture; private final SVGPaint mSVGPaint; private boolean mBoundsMode; private RectF mBounds; private final Stack<SVGGroup> mSVGGroupStack = new Stack<SVGGroup>(); private final SVGPathParser mSVGPathParser = new SVGPathParser(); private SVGGradient mCurrentSVGGradient; private SVGFilter mCurrentSVGFilter; private boolean mHidden; /** Multi purpose dummy rectangle. */ private final RectF mRect = new RectF(); // =========================================================== // Constructors // =========================================================== public SVGHandler(final Picture pPicture, final ISVGColorMapper pSVGColorMapper) { this.mPicture = pPicture; this.mSVGPaint = new SVGPaint(pSVGColorMapper); } // =========================================================== // Getter & Setter // =========================================================== public RectF getBounds() { return this.mBounds; } public RectF getComputedBounds() { return this.mSVGPaint.getComputedBounds(); } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void startElement(final String pNamespace, final String pLocalName, final String pQualifiedName, final Attributes pAttributes) throws SAXException { /* Ignore everything but rectangles in bounds mode. */ if (this.mBoundsMode) { this.parseBounds(pLocalName, pAttributes); return; } if (pLocalName.equals(TAG_SVG)) { this.parseSVG(pAttributes); } else if(pLocalName.equals(TAG_DEFS)) { // Ignore } else if(pLocalName.equals(TAG_GROUP)) { this.parseGroup(pAttributes); } else if(pLocalName.equals(TAG_LINEARGRADIENT)) { this.parseLinearGradient(pAttributes); } else if(pLocalName.equals(TAG_RADIALGRADIENT)) { this.parseRadialGradient(pAttributes); } else if(pLocalName.equals(TAG_STOP)) { this.parseGradientStop(pAttributes); } else if(pLocalName.equals(TAG_FILTER)) { this.parseFilter(pAttributes); } else if(pLocalName.equals(TAG_FILTER_ELEMENT_FEGAUSSIANBLUR)) { this.parseFilterElementGaussianBlur(pAttributes); } else if(!this.mHidden) { if(pLocalName.equals(TAG_RECTANGLE)) { this.parseRect(pAttributes); } else if(pLocalName.equals(TAG_LINE)) { this.parseLine(pAttributes); } else if(pLocalName.equals(TAG_CIRCLE)) { this.parseCircle(pAttributes); } else if(pLocalName.equals(TAG_ELLIPSE)) { this.parseEllipse(pAttributes); } else if(pLocalName.equals(TAG_POLYLINE)) { this.parsePolyline(pAttributes); } else if(pLocalName.equals(TAG_POLYGON)) { this.parsePolygon(pAttributes); } else if(pLocalName.equals(TAG_PATH)) { this.parsePath(pAttributes); } else { Debug.d("Unexpected SVG tag: '" + pLocalName + "'."); } } else { Debug.d("Unexpected SVG tag: '" + pLocalName + "'."); } } @Override public void endElement(final String pNamespace, final String pLocalName, final String pQualifiedName) throws SAXException { if (pLocalName.equals(TAG_SVG)) { this.mPicture.endRecording(); } else if (pLocalName.equals(TAG_GROUP)) { this.parseGroupEnd(); } } // =========================================================== // Methods // =========================================================== private void parseSVG(final Attributes pAttributes) { final int width = (int) FloatMath.ceil(SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_WIDTH, 0f)); final int height = (int) FloatMath.ceil(SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_HEIGHT, 0f)); this.mCanvas = this.mPicture.beginRecording(width, height); } private void parseBounds(final String pLocalName, final Attributes pAttributes) { if (pLocalName.equals(TAG_RECTANGLE)) { final float x = SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_X, 0f); final float y = SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_Y, 0f); final float width = SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_WIDTH, 0f); final float height = SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_HEIGHT, 0f); this.mBounds = new RectF(x, y, x + width, y + height); } } private void parseFilter(final Attributes pAttributes) { this.mCurrentSVGFilter = this.mSVGPaint.parseFilter(pAttributes); } private void parseFilterElementGaussianBlur(final Attributes pAttributes) { final ISVGFilterElement svgFilterElement = this.mSVGPaint.parseFilterElementGaussianBlur(pAttributes); this.mCurrentSVGFilter.addFilterElement(svgFilterElement); } private void parseLinearGradient(final Attributes pAttributes) { this.mCurrentSVGGradient = this.mSVGPaint.parseGradient(pAttributes, true); } private void parseRadialGradient(final Attributes pAttributes) { this.mCurrentSVGGradient = this.mSVGPaint.parseGradient(pAttributes, false); } private void parseGradientStop(final Attributes pAttributes) { final SVGGradientStop svgGradientStop = this.mSVGPaint.parseGradientStop(this.getSVGPropertiesFromAttributes(pAttributes)); this.mCurrentSVGGradient.addSVGGradientStop(svgGradientStop); } private void parseGroup(final Attributes pAttributes) { /* Check to see if this is the "bounds" layer. */ if ("bounds".equals(SAXHelper.getStringAttribute(pAttributes, ATTRIBUTE_ID))) { this.mBoundsMode = true; } final SVGGroup parentSVGGroup = (this.mSVGGroupStack.size() > 0) ? this.mSVGGroupStack.peek() : null; final boolean hasTransform = this.pushTransform(pAttributes); this.mSVGGroupStack.push(new SVGGroup(parentSVGGroup, this.getSVGPropertiesFromAttributes(pAttributes, true), hasTransform)); this.updateHidden(); } private void parseGroupEnd() { if (this.mBoundsMode) { this.mBoundsMode = false; } /* Pop group transform if there was one pushed. */ if(this.mSVGGroupStack.pop().hasTransform()) { this.popTransform(); } this.updateHidden(); } private void updateHidden() { if(this.mSVGGroupStack.size() == 0) { this.mHidden = false; } else { this.mSVGGroupStack.peek().isHidden(); } } private void parsePath(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); this.mSVGPathParser.parse(svgProperties, this.mCanvas, this.mSVGPaint); if(pushed) { this.popTransform(); } } private void parsePolygon(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGPolygonParser.parse(svgProperties, this.mCanvas, this.mSVGPaint); if(pushed) { this.popTransform(); } } private void parsePolyline(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGPolylineParser.parse(svgProperties, this.mCanvas, this.mSVGPaint); if(pushed) { this.popTransform(); } } private void parseEllipse(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGEllipseParser.parse(svgProperties, this.mCanvas, this.mSVGPaint, this.mRect); if(pushed) { this.popTransform(); } } private void parseCircle(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGCircleParser.parse(svgProperties, this.mCanvas, this.mSVGPaint); if(pushed) { this.popTransform(); } } private void parseLine(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGLineParser.parse(svgProperties, this.mCanvas, this.mSVGPaint); if(pushed) { this.popTransform(); } } private void parseRect(final Attributes pAttributes) { final SVGProperties svgProperties = this.getSVGPropertiesFromAttributes(pAttributes); final boolean pushed = this.pushTransform(pAttributes); SVGRectParser.parse(svgProperties, this.mCanvas, this.mSVGPaint, this.mRect); if(pushed) { this.popTransform(); } } private SVGProperties getSVGPropertiesFromAttributes(final Attributes pAttributes) { return this.getSVGPropertiesFromAttributes(pAttributes, false); } private SVGProperties getSVGPropertiesFromAttributes(final Attributes pAttributes, final boolean pDeepCopy) { if(this.mSVGGroupStack.size() > 0) { return new SVGProperties(this.mSVGGroupStack.peek().getSVGProperties(), pAttributes, pDeepCopy); } else { return new SVGProperties(null, pAttributes, pDeepCopy); } } private boolean pushTransform(final Attributes pAttributes) { final String transform = SAXHelper.getStringAttribute(pAttributes, ATTRIBUTE_TRANSFORM); if(transform == null) { return false; } else { final Matrix matrix = SVGTransformParser.parseTransform(transform); this.mCanvas.save(); this.mCanvas.concat(matrix); return true; } } private void popTransform() { this.mCanvas.restore(); } // =========================================================== // Inner and Anonymous Classes // =========================================================== }