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  function parseDimension(unitRegExp, string) {
18    string = string.trim().toLowerCase();
19
20    if (string == '0' && 'px'.search(unitRegExp) >= 0)
21      return {px: 0};
22
23    // If we have parenthesis, we're a calc and need to start with 'calc'.
24    if (!/^[^(]*$|^calc/.test(string))
25      return;
26    string = string.replace(/calc\(/g, '(');
27
28    // We tag units by prefixing them with 'U' (note that we are already
29    // lowercase) to prevent problems with types which are substrings of
30    // each other (although prefixes may be problematic!)
31    var matchedUnits = {};
32    string = string.replace(unitRegExp, function(match) {
33      matchedUnits[match] = null;
34      return 'U' + match;
35    });
36    var taggedUnitRegExp = 'U(' + unitRegExp.source + ')';
37
38    // Validating input is simply applying as many reductions as we can.
39    var typeCheck = string.replace(/[-+]?(\d*\.)?\d+/g, 'N')
40                          .replace(new RegExp('N' + taggedUnitRegExp, 'g'), 'D')
41                          .replace(/\s[+-]\s/g, 'O')
42                          .replace(/\s/g, '');
43    var reductions = [/N\*(D)/g, /(N|D)[*/]N/g, /(N|D)O\1/g, /\((N|D)\)/g];
44    var i = 0;
45    while (i < reductions.length) {
46      if (reductions[i].test(typeCheck)) {
47        typeCheck = typeCheck.replace(reductions[i], '$1');
48        i = 0;
49      } else {
50        i++;
51      }
52    }
53    if (typeCheck != 'D')
54      return;
55
56    for (var unit in matchedUnits) {
57      var result = eval(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
58      if (!isFinite(result))
59        return;
60      matchedUnits[unit] = result;
61    }
62    return matchedUnits;
63  }
64
65  function mergeDimensionsNonNegative(left, right) {
66    return mergeDimensions(left, right, true);
67  }
68
69  function mergeDimensions(left, right, nonNegative) {
70    var units = [], unit;
71    for (unit in left)
72      units.push(unit);
73    for (unit in right) {
74      if (units.indexOf(unit) < 0)
75        units.push(unit);
76    }
77
78    left = units.map(function(unit) { return left[unit] || 0; });
79    right = units.map(function(unit) { return right[unit] || 0; });
80    return [left, right, function(values) {
81      var result = values.map(function(value, i) {
82        if (values.length == 1 && nonNegative) {
83          value = Math.max(value, 0);
84        }
85        // Scientific notation (e.g. 1e2) is not yet widely supported by browser vendors.
86        return scope.numberToString(value) + units[i];
87      }).join(' + ');
88      return values.length > 1 ? 'calc(' + result + ')' : result;
89    }];
90  }
91
92  var lengthUnits = 'px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc';
93  var parseLength = parseDimension.bind(null, new RegExp(lengthUnits, 'g'));
94  var parseLengthOrPercent = parseDimension.bind(null, new RegExp(lengthUnits + '|%', 'g'));
95  var parseAngle = parseDimension.bind(null, /deg|rad|grad|turn/g);
96
97  scope.parseLength = parseLength;
98  scope.parseLengthOrPercent = parseLengthOrPercent;
99  scope.consumeLengthOrPercent = scope.consumeParenthesised.bind(null, parseLengthOrPercent);
100  scope.parseAngle = parseAngle;
101  scope.mergeDimensions = mergeDimensions;
102
103  var consumeLength = scope.consumeParenthesised.bind(null, parseLength);
104  var consumeSizePair = scope.consumeRepeated.bind(undefined, consumeLength, /^/);
105  var consumeSizePairList = scope.consumeRepeated.bind(undefined, consumeSizePair, /^,/);
106  scope.consumeSizePairList = consumeSizePairList;
107
108  var parseSizePairList = function(input) {
109    var result = consumeSizePairList(input);
110    if (result && result[1] == '') {
111      return result[0];
112    }
113  };
114
115  var mergeNonNegativeSizePair = scope.mergeNestedRepeated.bind(undefined, mergeDimensionsNonNegative, ' ');
116  var mergeNonNegativeSizePairList = scope.mergeNestedRepeated.bind(undefined, mergeNonNegativeSizePair, ',');
117  scope.mergeNonNegativeSizePair = mergeNonNegativeSizePair;
118
119  scope.addPropertiesHandler(parseSizePairList, mergeNonNegativeSizePairList, [
120    'background-size'
121  ]);
122
123  scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensionsNonNegative, [
124    'border-bottom-width',
125    'border-image-width',
126    'border-left-width',
127    'border-right-width',
128    'border-top-width',
129    'flex-basis',
130    'font-size',
131    'height',
132    'line-height',
133    'max-height',
134    'max-width',
135    'outline-width',
136    'width',
137  ]);
138
139  scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensions, [
140    'border-bottom-left-radius',
141    'border-bottom-right-radius',
142    'border-top-left-radius',
143    'border-top-right-radius',
144    'bottom',
145    'left',
146    'letter-spacing',
147    'margin-bottom',
148    'margin-left',
149    'margin-right',
150    'margin-top',
151    'min-height',
152    'min-width',
153    'outline-offset',
154    'padding-bottom',
155    'padding-left',
156    'padding-right',
157    'padding-top',
158    'perspective',
159    'right',
160    'shape-margin',
161    'text-indent',
162    'top',
163    'vertical-align',
164    'word-spacing',
165  ]);
166
167})(webAnimations1, webAnimationsTesting);
168