123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- /**
- * Module dependencies
- */
- var balanced = require("balanced-match")
- var reduceFunctionCall = require("reduce-function-call")
- var mexp = require("math-expression-evaluator")
- /**
- * Constantes
- */
- var MAX_STACK = 100 // should be enough for a single calc()...
- var NESTED_CALC_RE = /(\+|\-|\*|\\|[^a-z]|)(\s*)(\()/g
- /**
- * Global variables
- */
- var stack
- /**
- * Expose reduceCSSCalc plugin
- *
- * @type {Function}
- */
- module.exports = reduceCSSCalc
- /**
- * Reduce CSS calc() in a string, whenever it's possible
- *
- * @param {String} value css input
- */
- function reduceCSSCalc(value, decimalPrecision) {
- stack = 0
- decimalPrecision = Math.pow(10, decimalPrecision === undefined ? 5 : decimalPrecision)
- // Allow calc() on multiple lines
- value = value.replace(/\n+/g, " ")
- /**
- * Evaluates an expression
- *
- * @param {String} expression
- * @returns {String}
- */
- function evaluateExpression (expression, functionIdentifier, call) {
- if (stack++ > MAX_STACK) {
- stack = 0
- throw new Error("Call stack overflow for " + call)
- }
- if (expression === "") {
- throw new Error(functionIdentifier + "(): '" + call + "' must contain a non-whitespace string")
- }
- expression = evaluateNestedExpression(expression, call)
- var units = getUnitsInExpression(expression)
- // If the expression contains multiple units or CSS variables,
- // then let the expression be (i.e. browser calc())
- if (units.length > 1 || expression.indexOf("var(") > -1) {
- return functionIdentifier + "(" + expression + ")"
- }
- var unit = units[0] || ""
- if (unit === "%") {
- // Convert percentages to numbers, to handle expressions like: 50% * 50% (will become: 25%):
- // console.log(expression)
- expression = expression.replace(/\b[0-9\.]+%/g, function(percent) {
- return parseFloat(percent.slice(0, -1)) * 0.01
- })
- }
- // Remove units in expression:
- var toEvaluate = expression.replace(new RegExp(unit, "gi"), "")
- var result
- try {
- result = mexp.eval(toEvaluate)
- }
- catch (e) {
- return functionIdentifier + "(" + expression + ")"
- }
- // Transform back to a percentage result:
- if (unit === "%") {
- result *= 100
- }
- // adjust rounding shit
- // (0.1 * 0.2 === 0.020000000000000004)
- if (functionIdentifier.length || unit === "%") {
- result = Math.round(result * decimalPrecision) / decimalPrecision
- }
- // Add unit
- result += unit
- return result
- }
- /**
- * Evaluates nested expressions
- *
- * @param {String} expression
- * @returns {String}
- */
- function evaluateNestedExpression(expression, call) {
- // Remove the calc part from nested expressions to ensure
- // better browser compatibility
- expression = expression.replace(/((?:\-[a-z]+\-)?calc)/g, "")
- var evaluatedPart = ""
- var nonEvaluatedPart = expression
- var matches
- while ((matches = NESTED_CALC_RE.exec(nonEvaluatedPart))) {
- if (matches[0].index > 0) {
- evaluatedPart += nonEvaluatedPart.substring(0, matches[0].index)
- }
- var balancedExpr = balanced("(", ")", nonEvaluatedPart.substring([0].index))
- if (balancedExpr.body === "") {
- throw new Error("'" + expression + "' must contain a non-whitespace string")
- }
- var evaluated = evaluateExpression(balancedExpr.body, "", call)
- evaluatedPart += balancedExpr.pre + evaluated
- nonEvaluatedPart = balancedExpr.post
- }
- return evaluatedPart + nonEvaluatedPart
- }
- return reduceFunctionCall(value, /((?:\-[a-z]+\-)?calc)\(/, evaluateExpression)
- }
- /**
- * Checks what units are used in an expression
- *
- * @param {String} expression
- * @returns {Array}
- */
- function getUnitsInExpression(expression) {
- var uniqueUnits = []
- var uniqueLowerCaseUnits = []
- var unitRegEx = /[\.0-9]([%a-z]+)/gi
- var matches = unitRegEx.exec(expression)
- while (matches) {
- if (!matches || !matches[1]) {
- continue
- }
- if (uniqueLowerCaseUnits.indexOf(matches[1].toLowerCase()) === -1) {
- uniqueUnits.push(matches[1])
- uniqueLowerCaseUnits.push(matches[1].toLowerCase())
- }
- matches = unitRegEx.exec(expression)
- }
- return uniqueUnits
- }
|