package org.andengine.extension.svg.util; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.andengine.extension.svg.exception.SVGParseException; import org.andengine.extension.svg.util.SVGNumberParser.SVGNumberParserFloatResult; import org.andengine.extension.svg.util.constants.ISVGConstants; import android.graphics.Matrix; /** * @author Larva Labs, LLC * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 16:56:54 - 21.05.2011 */ public class SVGTransformParser implements ISVGConstants { // =========================================================== // Constants // =========================================================== private static final Pattern MULTITRANSFORM_PATTERN = Pattern.compile("(\\w+\\([\\d\\s\\-eE,]*\\))"); // =========================================================== // Fields // =========================================================== // =========================================================== // Constructors // =========================================================== // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== public static Matrix parseTransform(final String pString) { if(pString == null) { return null; } /* If ')' is contained only once, we have a simple/single transform. * Otherwise, we have to split multi-transforms like this: * "translate(-10,-20) scale(2) rotate(45) translate(5,10)". */ final boolean singleTransform = pString.indexOf(')') == pString.lastIndexOf(')'); if(singleTransform) { return SVGTransformParser.parseSingleTransform(pString); } else { return SVGTransformParser.parseMultiTransform(pString); } } private static Matrix parseMultiTransform(final String pString) { final Matcher matcher = MULTITRANSFORM_PATTERN.matcher(pString); final Matrix matrix = new Matrix(); while(matcher.find()) { matrix.preConcat(SVGTransformParser.parseSingleTransform(matcher.group(1))); } return matrix; } private static Matrix parseSingleTransform(final String pString) { try { if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_MATRIX)) { return SVGTransformParser.parseTransformMatrix(pString); } else if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_TRANSLATE)) { return SVGTransformParser.parseTransformTranslate(pString); } else if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_SCALE)) { return SVGTransformParser.parseTransformScale(pString); } else if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_SKEW_X)) { return SVGTransformParser.parseTransformSkewX(pString); } else if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_SKEW_Y)) { return SVGTransformParser.parseTransformSkewY(pString); } else if (pString.startsWith(ATTRIBUTE_TRANSFORM_VALUE_ROTATE)) { return SVGTransformParser.parseTransformRotate(pString); } else { throw new SVGParseException("Unexpected transform type: '" + pString + "'."); } } catch (final SVGParseException e) { throw new SVGParseException("Could not parse transform: '" + pString + "'.", e); } } public static Matrix parseTransformRotate(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_ROTATE.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCountMinimum(svgNumberParserFloatResult, 1); final float angle = svgNumberParserFloatResult.getNumber(0); float cx = 0; float cy = 0; if (svgNumberParserFloatResult.getNumberCount() > 2) { cx = svgNumberParserFloatResult.getNumber(1); cy = svgNumberParserFloatResult.getNumber(2); } final Matrix matrix = new Matrix(); matrix.postTranslate(cx, cy); matrix.postRotate(angle); matrix.postTranslate(-cx, -cy); return matrix; } private static Matrix parseTransformSkewY(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_SKEW_Y.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCountMinimum(svgNumberParserFloatResult, 1); final float angle = svgNumberParserFloatResult.getNumber(0); final Matrix matrix = new Matrix(); matrix.postSkew(0, (float) Math.tan(angle)); return matrix; } private static Matrix parseTransformSkewX(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_SKEW_X.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCountMinimum(svgNumberParserFloatResult, 1); final float angle = svgNumberParserFloatResult.getNumber(0); final Matrix matrix = new Matrix(); matrix.postSkew((float) Math.tan(angle), 0); return matrix; } private static Matrix parseTransformScale(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_SCALE.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCountMinimum(svgNumberParserFloatResult, 1); final float sx = svgNumberParserFloatResult.getNumber(0); float sy = 0; if (svgNumberParserFloatResult.getNumberCount() > 1) { sy = svgNumberParserFloatResult.getNumber(1); } final Matrix matrix = new Matrix(); matrix.postScale(sx, sy); return matrix; } private static Matrix parseTransformTranslate(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_TRANSLATE.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCountMinimum(svgNumberParserFloatResult, 1); final float tx = svgNumberParserFloatResult.getNumber(0); float ty = 0; if (svgNumberParserFloatResult.getNumberCount() > 1) { ty = svgNumberParserFloatResult.getNumber(1); } final Matrix matrix = new Matrix(); matrix.postTranslate(tx, ty); return matrix; } private static Matrix parseTransformMatrix(final String pString) { final SVGNumberParserFloatResult svgNumberParserFloatResult = SVGNumberParser.parseFloats(pString.substring(ATTRIBUTE_TRANSFORM_VALUE_MATRIX.length() + 1, pString.indexOf(')'))); SVGTransformParser.assertNumberParserResultNumberCount(svgNumberParserFloatResult, 6); final Matrix matrix = new Matrix(); matrix.setValues(new float[]{ // Row 1 svgNumberParserFloatResult.getNumber(0), svgNumberParserFloatResult.getNumber(2), svgNumberParserFloatResult.getNumber(4), // Row 2 svgNumberParserFloatResult.getNumber(1), svgNumberParserFloatResult.getNumber(3), svgNumberParserFloatResult.getNumber(5), // Row 3 0, 0, 1, }); return matrix; } private static void assertNumberParserResultNumberCountMinimum(final SVGNumberParserFloatResult pSVGNumberParserFloatResult, final int pNumberParserResultNumberCountMinimum) { final int svgNumberParserFloatResultNumberCount = pSVGNumberParserFloatResult.getNumberCount(); if(svgNumberParserFloatResultNumberCount < pNumberParserResultNumberCountMinimum) { throw new SVGParseException("Not enough data. Minimum Expected: '" + pNumberParserResultNumberCountMinimum + "'. Actual: '" + svgNumberParserFloatResultNumberCount + "'."); } } private static void assertNumberParserResultNumberCount(final SVGNumberParserFloatResult pSVGNumberParserFloatResult, final int pNumberParserResultNumberCount) { final int svgNumberParserFloatResultNumberCount = pSVGNumberParserFloatResult.getNumberCount(); if(svgNumberParserFloatResultNumberCount != pNumberParserResultNumberCount) { throw new SVGParseException("Unexpected number count. Expected: '" + pNumberParserResultNumberCount + "'. Actual: '" + svgNumberParserFloatResultNumberCount + "'."); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }