diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3854c57..6eb0416 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
## Changelog
+## 0.9.0
+
+* Adds the modulus operator `%` as an option
+* Adds a new field-type like `[FIELD]` instead of "[FIELD"
+
+### 0.8.0
+
+* Supports function syntax for transforms, optionally with variables and arguments.
+
### 0.7.0
* Support an `opacity` property on any style that is a style-level property
diff --git a/bin/carto b/bin/carto
index 5ab0274..1b0b9b8 100755
--- a/bin/carto
+++ b/bin/carto
@@ -2,7 +2,7 @@
var path = require('path'),
fs = require('fs'),
- sys = require('sys'),
+ util = require('util'),
carto = require('carto');
var args = process.argv.slice(1);
@@ -17,7 +17,7 @@ args = args.filter(function (arg) {
switch (arg) {
case 'v':
case 'version':
- sys.puts("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
+ util.puts("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
process.exit(0);
break;
case 'b':
@@ -46,7 +46,7 @@ if (input && input[0] != '/') {
}
if (!input) {
- sys.puts("carto: no input files");
+ util.puts("carto: no input files");
process.exit(1);
}
@@ -57,14 +57,14 @@ if (options.benchmark) {
try {
var data = fs.readFileSync(input, 'utf-8');
} catch(err) {
- sys.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
+ util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
try {
data = JSON.parse(data);
} catch(err) {
- sys.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
+ util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
process.exit(1);
}
@@ -98,7 +98,7 @@ function compile(err, data) {
process.exit(1);
} else {
if (!options.benchmark) {
- sys.puts(output);
+ util.puts(output);
} else {
var duration = (+new Date) - start;
console.log('TOTAL: ' + (duration) + 'ms');
@@ -107,9 +107,9 @@ function compile(err, data) {
});
} catch (e) {
if (e.stack) {
- sys.error(e.stack);
+ util.error(e.stack);
} else {
- sys.error(e);
+ util.error(e);
}
}
};
diff --git a/lib/carto/functions.js b/lib/carto/functions.js
index 47129ef..8f45a44 100644
--- a/lib/carto/functions.js
+++ b/lib/carto/functions.js
@@ -94,6 +94,13 @@ tree.functions = {
return hsla(hsl);
},
+ replace: function (entity, a, b) {
+ if (entity.is === 'field') {
+ return entity.toString + '.replace(' + a.toString() + ', ' + b.toString() + ')';
+ } else {
+ return entity.replace(a, b);
+ }
+ },
//
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
// http://sass-lang.com
@@ -126,7 +133,7 @@ tree.functions = {
.replace(/%[da]/, args[i].toString());
}
str = str.replace(/%%/g, '%');
- return new tree.Quoted('"' + str + '"', str);
+ return new tree.Quoted(str);
}
};
diff --git a/lib/carto/index.js b/lib/carto/index.js
index e1c72a9..9e73bcd 100644
--- a/lib/carto/index.js
+++ b/lib/carto/index.js
@@ -50,9 +50,9 @@ var carto = {
}
};
-[ 'anonymous', 'call', 'color', 'comment', 'definition', 'dimension',
- 'directive', 'element', 'expression', 'filterset', 'filter',
- 'keyword', 'layer', 'operation', 'quoted', 'imagefilter',
+[ 'call', 'color', 'comment', 'definition', 'dimension',
+ 'directive', 'element', 'expression', 'filterset', 'filter', 'field',
+ 'keyword', 'layer', 'literal', 'operation', 'quoted', 'imagefilter',
'reference', 'rule', 'ruleset', 'selector', 'style', 'url', 'value',
'variable', 'zoom', 'invalid', 'fontset'
].forEach(function(n) {
diff --git a/lib/carto/parser.js b/lib/carto/parser.js
index d65afc2..733cae7 100644
--- a/lib/carto/parser.js
+++ b/lib/carto/parser.js
@@ -419,10 +419,24 @@ carto.Parser = function Parser(env) {
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
if (str) {
- return new tree.Quoted(str[0], str[1] || str[2]);
+ return new tree.Quoted(str[1] || str[2]);
}
},
+ // A reference to a Mapnik field, like
+ //
+ // [NAME]
+ //
+ // Behind the scenes, this has the same representation, but Carto
+ // needs to be careful to warn when unsupported operations are used.
+ field: function() {
+ if (! $('[')) return;
+ var field_name = $(/(^[a-zA-Z0-9\-_]+)/);
+ if (! $(']')) return;
+ if (field_name) return new tree.Field(field_name[1]);
+ },
+
+ // This is a comparison operator
comparison: function() {
var str = $(/^=~|=|!=|<=|>=|<|>/);
if (str) {
@@ -449,7 +463,7 @@ carto.Parser = function Parser(env) {
call: function() {
var name, args;
- if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
+ if (! (name = /^([\w\-]+|%)\(/.exec(chunks[j]))) return;
name = name[1].toLowerCase();
@@ -467,13 +481,15 @@ carto.Parser = function Parser(env) {
return new tree.Call(name, args, i);
}
},
- arguments: function() {
+ // Arguments are comma-separated expressions
+ 'arguments': function() {
var args = [], arg;
while (arg = $(this.expression)) {
args.push(arg);
if (! $(',')) { break; }
}
+
return args;
},
literal: function() {
@@ -497,7 +513,7 @@ carto.Parser = function Parser(env) {
return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
} else {
return new tree.URL((value.value || value instanceof tree.Variable) ?
- value : new tree.Anonymous(value), imports.paths);
+ value : new tree.Quoted(value), imports.paths);
}
},
@@ -553,7 +569,9 @@ carto.Parser = function Parser(env) {
variable: function() {
var name;
- if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1]; }
+ if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) {
+ return name[1];
+ }
},
//
@@ -561,8 +579,12 @@ carto.Parser = function Parser(env) {
// and can be found inside a rule's value.
//
entity: function() {
- return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
- $(this.entities.call) || $(this.entities.keyword);
+ return $(this.entities.literal) ||
+ $(this.entities.field) ||
+ $(this.entities.variable) ||
+ $(this.entities.url) ||
+ $(this.entities.call) ||
+ $(this.entities.keyword);
},
//
@@ -743,6 +765,7 @@ carto.Parser = function Parser(env) {
return new tree.Value(expressions);
}
},
+ // A sub-expression, contained by parenthensis
sub: function() {
var e;
@@ -750,10 +773,12 @@ carto.Parser = function Parser(env) {
return e;
}
},
+ // This is a misnomer because it actually handles multiplication
+ // and division.
multiplication: function() {
var m, a, op, operation;
if (m = $(this.operand)) {
- while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
+ while ((op = ($('/') || $('*') || $('%'))) && (a = $(this.operand))) {
operation = new tree.Operation(op, [operation || m, a], memo);
}
return operation || m;
@@ -773,9 +798,7 @@ carto.Parser = function Parser(env) {
// An operand is anything that can be part of an operation,
// such as a Color, or a Variable
operand: function() {
- return $(this.sub) || $(this.entities.dimension) ||
- $(this.entities.color) || $(this.entities.variable) ||
- $(this.entities.call);
+ return $(this.sub) || $(this.entity);
},
// Expressions either represent mathematical operations,
diff --git a/lib/carto/tree/call.js b/lib/carto/tree/call.js
index 10015eb..d4bf72c 100644
--- a/lib/carto/tree/call.js
+++ b/lib/carto/tree/call.js
@@ -1,8 +1,5 @@
(function(tree) {
-//
-// A function call node.
-//
tree.Call = function Call(name, args, index) {
this.name = name;
this.args = args;
@@ -50,9 +47,37 @@ tree.Call.prototype = {
value: 'undefined'
};
}
- } else { // 2.
- return new tree.Anonymous(this.name +
- '(' + args.map(function(a) { return a.toString(); }).join(', ') + ')');
+ } else {
+ var fn = tree.Reference.mapnikFunction(this.name);
+ if (!fn) {
+ env.error({
+ message: 'unknown function ' + this.name,
+ index: this.index,
+ type: 'runtime',
+ filename: this.filename
+ });
+ return {
+ is: 'undefined',
+ value: 'undefined'
+ };
+ }
+ if (fn[1] !== args.length) {
+ env.error({
+ message: 'function ' + this.name + ' takes ' +
+ fn[1] + ' arguments and was given ' + args.length,
+ index: this.index,
+ type: 'runtime',
+ filename: this.filename
+ });
+ return {
+ is: 'undefined',
+ value: 'undefined'
+ };
+ } else {
+ // Save the evaluated versions of arguments
+ this.args = args;
+ return this;
+ }
}
},
toString: function(env) {
diff --git a/lib/carto/tree/comment.js b/lib/carto/tree/comment.js
index 98385c2..f7e0d90 100644
--- a/lib/carto/tree/comment.js
+++ b/lib/carto/tree/comment.js
@@ -4,6 +4,7 @@ tree.Comment = function Comment(value, silent) {
this.value = value;
this.silent = !!silent;
};
+
tree.Comment.prototype = {
toString: function(env) {
return '';
diff --git a/lib/carto/tree/dimension.js b/lib/carto/tree/dimension.js
index dfa7de2..0fe8458 100644
--- a/lib/carto/tree/dimension.js
+++ b/lib/carto/tree/dimension.js
@@ -25,7 +25,7 @@ tree.Dimension.prototype = {
return new tree.Color([this.value, this.value, this.value]);
},
toString: function() {
- return this.value;
+ return this.value.toString();
},
// In an operation between two Dimensions,
diff --git a/lib/carto/tree/field.js b/lib/carto/tree/field.js
new file mode 100644
index 0000000..f6ebe8d
--- /dev/null
+++ b/lib/carto/tree/field.js
@@ -0,0 +1,17 @@
+(function(tree) {
+
+tree.Field = function Field(content) {
+ this.value = content || '';
+ this.is = 'field';
+};
+
+tree.Field.prototype = {
+ toString: function() {
+ return '[' + this.value + ']';
+ },
+ 'eval': function() {
+ return this;
+ }
+};
+
+})(require('../tree'));
diff --git a/lib/carto/tree/literal.js b/lib/carto/tree/literal.js
new file mode 100644
index 0000000..135be6c
--- /dev/null
+++ b/lib/carto/tree/literal.js
@@ -0,0 +1,20 @@
+// A literal is a literal string for Mapnik - the
+// result of the combination of a `tree.Field` with any
+// other type.
+(function(tree) {
+
+tree.Literal = function Field(content) {
+ this.value = content || '';
+ this.is = 'field';
+};
+
+tree.Literal.prototype = {
+ toString: function() {
+ return this.value;
+ },
+ 'eval': function() {
+ return this;
+ }
+};
+
+})(require('../tree'));
diff --git a/lib/carto/tree/operation.js b/lib/carto/tree/operation.js
index 8fd0e87..627063a 100644
--- a/lib/carto/tree/operation.js
+++ b/lib/carto/tree/operation.js
@@ -1,11 +1,12 @@
(function(tree) {
-
tree.Operation = function Operation(op, operands, index) {
this.op = op.trim();
this.operands = operands;
this.index = index;
+ this.is = 'operation';
};
+
tree.Operation.prototype.eval = function(env) {
var a = this.operands[0].eval(env),
b = this.operands[1].eval(env),
@@ -30,9 +31,11 @@ tree.Operation.prototype.eval = function(env) {
}
}
- if (a instanceof tree.Quoted || b instanceof tree.Quoted) {
+ // Only concatenate plain strings, because this is easily
+ // pre-processed
+ if (a instanceof tree.Quoted && b instanceof tree.Quoted && this.op !== '+') {
env.error({
- message: 'One cannot add, subtract, divide, or multiply strings.',
+ message: "Can't subtract, divide, or multiply strings.",
index: this.index,
type: 'runtime',
filename: this.filename
@@ -43,6 +46,25 @@ tree.Operation.prototype.eval = function(env) {
};
}
+ // Fields, literals, dimensions, and quoted strings can be combined.
+ if (a instanceof tree.Field || b instanceof tree.Field ||
+ a instanceof tree.Literal || b instanceof tree.Literal) {
+ if (a.is === 'color' || b.is === 'color') {
+ env.error({
+ message: "Can't subtract, divide, or multiply colors in expressions.",
+ index: this.index,
+ type: 'runtime',
+ filename: this.filename
+ });
+ return {
+ is: 'undefined',
+ value: 'undefined'
+ };
+ } else {
+ return new tree.Literal(a.eval(env).toString(true) + this.op + b.eval(env).toString(true));
+ }
+ }
+
return a.operate(this.op, b);
};
@@ -51,6 +73,7 @@ tree.operate = function(op, a, b) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
+ case '%': return a % b;
case '/': return a / b;
}
};
diff --git a/lib/carto/tree/quoted.js b/lib/carto/tree/quoted.js
index 9c30a3d..4064098 100644
--- a/lib/carto/tree/quoted.js
+++ b/lib/carto/tree/quoted.js
@@ -1,17 +1,23 @@
(function(tree) {
-tree.Quoted = function Quoted(str, content) {
+tree.Quoted = function Quoted(content) {
this.value = content || '';
- this.quote = str.charAt(0);
this.is = 'string';
};
+
tree.Quoted.prototype = {
toString: function(quotes) {
var xmlvalue = this.value.replace(/\'/g, ''');
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
},
+
'eval': function() {
return this;
+ },
+
+ operate: function(op, other) {
+ return new tree.Quoted(true,
+ tree.operate(op, this.toString(), other.toString(this.contains_field)));
}
};
diff --git a/lib/carto/tree/reference.js b/lib/carto/tree/reference.js
index bdc212d..e46622b 100644
--- a/lib/carto/tree/reference.js
+++ b/lib/carto/tree/reference.js
@@ -1,3 +1,9 @@
+/*
+ * Carto pulls in a reference from the `mapnik-reference`
+ * module. This file builds indexes from that file for its various
+ * options, and provides validation methods for property: value
+ * combinations.
+ */
(function(tree) {
var _ = require('underscore');
@@ -59,6 +65,24 @@ tree.Reference.symbolizer = function(selector) {
}
};
+/*
+ * For transform properties and image-filters,
+ * mapnik has its own functions.
+ */
+tree.Reference.mapnikFunction = function(name) {
+ var functions = [];
+ for (var i in tree.Reference.data.symbolizers) {
+ for (var j in tree.Reference.data.symbolizers[i]) {
+ if (tree.Reference.data.symbolizers[i][j].type === 'functions') {
+ functions = functions.concat(tree.Reference.data.symbolizers[i][j].functions);
+ }
+ }
+ }
+ return _.find(functions, function(f) {
+ return f[0] === name;
+ });
+};
+
tree.Reference.requiredPropertyList = function(symbolizer_name) {
if (this.required_prop_list_cache[symbolizer_name]) {
return this.required_prop_list_cache[symbolizer_name];
@@ -101,28 +125,30 @@ tree.Reference.isFont = function(selector) {
tree.Reference.validValue = function(env, selector, value) {
var i, j;
- if (value[0]) {
- return tree.Reference.selector(selector).type == value[0].is;
- } else {
- // TODO: handle in reusable way
- if (!tree.Reference.selector(selector)) {
- return false;
- } else if (value.value[0].is == 'keyword') {
- return tree.Reference
- .selector(selector).type
- .indexOf(value.value[0].value) !== -1;
- } else if (value.value[0].is == 'undefined') {
- // caught earlier in the chain - ignore here so that
- // error is not overridden
- return true;
- } else if (tree.Reference.selector(selector).type == 'numbers') {
- for (i in value.value) {
- if (value.value[i].is !== 'float') {
- return false;
- }
+ // TODO: handle in reusable way
+ if (!tree.Reference.selector(selector)) {
+ return false;
+ } else if (value.value[0].is == 'keyword') {
+ return tree.Reference
+ .selector(selector).type
+ .indexOf(value.value[0].value) !== -1;
+ } else if (value.value[0].is == 'undefined') {
+ // caught earlier in the chain - ignore here so that
+ // error is not overridden
+ return true;
+ } else if (tree.Reference.selector(selector).type == 'numbers') {
+ for (i in value.value) {
+ if (value.value[i].is !== 'float') {
+ return false;
}
+ }
+ return true;
+ } else if (tree.Reference.selector(selector).type == 'functions') {
+ // For backwards compatibility, you can specify a string for `functions`-compatible
+ // values, though they will not be validated.
+ if (value.value[0].is === 'string') {
return true;
- } else if (tree.Reference.selector(selector).type == 'functions') {
+ } else {
for (i in value.value) {
for (j in value.value[i].value) {
if (value.value[i].value[j].is !== 'call') {
@@ -139,22 +165,24 @@ tree.Reference.validValue = function(env, selector, value) {
}
}
return true;
- } else {
- if (tree.Reference.selector(selector).validate) {
- var valid = false;
- for (i = 0; i < value.value.length; i++) {
- if (tree.Reference.selector(selector).type == value.value[i].is &&
- tree.Reference
- ._validateValue
- [tree.Reference.selector(selector).validate]
- (env, value.value[i].value)) {
- return true;
- }
+ }
+ } else if (tree.Reference.selector(selector).type == 'expression') {
+ return true;
+ } else {
+ if (tree.Reference.selector(selector).validate) {
+ var valid = false;
+ for (i = 0; i < value.value.length; i++) {
+ if (tree.Reference.selector(selector).type == value.value[i].is &&
+ tree.Reference
+ ._validateValue
+ [tree.Reference.selector(selector).validate]
+ (env, value.value[i].value)) {
+ return true;
}
- return valid;
- } else {
- return tree.Reference.selector(selector).type == value.value[0].is;
}
+ return valid;
+ } else {
+ return tree.Reference.selector(selector).type == value.value[0].is;
}
}
};
diff --git a/package.json b/package.json
index f07837e..35ac355 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
},
"dependencies": {
"underscore": "~1.3.3",
- "mapnik-reference": "~2.1.0",
+ "mapnik-reference": "https://github.com/mapnik/mapnik-reference/tarball/transform-functions",
"xml2js": "~0.1.13"
},
"devDependencies": {
diff --git a/test/rendering/building_height.mml b/test/rendering/building_height.mml
new file mode 100644
index 0000000..79ba517
--- /dev/null
+++ b/test/rendering/building_height.mml
@@ -0,0 +1,23 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "building_height.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ },
+ {
+ "class": "new",
+ "name": "countries",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/building_height.mss b/test/rendering/building_height.mss
new file mode 100644
index 0000000..6122c84
--- /dev/null
+++ b/test/rendering/building_height.mss
@@ -0,0 +1,4 @@
+@n: 4;
+#world {
+ building-height: 2 * 3 * [HEIGHT] + 2 + [NOTHEIGHT] + (@n * 2);
+}
diff --git a/test/rendering/building_height.result b/test/rendering/building_height.result
new file mode 100644
index 0000000..393778d
--- /dev/null
+++ b/test/rendering/building_height.result
@@ -0,0 +1,20 @@
+
+
+
diff --git a/test/rendering/field.mml b/test/rendering/field.mml
new file mode 100644
index 0000000..94629dc
--- /dev/null
+++ b/test/rendering/field.mml
@@ -0,0 +1,23 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "field.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ },
+ {
+ "class": "new",
+ "name": "countries",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/field.mss b/test/rendering/field.mss
new file mode 100644
index 0000000..5cd939b
--- /dev/null
+++ b/test/rendering/field.mss
@@ -0,0 +1,6 @@
+#world {
+ text-name: "hello " + [NAME] + " hello";
+ text-size: 11;
+ text-face-name: "Georgia Regular", "Arial Italic";
+}
+
diff --git a/test/rendering/field.result b/test/rendering/field.result
new file mode 100644
index 0000000..716e378
--- /dev/null
+++ b/test/rendering/field.result
@@ -0,0 +1,23 @@
+
+
+
diff --git a/test/rendering/field_advanced.mml b/test/rendering/field_advanced.mml
new file mode 100644
index 0000000..12153a4
--- /dev/null
+++ b/test/rendering/field_advanced.mml
@@ -0,0 +1,23 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "field_advanced.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ },
+ {
+ "class": "new",
+ "name": "countries",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/field_advanced.mss b/test/rendering/field_advanced.mss
new file mode 100644
index 0000000..68f41e8
--- /dev/null
+++ b/test/rendering/field_advanced.mss
@@ -0,0 +1,6 @@
+#world {
+ text-name: "hello " + [NAME] + " hello" + 2;
+ text-size: 11;
+ text-face-name: "Georgia Regular", "Arial Italic";
+}
+
diff --git a/test/rendering/field_advanced.result b/test/rendering/field_advanced.result
new file mode 100644
index 0000000..cf47ac3
--- /dev/null
+++ b/test/rendering/field_advanced.result
@@ -0,0 +1,23 @@
+
+
+
diff --git a/test/rendering/modulus.mml b/test/rendering/modulus.mml
new file mode 100644
index 0000000..e46eb87
--- /dev/null
+++ b/test/rendering/modulus.mml
@@ -0,0 +1,23 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "modulus.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ },
+ {
+ "class": "new",
+ "name": "countries",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/modulus.mss b/test/rendering/modulus.mss
new file mode 100644
index 0000000..e9d797d
--- /dev/null
+++ b/test/rendering/modulus.mss
@@ -0,0 +1,6 @@
+#world {
+ text-name: "hello ";
+ text-size: 11 % 2;
+ text-face-name: "Georgia Regular", "Arial Italic";
+}
+
diff --git a/test/rendering/modulus.result b/test/rendering/modulus.result
new file mode 100644
index 0000000..b0f7020
--- /dev/null
+++ b/test/rendering/modulus.result
@@ -0,0 +1,23 @@
+
+
+
diff --git a/test/rendering/transforms.mss b/test/rendering/transforms.mss
new file mode 100644
index 0000000..628b984
--- /dev/null
+++ b/test/rendering/transforms.mss
@@ -0,0 +1,5 @@
+@trans: 2;
+#world {
+ point-file: url(foo.png);
+ point-transform: translate(@trans * 2, @trans);
+}
diff --git a/test/rendering/transforms.result b/test/rendering/transforms.result
new file mode 100644
index 0000000..80c5961
--- /dev/null
+++ b/test/rendering/transforms.result
@@ -0,0 +1,20 @@
+
+
+
diff --git a/test/rendering/transforms_backwards.mml b/test/rendering/transforms_backwards.mml
new file mode 100644
index 0000000..b901d36
--- /dev/null
+++ b/test/rendering/transforms_backwards.mml
@@ -0,0 +1,14 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "transforms_backwards.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/transforms_backwards.mss b/test/rendering/transforms_backwards.mss
new file mode 100644
index 0000000..f7d75e1
--- /dev/null
+++ b/test/rendering/transforms_backwards.mss
@@ -0,0 +1,4 @@
+#world {
+ point-file: url(foo.png);
+ point-transform: "translate(2, 2)";
+}
diff --git a/test/rendering/transforms_backwards.result b/test/rendering/transforms_backwards.result
new file mode 100644
index 0000000..e472fba
--- /dev/null
+++ b/test/rendering/transforms_backwards.result
@@ -0,0 +1,20 @@
+
+
+
diff --git a/test/rendering/transforms_field.mml b/test/rendering/transforms_field.mml
new file mode 100644
index 0000000..57fdb81
--- /dev/null
+++ b/test/rendering/transforms_field.mml
@@ -0,0 +1,14 @@
+{
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Stylesheet": [
+ "transforms_field.mss"
+ ],
+ "Layer": [{
+ "name": "world",
+ "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
+ "Datasource": {
+ "file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
+ "type": "shape"
+ }
+ }]
+}
diff --git a/test/rendering/transforms_field.mss b/test/rendering/transforms_field.mss
new file mode 100644
index 0000000..834051b
--- /dev/null
+++ b/test/rendering/transforms_field.mss
@@ -0,0 +1,4 @@
+#world {
+ point-file:url(foo.png);
+ point-transform: scale(2 * 5 * [POP2005], [POP2005]);
+}
diff --git a/test/rendering/transforms_field.result b/test/rendering/transforms_field.result
new file mode 100644
index 0000000..cb89a8a
--- /dev/null
+++ b/test/rendering/transforms_field.result
@@ -0,0 +1,20 @@
+
+
+