1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15(function(scope, testing) { 16 17 // This returns a function for converting transform functions to equivalent 18 // primitive functions, which will take an array of values from the 19 // derivative type and fill in the blanks (underscores) with them. 20 var _ = null; 21 function cast(pattern) { 22 return function(contents) { 23 var i = 0; 24 return pattern.map(function(x) { return x === _ ? contents[i++] : x; }); 25 } 26 } 27 28 function id(x) { return x; } 29 30 var Opx = {px: 0}; 31 var Odeg = {deg: 0}; 32 33 // type: [argTypes, convertTo3D, convertTo2D] 34 // In the argument types string, lowercase characters represent optional arguments 35 var transformFunctions = { 36 matrix: ['NNNNNN', [_, _, 0, 0, _, _, 0, 0, 0, 0, 1, 0, _, _, 0, 1], id], 37 matrix3d: ['NNNNNNNNNNNNNNNN', id], 38 rotate: ['A'], 39 rotatex: ['A'], 40 rotatey: ['A'], 41 rotatez: ['A'], 42 rotate3d: ['NNNA'], 43 perspective: ['L'], 44 scale: ['Nn', cast([_, _, 1]), id], 45 scalex: ['N', cast([_, 1, 1]), cast([_, 1])], 46 scaley: ['N', cast([1, _, 1]), cast([1, _])], 47 scalez: ['N', cast([1, 1, _])], 48 scale3d: ['NNN', id], 49 skew: ['Aa', null, id], 50 skewx: ['A', null, cast([_, Odeg])], 51 skewy: ['A', null, cast([Odeg, _])], 52 translate: ['Tt', cast([_, _, Opx]), id], 53 translatex: ['T', cast([_, Opx, Opx]), cast([_, Opx])], 54 translatey: ['T', cast([Opx, _, Opx]), cast([Opx, _])], 55 translatez: ['L', cast([Opx, Opx, _])], 56 translate3d: ['TTL', id], 57 }; 58 59 function parseTransform(string) { 60 string = string.toLowerCase().trim(); 61 if (string == 'none') 62 return []; 63 // FIXME: Using a RegExp means calcs won't work here 64 var transformRegExp = /\s*(\w+)\(([^)]*)\)/g; 65 var result = []; 66 var match; 67 var prevLastIndex = 0; 68 while (match = transformRegExp.exec(string)) { 69 if (match.index != prevLastIndex) 70 return; 71 prevLastIndex = match.index + match[0].length; 72 var functionName = match[1]; 73 var functionData = transformFunctions[functionName]; 74 if (!functionData) 75 return; 76 var args = match[2].split(','); 77 var argTypes = functionData[0]; 78 if (argTypes.length < args.length) 79 return; 80 81 var parsedArgs = []; 82 for (var i = 0; i < argTypes.length; i++) { 83 var arg = args[i]; 84 var type = argTypes[i]; 85 var parsedArg; 86 if (!arg) 87 parsedArg = ({a: Odeg, 88 n: parsedArgs[0], 89 t: Opx})[type]; 90 else 91 parsedArg = ({A: function(s) { return s.trim() == '0' ? Odeg : scope.parseAngle(s); }, 92 N: scope.parseNumber, 93 T: scope.parseLengthOrPercent, 94 L: scope.parseLength})[type.toUpperCase()](arg); 95 if (parsedArg === undefined) 96 return; 97 parsedArgs.push(parsedArg); 98 } 99 result.push({t: functionName, d: parsedArgs}); 100 101 if (transformRegExp.lastIndex == string.length) 102 return result; 103 } 104 }; 105 106 function numberToLongString(x) { 107 return x.toFixed(6).replace('.000000', ''); 108 } 109 110 function mergeMatrices(left, right) { 111 if (left.decompositionPair !== right) { 112 left.decompositionPair = right; 113 var leftArgs = scope.makeMatrixDecomposition(left); 114 } 115 if (right.decompositionPair !== left) { 116 right.decompositionPair = left; 117 var rightArgs = scope.makeMatrixDecomposition(right); 118 } 119 if (leftArgs[0] == null || rightArgs[0] == null) 120 return [[false], [true], function(x) { return x ? right[0].d : left[0].d; }]; 121 leftArgs[0].push(0); 122 rightArgs[0].push(1); 123 return [ 124 leftArgs, 125 rightArgs, 126 function(list) { 127 var quat = scope.quat(leftArgs[0][3], rightArgs[0][3], list[5]); 128 var mat = scope.composeMatrix(list[0], list[1], list[2], quat, list[4]); 129 var stringifiedArgs = mat.map(numberToLongString).join(','); 130 return stringifiedArgs; 131 } 132 ]; 133 } 134 135 function typeTo2D(type) { 136 return type.replace(/[xy]/, ''); 137 } 138 139 function typeTo3D(type) { 140 return type.replace(/(x|y|z|3d)?$/, '3d'); 141 } 142 143 function mergeTransforms(left, right) { 144 var matrixModulesLoaded = scope.makeMatrixDecomposition && true; 145 146 var flipResults = false; 147 if (!left.length || !right.length) { 148 if (!left.length) { 149 flipResults = true; 150 left = right; 151 right = []; 152 } 153 for (var i = 0; i < left.length; i++) { 154 var type = left[i].t; 155 var args = left[i].d; 156 var defaultValue = type.substr(0, 5) == 'scale' ? 1 : 0; 157 right.push({t: type, d: args.map(function(arg) { 158 if (typeof arg == 'number') 159 return defaultValue; 160 var result = {}; 161 for (var unit in arg) 162 result[unit] = defaultValue; 163 return result; 164 })}); 165 } 166 } 167 168 var isMatrixOrPerspective = function(lt, rt) { 169 return ((lt == 'perspective') && (rt == 'perspective')) || 170 ((lt == 'matrix' || lt == 'matrix3d') && (rt == 'matrix' || rt == 'matrix3d')); 171 }; 172 var leftResult = []; 173 var rightResult = []; 174 var types = []; 175 176 if (left.length != right.length) { 177 if (!matrixModulesLoaded) 178 return; 179 var merged = mergeMatrices(left, right); 180 leftResult = [merged[0]]; 181 rightResult = [merged[1]]; 182 types = [['matrix', [merged[2]]]]; 183 } else { 184 for (var i = 0; i < left.length; i++) { 185 var leftType = left[i].t; 186 var rightType = right[i].t; 187 var leftArgs = left[i].d; 188 var rightArgs = right[i].d; 189 190 var leftFunctionData = transformFunctions[leftType]; 191 var rightFunctionData = transformFunctions[rightType]; 192 193 var type; 194 if (isMatrixOrPerspective(leftType, rightType)) { 195 if (!matrixModulesLoaded) 196 return; 197 var merged = mergeMatrices([left[i]], [right[i]]); 198 leftResult.push(merged[0]); 199 rightResult.push(merged[1]); 200 types.push(['matrix', [merged[2]]]); 201 continue; 202 } else if (leftType == rightType) { 203 type = leftType; 204 } else if (leftFunctionData[2] && rightFunctionData[2] && typeTo2D(leftType) == typeTo2D(rightType)) { 205 type = typeTo2D(leftType); 206 leftArgs = leftFunctionData[2](leftArgs); 207 rightArgs = rightFunctionData[2](rightArgs); 208 } else if (leftFunctionData[1] && rightFunctionData[1] && typeTo3D(leftType) == typeTo3D(rightType)) { 209 type = typeTo3D(leftType); 210 leftArgs = leftFunctionData[1](leftArgs); 211 rightArgs = rightFunctionData[1](rightArgs); 212 } else { 213 if (!matrixModulesLoaded) 214 return; 215 var merged = mergeMatrices(left, right); 216 leftResult = [merged[0]]; 217 rightResult = [merged[1]]; 218 types = [['matrix', [merged[2]]]]; 219 break; 220 } 221 222 var leftArgsCopy = []; 223 var rightArgsCopy = []; 224 var stringConversions = []; 225 for (var j = 0; j < leftArgs.length; j++) { 226 var merge = typeof leftArgs[j] == 'number' ? scope.mergeNumbers : scope.mergeDimensions; 227 var merged = merge(leftArgs[j], rightArgs[j]); 228 leftArgsCopy[j] = merged[0]; 229 rightArgsCopy[j] = merged[1]; 230 stringConversions.push(merged[2]); 231 } 232 leftResult.push(leftArgsCopy); 233 rightResult.push(rightArgsCopy); 234 types.push([type, stringConversions]); 235 } 236 } 237 238 if (flipResults) { 239 var tmp = leftResult; 240 leftResult = rightResult; 241 rightResult = tmp; 242 } 243 244 return [leftResult, rightResult, function(list) { 245 return list.map(function(args, i) { 246 var stringifiedArgs = args.map(function(arg, j) { 247 return types[i][1][j](arg); 248 }).join(','); 249 if (types[i][0] == 'matrix' && stringifiedArgs.split(',').length == 16) 250 types[i][0] = 'matrix3d'; 251 return types[i][0] + '(' + stringifiedArgs + ')'; 252 253 }).join(' '); 254 }]; 255 } 256 257 scope.addPropertiesHandler(parseTransform, mergeTransforms, ['transform']); 258 259 scope.transformToSvgMatrix = function(string) { 260 // matrix(<a> <b> <c> <d> <e> <f>) 261 var mat = scope.transformListToMatrix(parseTransform(string)); 262 return 'matrix(' + 263 numberToLongString(mat[0]) + ' ' + // <a> 264 numberToLongString(mat[1]) + ' ' + // <b> 265 numberToLongString(mat[4]) + ' ' + // <c> 266 numberToLongString(mat[5]) + ' ' + // <d> 267 numberToLongString(mat[12]) + ' ' + // <e> 268 numberToLongString(mat[13]) + // <f> 269 ')'; 270 }; 271 272 if (WEB_ANIMATIONS_TESTING) 273 testing.parseTransform = parseTransform; 274 275})(webAnimations1, webAnimationsTesting); 276