Compare commits

...

457 Commits

Author SHA1 Message Date
Daniel García Aubert
85881d99dd Release 0.15.1-cdb5 2018-11-20 17:47:20 +01:00
Daniel G. Aubert
d3a5c97143
Merge pull request #50 from CartoDB/nodejs-10
Drop support for oldest Node.js versions and support latest LTS releases
2018-11-20 17:45:16 +01:00
Daniel García Aubert
8ed3ad15c1 Add CHANGELOG.carto.md 2018-11-20 17:36:46 +01:00
Daniel García Aubert
72f79efbdd Prepare next release version 2018-11-20 17:36:30 +01:00
Daniel García Aubert
161516b721 Support package-lock.json 2018-10-29 12:47:17 +01:00
Daniel García Aubert
e48d5ff993 Drop support for oldest Node.js versions and support latest LTS releases 2018-10-29 12:37:47 +01:00
IagoLast
31abb8bee0 0.15.1 2018-05-10 16:07:00 +02:00
IagoLast
e2177cf443 Add release scripts 2018-05-10 16:06:56 +02:00
Carlos Matallín
664b45cdde
Merge pull request #47 from CartoDB/shield_placement_type
adds shield-placement-type to torque-reference
2018-02-23 11:05:33 +01:00
Alegoiko
368dad0d11 adds shield-placement-type to torque-reference 2018-02-20 12:01:45 +01:00
David M
4dfe5361b3 Merge pull request #45 from CartoDB/ampersand-fix
Fix for ampersand on values (JS only)
2017-10-20 15:08:27 +02:00
David Manzanares
3596991362 Fix for ampersand on values (JS only) 2017-10-20 12:45:03 +02:00
David M
bf4c67034a Merge pull request #44 from CartoDB/js-ampersand-fix
Fix ampersand escaping on JS values
2017-10-10 18:20:57 +02:00
David Manzanares
ad5d919139 Use regular expression to replace all occurrences 2017-10-10 18:12:01 +02:00
David Manzanares
9fb894181d Fix replacement 2017-10-10 18:10:40 +02:00
David Manzanares
ffdf2d3c9b Fix ampersand escaping on JS values 2017-10-10 18:00:47 +02:00
David M
cbe66020f9 Merge pull request #42 from CartoDB/dasharrayJSsupport
Cover 'numbers' type, like in line-dasharray
2017-09-01 17:33:15 +02:00
David Manzanares
056081ace6 Cover 'numbers' type, like in line-dasharray 2017-09-01 13:30:19 +02:00
Pablo Alonso
a48ba58cae Merge pull request #41 from CartoDB/carto-40
Ignore zoom for the filtered field when the value is the default one.
2017-08-23 13:26:00 +02:00
Pablo Alonso Garcia
9f67f1bdde Dist files 2017-08-23 13:25:35 +02:00
IagoLast
55c0786130 Use tree.Zoom.all instead custom constant 2017-08-21 17:40:04 +02:00
Pablo Alonso Garcia
fef8cb369f Minor copy changes 2017-08-21 17:35:08 +02:00
IagoLast
d35e54859a Ignore zoom for the filtered field when the value is the default one. 2017-08-21 16:33:05 +02:00
IagoLast
daafa9fbf2 Merge pull request #39 from CartoDB/expose-filtered-field
Expose filtered field
2017-08-18 16:46:13 +02:00
IagoLast
34799db0cc Boy scout rule 2017-08-18 15:59:04 +02:00
IagoLast
41133a65ad Make test compatible with an old node version 2017-08-18 13:21:22 +02:00
IagoLast
1016f6870c Refactor filtered field code 2017-08-18 13:10:15 +02:00
IagoLast
72f93abf16 Expose filtered field 2017-08-18 12:58:01 +02:00
IagoLast
a3538d9007 Add filtered field tests 2017-08-18 12:57:45 +02:00
IagoLast
66cc146a02 Merge pull request #38 from CartoDB/use-hexa-instead-octal
Use hexadecimal values instead octal
2017-08-01 11:04:01 +02:00
IagoLast
aab27e6be8 Use hexadecimal values instead octal
Strict mode in ECMAScript 5 forbids octal syntax. This octal literals
are breaking the webpack compilation in upper packages like tangram-cartocss.

Mapbox have already [fixed this](https://github.com/mapbox/carto/blob/master/lib/carto/index.js#L104)

This PR changes the octal literals and uses hexadecimal ones.
2017-07-31 18:02:13 +02:00
IagoLast
cea1e7c620 Merge pull request #37 from CartoDB/36-remove-console-log
Remove console.log from tests
2017-07-31 17:58:14 +02:00
IagoLast
f8a9995050 Add a tdd option into the package.json scripts
This option includes a env variable to control the verbosity of the test logs.
2017-07-28 12:01:37 +02:00
IagoLast
0de8b82ff9 Remove console.log from tests 2017-07-26 13:08:05 +02:00
Francisco López
7d00bfdf23 Merge pull request #35 from CartoDB/v-release
Bump version
2017-06-15 15:30:33 +02:00
Francisco López
3db2fa322b Bump version 2017-06-15 14:54:29 +02:00
Francisco López
f4a2605a23 Release dist files 2017-05-19 13:57:16 +02:00
Francisco López
d95967247d Merge branch 'master' of github.com:CartoDB/carto 2017-05-19 13:56:36 +02:00
Francisco López
da8707a17f Release changes! 2017-05-19 13:56:26 +02:00
Francisco López
3f9f6ef40d Merge pull request #27 from CartoDB/reference-errors
[WIP] Reference errors (attempt 2)
2017-05-19 13:55:39 +02:00
Francisco López
b051ae284e Remove throw check 2017-05-19 13:51:27 +02:00
Francisco López
ea880ccf7b Check throw 2017-05-19 12:24:50 +02:00
Francisco López
ab412165b2 Set correct indentation 2017-05-19 12:21:00 +02:00
Francisco López
7974fb0a05 Remove editor.test.js 2017-05-19 12:20:22 +02:00
Francisco López
59c0208caa Fix tests.
Now we don't run the parser so there is no errors, we can't look for errors.
2017-04-17 16:00:07 +02:00
Francisco López
9b5fb6a408 Make the error flag more strict 2017-04-17 15:48:55 +02:00
Daniel García Aubert
945f5efb74 Release 0.15.1-cdb3 2017-03-07 11:10:29 +01:00
Daniel
decdcc5d46 Merge pull request #30 from CartoDB/support-multiple-values
Support multiple values/operands
2017-03-07 11:03:23 +01:00
Daniel García Aubert
34bd0a045d Merge branch 'master' into support-multiple-values 2017-03-07 10:46:59 +01:00
Daniel
ed819c3b51 Merge pull request #29 from CartoDB/24-reference-error
Support `=~` operator
2017-03-07 10:43:04 +01:00
Daniel García Aubert
a42afef5a8 Add assertions 2017-03-06 19:20:01 +01:00
Daniel García Aubert
410ecfd3c7 Use constructor for test validation 2017-03-06 12:04:29 +01:00
Daniel García Aubert
8c75b4b0c6 Remove spaces 2017-03-06 11:59:41 +01:00
Daniel García Aubert
fd94fbd2e6 Support numbers for '=~' operator 2017-03-06 11:57:07 +01:00
Daniel García Aubert
5c4bed9593 Keep spaces 2017-03-06 11:38:59 +01:00
Raul Ochoa
2c092f6b39 Support multiple values/operands 2017-03-05 23:31:32 +01:00
Daniel García Aubert
cc2104eb49 Fix typo 2017-03-03 15:35:57 +01:00
Daniel García Aubert
c780998dc8 Prevent TypeError when parsing '=~' operator 2017-03-03 15:27:42 +01:00
Daniel García Aubert
0063ddba7f Support '=~' operator 2017-03-03 14:36:14 +01:00
Francisco López
510847a3b2 Make the validation in the correct place 2017-02-03 16:49:30 +01:00
Raul Ochoa
0c1990f655 Merge branch 'master' into reference-errors 2017-02-01 12:25:57 +01:00
Raul Ochoa
7315428079 Adds test to validate supported features are validated and work 2017-02-01 12:15:46 +01:00
Raul Ochoa
515fbd0991 Only set previous reference if it existed 2017-02-01 11:23:39 +01:00
Raul Ochoa
975abe9b5c Set tree data just before render happens 2017-02-01 11:16:03 +01:00
Raul Ochoa
b6186d884c Merge pull request #26 from CartoDB/revert-21-reference-errors
Revert "Throw an error when not valid ccss"
2017-02-01 11:12:22 +01:00
Raul Ochoa
e6ba32bc07 Revert "Throw an error when not valid ccss" 2017-02-01 10:44:42 +01:00
Raul Ochoa
fd4caf7595 Merge pull request #21 from CartoDB/reference-errors
Throw an error when not valid ccss
2017-01-31 15:36:38 +01:00
Raul Ochoa
45e59f6a5b Revert changes not needed for PR 2017-01-31 15:23:18 +01:00
Raul Ochoa
dfecbbb976 Revert changes in rendering_js test 2017-01-31 15:22:13 +01:00
Raul Ochoa
50fa97564e Strict parser mode.
When options.strict is set to true, it throws parse errors associated to invalid rules,
or in general rules not compliant with the given reference.
2017-01-31 15:19:47 +01:00
Raul Ochoa
8f9982c313 Fix reference to make test pass due to valid error message 2017-01-31 14:50:56 +01:00
Raul Ochoa
01c6f0c6e5 Validate the expected error message, make test to fail 2017-01-31 14:50:09 +01:00
Raul Ochoa
7e02aac641 Reference options from a valid object 2017-01-31 11:31:11 +01:00
María Checa
4f792fbead Merge pull request #25 from CartoDB/allow_expression_in_polygon_pattern_file
Added expression property to `polygon-pattern-file` and `line-pattern-file`
2017-01-19 18:18:05 +01:00
María Checa
9e12a3b0d8 Added expression property to line-pattern-file 2017-01-19 16:44:49 +01:00
María Checa
09d6384b1f Added expression property to polygon-pattern-file 2017-01-19 16:39:10 +01:00
Raul Ochoa
11ffba0a8e Isolate reference as options test 2017-01-17 17:34:28 +01:00
Francisco López
199d41f20d Throw an error when not valid ccss 2017-01-11 11:39:33 +01:00
Raul Ochoa
e931f91475 Merge pull request #20 from CartoDB/more-expressions
More expressions
2017-01-04 17:32:21 +01:00
Raul Ochoa
11d597e733 Accept expressions in marker file 2017-01-03 19:03:39 +01:00
Raul Ochoa
1960aca276 Accept expresions in polygon fill 2017-01-03 19:03:10 +01:00
Raul Ochoa
f17aea8657 Merge pull request #18 from CartoDB/turbo-carto-fn-support
Adds support for other turbo-carto functions
2016-07-20 14:32:34 +02:00
Raul Ochoa
dfaed546e0 Remove only from test 2016-07-20 13:19:00 +02:00
Raul Ochoa
803f0c0a49 Adds support for other turbo-carto functions 2016-07-20 13:17:10 +02:00
Raul Ochoa
0d6f9d4634 Ignore idea based configurations 2016-07-20 13:08:41 +02:00
javi santana
c042733845 Merge pull request #17 from CartoDB/update_reference
adds expression: true for attributes supported by turbo
2016-06-01 14:37:37 +02:00
javi
1fc486b1b9 adds expression: true for attributes supported by turbo 2016-06-01 14:31:14 +02:00
Buti
b50ee48386 Merge pull request #16 from CartoDB/turbo
adds parser support for arrays and also updates reference to add "ram…
2016-05-31 15:12:06 +02:00
javi
cf5886579f adds parser support for arrays and also updates reference to add "ramp" function 2016-05-31 12:40:31 +02:00
Buti
72d005a082 Merge pull request #15 from CartoDB/fix-parser-underscore-template
Fix parser's use of underscore's template
2016-05-30 16:45:18 +02:00
nobuti
9e8c90b6f9 Bumped version of underscore.
Updated some method calls.
Dist generation files.
2016-05-30 16:06:04 +02:00
nobuti
d3e23dcb5d Fix parser's use of underscore's template method to work with modern use of it. 2016-05-30 14:05:42 +02:00
Raul Ochoa
176886f1ad Use build status from cartodb repo 2016-04-27 15:37:35 +02:00
Raul Ochoa
673cf38121 Add test for non-dot notation filters 2016-04-27 15:23:01 +02:00
Raul Ochoa
860bc0adeb Merge pull request #14 from CartoDB/filter-avoid-dot-notation
Avoid using dot notation to allow complex keys
2016-04-27 15:12:51 +02:00
Raul Ochoa
be56e24d9a Avoid using dot notation to allow complex keys, e.g., mapnik::geometry_type 2016-04-26 18:19:53 +02:00
javi santana
27850ed122 Merge pull request #12 from CartoDB/fixed_strings_with_quote
fixed cartocss with single quoted strings
2016-02-24 12:28:45 +01:00
javi
0d2dddf978 fixed cartocss with single quoted strings 2016-02-24 12:16:29 +01:00
Raul Ochoa
fba91a0633 Merge pull request #11 from CartoDB/missed_small_changes
Missed changes
2015-12-14 17:44:59 +01:00
Young Hahn
1612b5a8b7 Drop xml2js and thus mml2json.
(cherry picked from commit bde0d0e2ab)
2015-11-30 16:14:53 -08:00
Paul Norman
4f13aabb6c Update less link
Update Less page link to match the current layout
(cherry picked from commit 73e6726e089ff47fae5115183aa3776918946ab7)
2015-11-30 13:56:53 -08:00
Raul Ochoa
8050ec843f Release 0.15.1-cdb1 2015-02-20 19:48:15 +01:00
Francisco Dans
885849fe82 Merge pull request #9 from CartoDB/image-filters-fixes
Image filters fixes
2015-01-21 14:41:51 +01:00
Francisco Dans
14c0d3f550 adds color sequence test 2015-01-21 14:38:43 +01:00
Francisco Dans
0f46b57020 returns correct object for color list 2015-01-21 12:54:04 +01:00
Francisco Dans
152954ee70 adds additional filters to ref 2015-01-21 12:53:13 +01:00
Francisco Dans
2631c928b7 adds Makefile to produce uncompressed and minified dists 2015-01-19 14:29:11 +01:00
Francisco Dans
9e3ae6e6fd adds uncompressed version of carto 2015-01-12 18:39:49 +01:00
Francisco Dans
eae8886b95 new dist 2015-01-12 18:35:15 +01:00
Francisco Dans
00c7a631b1 Merge pull request #8 from CartoDB/icon-list
Adding method to extract all "marker-file" properties
2015-01-08 17:35:53 +01:00
Francisco Dans
bd03e0c454 adds basic test for marker-files 2015-01-08 17:30:49 +01:00
Francisco Dans
981e117731 uses proper equality checks 2015-01-08 17:10:43 +01:00
Francisco Dans
d2a557acd9 removes old reference, renames method 2015-01-08 16:40:02 +01:00
Francisco Dans
f91ac22bfc adds method to gather image asset file names 2015-01-08 16:21:24 +01:00
javi
db1cf0a8aa missing torque prop 2014-12-18 11:02:20 +01:00
javi
46b3f4857f version bump 2014-12-18 09:04:03 +01:00
javi
b5b03cc8d7 expose the default reference used 2014-12-18 09:02:13 +01:00
javi
8f86216fe0 updates the reference to work with the new parser
see db3c03bd1a
2014-12-18 09:01:59 +01:00
Raul Ochoa
bbfe3b3084 Merge pull request #6 from CartoDB/browserify_fixes
browserify fixes
2014-12-17 14:47:42 +01:00
javi
56d69ab68a browserify fixes 2014-12-17 14:35:42 +01:00
javi
e491c64ceb improved package.json 2014-12-16 18:39:18 +01:00
Raul Ochoa
26c30d2fb5 Remove doc block for non-existent param 2014-12-09 12:52:42 +01:00
Raul Ochoa
6ae21b3ee0 Merge pull request #4 from CartoDB/remove-run-coverage-on-travis
Remove coverage run from travis configuration as it is failing
2014-12-05 17:36:42 +01:00
Raul Ochoa
12d1b1a4fe Merge pull request #3 from CartoDB/console-output-on-debug-only
Only use console output if debug option is present
2014-12-05 17:36:24 +01:00
Raul Ochoa
8a5f75546f Remove coverage run from travis configuration as it is failing 2014-12-05 13:06:53 +01:00
Raul Ochoa
74d807a3ae Only use console output if debug option is present 2014-12-05 13:01:15 +01:00
Raul Ochoa
bf6537071a Check for style property 2014-12-04 19:09:43 +01:00
Raul Ochoa
1cb891ef92 Return style function from shader 2014-12-04 18:58:37 +01:00
javi
79a770f0af removed unnused files 2014-12-04 18:43:13 +01:00
javi
44610ab1c4 using torque reference 2014-12-04 18:42:30 +01:00
javi
7c35dda115 Merge remote-tracking branch 'origin/browserify' into update_to_master
Conflicts:
	.gitignore
	.travis.yml
	CHANGELOG.md
	Makefile
	README.md
	bin/mml2json.js
	lib/carto/index.js
	lib/carto/parser.js
	lib/carto/tree/call.js
	lib/carto/tree/filterset.js
	lib/carto/tree/reference.js
	lib/carto/tree/ruleset.js
	package.json
	test/errorhandling.test.js
	test/errorhandling/invalid_value.mss
	test/errorhandling/invalid_value.result
	test/errorhandling/issue297.mss
	test/errorhandling/issue297.result
	test/rendering-mss.test.js
	test/rendering/transforms.mss
	test/rendering/transforms.result
	test/specificity/demo.result
	test/specificity/filters_and_ids.result
	test/specificity/issue60.result
	test/version.test.js
2014-12-04 18:35:24 +01:00
javi
146976c8a3 using process.browser instead of type window 2014-12-04 17:29:15 +01:00
javi
ae111041dd making explicit imports 2014-12-04 17:28:54 +01:00
javi
2e00705b64 adds support for browserify
this pr also changes some code to make compatible with the browser. It exposes the global variable "carto"
One of the problems is mapnik reference, the module is not read to work with browserify since it loads modules dynamically. It does not make sense to make it compatible since mapnik-reference is not going to be used so people must to set the reference needed before parse/render:

carto.tree.Reference.setData(reference)
2014-12-04 16:29:20 +01:00
Dane Springmeyer
b19ade3850 use latest mapnik-reference@6.0.2 2014-09-26 13:10:51 -07:00
Dane Springmeyer
fe770630bb Add test for proposed syntax for passing variable to mapnik (just as string) - refs #269 2014-09-25 18:57:48 -07:00
Dane Springmeyer
8df31b4fe6 Merge pull request #373 from mapbox/expressions
Al(most) all symbolizer properties as expressions
2014-09-25 18:56:53 -07:00
Dane Springmeyer
e29b900342 call this branch v0.14.0 2014-09-25 18:17:48 -07:00
Dane Springmeyer
150e7166f6 mapnik ref: default to Mapnik 3.0.0 2014-09-25 18:13:35 -07:00
Dane Springmeyer
1d6e4a6f5d drop node v0.8.x testing on travis 2014-09-25 15:12:54 -07:00
Dane Springmeyer
52343ff833 text-name is an expression so flat string is okay - tweak issue297 to actually be invalid since text-face-name is not currently an expression 2014-09-25 15:06:56 -07:00
Dane Springmeyer
544491b91d quoting of enumerations is okay, so let this be okay to carto tests 2014-09-25 14:58:58 -07:00
Dane Springmeyer
7d3ba895f5 trim rendering results to avoid comparing ending newlines 2014-09-25 14:56:29 -07:00
Dane Springmeyer
d5bf19a64f remove back compat shim now that new mapnik-ref@6.0.0 is tagged 2014-09-25 12:37:52 -07:00
Dane Springmeyer
5890802d6d use new mapnik-reference@6.0.0 2014-09-25 12:33:06 -07:00
Saman Bemel-Benrud
26a918d2e6 Merge pull request #372 from mapbox/fix-formatting
Fix formatting
2014-09-25 12:33:48 -04:00
samanpwbb
1ba5e0035e Re-generate 2014-09-25 12:24:01 -04:00
samanpwbb
1e0657ee1e bump / add newline 2014-09-23 18:58:14 -04:00
samanpwbb
2a5c85a9d5 smaller headings 2014-09-23 18:54:19 -04:00
samanpwbb
cbd4c0250d better rules 2014-09-23 18:52:24 -04:00
samanpwbb
08ca40d3f7 try linebreaks 2014-09-23 18:51:11 -04:00
samanpwbb
9386a56464 whitespace adjustments 2014-09-23 18:47:10 -04:00
samanpwbb
d24998705d adjust docs formatting 2014-09-23 18:45:55 -04:00
Dane Springmeyer
4de08ce68d new mapnik-reference that declares all expressions for mapnik 3.x 2014-09-23 15:31:58 -07:00
samanpwbb
9f65589279 formatting fixes 2014-09-23 18:19:02 -04:00
Saman Bemel-Benrud
6f2ec7b335 Merge pull request #371 from mapbox/no-site
add minimal markdown docs
2014-09-23 18:10:13 -04:00
samanpwbb
89a282be82 add minimal markdown docs 2014-09-23 17:52:36 -04:00
Dane Springmeyer
51baca34ae if test does not error (as expected) then assert false instead of hanging 2014-09-23 14:22:59 -07:00
Young Hahn
481a0fc406 0.13.0 2014-09-04 17:22:19 -04:00
Young Hahn
60e33e609b Merge pull request #366 from mapbox/newref
Use newer mapnik ref
2014-09-04 17:17:10 -04:00
Young Hahn
339d781ca6 Use newer mapnik ref. 2014-09-04 16:34:48 -04:00
Tom MacWright
b7819be42d Merge pull request #365 from mapbox/variable-transforms-test-coverage
Add support for variable transforms - refs #163, and improve test covera...
2014-09-03 15:21:16 -04:00
Tom MacWright
c570c2cd0e Add support for variable transforms - refs #163, and improve test coverage 2014-09-03 15:12:31 -04:00
javi santana
30b4fe1fc6 Merge pull request #2 from CartoDB/update_to_master_fix
env parameter added to several function calls
2014-08-29 10:52:11 +02:00
Jan Marsch
1ddefbe8eb env parameter added to several function calls
filtercondition-value now properly quotes strings
2014-08-25 12:34:29 +02:00
Young Hahn
57ddf46813 0.12.1 2014-08-02 21:47:27 -04:00
Young Hahn
ba720bcb84 Update changelog 2014-08-02 21:42:03 -04:00
Young Hahn
d608fa93b8 0.12.0 2014-08-02 21:41:17 -04:00
Young Hahn
bb81e5c785 Merge pull request #364 from mapbox/no-xml2js
Drop xml2js and thus mml2json
2014-08-02 18:40:27 -07:00
Young Hahn
bde0d0e2ab Drop xml2js and thus mml2json. 2014-08-02 17:40:54 -04:00
Tom MacWright
145f1cc0b1 test color fns 2014-07-03 12:00:40 -04:00
Tom MacWright
2273e0174f Test colors and comments 2014-07-03 11:53:12 -04:00
Tom MacWright
b9a00ed68b Use coveralls and add coverage 2014-07-03 11:44:35 -04:00
javi
a40a87cd39 modified to support rendering order and symbolizer 2014-06-23 18:03:28 +02:00
javi
88bddbabc4 fixed makefile lowercase :( 2014-06-16 18:58:14 +02:00
javi
ef59be8abf fixed filter set (need test) 2014-06-16 18:58:04 +02:00
javi
b83d9c4037 fixed makefile 2014-06-16 18:57:50 +02:00
javi
678157b478 added getShader 2014-06-16 18:57:42 +02:00
javi
1a21c88484 added a test for filter based render 2014-06-16 18:57:24 +02:00
javi
ddb4bd338b moved code where it belongs 2014-06-16 12:54:42 +02:00
javi
6fad5676b4 removed renderer options, just return styles 2014-06-16 12:45:17 +02:00
javi
6dc65cc991 update to mapbox 0.0.11 master 2014-06-11 17:52:06 +02:00
javi
10b81aa2c8 updated doc 2014-06-06 10:04:19 +02:00
javi
2fc7473a8c added rectangle as marker-type 2014-06-06 10:04:11 +02:00
Young Hahn
b675a648c0 0.11.0 2014-05-20 17:15:09 -04:00
Young Hahn
e0fe7bce17 Merge pull request #346 from mapbox/sync
Sync
2014-05-20 17:12:17 -04:00
Young Hahn
eb2623d677 Add tests that run the carto binary. 2014-05-20 17:00:37 -04:00
Young Hahn
302d409fab Update error handling tests. 2014-05-20 16:10:31 -04:00
Young Hahn
c5d8f4510e Switch carto to be synchronous. 2014-05-20 16:01:43 -04:00
Young Hahn
cd18bea7ba 0.10.0 2014-05-15 00:25:17 -04:00
Young Hahn
55b70b86c8 Merge pull request #342 from mapbox/no-max-extent
Nuke forced maximum-extent
2014-05-14 19:52:17 -04:00
Young Hahn
d9f97d3202 Nuke forced maximum-extent property to enable buffered geometries beyond visual extent. 2014-05-08 16:37:12 -04:00
Dane Springmeyer
a52412c41e Merge pull request #340 from scw/patch-1
minor typos.
2014-04-28 18:16:09 -07:00
Shaun Walbridge
be4e687aa3 minor typos. 2014-04-28 21:02:08 -04:00
Dane Springmeyer
4b386326c7 don't output style if opacity and comp-op are defaults - refs #219 and #339 2014-04-28 16:05:59 -07:00
Dane Springmeyer
4f771ed2a5 fix #339 2014-04-28 15:57:51 -07:00
Dane Springmeyer
580e946cc0 add currently failing testcase for #339 2014-04-28 15:56:52 -07:00
Dane Springmeyer
5dc4610785 use latest mapnik-reference 2014-04-25 16:11:28 -07:00
Dane Springmeyer
9d44691111 fix #313 2014-04-17 10:38:32 -07:00
Dane Springmeyer
bb2c045325 add testcase for #315 if I'm understanding right should produce no output 2014-04-10 22:20:33 -04:00
Dane Springmeyer
9f18e9cc2c bump to v0.9.6 in package.json 2014-04-10 22:10:23 -04:00
Dane Springmeyer
c6f787f761 add v0.9.6 to changelog 2014-04-10 22:06:27 -04:00
Dane Springmeyer
cfab4f6369 fix #303 2014-04-10 22:03:09 -04:00
Dane Springmeyer
fb06bb4bb1 fix #319 2014-04-10 21:51:19 -04:00
Dane Springmeyer
87f57bdb38 Merge pull request #319 from mojodna/text-face-name-escaping
[bug] text-face-names containing &s aren't escaped properly
2014-04-10 21:50:25 -04:00
Dane Springmeyer
b9309a7f80 Merge pull request #308 from strk/aposquote
Fix escaping of single quote.
2014-04-10 21:22:50 -04:00
Dane Springmeyer
96886c73c6 use mapnik-reference@5.0.8 2014-04-10 21:07:47 -04:00
Dane Springmeyer
443b81012e upgrade underscore and xml2js 2014-04-10 21:02:00 -04:00
Dane Springmeyer
f691a47306 more readme modernization tweaks 2014-04-07 16:56:25 -04:00
Dane Springmeyer
02a657f373 modernize readme and move cascadenik mentions to https://github.com/mapbox/carto/wiki/Differences-With-Cascadenik 2014-04-07 16:49:27 -04:00
Dane Springmeyer
a5e5c045c9 add support for image-filters-inflate - refs mapnik/mapnik#2165 2014-02-27 10:41:43 -08:00
javi
c288d09dcc dist file 2014-02-25 09:53:20 +01:00
javi
5c8963f02d added source-over to reference 2014-02-25 09:53:15 +01:00
Dane Springmeyer
12a3d6cad2 start using mapnik-reference master 2014-02-19 11:57:58 -08:00
Tom MacWright
bbdb6e5988 Merge pull request #325 from iwillig/iwillig/fix-jsdocs
IW: Fix docs strings
2014-02-07 08:04:55 -05:00
Ivan Willig
f6a76ec666 IW: Fix docs strings 2014-02-07 07:41:03 -05:00
javi
83c478875f backport from master to support unsigned types 2014-01-23 17:46:13 +01:00
javi
449be47e91 dist file 2014-01-21 10:17:27 +01:00
javi
a240695b65 added torque properties to general reference 2014-01-21 10:17:22 +01:00
Dane Springmeyer
84618be5df travis: stop testing on node v0.6.x as per isaacs/npm#4379 2014-01-07 16:04:10 -08:00
samanpwbb
f333705cc2 revert rename 2013-12-09 20:39:54 -05:00
javi
f45aa875b7 fixed keywords 2013-11-26 18:39:07 +01:00
javi
834985ba81 dist files 2013-11-25 16:36:25 +01:00
javi
d1c6c9fb82 fixed rendering with variables 2013-11-25 16:36:14 +01:00
Seth Fitzsimmons
0b584a84e7 Failing test for text-face-names containing &s 2013-11-22 16:36:57 -08:00
javi
fadcb3391d added utility function eval to get the real value for a property 2013-11-18 17:31:18 +01:00
javi
66d7c45a0c fixed string eval 2013-11-18 17:31:00 +01:00
Tom MacWright
65a7d6589e Merge pull request #314 from strk/exponent-in-filter
Support exponential notation in filter values
2013-11-07 07:09:11 -08:00
Sandro Santilli
ce4c61cc7f Support exponential notation in filter values
Closes #311
Includes testcase
2013-11-07 12:48:19 +01:00
javi
677ed7122a dist file 2013-11-05 08:52:14 +01:00
javi
f3fd24583d fixed IE7 and IE8 script loading 2013-11-05 08:52:08 +01:00
Dane Springmeyer
5a7429d30d Bump to v0.9.5 with many fixes and optimizations 2013-10-25 13:23:25 -07:00
Tom MacWright
cb177b7a24 Throw a more descriptive error for invalid colors in ops. Fixes #309 2013-10-23 19:45:19 -04:00
Dane Springmeyer
15140e49ee travis: add more node versions 2013-10-18 18:07:25 -04:00
Dane Springmeyer
c4f02e4681 update developing doc 2013-10-05 16:06:14 -07:00
Sandro Santilli
60030b1e69 Fix escaping of single quote. Closes #307, updates tests. 2013-10-03 13:40:50 +02:00
Dane Springmeyer
21f03b3f9e track latest mapnik-reference 2013-09-26 16:20:26 -07:00
Dane Springmeyer
922740da5a fix direct-image-filters output and add test for image-filters 2013-09-25 17:21:27 -07:00
Dane Springmeyer
c1d750a246 Merge pull request #306 from dboze/fix-drainpool
check for millstone.drainpool only if -l flag is passed
2013-09-24 13:34:39 -07:00
Daniel Bozeman
72a79ab073 checks for millstone.drainpool only if -l flag is passed 2013-09-24 15:16:17 -05:00
Dane Springmeyer
f6dea7fcb3 drain millstone pool forcefully so we can exit quickly - refs #301 and mapbox/millstone#110 2013-09-23 18:55:38 -07:00
Tom MacWright
4b5e217107 Merge pull request #305 from yohanboniface/empty_name_invalid_xml
Fix empty name value resulting in non closed tag
2013-09-21 08:12:24 -07:00
Yohan Boniface
6b51cd370b Fix empty name value resulting in non closed tag 2013-09-21 17:05:10 +02:00
Dane Springmeyer
1c5c84587e bump mapnik-reference version and prep changelog for v0.9.5 release 2013-09-19 10:57:57 -07:00
Tom MacWright
2c9692b029 Merge pull request #294 from kapouer/patch-1
layer srs is inherited from map srs
2013-09-17 07:26:08 -07:00
Tom MacWright
3a6cae7836 Merge pull request #300 from strk/master-invalid-non-selfclosing-mml
Integrate mml parsing test for issue #297
2013-09-17 07:25:40 -07:00
Sandro Santilli
ba51c74771 Integrate mml parsing test for issue #297 2013-09-12 10:40:35 +02:00
Jérémy Lal
bd454b4b6b Add test for #294 2013-09-12 10:23:46 +02:00
Jérémy Lal
b63fe0a9af layer srs is inherited from map srs
Do not output srs attribute when srs is undefined,
since layer srs is inherited from map srs, it is optional.
2013-09-12 09:56:45 +02:00
Tom MacWright
525bdc5bef Add test for #197. Fixes #197 2013-09-11 11:26:28 -04:00
Tom MacWright
5938ebb609 Correctly deal with invalid content in non-selfclosing tags. Fixes #297 2013-09-11 11:18:40 -04:00
javi
d99c5d6c14 dist files 2013-08-28 12:37:51 +02:00
javi
2bfc8a154b uniq frames 2013-08-28 12:37:45 +02:00
javi
5a8461a6c1 fixed renderer 2013-08-28 12:12:06 +02:00
javi
dfacf064e8 added frame offset 2013-08-28 11:52:18 +02:00
javi
6562d77935 fixed js renderer 2013-08-27 17:34:55 +02:00
Tom MacWright
2ead8dafa2 Backtrack on invalid zoom. Fixes #290 2013-08-09 19:58:52 -04:00
Tom MacWright
b18f162d87 Merge pull request #293 from yohanboniface/attachment-tests
More attachment tests (cf #245)
2013-08-09 14:53:18 -07:00
Yohan Boniface
8497bd36f7 More attachment tests (cf #245) 2013-08-09 22:03:55 +02:00
Dane Springmeyer
2729aefd6e Merge pull request #292 from yohanboniface/attachment-tests
Attachment tests (cf #245)
2013-08-09 09:40:49 -07:00
Yohan Boniface
7a3b659d4e More tests for attachement (cf #245) 2013-08-09 15:39:12 +02:00
Yohan Boniface
50a27c213c Add test/rendering-mss/npm-debug.log to .gitignore 2013-08-09 15:37:06 +02:00
Dane Springmeyer
d1af35763a add currently failing test for #290 2013-08-07 20:46:32 -04:00
Tom MacWright
92b67a0a73 Test for #288 to keep it closed, space before zoom bug 2013-08-06 11:45:02 -04:00
Tom MacWright
d170825685 Merge pull request #289 from javisantana/master
fixes mapbox/carto#288
2013-08-06 08:42:38 -07:00
javi
d9e412696d dist files 2013-08-05 18:16:26 +02:00
javi
08db949fbf fixes mapbox/carto#288 2013-08-05 18:16:10 +02:00
javi
d788fb8f07 fixes mapbox/carto#288 2013-08-05 18:12:42 +02:00
javi
e32aeb9fbb dist files 2013-07-31 16:53:38 +02:00
javi
d7d49214cd support for uris in javascript compiler 2013-07-31 16:53:33 +02:00
javi
ca42300648 dist 2013-07-31 12:43:09 +02:00
javi
393fc249b2 be able to set the the reference 2013-07-31 12:33:39 +02:00
javi
2ae7743c7d created assert stub for the browser 2013-07-31 12:33:19 +02:00
javi
1d752708c4 improved makefile 2013-07-31 12:32:55 +02:00
javi
4f963b8bb3 error management, an exception is thrown when there is a parse error 2013-07-29 13:55:31 +02:00
javi
bc9d67b328 dist file 2013-07-29 12:43:05 +02:00
javi
656b6db9a3 added rendered to transform cartocss to javascript object 2013-07-29 12:42:59 +02:00
javi
a57a162248 fixed build 2013-07-29 12:42:41 +02:00
Dane Springmeyer
60396cbeef add support for direct-image-filters 2013-07-24 22:03:13 -04:00
Dane Springmeyer
f88711db72 add scale-hsla function to work with latest mapnik-reference: 66502bfde0 2013-07-24 21:59:05 -04:00
Tom MacWright
4f0e998dab Merge branch 'master' of github.com:mapbox/carto 2013-07-16 14:09:09 -04:00
Tom MacWright
fd3338ccb5 Add developing docs 2013-07-16 14:08:54 -04:00
javi
3d87cd490d fixed global imports 2013-07-11 16:14:32 +02:00
Dane Springmeyer
1d637717d5 fix typo - refs #245 2013-07-09 15:41:00 -04:00
Dane Springmeyer
45fae55ac0 add more variations (currently passing) on the issue #284 test 2013-07-09 15:33:42 -04:00
Tom MacWright
bbeff81a16 Fix byFilter regression 2013-07-09 15:00:13 -04:00
Tom MacWright
afac483b35 More comments
:
2013-07-09 14:38:27 -04:00
Tom MacWright
66b0c1ff7a Update mocha and optimist dependencies 2013-07-09 14:03:59 -04:00
Dane Springmeyer
93264c6e41 add test for #284 - passes as 6eda91a and fails in master 2013-07-02 19:00:33 -04:00
javi
97f03a2eb3 added functions to browser build 2013-06-26 14:50:53 +02:00
Young Hahn
45b5c107af Merge pull request #279 from mapbox/dataonly
Allow Layer-only rendering without corresponding styles
2013-06-24 08:58:48 -07:00
javi
99ee75ab8b added tree functions to cart build for browser 2013-06-24 17:15:35 +02:00
Tom MacWright
84e0628a8e Merge pull request #280 from mapbox/custom-params
Allow custom map parameters
2013-06-17 06:16:50 -07:00
Young Hahn
15ab78a3a8 Allow custom map parameters. 2013-06-14 18:25:55 -04:00
Young Hahn
bb153521e2 Allow Layer-only rendering without corresponding styles. 2013-06-14 12:21:13 -04:00
Young Hahn
ff0fc2b1c8 Revert "Dimensions." Refs #258.
This reverts commit 0f65b869fd.
2013-06-14 10:47:43 -04:00
javi
264925324f updated reference to 2.1 2013-06-13 15:42:47 +02:00
javi
d81b4b9d18 browser version working 2013-06-10 13:26:19 +02:00
Tom MacWright
4cc262b563 Merge pull request #277 from mapbox/fix-attachment-order-breakage
fix #247
2013-06-01 09:13:50 -07:00
Dane Springmeyer
4203578093 fix #247 2013-05-31 17:17:36 -07:00
Dane Springmeyer
cfc90da91e Add regression test for issue #247 - broken in master at 4d4abb2, works in 6eda91a 2013-05-31 17:15:45 -07:00
Dane Springmeyer
8786bf51c7 bin/carto: be careful not to resolve inline stylesheet data 2013-05-29 15:52:59 -07:00
Dane Springmeyer
ae95aa7575 fix repository in package.json 2013-05-22 19:16:35 -07:00
Tom MacWright
b113bfea99 Prevent image filter duplication. Fixes #270 2013-04-16 10:24:48 -04:00
Tom MacWright
0f65b869fd Dimensions. 2013-04-15 16:10:00 -04:00
Tom MacWright
c21a763dc7 Refactor physical unit support 2013-04-15 15:59:17 -04:00
Tom MacWright
207b120dee Fix dimension.js code style 2013-04-15 15:49:39 -04:00
Tom MacWright
49b2324ea1 Handle errant zoom-as-field better. Fixes #269. 2013-04-15 15:45:53 -04:00
Dane Springmeyer
314cef0c75 Merge pull request #271 from mapbox/colorize-alpha
Support variable-length arguments in functions with -1
2013-04-08 13:08:55 -07:00
Tom MacWright
04b1602310 Support variable-length arguments in functions with -1 2013-04-05 16:10:27 -04:00
Tom MacWright
2f76fec686 Do not unnecessarily escape < and chain replacements 2013-03-29 11:47:04 -04:00
Tom MacWright
435452ba50 Merge pull request #265 from strk/master-quote-amp
Quote ampersend chars in XML text. Closes #263.
2013-03-29 08:44:59 -07:00
Tom MacWright
5d626d3c10 Do not output references to empty styles. Fixes #244 2013-03-27 11:59:09 -04:00
Tom MacWright
49c81edd3a Tolerate single-stop colorizers. Fixes #251 2013-03-27 11:48:47 -04:00
Tom MacWright
05797dd711 Tolerate commas in values. Fixes #266 2013-03-27 11:30:51 -04:00
Tom MacWright
86abdcd700 Fix #262,
when selectors are exactly the same, still push them into the list.
2013-03-27 10:44:12 -04:00
Sandro Santilli
d6585d3691 Quote all needed XML chars. See #263.
Includes testcase.
2013-03-27 12:36:25 +01:00
Sandro Santilli
acf94e5fab Quote ampersend chars in XML text. Closes #263.
Includes testcase.
2013-03-22 11:11:16 +01:00
Tom MacWright
60661b68c3 Add failing identity nesting case, refs #262 2013-03-17 19:31:22 -04:00
Tom MacWright
5e2ea67df9 Fix repeated comments in selectors. Fixes #260 2013-03-13 11:27:00 -04:00
Tom MacWright
26dbcf3ca3 Fix test for missing brackets 2013-03-11 16:34:43 -04:00
Tom MacWright
3382bfa29f Merge branch 'missing-bracket' 2013-03-11 16:32:48 -04:00
Tom MacWright
7ac2d81062 Merge pull request #257 from tomhughes/master
Add a man page for carto
2013-03-03 11:30:48 -08:00
Tom Hughes
caa639beb8 Add a man page for carto 2013-03-03 19:05:47 +00:00
Dane Springmeyer
039031b68d fix failing tests in master 2013-03-01 15:22:22 -05:00
Dane Springmeyer
19bf87cf3a fix expected result so test passes because it appears #239 is not actually a regression 2013-03-01 15:14:23 -05:00
Dane Springmeyer
8efc1c5d5e output both path to json mismatches to make debugging test failures easier 2013-03-01 15:08:29 -05:00
Dane Springmeyer
e8566e817e point to master mapnik-reference 2013-03-01 15:08:05 -05:00
root
7deb1b86e0 Merge branch 'layerzoom' 2013-02-26 11:40:58 -05:00
Tom MacWright
c5a67fa938 Missing bracket. Refs #254 2013-02-25 09:17:56 -05:00
Young Hahn
be78202e0b Allow layers to be rendered without datasources. 2013-02-21 15:29:33 -05:00
Tom MacWright
0e0bac0e5c Fix fadeout 2013-02-18 17:34:49 -05:00
Dane Springmeyer
6ca0c705c8 add raster colorizer (https://github.com/mapnik/mapnik/wiki/RasterColorizer) support to master - closes #5 - refs mapbox/tilemill#761 - depends on 41772edbd7 2013-02-12 11:17:56 -08:00
Tom MacWright
4c044f93fe Merge pull request #250 from tomhughes/master
Update xml2js dependency
2013-02-10 12:49:43 -08:00
Tom Hughes
48d89889fe Update to use v0.2.x of xml2js 2013-02-10 20:46:12 +00:00
root
47464fc18d Convert minzoom/maxzoom parameters on Layer properties. 2013-02-04 16:42:57 -05:00
Dane Springmeyer
74fa914c8c updated raster colorizer support 2013-02-01 15:57:29 -05:00
Tom MacWright
55fbafe0d0 Merge branch 'master' of github.com:mapbox/carto 2013-01-31 08:54:41 -05:00
Tom MacWright
5fa4478d40 Fix rgba handling, fixes #246 2013-01-31 08:54:27 -05:00
Dane Springmeyer
633754306e add testcase for issue #239 2013-01-23 16:59:22 -08:00
Dane Springmeyer
3f70b8a36c avoid unneeded whitespace in style XML output 2013-01-23 16:57:27 -08:00
Tom MacWright
16f60edc50 Merge pull request #243 from mapbox/localize-on-demand
Localize on demand
2013-01-23 16:16:39 -08:00
Dane Springmeyer
76b271ebc5 change carto command line to only try to localize mml docs if explicitly requested 2013-01-23 13:01:55 -08:00
Tom MacWright
3fd91ccb6b Predo changelog 2013-01-23 16:01:31 -05:00
Tom MacWright
e2764d12f1 Add rendering tests for units 2013-01-23 16:01:03 -05:00
Dane Springmeyer
8c51c59fe9 Merge pull request #240 from stefanklug/units
Add unit support to css
2013-01-23 12:47:30 -08:00
Stefan Klug
cf8c11f038 replace dpi by ppi 2013-01-23 10:20:04 +01:00
Tom MacWright
31504baabe Merge pull request #241 from stefanklug/extent
don't overwrite maximum-extent when specified in mml
2013-01-21 09:50:41 -08:00
Stefan Klug
8f8c1ad39b remove debug output 2013-01-21 18:43:49 +01:00
Stefan Klug
494c07d383 don't overwrite maximum-extent when specified in mml 2013-01-21 18:28:10 +01:00
Stefan Klug
de40fd4e42 added dpi parameter to carto bin
use optimist to parse command line arguments
2013-01-21 16:40:16 +01:00
Stefan Klug
6a5309e22e fixed regression introduced in last commit 2013-01-21 16:40:16 +01:00
Stefan Klug
56ac678c0a add support for the units m, mm, cm, pt, pc
these are converted to pixels depending on env.dpi (defaulting to 90.714)

adding and subtracting percentages works also
2013-01-21 16:40:15 +01:00
Tom MacWright
1769cb4a59 Merge branch 'master' of github.com:mapbox/carto 2013-01-16 17:42:31 -05:00
AJ Ashton
9deb60cd08 Fix numeric check regex 2013-01-16 17:15:28 -05:00
Tom MacWright
a687bec9b6 Merge pull request #236 from stefanklug/fix-global-install
fix carto to work as global node module
2013-01-11 05:41:46 -08:00
Stefan Klug
c9d88add12 fix carto to work as global node module 2013-01-11 13:17:58 +01:00
Tom MacWright
6ce476d2a2 parseFloat sucks 2013-01-08 16:42:50 -05:00
Dane Springmeyer
0d686bb8d8 fixup code comments - amends 87d4f9627b 2013-01-06 11:58:14 -08:00
Tom MacWright
f3bde1fde3 No more in operator, checking for undefined is much much faster 2013-01-04 18:45:19 -05:00
Dane Springmeyer
bfc9d40f43 fix testcase issue # 214 -> 204 - previously incorrectly referenced 214 - refs #214 and #204 2013-01-03 18:47:10 -08:00
Tom MacWright
f0e245183a Fix hang around massive stylesheets, fixes mapbox-base 2013-01-03 18:43:12 -05:00
Tom MacWright
4d4abb27b5 clean up renderer, possibly fix issue with attachments, work on function names 2013-01-03 18:32:04 -05:00
Tom MacWright
6eda91a541 Fixing basic jshint issues 2013-01-03 16:29:22 -05:00
Tom MacWright
87d4f9627b Rename eval to ev to fix many jshint problems and future safari problems 2013-01-03 16:19:25 -05:00
Tom MacWright
29b641c72d Detect and report basic self-conflicting filters.
This does not yet report anything for more complex cases. We were also testing for
tolerance of self-conflicting filters, which this breaks, in
partial_overrides. This patch may want to wait for warning reporting
since it may break stylesheets in the wild.
2013-01-03 15:06:07 -05:00
Tom MacWright
6f687ff9e3 Remove throws in function evaluation and handle color function arguments
correctly.
2013-01-03 13:47:51 -05:00
Tom MacWright
ece3eb3b0e Remove throw in zoom evaluation 2013-01-03 13:20:49 -05:00
Tom MacWright
d97286de57 Merge branch 'master' of github.com:mapbox/carto 2013-01-03 10:12:02 -05:00
Tom MacWright
c1e8c3b8f3 Support mapnik keywords in filters and error on bare keywords. Fixes 2013-01-03 10:11:30 -05:00
Dane Springmeyer
3cefa968a0 remove Mapnik2 name in readme for more explicit version string 2013-01-02 10:50:40 -08:00
Tom MacWright
b7773c6452 Tolerate everything but ] in field names. Fixes #230 2013-01-02 11:31:17 -05:00
Tom MacWright
9ed9c2b028 Merge pull request #231 from mapbox/condense
Condense
2013-01-02 08:25:44 -08:00
Tom MacWright
fc500db69b Pay attention to types in filterset, fixes #229. 2013-01-02 11:22:25 -05:00
Tom MacWright
0233c523ea Be backwards compatible for now. 2012-12-29 12:40:31 -05:00
Tom MacWright
4b8256e0f8 Fixing new parser reference to makeError. Fixes #228 2012-12-27 18:19:12 -05:00
Tom MacWright
551571fc17 Refactor 2012-12-27 18:11:48 -05:00
Tom MacWright
43073fa1e8 Further performance tuning, about a 10% improvement on
openstreetmap-carto
2012-12-26 22:23:04 -05:00
Tom MacWright
baab7dd0ec Simplify and unify parser code style 2012-12-26 18:32:47 -05:00
Tom MacWright
f87e8adc95 Unswap specificity tests: ordering is now same as master 2012-12-26 18:24:17 -05:00
Tom MacWright
3f31bcbe5f Fix absolute paths in other symbolizer types 2012-12-26 18:19:39 -05:00
Tom MacWright
9f00195100 Use for in rather than object create 2012-12-26 18:12:01 -05:00
Tom MacWright
6fed91d728 Update chunker, fixes #184. 2012-12-26 17:48:01 -05:00
Tom MacWright
a8133e0d77 Bump mocha version to current 2012-12-26 17:40:33 -05:00
Dane Springmeyer
06b147323f add currently failing test for handling of quoted attributes with : 2012-12-21 18:57:56 -08:00
Dane Springmeyer
47882ccb00 Add another empty rendering test 2012-12-21 18:37:39 -08:00
Dane Springmeyer
73e5178f1f re-enable the standalone-mss rendering tests and fix them by avoiding empty styles and empty rules - closes #219 2012-12-21 18:34:30 -08:00
Dane Springmeyer
8846bfbbcd use console.time for mss benchmarking 2012-12-21 17:49:53 -08:00
Dane Springmeyer
16db1c5b03 fix MSS standalone renderer, fixing failing error handling tests after f6c07afee6 2012-12-21 17:36:18 -08:00
Dane Springmeyer
800122e1af error handling tests should rethrow if the test itself does not throw at all 2012-12-21 17:35:48 -08:00
Dane Springmeyer
3ac86f5fd3 use tree off carto object for consistency 2012-12-21 16:39:29 -08:00
Dane Springmeyer
bd17eed9f5 fix race condition in specificity tests and change expected result so all specificity tests now pass 2012-12-21 16:29:59 -08:00
Dane Springmeyer
c707188ed5 serialize json representation rather than mapnik xml for easier comparision 2012-12-21 16:12:02 -08:00
Dane Springmeyer
539d293388 fix failing zoomselector test by adjusting expected test output as it appears we were testing a bug 2012-12-21 16:11:35 -08:00
Dane Springmeyer
12cd05764b Revert "partially fix regex nest test"
This reverts commit 92d239b7f8.
2012-12-21 15:39:18 -08:00
Dane Springmeyer
92d239b7f8 partially fix regex nest test 2012-12-21 15:32:59 -08:00
Dane Springmeyer
a7c1e0bc49 Merge branch 'condense' of github.com:mapbox/carto into condense 2012-12-21 15:22:25 -08:00
Dane Springmeyer
a55b4ca0e9 disable mss tests for now since they are intentionally failing - lets focus on unintential failures 2012-12-21 15:22:14 -08:00
Tom MacWright
958b61c343 Fix specificity tests 2012-12-21 18:17:59 -05:00
Dane Springmeyer
0a9cb6afe7 Merge branch 'condense' of github.com:mapbox/carto into condense 2012-12-21 15:16:26 -08:00
Tom MacWright
67b66b5568 Fix zoom interpretation in helper 2012-12-21 18:15:31 -05:00
Dane Springmeyer
9967393820 cleanup old failures that now pass 2012-12-21 15:14:36 -08:00
Tom MacWright
75f55f8d04 Update specificity test helper 2012-12-21 18:13:36 -05:00
Dane Springmeyer
2aba917e3d merge master into condense to bring in further race condition fixes in errorhandling tests 2012-12-21 15:13:22 -08:00
Dane Springmeyer
f2a6922586 fix race condition in renderer test and write failures instead of diffing since this can crash on large diffs like the fontset dupe test 2012-12-21 15:05:17 -08:00
Tom MacWright
f6c07afee6 Refine heap performance. Switch back to underscore since lodash does npm
all wrong.
2012-12-21 16:53:38 -05:00
Tom MacWright
22f5d0cc45 Support : in column names 2012-12-20 17:49:11 -05:00
Tom MacWright
f4722f516e Fix tiny, critical bug 2012-12-20 17:42:41 -05:00
Tom MacWright
607c4dba5a Be a little safer with naming 2012-12-20 17:35:03 -05:00
Tom MacWright
b3b7fec337 Compare filters numerically if possible 2012-12-20 15:45:43 -05:00
Tom MacWright
5145655c46 Remove oneline tests for now, update external image 2012-12-20 15:37:12 -05:00
Tom MacWright
a106e2768f Revert "Update chunker to less.js's version. Fixes #184"
This reverts commit 78b3a5a5d4.
2012-12-20 14:36:04 -05:00
Tom MacWright
04cf9013a8 Rewrite zoom filters as objects, support variables in zoom definitions. 2012-12-20 12:37:38 -05:00
Tom MacWright
3c4baaf8cb Update filtervariable test 2012-12-20 11:13:18 -05:00
Tom MacWright
667fd483cc Merge pull request #220 from mapbox/mss-renderer
Mss renderer
2012-12-19 16:35:02 -08:00
Tom MacWright
f693f062ec Allow for filters which compare fields. 2012-12-19 18:59:16 -05:00
Tom MacWright
89f8edbddc Optimize fontset detection 2012-12-19 18:43:05 -05:00
Tom MacWright
73f544333a Remove straggling import stuff 2012-12-19 18:24:35 -05:00
Tom MacWright
d4fe84a7cf Simplify, use lodash where possible 2012-12-19 17:43:35 -05:00
Tom MacWright
84a34be10a Add filter test 2012-12-19 17:37:17 -05:00
Tom MacWright
180cd0cc6e Add field test 2012-12-19 17:34:34 -05:00
Tom MacWright
78ea179c46 Add filterset.eval, support variable filter combinations. 2012-12-19 17:29:53 -05:00
Tom MacWright
9eee907467 Simplify filter system, support numeric variables in filters 2012-12-19 17:01:35 -05:00
Tom MacWright
8603799fa7 Add oneline test 2012-12-19 14:19:13 -05:00
Tom MacWright
78b3a5a5d4 Update chunker to less.js's version. Fixes #184 2012-12-19 14:18:35 -05:00
Tom MacWright
71547d059f Merge and fix renderer optimization' 2012-12-19 12:53:09 -05:00
Tom MacWright
0ebfa5f258 Revert renderer optim 2012-12-19 12:46:06 -05:00
Tom MacWright
4ef52a82af Profile and optimize reference lookups 2012-12-19 12:37:48 -05:00
Tom MacWright
ec0699dd45 Cleanup 2012-12-19 11:53:10 -05:00
Dane Springmeyer
e7ba697fc4 Deeper benchmark output 2012-12-18 19:49:38 -08:00
Dane Springmeyer
0d294c1075 add standalone mss rendering tests for desired empty style dropping behavior - refs #219 2012-12-18 18:25:13 -08:00
Dane Springmeyer
23d11fedc6 skip mss that is valid 2012-12-18 16:40:11 -08:00
Dane Springmeyer
e3e2b42277 do not overwrite filename for mss 2012-12-18 16:25:00 -08:00
Dane Springmeyer
675158cba9 call path.basename on mss filename input 2012-12-18 16:24:14 -08:00
Dane Springmeyer
1e6ede278f add to ignores 2012-12-18 16:14:35 -08:00
Dane Springmeyer
75875e2781 add tests for #214 - test 'a' currently fails for me 2012-12-18 16:12:47 -08:00
Dane Springmeyer
cd948535a5 add test for #218 2012-12-18 16:09:31 -08:00
Dane Springmeyer
33d325b0fc return errors if any have occurred - refs #128 2012-12-18 16:07:50 -08:00
Dane Springmeyer
4825f9aee8 only call done when the test is actually finished, whether error or not - refs #210 2012-12-18 16:02:44 -08:00
Dane Springmeyer
7d7cc2653c add tests specifically of error handling of parsing standalone mss files 2012-12-18 15:51:39 -08:00
Dane Springmeyer
0c04ad07b6 move the mss standalone renderer into core to make it easier to maintain - and add more fine grained benchmark output 2012-12-18 15:44:39 -08:00
Tom MacWright
1c569ce39d Simplify style.js 2012-12-18 17:41:08 -05:00
Tom MacWright
f41312597c Move .is to prototype, start testing quoteds 2012-12-18 17:16:40 -05:00
Tom MacWright
4181df378f Rewrite filterset in more vanilla-javascript style, add more tests 2012-12-18 16:50:53 -05:00
Tom MacWright
13bc8c3488 Start on a condense branch. This is related to #20
See https://gist.github.com/4329932 for more detail.
2012-12-18 13:27:04 -05:00
Tom MacWright
4c7af7f492 Merge pull request #217 from mapbox/stderr
Use stderr for error messages, use console instead of util
2012-12-18 08:57:23 -08:00
Tom MacWright
19ac7b2fb0 Use stderr for error messages, use console instead of util 2012-12-15 19:24:44 -05:00
Tom MacWright
217498a207 Push to 0.9.4 with regex nesting fix 2012-12-06 12:50:04 -05:00
Tom MacWright
25a2940ebc Fix regex nesting inheritance 2012-12-06 12:48:09 -05:00
Tom MacWright
2fcbdaacfd Stricter equality for empty string arguments to functions, and tests. 2012-11-29 15:17:06 -05:00
Dane Springmeyer
8ef4efbe39 fix handling of no errors from error handling tests - closes #210 2012-11-29 11:36:39 -08:00
javi
6fb339e21c test files 2012-07-25 17:42:49 +02:00
javi
4bb0fc0ebe changed some stuff to get cart working on the browser 2012-07-25 17:42:41 +02:00
Tom MacWright
bfc92c26bc Stubbing out assert, trying to get higher-level stylesheets 2012-07-18 17:27:19 +02:00
Tom MacWright
f11259d734 Carto, with a few small tweaks, starting to operate in-browser 2012-07-18 17:27:19 +02:00
cloudhead
2481c21365 deleted compiler.jar 2012-07-18 17:23:52 +02:00
cloudhead
e7a3d65cad Added make min for minification though google's js closure compiler. 2012-07-18 17:23:51 +02:00
336 changed files with 27866 additions and 1934 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
/node_modules
.DS_Store
test/rendering/layers/
test/rendering/cache/
test/rendering-mss/npm-debug.log
.idea/

View File

@ -1,3 +1,9 @@
language: node_js
node_js:
- 0.6
- '6'
- '8'
- '10'
script:
- npm test

9
CHANGELOG.carto.md Normal file
View 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

View File

@ -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
View 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.

View File

@ -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
View File

@ -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>&lt;Stylesheet&gt;&lt;![CDATA[
Map {
map-bgcolor: #69f;
}
Layer {
line-width: 1;
line-color: #696;
polygon-fill: #6f9;
}
]]&gt;&lt;/Stylesheet&gt;
&lt;Layer srs=&quot;+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs&quot;&gt;
&lt;Datasource&gt;
&lt;Parameter name=&quot;type&quot;&gt;shape&lt;/Parameter&gt;
&lt;Parameter name=&quot;file&quot;&gt;world_borders&lt;/Parameter&gt;
&lt;/Datasource&gt;
&lt;/Layer&gt;
&lt;/Map&gt;</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>&lt;FontSet name=&quot;fontset-0&quot;&gt;
&lt;Font face-name=&quot;Georgia Regular&quot;/&gt;
&lt;Font face-name=&quot;Arial Italic&quot;/&gt;
&lt;/FontSet&gt;
&lt;Style name=&quot;world-text&quot;&gt;
&lt;Rule&gt;
&lt;TextSymbolizer fontset-name=&quot;fontset-0&quot;
size=&quot;11&quot;
name=&quot;[NAME]&quot;/&gt;
&lt;/Rule&gt;
&lt;/Style&gt;</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
View File

@ -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");
}

View 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
View 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

File diff suppressed because one or more lines are too long

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
View File

@ -0,0 +1,15 @@
# Generating CartoCSS docs
From the `docs-generator/` directory:
```
$ npm install
```
Then:
```
$ node generate.js
```
Will save docs to `docs/`.

View 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
View 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);
}
```

View 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"
}
}

View 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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

1687
docs/latest.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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';
}

View File

@ -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;
};

View File

@ -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
View 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'));

File diff suppressed because it is too large Load Diff

View File

@ -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;
};
}

View File

@ -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

View File

@ -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'));

View File

@ -9,7 +9,7 @@ tree.Comment.prototype = {
toString: function(env) {
return '<!--' + this.value + '-->';
},
eval: function() { return this; }
'ev': function() { return this; }
};
})(require('../tree'));

View File

@ -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'));

View File

@ -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);
}
};

View File

@ -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'));

View File

@ -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);
}
},

View File

@ -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;
}
};

View File

@ -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 = {
'<': [' &lt; ', '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() {

View File

@ -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(/&amp;/g, '&') + "'" : val) + ")";
}
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/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;
}
}
});
};

View File

@ -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) {

View 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;

View File

@ -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) {

View File

@ -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,

View File

@ -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; }
};

View File

@ -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';
};

View File

@ -12,7 +12,7 @@ tree.Literal.prototype = {
toString: function() {
return this.value;
},
'eval': function() {
'ev': function() {
return this;
}
};

View File

@ -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) {

View File

@ -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, '&apos;');
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
var escapedValue = this.value
.replace(/&/g, '&amp;')
var xmlvalue = escapedValue
.replace(/\'/g, '\\\'')
.replace(/\"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/\>/g, '&gt;');
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)));
}
};

View File

@ -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'));

View File

@ -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);
};

View File

@ -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) {

View File

@ -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();

View File

@ -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'));

View File

@ -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);
}
};

View File

@ -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(/&amp;/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'));

View File

@ -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',

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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
View 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);
});
});
});

View File

@ -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();
});
});
});

View 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"
}
}]
}

View File

@ -0,0 +1,3 @@
#world {
line-width: 20% + 2px;
}

View File

@ -0,0 +1 @@
bad_op.mss:2:4 If two operands differ, the first must not be %

View 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"
}
}]
}

View File

@ -0,0 +1,3 @@
#world {
line-width: 20px * 2%;
}

View File

@ -0,0 +1 @@
bad_op_2.mss:2:4 Percent values can only be added or subtracted from other values

View 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"
}
}]
}

View File

@ -0,0 +1,4 @@
@foo: 'bar';
#world {
polygon-fill: hsl(1, @foo, 3);
}

View File

@ -0,0 +1 @@
color_functions.mss:3:31 incorrect arguments given to hsl()

View 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"
}
}]
}

View File

@ -0,0 +1,3 @@
#world[FeatureCla!=""][FeatureCla=""] {
polygon-fill: #fff;
}

View File

@ -0,0 +1 @@
contradiction.mss:1:37 [[FeatureCla]=] added to [FeatureCla]!= produces an invalid filter

View File

@ -0,0 +1,3 @@
#world[FeatureCla=""][FeatureCla!=""] {
polygon-fill: #fff;
}

View File

@ -0,0 +1 @@
contradiction_2.mss:1:37 [[FeatureCla]!=] added to [FeatureCla]= produces an invalid filter

View File

@ -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)

View File

@ -0,0 +1,3 @@
#world {
polygon-fill: spin(#f00f00f, 10);
}

View File

@ -0,0 +1 @@
invalid_color_in_fn.mss:2:34 incorrect arguments given to spin()

View File

@ -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?

View File

@ -1,5 +1,4 @@
#world[zoom=5] {
text-face-name: 2;
line-rasterizer: 'full';
text-name: 'foo';
}

View File

@ -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.

View 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"
}
}]
}

View File

@ -0,0 +1,3 @@
#world {
line-width: 10wifflewaffles;
}

View File

@ -0,0 +1 @@
invaliddimension.mss:2:4 Invalid unit: 'wifflewaffles'

View File

@ -0,0 +1 @@
issue119.mss:2:2 Map properties are not permitted in other rules

View File

@ -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.

View File

@ -0,0 +1 @@
issue124.mss:6:0 missing closing `}`

View File

@ -0,0 +1,8 @@
{
"Stylesheet": [
"issue297.mss"
],
"Layer": [{
"name": "t"
}]
}

View File

@ -0,0 +1,4 @@
#t {
text-name: valid;
text-face-name: 2;
}

View 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.

View File

@ -0,0 +1,3 @@
#countries {
polygon-fill: green;
}}

View File

@ -0,0 +1 @@
issue_204_a.mss:3:1 missing opening `{`

View File

@ -0,0 +1,3 @@
#countries {
polygon-fill: green;
}}

View File

@ -0,0 +1 @@
issue_204_b.mss:3:3 missing opening `{`

View File

@ -0,0 +1,3 @@
#countries {
polygon-fill: green;
}}

View 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