/******************************************************************************* * Copyright 2012-present Pixate, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /** * Copyright (c) 2012-2013 Pixate, Inc. All rights reserved. */ package com.pixate.freestyle.cg.parsing; import java.util.EnumSet; import android.graphics.Matrix; import android.util.DisplayMetrics; import com.pixate.freestyle.cg.math.PXDimension; import com.pixate.freestyle.parsing.Lexeme; import com.pixate.freestyle.parsing.PXParserBase; /** * PXTransformParser generates a CGAffineTransform by parsing an SVG transform * value */ public class PXTransformParser extends PXParserBase<PXTransformTokenType> { public static Matrix IDENTITY_MATRIX = new Matrix(); private static EnumSet<PXTransformTokenType> TRANSFORM_KEYWORD_SET; private static EnumSet<PXTransformTokenType> ANGLE_SET; private static EnumSet<PXTransformTokenType> LENGTH_SET; private static EnumSet<PXTransformTokenType> PERCENTAGE_SET; static { TRANSFORM_KEYWORD_SET = EnumSet.of(PXTransformTokenType.TRANSLATE, PXTransformTokenType.TRANSLATEX, PXTransformTokenType.TRANSLATEY, PXTransformTokenType.SCALE, PXTransformTokenType.SCALEX, PXTransformTokenType.SCALEY, PXTransformTokenType.SKEW, PXTransformTokenType.SKEWX, PXTransformTokenType.SKEWY, PXTransformTokenType.ROTATE, PXTransformTokenType.MATRIX); } static { ANGLE_SET = EnumSet.of(PXTransformTokenType.NUMBER, PXTransformTokenType.ANGLE); } static { LENGTH_SET = EnumSet.of(PXTransformTokenType.NUMBER, PXTransformTokenType.LENGTH); } static { PERCENTAGE_SET = EnumSet.of(PXTransformTokenType.NUMBER, PXTransformTokenType.PERCENTAGE); } private PXTransformLexer lexer; /** * Constructs a new transform parser. */ public PXTransformParser() { lexer = new PXTransformLexer(); } /** * Parse the specified source, generating a {@link Matrix} transformation as * a result * * @param source The source to parse */ public Matrix parse(String source) { Matrix result = new Matrix(); // clear errors clearErrors(); // setup lexer and prime lexer stream lexer.setSource(source); advance(); // TODO: move try/catch inside while loop after adding some error // recovery try { while (currentLexeme != null && currentLexeme.getType() != PXTransformTokenType.EOF) { Matrix transform = parseTransform(); result.preConcat(transform); } } catch (Exception e) { addError(e.getMessage()); } return result; } @Override public Lexeme<PXTransformTokenType> advance() { currentLexeme = lexer.nextLexeme(); return currentLexeme; } /** * Parse the matrix * * @return A parsed Matrix */ private Matrix parseTransform() { Matrix result = null; // advance over keyword assertTypeInSet(TRANSFORM_KEYWORD_SET); Lexeme<PXTransformTokenType> transformType = currentLexeme; advance(); // advance over '(' assertTypeAndAdvance(PXTransformTokenType.LPAREN); switch (transformType.getType()) { case TRANSLATE: result = parseTranslate(); break; case TRANSLATEX: result = parseTranslateX(); break; case TRANSLATEY: result = parseTranslateY(); break; case SCALE: result = parseScale(); break; case SCALEX: result = parseScaleX(); break; case SCALEY: result = parseScaleY(); break; case SKEW: result = parseSkew(); break; case SKEWX: result = parseSkewX(); break; case SKEWY: result = parseSkewY(); break; case ROTATE: result = parseRotate(); break; case MATRIX: result = parseMatrix(); break; default: result = new Matrix(); errorWithMessage("Unrecognized transform type"); break; } // advance over ')' advanceIfIsType(PXTransformTokenType.RPAREN); return result; } private Matrix parseMatrix() { Matrix result = new Matrix(); float[] values = new float[9]; values[0] = floatValue(); values[3] = floatValue(); values[1] = floatValue(); values[4] = floatValue(); values[2] = floatValue(); values[5] = floatValue(); values[6] = 0; values[7] = 0; values[8] = 1; result.setValues(values); return result; } private Matrix parseRotate() { float angle = angleValue(); Matrix result = new Matrix(); if (isInTypeSet(LENGTH_SET)) { float x = lengthValue(); float y = lengthValue(); result.setTranslate(x, y); result.setRotate(angle); result.setTranslate(-x, -y); } else { result.setRotate(angle); } return result; } private Matrix parseScale() { float sx = floatValue(); float sy = (isType(PXTransformTokenType.NUMBER)) ? floatValue() : sx; Matrix result = new Matrix(); result.setScale(sx, sy); return result; } private Matrix parseScaleX() { float sx = floatValue(); Matrix result = new Matrix(); result.setScale(sx, 1.0f); return result; } private Matrix parseScaleY() { float sy = floatValue(); Matrix result = new Matrix(); result.setScale(1.0f, sy); return result; } private Matrix parseSkew() { float sx = (float) Math.tan(angleValue()); float sy = (float) ((isInTypeSet(ANGLE_SET)) ? Math.tan(angleValue()) : 0.0f); Matrix result = new Matrix(); result.setValues(new float[] { 1f, sy, 0f, sx, 1f, 0f, 0f, 0f, 1f }); return result; } private Matrix parseSkewX() { float sx = (float) Math.tan(angleValue()); Matrix result = new Matrix(); result.setValues(new float[] { 1f, 0f, 0f, sx, 1f, 0f, 0f, 0f, 1f }); return result; } private Matrix parseSkewY() { float sy = (float) Math.tan(angleValue()); Matrix result = new Matrix(); result.setValues(new float[] { 1f, sy, 0f, 0f, 1f, 0f, 0f, 0f, 1f }); return result; } private Matrix parseTranslate() { float tx = lengthValue(); float ty = (isInTypeSet(LENGTH_SET)) ? lengthValue() : 0.0f; Matrix result = new Matrix(); result.setTranslate(tx, ty); return result; } private Matrix parseTranslateX() { float tx = lengthValue(); Matrix result = new Matrix(); result.setTranslate(tx, 0.0f); return result; } private Matrix parseTranslateY() { float ty = lengthValue(); Matrix result = new Matrix(); result.setTranslate(0.0f, ty); return result; } private float angleValue() { float result = 0.0f; if (isInTypeSet(ANGLE_SET)) { switch (currentLexeme.getType()) { case NUMBER: { result = (float) Math.toRadians((Float) currentLexeme.getValue()); break; } case ANGLE: { PXDimension angle = (PXDimension) currentLexeme.getValue(); result = angle.radians().getNumber(); break; } default: { errorWithMessage("Unrecognized token type in LENGTH_SET: " + currentLexeme); break; } } advance(); advanceIfIsType(PXTransformTokenType.COMMA); } return result; } private float floatValue() { float result = 0.0f; if (isType(PXTransformTokenType.NUMBER)) { result = (Float) currentLexeme.getValue(); advance(); advanceIfIsType(PXTransformTokenType.COMMA); } else { errorWithMessage("Expected a NUMBER token"); } return result; } private float lengthValue() { float result = 0.0f; if (isInTypeSet(LENGTH_SET)) { switch (currentLexeme.getType()) { case NUMBER: { result = (Float) currentLexeme.getValue(); break; } case LENGTH: { PXDimension length = (PXDimension) currentLexeme.getValue(); // FIXME - we need the real DisplayMetrics! DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); result = length.points(metrics).getNumber(); break; } default: { errorWithMessage("Unrecognized token type in LENGTH_SET: " + currentLexeme); break; } } advance(); advanceIfIsType(PXTransformTokenType.COMMA); } else { errorWithMessage("Expected a LENGTH or NUMBER token"); } return result; } @SuppressWarnings("unused") /* Unused here, and appears to be unused in iOS as well. */ private float percentageValue() { float result = 0.0f; if (isInTypeSet(PERCENTAGE_SET)) { switch (currentLexeme.getType()) { case PERCENTAGE: { PXDimension percentage = (PXDimension) currentLexeme.getValue(); result = percentage.getNumber() / 100.0f; break; } case NUMBER: { result = (Float) currentLexeme.getValue(); break; } default: { errorWithMessage("Unrecognized token type in PERCENTAGE_SET: " + currentLexeme); break; } } advance(); advanceIfIsType(PXTransformTokenType.COMMA); } else { errorWithMessage("Expected a PERCENTAGE or NUMBER token"); } return result; } }