mirror of
https://github.com/less/less.js.git
synced 2026-01-22 13:48:03 -05:00
move functions into its own folder and give it a set interface
This commit is contained in:
@@ -54,11 +54,7 @@ less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
|
||||
|
||||
//Setup user functions
|
||||
if (options.functions) {
|
||||
for(var func in options.functions) {
|
||||
if (options.functions.hasOwnProperty(func)) {
|
||||
less.tree.functions[func] = options.functions[func];
|
||||
}
|
||||
}
|
||||
less.functions.functionRegistry.addMultiple(options.functions);
|
||||
}
|
||||
|
||||
var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
|
||||
@@ -352,7 +348,7 @@ function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
|
||||
}
|
||||
|
||||
less.environment.loadFile(env, sheet.href, null, function loadInitialFileCallback(e, data, path, webInfo) {
|
||||
|
||||
|
||||
var newFileInfo = {
|
||||
currentDirectory: less.environment.getPath(env, path),
|
||||
filename: path,
|
||||
|
||||
@@ -1,736 +0,0 @@
|
||||
module.exports = function (less, tree) {
|
||||
|
||||
var functions = {
|
||||
rgb: function (r, g, b) {
|
||||
return this.rgba(r, g, b, 1.0);
|
||||
},
|
||||
rgba: function (r, g, b, a) {
|
||||
var rgb = [r, g, b].map(function (c) { return scaled(c, 255); });
|
||||
a = number(a);
|
||||
return new(tree.Color)(rgb, a);
|
||||
},
|
||||
hsl: function (h, s, l) {
|
||||
return this.hsla(h, s, l, 1.0);
|
||||
},
|
||||
hsla: function (h, s, l, a) {
|
||||
function hue(h) {
|
||||
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
|
||||
if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
|
||||
else if (h * 2 < 1) { return m2; }
|
||||
else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
|
||||
else { return m1; }
|
||||
}
|
||||
|
||||
h = (number(h) % 360) / 360;
|
||||
s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
|
||||
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||
var m1 = l * 2 - m2;
|
||||
|
||||
return this.rgba(hue(h + 1/3) * 255,
|
||||
hue(h) * 255,
|
||||
hue(h - 1/3) * 255,
|
||||
a);
|
||||
},
|
||||
|
||||
hsv: function(h, s, v) {
|
||||
return this.hsva(h, s, v, 1.0);
|
||||
},
|
||||
|
||||
hsva: function(h, s, v, a) {
|
||||
h = ((number(h) % 360) / 360) * 360;
|
||||
s = number(s); v = number(v); a = number(a);
|
||||
|
||||
var i, f;
|
||||
i = Math.floor((h / 60) % 6);
|
||||
f = (h / 60) - i;
|
||||
|
||||
var vs = [v,
|
||||
v * (1 - s),
|
||||
v * (1 - f * s),
|
||||
v * (1 - (1 - f) * s)];
|
||||
var perm = [[0, 3, 1],
|
||||
[2, 0, 1],
|
||||
[1, 0, 3],
|
||||
[1, 2, 0],
|
||||
[3, 1, 0],
|
||||
[0, 1, 2]];
|
||||
|
||||
return this.rgba(vs[perm[i][0]] * 255,
|
||||
vs[perm[i][1]] * 255,
|
||||
vs[perm[i][2]] * 255,
|
||||
a);
|
||||
},
|
||||
|
||||
hue: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().h);
|
||||
},
|
||||
saturation: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().s * 100, '%');
|
||||
},
|
||||
lightness: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().l * 100, '%');
|
||||
},
|
||||
hsvhue: function(color) {
|
||||
return new(tree.Dimension)(color.toHSV().h);
|
||||
},
|
||||
hsvsaturation: function (color) {
|
||||
return new(tree.Dimension)(color.toHSV().s * 100, '%');
|
||||
},
|
||||
hsvvalue: function (color) {
|
||||
return new(tree.Dimension)(color.toHSV().v * 100, '%');
|
||||
},
|
||||
red: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[0]);
|
||||
},
|
||||
green: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[1]);
|
||||
},
|
||||
blue: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[2]);
|
||||
},
|
||||
alpha: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().a);
|
||||
},
|
||||
luma: function (color) {
|
||||
return new(tree.Dimension)(color.luma() * color.alpha * 100, '%');
|
||||
},
|
||||
luminance: function (color) {
|
||||
var luminance =
|
||||
(0.2126 * color.rgb[0] / 255)
|
||||
+ (0.7152 * color.rgb[1] / 255)
|
||||
+ (0.0722 * color.rgb[2] / 255);
|
||||
|
||||
return new(tree.Dimension)(luminance * color.alpha * 100, '%');
|
||||
},
|
||||
saturate: function (color, amount) {
|
||||
// filter: saturate(3.2);
|
||||
// should be kept as is, so check for color
|
||||
if (!color.rgb) {
|
||||
return null;
|
||||
}
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s += amount.value / 100;
|
||||
hsl.s = clamp(hsl.s);
|
||||
return hsla(hsl);
|
||||
},
|
||||
desaturate: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s -= amount.value / 100;
|
||||
hsl.s = clamp(hsl.s);
|
||||
return hsla(hsl);
|
||||
},
|
||||
lighten: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l += amount.value / 100;
|
||||
hsl.l = clamp(hsl.l);
|
||||
return hsla(hsl);
|
||||
},
|
||||
darken: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l -= amount.value / 100;
|
||||
hsl.l = clamp(hsl.l);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadein: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a += amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadeout: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a -= amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fade: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a = amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
spin: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
var hue = (hsl.h + amount.value) % 360;
|
||||
|
||||
hsl.h = hue < 0 ? 360 + hue : hue;
|
||||
|
||||
return hsla(hsl);
|
||||
},
|
||||
//
|
||||
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
|
||||
// http://sass-lang.com
|
||||
//
|
||||
mix: function (color1, color2, weight) {
|
||||
if (!weight) {
|
||||
weight = new(tree.Dimension)(50);
|
||||
}
|
||||
var p = weight.value / 100.0;
|
||||
var w = p * 2 - 1;
|
||||
var a = color1.toHSL().a - color2.toHSL().a;
|
||||
|
||||
var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
|
||||
var w2 = 1 - w1;
|
||||
|
||||
var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
|
||||
color1.rgb[1] * w1 + color2.rgb[1] * w2,
|
||||
color1.rgb[2] * w1 + color2.rgb[2] * w2];
|
||||
|
||||
var alpha = color1.alpha * p + color2.alpha * (1 - p);
|
||||
|
||||
return new(tree.Color)(rgb, alpha);
|
||||
},
|
||||
greyscale: function (color) {
|
||||
return this.desaturate(color, new(tree.Dimension)(100));
|
||||
},
|
||||
contrast: function (color, dark, light, threshold) {
|
||||
// filter: contrast(3.2);
|
||||
// should be kept as is, so check for color
|
||||
if (!color.rgb) {
|
||||
return null;
|
||||
}
|
||||
if (typeof light === 'undefined') {
|
||||
light = this.rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
if (typeof dark === 'undefined') {
|
||||
dark = this.rgba(0, 0, 0, 1.0);
|
||||
}
|
||||
//Figure out which is actually light and dark!
|
||||
if (dark.luma() > light.luma()) {
|
||||
var t = light;
|
||||
light = dark;
|
||||
dark = t;
|
||||
}
|
||||
if (typeof threshold === 'undefined') {
|
||||
threshold = 0.43;
|
||||
} else {
|
||||
threshold = number(threshold);
|
||||
}
|
||||
if (color.luma() < threshold) {
|
||||
return light;
|
||||
} else {
|
||||
return dark;
|
||||
}
|
||||
},
|
||||
e: function (str) {
|
||||
return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value);
|
||||
},
|
||||
escape: function (str) {
|
||||
return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
|
||||
},
|
||||
replace: function (string, pattern, replacement, flags) {
|
||||
var result = string.value;
|
||||
|
||||
result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
},
|
||||
'%': function (string /* arg, arg, ...*/) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
result = string.value;
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
/*jshint loopfunc:true */
|
||||
result = result.replace(/%[sda]/i, function(token) {
|
||||
var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
|
||||
return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
|
||||
});
|
||||
}
|
||||
result = result.replace(/%%/g, '%');
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
},
|
||||
unit: function (val, unit) {
|
||||
if(!(val instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
|
||||
}
|
||||
if (unit) {
|
||||
if (unit instanceof tree.Keyword) {
|
||||
unit = unit.value;
|
||||
} else {
|
||||
unit = unit.toCSS();
|
||||
}
|
||||
} else {
|
||||
unit = "";
|
||||
}
|
||||
return new(tree.Dimension)(val.value, unit);
|
||||
},
|
||||
convert: function (val, unit) {
|
||||
return val.convertTo(unit.value);
|
||||
},
|
||||
round: function (n, f) {
|
||||
var fraction = typeof(f) === "undefined" ? 0 : f.value;
|
||||
return _math(function(num) { return num.toFixed(fraction); }, null, n);
|
||||
},
|
||||
pi: function () {
|
||||
return new(tree.Dimension)(Math.PI);
|
||||
},
|
||||
mod: function(a, b) {
|
||||
return new(tree.Dimension)(a.value % b.value, a.unit);
|
||||
},
|
||||
pow: function(x, y) {
|
||||
if (typeof x === "number" && typeof y === "number") {
|
||||
x = new(tree.Dimension)(x);
|
||||
y = new(tree.Dimension)(y);
|
||||
} else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "arguments must be numbers" };
|
||||
}
|
||||
|
||||
return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
|
||||
},
|
||||
_minmax: function (isMin, args) {
|
||||
args = Array.prototype.slice.call(args);
|
||||
switch(args.length) {
|
||||
case 0: throw { type: "Argument", message: "one or more arguments required" };
|
||||
}
|
||||
var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
|
||||
order = [], // elems only contains original argument values.
|
||||
values = {}; // key is the unit.toString() for unified tree.Dimension values,
|
||||
// value is the index into the order array.
|
||||
for (i = 0; i < args.length; i++) {
|
||||
current = args[i];
|
||||
if (!(current instanceof tree.Dimension)) {
|
||||
if(Array.isArray(args[i].value)) {
|
||||
Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify();
|
||||
unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString();
|
||||
unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic;
|
||||
unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
|
||||
j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
|
||||
if (j === undefined) {
|
||||
if(unitStatic !== undefined && unit !== unitStatic) {
|
||||
throw{ type: "Argument", message: "incompatible types" };
|
||||
}
|
||||
values[unit] = order.length;
|
||||
order.push(current);
|
||||
continue;
|
||||
}
|
||||
referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify();
|
||||
if ( isMin && currentUnified.value < referenceUnified.value ||
|
||||
!isMin && currentUnified.value > referenceUnified.value) {
|
||||
order[j] = current;
|
||||
}
|
||||
}
|
||||
if (order.length == 1) {
|
||||
return order[0];
|
||||
}
|
||||
args = order.map(function (a) { return a.toCSS(this.env); }).join(this.env.compress ? "," : ", ");
|
||||
return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
|
||||
},
|
||||
min: function () {
|
||||
return this._minmax(true, arguments);
|
||||
},
|
||||
max: function () {
|
||||
return this._minmax(false, arguments);
|
||||
},
|
||||
"get-unit": function (n) {
|
||||
return new(tree.Anonymous)(n.unit);
|
||||
},
|
||||
argb: function (color) {
|
||||
return new(tree.Anonymous)(color.toARGB());
|
||||
},
|
||||
percentage: function (n) {
|
||||
return new(tree.Dimension)(n.value * 100, '%');
|
||||
},
|
||||
color: function(c) {
|
||||
if ((c instanceof tree.Quoted) &&
|
||||
(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i.test(c.value))) {
|
||||
return new(tree.Color)(c.value.slice(1));
|
||||
}
|
||||
if ((c instanceof tree.Color) || (c = tree.Color.fromKeyword(c.value))) {
|
||||
c.keyword = undefined;
|
||||
return c;
|
||||
}
|
||||
throw {
|
||||
type: "Argument",
|
||||
message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF"
|
||||
};
|
||||
},
|
||||
iscolor: function (n) {
|
||||
return this._isa(n, tree.Color);
|
||||
},
|
||||
isnumber: function (n) {
|
||||
return this._isa(n, tree.Dimension);
|
||||
},
|
||||
isstring: function (n) {
|
||||
return this._isa(n, tree.Quoted);
|
||||
},
|
||||
iskeyword: function (n) {
|
||||
return this._isa(n, tree.Keyword);
|
||||
},
|
||||
isurl: function (n) {
|
||||
return this._isa(n, tree.URL);
|
||||
},
|
||||
ispixel: function (n) {
|
||||
return this.isunit(n, 'px');
|
||||
},
|
||||
ispercentage: function (n) {
|
||||
return this.isunit(n, '%');
|
||||
},
|
||||
isem: function (n) {
|
||||
return this.isunit(n, 'em');
|
||||
},
|
||||
isunit: function (n, unit) {
|
||||
return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
|
||||
},
|
||||
_isa: function (n, Type) {
|
||||
return (n instanceof Type) ? tree.True : tree.False;
|
||||
},
|
||||
tint: function(color, amount) {
|
||||
return this.mix(this.rgb(255,255,255), color, amount);
|
||||
},
|
||||
shade: function(color, amount) {
|
||||
return this.mix(this.rgb(0, 0, 0), color, amount);
|
||||
},
|
||||
extract: function(values, index) {
|
||||
index = index.value - 1; // (1-based index)
|
||||
// handle non-array values as an array of length 1
|
||||
// return 'undefined' if index is invalid
|
||||
return Array.isArray(values.value)
|
||||
? values.value[index] : Array(values)[index];
|
||||
},
|
||||
length: function(values) {
|
||||
var n = Array.isArray(values.value) ? values.value.length : 1;
|
||||
return new tree.Dimension(n);
|
||||
},
|
||||
|
||||
"data-uri": function(mimetypeNode, filePathNode) {
|
||||
|
||||
if (!less.environment.supportsDataURI(this.env)) {
|
||||
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
|
||||
}
|
||||
|
||||
var mimetype = mimetypeNode.value;
|
||||
var filePath = (filePathNode && filePathNode.value);
|
||||
|
||||
var useBase64 = false;
|
||||
|
||||
if (arguments.length < 2) {
|
||||
filePath = mimetype;
|
||||
}
|
||||
|
||||
var fragmentStart = filePath.indexOf('#');
|
||||
var fragment = '';
|
||||
if (fragmentStart!==-1) {
|
||||
fragment = filePath.slice(fragmentStart);
|
||||
filePath = filePath.slice(0, fragmentStart);
|
||||
}
|
||||
|
||||
if (this.env.isPathRelative(filePath)) {
|
||||
if (this.currentFileInfo.relativeUrls) {
|
||||
filePath = less.environment.join(this.currentFileInfo.currentDirectory, filePath);
|
||||
} else {
|
||||
filePath = less.environment.join(this.currentFileInfo.entryPath, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// detect the mimetype if not given
|
||||
if (arguments.length < 2) {
|
||||
|
||||
mimetype = less.environment.mimeLookup(this.env, filePath);
|
||||
|
||||
// use base 64 unless it's an ASCII or UTF-8 format
|
||||
var charset = less.environment.charsetLookup(this.env, mimetype);
|
||||
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
|
||||
if (useBase64) { mimetype += ';base64'; }
|
||||
}
|
||||
else {
|
||||
useBase64 = /;base64$/.test(mimetype);
|
||||
}
|
||||
|
||||
var buf = less.environment.readFileSync(filePath);
|
||||
|
||||
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
|
||||
// and the --ieCompat flag is enabled, return a normal url() instead.
|
||||
var DATA_URI_MAX_KB = 32,
|
||||
fileSizeInKB = parseInt((buf.length / 1024), 10);
|
||||
if (fileSizeInKB >= DATA_URI_MAX_KB) {
|
||||
|
||||
if (this.env.ieCompat !== false) {
|
||||
if (!this.env.silent) {
|
||||
console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
|
||||
}
|
||||
|
||||
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
|
||||
}
|
||||
}
|
||||
|
||||
buf = useBase64 ? buf.toString('base64')
|
||||
: encodeURIComponent(buf);
|
||||
|
||||
var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
|
||||
return new(tree.URL)(new(tree.Anonymous)(uri));
|
||||
},
|
||||
|
||||
"svg-gradient": function(direction) {
|
||||
|
||||
function throwArgumentDescriptor() {
|
||||
throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
|
||||
}
|
||||
|
||||
if (arguments.length < 3) {
|
||||
throwArgumentDescriptor();
|
||||
}
|
||||
var stops = Array.prototype.slice.call(arguments, 1),
|
||||
gradientDirectionSvg,
|
||||
gradientType = "linear",
|
||||
rectangleDimension = 'x="0" y="0" width="1" height="1"',
|
||||
useBase64 = true,
|
||||
renderEnv = {compress: false},
|
||||
returner,
|
||||
directionValue = direction.toCSS(renderEnv),
|
||||
i, color, position, positionValue, alpha;
|
||||
|
||||
switch (directionValue) {
|
||||
case "to bottom":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
|
||||
break;
|
||||
case "to right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
|
||||
break;
|
||||
case "to bottom right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
|
||||
break;
|
||||
case "to top right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
|
||||
break;
|
||||
case "ellipse":
|
||||
case "ellipse at center":
|
||||
gradientType = "radial";
|
||||
gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
|
||||
rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
|
||||
break;
|
||||
default:
|
||||
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
|
||||
}
|
||||
returner = '<?xml version="1.0" ?>' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
|
||||
'<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
|
||||
|
||||
for (i = 0; i < stops.length; i+= 1) {
|
||||
if (stops[i].value) {
|
||||
color = stops[i].value[0];
|
||||
position = stops[i].value[1];
|
||||
} else {
|
||||
color = stops[i];
|
||||
position = undefined;
|
||||
}
|
||||
|
||||
if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
|
||||
throwArgumentDescriptor();
|
||||
}
|
||||
positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
|
||||
alpha = color.alpha;
|
||||
returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
|
||||
}
|
||||
returner += '</' + gradientType + 'Gradient>' +
|
||||
'<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
|
||||
|
||||
if (useBase64) {
|
||||
try {
|
||||
returner = less.environment.encodeBase64(this.env, returner);
|
||||
} catch(e) {
|
||||
useBase64 = false;
|
||||
}
|
||||
}
|
||||
|
||||
returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
|
||||
return new(tree.URL)(new(tree.Anonymous)(returner));
|
||||
}
|
||||
};
|
||||
|
||||
// Math
|
||||
|
||||
var mathFunctions = {
|
||||
// name, unit
|
||||
ceil: null,
|
||||
floor: null,
|
||||
sqrt: null,
|
||||
abs: null,
|
||||
tan: "",
|
||||
sin: "",
|
||||
cos: "",
|
||||
atan: "rad",
|
||||
asin: "rad",
|
||||
acos: "rad"
|
||||
};
|
||||
|
||||
function _math(fn, unit, n) {
|
||||
if (!(n instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "argument must be a number" };
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = n.unit;
|
||||
} else {
|
||||
n = n.unify();
|
||||
}
|
||||
return new(tree.Dimension)(fn(parseFloat(n.value)), unit);
|
||||
}
|
||||
|
||||
// ~ End of Math
|
||||
|
||||
// Color Blending
|
||||
// ref: http://www.w3.org/TR/compositing-1
|
||||
|
||||
function colorBlend(mode, color1, color2) {
|
||||
var ab = color1.alpha, cb, // backdrop
|
||||
as = color2.alpha, cs, // source
|
||||
ar, cr, r = []; // result
|
||||
|
||||
ar = as + ab * (1 - as);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
cb = color1.rgb[i] / 255;
|
||||
cs = color2.rgb[i] / 255;
|
||||
cr = mode(cb, cs);
|
||||
if (ar) {
|
||||
cr = (as * cs + ab * (cb
|
||||
- as * (cb + cs - cr))) / ar;
|
||||
}
|
||||
r[i] = cr * 255;
|
||||
}
|
||||
|
||||
return new(tree.Color)(r, ar);
|
||||
}
|
||||
|
||||
var colorBlendMode = {
|
||||
multiply: function(cb, cs) {
|
||||
return cb * cs;
|
||||
},
|
||||
screen: function(cb, cs) {
|
||||
return cb + cs - cb * cs;
|
||||
},
|
||||
overlay: function(cb, cs) {
|
||||
cb *= 2;
|
||||
return (cb <= 1)
|
||||
? colorBlendMode.multiply(cb, cs)
|
||||
: colorBlendMode.screen(cb - 1, cs);
|
||||
},
|
||||
softlight: function(cb, cs) {
|
||||
var d = 1, e = cb;
|
||||
if (cs > 0.5) {
|
||||
e = 1;
|
||||
d = (cb > 0.25) ? Math.sqrt(cb)
|
||||
: ((16 * cb - 12) * cb + 4) * cb;
|
||||
}
|
||||
return cb - (1 - 2 * cs) * e * (d - cb);
|
||||
},
|
||||
hardlight: function(cb, cs) {
|
||||
return colorBlendMode.overlay(cs, cb);
|
||||
},
|
||||
difference: function(cb, cs) {
|
||||
return Math.abs(cb - cs);
|
||||
},
|
||||
exclusion: function(cb, cs) {
|
||||
return cb + cs - 2 * cb * cs;
|
||||
},
|
||||
|
||||
// non-w3c functions:
|
||||
average: function(cb, cs) {
|
||||
return (cb + cs) / 2;
|
||||
},
|
||||
negation: function(cb, cs) {
|
||||
return 1 - Math.abs(cb + cs - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// ~ End of Color Blending
|
||||
|
||||
tree.defaultFunc = {
|
||||
eval: function () {
|
||||
var v = this.value_, e = this.error_;
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
if (v != null) {
|
||||
return v ? tree.True : tree.False;
|
||||
}
|
||||
},
|
||||
value: function (v) {
|
||||
this.value_ = v;
|
||||
},
|
||||
error: function (e) {
|
||||
this.error_ = e;
|
||||
},
|
||||
reset: function () {
|
||||
this.value_ = this.error_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
function initFunctions() {
|
||||
var f;
|
||||
|
||||
// math
|
||||
for (f in mathFunctions) {
|
||||
if (mathFunctions.hasOwnProperty(f)) {
|
||||
functions[f] = _math.bind(null, Math[f], mathFunctions[f]);
|
||||
}
|
||||
}
|
||||
|
||||
// color blending
|
||||
for (f in colorBlendMode) {
|
||||
if (colorBlendMode.hasOwnProperty(f)) {
|
||||
functions[f] = colorBlend.bind(null, colorBlendMode[f]);
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
f = tree.defaultFunc;
|
||||
functions["default"] = f.eval.bind(f);
|
||||
|
||||
} initFunctions();
|
||||
|
||||
function hsla(color) {
|
||||
return functions.hsla(color.h, color.s, color.l, color.a);
|
||||
}
|
||||
|
||||
function scaled(n, size) {
|
||||
if (n instanceof tree.Dimension && n.unit.is('%')) {
|
||||
return parseFloat(n.value * size / 100);
|
||||
} else {
|
||||
return number(n);
|
||||
}
|
||||
}
|
||||
|
||||
function number(n) {
|
||||
if (n instanceof tree.Dimension) {
|
||||
return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
|
||||
} else if (typeof(n) === 'number') {
|
||||
return n;
|
||||
} else {
|
||||
throw {
|
||||
error: "RuntimeError",
|
||||
message: "color functions take numbers as parameters"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(val) {
|
||||
return Math.min(1, Math.max(0, val));
|
||||
}
|
||||
|
||||
tree.fround = function(env, value) {
|
||||
var p = env && env.numPrecision;
|
||||
//add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
|
||||
return (p == null) ? value : Number((value + 2e-16).toFixed(p));
|
||||
};
|
||||
|
||||
tree.functionCall = function(env, currentFileInfo, environment) {
|
||||
this.env = env;
|
||||
this.environment = environment;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
};
|
||||
|
||||
tree.functionCall.prototype = functions;
|
||||
|
||||
return functions;
|
||||
|
||||
};
|
||||
74
lib/less/functions/color-blending.js
Normal file
74
lib/less/functions/color-blending.js
Normal file
@@ -0,0 +1,74 @@
|
||||
module.exports = function(functions, tree) {
|
||||
|
||||
// Color Blending
|
||||
// ref: http://www.w3.org/TR/compositing-1
|
||||
|
||||
function colorBlend(mode, color1, color2) {
|
||||
var ab = color1.alpha, cb, // backdrop
|
||||
as = color2.alpha, cs, // source
|
||||
ar, cr, r = []; // result
|
||||
|
||||
ar = as + ab * (1 - as);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
cb = color1.rgb[i] / 255;
|
||||
cs = color2.rgb[i] / 255;
|
||||
cr = mode(cb, cs);
|
||||
if (ar) {
|
||||
cr = (as * cs + ab * (cb
|
||||
- as * (cb + cs - cr))) / ar;
|
||||
}
|
||||
r[i] = cr * 255;
|
||||
}
|
||||
|
||||
return new(tree.Color)(r, ar);
|
||||
}
|
||||
|
||||
var colorBlendModeFunctions = {
|
||||
multiply: function(cb, cs) {
|
||||
return cb * cs;
|
||||
},
|
||||
screen: function(cb, cs) {
|
||||
return cb + cs - cb * cs;
|
||||
},
|
||||
overlay: function(cb, cs) {
|
||||
cb *= 2;
|
||||
return (cb <= 1)
|
||||
? colorBlendModeFunctions.multiply(cb, cs)
|
||||
: colorBlendModeFunctions.screen(cb - 1, cs);
|
||||
},
|
||||
softlight: function(cb, cs) {
|
||||
var d = 1, e = cb;
|
||||
if (cs > 0.5) {
|
||||
e = 1;
|
||||
d = (cb > 0.25) ? Math.sqrt(cb)
|
||||
: ((16 * cb - 12) * cb + 4) * cb;
|
||||
}
|
||||
return cb - (1 - 2 * cs) * e * (d - cb);
|
||||
},
|
||||
hardlight: function(cb, cs) {
|
||||
return colorBlendModeFunctions.overlay(cs, cb);
|
||||
},
|
||||
difference: function(cb, cs) {
|
||||
return Math.abs(cb - cs);
|
||||
},
|
||||
exclusion: function(cb, cs) {
|
||||
return cb + cs - 2 * cb * cs;
|
||||
},
|
||||
|
||||
// non-w3c functions:
|
||||
average: function(cb, cs) {
|
||||
return (cb + cs) / 2;
|
||||
},
|
||||
negation: function(cb, cs) {
|
||||
return 1 - Math.abs(cb + cs - 1);
|
||||
}
|
||||
};
|
||||
|
||||
for (var f in colorBlendModeFunctions) {
|
||||
if (colorBlendModeFunctions.hasOwnProperty(f)) {
|
||||
colorBlend[f] = colorBlend.bind(null, colorBlendModeFunctions[f]);
|
||||
}
|
||||
}
|
||||
|
||||
functions.functionRegistry.addMultiple(colorBlend);
|
||||
};
|
||||
272
lib/less/functions/color.js
Normal file
272
lib/less/functions/color.js
Normal file
@@ -0,0 +1,272 @@
|
||||
module.exports = function(functions, tree) {
|
||||
function clamp(val) {
|
||||
return Math.min(1, Math.max(0, val));
|
||||
}
|
||||
function hsla(color) {
|
||||
return colorFunctions.hsla(color.h, color.s, color.l, color.a);
|
||||
}
|
||||
function number(n) {
|
||||
if (n instanceof tree.Dimension) {
|
||||
return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
|
||||
} else if (typeof(n) === 'number') {
|
||||
return n;
|
||||
} else {
|
||||
throw {
|
||||
error: "RuntimeError",
|
||||
message: "color functions take numbers as parameters"
|
||||
};
|
||||
}
|
||||
}
|
||||
function scaled(n, size) {
|
||||
if (n instanceof tree.Dimension && n.unit.is('%')) {
|
||||
return parseFloat(n.value * size / 100);
|
||||
} else {
|
||||
return number(n);
|
||||
}
|
||||
}
|
||||
var colorFunctions = {
|
||||
rgb: function (r, g, b) {
|
||||
return colorFunctions.rgba(r, g, b, 1.0);
|
||||
},
|
||||
rgba: function (r, g, b, a) {
|
||||
var rgb = [r, g, b].map(function (c) { return scaled(c, 255); });
|
||||
a = number(a);
|
||||
return new(tree.Color)(rgb, a);
|
||||
},
|
||||
hsl: function (h, s, l) {
|
||||
return colorFunctions.hsla(h, s, l, 1.0);
|
||||
},
|
||||
hsla: function (h, s, l, a) {
|
||||
function hue(h) {
|
||||
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
|
||||
if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
|
||||
else if (h * 2 < 1) { return m2; }
|
||||
else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
|
||||
else { return m1; }
|
||||
}
|
||||
|
||||
h = (number(h) % 360) / 360;
|
||||
s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
|
||||
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||
var m1 = l * 2 - m2;
|
||||
|
||||
return colorFunctions.rgba(hue(h + 1/3) * 255,
|
||||
hue(h) * 255,
|
||||
hue(h - 1/3) * 255,
|
||||
a);
|
||||
},
|
||||
|
||||
hsv: function(h, s, v) {
|
||||
return colorFunctions.hsva(h, s, v, 1.0);
|
||||
},
|
||||
|
||||
hsva: function(h, s, v, a) {
|
||||
h = ((number(h) % 360) / 360) * 360;
|
||||
s = number(s); v = number(v); a = number(a);
|
||||
|
||||
var i, f;
|
||||
i = Math.floor((h / 60) % 6);
|
||||
f = (h / 60) - i;
|
||||
|
||||
var vs = [v,
|
||||
v * (1 - s),
|
||||
v * (1 - f * s),
|
||||
v * (1 - (1 - f) * s)];
|
||||
var perm = [[0, 3, 1],
|
||||
[2, 0, 1],
|
||||
[1, 0, 3],
|
||||
[1, 2, 0],
|
||||
[3, 1, 0],
|
||||
[0, 1, 2]];
|
||||
|
||||
return colorFunctions.rgba(vs[perm[i][0]] * 255,
|
||||
vs[perm[i][1]] * 255,
|
||||
vs[perm[i][2]] * 255,
|
||||
a);
|
||||
},
|
||||
|
||||
hue: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().h);
|
||||
},
|
||||
saturation: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().s * 100, '%');
|
||||
},
|
||||
lightness: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().l * 100, '%');
|
||||
},
|
||||
hsvhue: function(color) {
|
||||
return new(tree.Dimension)(color.toHSV().h);
|
||||
},
|
||||
hsvsaturation: function (color) {
|
||||
return new(tree.Dimension)(color.toHSV().s * 100, '%');
|
||||
},
|
||||
hsvvalue: function (color) {
|
||||
return new(tree.Dimension)(color.toHSV().v * 100, '%');
|
||||
},
|
||||
red: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[0]);
|
||||
},
|
||||
green: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[1]);
|
||||
},
|
||||
blue: function (color) {
|
||||
return new(tree.Dimension)(color.rgb[2]);
|
||||
},
|
||||
alpha: function (color) {
|
||||
return new(tree.Dimension)(color.toHSL().a);
|
||||
},
|
||||
luma: function (color) {
|
||||
return new(tree.Dimension)(color.luma() * color.alpha * 100, '%');
|
||||
},
|
||||
luminance: function (color) {
|
||||
var luminance =
|
||||
(0.2126 * color.rgb[0] / 255)
|
||||
+ (0.7152 * color.rgb[1] / 255)
|
||||
+ (0.0722 * color.rgb[2] / 255);
|
||||
|
||||
return new(tree.Dimension)(luminance * color.alpha * 100, '%');
|
||||
},
|
||||
saturate: function (color, amount) {
|
||||
// filter: saturate(3.2);
|
||||
// should be kept as is, so check for color
|
||||
if (!color.rgb) {
|
||||
return null;
|
||||
}
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s += amount.value / 100;
|
||||
hsl.s = clamp(hsl.s);
|
||||
return hsla(hsl);
|
||||
},
|
||||
desaturate: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s -= amount.value / 100;
|
||||
hsl.s = clamp(hsl.s);
|
||||
return hsla(hsl);
|
||||
},
|
||||
lighten: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l += amount.value / 100;
|
||||
hsl.l = clamp(hsl.l);
|
||||
return hsla(hsl);
|
||||
},
|
||||
darken: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l -= amount.value / 100;
|
||||
hsl.l = clamp(hsl.l);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadein: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a += amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadeout: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a -= amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
fade: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a = amount.value / 100;
|
||||
hsl.a = clamp(hsl.a);
|
||||
return hsla(hsl);
|
||||
},
|
||||
spin: function (color, amount) {
|
||||
var hsl = color.toHSL();
|
||||
var hue = (hsl.h + amount.value) % 360;
|
||||
|
||||
hsl.h = hue < 0 ? 360 + hue : hue;
|
||||
|
||||
return hsla(hsl);
|
||||
},
|
||||
//
|
||||
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
|
||||
// http://sass-lang.com
|
||||
//
|
||||
mix: function (color1, color2, weight) {
|
||||
if (!weight) {
|
||||
weight = new(tree.Dimension)(50);
|
||||
}
|
||||
var p = weight.value / 100.0;
|
||||
var w = p * 2 - 1;
|
||||
var a = color1.toHSL().a - color2.toHSL().a;
|
||||
|
||||
var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
|
||||
var w2 = 1 - w1;
|
||||
|
||||
var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
|
||||
color1.rgb[1] * w1 + color2.rgb[1] * w2,
|
||||
color1.rgb[2] * w1 + color2.rgb[2] * w2];
|
||||
|
||||
var alpha = color1.alpha * p + color2.alpha * (1 - p);
|
||||
|
||||
return new(tree.Color)(rgb, alpha);
|
||||
},
|
||||
greyscale: function (color) {
|
||||
return colorFunctions.desaturate(color, new(tree.Dimension)(100));
|
||||
},
|
||||
contrast: function (color, dark, light, threshold) {
|
||||
// filter: contrast(3.2);
|
||||
// should be kept as is, so check for color
|
||||
if (!color.rgb) {
|
||||
return null;
|
||||
}
|
||||
if (typeof light === 'undefined') {
|
||||
light = colorFunctions.rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
if (typeof dark === 'undefined') {
|
||||
dark = colorFunctions.rgba(0, 0, 0, 1.0);
|
||||
}
|
||||
//Figure out which is actually light and dark!
|
||||
if (dark.luma() > light.luma()) {
|
||||
var t = light;
|
||||
light = dark;
|
||||
dark = t;
|
||||
}
|
||||
if (typeof threshold === 'undefined') {
|
||||
threshold = 0.43;
|
||||
} else {
|
||||
threshold = number(threshold);
|
||||
}
|
||||
if (color.luma() < threshold) {
|
||||
return light;
|
||||
} else {
|
||||
return dark;
|
||||
}
|
||||
},
|
||||
argb: function (color) {
|
||||
return new(tree.Anonymous)(color.toARGB());
|
||||
},
|
||||
color: function(c) {
|
||||
if ((c instanceof tree.Quoted) &&
|
||||
(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i.test(c.value))) {
|
||||
return new(tree.Color)(c.value.slice(1));
|
||||
}
|
||||
if ((c instanceof tree.Color) || (c = tree.Color.fromKeyword(c.value))) {
|
||||
c.keyword = undefined;
|
||||
return c;
|
||||
}
|
||||
throw {
|
||||
type: "Argument",
|
||||
message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF"
|
||||
};
|
||||
},
|
||||
tint: function(color, amount) {
|
||||
return colorFunctions.mix(colorFunctions.rgb(255,255,255), color, amount);
|
||||
},
|
||||
shade: function(color, amount) {
|
||||
return colorFunctions.mix(colorFunctions.rgb(0, 0, 0), color, amount);
|
||||
}
|
||||
};
|
||||
functions.functionRegistry.addMultiple(colorFunctions);
|
||||
};
|
||||
69
lib/less/functions/data-uri.js
Normal file
69
lib/less/functions/data-uri.js
Normal file
@@ -0,0 +1,69 @@
|
||||
module.exports = function(functions, tree, less) {
|
||||
functions.functionRegistry.add("data-uri", function(mimetypeNode, filePathNode) {
|
||||
|
||||
if (!less.environment.supportsDataURI(this.env)) {
|
||||
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
|
||||
}
|
||||
|
||||
var mimetype = mimetypeNode.value;
|
||||
var filePath = (filePathNode && filePathNode.value);
|
||||
|
||||
var useBase64 = false;
|
||||
|
||||
if (arguments.length < 2) {
|
||||
filePath = mimetype;
|
||||
}
|
||||
|
||||
var fragmentStart = filePath.indexOf('#');
|
||||
var fragment = '';
|
||||
if (fragmentStart!==-1) {
|
||||
fragment = filePath.slice(fragmentStart);
|
||||
filePath = filePath.slice(0, fragmentStart);
|
||||
}
|
||||
|
||||
if (this.env.isPathRelative(filePath)) {
|
||||
if (this.currentFileInfo.relativeUrls) {
|
||||
filePath = less.environment.join(this.currentFileInfo.currentDirectory, filePath);
|
||||
} else {
|
||||
filePath = less.environment.join(this.currentFileInfo.entryPath, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// detect the mimetype if not given
|
||||
if (arguments.length < 2) {
|
||||
|
||||
mimetype = less.environment.mimeLookup(this.env, filePath);
|
||||
|
||||
// use base 64 unless it's an ASCII or UTF-8 format
|
||||
var charset = less.environment.charsetLookup(this.env, mimetype);
|
||||
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
|
||||
if (useBase64) { mimetype += ';base64'; }
|
||||
}
|
||||
else {
|
||||
useBase64 = /;base64$/.test(mimetype);
|
||||
}
|
||||
|
||||
var buf = less.environment.readFileSync(filePath);
|
||||
|
||||
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
|
||||
// and the --ieCompat flag is enabled, return a normal url() instead.
|
||||
var DATA_URI_MAX_KB = 32,
|
||||
fileSizeInKB = parseInt((buf.length / 1024), 10);
|
||||
if (fileSizeInKB >= DATA_URI_MAX_KB) {
|
||||
|
||||
if (this.env.ieCompat !== false) {
|
||||
if (!this.env.silent) {
|
||||
console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
|
||||
}
|
||||
|
||||
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
|
||||
}
|
||||
}
|
||||
|
||||
buf = useBase64 ? buf.toString('base64')
|
||||
: encodeURIComponent(buf);
|
||||
|
||||
var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
|
||||
return new(tree.URL)(new(tree.Anonymous)(uri));
|
||||
});
|
||||
};
|
||||
25
lib/less/functions/default.js
Normal file
25
lib/less/functions/default.js
Normal file
@@ -0,0 +1,25 @@
|
||||
module.exports = function(functions, tree) {
|
||||
var defaultFunc = {
|
||||
eval: function () {
|
||||
var v = this.value_, e = this.error_;
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
if (v != null) {
|
||||
return v ? tree.True : tree.False;
|
||||
}
|
||||
},
|
||||
value: function (v) {
|
||||
this.value_ = v;
|
||||
},
|
||||
error: function (e) {
|
||||
this.error_ = e;
|
||||
},
|
||||
reset: function () {
|
||||
this.value_ = this.error_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
functions.functionRegistry.add("default", defaultFunc.eval.bind(defaultFunc));
|
||||
tree.defaultFunc = defaultFunc;
|
||||
};
|
||||
15
lib/less/functions/function-caller.js
Normal file
15
lib/less/functions/function-caller.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = function(functions) {
|
||||
var functionCaller = function(name, env, currentFileInfo) {
|
||||
this.name = name.toLowerCase();
|
||||
this.function = functions.functionRegistry.get(this.name);
|
||||
this.env = env;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
};
|
||||
functionCaller.prototype.isValid = function() {
|
||||
return Boolean(this.function);
|
||||
};
|
||||
functionCaller.prototype.call = function(args) {
|
||||
return this.function.apply(this, args);
|
||||
};
|
||||
return functionCaller;
|
||||
};
|
||||
18
lib/less/functions/function-registry.js
Normal file
18
lib/less/functions/function-registry.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
_data: {},
|
||||
add: function(name, func) {
|
||||
if (this._data.hasOwnProperty(name)) {
|
||||
//TODO warn
|
||||
}
|
||||
this._data[name] = func;
|
||||
},
|
||||
addMultiple: function(functions) {
|
||||
Object.keys(functions).forEach(
|
||||
function(name) {
|
||||
this.add(name, functions[name]);
|
||||
}.bind(this));
|
||||
},
|
||||
get: function(name) {
|
||||
return this._data[name];
|
||||
}
|
||||
};
|
||||
18
lib/less/functions/index.js
Normal file
18
lib/less/functions/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = function(less, tree) {
|
||||
var functions = {};
|
||||
functions.functionRegistry = require("./function-registry.js");
|
||||
functions.functionCaller = require("./function-caller.js")(functions);
|
||||
|
||||
//register functions
|
||||
require("./color.js")(functions, tree);
|
||||
require("./color-blending.js")(functions, tree);
|
||||
require("./data-uri.js")(functions, tree, less);
|
||||
require("./default.js")(functions, tree);
|
||||
require("./math.js")(functions, tree);
|
||||
require("./number.js")(functions, tree);
|
||||
require("./string.js")(functions, tree);
|
||||
require("./svg.js")(functions, tree, less);
|
||||
require("./types.js")(functions, tree);
|
||||
|
||||
return functions;
|
||||
};
|
||||
42
lib/less/functions/math.js
Normal file
42
lib/less/functions/math.js
Normal file
@@ -0,0 +1,42 @@
|
||||
module.exports = function(functions, tree) {
|
||||
|
||||
var mathFunctions = {
|
||||
// name, unit
|
||||
ceil: null,
|
||||
floor: null,
|
||||
sqrt: null,
|
||||
abs: null,
|
||||
tan: "",
|
||||
sin: "",
|
||||
cos: "",
|
||||
atan: "rad",
|
||||
asin: "rad",
|
||||
acos: "rad"
|
||||
};
|
||||
|
||||
function _math(fn, unit, n) {
|
||||
if (!(n instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "argument must be a number" };
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = n.unit;
|
||||
} else {
|
||||
n = n.unify();
|
||||
}
|
||||
return new(tree.Dimension)(fn(parseFloat(n.value)), unit);
|
||||
}
|
||||
|
||||
for (var f in mathFunctions) {
|
||||
if (mathFunctions.hasOwnProperty(f)) {
|
||||
mathFunctions[f] = _math.bind(null, Math[f], mathFunctions[f]);
|
||||
}
|
||||
}
|
||||
|
||||
mathFunctions.round = function (n, f) {
|
||||
var fraction = typeof(f) === "undefined" ? 0 : f.value;
|
||||
return _math(function(num) { return num.toFixed(fraction); }, null, n);
|
||||
};
|
||||
|
||||
functions.functionRegistry.addMultiple(mathFunctions);
|
||||
|
||||
};
|
||||
74
lib/less/functions/number.js
Normal file
74
lib/less/functions/number.js
Normal file
@@ -0,0 +1,74 @@
|
||||
module.exports = function(functions, tree) {
|
||||
var minMax = function (isMin, args) {
|
||||
args = Array.prototype.slice.call(args);
|
||||
switch(args.length) {
|
||||
case 0: throw { type: "Argument", message: "one or more arguments required" };
|
||||
}
|
||||
var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
|
||||
order = [], // elems only contains original argument values.
|
||||
values = {}; // key is the unit.toString() for unified tree.Dimension values,
|
||||
// value is the index into the order array.
|
||||
for (i = 0; i < args.length; i++) {
|
||||
current = args[i];
|
||||
if (!(current instanceof tree.Dimension)) {
|
||||
if(Array.isArray(args[i].value)) {
|
||||
Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify();
|
||||
unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString();
|
||||
unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic;
|
||||
unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
|
||||
j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
|
||||
if (j === undefined) {
|
||||
if(unitStatic !== undefined && unit !== unitStatic) {
|
||||
throw{ type: "Argument", message: "incompatible types" };
|
||||
}
|
||||
values[unit] = order.length;
|
||||
order.push(current);
|
||||
continue;
|
||||
}
|
||||
referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify();
|
||||
if ( isMin && currentUnified.value < referenceUnified.value ||
|
||||
!isMin && currentUnified.value > referenceUnified.value) {
|
||||
order[j] = current;
|
||||
}
|
||||
}
|
||||
if (order.length == 1) {
|
||||
return order[0];
|
||||
}
|
||||
args = order.map(function (a) { return a.toCSS(this.env); }).join(this.env.compress ? "," : ", ");
|
||||
return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
|
||||
};
|
||||
functions.functionRegistry.addMultiple({
|
||||
min: function () {
|
||||
return minMax(true, arguments);
|
||||
},
|
||||
max: function () {
|
||||
return minMax(false, arguments);
|
||||
},
|
||||
convert: function (val, unit) {
|
||||
return val.convertTo(unit.value);
|
||||
},
|
||||
pi: function () {
|
||||
return new(tree.Dimension)(Math.PI);
|
||||
},
|
||||
mod: function(a, b) {
|
||||
return new(tree.Dimension)(a.value % b.value, a.unit);
|
||||
},
|
||||
pow: function(x, y) {
|
||||
if (typeof x === "number" && typeof y === "number") {
|
||||
x = new(tree.Dimension)(x);
|
||||
y = new(tree.Dimension)(y);
|
||||
} else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "arguments must be numbers" };
|
||||
}
|
||||
|
||||
return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
|
||||
},
|
||||
percentage: function (n) {
|
||||
return new(tree.Dimension)(n.value * 100, '%');
|
||||
}
|
||||
});
|
||||
};
|
||||
30
lib/less/functions/string.js
Normal file
30
lib/less/functions/string.js
Normal file
@@ -0,0 +1,30 @@
|
||||
module.exports = function(functions, tree) {
|
||||
functions.functionRegistry.addMultiple({
|
||||
e: function (str) {
|
||||
return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value);
|
||||
},
|
||||
escape: function (str) {
|
||||
return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
|
||||
},
|
||||
replace: function (string, pattern, replacement, flags) {
|
||||
var result = string.value;
|
||||
|
||||
result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
},
|
||||
'%': function (string /* arg, arg, ...*/) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
result = string.value;
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
/*jshint loopfunc:true */
|
||||
result = result.replace(/%[sda]/i, function(token) {
|
||||
var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
|
||||
return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
|
||||
});
|
||||
}
|
||||
result = result.replace(/%%/g, '%');
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
}
|
||||
});
|
||||
};
|
||||
77
lib/less/functions/svg.js
Normal file
77
lib/less/functions/svg.js
Normal file
@@ -0,0 +1,77 @@
|
||||
module.exports = function(functions, tree, less) {
|
||||
functions.functionRegistry.add("svg-gradient", function(direction) {
|
||||
|
||||
function throwArgumentDescriptor() {
|
||||
throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
|
||||
}
|
||||
|
||||
if (arguments.length < 3) {
|
||||
throwArgumentDescriptor();
|
||||
}
|
||||
var stops = Array.prototype.slice.call(arguments, 1),
|
||||
gradientDirectionSvg,
|
||||
gradientType = "linear",
|
||||
rectangleDimension = 'x="0" y="0" width="1" height="1"',
|
||||
useBase64 = true,
|
||||
renderEnv = {compress: false},
|
||||
returner,
|
||||
directionValue = direction.toCSS(renderEnv),
|
||||
i, color, position, positionValue, alpha;
|
||||
|
||||
switch (directionValue) {
|
||||
case "to bottom":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
|
||||
break;
|
||||
case "to right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
|
||||
break;
|
||||
case "to bottom right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
|
||||
break;
|
||||
case "to top right":
|
||||
gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
|
||||
break;
|
||||
case "ellipse":
|
||||
case "ellipse at center":
|
||||
gradientType = "radial";
|
||||
gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
|
||||
rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
|
||||
break;
|
||||
default:
|
||||
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
|
||||
}
|
||||
returner = '<?xml version="1.0" ?>' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
|
||||
'<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
|
||||
|
||||
for (i = 0; i < stops.length; i+= 1) {
|
||||
if (stops[i].value) {
|
||||
color = stops[i].value[0];
|
||||
position = stops[i].value[1];
|
||||
} else {
|
||||
color = stops[i];
|
||||
position = undefined;
|
||||
}
|
||||
|
||||
if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
|
||||
throwArgumentDescriptor();
|
||||
}
|
||||
positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
|
||||
alpha = color.alpha;
|
||||
returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
|
||||
}
|
||||
returner += '</' + gradientType + 'Gradient>' +
|
||||
'<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
|
||||
|
||||
if (useBase64) {
|
||||
try {
|
||||
returner = less.environment.encodeBase64(this.env, returner);
|
||||
} catch(e) {
|
||||
useBase64 = false;
|
||||
}
|
||||
}
|
||||
|
||||
returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
|
||||
return new(tree.URL)(new(tree.Anonymous)(returner));
|
||||
});
|
||||
};
|
||||
64
lib/less/functions/types.js
Normal file
64
lib/less/functions/types.js
Normal file
@@ -0,0 +1,64 @@
|
||||
module.exports = function(functions, tree) {
|
||||
var isa = function (n, Type) {
|
||||
return (n instanceof Type) ? tree.True : tree.False;
|
||||
},
|
||||
isunit = function (n, unit) {
|
||||
return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
|
||||
};
|
||||
functions.functionRegistry.addMultiple({
|
||||
iscolor: function (n) {
|
||||
return isa(n, tree.Color);
|
||||
},
|
||||
isnumber: function (n) {
|
||||
return isa(n, tree.Dimension);
|
||||
},
|
||||
isstring: function (n) {
|
||||
return isa(n, tree.Quoted);
|
||||
},
|
||||
iskeyword: function (n) {
|
||||
return isa(n, tree.Keyword);
|
||||
},
|
||||
isurl: function (n) {
|
||||
return isa(n, tree.URL);
|
||||
},
|
||||
ispixel: function (n) {
|
||||
return isunit(n, 'px');
|
||||
},
|
||||
ispercentage: function (n) {
|
||||
return isunit(n, '%');
|
||||
},
|
||||
isem: function (n) {
|
||||
return isunit(n, 'em');
|
||||
},
|
||||
isunit: isunit,
|
||||
unit: function (val, unit) {
|
||||
if(!(val instanceof tree.Dimension)) {
|
||||
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
|
||||
}
|
||||
if (unit) {
|
||||
if (unit instanceof tree.Keyword) {
|
||||
unit = unit.value;
|
||||
} else {
|
||||
unit = unit.toCSS();
|
||||
}
|
||||
} else {
|
||||
unit = "";
|
||||
}
|
||||
return new(tree.Dimension)(val.value, unit);
|
||||
},
|
||||
"get-unit": function (n) {
|
||||
return new(tree.Anonymous)(n.unit);
|
||||
},
|
||||
extract: function(values, index) {
|
||||
index = index.value - 1; // (1-based index)
|
||||
// handle non-array values as an array of length 1
|
||||
// return 'undefined' if index is invalid
|
||||
return Array.isArray(values.value) ?
|
||||
values.value[index] : Array(values)[index];
|
||||
},
|
||||
length: function(values) {
|
||||
var n = Array.isArray(values.value) ? values.value.length : 1;
|
||||
return new tree.Dimension(n);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -5,10 +5,10 @@ var less = {
|
||||
}
|
||||
};
|
||||
|
||||
less.tree = (require('./tree'))(less);
|
||||
less.tree = (require('./tree'))(less, less.data);
|
||||
less.visitor = require('./visitor/index.js')(less, less.tree);
|
||||
less.Parser = (require('./parser'))(less, less.tree, less.visitor);
|
||||
less.tree.functions = (require('./functions'))(less, less.tree);
|
||||
less.functions = (require('./functions/index.js'))(less, less.tree);
|
||||
require('./env')(less.tree);
|
||||
|
||||
less.tree.sourceMapOutput = require('./source-map-output.js')(less);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function (less) {
|
||||
module.exports = function (less, data) {
|
||||
|
||||
var tree = {};
|
||||
|
||||
@@ -97,7 +97,7 @@ tree.outputRuleset = function (env, output, rules) {
|
||||
};
|
||||
|
||||
tree.Alpha = require('./tree/alpha')(tree);
|
||||
tree.Color = require('./tree/color')(less.data, tree);
|
||||
tree.Color = require('./tree/color')(data, tree);
|
||||
tree.Directive = require('./tree/directive')(tree);
|
||||
tree.DetachedRuleset = require('./tree/detached-ruleset')(tree);
|
||||
tree.Operation = require('./tree/operation')(tree);
|
||||
@@ -113,7 +113,7 @@ tree.Selector = require('./tree/selector')(tree);
|
||||
tree.Quoted = require('./tree/quoted')(tree);
|
||||
tree.Expression = require('./tree/expression')(tree);
|
||||
tree.Rule = require('./tree/rule')(tree);
|
||||
tree.Call = require('./tree/call')(tree);
|
||||
tree.Call = require('./tree/call')(tree, less);
|
||||
tree.URL = require('./tree/url')(tree);
|
||||
tree.Import = require('./tree/import')(tree);
|
||||
tree.mixin = {
|
||||
@@ -133,6 +133,12 @@ tree.Negative = require('./tree/negative')(tree);
|
||||
tree.Extend = require('./tree/extend')(tree);
|
||||
tree.RulesetCall = require('./tree/ruleset-call')(tree);
|
||||
|
||||
tree.fround = function(env, value) {
|
||||
var precision = env && env.numPrecision;
|
||||
//add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
|
||||
return (precision == null) ? value : Number((value + 2e-16).toFixed(precision));
|
||||
};
|
||||
|
||||
return tree;
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function (tree) {
|
||||
module.exports = function (tree, less) {
|
||||
|
||||
//
|
||||
// A function call node.
|
||||
@@ -18,7 +18,7 @@ Call.prototype = {
|
||||
},
|
||||
//
|
||||
// When evaluating a function call,
|
||||
// we either find the function in `tree.functions` [1],
|
||||
// we either find the function in `less.functions` [1],
|
||||
// in which case we call it, passing the evaluated arguments,
|
||||
// if this returns null or we cannot find the function, we
|
||||
// simply print it out as it appeared originally [2].
|
||||
@@ -31,13 +31,11 @@ Call.prototype = {
|
||||
//
|
||||
eval: function (env) {
|
||||
var args = this.args.map(function (a) { return a.eval(env); }),
|
||||
nameLC = this.name.toLowerCase(),
|
||||
result, func;
|
||||
result, funcCaller = new less.functions.functionCaller(this.name, env, this.currentFileInfo);
|
||||
|
||||
if (nameLC in tree.functions) { // 1.
|
||||
if (funcCaller.isValid()) { // 1.
|
||||
try {
|
||||
func = new tree.functionCall(env, this.currentFileInfo);
|
||||
result = func[nameLC].apply(func, args);
|
||||
result = funcCaller.call(args);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,17 @@ module.exports = function() {
|
||||
passedTests = 0;
|
||||
|
||||
|
||||
less.tree.functions.add = function (a, b) {
|
||||
return new(less.tree.Dimension)(a.value + b.value);
|
||||
};
|
||||
less.tree.functions.increment = function (a) {
|
||||
return new(less.tree.Dimension)(a.value + 1);
|
||||
};
|
||||
less.tree.functions._color = function (str) {
|
||||
if (str.value === "evil red") { return new(less.tree.Color)("600"); }
|
||||
};
|
||||
less.functions.functionRegistry.addMultiple({
|
||||
add: function (a, b) {
|
||||
return new(less.tree.Dimension)(a.value + b.value);
|
||||
},
|
||||
increment: function (a) {
|
||||
return new(less.tree.Dimension)(a.value + 1);
|
||||
},
|
||||
_color: function (str) {
|
||||
if (str.value === "evil red") { return new(less.tree.Color)("600"); }
|
||||
}
|
||||
});
|
||||
|
||||
function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
|
||||
fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) {
|
||||
|
||||
Reference in New Issue
Block a user