• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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