blob: c44829610b1ae48dbf6d7d977513d833316b1472 [file] [log] [blame]
// Copyright 2014 Google Inc. All rights reserved.
//
// 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.
(function(scope, testing) {
// This returns a function for converting transform functions to equivalent
// primitive functions, which will take an array of values from the
// derivative type and fill in the blanks (underscores) with them.
var _ = null;
function cast(pattern) {
return function(contents) {
var i = 0;
return pattern.map(function(x) { return x === _ ? contents[i++] : x; });
}
}
function id(x) { return x; }
var Opx = {px: 0};
var Odeg = {deg: 0};
// type: [argTypes, convertTo3D, convertTo2D]
// In the argument types string, lowercase characters represent optional arguments
var transformFunctions = {
matrix: ['NNNNNN', [_, _, 0, 0, _, _, 0, 0, 0, 0, 1, 0, _, _, 0, 1], id],
matrix3d: ['NNNNNNNNNNNNNNNN', id],
rotate: ['A'],
rotatex: ['A'],
rotatey: ['A'],
rotatez: ['A'],
rotate3d: ['NNNA'],
perspective: ['L'],
scale: ['Nn', cast([_, _, 1]), id],
scalex: ['N', cast([_, 1, 1]), cast([_, 1])],
scaley: ['N', cast([1, _, 1]), cast([1, _])],
scalez: ['N', cast([1, 1, _])],
scale3d: ['NNN', id],
skew: ['Aa', null, id],
skewx: ['A', null, cast([_, Odeg])],
skewy: ['A', null, cast([Odeg, _])],
translate: ['Tt', cast([_, _, Opx]), id],
translatex: ['T', cast([_, Opx, Opx]), cast([_, Opx])],
translatey: ['T', cast([Opx, _, Opx]), cast([Opx, _])],
translatez: ['L', cast([Opx, Opx, _])],
translate3d: ['TTL', id],
};
function parseTransform(string) {
string = string.toLowerCase().trim();
if (string == 'none')
return [];
// FIXME: Using a RegExp means calcs won't work here
var transformRegExp = /\s*(\w+)\(([^)]*)\)/g;
var result = [];
var match;
var prevLastIndex = 0;
while (match = transformRegExp.exec(string)) {
if (match.index != prevLastIndex)
return;
prevLastIndex = match.index + match[0].length;
var functionName = match[1];
var functionData = transformFunctions[functionName];
if (!functionData)
return;
var args = match[2].split(',');
var argTypes = functionData[0];
if (argTypes.length < args.length)
return;
var parsedArgs = [];
for (var i = 0; i < argTypes.length; i++) {
var arg = args[i];
var type = argTypes[i];
var parsedArg;
if (!arg)
parsedArg = ({a: Odeg,
n: parsedArgs[0],
t: Opx})[type];
else
parsedArg = ({A: function(s) { return s.trim() == '0' ? Odeg : scope.parseAngle(s); },
N: scope.parseNumber,
T: scope.parseLengthOrPercent,
L: scope.parseLength})[type.toUpperCase()](arg);
if (parsedArg === undefined)
return;
parsedArgs.push(parsedArg);
}
result.push({t: functionName, d: parsedArgs});
if (transformRegExp.lastIndex == string.length)
return result;
}
};
function numberToLongString(x) {
return x.toFixed(6).replace('.000000', '');
}
function mergeMatrices(left, right) {
if (left.decompositionPair !== right) {
left.decompositionPair = right;
var leftArgs = scope.makeMatrixDecomposition(left);
}
if (right.decompositionPair !== left) {
right.decompositionPair = left;
var rightArgs = scope.makeMatrixDecomposition(right);
}
if (leftArgs[0] == null || rightArgs[0] == null)
return [[false], [true], function(x) { return x ? right[0].d : left[0].d; }];
leftArgs[0].push(0);
rightArgs[0].push(1);
return [
leftArgs,
rightArgs,
function(list) {
var quat = scope.quat(leftArgs[0][3], rightArgs[0][3], list[5]);
var mat = scope.composeMatrix(list[0], list[1], list[2], quat, list[4]);
var stringifiedArgs = mat.map(numberToLongString).join(',');
return stringifiedArgs;
}
];
}
function typeTo2D(type) {
return type.replace(/[xy]/, '');
}
function typeTo3D(type) {
return type.replace(/(x|y|z|3d)?$/, '3d');
}
function mergeTransforms(left, right) {
var matrixModulesLoaded = scope.makeMatrixDecomposition && true;
var flipResults = false;
if (!left.length || !right.length) {
if (!left.length) {
flipResults = true;
left = right;
right = [];
}
for (var i = 0; i < left.length; i++) {
var type = left[i].t;
var args = left[i].d;
var defaultValue = type.substr(0, 5) == 'scale' ? 1 : 0;
right.push({t: type, d: args.map(function(arg) {
if (typeof arg == 'number')
return defaultValue;
var result = {};
for (var unit in arg)
result[unit] = defaultValue;
return result;
})});
}
}
var isMatrixOrPerspective = function(lt, rt) {
return ((lt == 'perspective') && (rt == 'perspective')) ||
((lt == 'matrix' || lt == 'matrix3d') && (rt == 'matrix' || rt == 'matrix3d'));
};
var leftResult = [];
var rightResult = [];
var types = [];
if (left.length != right.length) {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices(left, right);
leftResult = [merged[0]];
rightResult = [merged[1]];
types = [['matrix', [merged[2]]]];
} else {
for (var i = 0; i < left.length; i++) {
var leftType = left[i].t;
var rightType = right[i].t;
var leftArgs = left[i].d;
var rightArgs = right[i].d;
var leftFunctionData = transformFunctions[leftType];
var rightFunctionData = transformFunctions[rightType];
var type;
if (isMatrixOrPerspective(leftType, rightType)) {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices([left[i]], [right[i]]);
leftResult.push(merged[0]);
rightResult.push(merged[1]);
types.push(['matrix', [merged[2]]]);
continue;
} else if (leftType == rightType) {
type = leftType;
} else if (leftFunctionData[2] && rightFunctionData[2] && typeTo2D(leftType) == typeTo2D(rightType)) {
type = typeTo2D(leftType);
leftArgs = leftFunctionData[2](leftArgs);
rightArgs = rightFunctionData[2](rightArgs);
} else if (leftFunctionData[1] && rightFunctionData[1] && typeTo3D(leftType) == typeTo3D(rightType)) {
type = typeTo3D(leftType);
leftArgs = leftFunctionData[1](leftArgs);
rightArgs = rightFunctionData[1](rightArgs);
} else {
if (!matrixModulesLoaded)
return;
var merged = mergeMatrices(left, right);
leftResult = [merged[0]];
rightResult = [merged[1]];
types = [['matrix', [merged[2]]]];
break;
}
var leftArgsCopy = [];
var rightArgsCopy = [];
var stringConversions = [];
for (var j = 0; j < leftArgs.length; j++) {
var merge = typeof leftArgs[j] == 'number' ? scope.mergeNumbers : scope.mergeDimensions;
var merged = merge(leftArgs[j], rightArgs[j]);
leftArgsCopy[j] = merged[0];
rightArgsCopy[j] = merged[1];
stringConversions.push(merged[2]);
}
leftResult.push(leftArgsCopy);
rightResult.push(rightArgsCopy);
types.push([type, stringConversions]);
}
}
if (flipResults) {
var tmp = leftResult;
leftResult = rightResult;
rightResult = tmp;
}
return [leftResult, rightResult, function(list) {
return list.map(function(args, i) {
var stringifiedArgs = args.map(function(arg, j) {
return types[i][1][j](arg);
}).join(',');
if (types[i][0] == 'matrix' && stringifiedArgs.split(',').length == 16)
types[i][0] = 'matrix3d';
return types[i][0] + '(' + stringifiedArgs + ')';
}).join(' ');
}];
}
scope.addPropertiesHandler(parseTransform, mergeTransforms, ['transform']);
if (WEB_ANIMATIONS_TESTING)
testing.parseTransform = parseTransform;
})(webAnimations1, webAnimationsTesting);