| // 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); |