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