Compare commits
457 Commits
master-map
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
85881d99dd | ||
|
d3a5c97143 | ||
|
8ed3ad15c1 | ||
|
72f79efbdd | ||
|
161516b721 | ||
|
e48d5ff993 | ||
|
31abb8bee0 | ||
|
e2177cf443 | ||
|
664b45cdde | ||
|
368dad0d11 | ||
|
4dfe5361b3 | ||
|
3596991362 | ||
|
bf4c67034a | ||
|
ad5d919139 | ||
|
9fb894181d | ||
|
ffdf2d3c9b | ||
|
cbe66020f9 | ||
|
056081ace6 | ||
|
a48ba58cae | ||
|
9f67f1bdde | ||
|
55c0786130 | ||
|
fef8cb369f | ||
|
d35e54859a | ||
|
daafa9fbf2 | ||
|
34799db0cc | ||
|
41133a65ad | ||
|
1016f6870c | ||
|
72f93abf16 | ||
|
a3538d9007 | ||
|
66cc146a02 | ||
|
aab27e6be8 | ||
|
cea1e7c620 | ||
|
f8a9995050 | ||
|
0de8b82ff9 | ||
|
7d00bfdf23 | ||
|
3db2fa322b | ||
|
f4a2605a23 | ||
|
d95967247d | ||
|
da8707a17f | ||
|
3f9f6ef40d | ||
|
b051ae284e | ||
|
ea880ccf7b | ||
|
ab412165b2 | ||
|
7974fb0a05 | ||
|
59c0208caa | ||
|
9b5fb6a408 | ||
|
945f5efb74 | ||
|
decdcc5d46 | ||
|
34bd0a045d | ||
|
ed819c3b51 | ||
|
a42afef5a8 | ||
|
410ecfd3c7 | ||
|
8c75b4b0c6 | ||
|
fd94fbd2e6 | ||
|
5c4bed9593 | ||
|
2c092f6b39 | ||
|
cc2104eb49 | ||
|
c780998dc8 | ||
|
0063ddba7f | ||
|
510847a3b2 | ||
|
0c1990f655 | ||
|
7315428079 | ||
|
515fbd0991 | ||
|
975abe9b5c | ||
|
b6186d884c | ||
|
e6ba32bc07 | ||
|
fd4caf7595 | ||
|
45e59f6a5b | ||
|
dfecbbb976 | ||
|
50fa97564e | ||
|
8f9982c313 | ||
|
01c6f0c6e5 | ||
|
7e02aac641 | ||
|
4f792fbead | ||
|
9e12a3b0d8 | ||
|
09d6384b1f | ||
|
11ffba0a8e | ||
|
199d41f20d | ||
|
e931f91475 | ||
|
11d597e733 | ||
|
1960aca276 | ||
|
f17aea8657 | ||
|
dfaed546e0 | ||
|
803f0c0a49 | ||
|
0d6f9d4634 | ||
|
c042733845 | ||
|
1fc486b1b9 | ||
|
b50ee48386 | ||
|
cf5886579f | ||
|
72d005a082 | ||
|
9e8c90b6f9 | ||
|
d3e23dcb5d | ||
|
176886f1ad | ||
|
673cf38121 | ||
|
860bc0adeb | ||
|
be56e24d9a | ||
|
27850ed122 | ||
|
0d2dddf978 | ||
|
fba91a0633 | ||
|
1612b5a8b7 | ||
|
4f13aabb6c | ||
|
8050ec843f | ||
|
885849fe82 | ||
|
14c0d3f550 | ||
|
0f46b57020 | ||
|
152954ee70 | ||
|
2631c928b7 | ||
|
9e3ae6e6fd | ||
|
eae8886b95 | ||
|
00c7a631b1 | ||
|
bd03e0c454 | ||
|
981e117731 | ||
|
d2a557acd9 | ||
|
f91ac22bfc | ||
|
db1cf0a8aa | ||
|
46b3f4857f | ||
|
b5b03cc8d7 | ||
|
8f86216fe0 | ||
|
bbfe3b3084 | ||
|
56d69ab68a | ||
|
e491c64ceb | ||
|
26c30d2fb5 | ||
|
6ae21b3ee0 | ||
|
12d1b1a4fe | ||
|
8a5f75546f | ||
|
74d807a3ae | ||
|
bf6537071a | ||
|
1cb891ef92 | ||
|
79a770f0af | ||
|
44610ab1c4 | ||
|
7c35dda115 | ||
|
146976c8a3 | ||
|
ae111041dd | ||
|
2e00705b64 | ||
|
b19ade3850 | ||
|
fe770630bb | ||
|
8df31b4fe6 | ||
|
e29b900342 | ||
|
150e7166f6 | ||
|
1d6e4a6f5d | ||
|
52343ff833 | ||
|
544491b91d | ||
|
7d3ba895f5 | ||
|
d5bf19a64f | ||
|
5890802d6d | ||
|
26a918d2e6 | ||
|
1ba5e0035e | ||
|
1e0657ee1e | ||
|
2a5c85a9d5 | ||
|
cbd4c0250d | ||
|
08ca40d3f7 | ||
|
9386a56464 | ||
|
d24998705d | ||
|
4de08ce68d | ||
|
9f65589279 | ||
|
6f2ec7b335 | ||
|
89a282be82 | ||
|
51baca34ae | ||
|
481a0fc406 | ||
|
60e33e609b | ||
|
339d781ca6 | ||
|
b7819be42d | ||
|
c570c2cd0e | ||
|
30b4fe1fc6 | ||
|
1ddefbe8eb | ||
|
57ddf46813 | ||
|
ba720bcb84 | ||
|
d608fa93b8 | ||
|
bb81e5c785 | ||
|
bde0d0e2ab | ||
|
145f1cc0b1 | ||
|
2273e0174f | ||
|
b9a00ed68b | ||
|
a40a87cd39 | ||
|
88bddbabc4 | ||
|
ef59be8abf | ||
|
b83d9c4037 | ||
|
678157b478 | ||
|
1a21c88484 | ||
|
ddb4bd338b | ||
|
6fad5676b4 | ||
|
6dc65cc991 | ||
|
10b81aa2c8 | ||
|
2fc7473a8c | ||
|
b675a648c0 | ||
|
e0fe7bce17 | ||
|
eb2623d677 | ||
|
302d409fab | ||
|
c5d8f4510e | ||
|
cd18bea7ba | ||
|
55b70b86c8 | ||
|
d9f97d3202 | ||
|
a52412c41e | ||
|
be4e687aa3 | ||
|
4b386326c7 | ||
|
4f771ed2a5 | ||
|
580e946cc0 | ||
|
5dc4610785 | ||
|
9d44691111 | ||
|
bb2c045325 | ||
|
9f18e9cc2c | ||
|
c6f787f761 | ||
|
cfab4f6369 | ||
|
fb06bb4bb1 | ||
|
87f57bdb38 | ||
|
b9309a7f80 | ||
|
96886c73c6 | ||
|
443b81012e | ||
|
f691a47306 | ||
|
02a657f373 | ||
|
a5e5c045c9 | ||
|
c288d09dcc | ||
|
5c8963f02d | ||
|
12a3d6cad2 | ||
|
bbdb6e5988 | ||
|
f6a76ec666 | ||
|
83c478875f | ||
|
449be47e91 | ||
|
a240695b65 | ||
|
84618be5df | ||
|
f333705cc2 | ||
|
f45aa875b7 | ||
|
834985ba81 | ||
|
d1c6c9fb82 | ||
|
0b584a84e7 | ||
|
fadcb3391d | ||
|
66d7c45a0c | ||
|
65a7d6589e | ||
|
ce4c61cc7f | ||
|
677ed7122a | ||
|
f3fd24583d | ||
|
5a7429d30d | ||
|
cb177b7a24 | ||
|
15140e49ee | ||
|
c4f02e4681 | ||
|
60030b1e69 | ||
|
21f03b3f9e | ||
|
922740da5a | ||
|
c1d750a246 | ||
|
72a79ab073 | ||
|
f6dea7fcb3 | ||
|
4b5e217107 | ||
|
6b51cd370b | ||
|
1c5c84587e | ||
|
2c9692b029 | ||
|
3a6cae7836 | ||
|
ba51c74771 | ||
|
bd454b4b6b | ||
|
b63fe0a9af | ||
|
525bdc5bef | ||
|
5938ebb609 | ||
|
d99c5d6c14 | ||
|
2bfc8a154b | ||
|
5a8461a6c1 | ||
|
dfacf064e8 | ||
|
6562d77935 | ||
|
2ead8dafa2 | ||
|
b18f162d87 | ||
|
8497bd36f7 | ||
|
2729aefd6e | ||
|
7a3b659d4e | ||
|
50a27c213c | ||
|
d1af35763a | ||
|
92b67a0a73 | ||
|
d170825685 | ||
|
d9e412696d | ||
|
08db949fbf | ||
|
d788fb8f07 | ||
|
e32aeb9fbb | ||
|
d7d49214cd | ||
|
ca42300648 | ||
|
393fc249b2 | ||
|
2ae7743c7d | ||
|
1d752708c4 | ||
|
4f963b8bb3 | ||
|
bc9d67b328 | ||
|
656b6db9a3 | ||
|
a57a162248 | ||
|
60396cbeef | ||
|
f88711db72 | ||
|
4f0e998dab | ||
|
fd3338ccb5 | ||
|
3d87cd490d | ||
|
1d637717d5 | ||
|
45fae55ac0 | ||
|
bbeff81a16 | ||
|
afac483b35 | ||
|
66b0c1ff7a | ||
|
93264c6e41 | ||
|
97f03a2eb3 | ||
|
45b5c107af | ||
|
99ee75ab8b | ||
|
84e0628a8e | ||
|
15ab78a3a8 | ||
|
bb153521e2 | ||
|
ff0fc2b1c8 | ||
|
264925324f | ||
|
d81b4b9d18 | ||
|
4cc262b563 | ||
|
4203578093 | ||
|
cfc90da91e | ||
|
8786bf51c7 | ||
|
ae95aa7575 | ||
|
b113bfea99 | ||
|
0f65b869fd | ||
|
c21a763dc7 | ||
|
207b120dee | ||
|
49b2324ea1 | ||
|
314cef0c75 | ||
|
04b1602310 | ||
|
2f76fec686 | ||
|
435452ba50 | ||
|
5d626d3c10 | ||
|
49c81edd3a | ||
|
05797dd711 | ||
|
86abdcd700 | ||
|
d6585d3691 | ||
|
acf94e5fab | ||
|
60661b68c3 | ||
|
5e2ea67df9 | ||
|
26dbcf3ca3 | ||
|
3382bfa29f | ||
|
7ac2d81062 | ||
|
caa639beb8 | ||
|
039031b68d | ||
|
19bf87cf3a | ||
|
8efc1c5d5e | ||
|
e8566e817e | ||
|
7deb1b86e0 | ||
|
c5a67fa938 | ||
|
be78202e0b | ||
|
0e0bac0e5c | ||
|
6ca0c705c8 | ||
|
4c044f93fe | ||
|
48d89889fe | ||
|
47464fc18d | ||
|
74fa914c8c | ||
|
55fbafe0d0 | ||
|
5fa4478d40 | ||
|
633754306e | ||
|
3f70b8a36c | ||
|
16f60edc50 | ||
|
76b271ebc5 | ||
|
3fd91ccb6b | ||
|
e2764d12f1 | ||
|
8c51c59fe9 | ||
|
cf8c11f038 | ||
|
31504baabe | ||
|
8f8c1ad39b | ||
|
494c07d383 | ||
|
de40fd4e42 | ||
|
6a5309e22e | ||
|
56ac678c0a | ||
|
1769cb4a59 | ||
|
9deb60cd08 | ||
|
a687bec9b6 | ||
|
c9d88add12 | ||
|
6ce476d2a2 | ||
|
0d686bb8d8 | ||
|
f3bde1fde3 | ||
|
bfc9d40f43 | ||
|
f0e245183a | ||
|
4d4abb27b5 | ||
|
6eda91a541 | ||
|
87d4f9627b | ||
|
29b641c72d | ||
|
6f687ff9e3 | ||
|
ece3eb3b0e | ||
|
d97286de57 | ||
|
c1e8c3b8f3 | ||
|
3cefa968a0 | ||
|
b7773c6452 | ||
|
9ed9c2b028 | ||
|
fc500db69b | ||
|
0233c523ea | ||
|
4b8256e0f8 | ||
|
551571fc17 | ||
|
43073fa1e8 | ||
|
baab7dd0ec | ||
|
f87e8adc95 | ||
|
3f31bcbe5f | ||
|
9f00195100 | ||
|
6fed91d728 | ||
|
a8133e0d77 | ||
|
06b147323f | ||
|
47882ccb00 | ||
|
73e5178f1f | ||
|
8846bfbbcd | ||
|
16db1c5b03 | ||
|
800122e1af | ||
|
3ac86f5fd3 | ||
|
bd17eed9f5 | ||
|
c707188ed5 | ||
|
539d293388 | ||
|
12cd05764b | ||
|
92d239b7f8 | ||
|
a7c1e0bc49 | ||
|
a55b4ca0e9 | ||
|
958b61c343 | ||
|
0a9cb6afe7 | ||
|
67b66b5568 | ||
|
9967393820 | ||
|
75f55f8d04 | ||
|
2aba917e3d | ||
|
f2a6922586 | ||
|
f6c07afee6 | ||
|
22f5d0cc45 | ||
|
f4722f516e | ||
|
607c4dba5a | ||
|
b3b7fec337 | ||
|
5145655c46 | ||
|
a106e2768f | ||
|
04cf9013a8 | ||
|
3c4baaf8cb | ||
|
667fd483cc | ||
|
f693f062ec | ||
|
89f8edbddc | ||
|
73f544333a | ||
|
d4fe84a7cf | ||
|
84a34be10a | ||
|
180cd0cc6e | ||
|
78ea179c46 | ||
|
9eee907467 | ||
|
8603799fa7 | ||
|
78b3a5a5d4 | ||
|
71547d059f | ||
|
0ebfa5f258 | ||
|
4ef52a82af | ||
|
ec0699dd45 | ||
|
e7ba697fc4 | ||
|
0d294c1075 | ||
|
23d11fedc6 | ||
|
e3e2b42277 | ||
|
675158cba9 | ||
|
1e6ede278f | ||
|
75875e2781 | ||
|
cd948535a5 | ||
|
33d325b0fc | ||
|
4825f9aee8 | ||
|
7d7cc2653c | ||
|
0c04ad07b6 | ||
|
1c569ce39d | ||
|
f41312597c | ||
|
4181df378f | ||
|
13bc8c3488 | ||
|
4c7af7f492 | ||
|
19ac7b2fb0 | ||
|
217498a207 | ||
|
25a2940ebc | ||
|
2fcbdaacfd | ||
|
8ef4efbe39 | ||
|
6fb339e21c | ||
|
4bb0fc0ebe | ||
|
bfc92c26bc | ||
|
f11259d734 | ||
|
2481c21365 | ||
|
e7a3d65cad |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
||||
/node_modules
|
||||
.DS_Store
|
||||
test/rendering/layers/
|
||||
test/rendering/cache/
|
||||
test/rendering-mss/npm-debug.log
|
||||
.idea/
|
||||
|
@ -1,3 +1,9 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- 0.6
|
||||
- '6'
|
||||
- '8'
|
||||
- '10'
|
||||
|
||||
script:
|
||||
- npm test
|
||||
|
9
CHANGELOG.carto.md
Normal file
9
CHANGELOG.carto.md
Normal file
@ -0,0 +1,9 @@
|
||||
## CARTO's Changelog
|
||||
|
||||
## 0.15.1-cdb5
|
||||
2018-11-20
|
||||
|
||||
* Support Node.js 6, 8 and, 10
|
||||
* Drop support for Node.js 0.10 and 0.11
|
||||
* Add package-lock.json
|
||||
* Add CHANGELOG.carto.md
|
53
CHANGELOG.md
53
CHANGELOG.md
@ -1,5 +1,58 @@
|
||||
## Changelog
|
||||
|
||||
## 0.14.0
|
||||
|
||||
* Support for Mapnik 3.x
|
||||
* Bump `mapnik-reference` dependency to ~6.0.1.
|
||||
|
||||
## 0.13.0
|
||||
|
||||
* Allows optional args in transforms.
|
||||
* Bump `mapnik-reference` dependency to 5.1.x.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
* Drop mml2json and xml2js dependency.
|
||||
|
||||
## 0.11.0
|
||||
|
||||
* Switch API to be synchronous. All errors should be caught using try/catch now.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* Remove automatic inclusion of `maximum-extent` on Map element to allow geometries that are buffered past extent bounds (e.g. dateline).
|
||||
* Bump `mapnik-reference` dependency to ~5.0.9 (with `shield-halo-rasterizer`)
|
||||
|
||||
## 0.9.6
|
||||
|
||||
* Fixed support for `text-face-name` values with `&` like `El&Font Bubble`
|
||||
* Fixed support for filtering on fields containing single quotes. Now `#layer[name="it's"] { ... }` is possible.
|
||||
* Fixed support for filtering on fields containing `&`. Now `#layer["Hello&Goodbye"="yes"] { ... }` is possible.
|
||||
* Added support for exponential notation in filters. Now `#layer[value = 1.2e3] { ... }` is possible.
|
||||
* Bump `mapnik-reference` dependency to ~5.0.8 (with support for Mapnik v2.3.0 and 3.x)
|
||||
|
||||
## 0.9.5
|
||||
|
||||
* Various speed optimizations to help address #20 (#231)
|
||||
* Fixed support for fields that contain the word `zoom` in them (previous clashed with `zoom` keyword)
|
||||
* Fixed support for a space in front of `zoom` keyword (#288)
|
||||
* Improved error messages when color functions encounter invalid color (#309)
|
||||
* The `carto` command line tool now exits cleanly when millstone is used
|
||||
* The `carto` command line tool now only localized with millstone if requested (#243)
|
||||
* Added man page for `carto` (#257)
|
||||
* Fix repeated comments in selectors. Fixes #260
|
||||
* Fixed `image-filter` duplication (#270)
|
||||
* Quote all needed XML chars. See #263.
|
||||
* Added higher tolerance for various characters in field names (#230)
|
||||
* Bump `mapnik-reference` dependency to ~5.0.7 (with support for Mapnik v2.2.0)
|
||||
* Adds compatibility with screen units.
|
||||
* Fixed ability to use carto as global module (#236)
|
||||
* Now using 'console' instead of `util` for `stderr` (#217)
|
||||
|
||||
## 0.9.4
|
||||
|
||||
* Fixes nesting of regex calls
|
||||
|
||||
## 0.9.3
|
||||
|
||||
* Allows `text-face-name` properties to be unquoted
|
||||
|
34
DEVELOPING.md
Normal file
34
DEVELOPING.md
Normal file
@ -0,0 +1,34 @@
|
||||
## Developing
|
||||
|
||||
Installing:
|
||||
|
||||
git clone git@github.com:mapbox/carto.git
|
||||
npm install
|
||||
|
||||
Test:
|
||||
|
||||
npm test
|
||||
|
||||
Running the head binary:
|
||||
|
||||
./bin/carto
|
||||
|
||||
## Documentation
|
||||
|
||||
This repository contains auto-generated documentation of the content of Carto
|
||||
that's published on Mapbox.com.
|
||||
|
||||
git fetch origin gh-pages:gh-pages
|
||||
|
||||
Edit `_docs/package.json` to point to the head version of [mapnik-reference](https://github.com/mapnik/mapnik-reference).
|
||||
|
||||
cd _docs
|
||||
npm install
|
||||
node generate.js
|
||||
|
||||
Then run up a directory and run the testing server:
|
||||
|
||||
cd ../
|
||||
jekyll serve -p 4000
|
||||
|
||||
Test the new site at `localhost:4000/carto` and if things look good then git add your changes and push.
|
17
Makefile
17
Makefile
@ -3,7 +3,15 @@
|
||||
#
|
||||
|
||||
expresso = ./node_modules/.bin/mocha
|
||||
docco = ./node_modules/.bin/docco
|
||||
UGLIFYJS=./node_modules/.bin/uglifyjs
|
||||
BROWSERIFY = ./node_modules/.bin/browserify
|
||||
|
||||
dist/carto.js: dist/carto.uncompressed.js $(shell $(BROWSERIFY) --list lib/carto/index.js)
|
||||
$(UGLIFYJS) dist/carto.uncompressed.js > $@
|
||||
|
||||
dist/carto.uncompressed.js: dist $(shell $(BROWSERIFY) --list lib/carto/index.js)
|
||||
$(BROWSERIFY) lib/carto/index.js --exclude node_modules/underscore/underscore.js --standalone carto > $@
|
||||
|
||||
|
||||
lint:
|
||||
./node_modules/.bin/jshint lib/carto/*.js lib/carto/tree/*.js
|
||||
@ -18,7 +26,10 @@ endif
|
||||
|
||||
check: test
|
||||
|
||||
doc:
|
||||
$(docco) lib/carto/*.js lib/carto/tree/*.js
|
||||
dist:
|
||||
mkdir -p dist
|
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: test
|
||||
|
372
README.md
372
README.md
@ -1,307 +1,83 @@
|
||||
# CartoCSS
|
||||
|
||||
[![Build Status](https://secure.travis-ci.org/mapbox/carto.png)](http://travis-ci.org/mapbox/carto)
|
||||
[![Build Status](https://travis-ci.org/CartoDB/carto.png?branch=master)](https://travis-ci.org/CartoDB/carto)
|
||||
|
||||
Is as stylesheet renderer for javascript, It's an evolution of the Mapnik renderer from Mapbox.
|
||||
Please, see original [Mapbox repo](http://github.com/mapbox/carto) for more information and credits
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
// shader is a CartoCSS object
|
||||
|
||||
var cartocss = [
|
||||
'#layer {',
|
||||
' marker-width: [property]',
|
||||
' marker-fill: red',
|
||||
'}'
|
||||
].join('')
|
||||
var shader = new carto.RendererJS().render(cartocss);
|
||||
var layers = shader.getLayers()
|
||||
for (var i = 0; i < layers.length; ++i) {
|
||||
var layer = layers[i];
|
||||
console.log("layer name: ", layer.fullName())
|
||||
console.log("- frames: ", layer.frames())
|
||||
console.log("- attachment: ", layer.attachment())
|
||||
|
||||
var layerShader = layer.getStyle({ property: 1 }, { zoom: 10 })
|
||||
console.log(layerShader['marker-width']) // 1
|
||||
console.log(layerShader['marker-fill']) // #FF0000
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
## RendererJS
|
||||
|
||||
### render(cartocss)
|
||||
|
||||
## CartoCSS
|
||||
|
||||
compiled cartocss object
|
||||
|
||||
### getLayers
|
||||
|
||||
return the layers, an array of ``CartoCSS.Layer`` object
|
||||
|
||||
### getDefault
|
||||
|
||||
return the default layer (``CartoCSS.Layer``), usually the Map layer
|
||||
|
||||
|
||||
### findLayer(where)
|
||||
|
||||
find a layer using where object.
|
||||
|
||||
```
|
||||
shader.findLayer({ name: 'test' })
|
||||
```
|
||||
|
||||
## CartoCSS.Layer
|
||||
|
||||
### getStyle(props, context)
|
||||
|
||||
return the evaluated style:
|
||||
- props: object containing properties needed to render the style. If the cartocss style uses
|
||||
some variables they should be passed in this object
|
||||
- context: rendering context variables like ``zoom`` or animation ``frame``
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Is a stylesheet renderer for Mapnik. It's an evolution of the
|
||||
[Cascadenik](https://github.com/mapnik/Cascadenik) idea and language,
|
||||
with an emphasis on speed and flexibility.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
* [mapbox.com/carto](http://mapbox.com/carto/)
|
||||
|
||||
## MML
|
||||
_incompatibility_
|
||||
|
||||
* MML files are assumed to be JSON, not XML. The files are near-identical
|
||||
to the XML files accepted by Cascadenik, just translated into JSON.
|
||||
* CartoCSS will not embed files or download URLs for you. Stylesheets should
|
||||
be embedded directly into your MML JSON and any datasources should be
|
||||
paths (relative or absolute) that would be acceptable in Mapnik XML.
|
||||
The [millstone project](https://github.com/mapbox/millstone) aims to fill this need.
|
||||
|
||||
CartoCSS MML:
|
||||
|
||||
{
|
||||
"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": [{"id":"style.mss","data":"Map {\n background-color: #fff;\n}\n\n#world {\n line-color: #ccc;\n line-width: 0.5;\n polygon-fill: #eee;\n}"}],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs",
|
||||
"Datasource": {
|
||||
"file": "world_borders",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
Cascadenik MML
|
||||
|
||||
<pre><Stylesheet><![CDATA[
|
||||
Map {
|
||||
map-bgcolor: #69f;
|
||||
}
|
||||
|
||||
Layer {
|
||||
line-width: 1;
|
||||
line-color: #696;
|
||||
polygon-fill: #6f9;
|
||||
}
|
||||
]]></Stylesheet>
|
||||
<Layer srs="+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs">
|
||||
<Datasource>
|
||||
<Parameter name="type">shape</Parameter>
|
||||
<Parameter name="file">world_borders</Parameter>
|
||||
</Datasource>
|
||||
</Layer>
|
||||
</Map></pre>
|
||||
|
||||
## Attachments and Instances
|
||||
_new_
|
||||
|
||||
In CSS, a certain object can only have one instance of a property. A `<div>` has a specific border width and color, rules that match better than others (#id instead of .class) override previous definitions. `CartoCSS` acts the same way normally for the sake of familiarity and organization, but Mapnik itself is more powerful.
|
||||
|
||||
Layers in Mapnik can have multiple [borders](http://trac.mapnik.org/wiki/LineSymbolizer) and multiple copies of other attributes. This ability is useful in drawing line outlines, like in the case of road borders or 'glow' effects around coasts. `CartoCSS` makes this accessible by allowing attachments to styles:
|
||||
|
||||
#world {
|
||||
line-color: #fff;
|
||||
line-width: 3;
|
||||
}
|
||||
|
||||
#world::outline {
|
||||
line-color: #000;
|
||||
line-width: 6;
|
||||
}
|
||||
|
||||
Attachments are optional: if you don't define them, CartoCSS does overriding of styles just like Cascadenik.
|
||||
|
||||
This brings us to another _incompatibility_: `line-inline` and `line-outline` have been removed from the language, because attachments are capable of the same trick.
|
||||
|
||||
While attachments allow creating implicit "layers" with the same data, using **instances** allows you to create multiple symbolizers in the same style/layer:
|
||||
|
||||
#roads {
|
||||
casing/line-width: 6;
|
||||
casing/line-color: #333;
|
||||
line-width: 4;
|
||||
line-color: #666;
|
||||
}
|
||||
|
||||
This makes Mapnik first draw the line of color #333 with a width of 6, and then immediately afterwards, it draws the same line again with width 4 and color #666. Contrast that to attachments: Mapnik would first draw all casings before proceeding to the actual lines.
|
||||
|
||||
## text-name
|
||||
_incompatibility_
|
||||
|
||||
Instead of the name attribute of the [TextSymbolizer](http://trac.mapnik.org/wiki/TextSymbolizer) and [ShieldSymbolizer](http://trac.mapnik.org/wiki/ShieldSymbolizer) being a part of the selector, it is a property of a rule. Thus the evaluation is less complex and one can use expressions in names.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>cascadenik</th>
|
||||
<th>CartoCSS</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign='top'>
|
||||
<pre>
|
||||
#world NAME {
|
||||
text-face-name: "Arial";
|
||||
}</pre>
|
||||
</td>
|
||||
<td valign='top'>
|
||||
<pre>
|
||||
#world {
|
||||
text-name: "NAME";
|
||||
text-face-name: "Arial";
|
||||
}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Mapnik2
|
||||
_new_
|
||||
|
||||
CartoCSS is only compatible with [Mapnik2](http://trac.mapnik.org/wiki/Mapnik2). Compatibility with Mapnik 0.7.x is not planned.
|
||||
|
||||
## Rasters and Buildings
|
||||
_new_
|
||||
|
||||
Rasters are supported in CartoCSS - it knows how to download `.vrt`, `.tiff`, and soon other raster formats, and the properties of the [RasterSymbolizer](http://trac.mapnik.org/wiki/RasterSymbolizer) are exposed in the language.
|
||||
|
||||
The [BuildingSymbolizer](http://trac.mapnik.org/wiki/BuildingSymbolizer) is also supported in `CartoCSS`. The code stores symbolizer types and properties in a JSON file (in `tree/reference.json`), so new Mapnik features can be quickly implemented here.
|
||||
|
||||
## Variables & Expressions
|
||||
_new_
|
||||
|
||||
CartoCSS inherits from its basis in [less.js](http://lesscss.org/) some new features in CSS. One can define variables in stylesheets, and use expressions to modify them.
|
||||
|
||||
@mybackground: #2B4D2D;
|
||||
|
||||
Map {
|
||||
background-color: @mybackground
|
||||
}
|
||||
|
||||
#world {
|
||||
polygon-fill: @mybackground + #222;
|
||||
line-color: darken(@mybackground, 10%);
|
||||
}
|
||||
|
||||
## Nested Styles
|
||||
_new_
|
||||
|
||||
CartoCSS also inherits nesting of rules from less.js.
|
||||
|
||||
/* Applies to all layers with .land class */
|
||||
.land {
|
||||
line-color: #ccc;
|
||||
line-width: 0.5;
|
||||
polygon-fill: #eee;
|
||||
/* Applies to #lakes.land */
|
||||
#lakes {
|
||||
polygon-fill: #000;
|
||||
}
|
||||
}
|
||||
|
||||
This can be a convenient way to group style changes by zoom level:
|
||||
|
||||
[zoom > 1] {
|
||||
/* Applies to all layers at zoom > 1 */
|
||||
polygon-gamma: 0.3;
|
||||
#world {
|
||||
polygon-fill: #323;
|
||||
}
|
||||
#lakes {
|
||||
polygon-fill: #144;
|
||||
}
|
||||
}
|
||||
|
||||
## FontSets
|
||||
|
||||
_new_
|
||||
|
||||
By defining multiple fonts in a `text-face-name` definition, you create [FontSets](http://trac.mapnik.org/wiki/FontSet) in `CartoCSS`. These are useful for supporting multiple character sets and fallback fonts for distributed styles.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>carto</th><th>XML</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign='top'>
|
||||
|
||||
<pre>#world {
|
||||
text-name: "[NAME]";
|
||||
text-size: 11;
|
||||
text-face-name: "Georgia Regular", "Arial Italic";
|
||||
}</pre>
|
||||
|
||||
</td>
|
||||
<td valign='top'>
|
||||
<pre><FontSet name="fontset-0">
|
||||
<Font face-name="Georgia Regular"/>
|
||||
<Font face-name="Arial Italic"/>
|
||||
</FontSet>
|
||||
<Style name="world-text">
|
||||
<Rule>
|
||||
<TextSymbolizer fontset-name="fontset-0"
|
||||
size="11"
|
||||
name="[NAME]"/>
|
||||
</Rule>
|
||||
</Style></pre>
|
||||
</td>
|
||||
<tr>
|
||||
</table>
|
||||
|
||||
## Filters
|
||||
|
||||
CartoCSS supports a variety of filter styles:
|
||||
|
||||
Numeric comparisons:
|
||||
|
||||
```
|
||||
#world[population > 100]
|
||||
#world[population < 100]
|
||||
#world[population >= 100]
|
||||
#world[population <= 100]
|
||||
```
|
||||
|
||||
General comparisons:
|
||||
|
||||
```
|
||||
#world[population = 100]
|
||||
#world[population != 100]
|
||||
```
|
||||
|
||||
|
||||
String comparisons:
|
||||
|
||||
```
|
||||
/* a regular expression over name */
|
||||
#world[name =~ "A.*"]
|
||||
```
|
||||
|
||||
## Developers
|
||||
|
||||
#### Installation
|
||||
|
||||
If you're using [TileMill](http://mapbox.com/tilemill/), you're already
|
||||
using CartoCSS and don't need to do a thing.
|
||||
|
||||
If you're a developer-type and want to use the `carto` binary with
|
||||
`node.js` (and you have [npm](http://npmjs.org/) installed),
|
||||
|
||||
npm install carto
|
||||
|
||||
#### From the binary
|
||||
|
||||
Install `millstone` to enable support for localizing external resources (URLs and local files) referenced in your mml file.
|
||||
|
||||
npm install millstone
|
||||
carto map_file.json
|
||||
|
||||
#### From code
|
||||
|
||||
Currently CartoCSS is designed to be invoked from [node.js](http://nodejs.org/).
|
||||
The `Renderer` interface is the main API for developers, and it takes an MML file as a string as input.
|
||||
|
||||
// defined variables:
|
||||
// - input (the name or identifier of the file being parsed)
|
||||
// - data (a string containing the MML or an object of MML)
|
||||
var carto = require('carto');
|
||||
|
||||
new carto.Renderer({
|
||||
filename: input,
|
||||
local_data_dir: path.dirname(input),
|
||||
}).render(data, function(err, output) {
|
||||
if (err) {
|
||||
if (Array.isArray(err)) {
|
||||
err.forEach(function(e) {
|
||||
carto.writeError(e, options);
|
||||
});
|
||||
} else { throw err; }
|
||||
} else {
|
||||
sys.puts(output);
|
||||
}
|
||||
});
|
||||
|
||||
### Vim
|
||||
|
||||
To install, download or clone this repository, then add the `vim-carto`
|
||||
directory located at `build/vim-carto` to your `~/.vim` file.
|
||||
|
||||
## Credits
|
||||
|
||||
CartoCSS is based on [less.js](https://github.com/cloudhead/less.js), a CSS compiler written by Alexis Sellier.
|
||||
|
||||
It depends on:
|
||||
|
||||
* [underscore.js](https://github.com/documentcloud/underscore/)
|
||||
|
||||
Only for running tests:
|
||||
|
||||
* [mocha](https://github.com/visionmedia/mocha)
|
||||
* [sax-js](https://github.com/isaacs/sax-js/)
|
||||
|
||||
## Authors
|
||||
|
||||
* Tom MacWright (tmcw)
|
||||
* Konstantin Käfer (kkaefer)
|
||||
* AJ Ashton (ajashton)
|
||||
* Dane Springmeyer (springmeyer)
|
||||
|
206
bin/carto
206
bin/carto
@ -1,55 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var carto = require('carto');
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
carto = require('../lib/carto'),
|
||||
url = require('url'),
|
||||
_ = require('underscore');
|
||||
|
||||
var url = require('url');
|
||||
var _ = require('underscore');
|
||||
var existsSync = require('fs').existsSync || require('path').existsSync
|
||||
|
||||
var args = process.argv.slice(1);
|
||||
var options = { nosymlink:false };
|
||||
var optimist = require('optimist')
|
||||
.usage("Usage: $0 <source MML file>")
|
||||
.options('h', {alias:'help', describe:'Display this help message'})
|
||||
.options('v', {alias:'version', boolean:true, describe:'Display version information'})
|
||||
.options('b', {alias:'benchmark', boolean:true, describe:'Outputs total compile time'})
|
||||
.options('l', {alias:'localize', boolean:true, default:false, describe:'Use millstone to localize resources when loading an MML'})
|
||||
.options('n', {alias:'nosymlink', boolean:true, describe:'Use absolute paths instead of symlinking files'})
|
||||
.options('ppi', {describe:'Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels', default:90.714});
|
||||
|
||||
args = args.filter(function (arg) {
|
||||
var match;
|
||||
var options = optimist.argv;
|
||||
|
||||
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
|
||||
else { return arg }
|
||||
if (options.help) {
|
||||
optimist.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case 'v':
|
||||
case 'version':
|
||||
util.puts("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
|
||||
process.exit(0);
|
||||
break;
|
||||
case 'b':
|
||||
case 'benchmark':
|
||||
options.benchmark = true;
|
||||
break;
|
||||
case 'n':
|
||||
case 'nosymlink':
|
||||
options.nosymlink = true;
|
||||
break;
|
||||
default:
|
||||
util.puts("Usage: carto <source MML file>");
|
||||
util.puts("Options:");
|
||||
util.puts(" -v --version Parse JSON map manifest");
|
||||
util.puts(" -b --benchmark Outputs total compile time");
|
||||
util.puts(" -n --nosymlink Use absolute paths instead of symlinking files");
|
||||
process.exit(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (options.version) {
|
||||
console.log("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
var input = args[1];
|
||||
var input = options._[0];
|
||||
if (input && input[0] != '/') {
|
||||
input = path.join(process.cwd(), input);
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
util.puts("carto: no input files");
|
||||
console.log("carto: no input files ('carto -h or --help' for help)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -60,48 +46,80 @@ if (options.benchmark) {
|
||||
var ext = path.extname(input);
|
||||
|
||||
if (!ext) {
|
||||
util.puts("carto: please pass either a .mml file or .mss file");
|
||||
console.log("carto: please pass either a .mml file or .mss file");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(input)) {
|
||||
util.puts("carto: file does not exist: '" + input + "'");
|
||||
console.log("carto: file does not exist: '" + input + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function compile(err, data) {
|
||||
if (err) throw err;
|
||||
function compileMML(err, data) {
|
||||
// force drain the millstone download pool now
|
||||
// to ensure we can exit without waiting
|
||||
if (options.localize && millstone.drainPool) {
|
||||
millstone.drainPool(function() {});
|
||||
}
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark
|
||||
}).render(data, function(err, output) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
process.exit(1);
|
||||
} else {
|
||||
if (!options.benchmark) {
|
||||
util.puts(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
}
|
||||
});
|
||||
var output = renderer.render(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
util.error(e.stack);
|
||||
console.error(e.stack);
|
||||
} else {
|
||||
util.error(e);
|
||||
console.error(e);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
function compileMSS(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: path.basename(input),
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
var output = renderer.renderMSS(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
var data = fs.readFileSync(input, 'utf-8');
|
||||
} catch(err) {
|
||||
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -109,48 +127,36 @@ if (ext == '.mml') {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch(err) {
|
||||
util.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var millstone = undefined;
|
||||
try {
|
||||
require.resolve('millstone');
|
||||
millstone = require('millstone');
|
||||
} catch (err) {
|
||||
util.puts('carto: Millstone not found. ' + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
millstone.resolve({
|
||||
mml: data,
|
||||
base: path.dirname(input),
|
||||
cache: path.join(path.dirname(input), 'cache'),
|
||||
nosymlink: options.nosymlink
|
||||
}, compile);
|
||||
} else if (ext == '.mss') {
|
||||
var env = _({}).defaults({
|
||||
benchmark: options.benchmark,
|
||||
validation_data: false,
|
||||
effects: []
|
||||
});
|
||||
var output = [];
|
||||
var parser = (carto.Parser(env)).parse(data);
|
||||
var rules = parser.toList(env);
|
||||
var inherited = carto.inheritRules(rules,env);
|
||||
var sorted = carto.sortStyles(inherited,env);
|
||||
_(sorted).each(function(rule) {
|
||||
var style = new carto.tree.Style('layer', rule.attachment, rule);
|
||||
if (style) {
|
||||
// env.effects can be modified by this call
|
||||
output.push(style.toXML(env));
|
||||
}
|
||||
});
|
||||
if (!options.benchmark) {
|
||||
util.puts(output.join('\n'));
|
||||
if (options.localize) {
|
||||
var millstone = undefined;
|
||||
try {
|
||||
require.resolve('millstone');
|
||||
millstone = require('millstone');
|
||||
} catch (err) {
|
||||
console.error('carto: Millstone not found, required if localizing stylesheet resources. ' + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
millstone.resolve({
|
||||
mml: data,
|
||||
base: path.dirname(input),
|
||||
cache: path.join(path.dirname(input), 'cache'),
|
||||
nosymlink: options.nosymlink
|
||||
}, compileMML);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
data.Stylesheet = data.Stylesheet.map(function(x) {
|
||||
if (typeof x !== 'string') {
|
||||
return { id: x, data: x.data }
|
||||
}
|
||||
return { id: x, data: fs.readFileSync(path.join(path.dirname(input), x), 'utf8') }
|
||||
});
|
||||
compileMML(null,data);
|
||||
}
|
||||
} else if (ext == '.mss') {
|
||||
compileMSS(null,data);
|
||||
} else {
|
||||
util.puts("carto: please pass either a .mml file or .mss file");
|
||||
console.log("carto: please pass either a .mml file or .mss file");
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var xml2js = require('xml2js'),
|
||||
fs = require('fs');
|
||||
|
||||
if (!process.argv[2]) {
|
||||
console.log('Please specify a XML file.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.readFile(process.argv[2], 'utf-8', function(err, data) {
|
||||
if (err) throw err;
|
||||
|
||||
// Replace entities.
|
||||
var entities = {};
|
||||
var match = data.match(/<!ENTITY([^>]|"([^"]|\\")*")+>/g)
|
||||
if (match != null) {
|
||||
match.forEach(function(entity) {
|
||||
var parts = entity.match(/^<!ENTITY\s+(\w+)\s+"(.+)">$/);
|
||||
entities['&' + parts[1] + ';'] = parts[2];
|
||||
});
|
||||
}
|
||||
data = data.replace(/&\w+;/g, function(entity) {
|
||||
return entities[entity];
|
||||
});
|
||||
|
||||
function addAttributes(obj) {
|
||||
if (obj['@']) for (var key in obj['@']) obj[key] = obj['@'][key];
|
||||
delete obj['@'];
|
||||
return obj;
|
||||
}
|
||||
|
||||
function simplifyExternal(obj) {
|
||||
if (obj.src) return obj.src;
|
||||
else return obj;
|
||||
}
|
||||
|
||||
var parser = new xml2js.Parser();
|
||||
parser.addListener('end', function(json) {
|
||||
console.log(JSON.stringify(json, function(key, value) {
|
||||
if (!key) {
|
||||
return addAttributes(value);
|
||||
}
|
||||
else if (key === 'Stylesheet') {
|
||||
if (Array.isArray(value)) return value.map(addAttributes).map(simplifyExternal);
|
||||
else return [ simplifyExternal(addAttributes(value)) ];
|
||||
}
|
||||
else if (key === 'Layer' || key === 'Stylesheet') {
|
||||
if (Array.isArray(value)) return value.map(addAttributes);
|
||||
else return [ addAttributes(value) ];
|
||||
}
|
||||
else if (key === 'Datasource') {
|
||||
value = addAttributes(value);
|
||||
value.Parameter.forEach(function(parameter) {
|
||||
value[parameter['@'].name] = parameter['#'];
|
||||
});
|
||||
delete value.Parameter;
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}, 4));
|
||||
});
|
||||
parser.parseString(data);
|
||||
});
|
7
build/header.js
Normal file
7
build/header.js
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// LESS - Leaner CSS v@VERSION
|
||||
// http://lesscss.org
|
||||
//
|
||||
// Copyright (c) 2010, Alexis Sellier
|
||||
// Licensed under the MIT license.
|
||||
//
|
4
dist/carto.js
vendored
Normal file
4
dist/carto.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7375
dist/carto.uncompressed.js
vendored
Normal file
7375
dist/carto.uncompressed.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
docs-generator/README.md
Normal file
15
docs-generator/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Generating CartoCSS docs
|
||||
|
||||
From the `docs-generator/` directory:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
$ node generate.js
|
||||
```
|
||||
|
||||
Will save docs to `docs/`.
|
23
docs-generator/generate.js
Normal file
23
docs-generator/generate.js
Normal file
@ -0,0 +1,23 @@
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
refs = require('mapnik-reference'),
|
||||
_ = require('underscore');
|
||||
|
||||
function tmpl(x) {
|
||||
return _.template(fs.readFileSync(path.join(__dirname, x), 'utf-8'));
|
||||
}
|
||||
|
||||
var index = tmpl('index._');
|
||||
var table = tmpl('symbolizers._');
|
||||
var versions = Object.keys(refs.version);
|
||||
|
||||
for (var v in refs.version) {
|
||||
var ref = refs.version[v];
|
||||
fs.writeFileSync(path.join(__dirname, '../docs/' + v + '.md'), index({
|
||||
symbolizers: ref.symbolizers,
|
||||
table: table,
|
||||
version: v,
|
||||
versions: versions,
|
||||
_: _
|
||||
}));
|
||||
}
|
144
docs-generator/index._
Normal file
144
docs-generator/index._
Normal file
@ -0,0 +1,144 @@
|
||||
# Carto documentation
|
||||
|
||||
The following is a list of properties provided in CartoCSS that you can apply to map elements.
|
||||
<%= table({symbolizers:symbolizers}) %>
|
||||
|
||||
### Values
|
||||
|
||||
Below is a list of values and an explanation of any expression that can be applied to properties in CartCSS.
|
||||
|
||||
### Color
|
||||
|
||||
CartoCSS accepts a variety of syntaxes for colors - HTML-style hex values, rgb, rgba, hsl, and hsla. It also supports the predefined HTML colors names, like `yellow` and `blue`.
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-color: #ff0;
|
||||
line-color: #ffff00;
|
||||
line-color: rgb(255, 255, 0);
|
||||
line-color: rgba(255, 255, 0, 1);
|
||||
line-color: hsl(100, 50%, 50%);
|
||||
line-color: hsla(100, 50%, 50%, 1);
|
||||
line-color: yellow;
|
||||
}
|
||||
```
|
||||
|
||||
Especially of note is the support for hsl, which can be [easier to reason about than rgb()](http://mothereffinghsl.com/). Carto also includes several color operation functions [borrowed from less](http://lesscss.org/functions/#color-operations):
|
||||
|
||||
``` css
|
||||
// lighten and darken colors
|
||||
lighten(#ace, 10%);
|
||||
darken(#ace, 10%);
|
||||
|
||||
// saturate and desaturate
|
||||
saturate(#550000, 10%);
|
||||
desaturate(#00ff00, 10%);
|
||||
|
||||
// increase or decrease the opacity of a color
|
||||
fadein(#fafafa, 10%);
|
||||
fadeout(#fefefe, 14%);
|
||||
|
||||
// spin rotates a color around the color wheel by degrees
|
||||
spin(#ff00ff, 10);
|
||||
|
||||
// mix generates a color in between two other colors.
|
||||
mix(#fff, #000, 50%);
|
||||
```
|
||||
|
||||
These functions all take arguments which can be color variables, literal colors, or the results of other functions operating on colors.
|
||||
|
||||
### Float
|
||||
|
||||
Float is a fancy way of saying 'number'. In CartoCSS, you specify _just a number_ - unlike CSS, there are no units, but everything is specified in pixels.
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-width: 2;
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible to do simple math with number values:
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-width: 4 / 2; // division
|
||||
line-width: 4 + 2; // addition
|
||||
line-width: 4 - 2; // subtraction
|
||||
line-width: 4 * 2; // multiplication
|
||||
line-width: 4 % 2; // modulus
|
||||
}
|
||||
```
|
||||
|
||||
### URI
|
||||
|
||||
URI is a fancy way of saying URL. When an argument is a URI, you use the same kind of `url('place.png')` notation that you would with HTML. Quotes around the URL aren't required, but are highly recommended. URIs can be paths to places on your computer, or on the internet.
|
||||
|
||||
```css
|
||||
#markers {
|
||||
marker-file: url('marker.png');
|
||||
}
|
||||
```
|
||||
|
||||
### String
|
||||
|
||||
A string is basically just text. In the case of CartoCSS, you're going to put it in quotes. Strings can be anything, though pay attention to the cases of `text-name` and `shield-name` - they actually will refer to features, which you refer to by putting them in brackets, as seen in the example below.
|
||||
|
||||
```css
|
||||
#labels {
|
||||
text-name: "[MY_FIELD]";
|
||||
}
|
||||
```
|
||||
|
||||
### Boolean
|
||||
|
||||
Boolean means yes or no, so it accepts the values `true` or `false`.
|
||||
|
||||
```css
|
||||
#markers {
|
||||
marker-allow-overlap:true;
|
||||
}
|
||||
```
|
||||
|
||||
### Expressions
|
||||
|
||||
Expressions are statements that can include fields, numbers, and other types in a really flexible way. You have run into expressions before, in the realm of 'fields', where you'd specify `"[FIELD]"`, but expressions allow you to drop the quotes and also do quick addition, division, multiplication, and concatenation from within Carto syntax.
|
||||
|
||||
```css
|
||||
#buildings {
|
||||
building-height: [HEIGHT_FIELD] * 10;
|
||||
}
|
||||
```
|
||||
|
||||
### Numbers
|
||||
Numbers are comma-separated lists of one or more number in a specific order. They're used in line dash arrays, in which the numbers specify intervals of line, break, and line again.
|
||||
|
||||
```css
|
||||
#disputedboundary {
|
||||
line-dasharray: 1, 4, 2;
|
||||
}
|
||||
```
|
||||
|
||||
### Percentages
|
||||
In Carto, the percentage symbol, `%` universally means `value/100`. It's meant to be used with ratio-related properties, like opacity rules.
|
||||
|
||||
_You should not use percentages as widths, heights, or other properties - unlike CSS, percentages are not relative to cascaded classes or page size, they're, as stated, simply the value divided by one hundred._
|
||||
|
||||
```css
|
||||
#world {
|
||||
// this syntax
|
||||
polygon-opacity: 50%;
|
||||
|
||||
// is equivalent to
|
||||
polygon-opacity: 0.5;
|
||||
}
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
Functions are comma-separated lists of one or more functions. For instance, transforms use the `functions` type to allow for transforms within Carto, which are optionally chainable.
|
||||
|
||||
```css
|
||||
#point {
|
||||
point-transform: scale(2, 2);
|
||||
}
|
||||
```
|
20
docs-generator/package.json
Normal file
20
docs-generator/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "carto-site",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Mapnik Stylesheet Compiler",
|
||||
"url": "https://github.com/mapbox/carto",
|
||||
"repositories": [{
|
||||
"type": "git",
|
||||
"url": "http://github.com/mapbox/carto.git"
|
||||
}],
|
||||
"author": {
|
||||
"name": "MapBox",
|
||||
"url": "http://mapbox.com/",
|
||||
"email": "info@mapbox.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"mapnik-reference": "5.0.x",
|
||||
"underscore": "~1.3.3"
|
||||
}
|
||||
}
|
13
docs-generator/symbolizers._
Normal file
13
docs-generator/symbolizers._
Normal file
@ -0,0 +1,13 @@
|
||||
<% _(symbolizers).each(function(symbolizer, name) { %>
|
||||
<% if (name == '*') { %>## All elements<% } else { %>## <%= name %><% } %>
|
||||
<% _(symbolizer).chain().filter(function(p) { return p.css; }).each(function(p) { %>
|
||||
##### <%= p.css.replace(/\s/g, '') %> <% if (_.isArray(p.type)) { %>`keyword`<% } else { %>`<%= p.type %>`<% } %>
|
||||
<% if (_.isArray(p.type)) { %><% _(p.type).each(function(type) { %>`<%= type %>`<% }); %><% } %>
|
||||
|
||||
<% if (typeof p['default-value'] !== '') { %>Default Value: <%= p['default-value'] %><% } %>
|
||||
<% if (p['default-meaning']) { %>_(<%- p['default-meaning'] %>)_<% } %>
|
||||
<% if (typeof p['range'] !== 'undefined') { %>Range: <%= '' + p['range'] %><% } %>
|
||||
<% if (p.doc) { %><%- p.doc%><% } %>
|
||||
* * *
|
||||
<% }); %>
|
||||
<% }); %>
|
1159
docs/2.0.0.md
Normal file
1159
docs/2.0.0.md
Normal file
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.1.md
Normal file
1159
docs/2.0.1.md
Normal file
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.2.md
Normal file
1159
docs/2.0.2.md
Normal file
File diff suppressed because it is too large
Load Diff
1486
docs/2.1.0.md
Normal file
1486
docs/2.1.0.md
Normal file
File diff suppressed because it is too large
Load Diff
1495
docs/2.1.1.md
Normal file
1495
docs/2.1.1.md
Normal file
File diff suppressed because it is too large
Load Diff
1687
docs/latest.md
Normal file
1687
docs/latest.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,17 +7,38 @@ tree.functions = {
|
||||
rgba: function (r, g, b, a) {
|
||||
var rgb = [r, g, b].map(function (c) { return number(c); });
|
||||
a = number(a);
|
||||
if (rgb.some(isNaN) || isNaN(a)) return null;
|
||||
return new tree.Color(rgb, a);
|
||||
},
|
||||
// Only require val
|
||||
stop: function (val) {
|
||||
var color, mode;
|
||||
if (arguments.length > 1) color = arguments[1];
|
||||
if (arguments.length > 2) mode = arguments[2];
|
||||
|
||||
return {
|
||||
is: 'tag',
|
||||
val: val,
|
||||
color: color,
|
||||
mode: mode,
|
||||
toString: function(env) {
|
||||
return '\n\t<stop value="' + val.ev(env) + '"' +
|
||||
(color ? ' color="' + color.ev(env) + '" ' : '') +
|
||||
(mode ? ' mode="' + mode.ev(env) + '" ' : '') +
|
||||
'/>';
|
||||
}
|
||||
};
|
||||
},
|
||||
hsl: function (h, s, l) {
|
||||
return this.hsla(h, s, l, 1.0);
|
||||
},
|
||||
hsla: function (h, s, l, a) {
|
||||
h = (number(h) % 360) / 360;
|
||||
s = number(s); l = number(l); a = number(a);
|
||||
if ([h, s, l, a].some(isNaN)) return null;
|
||||
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||
var m1 = l * 2 - m2;
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s,
|
||||
m1 = l * 2 - m2;
|
||||
|
||||
return this.rgba(hue(h + 1/3) * 255,
|
||||
hue(h) * 255,
|
||||
@ -33,18 +54,23 @@ tree.functions = {
|
||||
}
|
||||
},
|
||||
hue: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().h));
|
||||
},
|
||||
saturation: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().s * 100), '%');
|
||||
},
|
||||
lightness: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().l * 100), '%');
|
||||
},
|
||||
alpha: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(color.toHSL().a);
|
||||
},
|
||||
saturate: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s += amount.value / 100;
|
||||
@ -52,6 +78,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
desaturate: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s -= amount.value / 100;
|
||||
@ -59,6 +86,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
lighten: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l += amount.value / 100;
|
||||
@ -66,6 +94,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
darken: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l -= amount.value / 100;
|
||||
@ -73,6 +102,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadein: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a += amount.value / 100;
|
||||
@ -80,6 +110,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadeout: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a -= amount.value / 100;
|
||||
@ -87,6 +118,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
spin: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
var hue = (hsl.h + amount.value) % 360;
|
||||
|
||||
@ -154,8 +186,12 @@ tree.functions['agg-stack-blur'] = function(x, y) {
|
||||
return new tree.ImageFilter('agg-stack-blur', [x, y]);
|
||||
};
|
||||
|
||||
function hsla(hsla) {
|
||||
return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a);
|
||||
tree.functions['scale-hsla'] = function(h0,h1,s0,s1,l0,l1,a0,a1) {
|
||||
return new tree.ImageFilter('scale-hsla', [h0,h1,s0,s1,l0,l1,a0,a1]);
|
||||
};
|
||||
|
||||
function hsla(h) {
|
||||
return tree.functions.hsla(h.h, h.s, h.l, h.a);
|
||||
}
|
||||
|
||||
function number(n) {
|
||||
@ -164,7 +200,7 @@ function number(n) {
|
||||
} else if (typeof(n) === 'number') {
|
||||
return n;
|
||||
} else {
|
||||
throw new Error('Color functions take numbers as parameters.');
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
var util = require('util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var util = require('util'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
|
||||
function getVersion() {
|
||||
if (parseInt(process.version.split('.')[1]) > 4) {
|
||||
if (process.browser) {
|
||||
return require('../../package.json').version.split('.');
|
||||
} else if (parseInt(process.version.split('.')[1], 10) > 4) {
|
||||
return require('../../package.json').version.split('.');
|
||||
} else {
|
||||
// older node
|
||||
var package_json = JSON.parse(fs.readFileSync(path.join(__dirname,'../../package.json')))
|
||||
var package_json = JSON.parse(fs.readFileSync(path.join(__dirname,'../../package.json')));
|
||||
return package_json.version.split('.');
|
||||
}
|
||||
}
|
||||
@ -17,6 +20,8 @@ var carto = {
|
||||
Parser: require('./parser').Parser,
|
||||
Renderer: require('./renderer').Renderer,
|
||||
tree: require('./tree'),
|
||||
RendererJS: require('./renderer_js'),
|
||||
default_reference: require('./torque-reference'),
|
||||
|
||||
// @TODO
|
||||
writeError: function(ctx, options) {
|
||||
@ -48,7 +53,7 @@ var carto = {
|
||||
if (typeof(extract[2]) === 'string') {
|
||||
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
|
||||
}
|
||||
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
|
||||
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
|
||||
|
||||
message = options.indent + message + stylize(ctx.message, 'red');
|
||||
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
|
||||
@ -63,15 +68,34 @@ var carto = {
|
||||
}
|
||||
};
|
||||
|
||||
[ 'call', 'color', 'comment', 'definition', 'dimension',
|
||||
'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) {
|
||||
require('./tree/' + n);
|
||||
});
|
||||
|
||||
require('./tree/call');
|
||||
require('./tree/color');
|
||||
require('./tree/comment');
|
||||
require('./tree/definition');
|
||||
require('./tree/dimension');
|
||||
require('./tree/element');
|
||||
require('./tree/expression');
|
||||
require('./tree/filterset');
|
||||
require('./tree/filter');
|
||||
require('./tree/field');
|
||||
require('./tree/keyword');
|
||||
require('./tree/layer');
|
||||
require('./tree/literal');
|
||||
require('./tree/operation');
|
||||
require('./tree/quoted');
|
||||
require('./tree/imagefilter');
|
||||
require('./tree/reference');
|
||||
require('./tree/rule');
|
||||
require('./tree/ruleset');
|
||||
require('./tree/selector');
|
||||
require('./tree/style');
|
||||
require('./tree/url');
|
||||
require('./tree/value');
|
||||
require('./tree/variable');
|
||||
require('./tree/zoom');
|
||||
require('./tree/invalid');
|
||||
require('./tree/fontset');
|
||||
require('./tree/frame_offset');
|
||||
require('./functions');
|
||||
|
||||
for (var k in carto) { exports[k] = carto[k]; }
|
||||
@ -87,7 +111,6 @@ function stylize(str, style) {
|
||||
'red' : [31, 39],
|
||||
'grey' : [90, 39]
|
||||
};
|
||||
return '\033[' + styles[style][0] + 'm' + str +
|
||||
'\033[' + styles[style][1] + 'm';
|
||||
return '\x1B[' + styles[style][0] + 'm' + str +
|
||||
'\x1B[' + styles[style][1] + 'm';
|
||||
}
|
||||
|
||||
|
@ -1,46 +1,10 @@
|
||||
var carto, tree, _;
|
||||
var carto = exports,
|
||||
tree = require('./tree'),
|
||||
_ = global._ || require('underscore');
|
||||
|
||||
if (typeof(process) !== 'undefined') {
|
||||
carto = exports;
|
||||
tree = require('./tree');
|
||||
_ = require('underscore');
|
||||
} else {
|
||||
if (typeof(window.carto) === 'undefined') { window.carto = {}; }
|
||||
carto = window.carto;
|
||||
tree = window.carto.tree = {};
|
||||
}
|
||||
|
||||
// carto.js - parser
|
||||
//
|
||||
// A relatively straight-forward predictive parser.
|
||||
// There is no tokenization/lexing stage, the input is parsed
|
||||
// in one sweep.
|
||||
//
|
||||
// To make the parser fast enough to run in the browser, several
|
||||
// optimization had to be made:
|
||||
//
|
||||
// - Matching and slicing on a huge input is often cause of slowdowns.
|
||||
// The solution is to chunkify the input into smaller strings.
|
||||
// The chunks are stored in the `chunks` var,
|
||||
// `j` holds the current chunk index, and `current` holds
|
||||
// the index of the current chunk in relation to `input`.
|
||||
// This gives us an almost 4x speed-up.
|
||||
//
|
||||
// - In many cases, we don't need to match individual tokens;
|
||||
// for example, if a value doesn't hold any variables, operations
|
||||
// or dynamic references, the parser can effectively 'skip' it,
|
||||
// treating it as a literal.
|
||||
// An example would be '1px solid #000' - which evaluates to itself,
|
||||
// we don't need to know what the individual components are.
|
||||
// The drawback, of course is that you don't get the benefits of
|
||||
// syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
|
||||
// and a smaller speed-up in the code-gen.
|
||||
//
|
||||
//
|
||||
// Token matching is done with the `$` function, which either takes
|
||||
// a terminal string or regexp, or a non-terminal function to call.
|
||||
// It also takes care of moving all the indices forwards.
|
||||
|
||||
carto.Parser = function Parser(env) {
|
||||
var input, // LeSS input string
|
||||
i, // current index in `input`
|
||||
@ -58,29 +22,6 @@ carto.Parser = function Parser(env) {
|
||||
// have been imported through `@import`.
|
||||
var finish = function() {};
|
||||
|
||||
var imports = this.imports = {
|
||||
paths: env && env.paths || [], // Search paths, when importing
|
||||
queue: [], // Files which haven't been imported yet
|
||||
files: {}, // Holds the imported parse trees
|
||||
mime: env && env.mime, // MIME type of .carto files
|
||||
push: function(path, callback) {
|
||||
var that = this;
|
||||
this.queue.push(path);
|
||||
|
||||
//
|
||||
// Import a file asynchronously
|
||||
//
|
||||
carto.Parser.importer(path, this.paths, function(root) {
|
||||
that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
|
||||
that.files[path] = root; // Store the root
|
||||
|
||||
callback(root);
|
||||
|
||||
if (that.queue.length === 0) { finish(); } // Call `finish` if we're done importing
|
||||
}, env);
|
||||
}
|
||||
};
|
||||
|
||||
function save() {
|
||||
temp = chunks[j];
|
||||
memo = i;
|
||||
@ -104,17 +45,12 @@ carto.Parser = function Parser(env) {
|
||||
function $(tok) {
|
||||
var match, args, length, c, index, endIndex, k;
|
||||
|
||||
//
|
||||
// Non-terminal
|
||||
//
|
||||
if (tok instanceof Function) {
|
||||
return tok.call(parser.parsers);
|
||||
//
|
||||
// Terminal
|
||||
//
|
||||
// Either match a single character in the input,
|
||||
// or match a regexp in the current chunk (chunk[j]).
|
||||
//
|
||||
// Either match a single character in the input,
|
||||
// or match a regexp in the current chunk (chunk[j]).
|
||||
} else if (typeof(tok) === 'string') {
|
||||
match = input.charAt(i) === tok ? tok : null;
|
||||
length = 1;
|
||||
@ -162,14 +98,15 @@ carto.Parser = function Parser(env) {
|
||||
if (typeof(tok) === 'string') {
|
||||
return input.charAt(i) === tok;
|
||||
} else {
|
||||
if (tok.test(chunks[j])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return !!tok.test(chunks[j]);
|
||||
}
|
||||
}
|
||||
|
||||
function extractErrorLine(style, errorIndex) {
|
||||
return (style.slice(0, errorIndex).match(/\n/g) || '').length + 1;
|
||||
}
|
||||
|
||||
|
||||
// Make an error object from a passed set of properties.
|
||||
// Accepted properties:
|
||||
// - `message`: Text of the error message.
|
||||
@ -177,8 +114,9 @@ carto.Parser = function Parser(env) {
|
||||
// - `index`: Char. index where the error occurred.
|
||||
function makeError(err) {
|
||||
var einput;
|
||||
var errorTemplate;
|
||||
|
||||
_(err).defaults({
|
||||
_.defaults(err, {
|
||||
index: furthest,
|
||||
filename: env.filename,
|
||||
message: 'Parse error.',
|
||||
@ -192,11 +130,12 @@ carto.Parser = function Parser(env) {
|
||||
einput = input;
|
||||
}
|
||||
|
||||
err.line = (einput.slice(0, err.index).match(/\n/g) || '').length + 1;
|
||||
err.line = extractErrorLine(einput, err.index);
|
||||
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
|
||||
err.column++;
|
||||
}
|
||||
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
|
||||
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
|
||||
return new Error(errorTemplate(err));
|
||||
}
|
||||
|
||||
this.env = env = env || {};
|
||||
@ -204,9 +143,9 @@ carto.Parser = function Parser(env) {
|
||||
this.env.inputs = this.env.inputs || {};
|
||||
|
||||
// The Parser
|
||||
return parser = {
|
||||
parser = {
|
||||
|
||||
imports: imports,
|
||||
extractErrorLine: extractErrorLine,
|
||||
//
|
||||
// Parse an input string into an abstract syntax tree.
|
||||
// Throws an error on parse errors.
|
||||
@ -221,17 +160,19 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
var early_exit = false;
|
||||
|
||||
// Split the input into chunks.
|
||||
chunks = (function(chunks) {
|
||||
chunks = (function (chunks) {
|
||||
var j = 0,
|
||||
skip = /[^"'`\{\}\/]+/g,
|
||||
skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
|
||||
comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
|
||||
string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
|
||||
level = 0,
|
||||
match,
|
||||
chunk = chunks[0],
|
||||
inString;
|
||||
inParam;
|
||||
|
||||
chunker: for (var i = 0, c, cc; i < input.length; i++) {
|
||||
for (var i = 0, c, cc; i < input.length;) {
|
||||
skip.lastIndex = i;
|
||||
if (match = skip.exec(input)) {
|
||||
if (match.index === i) {
|
||||
@ -240,67 +181,65 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
}
|
||||
c = input.charAt(i);
|
||||
comment.lastIndex = i;
|
||||
comment.lastIndex = string.lastIndex = i;
|
||||
|
||||
if (!inString && c === '/') {
|
||||
if (match = string.exec(input)) {
|
||||
if (match.index === i) {
|
||||
i += match[0].length;
|
||||
chunk.push(match[0]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inParam && c === '/') {
|
||||
cc = input.charAt(i + 1);
|
||||
if (cc === '/' || cc === '*') {
|
||||
if (match = comment.exec(input)) {
|
||||
if (match.index === i) {
|
||||
i += match[0].length - 1;
|
||||
i += match[0].length;
|
||||
chunk.push(match[0]);
|
||||
c = input.charAt(i);
|
||||
continue chunker;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c === '{' && !inString) { level++;
|
||||
chunk.push(c);
|
||||
} else if (c === '}' && !inString) { level--;
|
||||
chunk.push(c);
|
||||
chunks[++j] = chunk = [];
|
||||
} else {
|
||||
if (c === '"' || c === "'" || c === '`') {
|
||||
if (! inString) {
|
||||
inString = c;
|
||||
} else {
|
||||
inString = inString === c ? false : inString;
|
||||
}
|
||||
}
|
||||
chunk.push(c);
|
||||
switch (c) {
|
||||
case '{': if (! inParam) { level ++; chunk.push(c); break; }
|
||||
case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break; }
|
||||
case '(': if (! inParam) { inParam = true; chunk.push(c); break; }
|
||||
case ')': if ( inParam) { inParam = false; chunk.push(c); break; }
|
||||
default: chunk.push(c);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
if (level > 0) {
|
||||
if (inString) {
|
||||
// TODO: make invalid instead
|
||||
throw makeError({
|
||||
message: 'Missing closing ' + inString,
|
||||
index: i
|
||||
});
|
||||
} else {
|
||||
// TODO: make invalid instead
|
||||
throw makeError({
|
||||
message: 'Missing closing `}`',
|
||||
index: i
|
||||
});
|
||||
}
|
||||
if (level !== 0) {
|
||||
error = {
|
||||
index: i - 1,
|
||||
type: 'Parse',
|
||||
message: (level > 0) ? "missing closing `}`" : "missing opening `{`"
|
||||
};
|
||||
}
|
||||
|
||||
return chunks.map(function(c) { return c.join(''); });
|
||||
return chunks.map(function (c) { return c.join(''); });
|
||||
})([[]]);
|
||||
|
||||
if (error) {
|
||||
throw makeError(error);
|
||||
}
|
||||
|
||||
// Start with the primary rule.
|
||||
// The whole syntax tree is held under a Ruleset node,
|
||||
// with the `root` property set to true, so no `{}` are
|
||||
// output. The callback is called when the input is parsed.
|
||||
// output.
|
||||
root = new tree.Ruleset([], $(this.parsers.primary));
|
||||
root.root = true;
|
||||
|
||||
// Get an array of Ruleset objects, flattened
|
||||
// and sorted according to specificitySort
|
||||
root.toList = (function() {
|
||||
var line, lines, column;
|
||||
return function(env) {
|
||||
env.error = function(e) {
|
||||
if (!env.errors) env.errors = new Error('');
|
||||
@ -312,6 +251,7 @@ carto.Parser = function Parser(env) {
|
||||
};
|
||||
env.frames = env.frames || [];
|
||||
|
||||
|
||||
// call populates Invalid-caused errors
|
||||
var definitions = this.flatten([], [], env);
|
||||
definitions.sort(specificitySort);
|
||||
@ -336,23 +276,9 @@ carto.Parser = function Parser(env) {
|
||||
return bs[3] - as[3];
|
||||
};
|
||||
|
||||
// If `i` is smaller than the `input.length - 1`,
|
||||
// it means the parser wasn't able to parse the whole
|
||||
// string, so we've got a parsing error.
|
||||
//
|
||||
// We try to extract a \n delimited string,
|
||||
// showing the line where the parse error occured.
|
||||
// We split it up into two parts (the part which parsed,
|
||||
// and the part which didn't), so we can color them differently.
|
||||
if (i < input.length - 1) throw makeError({
|
||||
message:'Parse error.',
|
||||
index:i
|
||||
});
|
||||
|
||||
return root;
|
||||
},
|
||||
|
||||
//
|
||||
// Here in, the parsing rules/functions
|
||||
//
|
||||
// The basic structure of the syntax tree generated is as follows:
|
||||
@ -362,7 +288,6 @@ carto.Parser = function Parser(env) {
|
||||
// In general, most rules will try to parse a token with the `$()` function, and if the return
|
||||
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check
|
||||
// first, before parsing, that's when we use `peek()`.
|
||||
//
|
||||
parsers: {
|
||||
// The `primary` rule is the *entry* and *exit* point of the parser.
|
||||
// The rules here can appear at any level of the parse tree.
|
||||
@ -383,7 +308,7 @@ carto.Parser = function Parser(env) {
|
||||
while ((node = $(this.rule) || $(this.ruleset) ||
|
||||
$(this.comment)) ||
|
||||
$(/^[\s\n]+/) || (node = $(this.invalid))) {
|
||||
node && root.push(node);
|
||||
if (node) root.push(node);
|
||||
}
|
||||
return root;
|
||||
},
|
||||
@ -393,7 +318,7 @@ carto.Parser = function Parser(env) {
|
||||
|
||||
// To fail gracefully, match everything until a semicolon or linebreak.
|
||||
if (chunk) {
|
||||
return new(tree.Invalid)(chunk, memo);
|
||||
return new tree.Invalid(chunk, memo);
|
||||
}
|
||||
},
|
||||
|
||||
@ -412,15 +337,10 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Entities are tokens which can be found inside an Expression
|
||||
//
|
||||
entities: {
|
||||
//
|
||||
// A string, which supports escaping " and '
|
||||
//
|
||||
// "milky way" 'he\'s the one!'
|
||||
//
|
||||
|
||||
// A string, which supports escaping " and ' "milky way" 'he\'s the one!'
|
||||
quoted: function() {
|
||||
if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
|
||||
var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
|
||||
@ -429,15 +349,12 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
// A reference to a Mapnik field, like
|
||||
//
|
||||
// [NAME]
|
||||
//
|
||||
// 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\-_]+)/);
|
||||
var field_name = $(/(^[^\]]+)/);
|
||||
if (! $(']')) return;
|
||||
if (field_name) return new tree.Field(field_name[1]);
|
||||
},
|
||||
@ -450,21 +367,15 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
// A catch-all word, such as:
|
||||
//
|
||||
// hard-light
|
||||
//
|
||||
// These can start with either a letter or a dash (-),
|
||||
// and then contain numbers, underscores, and letters.
|
||||
// A catch-all word, such as: hard-light
|
||||
// These can start with either a letter or a dash (-),
|
||||
// and then contain numbers, underscores, and letters.
|
||||
keyword: function() {
|
||||
var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/);
|
||||
if (k) { return new tree.Keyword(k); }
|
||||
},
|
||||
|
||||
// A function call
|
||||
//
|
||||
// rgb(255, 0, 255)
|
||||
//
|
||||
// A function call like rgb(255, 0, 255)
|
||||
// The arguments are parsed with the `entities.arguments` parser.
|
||||
call: function() {
|
||||
var name, args;
|
||||
@ -474,6 +385,7 @@ carto.Parser = function Parser(env) {
|
||||
name = name[1];
|
||||
|
||||
if (name === 'url') {
|
||||
// url() is handled by the url parser instead
|
||||
return null;
|
||||
} else {
|
||||
i += name.length;
|
||||
@ -481,12 +393,12 @@ carto.Parser = function Parser(env) {
|
||||
|
||||
$('('); // Parse the '(' and consume whitespace.
|
||||
|
||||
args = $(this.entities.arguments);
|
||||
args = $(this.entities['arguments']);
|
||||
|
||||
if (!$(')')) return;
|
||||
|
||||
if (name) {
|
||||
return new tree.Call(name, args, i);
|
||||
return new tree.Call(name, args, i);
|
||||
}
|
||||
},
|
||||
// Arguments are comma-separated expressions
|
||||
@ -502,8 +414,9 @@ carto.Parser = function Parser(env) {
|
||||
},
|
||||
literal: function() {
|
||||
return $(this.entities.dimension) ||
|
||||
$(this.entities.color) ||
|
||||
$(this.entities.quoted);
|
||||
$(this.entities.keywordcolor) ||
|
||||
$(this.entities.hexcolor) ||
|
||||
$(this.entities.quoted);
|
||||
},
|
||||
|
||||
// Parse url() tokens
|
||||
@ -520,8 +433,9 @@ carto.Parser = function Parser(env) {
|
||||
if (! $(')')) {
|
||||
return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
|
||||
} else {
|
||||
return new tree.URL((value.value || value instanceof tree.Variable) ?
|
||||
value : new tree.Quoted(value), imports.paths);
|
||||
return new tree.URL((typeof value.value !== 'undefined' ||
|
||||
value instanceof tree.Variable) ?
|
||||
value : new tree.Quoted(value));
|
||||
}
|
||||
},
|
||||
|
||||
@ -539,41 +453,35 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
// A Hexadecimal color
|
||||
//
|
||||
// #4F3C2F
|
||||
//
|
||||
// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
|
||||
color: function() {
|
||||
hexcolor: function() {
|
||||
var rgb;
|
||||
|
||||
if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
|
||||
return new tree.Color(rgb[1]);
|
||||
} else {
|
||||
rgb = chunks[j].match(/^[a-z]+/);
|
||||
if (rgb && rgb[0] in tree.Reference.data.colors) {
|
||||
return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
keywordcolor: function() {
|
||||
var rgb = chunks[j].match(/^[a-z]+/);
|
||||
if (rgb && rgb[0] in tree.Reference.data.colors) {
|
||||
return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
|
||||
}
|
||||
},
|
||||
|
||||
// A Dimension, that is, a number and a unit. The only
|
||||
// unit that has an effect is %
|
||||
//
|
||||
// 0.5em 95%
|
||||
dimension: function() {
|
||||
var c = input.charCodeAt(i);
|
||||
if ((c > 57 || c < 45) || c === 47) return;
|
||||
var value = $(/^(-?\d*\.?\d+)(\%|\w+)?/);
|
||||
var value = $(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);
|
||||
if (value) {
|
||||
return new tree.Dimension(value[1], value[2], memo);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// The variable part of a variable definition. Used in the `rule` parser
|
||||
//
|
||||
// @fink:
|
||||
// The variable part of a variable definition.
|
||||
// Used in the `rule` parser. Like @fink:
|
||||
variable: function() {
|
||||
var name;
|
||||
|
||||
@ -582,10 +490,8 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Entities are the smallest recognized token,
|
||||
// and can be found inside a rule's value.
|
||||
//
|
||||
entity: function() {
|
||||
return $(this.entities.call) ||
|
||||
$(this.entities.literal) ||
|
||||
@ -618,15 +524,17 @@ carto.Parser = function Parser(env) {
|
||||
|
||||
// Selectors are made out of one or more Elements, see above.
|
||||
selector: function() {
|
||||
var a, attachment;
|
||||
var e, elements = [];
|
||||
var f, filters = new tree.Filterset();
|
||||
var z, zoom = tree.Zoom.all;
|
||||
var segments = 0, conditions = 0;
|
||||
var a, attachment,
|
||||
e, elements = [],
|
||||
f, filters = new tree.Filterset(),
|
||||
z, zooms = [],
|
||||
frame_offset = tree.FrameOffset.none;
|
||||
segments = 0, conditions = 0;
|
||||
|
||||
while (
|
||||
(e = $(this.element)) ||
|
||||
(z = $(this.zoom)) ||
|
||||
(fo = $(this.frame_offset)) ||
|
||||
(f = $(this.filter)) ||
|
||||
(a = $(this.attachment))
|
||||
) {
|
||||
@ -634,15 +542,24 @@ carto.Parser = function Parser(env) {
|
||||
if (e) {
|
||||
elements.push(e);
|
||||
} else if (z) {
|
||||
zoom &= z;
|
||||
zooms.push(z);
|
||||
conditions++;
|
||||
} else if (fo) {
|
||||
frame_offset = fo;
|
||||
conditions++;
|
||||
} else if (f) {
|
||||
filters.add(f);
|
||||
var err = filters.add(f);
|
||||
if (err) {
|
||||
throw makeError({
|
||||
message: err,
|
||||
index: i - 1
|
||||
});
|
||||
}
|
||||
conditions++;
|
||||
} else if (attachment) {
|
||||
throw makeError({
|
||||
message:'Encountered second attachment name.',
|
||||
index:i - 1
|
||||
message: 'Encountered second attachment name.',
|
||||
index: i - 1
|
||||
});
|
||||
} else {
|
||||
attachment = a;
|
||||
@ -653,7 +570,7 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
if (segments) {
|
||||
return new tree.Selector(filters, zoom, elements, attachment, conditions, memo);
|
||||
return new tree.Selector(filters, zooms, frame_offset, elements, attachment, conditions, memo);
|
||||
}
|
||||
},
|
||||
|
||||
@ -661,23 +578,54 @@ carto.Parser = function Parser(env) {
|
||||
save();
|
||||
var key, op, val;
|
||||
if (! $('[')) return;
|
||||
if (key = $(/^[a-zA-Z0-9\-_]+/) || $(this.entities.quoted) || $(this.entities.variable)) {
|
||||
if (key = $(/^[a-zA-Z0-9\-_]+/) ||
|
||||
$(this.entities.quoted) ||
|
||||
$(this.entities.variable) ||
|
||||
$(this.entities.keyword) ||
|
||||
$(this.entities.field)) {
|
||||
// TODO: remove at 1.0.0
|
||||
if (key instanceof tree.Quoted) {
|
||||
key = new tree.Field(key.toString());
|
||||
}
|
||||
if ((op = $(this.entities.comparison)) &&
|
||||
(val = $(this.entities.quoted) || $(this.entities.variable) || $(/^[\w\-\.]+/))) {
|
||||
if (! $(']')) return;
|
||||
(val = $(this.entities.quoted) ||
|
||||
$(this.entities.variable) ||
|
||||
$(this.entities.dimension) ||
|
||||
$(this.entities.keyword) ||
|
||||
$(this.entities.field))) {
|
||||
if (! $(']')) {
|
||||
throw makeError({
|
||||
message: 'Missing closing ] of filter.',
|
||||
index: memo - 1
|
||||
});
|
||||
}
|
||||
if (!key.is) key = new tree.Field(key);
|
||||
return new tree.Filter(key, op, val, memo, env.filename);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
frame_offset: function() {
|
||||
save();
|
||||
var op, val;
|
||||
if ($(/^\[\s*frame-offset/g) &&
|
||||
(op = $(this.entities.comparison)) &&
|
||||
(val = $(/^\d+/)) &&
|
||||
$(']')) {
|
||||
return tree.FrameOffset(op, val, memo);
|
||||
}
|
||||
},
|
||||
|
||||
zoom: function() {
|
||||
save();
|
||||
var op, val;
|
||||
if ($(/^\[zoom/g) &&
|
||||
if ($(/^\[\s*zoom/g) &&
|
||||
(op = $(this.entities.comparison)) &&
|
||||
(val = $(/^\d+/)) &&
|
||||
$(']')) {
|
||||
return tree.Zoom(op, val, memo);
|
||||
(val = $(this.entities.variable) || $(this.entities.dimension)) && $(']')) {
|
||||
return new tree.Zoom(op, val, memo);
|
||||
} else {
|
||||
// backtrack
|
||||
restore();
|
||||
}
|
||||
},
|
||||
|
||||
@ -698,11 +646,13 @@ carto.Parser = function Parser(env) {
|
||||
|
||||
while (s = $(this.selector)) {
|
||||
selectors.push(s);
|
||||
$(this.comment);
|
||||
while ($(this.comment)) {}
|
||||
if (! $(',')) { break; }
|
||||
$(this.comment);
|
||||
while ($(this.comment)) {}
|
||||
}
|
||||
if (s) {
|
||||
while ($(this.comment)) {}
|
||||
}
|
||||
if (s) $(this.comment);
|
||||
|
||||
if (selectors.length > 0 && (rules = $(this.block))) {
|
||||
if (selectors.length === 1 &&
|
||||
@ -718,6 +668,7 @@ carto.Parser = function Parser(env) {
|
||||
restore();
|
||||
}
|
||||
},
|
||||
|
||||
rule: function() {
|
||||
var name, value, c = input.charAt(i);
|
||||
save();
|
||||
@ -765,16 +716,32 @@ carto.Parser = function Parser(env) {
|
||||
if (! $(',')) { break; }
|
||||
}
|
||||
|
||||
if (expressions.length > 0) {
|
||||
if (expressions.length > 1) {
|
||||
return new tree.Value(expressions.map(function(e) {
|
||||
return e.value[0];
|
||||
}));
|
||||
} else if (expressions.length === 1) {
|
||||
return new tree.Value(expressions);
|
||||
}
|
||||
},
|
||||
// A sub-expression, contained by parenthensis
|
||||
sub: function() {
|
||||
var e;
|
||||
var e, expressions = [];
|
||||
|
||||
if ($('(') && (e = $(this.expression)) && $(')')) {
|
||||
return e;
|
||||
if ($('(')) {
|
||||
while (e = $(this.expression)) {
|
||||
expressions.push(e);
|
||||
if (! $(',')) { break; }
|
||||
}
|
||||
$(')');
|
||||
}
|
||||
|
||||
if (expressions.length > 1) {
|
||||
return new tree.Value(expressions.map(function(e) {
|
||||
return e.value[0];
|
||||
}));
|
||||
} else if (expressions.length === 1) {
|
||||
return new tree.Value(expressions);
|
||||
}
|
||||
},
|
||||
// This is a misnomer because it actually handles multiplication
|
||||
@ -806,16 +773,14 @@ carto.Parser = function Parser(env) {
|
||||
},
|
||||
|
||||
// Expressions either represent mathematical operations,
|
||||
// or white-space delimited Entities.
|
||||
//
|
||||
// 1px solid black
|
||||
// @var * 2
|
||||
// or white-space delimited Entities. @var * 2
|
||||
expression: function() {
|
||||
var e, delim, entities = [], d;
|
||||
|
||||
while (e = $(this.addition) || $(this.entity)) {
|
||||
entities.push(e);
|
||||
}
|
||||
|
||||
if (entities.length > 0) {
|
||||
return new tree.Expression(entities);
|
||||
}
|
||||
@ -826,5 +791,5 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
}
|
||||
};
|
||||
return parser;
|
||||
};
|
||||
|
||||
|
@ -1,44 +1,98 @@
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
var carto = require('./index');
|
||||
var tree = require('./tree');
|
||||
|
||||
carto.Renderer = function Renderer(env, options) {
|
||||
this.env = env || {};
|
||||
this.options = options || {};
|
||||
this.options.mapnik_version = this.options.mapnik_version || 'latest';
|
||||
this.options.mapnik_version = this.options.mapnik_version || '3.0.0';
|
||||
};
|
||||
|
||||
// Prepare a MML document (given as an object) into a
|
||||
// fully-localized XML file ready for Mapnik2 consumption
|
||||
//
|
||||
// - @param {String} str the JSON file as a string.
|
||||
// - @param {Object} env renderer environment options.
|
||||
carto.Renderer.prototype.render = function render(m, callback) {
|
||||
/**
|
||||
* Prepare a MSS document (given as an string) into a
|
||||
* XML Style fragment (mostly useful for debugging)
|
||||
*
|
||||
* @param {String} data the mss contents as a string.
|
||||
*/
|
||||
carto.Renderer.prototype.renderMSS = function render(data) {
|
||||
// effects is a container for side-effects, which currently
|
||||
// are limited to FontSets.
|
||||
var env = _(this.env).defaults({
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: []
|
||||
});
|
||||
|
||||
if (!tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
return callback(new Error("Could not set mapnik version to " + this.options.mapnik_version), null);
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
var styles = [];
|
||||
|
||||
if (env.benchmark) console.time('Parsing MSS');
|
||||
var parser = (carto.Parser(env)).parse(data);
|
||||
if (env.benchmark) console.timeEnd('Parsing MSS');
|
||||
|
||||
if (env.benchmark) console.time('Rule generation');
|
||||
var rule_list = parser.toList(env);
|
||||
if (env.benchmark) console.timeEnd('Rule generation');
|
||||
|
||||
if (env.benchmark) console.time('Rule inheritance');
|
||||
var rules = inheritDefinitions(rule_list, env);
|
||||
if (env.benchmark) console.timeEnd('Rule inheritance');
|
||||
|
||||
if (env.benchmark) console.time('Style sort');
|
||||
var sorted = sortStyles(rules,env);
|
||||
if (env.benchmark) console.timeEnd('Style sort');
|
||||
|
||||
if (env.benchmark) console.time('Total Style generation');
|
||||
for (var k = 0, rule, style_name; k < sorted.length; k++) {
|
||||
rule = sorted[k];
|
||||
style_name = 'style' + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
|
||||
styles.push(style_name);
|
||||
var bench_name = '\tStyle "'+style_name+'" (#'+k+') toXML';
|
||||
if (env.benchmark) console.time(bench_name);
|
||||
// env.effects can be modified by this call
|
||||
output.push(carto.tree.StyleXML(style_name, rule.attachment, rule, env));
|
||||
if (env.benchmark) console.timeEnd(bench_name);
|
||||
}
|
||||
if (env.benchmark) console.timeEnd('Total Style generation');
|
||||
if (env.errors) throw env.errors;
|
||||
return output.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare a MML document (given as an object) into a
|
||||
* fully-localized XML file ready for Mapnik2 consumption
|
||||
*
|
||||
* @param {String} m - the JSON file as a string.
|
||||
*/
|
||||
carto.Renderer.prototype.render = function render(m) {
|
||||
// effects is a container for side-effects, which currently
|
||||
// are limited to FontSets.
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: [],
|
||||
ppi: 90.714
|
||||
});
|
||||
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
|
||||
// Transform stylesheets into rulesets.
|
||||
var rulesets = _(m.Stylesheet).chain()
|
||||
// Transform stylesheets into definitions.
|
||||
var definitions = _.chain(m.Stylesheet)
|
||||
.map(function(s) {
|
||||
if (typeof s == 'string') {
|
||||
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
|
||||
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
|
||||
}
|
||||
// Passing the environment from stylesheet to stylesheet,
|
||||
// allows frames and effects to be maintained.
|
||||
env = _(env).extend({filename:s.id});
|
||||
env = _.extend(env, {filename:s.id});
|
||||
|
||||
// @TODO try/catch?
|
||||
var time = +new Date(),
|
||||
root = (carto.Parser(env)).parse(s.data);
|
||||
if (env.benchmark)
|
||||
@ -48,52 +102,65 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
.flatten()
|
||||
.value();
|
||||
|
||||
function appliesTo(name, classIndex) {
|
||||
return function(definition) {
|
||||
return definition.appliesTo(l.name, classIndex);
|
||||
};
|
||||
}
|
||||
|
||||
// Iterate through layers and create styles custom-built
|
||||
// for each of them, and apply those styles to the layers.
|
||||
m.Layer.forEach(function(l) {
|
||||
var styles, l, classIndex, rules, sorted, matching;
|
||||
for (var i = 0; i < m.Layer.length; i++) {
|
||||
l = m.Layer[i];
|
||||
styles = [];
|
||||
classIndex = {};
|
||||
|
||||
if (env.benchmark) console.warn('processing layer: ' + l.id);
|
||||
l.styles = [];
|
||||
// Classes are given as space-separated alphanumeric strings.
|
||||
var classes = (l['class'] || '').split(/\s+/g);
|
||||
var matching = rulesets.filter(function(definition) {
|
||||
return definition.appliesTo(l.name, classes);
|
||||
});
|
||||
var rules = inheritRules(matching, env);
|
||||
var sorted = sortStyles(rules, env);
|
||||
_(sorted).each(function(rule) {
|
||||
var style = new tree.Style(l.name, rule.attachment, rule);
|
||||
if (style) {
|
||||
l.styles.push(style.name);
|
||||
|
||||
// env.effects can be modified by this call
|
||||
output.push(style.toXML(env));
|
||||
}
|
||||
});
|
||||
if (l.styles.length) {
|
||||
output.push((new carto.tree.Layer(l)).toXML());
|
||||
for (var j = 0; j < classes.length; j++) {
|
||||
classIndex[classes[j]] = true;
|
||||
}
|
||||
});
|
||||
matching = definitions.filter(appliesTo(l.name, classIndex));
|
||||
rules = inheritDefinitions(matching, env);
|
||||
sorted = sortStyles(rules, env);
|
||||
|
||||
for (var k = 0, rule, style_name; k < sorted.length; k++) {
|
||||
rule = sorted[k];
|
||||
style_name = l.name + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
|
||||
|
||||
// env.effects can be modified by this call
|
||||
var styleXML = carto.tree.StyleXML(style_name, rule.attachment, rule, env);
|
||||
|
||||
if (styleXML) {
|
||||
output.push(styleXML);
|
||||
styles.push(style_name);
|
||||
}
|
||||
}
|
||||
|
||||
output.push(carto.tree.LayerXML(l, styles));
|
||||
}
|
||||
|
||||
output.unshift(env.effects.map(function(e) {
|
||||
return e.toXML(env);
|
||||
}).join('\n'));
|
||||
|
||||
var map_properties;
|
||||
try {
|
||||
map_properties = getMapProperties(m, rulesets, env);
|
||||
} catch (err) {
|
||||
env.error(err);
|
||||
return callback(err);
|
||||
}
|
||||
var map_properties = getMapProperties(m, definitions, env);
|
||||
|
||||
// Exit on errors.
|
||||
if (env.errors) return callback(env.errors);
|
||||
if (env.errors) throw env.errors;
|
||||
|
||||
// Pass TileJSON and other custom parameters through to Mapnik XML.
|
||||
var parameters = _(m).reduce(function(memo, v, k) {
|
||||
var parameters = _.reduce(m, function(memo, v, k) {
|
||||
if (!v && v !== 0) return memo;
|
||||
|
||||
switch (k) {
|
||||
// Known skippable properties.
|
||||
case 'srs':
|
||||
case 'Layer':
|
||||
case 'Stylesheet':
|
||||
break;
|
||||
// Non URL-bound TileJSON properties.
|
||||
case 'bounds':
|
||||
case 'center':
|
||||
@ -119,6 +186,16 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
memo.push(' <Parameter name="interactivity_layer">' + v.layer + '</Parameter>');
|
||||
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
|
||||
break;
|
||||
// Support any additional scalar properties.
|
||||
default:
|
||||
if ('string' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
|
||||
} else if ('number' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
} else if ('boolean' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
}
|
||||
break;
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
@ -128,28 +205,36 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
'\n</Parameters>\n'
|
||||
);
|
||||
|
||||
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
|
||||
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
|
||||
|
||||
output.unshift(
|
||||
'<?xml version="1.0" ' +
|
||||
'encoding="utf-8"?>\n' +
|
||||
'<!DOCTYPE Map[]>\n' +
|
||||
'<Map' + properties + ' maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">\n');
|
||||
'<Map' + properties +'>\n');
|
||||
output.push('</Map>');
|
||||
return callback(null, output.join('\n'));
|
||||
return output.join('\n');
|
||||
};
|
||||
|
||||
// This function currently modifies 'current'
|
||||
function addRules(current, definition, existing) {
|
||||
var newFilters = definition.filters;
|
||||
var newRules = definition.rules;
|
||||
var updatedFilters, clone, previous;
|
||||
/**
|
||||
* This function currently modifies 'current'
|
||||
* @param {Array} current current list of rules
|
||||
* @param {Object} definition a Definition object to add to the rules
|
||||
* @param {Object} byFilter an object/dictionary of existing filters. This is
|
||||
* actually keyed `attachment->filter`
|
||||
* @param {Object} env the current environment
|
||||
*/
|
||||
function addRules(current, definition, byFilter, env) {
|
||||
var newFilters = definition.filters,
|
||||
newRules = definition.rules,
|
||||
updatedFilters, clone, previous;
|
||||
|
||||
// The current definition might have been split up into
|
||||
// multiple definitions already.
|
||||
for (var k = 0; k < current.length; k++) {
|
||||
updatedFilters = current[k].filters.cloneWith(newFilters);
|
||||
if (updatedFilters) {
|
||||
previous = existing[updatedFilters];
|
||||
previous = byFilter[updatedFilters];
|
||||
if (previous) {
|
||||
// There's already a definition with those exact
|
||||
// filters. Add the current definitions' rules
|
||||
@ -166,32 +251,63 @@ function addRules(current, definition, existing) {
|
||||
// to make sure that in the next loop iteration, we're
|
||||
// not performing the same task for this element again,
|
||||
// hence the k++.
|
||||
byFilter[updatedFilters] = clone;
|
||||
current.splice(k, 0, clone);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
} else if (updatedFilters === null) {
|
||||
// if updatedFilters is null, then adding the filters doesn't
|
||||
// invalidate or split the selector, so we addRules to the
|
||||
// combined selector
|
||||
|
||||
// Filters can be added, but they don't change the
|
||||
// filters. This means we don't have to split the
|
||||
// definition.
|
||||
//
|
||||
// this is cloned here because of shared classes, see
|
||||
// sharedclass.mss
|
||||
current[k] = current[k].clone();
|
||||
current[k].addRules(newRules);
|
||||
}
|
||||
// if updatedFeatures is false, then the filters split the rule,
|
||||
// so they aren't the same inheritance chain
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
// Apply inherited styles from their ancestors to them.
|
||||
function inheritRules(definitions, env) {
|
||||
/**
|
||||
* Apply inherited styles from their ancestors to them.
|
||||
*
|
||||
* called either once per render (in the case of mss) or per layer
|
||||
* (for mml)
|
||||
*
|
||||
* @param {Object} definitions - a list of definitions objects
|
||||
* that contain .rules
|
||||
* @param {Object} env - the environment
|
||||
* @return {Array<Array>} an array of arrays is returned,
|
||||
* in which each array refers to a specific attachment
|
||||
*/
|
||||
function inheritDefinitions(definitions, env) {
|
||||
var inheritTime = +new Date();
|
||||
// definitions are ordered by specificity,
|
||||
// high (index 0) to low
|
||||
var byAttachment = {}, byFilter = {};
|
||||
var byAttachment = {},
|
||||
byFilter = {};
|
||||
var result = [];
|
||||
var current, previous, attachment;
|
||||
|
||||
// Evaluate the filters specified by each definition with the given
|
||||
// environment to correctly resolve variable references
|
||||
definitions.forEach(function(d) {
|
||||
d.filters.ev(env);
|
||||
});
|
||||
|
||||
for (var i = 0; i < definitions.length; i++) {
|
||||
|
||||
attachment = definitions[i].attachment;
|
||||
current = [definitions[i]];
|
||||
|
||||
if (!byAttachment[attachment]) {
|
||||
byAttachment[attachment] = [];
|
||||
byAttachment[attachment].attachment = attachment;
|
||||
@ -199,12 +315,11 @@ function inheritRules(definitions, env) {
|
||||
result.push(byAttachment[attachment]);
|
||||
}
|
||||
|
||||
current = [definitions[i]];
|
||||
// Iterate over all subsequent rules.
|
||||
for (var j = i + 1; j < definitions.length; j++) {
|
||||
if (definitions[j].attachment === attachment) {
|
||||
// Only inherit rules from the same attachment.
|
||||
current = addRules(current, definitions[j], byFilter);
|
||||
current = addRules(current, definitions[j], byFilter[attachment], env);
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,49 +332,54 @@ function inheritRules(definitions, env) {
|
||||
if (env.benchmark) console.warn('Inheritance time: ' + ((new Date() - inheritTime)) + 'ms');
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// Sort styles by the minimum index of their rules.
|
||||
// This sorts a slice of the styles, so it returns a sorted
|
||||
// array but does not change the input.
|
||||
function sortStylesIndex(a, b) { return b.index - a.index; }
|
||||
function sortStyles(styles, env) {
|
||||
styles.forEach(function(style) {
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
var style = styles[i];
|
||||
style.index = Infinity;
|
||||
style.forEach(function(block) {
|
||||
block.rules.forEach(function(rule) {
|
||||
for (var b = 0; b < style.length; b++) {
|
||||
var rules = style[b].rules;
|
||||
for (var r = 0; r < rules.length; r++) {
|
||||
var rule = rules[r];
|
||||
if (rule.index < style.index) {
|
||||
style.index = rule.index;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = styles.slice();
|
||||
result.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
});
|
||||
result.sort(sortStylesIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find a rule like Map { background-color: #fff; },
|
||||
// if any, and return a list of properties to be inserted
|
||||
// into the <Map element of the resulting XML. Translates
|
||||
// properties of the mml object at `m` directly into XML
|
||||
// properties.
|
||||
//
|
||||
// - @param {Object} m the mml object.
|
||||
// - @param {Array} rulesets the output of toList.
|
||||
// - @param {Object} env.
|
||||
// - @return {String} rendered properties.
|
||||
function getMapProperties(m, rulesets, env) {
|
||||
/**
|
||||
* Find a rule like Map { background-color: #fff; },
|
||||
* if any, and return a list of properties to be inserted
|
||||
* into the <Map element of the resulting XML. Translates
|
||||
* properties of the mml object at `m` directly into XML
|
||||
* properties.
|
||||
*
|
||||
* @param {Object} m the mml object.
|
||||
* @param {Array} definitions the output of toList.
|
||||
* @param {Object} env
|
||||
* @return {String} rendered properties.
|
||||
*/
|
||||
function getMapProperties(m, definitions, env) {
|
||||
var rules = {};
|
||||
var symbolizers = tree.Reference.data.symbolizers.map;
|
||||
var symbolizers = carto.tree.Reference.data.symbolizers.map;
|
||||
|
||||
_(m).each(function(value, key) {
|
||||
if (key in symbolizers) rules[key] = key + '="' + value + '"';
|
||||
});
|
||||
|
||||
rulesets.filter(function(r) {
|
||||
definitions.filter(function(r) {
|
||||
return r.elements.join('') === 'Map';
|
||||
}).forEach(function(r) {
|
||||
for (var i = 0; i < r.rules.length; i++) {
|
||||
@ -270,7 +390,7 @@ function getMapProperties(m, rulesets, env) {
|
||||
index: r.rules[i].index
|
||||
});
|
||||
}
|
||||
rules[key] = r.rules[i].eval(env).toXML(env);
|
||||
rules[key] = r.rules[i].ev(env).toXML(env);
|
||||
}
|
||||
});
|
||||
return rules;
|
||||
@ -278,5 +398,5 @@ function getMapProperties(m, rulesets, env) {
|
||||
|
||||
module.exports = carto;
|
||||
module.exports.addRules = addRules;
|
||||
module.exports.inheritRules = inheritRules;
|
||||
module.exports.inheritDefinitions = inheritDefinitions;
|
||||
module.exports.sortStyles = sortStyles;
|
||||
|
302
lib/carto/renderer_js.js
Normal file
302
lib/carto/renderer_js.js
Normal file
@ -0,0 +1,302 @@
|
||||
(function(carto) {
|
||||
var tree = require('./tree');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
|
||||
function CartoCSS(style, options) {
|
||||
this.options = options || {};
|
||||
this.imageURLs = [];
|
||||
if(style) {
|
||||
this.setStyle(style);
|
||||
}
|
||||
}
|
||||
|
||||
CartoCSS.Layer = function(shader, options) {
|
||||
this.options = options;
|
||||
this.shader = shader;
|
||||
};
|
||||
|
||||
|
||||
CartoCSS.Layer.prototype = {
|
||||
|
||||
fullName: function() {
|
||||
return this.shader.attachment;
|
||||
},
|
||||
|
||||
name: function() {
|
||||
return this.fullName().split('::')[0];
|
||||
},
|
||||
|
||||
// frames this layer need to be rendered
|
||||
frames: function() {
|
||||
return this.shader.frames;
|
||||
},
|
||||
|
||||
attachment: function() {
|
||||
return this.fullName().split('::')[1];
|
||||
},
|
||||
|
||||
eval: function(prop) {
|
||||
var p = this.shader[prop];
|
||||
if (!p || !p.style) return;
|
||||
return p.style({}, { zoom: 0, 'frame-offset': 0 });
|
||||
},
|
||||
|
||||
/*
|
||||
* `props`: feature properties
|
||||
* `context`: rendering properties, i.e zoom
|
||||
*/
|
||||
getStyle: function(props, context) {
|
||||
var style = {};
|
||||
for(var i in this.shader) {
|
||||
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
|
||||
style[i] = this.shader[i].style(props, context);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
/**
|
||||
* return the symbolizers that need to be rendered with
|
||||
* this style. The order is the rendering order.
|
||||
* @returns a list with 3 possible values 'line', 'marker', 'polygon'
|
||||
*/
|
||||
getSymbolizers: function() {
|
||||
return this.shader.symbolizers;
|
||||
},
|
||||
|
||||
/**
|
||||
* returns if the style varies with some feature property.
|
||||
* Useful to optimize rendering
|
||||
*/
|
||||
isVariable: function() {
|
||||
for(var i in this.shader) {
|
||||
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
|
||||
if (!this.shader[i].constant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getShader: function() {
|
||||
return this.shader;
|
||||
},
|
||||
|
||||
/**
|
||||
* returns true if a feature needs to be rendered
|
||||
*/
|
||||
filter: function(featureType, props, context) {
|
||||
for(var i in this.shader) {
|
||||
var s = this.shader[i](props, context);
|
||||
if(s) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
//
|
||||
// given a geoemtry type returns the transformed one acording the CartoCSS
|
||||
// For points there are two kind of types: point and sprite, the first one
|
||||
// is a circle, second one is an image sprite
|
||||
//
|
||||
// the other geometry types are the same than geojson (polygon, linestring...)
|
||||
//
|
||||
transformGeometry: function(type) {
|
||||
return type;
|
||||
},
|
||||
|
||||
transformGeometries: function(geojson) {
|
||||
return geojson;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CartoCSS.prototype = {
|
||||
|
||||
setStyle: function(style) {
|
||||
var layers = this.parse(style);
|
||||
if(!layers) {
|
||||
throw new Error(this.parse_env.errors);
|
||||
}
|
||||
this.layers = layers.map(function(shader) {
|
||||
return new CartoCSS.Layer(shader);
|
||||
});
|
||||
},
|
||||
|
||||
getLayers: function() {
|
||||
return this.layers;
|
||||
},
|
||||
|
||||
getDefault: function() {
|
||||
return this.findLayer({ attachment: '__default__' });
|
||||
},
|
||||
|
||||
findLayer: function(where) {
|
||||
return _.find(this.layers, function(value) {
|
||||
for (var key in where) {
|
||||
var v = value[key];
|
||||
if (typeof(v) === 'function') {
|
||||
v = v.call(value);
|
||||
}
|
||||
if (where[key] !== v) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
_createFn: function(ops) {
|
||||
var body = ops.join('\n');
|
||||
if(this.options.debug) console.log(body);
|
||||
return Function("data","ctx", "var _value = null; " + body + "; return _value; ");
|
||||
},
|
||||
|
||||
_compile: function(shader) {
|
||||
if(typeof shader === 'string') {
|
||||
shader = eval("(function() { return " + shader +"; })()");
|
||||
}
|
||||
this.shader_src = shader;
|
||||
for(var attr in shader) {
|
||||
var c = mapper[attr];
|
||||
if(c) {
|
||||
this.compiled[c] = eval("(function() { return shader[attr]; })();");
|
||||
}
|
||||
}
|
||||
},
|
||||
getImageURLs: function(){
|
||||
return this.imageURLs;
|
||||
},
|
||||
|
||||
parse: function(cartocss) {
|
||||
var parse_env = {
|
||||
frames: [],
|
||||
errors: [],
|
||||
error: function(obj) {
|
||||
this.errors.push(obj);
|
||||
}
|
||||
};
|
||||
this.parse_env = parse_env;
|
||||
|
||||
var ruleset = null;
|
||||
try {
|
||||
ruleset = (new carto.Parser(parse_env)).parse(cartocss);
|
||||
} catch(e) {
|
||||
// add the style.mss string to match the response from the server
|
||||
parse_env.errors.push(e.message);
|
||||
return;
|
||||
}
|
||||
if(ruleset) {
|
||||
|
||||
function defKey(def) {
|
||||
return def.elements[0] + "::" + def.attachment;
|
||||
}
|
||||
var defs = ruleset.toList(parse_env);
|
||||
defs.reverse();
|
||||
// group by elements[0].value::attachment
|
||||
var layers = {};
|
||||
for(var i = 0; i < defs.length; ++i) {
|
||||
var def = defs[i];
|
||||
var key = defKey(def);
|
||||
var layer = layers[key] = (layers[key] || {
|
||||
symbolizers: []
|
||||
});
|
||||
|
||||
for(var u = 0; u<def.rules.length; u++){
|
||||
var rule = def.rules[u];
|
||||
if(rule.name === "marker-file" || rule.name === "point-file"){
|
||||
var value = rule.value.value[0].value[0].value.value;
|
||||
this.imageURLs.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
layer.frames = [];
|
||||
layer.zoom = tree.Zoom.all;
|
||||
var props = def.toJS(parse_env);
|
||||
if (this.options.debug) console.log("props", props);
|
||||
for(var v in props) {
|
||||
var lyr = layer[v] = layer[v] || {
|
||||
constant: false,
|
||||
symbolizer: null,
|
||||
js: [],
|
||||
index: 0
|
||||
};
|
||||
// build javascript statements
|
||||
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
|
||||
// get symbolizer for prop
|
||||
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
|
||||
// serach the max index to know rendering order
|
||||
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
|
||||
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
|
||||
// True when the property is filtered.
|
||||
lyr.filtered = props[v][0].filtered;
|
||||
}
|
||||
}
|
||||
|
||||
var ordered_layers = [];
|
||||
if (this.options.debug) console.log(layers);
|
||||
|
||||
var done = {};
|
||||
for(var i = 0; i < defs.length; ++i) {
|
||||
var def = defs[i];
|
||||
|
||||
if (this.options.strict) {
|
||||
def.toXML(parse_env, {});
|
||||
if (parse_env.errors.message) {
|
||||
throw new Error(parse_env.errors.message);
|
||||
}
|
||||
}
|
||||
|
||||
var k = defKey(def);
|
||||
var layer = layers[k];
|
||||
if(!done[k]) {
|
||||
if(this.options.debug) console.log("**", k);
|
||||
for(var prop in layer) {
|
||||
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
|
||||
if(this.options.debug) console.log("*", prop);
|
||||
layer[prop].style = this._createFn(layer[prop].js);
|
||||
layer.symbolizers.push(layer[prop].symbolizer);
|
||||
layer.symbolizers = _.uniq(layer.symbolizers);
|
||||
}
|
||||
}
|
||||
layer.attachment = k;
|
||||
ordered_layers.push(layer);
|
||||
done[k] = true;
|
||||
}
|
||||
layer.zoom |= def.zoom;
|
||||
layer.frames.push(def.frame_offset);
|
||||
}
|
||||
|
||||
// uniq the frames
|
||||
for(i = 0; i < ordered_layers.length; ++i) {
|
||||
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
|
||||
}
|
||||
|
||||
return ordered_layers;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
carto.RendererJS = function (options) {
|
||||
this.options = options || {};
|
||||
this.options.mapnik_version = this.options.mapnik_version || 'latest';
|
||||
this.reference = this.options.reference || require('./torque-reference').version.latest;
|
||||
this.options.strict = this.options.hasOwnProperty('strict') ? this.options.strict : false;
|
||||
};
|
||||
|
||||
// Prepare a javascript object which contains the layers
|
||||
carto.RendererJS.prototype.render = function render(cartocss, callback) {
|
||||
tree.Reference.setData(this.reference);
|
||||
return new CartoCSS(cartocss, this.options);
|
||||
}
|
||||
|
||||
if(typeof(module) !== 'undefined') {
|
||||
module.exports = carto.RendererJS;
|
||||
}
|
||||
|
||||
|
||||
})(require('../carto'));
|
1942
lib/carto/torque-reference.js
Normal file
1942
lib/carto/torque-reference.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,11 @@
|
||||
/**
|
||||
* TODO: document this. What does this do?
|
||||
*/
|
||||
module.exports.find = function (obj, fun) {
|
||||
for (var i = 0, r; i < obj.length; i++) {
|
||||
if (r = fun.call(obj, obj[i])) { return r; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if(typeof(module) !== "undefined") {
|
||||
module.exports.find = function (obj, fun) {
|
||||
for (var i = 0, r; i < obj.length; i++) {
|
||||
if (r = fun.call(obj, obj[i])) { return r; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
@ -1,29 +1,23 @@
|
||||
(function(tree) {
|
||||
var _ = require('underscore');
|
||||
|
||||
var _ = global._ || require('underscore');
|
||||
tree.Call = function Call(name, args, index) {
|
||||
this.is = 'call';
|
||||
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Call.prototype = {
|
||||
//
|
||||
// When evaluating a function call,
|
||||
is: 'call',
|
||||
// When evuating a function call,
|
||||
// we either find the function in `tree.functions` [1],
|
||||
// in which case we call it, passing the evaluated arguments,
|
||||
// or we simply print it out as it appeared originally [2].
|
||||
//
|
||||
// The *functions.js* file contains the built-in functions.
|
||||
//
|
||||
// The reason why we evaluate the arguments, is in the case where
|
||||
// we try to pass a variable to a function, like: `saturate(@color)`.
|
||||
// The function should receive the value, not the variable.
|
||||
//
|
||||
'eval': function(env) {
|
||||
var args = this.args.map(function(a) { return a.eval(env); });
|
||||
'ev': function(env) {
|
||||
var args = this.args.map(function(a) { return a.ev(env); });
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (args[i].is === 'undefined') {
|
||||
@ -35,8 +29,18 @@ tree.Call.prototype = {
|
||||
}
|
||||
|
||||
if (this.name in tree.functions) {
|
||||
if (tree.functions[this.name].length === args.length) {
|
||||
return tree.functions[this.name].apply(tree.functions, args);
|
||||
if (tree.functions[this.name].length <= args.length) {
|
||||
var val = tree.functions[this.name].apply(tree.functions, args);
|
||||
if (val === null) {
|
||||
env.error({
|
||||
message: 'incorrect arguments given to ' + this.name + '()',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
return val;
|
||||
} else {
|
||||
env.error({
|
||||
message: 'incorrect number of arguments for ' + this.name +
|
||||
@ -51,9 +55,9 @@ tree.Call.prototype = {
|
||||
};
|
||||
}
|
||||
} else {
|
||||
var fn = tree.Reference.mapnikFunction(this.name);
|
||||
if (!fn) {
|
||||
var functions = tree.Reference.mapnikFunctions();
|
||||
var fn = tree.Reference.mapnikFunctions[this.name];
|
||||
if (fn === undefined) {
|
||||
var functions = _.pairs(tree.Reference.mapnikFunctions);
|
||||
// cheap closest, needs improvement.
|
||||
var name = this.name;
|
||||
var mean = functions.map(function(f) {
|
||||
@ -73,10 +77,13 @@ tree.Call.prototype = {
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
if (fn[1] !== args.length) {
|
||||
if (fn !== args.length &&
|
||||
!(Array.isArray(fn) && _.include(fn, args.length)) &&
|
||||
// support variable-arg functions like `colorize-alpha`
|
||||
fn !== -1) {
|
||||
env.error({
|
||||
message: 'function ' + this.name + '() takes ' +
|
||||
fn[1] + ' arguments and was given ' + args.length,
|
||||
fn + ' arguments and was given ' + args.length,
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
|
@ -1,16 +1,14 @@
|
||||
(function(tree) {
|
||||
//
|
||||
// RGB Colors - #ff0014, #eee
|
||||
//
|
||||
// can be initialized with a 3 or 6 char string or a 3 or 4 element
|
||||
// numerical array
|
||||
tree.Color = function Color(rgb, a) {
|
||||
//
|
||||
// The end goal here, is to parse the arguments
|
||||
// into an integer triplet, such as `128, 255, 0`
|
||||
//
|
||||
// This facilitates operations and conversions.
|
||||
//
|
||||
if (Array.isArray(rgb)) {
|
||||
this.rgb = rgb;
|
||||
this.rgb = rgb.slice(0, 3);
|
||||
} else if (rgb.length == 6) {
|
||||
this.rgb = rgb.match(/.{2}/g).map(function(c) {
|
||||
return parseInt(c, 16);
|
||||
@ -20,12 +18,19 @@ tree.Color = function Color(rgb, a) {
|
||||
return parseInt(c + c, 16);
|
||||
});
|
||||
}
|
||||
this.is = 'color';
|
||||
this.alpha = typeof(a) === 'number' ? a : 1;
|
||||
|
||||
if (typeof(a) === 'number') {
|
||||
this.alpha = a;
|
||||
} else if (rgb.length === 4) {
|
||||
this.alpha = rgb[3];
|
||||
} else {
|
||||
this.alpha = 1;
|
||||
}
|
||||
};
|
||||
|
||||
tree.Color.prototype = {
|
||||
eval: function() { return this; },
|
||||
is: 'color',
|
||||
'ev': function() { return this; },
|
||||
|
||||
// If we have some transparency, the only way to represent it
|
||||
// is via `rgba`. Otherwise, we use the hex representation,
|
||||
@ -49,7 +54,7 @@ tree.Color.prototype = {
|
||||
// channels will spill onto each other. Once we have
|
||||
// our result, in the form of an integer triplet,
|
||||
// we create a new Color node to hold the result.
|
||||
operate: function(op, other) {
|
||||
operate: function(env, op, other) {
|
||||
var result = [];
|
||||
|
||||
if (! (other instanceof tree.Color)) {
|
||||
@ -87,5 +92,4 @@ tree.Color.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -9,7 +9,7 @@ tree.Comment.prototype = {
|
||||
toString: function(env) {
|
||||
return '<!--' + this.value + '-->';
|
||||
},
|
||||
eval: function() { return this; }
|
||||
'ev': function() { return this; }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,18 +1,26 @@
|
||||
(function(tree) {
|
||||
var assert = require('assert');
|
||||
var assert = require('assert'),
|
||||
_ = global._ || require('underscore');
|
||||
|
||||
// A definition is the combination of a selector and rules, like
|
||||
// #foo {
|
||||
// polygon-opacity:1.0;
|
||||
// }
|
||||
//
|
||||
// The selector can have filters
|
||||
tree.Definition = function Definition(selector, rules) {
|
||||
this.elements = selector.elements;
|
||||
assert.ok(selector.filters instanceof tree.Filterset);
|
||||
this.rules = rules;
|
||||
this.ruleIndex = [];
|
||||
this.ruleIndex = {};
|
||||
for (var i = 0; i < this.rules.length; i++) {
|
||||
if ('zoom' in this.rules[i]) this.rules[i] = this.rules[i].clone();
|
||||
this.rules[i].zoom = selector.zoom;
|
||||
this.ruleIndex.push(this.rules[i].updateID());
|
||||
this.ruleIndex[this.rules[i].updateID()] = true;
|
||||
}
|
||||
this.filters = selector.filters;
|
||||
this.zoom = selector.zoom;
|
||||
this.frame_offset = selector.frame_offset;
|
||||
this.attachment = selector.attachment || '__default__';
|
||||
this.specificity = selector.specificity();
|
||||
};
|
||||
@ -29,7 +37,7 @@ tree.Definition.prototype.clone = function(filters) {
|
||||
if (filters) assert.ok(filters instanceof tree.Filterset);
|
||||
var clone = Object.create(tree.Definition.prototype);
|
||||
clone.rules = this.rules.slice();
|
||||
clone.ruleIndex = this.ruleIndex.slice();
|
||||
clone.ruleIndex = _.clone(this.ruleIndex);
|
||||
clone.filters = filters ? filters : this.filters.clone();
|
||||
clone.attachment = this.attachment;
|
||||
return clone;
|
||||
@ -40,9 +48,9 @@ tree.Definition.prototype.addRules = function(rules) {
|
||||
|
||||
// Add only unique rules.
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
if (this.ruleIndex.indexOf(rules[i].id) < 0) {
|
||||
if (!this.ruleIndex[rules[i].id]) {
|
||||
this.rules.push(rules[i]);
|
||||
this.ruleIndex.push(rules[i].id);
|
||||
this.ruleIndex[rules[i].id] = true;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
@ -50,24 +58,33 @@ tree.Definition.prototype.addRules = function(rules) {
|
||||
return added;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether this selector matches a given id
|
||||
* and array of classes, by determining whether
|
||||
* all elements it contains match.
|
||||
*/
|
||||
// Determine whether this selector matches a given id
|
||||
// and array of classes, by determining whether
|
||||
// all elements it contains match.
|
||||
tree.Definition.prototype.appliesTo = function(id, classes) {
|
||||
for (var i = 0; i < this.elements.length; i++) {
|
||||
if (!this.elements[i].matches(id, classes)) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0, l = this.elements.length; i < l; i++) {
|
||||
var elem = this.elements[i];
|
||||
if (!(elem.wildcard ||
|
||||
(elem.type === 'class' && classes[elem.clean]) ||
|
||||
(elem.type === 'id' && id === elem.clean))) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function symbolizerName(symbolizer) {
|
||||
function capitalize(str) { return str[1].toUpperCase(); }
|
||||
return symbolizer.charAt(0).toUpperCase() +
|
||||
symbolizer.slice(1).replace(/\-./, capitalize) + 'Symbolizer';
|
||||
}
|
||||
|
||||
// Get a simple list of the symbolizers, in order
|
||||
function symbolizerList(sym_order) {
|
||||
return sym_order.sort(function(a, b) { return a[1] - b[1]; })
|
||||
.map(function(v) { return v[0]; });
|
||||
}
|
||||
|
||||
tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
var xml = ' <Rule>\n';
|
||||
xml += tree.Zoom.toXML(zoom).join('');
|
||||
xml += this.filters.toXML(env);
|
||||
var xml = zoom.toXML(env).join('') + this.filters.toXML(env);
|
||||
|
||||
// Sort symbolizers by the index of their first property definition
|
||||
var sym_order = [], indexes = [];
|
||||
@ -80,20 +97,17 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
sym_order.push([key, min_idx]);
|
||||
}
|
||||
|
||||
// Get a simple list of the symbolizers, in order
|
||||
sym_order = sym_order.sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
}).map(function(v) {
|
||||
return v[0];
|
||||
});
|
||||
sym_order = symbolizerList(sym_order);
|
||||
var sym_count = 0;
|
||||
|
||||
for (var i = 0; i < sym_order.length; i++) {
|
||||
var attributes = symbolizers[sym_order[i]];
|
||||
var symbolizer = sym_order[i].split('/').pop();
|
||||
|
||||
|
||||
// Skip the magical * symbolizer which is used for universal properties
|
||||
// which are bubbled up to Style elements intead of Symbolizer elements.
|
||||
if (symbolizer === '*') continue;
|
||||
sym_count++;
|
||||
|
||||
var fail = tree.Reference.requiredProperties(symbolizer, attributes);
|
||||
if (fail) {
|
||||
@ -105,41 +119,43 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
});
|
||||
}
|
||||
|
||||
var name = symbolizer.charAt(0).toUpperCase() +
|
||||
symbolizer.slice(1).replace(/\-./, function(str) {
|
||||
return str[1].toUpperCase();
|
||||
}) + 'Symbolizer';
|
||||
var name = symbolizerName(symbolizer);
|
||||
|
||||
var selfclosing = true, tagcontent;
|
||||
xml += ' <' + name + ' ';
|
||||
for (var key in attributes) {
|
||||
for (var j in attributes) {
|
||||
if (symbolizer === 'map') env.error({
|
||||
message: 'Map properties are not permitted in other rules',
|
||||
index: attributes[key].index,
|
||||
filename: attributes[key].filename
|
||||
index: attributes[j].index,
|
||||
filename: attributes[j].filename
|
||||
});
|
||||
var x = tree.Reference.selector(attributes[key].name);
|
||||
var x = tree.Reference.selector(attributes[j].name);
|
||||
if (x && x.serialization && x.serialization === 'content') {
|
||||
selfclosing = false;
|
||||
tagcontent = attributes[key].eval(env).toXML(env, true);
|
||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
||||
} else if (x && x.serialization && x.serialization === 'tag') {
|
||||
selfclosing = false;
|
||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
||||
} else {
|
||||
xml += attributes[key].eval(env).toXML(env) + ' ';
|
||||
xml += attributes[j].ev(env).toXML(env) + ' ';
|
||||
}
|
||||
}
|
||||
if (selfclosing) {
|
||||
xml += '/>\n';
|
||||
} else {
|
||||
if (tagcontent.indexOf('<Format') != -1) {
|
||||
} else if (typeof tagcontent !== "undefined") {
|
||||
if (tagcontent.indexOf('<') != -1) {
|
||||
xml += '>' + tagcontent + '</' + name + '>\n';
|
||||
} else {
|
||||
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
xml += ' </Rule>\n';
|
||||
return xml;
|
||||
if (!sym_count || !xml) return '';
|
||||
return ' <Rule>\n' + xml + ' </Rule>\n';
|
||||
};
|
||||
|
||||
// Take a zoom range of zooms and 'i', the index of a rule in this.rules,
|
||||
// and finds all applicable symbolizers
|
||||
tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
|
||||
var symbolizers = {}, child;
|
||||
|
||||
@ -163,18 +179,18 @@ tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
|
||||
}
|
||||
};
|
||||
|
||||
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
|
||||
// scaledenominators that cover the whole range from the first to last bit set.
|
||||
// This algorithm can produces zoom ranges that may have holes. However,
|
||||
// when using the filter-mode="first", more specific zoom filters will always
|
||||
// end up before broader ranges. The filter-mode will pick those first before
|
||||
// resorting to the zoom range with the hole and stop processing further rules.
|
||||
tree.Definition.prototype.toXML = function(env, existing) {
|
||||
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
|
||||
// scaledenominators that cover the whole range from the first to last bit set.
|
||||
// This algorithm can produces zoom ranges that may have holes. However,
|
||||
// when using the filter-mode="first", more specific zoom filters will always
|
||||
// end up before broader ranges. The filter-mode will pick those first before
|
||||
// resorting to the zoom range with the hole and stop processing further rules.
|
||||
var filter = this.filters.toString();
|
||||
if (!(filter in existing)) existing[filter] = tree.Zoom.all;
|
||||
|
||||
var available = tree.Zoom.all, xml = '', zoom, symbolizers;
|
||||
var zooms = { available: tree.Zoom.all };
|
||||
var available = tree.Zoom.all, xml = '', zoom, symbolizers,
|
||||
zooms = { available: tree.Zoom.all };
|
||||
for (var i = 0; i < this.rules.length && available; i++) {
|
||||
zooms.rule = this.rules[i].zoom;
|
||||
if (!(existing[filter] & zooms.rule)) continue;
|
||||
@ -182,7 +198,8 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
||||
while (zooms.current = zooms.rule & available) {
|
||||
if (symbolizers = this.collectSymbolizers(zooms, i)) {
|
||||
if (!(existing[filter] & zooms.current)) continue;
|
||||
xml += this.symbolizersToXML(env, symbolizers, existing[filter] & zooms.current);
|
||||
xml += this.symbolizersToXML(env, symbolizers,
|
||||
(new tree.Zoom()).setZoom(existing[filter] & zooms.current));
|
||||
existing[filter] &= ~zooms.current;
|
||||
}
|
||||
}
|
||||
@ -191,4 +208,41 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
||||
return xml;
|
||||
};
|
||||
|
||||
tree.Definition.prototype.toJS = function(env) {
|
||||
var shaderAttrs = {};
|
||||
var frame_offset = this.frame_offset;
|
||||
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
|
||||
var filters = [zoomFilter];
|
||||
var originalFilters = this.filters.toJS(env);
|
||||
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
|
||||
var zoomFiltered = this.zoom !== tree.Zoom.all;
|
||||
|
||||
if (originalFilters) {
|
||||
filters.push(originalFilters);
|
||||
}
|
||||
|
||||
if (frame_offset) {
|
||||
filters.push('ctx["frame-offset"] === ' + frame_offset);
|
||||
}
|
||||
|
||||
_.each(this.rules, function (rule) {
|
||||
var exportedRule = {};
|
||||
|
||||
if (!rule instanceof tree.Rule) {
|
||||
throw new Error("Ruleset not supported");
|
||||
}
|
||||
|
||||
exportedRule.index = rule.index;
|
||||
exportedRule.symbolizer = rule.symbolizer;
|
||||
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
|
||||
exportedRule.constant = rule.value.ev(env).is !== 'field';
|
||||
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
|
||||
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
|
||||
shaderAttrs[rule.name].push(exportedRule);
|
||||
});
|
||||
|
||||
return shaderAttrs;
|
||||
};
|
||||
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,24 +1,54 @@
|
||||
(function(tree) {
|
||||
|
||||
var _ = global._ || require('underscore');
|
||||
//
|
||||
// A number with a unit
|
||||
//
|
||||
tree.Dimension = function Dimension(value, unit, index) {
|
||||
this.value = parseFloat(value);
|
||||
this.unit = unit || null;
|
||||
this.is = 'float';
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Dimension.prototype = {
|
||||
eval: function (env) {
|
||||
if (this.unit && ['px', '%'].indexOf(this.unit) === -1) {
|
||||
env.error({
|
||||
is: 'float',
|
||||
physical_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc'],
|
||||
screen_units: ['px', '%'],
|
||||
all_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc', 'px', '%'],
|
||||
densities: {
|
||||
m: 0.0254,
|
||||
mm: 25.4,
|
||||
cm: 2.54,
|
||||
pt: 72,
|
||||
pc: 6
|
||||
},
|
||||
ev: function (env) {
|
||||
if (this.unit && !_.contains(this.all_units, this.unit)) {
|
||||
env.error({
|
||||
message: "Invalid unit: '" + this.unit + "'",
|
||||
index: this.index
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
|
||||
// normalize units which are not px or %
|
||||
if (this.unit && _.contains(this.physical_units, this.unit)) {
|
||||
if (!env.ppi) {
|
||||
env.error({
|
||||
message: "ppi is not set, so metric units can't be used",
|
||||
index: this.index
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
// convert all units to inch
|
||||
// convert inch to px using ppi
|
||||
this.value = (this.value / this.densities[this.unit]) * env.ppi;
|
||||
this.unit = 'px';
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
round: function() {
|
||||
this.value = Math.round(this.value);
|
||||
return this;
|
||||
},
|
||||
toColor: function() {
|
||||
@ -31,16 +61,38 @@ tree.Dimension.prototype = {
|
||||
toString: function() {
|
||||
return this.value.toString();
|
||||
},
|
||||
operate: function(env, op, other) {
|
||||
if (this.unit === '%' && other.unit !== '%') {
|
||||
env.error({
|
||||
message: 'If two operands differ, the first must not be %',
|
||||
index: this.index
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
// In an operation between two Dimensions,
|
||||
// we default to the first Dimension's unit,
|
||||
// so `1px + 2em` will yield `3px`.
|
||||
// In the future, we could implement some unit
|
||||
// conversions such that `100cm + 10mm` would yield
|
||||
// `101cm`.
|
||||
operate: function(op, other) {
|
||||
if (this.unit !== '%' && other.unit === '%') {
|
||||
if (op === '*' || op === '/' || op === '%') {
|
||||
env.error({
|
||||
message: 'Percent values can only be added or subtracted from other values',
|
||||
index: this.index
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
return new tree.Dimension(tree.operate(op,
|
||||
this.value, this.value * other.value * 0.01),
|
||||
this.unit);
|
||||
}
|
||||
|
||||
//here the operands are either the same (% or undefined or px), or one is undefined and the other is px
|
||||
return new tree.Dimension(tree.operate(op, this.value, other.value),
|
||||
this.unit || other.unit);
|
||||
this.unit || other.unit);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,31 +3,28 @@
|
||||
// An element is an id or class selector
|
||||
tree.Element = function Element(value) {
|
||||
this.value = value.trim();
|
||||
if (this.value[0] === '#') {
|
||||
this.type = 'id';
|
||||
this.clean = this.value.replace(/^#/, '');
|
||||
}
|
||||
if (this.value[0] === '.') {
|
||||
this.type = 'class';
|
||||
this.clean = this.value.replace(/^\./, '');
|
||||
}
|
||||
if (this.value.indexOf('*') !== -1) {
|
||||
this.type = 'wildcard';
|
||||
}
|
||||
};
|
||||
|
||||
// Determine the 'specificity matrix' of this
|
||||
// specific selector
|
||||
tree.Element.prototype.specificity = function() {
|
||||
return [
|
||||
(this.value[0] == '#') ? 1 : 0, // a
|
||||
(this.value[0] == '.') ? 1 : 0 // b
|
||||
(this.type === 'id') ? 1 : 0, // a
|
||||
(this.type === 'class') ? 1 : 0 // b
|
||||
];
|
||||
};
|
||||
|
||||
tree.Element.prototype.toString = function() {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
// Determine whether this element matches an id or classes.
|
||||
// An element is a single id or class, or check whether the given
|
||||
// array of classes contains this, or the id is equal to this.
|
||||
//
|
||||
// Takes a plain string for id and plain strings in the array of
|
||||
// classes.
|
||||
tree.Element.prototype.matches = function(id, classes) {
|
||||
return (classes.indexOf(this.value.replace(/^\./, '')) !== -1) ||
|
||||
(this.value.replace(/^#/, '') === id) ||
|
||||
(this.value === '*');
|
||||
};
|
||||
tree.Element.prototype.toString = function() { return this.value; };
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -2,17 +2,17 @@
|
||||
|
||||
tree.Expression = function Expression(value) {
|
||||
this.value = value;
|
||||
this.is = 'expression';
|
||||
};
|
||||
|
||||
tree.Expression.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'expression',
|
||||
ev: function(env) {
|
||||
if (this.value.length > 1) {
|
||||
return new tree.Expression(this.value.map(function(e) {
|
||||
return e.eval(env);
|
||||
return e.ev(env);
|
||||
}));
|
||||
} else {
|
||||
return this.value[0].eval(env);
|
||||
return this.value[0].ev(env);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
tree.Field = function Field(content) {
|
||||
this.value = content || '';
|
||||
this.is = 'field';
|
||||
};
|
||||
|
||||
tree.Field.prototype = {
|
||||
is: 'field',
|
||||
toString: function() {
|
||||
return '[' + this.value + ']';
|
||||
},
|
||||
'eval': function() {
|
||||
'ev': function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
@ -1,32 +1,15 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Filter = function Filter(key, op, val, index, filename) {
|
||||
if (key.is) {
|
||||
this.key = key.value;
|
||||
this._key = key;
|
||||
} else {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.op = op;
|
||||
this.val = val;
|
||||
this.index = index;
|
||||
this.filename = filename;
|
||||
|
||||
if (val.is) {
|
||||
this.val = val.value;
|
||||
this._val = val;
|
||||
} else {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
if (ops[this.op][1] == 'numeric') {
|
||||
this.val = 1 * this.val;
|
||||
}
|
||||
|
||||
this.id = this.key + this.op + this.val;
|
||||
};
|
||||
|
||||
|
||||
// xmlsafe, numeric, suffix
|
||||
var ops = {
|
||||
'<': [' < ', 'numeric'],
|
||||
@ -38,15 +21,35 @@ var ops = {
|
||||
'=~': ['.match(', 'string', ')']
|
||||
};
|
||||
|
||||
tree.Filter.prototype.ev = function(env) {
|
||||
this.key = this.key.ev(env);
|
||||
this.val = this.val.ev(env);
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toXML = function(env) {
|
||||
if (this.val.eval) this._val = this.val.eval(env);
|
||||
if (this.key.eval) this._key = this.key.eval(env);
|
||||
if (this._key) var key = this._key.toString(false);
|
||||
if (this._val) var val = this._val.toString(this._val.is == 'string');
|
||||
if (tree.Reference.data.filter) {
|
||||
if (this.key.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.key.toString())) {
|
||||
env.error({
|
||||
message: this.key.toString() + ' is not a valid keyword in a filter expression',
|
||||
index: this.index,
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
if (this.val.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.val.toString())) {
|
||||
env.error({
|
||||
message: this.val.toString() + ' is not a valid keyword in a filter expression',
|
||||
index: this.index,
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
}
|
||||
var key = this.key.toString(false);
|
||||
var val = this.val.toString(this.val.is == 'string');
|
||||
|
||||
if (
|
||||
(ops[this.op][1] == 'numeric' && isNaN(this.val)) ||
|
||||
(ops[this.op][1] == 'string' && (val || this.val)[0] != "'")
|
||||
(ops[this.op][1] == 'numeric' && isNaN(val) && this.val.is !== 'field') ||
|
||||
(ops[this.op][1] == 'string' && (val)[0] != "'")
|
||||
) {
|
||||
env.error({
|
||||
message: 'Cannot use operator "' + this.op + '" with value ' + this.val,
|
||||
@ -55,7 +58,7 @@ tree.Filter.prototype.toXML = function(env) {
|
||||
});
|
||||
}
|
||||
|
||||
return '[' + (key || this.key) + ']' + ops[this.op][0] + '' + (val || this.val) + (ops[this.op][2] || '');
|
||||
return key + ops[this.op][0] + val + (ops[this.op][2] || '');
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toString = function() {
|
||||
|
@ -1,224 +1,270 @@
|
||||
var tree = require('../tree');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
tree.Filterset = function Filterset() {};
|
||||
tree.Filterset = function Filterset() {
|
||||
this.filters = {};
|
||||
};
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'toXML', {
|
||||
enumerable: false,
|
||||
value: function(env) {
|
||||
var filters = [];
|
||||
for (var id in this) {
|
||||
filters.push('(' + this[id].toXML(env).trim() + ')');
|
||||
}
|
||||
|
||||
if (filters.length) {
|
||||
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
tree.Filterset.prototype.toXML = function(env) {
|
||||
var filters = [];
|
||||
for (var id in this.filters) {
|
||||
filters.push('(' + this.filters[id].toXML(env).trim() + ')');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'toString', {
|
||||
enumerable: false,
|
||||
value: function() {
|
||||
var arr = [];
|
||||
for (var id in this) arr.push(this[id].id);
|
||||
arr.sort();
|
||||
return arr.join('\t');
|
||||
if (filters.length) {
|
||||
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'clone', {
|
||||
enumerable: false,
|
||||
value: function() {
|
||||
var clone = new tree.Filterset();
|
||||
for (var id in this) {
|
||||
clone[id] = this[id];
|
||||
}
|
||||
return clone;
|
||||
tree.Filterset.prototype.toString = function() {
|
||||
var arr = [];
|
||||
for (var id in this.filters) arr.push(this.filters[id].id);
|
||||
return arr.sort().join('\t');
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.ev = function(env) {
|
||||
for (var i in this.filters) {
|
||||
this.filters[i].ev(env);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.clone = function() {
|
||||
var clone = new tree.Filterset();
|
||||
for (var id in this.filters) {
|
||||
clone.filters[id] = this.filters[id];
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
// Note: other has to be a tree.Filterset.
|
||||
Object.defineProperty(tree.Filterset.prototype, 'cloneWith', {
|
||||
enumerable: false,
|
||||
value: function(other) {
|
||||
var additions;
|
||||
for (var id in other) {
|
||||
var status = this.addable(other[id]);
|
||||
if (status === false) {
|
||||
return false;
|
||||
tree.Filterset.prototype.cloneWith = function(other) {
|
||||
var additions = [];
|
||||
for (var id in other.filters) {
|
||||
var status = this.addable(other.filters[id]);
|
||||
// status is true, false or null. if it's null we don't fail this
|
||||
// clone nor do we add the filter.
|
||||
if (status === false) {
|
||||
return false;
|
||||
}
|
||||
if (status === true) {
|
||||
// Adding the filter will override another value.
|
||||
additions.push(other.filters[id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the other filters doesn't make this filterset invalid, but it
|
||||
// doesn't add anything to it either.
|
||||
if (!additions.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can successfully add all filters. Now clone the filterset and add the
|
||||
// new rules.
|
||||
var clone = new tree.Filterset();
|
||||
|
||||
// We can add the rules that are already present without going through the
|
||||
// add function as a Filterset is always in it's simplest canonical form.
|
||||
for (id in this.filters) {
|
||||
clone.filters[id] = this.filters[id];
|
||||
}
|
||||
|
||||
// Only add new filters that actually change the filter.
|
||||
while (id = additions.shift()) {
|
||||
clone.add(id);
|
||||
}
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.toJS = function(env) {
|
||||
var opMap = {
|
||||
'=': '==='
|
||||
};
|
||||
return _.map(this.filters, function(filter) {
|
||||
var op = filter.op;
|
||||
if(op in opMap) {
|
||||
op = opMap[op];
|
||||
}
|
||||
var val = filter.val;
|
||||
if(filter._val !== undefined) {
|
||||
val = filter._val.toString(true);
|
||||
}
|
||||
var attrs = "data";
|
||||
if (op === '=~') {
|
||||
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val) + ")";
|
||||
}
|
||||
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val);
|
||||
}).join(' && ');
|
||||
};
|
||||
|
||||
// Returns true when the new filter can be added, false otherwise.
|
||||
// It can also return null, and on the other side we test for === true or
|
||||
// false
|
||||
tree.Filterset.prototype.addable = function(filter) {
|
||||
var key = filter.key.toString(),
|
||||
value = filter.val.toString();
|
||||
|
||||
if (value.match(/^[0-9]+(\.[0-9]*)?$/)) value = parseFloat(value);
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
// if there is already foo= and we're adding foo=
|
||||
if (this.filters[key + '='] !== undefined) {
|
||||
if (this.filters[key + '='].val.toString() != value) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (status === true) {
|
||||
// Adding the filter will override another value.
|
||||
if (!additions) additions = [];
|
||||
additions.push(other[id]);
|
||||
if (this.filters[key + '!=' + value] !== undefined) return false;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
|
||||
return true;
|
||||
|
||||
case '=~':
|
||||
return true;
|
||||
|
||||
case '!=':
|
||||
if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null;
|
||||
if (this.filters[key + '!=' + value] !== undefined) return null;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '>':
|
||||
if (key + '=' in this.filters) {
|
||||
if (this.filters[key + '='].val <= value) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return false;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
|
||||
return true;
|
||||
|
||||
case '>=':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : null;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return null;
|
||||
return true;
|
||||
|
||||
case '<':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : null;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '<=':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : null;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return null;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Does the new filter constitute a conflict?
|
||||
tree.Filterset.prototype.conflict = function(filter) {
|
||||
var key = filter.key.toString(),
|
||||
value = filter.val.toString();
|
||||
|
||||
if (!isNaN(parseFloat(value))) value = parseFloat(value);
|
||||
|
||||
// if (a=b) && (a=c)
|
||||
// if (a=b) && (a!=b)
|
||||
// or (a!=b) && (a=b)
|
||||
if ((filter.op === '=' && this.filters[key + '='] !== undefined &&
|
||||
value != this.filters[key + '='].val.toString()) ||
|
||||
(filter.op === '!=' && this.filters[key + '='] !== undefined &&
|
||||
value == this.filters[key + '='].val.toString()) ||
|
||||
(filter.op === '=' && this.filters[key + '!='] !== undefined &&
|
||||
value == this.filters[key + '!='].val.toString())) {
|
||||
return filter.toString() + ' added to ' + this.toString() + ' produces an invalid filter';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Only call this function for filters that have been cleared by .addable().
|
||||
tree.Filterset.prototype.add = function(filter, env) {
|
||||
var key = filter.key.toString(),
|
||||
id,
|
||||
op = filter.op,
|
||||
conflict = this.conflict(filter),
|
||||
numval;
|
||||
|
||||
if (conflict) return conflict;
|
||||
|
||||
if (op === '=') {
|
||||
for (var i in this.filters) {
|
||||
if (this.filters[i].key == key) delete this.filters[i];
|
||||
}
|
||||
this.filters[key + '='] = filter;
|
||||
} else if (op === '!=') {
|
||||
this.filters[key + '!=' + filter.val] = filter;
|
||||
} else if (op === '=~') {
|
||||
this.filters[key + '=~' + filter.val] = filter;
|
||||
} else if (op === '>') {
|
||||
// If there are other filters that are also >
|
||||
// but are less than this one, they don't matter, so
|
||||
// remove them.
|
||||
for (var j in this.filters) {
|
||||
if (this.filters[j].key == key && this.filters[j].val <= filter.val) {
|
||||
delete this.filters[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the other filters doesn't make this filterset invalid, but it
|
||||
// doesn't add anything to it either.
|
||||
if (!additions) return null;
|
||||
|
||||
// We can successfully add all filters. Now clone the filterset and add the
|
||||
// new rules.
|
||||
var clone = new tree.Filterset();
|
||||
|
||||
// We can add the rules that are already present without going through the
|
||||
// add function as a Filterset is always in it's simplest canonical form.
|
||||
for (var id in this) {
|
||||
clone[id] = this[id];
|
||||
this.filters[key + '>'] = filter;
|
||||
} else if (op === '>=') {
|
||||
for (var k in this.filters) {
|
||||
numval = (+this.filters[k].val.toString());
|
||||
if (this.filters[k].key == key && numval < filter.val) {
|
||||
delete this.filters[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Only add new filters that actually change the filter.
|
||||
while (id = additions.shift()) {
|
||||
clone.add(id);
|
||||
if (this.filters[key + '!=' + filter.val] !== undefined) {
|
||||
delete this.filters[key + '!=' + filter.val];
|
||||
filter.op = '>';
|
||||
this.filters[key + '>'] = filter;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns true when the new filter can be added, false otherwise.
|
||||
*/
|
||||
Object.defineProperty(tree.Filterset.prototype, 'addable', {
|
||||
enumerable: false,
|
||||
value: function(filter) {
|
||||
var key = filter.key, value = filter.val;
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
if (key + '=' in this) return (this[key + '='].val != value) ? false : null;
|
||||
if (key + '!=' + value in this) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return false;
|
||||
return true;
|
||||
|
||||
case '!=':
|
||||
if (key + '=' in this) return (this[key + '='].val == value) ? false : null;
|
||||
if (key + '!=' + value in this) return null;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '>':
|
||||
if (key + '=' in this) return (this[key + '='].val <= value) ? false : null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val <= value) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return null;
|
||||
return true;
|
||||
|
||||
case '>=':
|
||||
if (key + '=' in this) return (this[key + '='].val < value) ? false : null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val >= value) return null;
|
||||
return true;
|
||||
|
||||
case '<':
|
||||
if (key + '=' in this) return (this[key + '='].val >= value) ? false : null;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val >= value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '<=':
|
||||
if (key + '=' in this) return (this[key + '='].val > value) ? false : null;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val <= value) return null;
|
||||
return true;
|
||||
else {
|
||||
this.filters[key + '>='] = filter;
|
||||
}
|
||||
} else if (op === '<') {
|
||||
for (var l in this.filters) {
|
||||
numval = (+this.filters[l].val.toString());
|
||||
if (this.filters[l].key == key && numval >= filter.val) {
|
||||
delete this.filters[l];
|
||||
}
|
||||
}
|
||||
this.filters[key + '<'] = filter;
|
||||
} else if (op === '<=') {
|
||||
for (var m in this.filters) {
|
||||
numval = (+this.filters[m].val.toString());
|
||||
if (this.filters[m].key == key && numval > filter.val) {
|
||||
delete this.filters[m];
|
||||
}
|
||||
}
|
||||
if (this.filters[key + '!=' + filter.val] !== undefined) {
|
||||
delete this.filters[key + '!=' + filter.val];
|
||||
filter.op = '<';
|
||||
this.filters[key + '<'] = filter;
|
||||
}
|
||||
else {
|
||||
this.filters[key + '<='] = filter;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Only call this function for filters that have been cleared by .addable().
|
||||
*/
|
||||
Object.defineProperty(tree.Filterset.prototype, 'add', {
|
||||
enumerable: false,
|
||||
value: function(filter) {
|
||||
var key = filter.key;
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
for (var id in this) {
|
||||
if (this[id].key == key) {
|
||||
delete this[id];
|
||||
}
|
||||
}
|
||||
this[key + '='] = filter;
|
||||
break;
|
||||
|
||||
case '!=':
|
||||
this[key + '!=' + filter.val] = filter;
|
||||
break;
|
||||
|
||||
case '=~':
|
||||
this[key + '=~' + filter.val] = filter;
|
||||
break;
|
||||
|
||||
case '>':
|
||||
// If there are other filters that are also >
|
||||
// but are less than this one, they don't matter, so
|
||||
// remove them.
|
||||
for (var id in this) {
|
||||
if (this[id].key == key && this[id].val <= filter.val) {
|
||||
delete this[id];
|
||||
}
|
||||
}
|
||||
this[key + '>'] = filter;
|
||||
break;
|
||||
|
||||
case '>=':
|
||||
for (var id in this) {
|
||||
if (this[id].key == key && this[id].val < filter.val) {
|
||||
delete this[id];
|
||||
}
|
||||
}
|
||||
if (key + '!=' + filter.val in this) {
|
||||
delete this[key + '!=' + filter.val];
|
||||
filter.op = '>';
|
||||
this[key + '>'] = filter;
|
||||
}
|
||||
else {
|
||||
this[key + '>='] = filter;
|
||||
}
|
||||
break;
|
||||
|
||||
case '<':
|
||||
for (var id in this) {
|
||||
if (this[id].key == key && this[id].val >= filter.val) {
|
||||
delete this[id];
|
||||
}
|
||||
}
|
||||
this[key + '<'] = filter;
|
||||
break;
|
||||
|
||||
case '<=':
|
||||
for (var id in this) {
|
||||
if (this[id].key == key && this[id].val > filter.val) {
|
||||
delete this[id];
|
||||
}
|
||||
}
|
||||
if (key + '!=' + filter.val in this) {
|
||||
delete this[key + '!=' + filter.val];
|
||||
filter.op = '<';
|
||||
this[key + '<'] = filter;
|
||||
}
|
||||
else {
|
||||
this[key + '<='] = filter;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,23 +1,16 @@
|
||||
(function(tree) {
|
||||
|
||||
tree._getFontSet = function(env, fonts) {
|
||||
var find_existing = function(fonts) {
|
||||
var findFonts = fonts.join('');
|
||||
for (var i = 0; i < env.effects.length; i++) {
|
||||
if (findFonts == env.effects[i].fonts.join('')) {
|
||||
return env.effects[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var existing = false;
|
||||
if (existing = find_existing(fonts)) {
|
||||
return existing;
|
||||
} else {
|
||||
var new_fontset = new tree.FontSet(env, fonts);
|
||||
env.effects.push(new_fontset);
|
||||
return new_fontset;
|
||||
var fontKey = fonts.join('');
|
||||
if (env._fontMap && env._fontMap[fontKey]) {
|
||||
return env._fontMap[fontKey];
|
||||
}
|
||||
|
||||
var new_fontset = new tree.FontSet(env, fonts);
|
||||
env.effects.push(new_fontset);
|
||||
if (!env._fontMap) env._fontMap = {};
|
||||
env._fontMap[fontKey] = new_fontset;
|
||||
return new_fontset;
|
||||
};
|
||||
|
||||
tree.FontSet = function FontSet(env, fonts) {
|
||||
|
27
lib/carto/tree/frame_offset.js
Normal file
27
lib/carto/tree/frame_offset.js
Normal file
@ -0,0 +1,27 @@
|
||||
var tree = require('../tree');
|
||||
|
||||
// Storage for Frame offset value
|
||||
// and stores them as bit-sequences so that they can be combined,
|
||||
// inverted, and compared quickly.
|
||||
tree.FrameOffset = function(op, value, index) {
|
||||
value = parseInt(value, 10);
|
||||
if (value > tree.FrameOffset.max || value <= 0) {
|
||||
throw {
|
||||
message: 'Only frame-offset levels between 1 and ' +
|
||||
tree.FrameOffset.max + ' supported.',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
|
||||
if (op !== '=') {
|
||||
throw {
|
||||
message: 'only = operator is supported for frame-offset',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
tree.FrameOffset.max = 32;
|
||||
tree.FrameOffset.none = 0;
|
||||
|
@ -1,14 +1,13 @@
|
||||
(function(tree) {
|
||||
//
|
||||
// RGB Colors - #ff0014, #eee
|
||||
//
|
||||
|
||||
tree.ImageFilter = function ImageFilter(filter, args) {
|
||||
this.is = 'imagefilter';
|
||||
this.filter = filter;
|
||||
this.args = args || null;
|
||||
};
|
||||
|
||||
tree.ImageFilter.prototype = {
|
||||
eval: function() { return this; },
|
||||
is: 'imagefilter',
|
||||
ev: function() { return this; },
|
||||
|
||||
toString: function() {
|
||||
if (this.args) {
|
||||
|
@ -4,10 +4,11 @@ tree.Invalid = function Invalid(chunk, index, message) {
|
||||
this.index = index;
|
||||
this.type = 'syntax';
|
||||
this.message = message || "Invalid code: " + this.chunk;
|
||||
this.is = 'invalid';
|
||||
};
|
||||
|
||||
tree.Invalid.prototype.eval = function(env) {
|
||||
tree.Invalid.prototype.is = 'invalid';
|
||||
|
||||
tree.Invalid.prototype.ev = function(env) {
|
||||
env.error({
|
||||
chunk: this.chunk,
|
||||
index: this.index,
|
||||
|
@ -10,7 +10,7 @@ tree.Keyword = function Keyword(value) {
|
||||
this.is = special[value] ? special[value] : 'keyword';
|
||||
};
|
||||
tree.Keyword.prototype = {
|
||||
eval: function() { return this; },
|
||||
ev: function() { return this; },
|
||||
toString: function() { return this.value; }
|
||||
};
|
||||
|
||||
|
@ -1,37 +1,36 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Layer = function Layer(obj) {
|
||||
this.name = obj.name;
|
||||
this.status = obj.status;
|
||||
this.styles = obj.styles;
|
||||
this.properties = obj.properties || {};
|
||||
this.srs = obj.srs;
|
||||
this.datasource = obj.Datasource;
|
||||
};
|
||||
|
||||
tree.Layer.prototype.toXML = function() {
|
||||
tree.LayerXML = function(obj, styles) {
|
||||
var dsoptions = [];
|
||||
for (var i in this.datasource) {
|
||||
for (var i in obj.Datasource) {
|
||||
dsoptions.push('<Parameter name="' + i + '"><![CDATA[' +
|
||||
this.datasource[i] + ']]></Parameter>');
|
||||
obj.Datasource[i] + ']]></Parameter>');
|
||||
}
|
||||
|
||||
var prop_string = '';
|
||||
for (var i in this.properties) {
|
||||
prop_string += ' ' + i + '="' + this.properties[i] + '"\n';
|
||||
for (var prop in obj.properties) {
|
||||
if (prop === 'minzoom') {
|
||||
prop_string += ' maxzoom="' + tree.Zoom.ranges[obj.properties[prop]] + '"\n';
|
||||
} else if (prop === 'maxzoom') {
|
||||
prop_string += ' minzoom="' + tree.Zoom.ranges[obj.properties[prop]+1] + '"\n';
|
||||
} else {
|
||||
prop_string += ' ' + prop + '="' + obj.properties[prop] + '"\n';
|
||||
}
|
||||
}
|
||||
|
||||
return '<Layer' +
|
||||
' name="' + this.name + '"\n' +
|
||||
' name="' + obj.name + '"\n' +
|
||||
prop_string +
|
||||
((typeof this.status === 'undefined') ? '' : ' status="' + this.status + '"\n') +
|
||||
' srs="' + this.srs + '">\n ' +
|
||||
this.styles.reverse().map(function(s) {
|
||||
((typeof obj.status === 'undefined') ? '' : ' status="' + obj.status + '"\n') +
|
||||
((typeof obj.srs === 'undefined') ? '' : ' srs="' + obj.srs + '"') + '>\n ' +
|
||||
styles.reverse().map(function(s) {
|
||||
return '<StyleName>' + s + '</StyleName>';
|
||||
}).join('\n ') +
|
||||
(dsoptions.length ?
|
||||
'\n <Datasource>\n ' +
|
||||
dsoptions.join('\n ') +
|
||||
'\n </Datasource>\n' +
|
||||
'\n </Datasource>\n'
|
||||
: '') +
|
||||
' </Layer>\n';
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,7 @@ tree.Literal.prototype = {
|
||||
toString: function() {
|
||||
return this.value;
|
||||
},
|
||||
'eval': function() {
|
||||
'ev': function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
@ -1,15 +1,18 @@
|
||||
// An operation is an expression with an op in between two operands,
|
||||
// like 2 + 1.
|
||||
(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),
|
||||
tree.Operation.prototype.is = 'operation';
|
||||
|
||||
tree.Operation.prototype.ev = function(env) {
|
||||
var a = this.operands[0].ev(env),
|
||||
b = this.operands[1].ev(env),
|
||||
temp;
|
||||
|
||||
if (a.is === 'undefined' || b.is === 'undefined') {
|
||||
@ -61,11 +64,24 @@ tree.Operation.prototype.eval = function(env) {
|
||||
value: 'undefined'
|
||||
};
|
||||
} else {
|
||||
return new tree.Literal(a.eval(env).toString(true) + this.op + b.eval(env).toString(true));
|
||||
return new tree.Literal(a.ev(env).toString(true) + this.op + b.ev(env).toString(true));
|
||||
}
|
||||
}
|
||||
|
||||
return a.operate(this.op, b);
|
||||
if (a.operate === undefined) {
|
||||
env.error({
|
||||
message: 'Cannot do math with type ' + a.is + '.',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
return a.operate(env, this.op, b);
|
||||
};
|
||||
|
||||
tree.operate = function(op, a, b) {
|
||||
|
@ -2,20 +2,27 @@
|
||||
|
||||
tree.Quoted = function Quoted(content) {
|
||||
this.value = content || '';
|
||||
this.is = 'string';
|
||||
};
|
||||
|
||||
tree.Quoted.prototype = {
|
||||
is: 'string',
|
||||
|
||||
toString: function(quotes) {
|
||||
var xmlvalue = this.value.replace(/\'/g, ''');
|
||||
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
|
||||
var escapedValue = this.value
|
||||
.replace(/&/g, '&')
|
||||
var xmlvalue = escapedValue
|
||||
.replace(/\'/g, '\\\'')
|
||||
.replace(/\"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/\>/g, '>');
|
||||
return (quotes === true) ? "'" + xmlvalue + "'" : escapedValue;
|
||||
},
|
||||
|
||||
'eval': function() {
|
||||
'ev': function() {
|
||||
return this;
|
||||
},
|
||||
|
||||
operate: function(op, other) {
|
||||
operate: function(env, op, other) {
|
||||
return new tree.Quoted(tree.operate(op, this.toString(), other.toString(this.contains_field)));
|
||||
}
|
||||
};
|
||||
|
@ -1,118 +1,88 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// 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');
|
||||
var mapnik_reference = require('mapnik-reference');
|
||||
var _ = global._ || require('underscore'),
|
||||
ref = {};
|
||||
|
||||
tree.Reference = {
|
||||
data: mapnik_reference.version.latest
|
||||
ref.setData = function(data) {
|
||||
ref.data = data;
|
||||
ref.selector_cache = generateSelectorCache(data);
|
||||
ref.mapnikFunctions = generateMapnikFunctions(data);
|
||||
|
||||
ref.mapnikFunctions.matrix = [6];
|
||||
ref.mapnikFunctions.translate = [1, 2];
|
||||
ref.mapnikFunctions.scale = [1, 2];
|
||||
ref.mapnikFunctions.rotate = [1, 3];
|
||||
ref.mapnikFunctions.skewX = [1];
|
||||
ref.mapnikFunctions.skewY = [1];
|
||||
|
||||
ref.required_cache = generateRequiredProperties(data);
|
||||
};
|
||||
|
||||
/// @param version target mapnik version
|
||||
/// @return true on success, false on error (unsupported mapnik version)
|
||||
tree.Reference.setVersion = function(version) {
|
||||
ref.setVersion = function(version) {
|
||||
var mapnik_reference = require('mapnik-reference');
|
||||
if (mapnik_reference.version.hasOwnProperty(version)) {
|
||||
tree.Reference.data = mapnik_reference.version[version];
|
||||
ref.setData(mapnik_reference.version[version]);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.required_prop_list_cache = {};
|
||||
ref.selectorData = function(selector, i) {
|
||||
if (ref.selector_cache[selector]) return ref.selector_cache[selector][i];
|
||||
};
|
||||
|
||||
tree.Reference.selectors = tree.Reference.selectors || (function() {
|
||||
var list = [];
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (tree.Reference.data.symbolizers[i][j].hasOwnProperty('css')) {
|
||||
list.push(tree.Reference.data.symbolizers[i][j].css);
|
||||
ref.validSelector = function(selector) { return !!ref.selector_cache[selector]; };
|
||||
ref.selectorName = function(selector) { return ref.selectorData(selector, 2); };
|
||||
ref.selector = function(selector) { return ref.selectorData(selector, 0); };
|
||||
ref.symbolizer = function(selector) { return ref.selectorData(selector, 1); };
|
||||
|
||||
function generateSelectorCache(data) {
|
||||
var index = {};
|
||||
for (var i in data.symbolizers) {
|
||||
for (var j in data.symbolizers[i]) {
|
||||
if (data.symbolizers[i][j].hasOwnProperty('css')) {
|
||||
index[data.symbolizers[i][j].css] = [data.symbolizers[i][j], i, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
})();
|
||||
return index;
|
||||
}
|
||||
|
||||
tree.Reference.validSelector = function(selector) {
|
||||
return tree.Reference.selectors.indexOf(selector) !== -1;
|
||||
};
|
||||
|
||||
tree.Reference.selectorName = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.selector = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return tree.Reference.data.symbolizers[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.symbolizer = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* For transform properties and image-filters,
|
||||
* mapnik has its own functions.
|
||||
*/
|
||||
tree.Reference.mapnikFunctions = function() {
|
||||
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);
|
||||
function generateMapnikFunctions(data) {
|
||||
var functions = {};
|
||||
for (var i in data.symbolizers) {
|
||||
for (var j in data.symbolizers[i]) {
|
||||
if (data.symbolizers[i][j].type === 'functions') {
|
||||
for (var k = 0; k < data.symbolizers[i][j].functions.length; k++) {
|
||||
var fn = data.symbolizers[i][j].functions[k];
|
||||
functions[fn[0]] = fn[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return functions;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* For transform properties and image-filters,
|
||||
* mapnik has its own functions.
|
||||
*/
|
||||
tree.Reference.mapnikFunction = function(name) {
|
||||
return _.find(this.mapnikFunctions(), 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];
|
||||
}
|
||||
var properties = [];
|
||||
for (var j in tree.Reference.data.symbolizers[symbolizer_name]) {
|
||||
if (tree.Reference.data.symbolizers[symbolizer_name][j].required) {
|
||||
properties.push(tree.Reference.data.symbolizers[symbolizer_name][j].css);
|
||||
function generateRequiredProperties(data) {
|
||||
var cache = {};
|
||||
for (var symbolizer_name in data.symbolizers) {
|
||||
cache[symbolizer_name] = [];
|
||||
for (var j in data.symbolizers[symbolizer_name]) {
|
||||
if (data.symbolizers[symbolizer_name][j].required) {
|
||||
cache[symbolizer_name].push(data.symbolizers[symbolizer_name][j].css);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.required_prop_list_cache[symbolizer_name] = properties;
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
tree.Reference.requiredProperties = function(symbolizer_name, rules) {
|
||||
var req = tree.Reference.requiredPropertyList(symbolizer_name);
|
||||
ref.requiredProperties = function(symbolizer_name, rules) {
|
||||
var req = ref.required_cache[symbolizer_name];
|
||||
for (var i in req) {
|
||||
if (!(req[i] in rules)) {
|
||||
return 'Property ' + req[i] + ' required for defining ' +
|
||||
@ -121,10 +91,8 @@ tree.Reference.requiredProperties = function(symbolizer_name, rules) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: finish implementation - this is dead code
|
||||
*/
|
||||
tree.Reference._validateValue = {
|
||||
// TODO: finish implementation - this is dead code
|
||||
ref._validateValue = {
|
||||
'font': function(env, value) {
|
||||
if (env.validation_data && env.validation_data.fonts) {
|
||||
return env.validation_data.fonts.indexOf(value) != -1;
|
||||
@ -134,20 +102,17 @@ tree.Reference._validateValue = {
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.isFont = function(selector) {
|
||||
return tree.Reference.selector(selector).validate == 'font';
|
||||
ref.isFont = function(selector) {
|
||||
return ref.selector(selector).validate == 'font';
|
||||
};
|
||||
|
||||
// https://gist.github.com/982927
|
||||
tree.Reference.editDistance = function(a, b){
|
||||
ref.editDistance = function(a, b){
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
|
||||
var matrix = [];
|
||||
|
||||
for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; }
|
||||
for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; }
|
||||
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i-1) == a.charAt(j-1)) {
|
||||
@ -159,87 +124,96 @@ tree.Reference.editDistance = function(a, b){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[b.length][a.length];
|
||||
};
|
||||
|
||||
tree.Reference.validValue = function(env, selector, value) {
|
||||
function validateFunctions(value, selector) {
|
||||
if (value.value[0].is === 'string') return true;
|
||||
for (var i in value.value) {
|
||||
for (var j in value.value[i].value) {
|
||||
if (value.value[i].value[j].is !== 'call') return false;
|
||||
var f = _.find(ref
|
||||
.selector(selector).functions, function(x) {
|
||||
return x[0] == value.value[i].value[j].name;
|
||||
});
|
||||
if (!(f && f[1] == -1)) {
|
||||
// This filter is unknown or given an incorrect number of arguments
|
||||
if (!f || f[1] !== value.value[i].value[j].args.length) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateKeyword(value, selector) {
|
||||
if (typeof ref.selector(selector).type === 'object') {
|
||||
return ref.selector(selector).type
|
||||
.indexOf(value.value[0].value) !== -1;
|
||||
} else {
|
||||
// allow unquoted keywords as strings
|
||||
return ref.selector(selector).type === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
ref.validValue = function(env, selector, value) {
|
||||
var i, j;
|
||||
// TODO: handle in reusable way
|
||||
if (!tree.Reference.selector(selector)) {
|
||||
if (!ref.selector(selector)) {
|
||||
return false;
|
||||
} else if (value.value[0].is == 'keyword') {
|
||||
if (typeof tree.Reference.selector(selector).type === 'object') {
|
||||
return tree.Reference
|
||||
.selector(selector).type
|
||||
.indexOf(value.value[0].value) !== -1;
|
||||
// Lax permissions for single strings. If you provide a keyword
|
||||
// aka unquoted string to a property that is a string, it'll be fine.
|
||||
} else if (tree.Reference.selector(selector).type === 'string') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return validateKeyword(value, selector);
|
||||
} 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') {
|
||||
} else if (ref.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') {
|
||||
} else if (ref.selector(selector).type == 'tags') {
|
||||
if (!value.value) return false;
|
||||
if (!value.value[0].value) {
|
||||
return value.value[0].is === 'tag';
|
||||
}
|
||||
for (i = 0; i < value.value[0].value.length; i++) {
|
||||
if (value.value[0].value[i].is !== 'tag') return false;
|
||||
}
|
||||
return true;
|
||||
} else if (ref.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 {
|
||||
for (i in value.value) {
|
||||
for (j in value.value[i].value) {
|
||||
if (value.value[i].value[j].is !== 'call') {
|
||||
return false;
|
||||
}
|
||||
var f = _.find(tree.Reference
|
||||
.selector(selector).functions, function(x) {
|
||||
return x[0] == value.value[i].value[j].name;
|
||||
});
|
||||
// This filter is unknown
|
||||
if (!f) return false;
|
||||
// The filter has been given an incorrect number of arguments
|
||||
if (f[1] !== value.value[i].value[j].args.length) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (tree.Reference.selector(selector).type === 'expression') {
|
||||
return true;
|
||||
} else if (tree.Reference.selector(selector).type === 'unsigned') {
|
||||
return validateFunctions(value, selector);
|
||||
} else if (ref.selector(selector).type === 'unsigned') {
|
||||
if (value.value[0].is === 'float') {
|
||||
value.value[0].round();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if ((ref.selector(selector).expression)) {
|
||||
return true;
|
||||
} else {
|
||||
if (tree.Reference.selector(selector).validate) {
|
||||
if (ref.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
|
||||
if (ref.selector(selector).type == value.value[i].is &&
|
||||
ref
|
||||
._validateValue
|
||||
[tree.Reference.selector(selector).validate]
|
||||
[ref.selector(selector).validate]
|
||||
(env, value.value[i].value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
} else {
|
||||
return tree.Reference.selector(selector).type == value.value[0].is;
|
||||
return ref.selector(selector).type == value.value[0].is;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference = ref;
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,4 +1,7 @@
|
||||
(function(tree) {
|
||||
// a rule is a single property and value combination, or variable
|
||||
// name and value combination, like
|
||||
// polygon-opacity: 1.0; or @opacity: 1.0;
|
||||
tree.Rule = function Rule(name, value, index, filename) {
|
||||
var parts = name.split('/');
|
||||
this.name = parts.pop();
|
||||
@ -9,10 +12,10 @@ tree.Rule = function Rule(name, value, index, filename) {
|
||||
this.symbolizer = tree.Reference.symbolizer(this.name);
|
||||
this.filename = filename;
|
||||
this.variable = (name.charAt(0) === '@');
|
||||
|
||||
this.is = 'rule';
|
||||
};
|
||||
|
||||
tree.Rule.prototype.is = 'rule';
|
||||
|
||||
tree.Rule.prototype.clone = function() {
|
||||
var clone = Object.create(tree.Rule.prototype);
|
||||
clone.name = this.name;
|
||||
@ -33,18 +36,19 @@ tree.Rule.prototype.toString = function() {
|
||||
return '[' + tree.Zoom.toString(this.zoom) + '] ' + this.name + ': ' + this.value;
|
||||
};
|
||||
|
||||
function getMean(name) {
|
||||
return Object.keys(tree.Reference.selector_cache).map(function(f) {
|
||||
return [f, tree.Reference.editDistance(name, f)];
|
||||
}).sort(function(a, b) { return a[1] - b[1]; });
|
||||
}
|
||||
|
||||
// second argument, if true, outputs the value of this
|
||||
// rule without the usual attribute="content" wrapping. Right
|
||||
// now this is just for the TextSymbolizer, but applies to other
|
||||
// properties in reference.json which specify serialization=content
|
||||
tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
||||
if (!tree.Reference.validSelector(this.name)) {
|
||||
var name = this.name;
|
||||
var mean = tree.Reference.selectors.map(function(f) {
|
||||
return [f, tree.Reference.editDistance(name, f)];
|
||||
}).sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
});
|
||||
var mean = getMean(this.name);
|
||||
var mean_message = '';
|
||||
if (mean[0][1] < 3) {
|
||||
mean_message = '. Did you mean ' + mean[0][0] + '?';
|
||||
@ -105,12 +109,10 @@ tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Rule eval chain should add fontsets to env.frames
|
||||
*/
|
||||
tree.Rule.prototype['eval'] = function(context) {
|
||||
// TODO: Rule ev chain should add fontsets to env.frames
|
||||
tree.Rule.prototype.ev = function(context) {
|
||||
return new tree.Rule(this.name,
|
||||
this.value['eval'](context),
|
||||
this.value.ev(context),
|
||||
this.index,
|
||||
this.filename);
|
||||
};
|
||||
|
@ -5,10 +5,10 @@ tree.Ruleset = function Ruleset(selectors, rules) {
|
||||
this.rules = rules;
|
||||
// static cache of find() function
|
||||
this._lookups = {};
|
||||
this.is = 'ruleset';
|
||||
};
|
||||
tree.Ruleset.prototype = {
|
||||
'eval': function(env) {
|
||||
is: 'ruleset',
|
||||
'ev': function(env) {
|
||||
var i,
|
||||
ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
|
||||
ruleset.root = this.root;
|
||||
@ -16,20 +16,10 @@ tree.Ruleset.prototype = {
|
||||
// push the current ruleset to the frames stack
|
||||
env.frames.unshift(ruleset);
|
||||
|
||||
// Evaluate imports
|
||||
if (ruleset.root) {
|
||||
for (i = 0; i < ruleset.rules.length; i++) {
|
||||
if (ruleset.rules[i] instanceof tree.Import) {
|
||||
Array.prototype.splice
|
||||
.apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate everything else
|
||||
for (i = 0, rule; i < ruleset.rules.length; i++) {
|
||||
rule = ruleset.rules[i];
|
||||
ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
|
||||
ruleset.rules[i] = rule.ev ? rule.ev(env) : rule;
|
||||
}
|
||||
|
||||
// Pop the stack
|
||||
@ -76,7 +66,7 @@ tree.Ruleset.prototype = {
|
||||
if (match) {
|
||||
if (selector.elements.length > 1) {
|
||||
Array.prototype.push.apply(rules, rule.find(
|
||||
new tree.Selector(null, null, selector.elements.slice(1)), self));
|
||||
new tree.Selector(null, null, null, selector.elements.slice(1)), self));
|
||||
} else {
|
||||
rules.push(rule);
|
||||
}
|
||||
@ -87,11 +77,24 @@ tree.Ruleset.prototype = {
|
||||
});
|
||||
return this._lookups[key] = rules;
|
||||
},
|
||||
// Zooms can use variables. This replaces tree.Zoom objects on selectors
|
||||
// with simple bit-arrays that we can compare easily.
|
||||
evZooms: function(env) {
|
||||
for (var i = 0; i < this.selectors.length; i++) {
|
||||
var zval = tree.Zoom.all;
|
||||
for (var z = 0; z < this.selectors[i].zoom.length; z++) {
|
||||
zval = zval & this.selectors[i].zoom[z].ev(env).zoom;
|
||||
}
|
||||
this.selectors[i].zoom = zval;
|
||||
}
|
||||
},
|
||||
flatten: function(result, parents, env) {
|
||||
var selectors = [], i, j;
|
||||
if (this.selectors.length === 0) {
|
||||
env.frames = env.frames.concat(this.rules);
|
||||
}
|
||||
// evaluate zoom variables on this object.
|
||||
this.evZooms(env);
|
||||
for (i = 0; i < this.selectors.length; i++) {
|
||||
var child = this.selectors[i];
|
||||
|
||||
@ -111,8 +114,10 @@ tree.Ruleset.prototype = {
|
||||
// filters. This means that we only have to clone when
|
||||
// the zoom levels or the attachment is different too.
|
||||
if (parent.zoom === (parent.zoom & child.zoom) &&
|
||||
parent.frame_offset === child.frame_offset &&
|
||||
parent.attachment === child.attachment &&
|
||||
parent.elements.join() === child.elements.join()) {
|
||||
selectors.push(parent);
|
||||
continue;
|
||||
} else {
|
||||
mergedFilters = parent.filters;
|
||||
@ -126,6 +131,7 @@ tree.Ruleset.prototype = {
|
||||
var clone = Object.create(tree.Selector.prototype);
|
||||
clone.filters = mergedFilters;
|
||||
clone.zoom = parent.zoom & child.zoom;
|
||||
clone.frame_offset = child.frame_offset;
|
||||
clone.elements = parent.elements.concat(child.elements);
|
||||
if (parent.attachment && child.attachment) {
|
||||
clone.attachment = parent.attachment + '/' + child.attachment;
|
||||
@ -144,6 +150,7 @@ tree.Ruleset.prototype = {
|
||||
for (i = 0; i < this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
|
||||
// Recursively flatten any nested rulesets
|
||||
if (rule instanceof tree.Ruleset) {
|
||||
rule.flatten(result, selectors, env);
|
||||
} else if (rule instanceof tree.Rule) {
|
||||
|
@ -1,23 +1,20 @@
|
||||
var assert = require('assert');
|
||||
|
||||
(function(tree) {
|
||||
|
||||
tree.Selector = function Selector(filters, zoom, elements, attachment, conditions, index) {
|
||||
tree.Selector = function Selector(filters, zoom, frame_offset, elements, attachment, conditions, index) {
|
||||
this.elements = elements || [];
|
||||
this.attachment = attachment;
|
||||
this.filters = filters || {};
|
||||
this.frame_offset = frame_offset;
|
||||
this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all;
|
||||
this.conditions = conditions;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the specificity of this selector
|
||||
* based on the specificity of its elements - calling
|
||||
* Element.specificity() in order to do so
|
||||
*
|
||||
* [ID, Class, Filters, Position in document]
|
||||
*/
|
||||
// Determine the specificity of this selector
|
||||
// based on the specificity of its elements - calling
|
||||
// Element.specificity() in order to do so
|
||||
//
|
||||
// [ID, Class, Filters, Position in document]
|
||||
tree.Selector.prototype.specificity = function() {
|
||||
return this.elements.reduce(function(memo, e) {
|
||||
var spec = e.specificity();
|
||||
|
@ -1,54 +1,68 @@
|
||||
(function(tree) {
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
tree.Style = function Style(name, attachment, definitions) {
|
||||
this.attachment = attachment;
|
||||
this.definitions = definitions;
|
||||
this.name = name + (attachment !== '__default__' ? '-' + attachment : '');
|
||||
};
|
||||
|
||||
tree.Style.prototype.toXML = function(env) {
|
||||
// Given a style's name, attachment, definitions, and an environment object,
|
||||
// return a stringified style for Mapnik
|
||||
tree.StyleXML = function(name, attachment, definitions, env) {
|
||||
var existing = {};
|
||||
var image_filters = [], image_filters_inflate = [], direct_image_filters = [], comp_op = [], opacity = [];
|
||||
|
||||
var image_filters = _.flatten(this.definitions.map(function(definition) {
|
||||
return definition.rules.filter(function(rule) {
|
||||
return (rule.name === 'image-filters');
|
||||
});
|
||||
}));
|
||||
for (var i = 0; i < definitions.length; i++) {
|
||||
for (var j = 0; j < definitions[i].rules.length; j++) {
|
||||
if (definitions[i].rules[j].name === 'image-filters') {
|
||||
image_filters.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'image-filters-inflate') {
|
||||
image_filters_inflate.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'direct-image-filters') {
|
||||
direct_image_filters.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'comp-op') {
|
||||
comp_op.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'opacity') {
|
||||
opacity.push(definitions[i].rules[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var comp_op = _.flatten(this.definitions.map(function(definition) {
|
||||
return definition.rules.filter(function(rule) {
|
||||
return (rule.name === 'comp-op');
|
||||
});
|
||||
}));
|
||||
|
||||
var opacity = _.flatten(this.definitions.map(function(definition) {
|
||||
return definition.rules.filter(function(rule) {
|
||||
return (rule.name === 'opacity');
|
||||
});
|
||||
}));
|
||||
|
||||
var rules = this.definitions.map(function(definition) {
|
||||
var rules = definitions.map(function(definition) {
|
||||
return definition.toXML(env, existing);
|
||||
});
|
||||
|
||||
var attrs_xml = '';
|
||||
|
||||
if (image_filters.length) {
|
||||
attrs_xml += ' image-filters="' + image_filters.map(function(f) {
|
||||
return f.eval(env).toXML(env, true, ',', 'image-filter');
|
||||
}).join(',') + '" ';
|
||||
attrs_xml += ' image-filters="' + _.chain(image_filters)
|
||||
// prevent identical filters from being duplicated in the style
|
||||
.uniq(function(i) { return i.id; }).map(function(f) {
|
||||
return f.ev(env).toXML(env, true, ',', 'image-filter');
|
||||
}).value().join(',') + '"';
|
||||
}
|
||||
|
||||
if (comp_op.length) {
|
||||
attrs_xml += ' comp-op="' + comp_op[0].value.eval(env).toString() + '" ';
|
||||
if (image_filters_inflate.length) {
|
||||
attrs_xml += ' image-filters-inflate="' + image_filters_inflate[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
|
||||
if (opacity.length) {
|
||||
attrs_xml += ' opacity="' + opacity[0].value.eval(env).toString() + '" ';
|
||||
if (direct_image_filters.length) {
|
||||
attrs_xml += ' direct-image-filters="' + _.chain(direct_image_filters)
|
||||
// prevent identical filters from being duplicated in the style
|
||||
.uniq(function(i) { return i.id; }).map(function(f) {
|
||||
return f.ev(env).toXML(env, true, ',', 'direct-image-filter');
|
||||
}).value().join(',') + '"';
|
||||
}
|
||||
|
||||
return '<Style name="' + this.name + '" filter-mode="first" ' + attrs_xml + '>\n' + rules.join('') + '</Style>';
|
||||
if (comp_op.length && comp_op[0].value.ev(env).value != 'src-over') {
|
||||
attrs_xml += ' comp-op="' + comp_op[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
|
||||
if (opacity.length && opacity[0].value.ev(env).value != 1) {
|
||||
attrs_xml += ' opacity="' + opacity[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
var rule_string = rules.join('');
|
||||
if (!attrs_xml && !rule_string) return '';
|
||||
return '<Style name="' + name + '" filter-mode="first"' + attrs_xml + '>\n' + rule_string + '</Style>';
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -3,14 +3,15 @@
|
||||
tree.URL = function URL(val, paths) {
|
||||
this.value = val;
|
||||
this.paths = paths;
|
||||
this.is = 'uri';
|
||||
};
|
||||
|
||||
tree.URL.prototype = {
|
||||
is: 'uri',
|
||||
toString: function() {
|
||||
return this.value.toString();
|
||||
},
|
||||
eval: function(ctx) {
|
||||
return new tree.URL(this.value.eval(ctx), this.paths);
|
||||
ev: function(ctx) {
|
||||
return new tree.URL(this.value.ev(ctx), this.paths);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,16 +2,16 @@
|
||||
|
||||
tree.Value = function Value(value) {
|
||||
this.value = value;
|
||||
this.is = 'value';
|
||||
};
|
||||
|
||||
tree.Value.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'value',
|
||||
ev: function(env) {
|
||||
if (this.value.length === 1) {
|
||||
return this.value[0].eval(env);
|
||||
return this.value[0].ev(env);
|
||||
} else {
|
||||
return new tree.Value(this.value.map(function(v) {
|
||||
return v.eval(env);
|
||||
return v.ev(env);
|
||||
}));
|
||||
}
|
||||
},
|
||||
@ -26,7 +26,34 @@ tree.Value.prototype = {
|
||||
else obj.value = this.value;
|
||||
obj.is = this.is;
|
||||
return obj;
|
||||
},
|
||||
|
||||
toJS: function(env) {
|
||||
//var v = this.value[0].value[0];
|
||||
var val = this.ev(env);
|
||||
var v = val.toString();
|
||||
if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') {
|
||||
v = "'" + v.replace(/&/g, '&') + "'";
|
||||
} else if (Array.isArray(this.value) && this.value.length > 1) {
|
||||
// This covers something like `line-dasharray: 5, 10;`
|
||||
// where the return _value has more than one element.
|
||||
// Without this the generated code will look like:
|
||||
// _value = 5, 10; which will ignore the 10.
|
||||
v = '[' + this.value.join(',') + ']';
|
||||
} else if (val.is === 'field') {
|
||||
// replace [variable] by ctx['variable']
|
||||
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
|
||||
return matched.replace(/\[(.*)\]/g, "data['$1']");
|
||||
});
|
||||
}else if (val.is === 'call') {
|
||||
v = JSON.stringify({
|
||||
name: val.name,
|
||||
args: val.args
|
||||
})
|
||||
}
|
||||
return "_value = " + v + ";";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -7,19 +7,22 @@ tree.Variable = function Variable(name, index, filename) {
|
||||
};
|
||||
|
||||
tree.Variable.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'variable',
|
||||
toString: function() {
|
||||
return this.name;
|
||||
},
|
||||
ev: function(env) {
|
||||
var variable,
|
||||
v,
|
||||
that = this,
|
||||
name = this.name;
|
||||
|
||||
if (this._css) return this._css;
|
||||
|
||||
var thisframe = env.frames.filter(function(f) {
|
||||
return f.name == that.name;
|
||||
});
|
||||
return f.name == this.name;
|
||||
}.bind(this));
|
||||
if (thisframe.length) {
|
||||
return thisframe[0].value.eval(env);
|
||||
return thisframe[0].value.ev(env);
|
||||
} else {
|
||||
env.error({
|
||||
message: 'variable ' + this.name + ' is undefined',
|
||||
|
@ -4,22 +4,34 @@ var tree = require('../tree');
|
||||
// and stores them as bit-sequences so that they can be combined,
|
||||
// inverted, and compared quickly.
|
||||
tree.Zoom = function(op, value, index) {
|
||||
value = parseInt(value, 10);
|
||||
if (value > tree.Zoom.maxZoom || value < 0) {
|
||||
throw {
|
||||
message: 'Only zoom levels between 0 and ' +
|
||||
tree.Zoom.maxZoom + ' supported.',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
this.op = op;
|
||||
this.value = value;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.setZoom = function(zoom) {
|
||||
this.zoom = zoom;
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.ev = function(env) {
|
||||
var start = 0,
|
||||
end = Infinity,
|
||||
value = parseInt(this.value.ev(env).toString(), 10),
|
||||
zoom = 0;
|
||||
|
||||
switch (op) {
|
||||
if (value > tree.Zoom.maxZoom || value < 0) {
|
||||
env.error({
|
||||
message: 'Only zoom levels between 0 and ' +
|
||||
tree.Zoom.maxZoom + ' supported.',
|
||||
index: this.index
|
||||
});
|
||||
}
|
||||
|
||||
switch (this.op) {
|
||||
case '=':
|
||||
return 1 << value;
|
||||
this.zoom = 1 << value;
|
||||
return this;
|
||||
case '>':
|
||||
start = value + 1;
|
||||
break;
|
||||
@ -38,7 +50,12 @@ tree.Zoom = function(op, value, index) {
|
||||
zoom |= (1 << i);
|
||||
}
|
||||
}
|
||||
return zoom;
|
||||
this.zoom = zoom;
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.toString = function() {
|
||||
return this.zoom;
|
||||
};
|
||||
|
||||
// Covers all zoomlevels from 0 to 22
|
||||
@ -74,12 +91,12 @@ tree.Zoom.ranges = {
|
||||
};
|
||||
|
||||
// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid.
|
||||
tree.Zoom.toXML = function(zoom) {
|
||||
tree.Zoom.prototype.toXML = function() {
|
||||
var conditions = [];
|
||||
if (zoom != tree.Zoom.all) {
|
||||
if (this.zoom != tree.Zoom.all) {
|
||||
var start = null, end = null;
|
||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||
if (zoom & (1 << i)) {
|
||||
if (this.zoom & (1 << i)) {
|
||||
if (start === null) start = i;
|
||||
end = i;
|
||||
}
|
||||
@ -92,11 +109,10 @@ tree.Zoom.toXML = function(zoom) {
|
||||
return conditions;
|
||||
};
|
||||
|
||||
|
||||
tree.Zoom.toString = function(zoom) {
|
||||
tree.Zoom.prototype.toString = function() {
|
||||
var str = '';
|
||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||
str += (zoom & (1 << i)) ? 'X' : '.';
|
||||
str += (this.zoom & (1 << i)) ? 'X' : '.';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
24
man/carto.1
Normal file
24
man/carto.1
Normal file
@ -0,0 +1,24 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12.
|
||||
.TH CARTO "1" "March 2013" "carto 0.9.4" "User Commands"
|
||||
.SH NAME
|
||||
carto \- Carto map stylesheet compiler
|
||||
.SH SYNOPSIS
|
||||
.B carto [OPTION]
|
||||
\fI<source MML file>\fR
|
||||
.SH DESCRIPTION
|
||||
Carto is a stylesheet renderer for Mapnik. It's an evolution of
|
||||
the Cascadenik idea and language, with an emphasis on speed and
|
||||
flexibility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-v\fR \fB\-\-version\fR
|
||||
Parse JSON map manifest
|
||||
.TP
|
||||
\fB\-b\fR \fB\-\-benchmark\fR
|
||||
Outputs total compile time
|
||||
.TP
|
||||
\fB\-n\fR \fB\-\-nosymlink\fR
|
||||
Use absolute paths instead of symlinking files
|
||||
.SH REPORTING BUGS
|
||||
Please report bugs on the GitHub issue tracker:
|
||||
<\fBhttps://github.com/mapbox/carto/issues\fR>
|
2110
package-lock.json
generated
Normal file
2110
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
package.json
57
package.json
@ -1,19 +1,17 @@
|
||||
{
|
||||
"name": "carto",
|
||||
"version": "0.9.3",
|
||||
"description": "Mapnik Stylesheet Compiler",
|
||||
"url": "https://github.com/mapbox/carto",
|
||||
"repositories": [{
|
||||
"version": "0.15.1-cdb5",
|
||||
"description": "CartoCSS Stylesheet Compiler",
|
||||
"url": "https://github.com/cartodb/carto",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/mapbox/carto.git"
|
||||
}],
|
||||
"url": "http://github.com/cartodb/carto.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "MapBox",
|
||||
"url": "http://mapbox.com/",
|
||||
"email": "info@mapbox.com"
|
||||
"name": "CartoDB",
|
||||
"url": "http://cartodb.com/"
|
||||
},
|
||||
"keywords": [
|
||||
"mapnik",
|
||||
"maps",
|
||||
"css",
|
||||
"stylesheets"
|
||||
@ -21,32 +19,45 @@
|
||||
"contributors": [
|
||||
"Tom MacWright <macwright@gmail.com>",
|
||||
"Konstantin Käfer",
|
||||
"Alexis Sellier <self@cloudhead.net>"
|
||||
"Alexis Sellier <self@cloudhead.net>",
|
||||
"Raul Ochoa <rochoa@cartodb.com>",
|
||||
"Javi Santana <jsantana@cartodb.com>"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache"
|
||||
}
|
||||
],
|
||||
"licenses": [{
|
||||
"type": "Apache"
|
||||
}],
|
||||
"bin": {
|
||||
"carto": "./bin/carto",
|
||||
"mml2json.js": "./bin/mml2json.js"
|
||||
"carto": "./bin/carto"
|
||||
},
|
||||
"man": "./man/carto.1",
|
||||
"main": "./lib/carto/index",
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"underscore": "~1.3.3",
|
||||
"mapnik-reference": "~5.0.0",
|
||||
"xml2js": "~0.1.13"
|
||||
"underscore": "1.8.3",
|
||||
"mapnik-reference": "~6.0.2",
|
||||
"optimist": "~0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.3.x",
|
||||
"docco": "0.3.x",
|
||||
"mocha": "1.12.x",
|
||||
"jshint": "0.2.x",
|
||||
"sax": "0.1.x"
|
||||
"sax": "0.1.x",
|
||||
"istanbul": "~0.2.14",
|
||||
"coveralls": "~2.10.1",
|
||||
"browserify": "~7.0.0",
|
||||
"uglify-js": "1.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
"pretest": "npm install",
|
||||
"test": "mocha -R spec"
|
||||
"test": "mocha -R spec",
|
||||
"tdd": "env HIDE_LOGS=true mocha -w -R spec",
|
||||
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info",
|
||||
"bump": "npm version patch",
|
||||
"bump:major": "npm version major",
|
||||
"bump:minor": "npm version minor",
|
||||
"postversion": "git push origin master --follow-tags"
|
||||
}
|
||||
}
|
||||
|
37
test/bincarto.test.js
Normal file
37
test/bincarto.test.js
Normal file
@ -0,0 +1,37 @@
|
||||
var assert = require('assert');
|
||||
var exec = require('child_process').exec;
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var helper = require('./support/helper');
|
||||
var bin = path.resolve(path.join(__dirname, '..', 'bin', 'carto'));
|
||||
var fs = require('fs');
|
||||
|
||||
describe('bin/carto', function() {
|
||||
it('errors on no input', function(done) {
|
||||
exec(bin, function(err, stdout, stderr) {
|
||||
assert.equal(1, err.code);
|
||||
assert.equal("carto: no input files ('carto -h or --help' for help)\n", stdout);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('renders mml', function(done) {
|
||||
var file = path.join(__dirname, 'rendering', 'identity.mml');
|
||||
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
|
||||
assert.ifError(err);
|
||||
helper.compareToXMLFile(helper.resultFile(file), stdout, done, [
|
||||
helper.removeAbsoluteImages,
|
||||
helper.removeAbsoluteDatasources
|
||||
]);
|
||||
});
|
||||
});
|
||||
it('renders mss', function(done) {
|
||||
var file = path.join(__dirname, 'rendering-mss', 'empty_name.mss');
|
||||
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
|
||||
assert.ifError(err);
|
||||
var expected = file.replace(path.extname(file),'')+'.xml';
|
||||
var expected_data = fs.readFileSync(expected, 'utf8');
|
||||
assert.equal(stdout,expected_data + '\n');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
25
test/color.test.js
Normal file
25
test/color.test.js
Normal file
@ -0,0 +1,25 @@
|
||||
var assert = require('assert');
|
||||
var tree = require('../lib/carto/tree.js');
|
||||
require('../lib/carto/functions');
|
||||
require('../lib/carto/tree/color');
|
||||
require('../lib/carto/tree/dimension');
|
||||
|
||||
describe('Color', function() {
|
||||
describe('basic functionality', function() {
|
||||
it('should be constructed', function() {
|
||||
var f = new tree.Color([0, 0, 0], 1);
|
||||
assert.deepEqual(f.toHSL(), {"h":0,"s":0,"l":0,"a":1});
|
||||
assert.ok(f);
|
||||
});
|
||||
});
|
||||
describe('functions', function() {
|
||||
it('should be constructed', function() {
|
||||
assert.deepEqual(tree.functions.rgb(0, 0, 0), new tree.Color([0, 0, 0], 1));
|
||||
assert.deepEqual(tree.functions.hue(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0));
|
||||
assert.deepEqual(tree.functions.saturation(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
|
||||
assert.deepEqual(tree.functions.lightness(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
|
||||
assert.deepEqual(tree.functions.alpha(new tree.Color([0, 0, 0], 1)), new tree.Dimension(1));
|
||||
assert.deepEqual(tree.functions.greyscale(new tree.Color([0, 0, 0], 1)), new tree.Color([0, 0, 0], 1));
|
||||
});
|
||||
});
|
||||
});
|
14
test/comment.test.js
Normal file
14
test/comment.test.js
Normal file
@ -0,0 +1,14 @@
|
||||
var assert = require('assert');
|
||||
var tree = require('../lib/carto/tree.js');
|
||||
require('../lib/carto/tree/comment');
|
||||
|
||||
describe('Comment', function() {
|
||||
describe('basic functionality', function() {
|
||||
it('should be constructed', function() {
|
||||
var f = new tree.Comment('hello world');
|
||||
assert.deepEqual(f.toString(), '<!--hello world-->');
|
||||
assert.deepEqual(f.ev(), f);
|
||||
assert.ok(f);
|
||||
});
|
||||
});
|
||||
});
|
@ -6,9 +6,10 @@ var carto = require('../lib/carto');
|
||||
var tree = require('../lib/carto/tree');
|
||||
var helper = require('./support/helper');
|
||||
|
||||
describe('Error handling', function() {
|
||||
describe('Error handling mml+mss', function() {
|
||||
helper.files('errorhandling', 'mml', function(file) {
|
||||
it('should handle errors in ' + path.basename(file), function(done) {
|
||||
var basename = path.basename(file);
|
||||
it('should handle errors in ' + basename, function(done) {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mml = helper.mml(file);
|
||||
@ -18,26 +19,58 @@ helper.files('errorhandling', 'mml', function(file) {
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).render(mml, function (err) {
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||
});
|
||||
});
|
||||
}).render(mml);
|
||||
// should not get here
|
||||
assert.ok(false);
|
||||
done();
|
||||
} catch(err) {
|
||||
if (err.message.indexOf('***') > -1) throw err;
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||
});
|
||||
// fs.writeFileSync(helper.resultFile(file), output);
|
||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
||||
assert.deepEqual(output, data);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling mss', function() {
|
||||
helper.files('errorhandling', 'mss', function(file) {
|
||||
var basename = path.basename(file);
|
||||
if (basename == 'multi_stylesheets_a.mss') {
|
||||
return;
|
||||
}
|
||||
it('should handle errors in ' + basename, function(done) {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mss = helper.mss(file);
|
||||
try {
|
||||
new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
// note: we use the basename here so that the expected error result
|
||||
// will match if the style was loaded from mml
|
||||
filename: basename
|
||||
}).renderMSS(mss);
|
||||
// should not get here
|
||||
assert.ok(false);
|
||||
done();
|
||||
} catch(err) {
|
||||
if (err.message.indexOf('***') > -1) throw err;
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
// fs.writeFileSync(helper.resultFile(file), output);
|
||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
||||
assert.deepEqual(output, data);
|
||||
done();
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
15
test/errorhandling/bad_op.mml
Normal file
15
test/errorhandling/bad_op.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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": [
|
||||
"bad_op.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"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"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/bad_op.mss
Normal file
3
test/errorhandling/bad_op.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 20% + 2px;
|
||||
}
|
1
test/errorhandling/bad_op.result
Normal file
1
test/errorhandling/bad_op.result
Normal file
@ -0,0 +1 @@
|
||||
bad_op.mss:2:4 If two operands differ, the first must not be %
|
15
test/errorhandling/bad_op_2.mml
Normal file
15
test/errorhandling/bad_op_2.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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": [
|
||||
"bad_op_2.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"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"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/bad_op_2.mss
Normal file
3
test/errorhandling/bad_op_2.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 20px * 2%;
|
||||
}
|
1
test/errorhandling/bad_op_2.result
Normal file
1
test/errorhandling/bad_op_2.result
Normal file
@ -0,0 +1 @@
|
||||
bad_op_2.mss:2:4 Percent values can only be added or subtracted from other values
|
15
test/errorhandling/color_functions.mml
Normal file
15
test/errorhandling/color_functions.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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": [
|
||||
"color_functions.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"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"
|
||||
}
|
||||
}]
|
||||
}
|
4
test/errorhandling/color_functions.mss
Normal file
4
test/errorhandling/color_functions.mss
Normal file
@ -0,0 +1,4 @@
|
||||
@foo: 'bar';
|
||||
#world {
|
||||
polygon-fill: hsl(1, @foo, 3);
|
||||
}
|
1
test/errorhandling/color_functions.result
Normal file
1
test/errorhandling/color_functions.result
Normal file
@ -0,0 +1 @@
|
||||
color_functions.mss:3:31 incorrect arguments given to hsl()
|
15
test/errorhandling/contradiction.mml
Normal file
15
test/errorhandling/contradiction.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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": [
|
||||
"contradiction.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/district.geojson",
|
||||
"type": "ogr",
|
||||
"layer": "OGRGeoJSON"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/contradiction.mss
Normal file
3
test/errorhandling/contradiction.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world[FeatureCla!=""][FeatureCla=""] {
|
||||
polygon-fill: #fff;
|
||||
}
|
1
test/errorhandling/contradiction.result
Normal file
1
test/errorhandling/contradiction.result
Normal file
@ -0,0 +1 @@
|
||||
contradiction.mss:1:37 [[FeatureCla]=] added to [FeatureCla]!= produces an invalid filter
|
3
test/errorhandling/contradiction_2.mss
Normal file
3
test/errorhandling/contradiction_2.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world[FeatureCla=""][FeatureCla!=""] {
|
||||
polygon-fill: #fff;
|
||||
}
|
1
test/errorhandling/contradiction_2.result
Normal file
1
test/errorhandling/contradiction_2.result
Normal file
@ -0,0 +1 @@
|
||||
contradiction_2.mss:1:37 [[FeatureCla]!=] added to [FeatureCla]= produces an invalid filter
|
@ -1 +1 @@
|
||||
function_args.mss:3:38 unknown function agg-stack-blu(), did you mean agg-stack-blur(2)
|
||||
function_args.mss:3:38 unknown function agg-stack-blu(), did you mean agg-stack-blur(2)
|
3
test/errorhandling/invalid_color_in_fn.mss
Normal file
3
test/errorhandling/invalid_color_in_fn.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
polygon-fill: spin(#f00f00f, 10);
|
||||
}
|
1
test/errorhandling/invalid_color_in_fn.result
Normal file
1
test/errorhandling/invalid_color_in_fn.result
Normal file
@ -0,0 +1 @@
|
||||
invalid_color_in_fn.mss:2:34 incorrect arguments given to spin()
|
@ -1 +1 @@
|
||||
invalid_property.mss:3:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
||||
invalid_property.mss:3:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
@ -1,5 +1,4 @@
|
||||
#world[zoom=5] {
|
||||
text-face-name: 2;
|
||||
line-rasterizer: 'full';
|
||||
text-name: 'foo';
|
||||
}
|
||||
|
@ -1,2 +1 @@
|
||||
invalid_value.mss:2:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
|
||||
invalid_value.mss:3:2 Invalid value for line-rasterizer, the type keyword (options: full, fast) is expected. full (of type string) was given.
|
||||
invalid_value.mss:2:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
|
15
test/errorhandling/invaliddimension.mml
Normal file
15
test/errorhandling/invaliddimension.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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": [
|
||||
"invaliddimension.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"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"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/invaliddimension.mss
Normal file
3
test/errorhandling/invaliddimension.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 10wifflewaffles;
|
||||
}
|
1
test/errorhandling/invaliddimension.result
Normal file
1
test/errorhandling/invaliddimension.result
Normal file
@ -0,0 +1 @@
|
||||
invaliddimension.mss:2:4 Invalid unit: 'wifflewaffles'
|
1
test/errorhandling/issue119.result
Normal file
1
test/errorhandling/issue119.result
Normal file
@ -0,0 +1 @@
|
||||
issue119.mss:2:2 Map properties are not permitted in other rules
|
@ -1 +1 @@
|
||||
issue123.mss:3:31 incorrect number of arguments for darken(). 2 expected.
|
||||
issue123.mss:3:31 incorrect number of arguments for darken(). 2 expected.
|
1
test/errorhandling/issue124.result
Normal file
1
test/errorhandling/issue124.result
Normal file
@ -0,0 +1 @@
|
||||
issue124.mss:6:0 missing closing `}`
|
8
test/errorhandling/issue297.mml
Normal file
8
test/errorhandling/issue297.mml
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Stylesheet": [
|
||||
"issue297.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"name": "t"
|
||||
}]
|
||||
}
|
4
test/errorhandling/issue297.mss
Normal file
4
test/errorhandling/issue297.mss
Normal file
@ -0,0 +1,4 @@
|
||||
#t {
|
||||
text-name: valid;
|
||||
text-face-name: 2;
|
||||
}
|
1
test/errorhandling/issue297.result
Normal file
1
test/errorhandling/issue297.result
Normal file
@ -0,0 +1 @@
|
||||
issue297.mss:3:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
|
3
test/errorhandling/issue_204_a.mss
Normal file
3
test/errorhandling/issue_204_a.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#countries {
|
||||
polygon-fill: green;
|
||||
}}
|
1
test/errorhandling/issue_204_a.result
Normal file
1
test/errorhandling/issue_204_a.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_a.mss:3:1 missing opening `{`
|
3
test/errorhandling/issue_204_b.mss
Normal file
3
test/errorhandling/issue_204_b.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#countries {
|
||||
polygon-fill: green;
|
||||
}}
|
1
test/errorhandling/issue_204_b.result
Normal file
1
test/errorhandling/issue_204_b.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_b.mss:3:3 missing opening `{`
|
3
test/errorhandling/issue_204_c.mss
Normal file
3
test/errorhandling/issue_204_c.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#countries {
|
||||
polygon-fill: green;
|
||||
}}
|
1
test/errorhandling/issue_204_c.result
Normal file
1
test/errorhandling/issue_204_c.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_c.mss:4:0 missing opening `{`
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user