165 lines
4.1 KiB
JavaScript
Executable File
165 lines
4.1 KiB
JavaScript
Executable File
/**
|
|
* 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
|
|
}
|