/*******************************************************************************
* 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;
}
}