OLD | NEW |
(Empty) | |
| 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 // Evaluates a calc expression. |
| 18 // https://drafts.csswg.org/css-values-3/#calc-notation |
| 19 function calculate(expression) { |
| 20 // In calc expressions, white space is required on both sides of the |
| 21 // + and - operators. https://drafts.csswg.org/css-values-3/#calc-notation |
| 22 // Thus any + or - immediately adjacent to . or 0..9 is part of the number, |
| 23 // e.g. -1.23e+45 |
| 24 // This regular expression matches ( ) * / + - and numbers. |
| 25 var tokenRegularExpression = /([\+\-\w\.]+|[\(\)\*\/])/g; |
| 26 var currentToken; |
| 27 function consume() { |
| 28 var matchResult = tokenRegularExpression.exec(expression); |
| 29 if (matchResult) |
| 30 currentToken = matchResult[0]; |
| 31 else |
| 32 currentToken = undefined; |
| 33 } |
| 34 consume(); // Read the initial token. |
| 35 |
| 36 function calcNumber() { |
| 37 // https://drafts.csswg.org/css-values-3/#number-value |
| 38 var result = Number(currentToken); |
| 39 consume(); |
| 40 return result; |
| 41 } |
| 42 |
| 43 function calcValue() { |
| 44 // <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> ) |
| 45 if (currentToken !== '(') |
| 46 return calcNumber(); |
| 47 consume(); |
| 48 var result = calcSum(); |
| 49 if (currentToken !== ')') |
| 50 return NaN; |
| 51 consume(); |
| 52 return result; |
| 53 } |
| 54 |
| 55 function calcProduct() { |
| 56 // <calc-product> = <calc-value> [ '*' <calc-value> | '/' <calc-number-val
ue> ]* |
| 57 var left = calcValue(); |
| 58 while (currentToken === '*' || currentToken === '/') { |
| 59 var operator = currentToken; |
| 60 consume(); |
| 61 var right = calcValue(); |
| 62 if (operator === '*') |
| 63 left *= right; |
| 64 else |
| 65 left /= right; |
| 66 } |
| 67 return left; |
| 68 } |
| 69 |
| 70 function calcSum() { |
| 71 // <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]* |
| 72 var left = calcProduct(); |
| 73 while (currentToken === '+' || currentToken === '-') { |
| 74 var operator = currentToken; |
| 75 consume(); |
| 76 var right = calcProduct(); |
| 77 if (operator === '+') |
| 78 left += right; |
| 79 else |
| 80 left -= right; |
| 81 } |
| 82 return left; |
| 83 } |
| 84 |
| 85 // <calc()> = calc( <calc-sum> ) |
| 86 return calcSum(); |
| 87 } |
| 88 |
| 89 function parseDimension(unitRegExp, string) { |
| 90 string = string.trim().toLowerCase(); |
| 91 |
| 92 if (string == '0' && 'px'.search(unitRegExp) >= 0) |
| 93 return {px: 0}; |
| 94 |
| 95 // If we have parenthesis, we're a calc and need to start with 'calc'. |
| 96 if (!/^[^(]*$|^calc/.test(string)) |
| 97 return; |
| 98 string = string.replace(/calc\(/g, '('); |
| 99 |
| 100 // We tag units by prefixing them with 'U' (note that we are already |
| 101 // lowercase) to prevent problems with types which are substrings of |
| 102 // each other (although prefixes may be problematic!) |
| 103 var matchedUnits = {}; |
| 104 string = string.replace(unitRegExp, function(match) { |
| 105 matchedUnits[match] = null; |
| 106 return 'U' + match; |
| 107 }); |
| 108 var taggedUnitRegExp = 'U(' + unitRegExp.source + ')'; |
| 109 |
| 110 // Validating input is simply applying as many reductions as we can. |
| 111 var typeCheck = string.replace(/[-+]?(\d*\.)?\d+([Ee][-+]?\d+)?/g, 'N') |
| 112 .replace(new RegExp('N' + taggedUnitRegExp, 'g'), 'D') |
| 113 .replace(/\s[+-]\s/g, 'O') |
| 114 .replace(/\s/g, ''); |
| 115 var reductions = [/N\*(D)/g, /(N|D)[*/]N/g, /(N|D)O\1/g, /\((N|D)\)/g]; |
| 116 var i = 0; |
| 117 while (i < reductions.length) { |
| 118 if (reductions[i].test(typeCheck)) { |
| 119 typeCheck = typeCheck.replace(reductions[i], '$1'); |
| 120 i = 0; |
| 121 } else { |
| 122 i++; |
| 123 } |
| 124 } |
| 125 if (typeCheck != 'D') |
| 126 return; |
| 127 |
| 128 for (var unit in matchedUnits) { |
| 129 var result = calculate(string.replace(new RegExp('U' + unit, 'g'), '').rep
lace(new RegExp(taggedUnitRegExp, 'g'), '*0')); |
| 130 if (!isFinite(result)) |
| 131 return; |
| 132 matchedUnits[unit] = result; |
| 133 } |
| 134 return matchedUnits; |
| 135 } |
| 136 |
| 137 function mergeDimensionsNonNegative(left, right) { |
| 138 return mergeDimensions(left, right, true); |
| 139 } |
| 140 |
| 141 function mergeDimensions(left, right, nonNegative) { |
| 142 var units = [], unit; |
| 143 for (unit in left) |
| 144 units.push(unit); |
| 145 for (unit in right) { |
| 146 if (units.indexOf(unit) < 0) |
| 147 units.push(unit); |
| 148 } |
| 149 |
| 150 left = units.map(function(unit) { return left[unit] || 0; }); |
| 151 right = units.map(function(unit) { return right[unit] || 0; }); |
| 152 return [left, right, function(values) { |
| 153 var result = values.map(function(value, i) { |
| 154 if (values.length == 1 && nonNegative) { |
| 155 value = Math.max(value, 0); |
| 156 } |
| 157 // Scientific notation (e.g. 1e2) is not yet widely supported by browser
vendors. |
| 158 return scope.numberToString(value) + units[i]; |
| 159 }).join(' + '); |
| 160 return values.length > 1 ? 'calc(' + result + ')' : result; |
| 161 }]; |
| 162 } |
| 163 |
| 164 var lengthUnits = 'px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc'; |
| 165 var parseLength = parseDimension.bind(null, new RegExp(lengthUnits, 'g')); |
| 166 var parseLengthOrPercent = parseDimension.bind(null, new RegExp(lengthUnits +
'|%', 'g')); |
| 167 var parseAngle = parseDimension.bind(null, /deg|rad|grad|turn/g); |
| 168 |
| 169 scope.parseLength = parseLength; |
| 170 scope.parseLengthOrPercent = parseLengthOrPercent; |
| 171 scope.consumeLengthOrPercent = scope.consumeParenthesised.bind(null, parseLeng
thOrPercent); |
| 172 scope.parseAngle = parseAngle; |
| 173 scope.mergeDimensions = mergeDimensions; |
| 174 |
| 175 var consumeLength = scope.consumeParenthesised.bind(null, parseLength); |
| 176 var consumeSizePair = scope.consumeRepeated.bind(undefined, consumeLength, /^/
); |
| 177 var consumeSizePairList = scope.consumeRepeated.bind(undefined, consumeSizePai
r, /^,/); |
| 178 scope.consumeSizePairList = consumeSizePairList; |
| 179 |
| 180 var parseSizePairList = function(input) { |
| 181 var result = consumeSizePairList(input); |
| 182 if (result && result[1] == '') { |
| 183 return result[0]; |
| 184 } |
| 185 }; |
| 186 |
| 187 var mergeNonNegativeSizePair = scope.mergeNestedRepeated.bind(undefined, merge
DimensionsNonNegative, ' '); |
| 188 var mergeNonNegativeSizePairList = scope.mergeNestedRepeated.bind(undefined, m
ergeNonNegativeSizePair, ','); |
| 189 scope.mergeNonNegativeSizePair = mergeNonNegativeSizePair; |
| 190 |
| 191 scope.addPropertiesHandler(parseSizePairList, mergeNonNegativeSizePairList, [ |
| 192 'background-size' |
| 193 ]); |
| 194 |
| 195 scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensionsNonNegative, [ |
| 196 'border-bottom-width', |
| 197 'border-image-width', |
| 198 'border-left-width', |
| 199 'border-right-width', |
| 200 'border-top-width', |
| 201 'flex-basis', |
| 202 'font-size', |
| 203 'height', |
| 204 'line-height', |
| 205 'max-height', |
| 206 'max-width', |
| 207 'outline-width', |
| 208 'width', |
| 209 ]); |
| 210 |
| 211 scope.addPropertiesHandler(parseLengthOrPercent, mergeDimensions, [ |
| 212 'border-bottom-left-radius', |
| 213 'border-bottom-right-radius', |
| 214 'border-top-left-radius', |
| 215 'border-top-right-radius', |
| 216 'bottom', |
| 217 'left', |
| 218 'letter-spacing', |
| 219 'margin-bottom', |
| 220 'margin-left', |
| 221 'margin-right', |
| 222 'margin-top', |
| 223 'min-height', |
| 224 'min-width', |
| 225 'outline-offset', |
| 226 'padding-bottom', |
| 227 'padding-left', |
| 228 'padding-right', |
| 229 'padding-top', |
| 230 'perspective', |
| 231 'right', |
| 232 'shape-margin', |
| 233 'stroke-dashoffset', |
| 234 'text-indent', |
| 235 'top', |
| 236 'vertical-align', |
| 237 'word-spacing', |
| 238 ]); |
| 239 |
| 240 })(webAnimations1, webAnimationsTesting); |
OLD | NEW |