├── .gitignore ├── .travis.yml ├── Jakefile.js ├── LICENCE.txt ├── README.md ├── bower.json ├── build ├── build.js ├── deps.js └── hintrc.js ├── dist ├── leaflet.pattern-src.js └── leaflet.pattern.js ├── example ├── pattern-circle.html ├── pattern-geojson.html ├── pattern-path.html ├── pattern-rect.html └── pattern-stripes.html ├── package.json ├── spec ├── after.js ├── expect.js ├── happen.js ├── karma.conf.js ├── sinon.js └── suites │ └── onAddSpec.js └── src ├── Pattern.SVG.js ├── Pattern.js ├── PatternCircle.js ├── PatternPath.js ├── PatternRect.js ├── PatternShape.SVG.js ├── PatternShape.js ├── StripePattern.js └── copyright.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | tmp/**/* 4 | .idea 5 | .idea/**/* 6 | *.iml 7 | *.sublime-* 8 | _site 9 | coverage/ 10 | *.js.html 11 | index.html 12 | .mailmap 13 | component.json 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 -------------------------------------------------------------------------------- /Jakefile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.pattern building, testing and linting scripts. 3 | 4 | To use, install Node, then run the following commands in the project root: 5 | 6 | npm install -g jake 7 | npm install 8 | 9 | To check the code for errors and build Leaflet from source, run "jake". 10 | To run the tests, run "jake test". 11 | 12 | For a custom build, open build/build.html in the browser and follow the instructions. 13 | */ 14 | 15 | var build = require('./build/build.js'); 16 | 17 | desc('Check Leaflet.pattern source for errors with JSHint'); 18 | task('lint', build.lint); 19 | 20 | desc('Combine and compress Leaflet.pattern source files'); 21 | task('build', ['lint'], build.build); 22 | 23 | desc('Run PhantomJS tests'); 24 | task('test', ['lint'], build.test); 25 | 26 | task('default', ['build']); -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Tyler Eastman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are 5 | permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of 8 | conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | of conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 17 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 21 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet.pattern 2 | =============== 3 | 4 | Provides the ability to use SVG patterns as backgrounds for [Leaflet](http://leafletjs.com) Paths. 5 | 6 | *Requires Leaflet 0.7.0 or newer.* 7 | 8 | 9 | Usage Examples 10 | ============== 11 | 12 | * [Circle Pattern](https://teastman.github.io/Leaflet.pattern/pattern-circle.html) 13 | * [Rectangular Pattern](https://teastman.github.io/Leaflet.pattern/pattern-rect.html) 14 | * [Stripe Pattern](https://teastman.github.io/Leaflet.pattern/pattern-stripes.html) 15 | * [Path Pattern](https://teastman.github.io/Leaflet.pattern/pattern-path.html) 16 | * [GeoJson Pattern](https://teastman.github.io/Leaflet.pattern/pattern-geojson.html) 17 | 18 | You can define a pattern in 2 ways. 19 | 1. Using a pre-defined provided pattern. 20 | 2. Creating a custom pattern. 21 | 22 | 23 | In either case the Pattern object needs to be initialized. This can be done as follows. 24 | 25 | 26 | var pattern = new L.Pattern({options}); 27 | 28 | 29 | **Options** 30 | All custom and pre-defined patterns can make use of the following options. 31 | 32 | * **patternUnits**: {userSpaceOnUse | objectBoundingBox} (default: userSpaceOnUse) - Defines if the x, y, width, and height values are measured against the current user coordinate system, or are in the range of 0.0 - 1.0 as a percentage of the bounding box of their parent element. 33 | * **patternContentUnits**: {userSpaceOnUse | objectBoundingBox} (default: userSpaceOnUse) - Similar to patternUnits but applies to the shapes within the pattern. 34 | * **x**: {number} (default: 0) - The x offset of the pattern starting position. 35 | * **y**: {number} (default: 0) - The y offset of the pattern starting position. 36 | * **width**: {number} (default: 8) - The width of the pattern. 37 | * **height**: {number} (default: 8) - The height of the pattern. 38 | * **patternTransform**: {string} (default: null) - see http://www.w3.org/TR/SVG/coords.html#TransformAttribute. 39 | * **angle**: {number} (default: null) - a quick way to add rotate(angle) to the patternTransform. 40 | 41 | 42 | Pre-Built Patterns 43 | ------------------ 44 | 45 | Pre-Built patterns are just an easier way to use some common patterns. Pre-Built patterns typically have their own special options, but all can use the base options mentioned above. 46 | 47 | ### Stripes 48 | 49 | 50 | var stripes = new L.StripePattern({options}); 51 | stripes.addTo(map); 52 | 53 | 54 | **Options** 55 | 56 | * **weight**: {number} (default: 4) - The width of the primary stripe. 57 | * **spaceWeight**: {number} (default: 4) - The width of the secondaty stripe, typically an empty space. 58 | * **color**: {color} (default: #000000) - The color of the primary stripe. 59 | * **spaceColor**: {color} (default: #ffffff) - The color of the secondary stripe. 60 | * **opacity**: {0.0 - 1.0} (default: 1.0) - The opacity of the primary stripe. 61 | * **spaceOpacity**: {0.0 - 1.0} (default: 0.0) - The opacity of the secondary stripe. 62 | 63 | ## Usage 64 | 65 | Once the pre-built patterns are defined you can use them by adding them as the fill pattern property of any Path in leaflet. 66 | 67 | 68 | var circle = new L.Circle({LatLng}, {radius}, { 69 | fillPattern: stripes, 70 | fillOpacity: 1.0}); 71 | circle.addTo(_map); 72 | 73 | 74 | Custom Patterns 75 | --------------- 76 | 77 | To create custom patterns you must first create some shapes to define what the pattern looks like. 78 | 79 | ### Shapes 80 | 81 | **Options** 82 | 83 | All shapes have the following options. 84 | 85 | * **stroke**: {boolean} (default: true) - Whether to draw along the path or not. 86 | * **color**: {color} (default: 3388ff) - Color of the stroke. 87 | * **weight**: {number} (default: 3) - Width of the stroke. 88 | * **opacity**: {0.0 - 1.0} (default: 1.0) - Opacity of the stroke. 89 | * **lineCap**: {butt | round | square | inherit} (default: round) - Defines how the stroke looks at its ends 90 | * **lineJoin**: {butt | round | square | inherit} (default: round) - Defines how the stroke looks at its corners. 91 | * **dashArray**: {dashArray} (default: null) - Defines the strokes dash pattern. ex: '5, 5' 92 | * **dashOffset**: {number} (default: null) - 93 | * **fill**: {boolean} (default: false) - Should the shape be filled. 94 | * **fillColor**: {color} (default: same as color) - Color of the fill. 95 | * **fillOpacity**: {0.0 - 1.0} (default: 0.2) - Opacity of the fill. 96 | * **fillRule**: {nonzero | evenodd | inherit} (default: evenodd) - 97 | * **fillPattern**: {L.Pattern} (default: null) - The pattern to fill the Shape with. 98 | 99 | ### Path 100 | 101 | 102 | var shape = new L.PatternPath({ 103 | d: 'M10 0 L7 20 L25 20 Z', 104 | fill: true 105 | }); 106 | 107 | 108 | **Options** 109 | * **d**: {path} (default: null) - The SVG path definition. 110 | 111 | ### Circle 112 | 113 | 114 | var shape = new L.PatternCircle({ 115 | x: 12, 116 | y: 12, 117 | radius: 10, 118 | fill: true 119 | }); 120 | 121 | 122 | **Options** 123 | * **x**: {number} (default: 0) - x offset of the circle. 124 | * **y**: {number} (default: 0) - y offset of the circle. 125 | * **radius**: {number} (default: 0) - radius of the circle. 126 | 127 | ### Rectangle 128 | 129 | 130 | var shape = new L.PatternRect({ 131 | x: 5, 132 | y: 5, 133 | width: 40, 134 | height: 40, 135 | rx: 10, 136 | ry: 10, 137 | fill: true 138 | }); 139 | 140 | 141 | **Options** 142 | * **x**: {number} (default: 0) - x offset of the rectangle. 143 | * **y**: {number} (default: 0) - y offset of the rectangle. 144 | * **width**: {number} (default: 10) - width of the rectangle. 145 | * **height**: {number} (default: 10) - height of the rectangle. 146 | * **rx**: {number} (default: null) - x radius for rounded corners 147 | * **ry**: {number} (default: null) - y radius for rounded corners 148 | 149 | ## Usage 150 | 151 | Once the paths are defined you can use them by adding them to a Pattern. 152 | 153 | 154 | var pattern = new L.Pattern({options}); 155 | pattern.addShape(shape); 156 | pattern.addTo(map); 157 | 158 | 159 | Finally you can now use the pattern in the fill pattern property of any Path in leaflet. 160 | 161 | 162 | var circle = new L.Circle({LatLng}, {radius}, { 163 | fillPattern: pattern, 164 | fillOpacity: 1.0}); 165 | circle.addTo(_map); 166 | 167 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Leaflet.pattern", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/teastman/Leaflet.pattern", 5 | "authors": [ 6 | "teastman" 7 | ], 8 | "description": "Plugin for leaflet that allows for use of fill patterns in Paths", 9 | "main": "dist/leaflet.pattern.js", 10 | "keywords": [ 11 | "leaflet" 12 | ], 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | jshint = require('jshint'), 3 | UglifyJS = require('uglify-js'), 4 | 5 | deps = require('./deps.js').deps, 6 | hintrc = require('./hintrc.js').config; 7 | 8 | function lintFiles(files) { 9 | 10 | var errorsFound = 0, 11 | i, j, len, len2, src, errors, e; 12 | 13 | for (i = 0, len = files.length; i < len; i++) { 14 | 15 | jshint.JSHINT(fs.readFileSync(files[i], 'utf8'), hintrc, i ? {L: true} : null); 16 | errors = jshint.JSHINT.errors; 17 | 18 | for (j = 0, len2 = errors.length; j < len2; j++) { 19 | e = errors[j]; 20 | console.log(files[i] + '\tline ' + e.line + '\tcol ' + e.character + '\t ' + e.reason); 21 | } 22 | 23 | errorsFound += len2; 24 | } 25 | 26 | return errorsFound; 27 | } 28 | 29 | function getFiles(compsBase32) { 30 | var memo = {}, 31 | comps; 32 | 33 | if (compsBase32) { 34 | comps = parseInt(compsBase32, 32).toString(2).split(''); 35 | console.log('Managing dependencies...'); 36 | } 37 | 38 | function addFiles(srcs) { 39 | for (var j = 0, len = srcs.length; j < len; j++) { 40 | memo[srcs[j]] = true; 41 | } 42 | } 43 | 44 | for (var i in deps) { 45 | if (comps) { 46 | if (parseInt(comps.pop(), 2) === 1) { 47 | console.log('\t* ' + i); 48 | addFiles(deps[i].src); 49 | } else { 50 | console.log('\t ' + i); 51 | } 52 | } else { 53 | addFiles(deps[i].src); 54 | } 55 | } 56 | 57 | var files = []; 58 | 59 | for (var src in memo) { 60 | files.push('src/' + src); 61 | } 62 | 63 | return files; 64 | } 65 | 66 | exports.getFiles = getFiles; 67 | 68 | exports.lint = function () { 69 | 70 | var files = getFiles(); 71 | 72 | console.log('Checking for JS errors...'); 73 | 74 | var errorsFound = lintFiles(files); 75 | 76 | if (errorsFound > 0) { 77 | console.log(errorsFound + ' error(s) found.\n'); 78 | fail(); 79 | } else { 80 | console.log('\tCheck passed'); 81 | } 82 | }; 83 | 84 | 85 | function getSizeDelta(newContent, oldContent) { 86 | if (!oldContent) { 87 | return 'new'; 88 | } 89 | var newLen = newContent.replace(/\r\n?/g, '\n').length, 90 | oldLen = oldContent.replace(/\r\n?/g, '\n').length, 91 | delta = newLen - oldLen; 92 | 93 | return (delta >= 0 ? '+' : '') + delta; 94 | } 95 | 96 | function loadSilently(path) { 97 | try { 98 | return fs.readFileSync(path, 'utf8'); 99 | } catch (e) { 100 | return null; 101 | } 102 | } 103 | 104 | function combineFiles(files) { 105 | var content = ''; 106 | for (var i = 0, len = files.length; i < len; i++) { 107 | content += fs.readFileSync(files[i], 'utf8') + '\n\n'; 108 | } 109 | return content; 110 | } 111 | 112 | exports.build = function (compsBase32, buildName) { 113 | 114 | var files = getFiles(compsBase32); 115 | 116 | console.log('Concatenating ' + files.length + ' files...'); 117 | 118 | var copy = fs.readFileSync('src/copyright.js', 'utf8'), 119 | intro = '(function (window, document, undefined) {', 120 | outro = '}(window, document));', 121 | newSrc = copy + intro + combineFiles(files) + outro, 122 | 123 | pathPart = 'dist/leaflet.pattern' + (buildName ? '-' + buildName : ''), 124 | srcPath = pathPart + '-src.js', 125 | 126 | oldSrc = loadSilently(srcPath), 127 | srcDelta = getSizeDelta(newSrc, oldSrc); 128 | 129 | console.log('\tUncompressed size: ' + newSrc.length + ' bytes (' + srcDelta + ')'); 130 | 131 | if (newSrc === oldSrc) { 132 | console.log('\tNo changes'); 133 | } else { 134 | fs.writeFileSync(srcPath, newSrc); 135 | console.log('\tSaved to ' + srcPath); 136 | } 137 | 138 | console.log('Compressing...'); 139 | 140 | var path = pathPart + '.js', 141 | oldCompressed = loadSilently(path), 142 | newCompressed = copy + UglifyJS.minify(newSrc, { 143 | warnings: true, 144 | fromString: true 145 | }).code, 146 | delta = getSizeDelta(newCompressed, oldCompressed); 147 | 148 | console.log('\tCompressed size: ' + newCompressed.length + ' bytes (' + delta + ')'); 149 | 150 | if (newCompressed === oldCompressed) { 151 | console.log('\tNo changes'); 152 | } else { 153 | fs.writeFileSync(path, newCompressed); 154 | console.log('\tSaved to ' + path); 155 | } 156 | }; 157 | 158 | exports.test = function() { 159 | var karma = require('karma'), 160 | testConfig = {configFile : __dirname + '/../spec/karma.conf.js'}; 161 | 162 | testConfig.browsers = ['PhantomJS']; 163 | 164 | if (isArgv('--chrome')) { 165 | testConfig.browsers.push('Chrome'); 166 | } 167 | if (isArgv('--safari')) { 168 | testConfig.browsers.push('Safari'); 169 | } 170 | if (isArgv('--ff')) { 171 | testConfig.browsers.push('Firefox'); 172 | } 173 | if (isArgv('--ie')) { 174 | testConfig.browsers.push('IE'); 175 | } 176 | 177 | if (isArgv('--cov')) { 178 | testConfig.preprocessors = { 179 | '../src/**/*.js': 'coverage' 180 | }; 181 | testConfig.coverageReporter = { 182 | type : 'html', 183 | dir : 'coverage/' 184 | }; 185 | testConfig.reporters = ['coverage']; 186 | } 187 | 188 | karma.server.start(testConfig); 189 | 190 | function isArgv(optName) { 191 | return process.argv.indexOf(optName) !== -1; 192 | } 193 | }; 194 | -------------------------------------------------------------------------------- /build/deps.js: -------------------------------------------------------------------------------- 1 | var deps = { 2 | 3 | Pattern: { 4 | src: ['Pattern.js', 5 | 'Pattern.SVG.js', 6 | 'StripePattern.js'], 7 | desc: 'Pattern definitions' 8 | }, 9 | 10 | Shape: { 11 | src: ['PatternShape.js', 12 | 'PatternShape.SVG.js', 13 | 'PatternPath.js', 14 | 'PatternCircle.js', 15 | 'PatternRect.js'], 16 | desc: 'Shapes for use in patterns.' 17 | } 18 | }; 19 | 20 | if (typeof exports !== 'undefined') { 21 | exports.deps = deps; 22 | } 23 | -------------------------------------------------------------------------------- /build/hintrc.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | 3 | // environment 4 | "browser": true, 5 | "node": true, 6 | "predef": ['L', 'define'], 7 | "strict": false, 8 | 9 | // code style 10 | "bitwise": true, 11 | "camelcase": true, 12 | "curly": true, 13 | "eqeqeq": true, 14 | "forin": false, 15 | "immed": true, 16 | "latedef": true, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": true, 20 | "nonew": true, 21 | "undef": true, 22 | "unused": true, 23 | //"quotmark": "single", 24 | 25 | // whitespace 26 | "indent": 4, 27 | "trailing": true, 28 | "white": true, 29 | "smarttabs": true 30 | //"maxlen": 120 31 | 32 | // code simplicity - not enforced but nice to check from time to time 33 | // "maxstatements": 20, 34 | // "maxcomplexity": 5 35 | // "maxparams": 4, 36 | // "maxdepth": 4 37 | }; 38 | -------------------------------------------------------------------------------- /dist/leaflet.pattern-src.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.pattern, Provides tools to set the backgrounds of vector shapes in Leaflet to be patterns. 3 | https://github.com/teastman/Leaflet.pattern 4 | (c) 2015, Tyler Eastman 5 | */ 6 | (function (window, document, undefined) {/* 7 | * L.Pattern is the base class for fill patterns for leaflet Paths. 8 | */ 9 | 10 | L.Pattern = L.Class.extend({ 11 | includes: [L.Mixin.Events], 12 | 13 | options: { 14 | x: 0, 15 | y: 0, 16 | width: 8, 17 | height: 8, 18 | patternUnits: 'userSpaceOnUse', 19 | patternContentUnits: 'userSpaceOnUse' 20 | // angle: <0 - 360> 21 | // patternTransform: 22 | }, 23 | 24 | _addShapes: L.Util.falseFn, 25 | _update: L.Util.falseFn, 26 | 27 | initialize: function (options) { 28 | this._shapes = {}; 29 | L.setOptions(this, options); 30 | }, 31 | 32 | onAdd: function (map) { 33 | this._map = map.target ? map.target : map; 34 | this._map._initDefRoot(); 35 | 36 | // Create the DOM Object for the pattern. 37 | this._initDom(); 38 | 39 | // Any shapes that were added before this was added to the map need to have their onAdd called. 40 | for (var i in this._shapes) { 41 | this._shapes[i].onAdd(this); 42 | } 43 | 44 | // Call any children that want to add their own shapes. 45 | this._addShapes(); 46 | 47 | // Add the DOM Object to the DOM Tree 48 | this._addDom(); 49 | this.redraw(); 50 | 51 | if (this.getEvents) { 52 | this._map.on(this.getEvents(), this); 53 | } 54 | this.fire('add'); 55 | this._map.fire('patternadd', {pattern: this}); 56 | }, 57 | 58 | onRemove: function () { 59 | this._removeDom(); 60 | }, 61 | 62 | redraw: function () { 63 | if (this._map) { 64 | this._update(); 65 | for (var i in this._shapes) { 66 | this._shapes[i].redraw(); 67 | } 68 | } 69 | return this; 70 | }, 71 | 72 | setStyle: function (style) { 73 | L.setOptions(this, style); 74 | if (this._map) { 75 | this._updateStyle(); 76 | this.redraw(); 77 | } 78 | return this; 79 | }, 80 | 81 | addTo: function (map) { 82 | map.addPattern(this); 83 | return this; 84 | }, 85 | 86 | remove: function () { 87 | return this.removeFrom(this._map); 88 | }, 89 | 90 | removeFrom: function (map) { 91 | if (map) { 92 | map.removePattern(this); 93 | } 94 | return this; 95 | } 96 | }); 97 | 98 | L.Map.addInitHook(function () { 99 | this._patterns = {}; 100 | }); 101 | 102 | L.Map.include({ 103 | addPattern: function (pattern) { 104 | var id = L.stamp(pattern); 105 | if (this._patterns[id]) { return pattern; } 106 | this._patterns[id] = pattern; 107 | 108 | this.whenReady(pattern.onAdd, pattern); 109 | return this; 110 | }, 111 | 112 | removePattern: function (pattern) { 113 | var id = L.stamp(pattern); 114 | if (!this._patterns[id]) { return this; } 115 | 116 | if (this._loaded) { 117 | pattern.onRemove(this); 118 | } 119 | 120 | if (pattern.getEvents) { 121 | this.off(pattern.getEvents(), pattern); 122 | } 123 | 124 | delete this._patterns[id]; 125 | 126 | if (this._loaded) { 127 | this.fire('patternremove', {pattern: pattern}); 128 | pattern.fire('remove'); 129 | } 130 | 131 | pattern._map = null; 132 | return this; 133 | }, 134 | 135 | hasPattern: function (pattern) { 136 | return !!pattern && (L.stamp(pattern) in this._patterns); 137 | } 138 | }); 139 | 140 | 141 | 142 | L.Pattern.SVG_NS = 'http://www.w3.org/2000/svg'; 143 | 144 | L.Pattern = L.Pattern.extend({ 145 | _createElement: function (name) { 146 | return document.createElementNS(L.Pattern.SVG_NS, name); 147 | }, 148 | 149 | _initDom: function () { 150 | this._dom = this._createElement('pattern'); 151 | if (this.options.className) { 152 | L.DomUtil.addClass(this._dom, this.options.className); 153 | } 154 | this._updateStyle(); 155 | }, 156 | 157 | _addDom: function () { 158 | this._map._defRoot.appendChild(this._dom); 159 | }, 160 | 161 | _removeDom: function () { 162 | L.DomUtil.remove(this._dom); 163 | }, 164 | 165 | _updateStyle: function () { 166 | var dom = this._dom, 167 | options = this.options; 168 | 169 | if (!dom) { return; } 170 | 171 | dom.setAttribute('id', L.stamp(this)); 172 | dom.setAttribute('x', options.x); 173 | dom.setAttribute('y', options.y); 174 | dom.setAttribute('width', options.width); 175 | dom.setAttribute('height', options.height); 176 | dom.setAttribute('patternUnits', options.patternUnits); 177 | dom.setAttribute('patternContentUnits', options.patternContentUnits); 178 | 179 | if (options.patternTransform || options.angle) { 180 | var transform = options.patternTransform ? options.patternTransform + " " : ""; 181 | transform += options.angle ? "rotate(" + options.angle + ") " : ""; 182 | dom.setAttribute('patternTransform', transform); 183 | } 184 | else { 185 | dom.removeAttribute('patternTransform'); 186 | } 187 | 188 | for (var i in this._shapes) { 189 | this._shapes[i]._updateStyle(); 190 | } 191 | } 192 | }); 193 | 194 | L.Map.include({ 195 | _initDefRoot: function () { 196 | if (!this._defRoot) { 197 | if (typeof this.getRenderer === 'function') { 198 | var renderer = this.getRenderer(this); 199 | this._defRoot = L.Pattern.prototype._createElement('defs'); 200 | renderer._container.appendChild(this._defRoot); 201 | } else { 202 | if (!this._pathRoot) { 203 | this._initPathRoot(); 204 | } 205 | this._defRoot = L.Pattern.prototype._createElement('defs'); 206 | this._pathRoot.appendChild(this._defRoot); 207 | } 208 | } 209 | } 210 | }); 211 | 212 | if (L.SVG) { 213 | L.SVG.include({ 214 | _superUpdateStyle: L.SVG.prototype._updateStyle, 215 | 216 | _updateStyle: function (layer) { 217 | this._superUpdateStyle(layer); 218 | 219 | if (layer.options.fill && layer.options.fillPattern) { 220 | layer._path.setAttribute('fill', 'url(#' + L.stamp(layer.options.fillPattern) + ")"); 221 | } 222 | } 223 | }); 224 | } 225 | else { 226 | L.Path.include({ 227 | _superUpdateStyle: L.Path.prototype._updateStyle, 228 | 229 | _updateStyle: function () { 230 | this._superUpdateStyle(); 231 | 232 | if (this.options.fill && this.options.fillPattern) { 233 | this._path.setAttribute('fill', 'url(#' + L.stamp(this.options.fillPattern) + ")"); 234 | } 235 | } 236 | }); 237 | } 238 | 239 | 240 | /* 241 | * L.StripePattern is an implementation of Pattern that creates stripes. 242 | */ 243 | 244 | L.StripePattern = L.Pattern.extend({ 245 | 246 | options: { 247 | weight: 4, 248 | spaceWeight: 4, 249 | color: '#000000', 250 | spaceColor: '#ffffff', 251 | opacity: 1.0, 252 | spaceOpacity: 0.0 253 | }, 254 | 255 | _addShapes: function () { 256 | this._stripe = new L.PatternPath({ 257 | stroke: true, 258 | weight: this.options.weight, 259 | color: this.options.color, 260 | opacity: this.options.opacity 261 | }); 262 | 263 | this._space = new L.PatternPath({ 264 | stroke: true, 265 | weight: this.options.spaceWeight, 266 | color: this.options.spaceColor, 267 | opacity: this.options.spaceOpacity 268 | }); 269 | 270 | this.addShape(this._stripe); 271 | this.addShape(this._space); 272 | 273 | this._update(); 274 | }, 275 | 276 | _update: function () { 277 | this._stripe.options.d = 'M0 ' + this._stripe.options.weight / 2 + ' H ' + this.options.width; 278 | this._space.options.d = 'M0 ' + (this._stripe.options.weight + this._space.options.weight / 2) + ' H ' + this.options.width; 279 | }, 280 | 281 | setStyle: L.Pattern.prototype.setStyle 282 | }); 283 | 284 | L.stripePattern = function (options) { 285 | return new L.StripePattern(options); 286 | }; 287 | 288 | /* 289 | * L.PatternShape is the base class that is used to define the shapes in Patterns. 290 | */ 291 | 292 | L.PatternShape = L.Class.extend({ 293 | 294 | options: { 295 | stroke: true, 296 | color: '#3388ff', 297 | weight: 3, 298 | opacity: 1, 299 | lineCap: 'round', 300 | lineJoin: 'round', 301 | // dashArray: null 302 | // dashOffset: null 303 | 304 | // fill: false 305 | // fillColor: same as color by default 306 | fillOpacity: 0.2, 307 | fillRule: 'evenodd', 308 | // fillPattern: L.Pattern 309 | }, 310 | 311 | initialize: function (options) { 312 | L.setOptions(this, options); 313 | }, 314 | 315 | // Called when the parent Pattern get's added to the map, 316 | // or when added to a Pattern that is already on the map. 317 | onAdd: function (pattern) { 318 | this._pattern = pattern; 319 | if (this._pattern._dom) { 320 | this._initDom(); // This function is implemented by it's children. 321 | this._addDom(); 322 | } 323 | }, 324 | 325 | addTo: function (pattern) { 326 | pattern.addShape(this); 327 | return this; 328 | }, 329 | 330 | redraw: function () { 331 | if (this._pattern) { 332 | this._updateShape(); // This function is implemented by it's children. 333 | } 334 | return this; 335 | }, 336 | 337 | setStyle: function (style) { 338 | L.setOptions(this, style); 339 | if (this._pattern) { 340 | this._updateStyle(); 341 | } 342 | return this; 343 | }, 344 | 345 | setShape: function (shape) { 346 | this.options = L.extend({}, this.options, shape); 347 | this._updateShape(); 348 | }, 349 | }); 350 | 351 | L.Pattern.include({ 352 | addShape: function (shape) { 353 | var id = L.stamp(shape); 354 | if (this._shapes[id]) { return shape; } 355 | this._shapes[id] = shape; 356 | shape.onAdd(this); 357 | } 358 | }); 359 | 360 | 361 | 362 | L.PatternShape.SVG_NS = 'http://www.w3.org/2000/svg'; 363 | 364 | L.PatternShape = L.PatternShape.extend({ 365 | _createElement: function (name) { 366 | return document.createElementNS(L.PatternShape.SVG_NS, name); 367 | }, 368 | 369 | _initDom: L.Util.falseFn, 370 | _updateShape: L.Util.falseFn, 371 | 372 | _initDomElement: function (type) { 373 | this._dom = this._createElement(type); 374 | if (this.options.className) { 375 | L.DomUtil.addClass(this._dom, this.options.className); 376 | } 377 | this._updateStyle(); 378 | }, 379 | 380 | _addDom: function () { 381 | this._pattern._dom.appendChild(this._dom); 382 | }, 383 | 384 | _updateStyle: function () { 385 | var dom = this._dom, 386 | options = this.options; 387 | 388 | if (!dom) { return; } 389 | 390 | if (options.stroke) { 391 | dom.setAttribute('stroke', options.color); 392 | dom.setAttribute('stroke-opacity', options.opacity); 393 | dom.setAttribute('stroke-width', options.weight); 394 | dom.setAttribute('stroke-linecap', options.lineCap); 395 | dom.setAttribute('stroke-linejoin', options.lineJoin); 396 | 397 | if (options.dashArray) { 398 | dom.setAttribute('stroke-dasharray', options.dashArray); 399 | } else { 400 | dom.removeAttribute('stroke-dasharray'); 401 | } 402 | 403 | if (options.dashOffset) { 404 | dom.setAttribute('stroke-dashoffset', options.dashOffset); 405 | } else { 406 | dom.removeAttribute('stroke-dashoffset'); 407 | } 408 | } else { 409 | dom.setAttribute('stroke', 'none'); 410 | } 411 | 412 | if (options.fill) { 413 | if (options.fillPattern) { 414 | dom.setAttribute('fill', 'url(#' + L.stamp(options.fillPattern) + ")"); 415 | } 416 | else { 417 | dom.setAttribute('fill', options.fillColor || options.color); 418 | } 419 | dom.setAttribute('fill-opacity', options.fillOpacity); 420 | dom.setAttribute('fill-rule', options.fillRule || 'evenodd'); 421 | } else { 422 | dom.setAttribute('fill', 'none'); 423 | } 424 | 425 | dom.setAttribute('pointer-events', options.pointerEvents || (options.interactive ? 'visiblePainted' : 'none')); 426 | } 427 | }); 428 | 429 | 430 | 431 | /* 432 | * L.PatternPath is the implementation of PatternShape for adding Paths 433 | */ 434 | 435 | L.PatternPath = L.PatternShape.extend({ 436 | // options: { 437 | // d: 438 | // }, 439 | 440 | _initDom: function () { 441 | this._initDomElement('path'); 442 | }, 443 | 444 | _updateShape: function () { 445 | if (!this._dom) { return; } 446 | this._dom.setAttribute('d', this.options.d); 447 | } 448 | }); 449 | 450 | /* 451 | * L.PatternCircle is the implementation of PatternShape for adding Circles 452 | */ 453 | 454 | L.PatternCircle = L.PatternShape.extend({ 455 | options: { 456 | x: 0, 457 | y: 0, 458 | radius: 0 459 | }, 460 | 461 | _initDom: function () { 462 | this._initDomElement('circle'); 463 | }, 464 | 465 | _updateShape: function () { 466 | if (!this._dom) { return; } 467 | this._dom.setAttribute('cx', this.options.x); 468 | this._dom.setAttribute('cy', this.options.y); 469 | this._dom.setAttribute('r', this.options.radius); 470 | } 471 | }); 472 | 473 | /* 474 | * L.PatternRect is the implementation of PatternShape for adding Rectangles 475 | */ 476 | 477 | L.PatternRect = L.PatternShape.extend({ 478 | options: { 479 | x: 0, 480 | y: 0, 481 | width: 10, 482 | height: 10, 483 | // rx: x radius for rounded corners 484 | // ry: y radius for rounded corners 485 | }, 486 | 487 | _initDom: function () { 488 | this._initDomElement('rect'); 489 | }, 490 | 491 | _updateShape: function () { 492 | if (!this._dom) { return; } 493 | this._dom.setAttribute('x', this.options.x); 494 | this._dom.setAttribute('y', this.options.y); 495 | this._dom.setAttribute('width', this.options.width); 496 | this._dom.setAttribute('height', this.options.height); 497 | if (this.options.rx) { this._dom.setAttribute('rx', this.options.rx); } 498 | if (this.options.ry) { this._dom.setAttribute('ry', this.options.ry); } 499 | } 500 | }); 501 | 502 | }(window, document)); -------------------------------------------------------------------------------- /dist/leaflet.pattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.pattern, Provides tools to set the backgrounds of vector shapes in Leaflet to be patterns. 3 | https://github.com/teastman/Leaflet.pattern 4 | (c) 2015, Tyler Eastman 5 | */ 6 | !function(t,e){L.Pattern=L.Class.extend({includes:[L.Mixin.Events],options:{x:0,y:0,width:8,height:8,patternUnits:"userSpaceOnUse",patternContentUnits:"userSpaceOnUse"},_addShapes:L.Util.falseFn,_update:L.Util.falseFn,initialize:function(t){this._shapes={},L.setOptions(this,t)},onAdd:function(t){this._map=t.target?t.target:t,this._map._initDefRoot(),this._initDom();for(var e in this._shapes)this._shapes[e].onAdd(this);this._addShapes(),this._addDom(),this.redraw(),this.getEvents&&this._map.on(this.getEvents(),this),this.fire("add"),this._map.fire("patternadd",{pattern:this})},onRemove:function(){this._removeDom()},redraw:function(){if(this._map){this._update();for(var t in this._shapes)this._shapes[t].redraw()}return this},setStyle:function(t){return L.setOptions(this,t),this._map&&(this._updateStyle(),this.redraw()),this},addTo:function(t){return t.addPattern(this),this},remove:function(){return this.removeFrom(this._map)},removeFrom:function(t){return t&&t.removePattern(this),this}}),L.Map.addInitHook(function(){this._patterns={}}),L.Map.include({addPattern:function(t){var e=L.stamp(t);return this._patterns[e]?t:(this._patterns[e]=t,this.whenReady(t.onAdd,t),this)},removePattern:function(t){var e=L.stamp(t);return this._patterns[e]?(this._loaded&&t.onRemove(this),t.getEvents&&this.off(t.getEvents(),t),delete this._patterns[e],this._loaded&&(this.fire("patternremove",{pattern:t}),t.fire("remove")),t._map=null,this):this},hasPattern:function(t){return!!t&&L.stamp(t)in this._patterns}}),L.Pattern.SVG_NS="http://www.w3.org/2000/svg",L.Pattern=L.Pattern.extend({_createElement:function(t){return e.createElementNS(L.Pattern.SVG_NS,t)},_initDom:function(){this._dom=this._createElement("pattern"),this.options.className&&L.DomUtil.addClass(this._dom,this.options.className),this._updateStyle()},_addDom:function(){this._map._defRoot.appendChild(this._dom)},_removeDom:function(){L.DomUtil.remove(this._dom)},_updateStyle:function(){var t=this._dom,e=this.options;if(t){if(t.setAttribute("id",L.stamp(this)),t.setAttribute("x",e.x),t.setAttribute("y",e.y),t.setAttribute("width",e.width),t.setAttribute("height",e.height),t.setAttribute("patternUnits",e.patternUnits),t.setAttribute("patternContentUnits",e.patternContentUnits),e.patternTransform||e.angle){var i=e.patternTransform?e.patternTransform+" ":"";i+=e.angle?"rotate("+e.angle+") ":"",t.setAttribute("patternTransform",i)}else t.removeAttribute("patternTransform");for(var s in this._shapes)this._shapes[s]._updateStyle()}}}),L.Map.include({_initDefRoot:function(){if(!this._defRoot)if("function"==typeof this.getRenderer){var t=this.getRenderer(this);this._defRoot=L.Pattern.prototype._createElement("defs"),t._container.appendChild(this._defRoot)}else this._pathRoot||this._initPathRoot(),this._defRoot=L.Pattern.prototype._createElement("defs"),this._pathRoot.appendChild(this._defRoot)}}),L.SVG?L.SVG.include({_superUpdateStyle:L.SVG.prototype._updateStyle,_updateStyle:function(t){this._superUpdateStyle(t),t.options.fill&&t.options.fillPattern&&t._path.setAttribute("fill","url(#"+L.stamp(t.options.fillPattern)+")")}}):L.Path.include({_superUpdateStyle:L.Path.prototype._updateStyle,_updateStyle:function(){this._superUpdateStyle(),this.options.fill&&this.options.fillPattern&&this._path.setAttribute("fill","url(#"+L.stamp(this.options.fillPattern)+")")}}),L.StripePattern=L.Pattern.extend({options:{weight:4,spaceWeight:4,color:"#000000",spaceColor:"#ffffff",opacity:1,spaceOpacity:0},_addShapes:function(){this._stripe=new L.PatternPath({stroke:!0,weight:this.options.weight,color:this.options.color,opacity:this.options.opacity}),this._space=new L.PatternPath({stroke:!0,weight:this.options.spaceWeight,color:this.options.spaceColor,opacity:this.options.spaceOpacity}),this.addShape(this._stripe),this.addShape(this._space),this._update()},_update:function(){this._stripe.options.d="M0 "+this._stripe.options.weight/2+" H "+this.options.width,this._space.options.d="M0 "+(this._stripe.options.weight+this._space.options.weight/2)+" H "+this.options.width},setStyle:L.Pattern.prototype.setStyle}),L.stripePattern=function(t){return new L.StripePattern(t)},L.PatternShape=L.Class.extend({options:{stroke:!0,color:"#3388ff",weight:3,opacity:1,lineCap:"round",lineJoin:"round",fillOpacity:.2,fillRule:"evenodd"},initialize:function(t){L.setOptions(this,t)},onAdd:function(t){this._pattern=t,this._pattern._dom&&(this._initDom(),this._addDom())},addTo:function(t){return t.addShape(this),this},redraw:function(){return this._pattern&&this._updateShape(),this},setStyle:function(t){return L.setOptions(this,t),this._pattern&&this._updateStyle(),this},setShape:function(t){this.options=L.extend({},this.options,t),this._updateShape()}}),L.Pattern.include({addShape:function(t){var e=L.stamp(t);return this._shapes[e]?t:(this._shapes[e]=t,t.onAdd(this),void 0)}}),L.PatternShape.SVG_NS="http://www.w3.org/2000/svg",L.PatternShape=L.PatternShape.extend({_createElement:function(t){return e.createElementNS(L.PatternShape.SVG_NS,t)},_initDom:L.Util.falseFn,_updateShape:L.Util.falseFn,_initDomElement:function(t){this._dom=this._createElement(t),this.options.className&&L.DomUtil.addClass(this._dom,this.options.className),this._updateStyle()},_addDom:function(){this._pattern._dom.appendChild(this._dom)},_updateStyle:function(){var t=this._dom,e=this.options;t&&(e.stroke?(t.setAttribute("stroke",e.color),t.setAttribute("stroke-opacity",e.opacity),t.setAttribute("stroke-width",e.weight),t.setAttribute("stroke-linecap",e.lineCap),t.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?t.setAttribute("stroke-dasharray",e.dashArray):t.removeAttribute("stroke-dasharray"),e.dashOffset?t.setAttribute("stroke-dashoffset",e.dashOffset):t.removeAttribute("stroke-dashoffset")):t.setAttribute("stroke","none"),e.fill?(e.fillPattern?t.setAttribute("fill","url(#"+L.stamp(e.fillPattern)+")"):t.setAttribute("fill",e.fillColor||e.color),t.setAttribute("fill-opacity",e.fillOpacity),t.setAttribute("fill-rule",e.fillRule||"evenodd")):t.setAttribute("fill","none"),t.setAttribute("pointer-events",e.pointerEvents||(e.interactive?"visiblePainted":"none")))}}),L.PatternPath=L.PatternShape.extend({_initDom:function(){this._initDomElement("path")},_updateShape:function(){this._dom&&this._dom.setAttribute("d",this.options.d)}}),L.PatternCircle=L.PatternShape.extend({options:{x:0,y:0,radius:0},_initDom:function(){this._initDomElement("circle")},_updateShape:function(){this._dom&&(this._dom.setAttribute("cx",this.options.x),this._dom.setAttribute("cy",this.options.y),this._dom.setAttribute("r",this.options.radius))}}),L.PatternRect=L.PatternShape.extend({options:{x:0,y:0,width:10,height:10},_initDom:function(){this._initDomElement("rect")},_updateShape:function(){this._dom&&(this._dom.setAttribute("x",this.options.x),this._dom.setAttribute("y",this.options.y),this._dom.setAttribute("width",this.options.width),this._dom.setAttribute("height",this.options.height),this.options.rx&&this._dom.setAttribute("rx",this.options.rx),this.options.ry&&this._dom.setAttribute("ry",this.options.ry))}})}(window,document); -------------------------------------------------------------------------------- /example/pattern-circle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet 6 | 7 | 8 | 9 | 17 | 18 | 19 |
20 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/pattern-geojson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet 6 | 7 | 8 | 9 | 17 | 18 | 19 |
20 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /example/pattern-path.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet 6 | 7 | 8 | 9 | 17 | 18 | 19 |
20 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/pattern-rect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet 6 | 7 | 8 | 9 | 17 | 18 | 19 |
20 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/pattern-stripes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet 6 | 7 | 8 | 9 | 17 | 18 | 19 |
20 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet.pattern", 3 | "version": "0.1.0", 4 | "description": "Provides the ability to use SVG patterns as backgrounds for Leaflet Paths.", 5 | "dependencies": { 6 | "leaflet": "~0.7.1" 7 | }, 8 | "devDependencies": { 9 | "jshint": "~2.1.3", 10 | "mocha": "~1.10.0", 11 | "karma": "~0.8.5", 12 | "uglify-js": "~2.3.6", 13 | "jake": "~0.5.16" 14 | }, 15 | "main": "dist/leaflet.pattern.js", 16 | "scripts": { 17 | "test": "jake test", 18 | "prepublish": "jake" 19 | }, 20 | "keywords": ["gis", "map"] 21 | } -------------------------------------------------------------------------------- /spec/after.js: -------------------------------------------------------------------------------- 1 | // put after Leaflet files as imagePath can't be detected in a PhantomJS env 2 | L.Icon.Default.imagePath = "../dist/images"; 3 | -------------------------------------------------------------------------------- /spec/expect.js: -------------------------------------------------------------------------------- 1 | 2 | (function (global, module) { 3 | 4 | if ('undefined' == typeof module) { 5 | var module = { exports: {} } 6 | , exports = module.exports 7 | } 8 | 9 | /** 10 | * Exports. 11 | */ 12 | 13 | module.exports = expect; 14 | expect.Assertion = Assertion; 15 | 16 | /** 17 | * Exports version. 18 | */ 19 | 20 | expect.version = '0.1.2'; 21 | 22 | /** 23 | * Possible assertion flags. 24 | */ 25 | 26 | var flags = { 27 | not: ['to', 'be', 'have', 'include', 'only'] 28 | , to: ['be', 'have', 'include', 'only', 'not'] 29 | , only: ['have'] 30 | , have: ['own'] 31 | , be: ['an'] 32 | }; 33 | 34 | function expect (obj) { 35 | return new Assertion(obj); 36 | } 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @api private 42 | */ 43 | 44 | function Assertion (obj, flag, parent) { 45 | this.obj = obj; 46 | this.flags = {}; 47 | 48 | if (undefined != parent) { 49 | this.flags[flag] = true; 50 | 51 | for (var i in parent.flags) { 52 | if (parent.flags.hasOwnProperty(i)) { 53 | this.flags[i] = true; 54 | } 55 | } 56 | } 57 | 58 | var $flags = flag ? flags[flag] : keys(flags) 59 | , self = this 60 | 61 | if ($flags) { 62 | for (var i = 0, l = $flags.length; i < l; i++) { 63 | // avoid recursion 64 | if (this.flags[$flags[i]]) continue; 65 | 66 | var name = $flags[i] 67 | , assertion = new Assertion(this.obj, name, this) 68 | 69 | if ('function' == typeof Assertion.prototype[name]) { 70 | // clone the function, make sure we dont touch the prot reference 71 | var old = this[name]; 72 | this[name] = function () { 73 | return old.apply(self, arguments); 74 | } 75 | 76 | for (var fn in Assertion.prototype) { 77 | if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { 78 | this[name][fn] = bind(assertion[fn], assertion); 79 | } 80 | } 81 | } else { 82 | this[name] = assertion; 83 | } 84 | } 85 | } 86 | }; 87 | 88 | /** 89 | * Performs an assertion 90 | * 91 | * @api private 92 | */ 93 | 94 | Assertion.prototype.assert = function (truth, msg, error) { 95 | var msg = this.flags.not ? error : msg 96 | , ok = this.flags.not ? !truth : truth; 97 | 98 | if (!ok) { 99 | throw new Error(msg.call(this)); 100 | } 101 | 102 | this.and = new Assertion(this.obj); 103 | }; 104 | 105 | /** 106 | * Check if the value is truthy 107 | * 108 | * @api public 109 | */ 110 | 111 | Assertion.prototype.ok = function () { 112 | this.assert( 113 | !!this.obj 114 | , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } 115 | , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); 116 | }; 117 | 118 | /** 119 | * Assert that the function throws. 120 | * 121 | * @param {Function|RegExp} callback, or regexp to match error string against 122 | * @api public 123 | */ 124 | 125 | Assertion.prototype.throwError = 126 | Assertion.prototype.throwException = function (fn) { 127 | expect(this.obj).to.be.a('function'); 128 | 129 | var thrown = false 130 | , not = this.flags.not 131 | 132 | try { 133 | this.obj(); 134 | } catch (e) { 135 | if ('function' == typeof fn) { 136 | fn(e); 137 | } else if ('object' == typeof fn) { 138 | var subject = 'string' == typeof e ? e : e.message; 139 | if (not) { 140 | expect(subject).to.not.match(fn); 141 | } else { 142 | expect(subject).to.match(fn); 143 | } 144 | } 145 | thrown = true; 146 | } 147 | 148 | if ('object' == typeof fn && not) { 149 | // in the presence of a matcher, ensure the `not` only applies to 150 | // the matching. 151 | this.flags.not = false; 152 | } 153 | 154 | var name = this.obj.name || 'fn'; 155 | this.assert( 156 | thrown 157 | , function(){ return 'expected ' + name + ' to throw an exception' } 158 | , function(){ return 'expected ' + name + ' not to throw an exception' }); 159 | }; 160 | 161 | /** 162 | * Checks if the array is empty. 163 | * 164 | * @api public 165 | */ 166 | 167 | Assertion.prototype.empty = function () { 168 | var expectation; 169 | 170 | if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { 171 | if ('number' == typeof this.obj.length) { 172 | expectation = !this.obj.length; 173 | } else { 174 | expectation = !keys(this.obj).length; 175 | } 176 | } else { 177 | if ('string' != typeof this.obj) { 178 | expect(this.obj).to.be.an('object'); 179 | } 180 | 181 | expect(this.obj).to.have.property('length'); 182 | expectation = !this.obj.length; 183 | } 184 | 185 | this.assert( 186 | expectation 187 | , function(){ return 'expected ' + i(this.obj) + ' to be empty' } 188 | , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); 189 | return this; 190 | }; 191 | 192 | /** 193 | * Checks if the obj exactly equals another. 194 | * 195 | * @api public 196 | */ 197 | 198 | Assertion.prototype.be = 199 | Assertion.prototype.equal = function (obj) { 200 | this.assert( 201 | obj === this.obj 202 | , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } 203 | , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); 204 | return this; 205 | }; 206 | 207 | /** 208 | * Checks if the obj sortof equals another. 209 | * 210 | * @api public 211 | */ 212 | 213 | Assertion.prototype.eql = function (obj) { 214 | this.assert( 215 | expect.eql(obj, this.obj) 216 | , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } 217 | , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) }); 218 | return this; 219 | }; 220 | 221 | /** 222 | * Assert within start to finish (inclusive). 223 | * 224 | * @param {Number} start 225 | * @param {Number} finish 226 | * @api public 227 | */ 228 | 229 | Assertion.prototype.within = function (start, finish) { 230 | var range = start + '..' + finish; 231 | this.assert( 232 | this.obj >= start && this.obj <= finish 233 | , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } 234 | , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); 235 | return this; 236 | }; 237 | 238 | /** 239 | * Assert typeof / instance of 240 | * 241 | * @api public 242 | */ 243 | 244 | Assertion.prototype.a = 245 | Assertion.prototype.an = function (type) { 246 | if ('string' == typeof type) { 247 | // proper english in error msg 248 | var n = /^[aeiou]/.test(type) ? 'n' : ''; 249 | 250 | // typeof with support for 'array' 251 | this.assert( 252 | 'array' == type ? isArray(this.obj) : 253 | 'object' == type 254 | ? 'object' == typeof this.obj && null !== this.obj 255 | : type == typeof this.obj 256 | , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } 257 | , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); 258 | } else { 259 | // instanceof 260 | var name = type.name || 'supplied constructor'; 261 | this.assert( 262 | this.obj instanceof type 263 | , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } 264 | , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); 265 | } 266 | 267 | return this; 268 | }; 269 | 270 | /** 271 | * Assert numeric value above _n_. 272 | * 273 | * @param {Number} n 274 | * @api public 275 | */ 276 | 277 | Assertion.prototype.greaterThan = 278 | Assertion.prototype.above = function (n) { 279 | this.assert( 280 | this.obj > n 281 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } 282 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); 283 | return this; 284 | }; 285 | 286 | /** 287 | * Assert numeric value below _n_. 288 | * 289 | * @param {Number} n 290 | * @api public 291 | */ 292 | 293 | Assertion.prototype.lessThan = 294 | Assertion.prototype.below = function (n) { 295 | this.assert( 296 | this.obj < n 297 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } 298 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); 299 | return this; 300 | }; 301 | 302 | /** 303 | * Assert string value matches _regexp_. 304 | * 305 | * @param {RegExp} regexp 306 | * @api public 307 | */ 308 | 309 | Assertion.prototype.match = function (regexp) { 310 | this.assert( 311 | regexp.exec(this.obj) 312 | , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } 313 | , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); 314 | return this; 315 | }; 316 | 317 | /** 318 | * Assert property "length" exists and has value of _n_. 319 | * 320 | * @param {Number} n 321 | * @api public 322 | */ 323 | 324 | Assertion.prototype.length = function (n) { 325 | expect(this.obj).to.have.property('length'); 326 | var len = this.obj.length; 327 | this.assert( 328 | n == len 329 | , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } 330 | , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); 331 | return this; 332 | }; 333 | 334 | /** 335 | * Assert property _name_ exists, with optional _val_. 336 | * 337 | * @param {String} name 338 | * @param {Mixed} val 339 | * @api public 340 | */ 341 | 342 | Assertion.prototype.property = function (name, val) { 343 | if (this.flags.own) { 344 | this.assert( 345 | Object.prototype.hasOwnProperty.call(this.obj, name) 346 | , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } 347 | , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); 348 | return this; 349 | } 350 | 351 | if (this.flags.not && undefined !== val) { 352 | if (undefined === this.obj[name]) { 353 | throw new Error(i(this.obj) + ' has no property ' + i(name)); 354 | } 355 | } else { 356 | var hasProp; 357 | try { 358 | hasProp = name in this.obj 359 | } catch (e) { 360 | hasProp = undefined !== this.obj[name] 361 | } 362 | 363 | this.assert( 364 | hasProp 365 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } 366 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); 367 | } 368 | 369 | if (undefined !== val) { 370 | this.assert( 371 | val === this.obj[name] 372 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) 373 | + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } 374 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) 375 | + ' of ' + i(val) }); 376 | } 377 | 378 | this.obj = this.obj[name]; 379 | return this; 380 | }; 381 | 382 | /** 383 | * Assert that the array contains _obj_ or string contains _obj_. 384 | * 385 | * @param {Mixed} obj|string 386 | * @api public 387 | */ 388 | 389 | Assertion.prototype.string = 390 | Assertion.prototype.contain = function (obj) { 391 | if ('string' == typeof this.obj) { 392 | this.assert( 393 | ~this.obj.indexOf(obj) 394 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 395 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 396 | } else { 397 | this.assert( 398 | ~indexOf(this.obj, obj) 399 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 400 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 401 | } 402 | return this; 403 | }; 404 | 405 | /** 406 | * Assert exact keys or inclusion of keys by using 407 | * the `.own` modifier. 408 | * 409 | * @param {Array|String ...} keys 410 | * @api public 411 | */ 412 | 413 | Assertion.prototype.key = 414 | Assertion.prototype.keys = function ($keys) { 415 | var str 416 | , ok = true; 417 | 418 | $keys = isArray($keys) 419 | ? $keys 420 | : Array.prototype.slice.call(arguments); 421 | 422 | if (!$keys.length) throw new Error('keys required'); 423 | 424 | var actual = keys(this.obj) 425 | , len = $keys.length; 426 | 427 | // Inclusion 428 | ok = every($keys, function (key) { 429 | return ~indexOf(actual, key); 430 | }); 431 | 432 | // Strict 433 | if (!this.flags.not && this.flags.only) { 434 | ok = ok && $keys.length == actual.length; 435 | } 436 | 437 | // Key string 438 | if (len > 1) { 439 | $keys = map($keys, function (key) { 440 | return i(key); 441 | }); 442 | var last = $keys.pop(); 443 | str = $keys.join(', ') + ', and ' + last; 444 | } else { 445 | str = i($keys[0]); 446 | } 447 | 448 | // Form 449 | str = (len > 1 ? 'keys ' : 'key ') + str; 450 | 451 | // Have / include 452 | str = (!this.flags.only ? 'include ' : 'only have ') + str; 453 | 454 | // Assertion 455 | this.assert( 456 | ok 457 | , function(){ return 'expected ' + i(this.obj) + ' to ' + str } 458 | , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); 459 | 460 | return this; 461 | }; 462 | /** 463 | * Assert a failure. 464 | * 465 | * @param {String ...} custom message 466 | * @api public 467 | */ 468 | Assertion.prototype.fail = function (msg) { 469 | msg = msg || "explicit failure"; 470 | this.assert(false, msg, msg); 471 | return this; 472 | }; 473 | 474 | /** 475 | * Function bind implementation. 476 | */ 477 | 478 | function bind (fn, scope) { 479 | return function () { 480 | return fn.apply(scope, arguments); 481 | } 482 | } 483 | 484 | /** 485 | * Array every compatibility 486 | * 487 | * @see bit.ly/5Fq1N2 488 | * @api public 489 | */ 490 | 491 | function every (arr, fn, thisObj) { 492 | var scope = thisObj || global; 493 | for (var i = 0, j = arr.length; i < j; ++i) { 494 | if (!fn.call(scope, arr[i], i, arr)) { 495 | return false; 496 | } 497 | } 498 | return true; 499 | }; 500 | 501 | /** 502 | * Array indexOf compatibility. 503 | * 504 | * @see bit.ly/a5Dxa2 505 | * @api public 506 | */ 507 | 508 | function indexOf (arr, o, i) { 509 | if (Array.prototype.indexOf) { 510 | return Array.prototype.indexOf.call(arr, o, i); 511 | } 512 | 513 | if (arr.length === undefined) { 514 | return -1; 515 | } 516 | 517 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 518 | ; i < j && arr[i] !== o; i++); 519 | 520 | return j <= i ? -1 : i; 521 | }; 522 | 523 | // https://gist.github.com/1044128/ 524 | var getOuterHTML = function(element) { 525 | if ('outerHTML' in element) return element.outerHTML; 526 | var ns = "http://www.w3.org/1999/xhtml"; 527 | var container = document.createElementNS(ns, '_'); 528 | var elemProto = (window.HTMLElement || window.Element).prototype; 529 | var xmlSerializer = new XMLSerializer(); 530 | var html; 531 | if (document.xmlVersion) { 532 | return xmlSerializer.serializeToString(element); 533 | } else { 534 | container.appendChild(element.cloneNode(false)); 535 | html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); 536 | container.innerHTML = ''; 537 | return html; 538 | } 539 | }; 540 | 541 | // Returns true if object is a DOM element. 542 | var isDOMElement = function (object) { 543 | if (typeof HTMLElement === 'object') { 544 | return object instanceof HTMLElement; 545 | } else { 546 | return object && 547 | typeof object === 'object' && 548 | object.nodeType === 1 && 549 | typeof object.nodeName === 'string'; 550 | } 551 | }; 552 | 553 | /** 554 | * Inspects an object. 555 | * 556 | * @see taken from node.js `util` module (copyright Joyent, MIT license) 557 | * @api private 558 | */ 559 | 560 | function i (obj, showHidden, depth) { 561 | var seen = []; 562 | 563 | function stylize (str) { 564 | return str; 565 | }; 566 | 567 | function format (value, recurseTimes) { 568 | // Provide a hook for user-specified inspect functions. 569 | // Check that value is an object with an inspect function on it 570 | if (value && typeof value.inspect === 'function' && 571 | // Filter out the util module, it's inspect function is special 572 | value !== exports && 573 | // Also filter out any prototype objects using the circular check. 574 | !(value.constructor && value.constructor.prototype === value)) { 575 | return value.inspect(recurseTimes); 576 | } 577 | 578 | // Primitive types cannot have properties 579 | switch (typeof value) { 580 | case 'undefined': 581 | return stylize('undefined', 'undefined'); 582 | 583 | case 'string': 584 | var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') 585 | .replace(/'/g, "\\'") 586 | .replace(/\\"/g, '"') + '\''; 587 | return stylize(simple, 'string'); 588 | 589 | case 'number': 590 | return stylize('' + value, 'number'); 591 | 592 | case 'boolean': 593 | return stylize('' + value, 'boolean'); 594 | } 595 | // For some reason typeof null is "object", so special case here. 596 | if (value === null) { 597 | return stylize('null', 'null'); 598 | } 599 | 600 | if (isDOMElement(value)) { 601 | return getOuterHTML(value); 602 | } 603 | 604 | // Look up the keys of the object. 605 | var visible_keys = keys(value); 606 | var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; 607 | 608 | // Functions without properties can be shortcutted. 609 | if (typeof value === 'function' && $keys.length === 0) { 610 | if (isRegExp(value)) { 611 | return stylize('' + value, 'regexp'); 612 | } else { 613 | var name = value.name ? ': ' + value.name : ''; 614 | return stylize('[Function' + name + ']', 'special'); 615 | } 616 | } 617 | 618 | // Dates without properties can be shortcutted 619 | if (isDate(value) && $keys.length === 0) { 620 | return stylize(value.toUTCString(), 'date'); 621 | } 622 | 623 | var base, type, braces; 624 | // Determine the object type 625 | if (isArray(value)) { 626 | type = 'Array'; 627 | braces = ['[', ']']; 628 | } else { 629 | type = 'Object'; 630 | braces = ['{', '}']; 631 | } 632 | 633 | // Make functions say that they are functions 634 | if (typeof value === 'function') { 635 | var n = value.name ? ': ' + value.name : ''; 636 | base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; 637 | } else { 638 | base = ''; 639 | } 640 | 641 | // Make dates with properties first say the date 642 | if (isDate(value)) { 643 | base = ' ' + value.toUTCString(); 644 | } 645 | 646 | if ($keys.length === 0) { 647 | return braces[0] + base + braces[1]; 648 | } 649 | 650 | if (recurseTimes < 0) { 651 | if (isRegExp(value)) { 652 | return stylize('' + value, 'regexp'); 653 | } else { 654 | return stylize('[Object]', 'special'); 655 | } 656 | } 657 | 658 | seen.push(value); 659 | 660 | var output = map($keys, function (key) { 661 | var name, str; 662 | if (value.__lookupGetter__) { 663 | if (value.__lookupGetter__(key)) { 664 | if (value.__lookupSetter__(key)) { 665 | str = stylize('[Getter/Setter]', 'special'); 666 | } else { 667 | str = stylize('[Getter]', 'special'); 668 | } 669 | } else { 670 | if (value.__lookupSetter__(key)) { 671 | str = stylize('[Setter]', 'special'); 672 | } 673 | } 674 | } 675 | if (indexOf(visible_keys, key) < 0) { 676 | name = '[' + key + ']'; 677 | } 678 | if (!str) { 679 | if (indexOf(seen, value[key]) < 0) { 680 | if (recurseTimes === null) { 681 | str = format(value[key]); 682 | } else { 683 | str = format(value[key], recurseTimes - 1); 684 | } 685 | if (str.indexOf('\n') > -1) { 686 | if (isArray(value)) { 687 | str = map(str.split('\n'), function (line) { 688 | return ' ' + line; 689 | }).join('\n').substr(2); 690 | } else { 691 | str = '\n' + map(str.split('\n'), function (line) { 692 | return ' ' + line; 693 | }).join('\n'); 694 | } 695 | } 696 | } else { 697 | str = stylize('[Circular]', 'special'); 698 | } 699 | } 700 | if (typeof name === 'undefined') { 701 | if (type === 'Array' && key.match(/^\d+$/)) { 702 | return str; 703 | } 704 | name = json.stringify('' + key); 705 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 706 | name = name.substr(1, name.length - 2); 707 | name = stylize(name, 'name'); 708 | } else { 709 | name = name.replace(/'/g, "\\'") 710 | .replace(/\\"/g, '"') 711 | .replace(/(^"|"$)/g, "'"); 712 | name = stylize(name, 'string'); 713 | } 714 | } 715 | 716 | return name + ': ' + str; 717 | }); 718 | 719 | seen.pop(); 720 | 721 | var numLinesEst = 0; 722 | var length = reduce(output, function (prev, cur) { 723 | numLinesEst++; 724 | if (indexOf(cur, '\n') >= 0) numLinesEst++; 725 | return prev + cur.length + 1; 726 | }, 0); 727 | 728 | if (length > 50) { 729 | output = braces[0] + 730 | (base === '' ? '' : base + '\n ') + 731 | ' ' + 732 | output.join(',\n ') + 733 | ' ' + 734 | braces[1]; 735 | 736 | } else { 737 | output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 738 | } 739 | 740 | return output; 741 | } 742 | return format(obj, (typeof depth === 'undefined' ? 2 : depth)); 743 | }; 744 | 745 | function isArray (ar) { 746 | return Object.prototype.toString.call(ar) == '[object Array]'; 747 | }; 748 | 749 | function isRegExp(re) { 750 | var s; 751 | try { 752 | s = '' + re; 753 | } catch (e) { 754 | return false; 755 | } 756 | 757 | return re instanceof RegExp || // easy case 758 | // duck-type for context-switching evalcx case 759 | typeof(re) === 'function' && 760 | re.constructor.name === 'RegExp' && 761 | re.compile && 762 | re.test && 763 | re.exec && 764 | s.match(/^\/.*\/[gim]{0,3}$/); 765 | }; 766 | 767 | function isDate(d) { 768 | if (d instanceof Date) return true; 769 | return false; 770 | }; 771 | 772 | function keys (obj) { 773 | if (Object.keys) { 774 | return Object.keys(obj); 775 | } 776 | 777 | var keys = []; 778 | 779 | for (var i in obj) { 780 | if (Object.prototype.hasOwnProperty.call(obj, i)) { 781 | keys.push(i); 782 | } 783 | } 784 | 785 | return keys; 786 | } 787 | 788 | function map (arr, mapper, that) { 789 | if (Array.prototype.map) { 790 | return Array.prototype.map.call(arr, mapper, that); 791 | } 792 | 793 | var other= new Array(arr.length); 794 | 795 | for (var i= 0, n = arr.length; i= 2) { 821 | var rv = arguments[1]; 822 | } else { 823 | do { 824 | if (i in this) { 825 | rv = this[i++]; 826 | break; 827 | } 828 | 829 | // if array contains no values, no initial value to return 830 | if (++i >= len) 831 | throw new TypeError(); 832 | } while (true); 833 | } 834 | 835 | for (; i < len; i++) { 836 | if (i in this) 837 | rv = fun.call(null, rv, this[i], i, this); 838 | } 839 | 840 | return rv; 841 | }; 842 | 843 | /** 844 | * Asserts deep equality 845 | * 846 | * @see taken from node.js `assert` module (copyright Joyent, MIT license) 847 | * @api private 848 | */ 849 | 850 | expect.eql = function eql (actual, expected) { 851 | // 7.1. All identical values are equivalent, as determined by ===. 852 | if (actual === expected) { 853 | return true; 854 | } else if ('undefined' != typeof Buffer 855 | && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { 856 | if (actual.length != expected.length) return false; 857 | 858 | for (var i = 0; i < actual.length; i++) { 859 | if (actual[i] !== expected[i]) return false; 860 | } 861 | 862 | return true; 863 | 864 | // 7.2. If the expected value is a Date object, the actual value is 865 | // equivalent if it is also a Date object that refers to the same time. 866 | } else if (actual instanceof Date && expected instanceof Date) { 867 | return actual.getTime() === expected.getTime(); 868 | 869 | // 7.3. Other pairs that do not both pass typeof value == "object", 870 | // equivalence is determined by ==. 871 | } else if (typeof actual != 'object' && typeof expected != 'object') { 872 | return actual == expected; 873 | 874 | // 7.4. For all other Object pairs, including Array objects, equivalence is 875 | // determined by having the same number of owned properties (as verified 876 | // with Object.prototype.hasOwnProperty.call), the same set of keys 877 | // (although not necessarily the same order), equivalent values for every 878 | // corresponding key, and an identical "prototype" property. Note: this 879 | // accounts for both named and indexed properties on Arrays. 880 | } else { 881 | return objEquiv(actual, expected); 882 | } 883 | } 884 | 885 | function isUndefinedOrNull (value) { 886 | return value === null || value === undefined; 887 | } 888 | 889 | function isArguments (object) { 890 | return Object.prototype.toString.call(object) == '[object Arguments]'; 891 | } 892 | 893 | function objEquiv (a, b) { 894 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 895 | return false; 896 | // an identical "prototype" property. 897 | if (a.prototype !== b.prototype) return false; 898 | //~~~I've managed to break Object.keys through screwy arguments passing. 899 | // Converting to array solves the problem. 900 | if (isArguments(a)) { 901 | if (!isArguments(b)) { 902 | return false; 903 | } 904 | a = pSlice.call(a); 905 | b = pSlice.call(b); 906 | return expect.eql(a, b); 907 | } 908 | try{ 909 | var ka = keys(a), 910 | kb = keys(b), 911 | key, i; 912 | } catch (e) {//happens when one is a string literal and the other isn't 913 | return false; 914 | } 915 | // having the same number of owned properties (keys incorporates hasOwnProperty) 916 | if (ka.length != kb.length) 917 | return false; 918 | //the same set of keys (although not necessarily the same order), 919 | ka.sort(); 920 | kb.sort(); 921 | //~~~cheap key test 922 | for (i = ka.length - 1; i >= 0; i--) { 923 | if (ka[i] != kb[i]) 924 | return false; 925 | } 926 | //equivalent values for every corresponding key, and 927 | //~~~possibly expensive deep test 928 | for (i = ka.length - 1; i >= 0; i--) { 929 | key = ka[i]; 930 | if (!expect.eql(a[key], b[key])) 931 | return false; 932 | } 933 | return true; 934 | } 935 | 936 | var json = (function () { 937 | "use strict"; 938 | 939 | if ('object' == typeof JSON && JSON.parse && JSON.stringify) { 940 | return { 941 | parse: nativeJSON.parse 942 | , stringify: nativeJSON.stringify 943 | } 944 | } 945 | 946 | var JSON = {}; 947 | 948 | function f(n) { 949 | // Format integers to have at least two digits. 950 | return n < 10 ? '0' + n : n; 951 | } 952 | 953 | function date(d, key) { 954 | return isFinite(d.valueOf()) ? 955 | d.getUTCFullYear() + '-' + 956 | f(d.getUTCMonth() + 1) + '-' + 957 | f(d.getUTCDate()) + 'T' + 958 | f(d.getUTCHours()) + ':' + 959 | f(d.getUTCMinutes()) + ':' + 960 | f(d.getUTCSeconds()) + 'Z' : null; 961 | }; 962 | 963 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 964 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 965 | gap, 966 | indent, 967 | meta = { // table of character substitutions 968 | '\b': '\\b', 969 | '\t': '\\t', 970 | '\n': '\\n', 971 | '\f': '\\f', 972 | '\r': '\\r', 973 | '"' : '\\"', 974 | '\\': '\\\\' 975 | }, 976 | rep; 977 | 978 | 979 | function quote(string) { 980 | 981 | // If the string contains no control characters, no quote characters, and no 982 | // backslash characters, then we can safely slap some quotes around it. 983 | // Otherwise we must also replace the offending characters with safe escape 984 | // sequences. 985 | 986 | escapable.lastIndex = 0; 987 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 988 | var c = meta[a]; 989 | return typeof c === 'string' ? c : 990 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 991 | }) + '"' : '"' + string + '"'; 992 | } 993 | 994 | 995 | function str(key, holder) { 996 | 997 | // Produce a string from holder[key]. 998 | 999 | var i, // The loop counter. 1000 | k, // The member key. 1001 | v, // The member value. 1002 | length, 1003 | mind = gap, 1004 | partial, 1005 | value = holder[key]; 1006 | 1007 | // If the value has a toJSON method, call it to obtain a replacement value. 1008 | 1009 | if (value instanceof Date) { 1010 | value = date(key); 1011 | } 1012 | 1013 | // If we were called with a replacer function, then call the replacer to 1014 | // obtain a replacement value. 1015 | 1016 | if (typeof rep === 'function') { 1017 | value = rep.call(holder, key, value); 1018 | } 1019 | 1020 | // What happens next depends on the value's type. 1021 | 1022 | switch (typeof value) { 1023 | case 'string': 1024 | return quote(value); 1025 | 1026 | case 'number': 1027 | 1028 | // JSON numbers must be finite. Encode non-finite numbers as null. 1029 | 1030 | return isFinite(value) ? String(value) : 'null'; 1031 | 1032 | case 'boolean': 1033 | case 'null': 1034 | 1035 | // If the value is a boolean or null, convert it to a string. Note: 1036 | // typeof null does not produce 'null'. The case is included here in 1037 | // the remote chance that this gets fixed someday. 1038 | 1039 | return String(value); 1040 | 1041 | // If the type is 'object', we might be dealing with an object or an array or 1042 | // null. 1043 | 1044 | case 'object': 1045 | 1046 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 1047 | // so watch out for that case. 1048 | 1049 | if (!value) { 1050 | return 'null'; 1051 | } 1052 | 1053 | // Make an array to hold the partial results of stringifying this object value. 1054 | 1055 | gap += indent; 1056 | partial = []; 1057 | 1058 | // Is the value an array? 1059 | 1060 | if (Object.prototype.toString.apply(value) === '[object Array]') { 1061 | 1062 | // The value is an array. Stringify every element. Use null as a placeholder 1063 | // for non-JSON values. 1064 | 1065 | length = value.length; 1066 | for (i = 0; i < length; i += 1) { 1067 | partial[i] = str(i, value) || 'null'; 1068 | } 1069 | 1070 | // Join all of the elements together, separated with commas, and wrap them in 1071 | // brackets. 1072 | 1073 | v = partial.length === 0 ? '[]' : gap ? 1074 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 1075 | '[' + partial.join(',') + ']'; 1076 | gap = mind; 1077 | return v; 1078 | } 1079 | 1080 | // If the replacer is an array, use it to select the members to be stringified. 1081 | 1082 | if (rep && typeof rep === 'object') { 1083 | length = rep.length; 1084 | for (i = 0; i < length; i += 1) { 1085 | if (typeof rep[i] === 'string') { 1086 | k = rep[i]; 1087 | v = str(k, value); 1088 | if (v) { 1089 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1090 | } 1091 | } 1092 | } 1093 | } else { 1094 | 1095 | // Otherwise, iterate through all of the keys in the object. 1096 | 1097 | for (k in value) { 1098 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1099 | v = str(k, value); 1100 | if (v) { 1101 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1102 | } 1103 | } 1104 | } 1105 | } 1106 | 1107 | // Join all of the member texts together, separated with commas, 1108 | // and wrap them in braces. 1109 | 1110 | v = partial.length === 0 ? '{}' : gap ? 1111 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 1112 | '{' + partial.join(',') + '}'; 1113 | gap = mind; 1114 | return v; 1115 | } 1116 | } 1117 | 1118 | // If the JSON object does not yet have a stringify method, give it one. 1119 | 1120 | JSON.stringify = function (value, replacer, space) { 1121 | 1122 | // The stringify method takes a value and an optional replacer, and an optional 1123 | // space parameter, and returns a JSON text. The replacer can be a function 1124 | // that can replace values, or an array of strings that will select the keys. 1125 | // A default replacer method can be provided. Use of the space parameter can 1126 | // produce text that is more easily readable. 1127 | 1128 | var i; 1129 | gap = ''; 1130 | indent = ''; 1131 | 1132 | // If the space parameter is a number, make an indent string containing that 1133 | // many spaces. 1134 | 1135 | if (typeof space === 'number') { 1136 | for (i = 0; i < space; i += 1) { 1137 | indent += ' '; 1138 | } 1139 | 1140 | // If the space parameter is a string, it will be used as the indent string. 1141 | 1142 | } else if (typeof space === 'string') { 1143 | indent = space; 1144 | } 1145 | 1146 | // If there is a replacer, it must be a function or an array. 1147 | // Otherwise, throw an error. 1148 | 1149 | rep = replacer; 1150 | if (replacer && typeof replacer !== 'function' && 1151 | (typeof replacer !== 'object' || 1152 | typeof replacer.length !== 'number')) { 1153 | throw new Error('JSON.stringify'); 1154 | } 1155 | 1156 | // Make a fake root object containing our value under the key of ''. 1157 | // Return the result of stringifying the value. 1158 | 1159 | return str('', {'': value}); 1160 | }; 1161 | 1162 | // If the JSON object does not yet have a parse method, give it one. 1163 | 1164 | JSON.parse = function (text, reviver) { 1165 | // The parse method takes a text and an optional reviver function, and returns 1166 | // a JavaScript value if the text is a valid JSON text. 1167 | 1168 | var j; 1169 | 1170 | function walk(holder, key) { 1171 | 1172 | // The walk method is used to recursively walk the resulting structure so 1173 | // that modifications can be made. 1174 | 1175 | var k, v, value = holder[key]; 1176 | if (value && typeof value === 'object') { 1177 | for (k in value) { 1178 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1179 | v = walk(value, k); 1180 | if (v !== undefined) { 1181 | value[k] = v; 1182 | } else { 1183 | delete value[k]; 1184 | } 1185 | } 1186 | } 1187 | } 1188 | return reviver.call(holder, key, value); 1189 | } 1190 | 1191 | 1192 | // Parsing happens in four stages. In the first stage, we replace certain 1193 | // Unicode characters with escape sequences. JavaScript handles many characters 1194 | // incorrectly, either silently deleting them, or treating them as line endings. 1195 | 1196 | text = String(text); 1197 | cx.lastIndex = 0; 1198 | if (cx.test(text)) { 1199 | text = text.replace(cx, function (a) { 1200 | return '\\u' + 1201 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 1202 | }); 1203 | } 1204 | 1205 | // In the second stage, we run the text against regular expressions that look 1206 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 1207 | // because they can cause invocation, and '=' because it can cause mutation. 1208 | // But just to be safe, we want to reject all unexpected forms. 1209 | 1210 | // We split the second stage into 4 regexp operations in order to work around 1211 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 1212 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 1213 | // replace all simple value tokens with ']' characters. Third, we delete all 1214 | // open brackets that follow a colon or comma or that begin the text. Finally, 1215 | // we look to see that the remaining characters are only whitespace or ']' or 1216 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 1217 | 1218 | if (/^[\],:{}\s]*$/ 1219 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 1220 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 1221 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 1222 | 1223 | // In the third stage we use the eval function to compile the text into a 1224 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 1225 | // in JavaScript: it can begin a block or an object literal. We wrap the text 1226 | // in parens to eliminate the ambiguity. 1227 | 1228 | j = eval('(' + text + ')'); 1229 | 1230 | // In the optional fourth stage, we recursively walk the new structure, passing 1231 | // each name/value pair to a reviver function for possible transformation. 1232 | 1233 | return typeof reviver === 'function' ? 1234 | walk({'': j}, '') : j; 1235 | } 1236 | 1237 | // If the text is not JSON parseable, then a SyntaxError is thrown. 1238 | 1239 | throw new SyntaxError('JSON.parse'); 1240 | }; 1241 | 1242 | return JSON; 1243 | })(); 1244 | 1245 | if ('undefined' != typeof window) { 1246 | window.expect = module.exports; 1247 | } 1248 | 1249 | })( 1250 | this 1251 | , 'undefined' != typeof module ? module : {} 1252 | , 'undefined' != typeof exports ? exports : {} 1253 | ); 1254 | -------------------------------------------------------------------------------- /spec/happen.js: -------------------------------------------------------------------------------- 1 | // https://github.com/tmcw/happen 2 | 3 | !(function(context) { 4 | var h = {}; 5 | 6 | // Make inheritance bearable: clone one level of properties 7 | function extend(child, parent) { 8 | for (var property in parent) { 9 | if (typeof child[property] == 'undefined') { 10 | child[property] = parent[property]; 11 | } 12 | } 13 | return child; 14 | } 15 | 16 | h.once = function(x, o) { 17 | var evt; 18 | 19 | if (o.type.slice(0, 3) === 'key') { 20 | if (typeof Event === 'function') { 21 | evt = new Event(o.type); 22 | evt.keyCode = o.keyCode || 0; 23 | evt.charCode = o.charCode || 0; 24 | evt.shift = o.shift || false; 25 | evt.meta = o.meta || false; 26 | evt.ctrl = o.ctrl || false; 27 | evt.alt = o.alt || false; 28 | } else { 29 | evt = document.createEvent('KeyboardEvent'); 30 | // https://developer.mozilla.org/en/DOM/event.initKeyEvent 31 | // https://developer.mozilla.org/en/DOM/KeyboardEvent 32 | evt[(evt.initKeyEvent) ? 'initKeyEvent' 33 | : 'initKeyboardEvent']( 34 | o.type, // in DOMString typeArg, 35 | true, // in boolean canBubbleArg, 36 | true, // in boolean cancelableArg, 37 | null, // in nsIDOMAbstractView viewArg, Specifies UIEvent.view. This value may be null. 38 | o.ctrl || false, // in boolean ctrlKeyArg, 39 | o.alt || false, // in boolean altKeyArg, 40 | o.shift || false, // in boolean shiftKeyArg, 41 | o.meta || false, // in boolean metaKeyArg, 42 | o.keyCode || 0, // in unsigned long keyCodeArg, 43 | o.charCode || 0 // in unsigned long charCodeArg); 44 | ); 45 | } 46 | } else { 47 | evt = document.createEvent('MouseEvents'); 48 | // https://developer.mozilla.org/en/DOM/event.initMouseEvent 49 | evt.initMouseEvent(o.type, 50 | true, // canBubble 51 | true, // cancelable 52 | window, // 'AbstractView' 53 | o.clicks || 0, // click count 54 | o.screenX || 0, // screenX 55 | o.screenY || 0, // screenY 56 | o.clientX || 0, // clientX 57 | o.clientY || 0, // clientY 58 | o.ctrl || 0, // ctrl 59 | o.alt || false, // alt 60 | o.shift || false, // shift 61 | o.meta || false, // meta 62 | o.button || false, // mouse button 63 | null // relatedTarget 64 | ); 65 | } 66 | 67 | x.dispatchEvent(evt); 68 | }; 69 | 70 | var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove', 'keydown', 'keyup', 'keypress'], 71 | s, i = 0; 72 | 73 | while (s = shortcuts[i++]) { 74 | h[s] = (function(s) { 75 | return function(x, o) { 76 | h.once(x, extend(o || {}, { type: s })); 77 | }; 78 | })(s); 79 | } 80 | 81 | h.dblclick = function(x, o) { 82 | h.once(x, extend(o || {}, { 83 | type: 'dblclick', 84 | clicks: 2 85 | })); 86 | }; 87 | 88 | this.happen = h; 89 | 90 | if (typeof module !== 'undefined') { 91 | module.exports = this.happen; 92 | } 93 | })(this); 94 | -------------------------------------------------------------------------------- /spec/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | var libSources = require(__dirname+'/../build/build.js').getFiles(); 3 | var leafletSources = require(__dirname+'/../node_modules/leaflet/build/build.js').getFiles(); 4 | 5 | // base path, that will be used to resolve files and exclude 6 | basePath = ''; 7 | 8 | for (var i=0; i < libSources.length; i++) { 9 | libSources[i] = "../" + libSources[i]; 10 | } 11 | for (var i=0; i < leafletSources.length; i++) { 12 | leafletSources[i] = "../node_modules/leaflet/" + leafletSources[i]; 13 | } 14 | 15 | // list of files / patterns to load in the browser 16 | files = [].concat([ 17 | "../node_modules/mocha/mocha.js", 18 | MOCHA_ADAPTER, 19 | "sinon.js", 20 | "expect.js" 21 | ], leafletSources, libSources, [ 22 | "after.js", 23 | "happen.js", 24 | "suites/SpecHelper.js", 25 | "suites/**/*.js" 26 | ]); 27 | 28 | // list of files to exclude 29 | exclude = [ 30 | ]; 31 | 32 | // test results reporter to use 33 | // possible values: 'dots', 'progress', 'junit' 34 | reporters = ['dots']; 35 | 36 | // web server port 37 | port = 8080; 38 | 39 | // cli runner port 40 | runnerPort = 9100; 41 | 42 | // enable / disable colors in the output (reporters and logs) 43 | colors = true; 44 | 45 | // level of logging 46 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 47 | logLevel = LOG_WARN; 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch = false; 51 | 52 | // Start these browsers, currently available: 53 | // - Chrome 54 | // - ChromeCanary 55 | // - Firefox 56 | // - Opera 57 | // - Safari (only Mac) 58 | // - PhantomJS 59 | // - IE (only Windows) 60 | browsers = ['PhantomJS']; 61 | 62 | // If browser does not capture in given timeout [ms], kill it 63 | captureTimeout = 5000; 64 | 65 | // Continuous Integration mode 66 | // if true, it capture browsers, run tests and exit 67 | singleRun = true; 68 | -------------------------------------------------------------------------------- /spec/suites/onAddSpec.js: -------------------------------------------------------------------------------- 1 | describe('onAdd', function () { 2 | var map, div; 3 | 4 | beforeEach(function () { 5 | div = document.createElement('div'); 6 | div.style.width = '200px'; 7 | div.style.height = '200px'; 8 | document.body.appendChild(div); 9 | 10 | map = L.map(div); 11 | 12 | map.fitBounds(new L.LatLngBounds([ 13 | [1, 1], 14 | [2, 2] 15 | ])); 16 | }); 17 | 18 | afterEach(function () { 19 | document.body.removeChild(div); 20 | }); 21 | 22 | it('successfully handles adding a pattern to the map', function () { 23 | 24 | var path = new L.PatternPath({ 25 | d: 'M10 0 L7 20 L25 20 Z', 26 | color: '#000000', 27 | fill: true, 28 | fillColor: '#ff0000', 29 | fillOpacity: 1.0 30 | }); 31 | 32 | var pattern = new L.Pattern({width:50, height:50}); 33 | pattern.addShape(path); 34 | pattern.addTo(map); 35 | 36 | expect(map.hasPattern(pattern)).to.be(true); 37 | expect(pattern._map).to.not.be(null); 38 | }); 39 | }); -------------------------------------------------------------------------------- /src/Pattern.SVG.js: -------------------------------------------------------------------------------- 1 | 2 | L.Pattern.SVG_NS = 'http://www.w3.org/2000/svg'; 3 | 4 | L.Pattern = L.Pattern.extend({ 5 | _createElement: function (name) { 6 | return document.createElementNS(L.Pattern.SVG_NS, name); 7 | }, 8 | 9 | _initDom: function () { 10 | this._dom = this._createElement('pattern'); 11 | if (this.options.className) { 12 | L.DomUtil.addClass(this._dom, this.options.className); 13 | } 14 | this._updateStyle(); 15 | }, 16 | 17 | _addDom: function () { 18 | this._map._defRoot.appendChild(this._dom); 19 | }, 20 | 21 | _removeDom: function () { 22 | L.DomUtil.remove(this._dom); 23 | }, 24 | 25 | _updateStyle: function () { 26 | var dom = this._dom, 27 | options = this.options; 28 | 29 | if (!dom) { return; } 30 | 31 | dom.setAttribute('id', L.stamp(this)); 32 | dom.setAttribute('x', options.x); 33 | dom.setAttribute('y', options.y); 34 | dom.setAttribute('width', options.width); 35 | dom.setAttribute('height', options.height); 36 | dom.setAttribute('patternUnits', options.patternUnits); 37 | dom.setAttribute('patternContentUnits', options.patternContentUnits); 38 | 39 | if (options.patternTransform || options.angle) { 40 | var transform = options.patternTransform ? options.patternTransform + " " : ""; 41 | transform += options.angle ? "rotate(" + options.angle + ") " : ""; 42 | dom.setAttribute('patternTransform', transform); 43 | } 44 | else { 45 | dom.removeAttribute('patternTransform'); 46 | } 47 | 48 | for (var i in this._shapes) { 49 | this._shapes[i]._updateStyle(); 50 | } 51 | } 52 | }); 53 | 54 | L.Map.include({ 55 | _initDefRoot: function () { 56 | if (!this._defRoot) { 57 | if (typeof this.getRenderer === 'function') { 58 | var renderer = this.getRenderer(this); 59 | this._defRoot = L.Pattern.prototype._createElement('defs'); 60 | renderer._container.appendChild(this._defRoot); 61 | } else { 62 | if (!this._pathRoot) { 63 | this._initPathRoot(); 64 | } 65 | this._defRoot = L.Pattern.prototype._createElement('defs'); 66 | this._pathRoot.appendChild(this._defRoot); 67 | } 68 | } 69 | } 70 | }); 71 | 72 | if (L.SVG) { 73 | L.SVG.include({ 74 | _superUpdateStyle: L.SVG.prototype._updateStyle, 75 | 76 | _updateStyle: function (layer) { 77 | this._superUpdateStyle(layer); 78 | 79 | if (layer.options.fill && layer.options.fillPattern) { 80 | layer._path.setAttribute('fill', 'url(#' + L.stamp(layer.options.fillPattern) + ")"); 81 | } 82 | } 83 | }); 84 | } 85 | else { 86 | L.Path.include({ 87 | _superUpdateStyle: L.Path.prototype._updateStyle, 88 | 89 | _updateStyle: function () { 90 | this._superUpdateStyle(); 91 | 92 | if (this.options.fill && this.options.fillPattern) { 93 | this._path.setAttribute('fill', 'url(#' + L.stamp(this.options.fillPattern) + ")"); 94 | } 95 | } 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /src/Pattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.Pattern is the base class for fill patterns for leaflet Paths. 3 | */ 4 | 5 | L.Pattern = L.Class.extend({ 6 | // L.Mixin.Events is deprecated 7 | includes: [L.Evented.prototype || L.Mixin.Events], 8 | 9 | options: { 10 | x: 0, 11 | y: 0, 12 | width: 8, 13 | height: 8, 14 | patternUnits: 'userSpaceOnUse', 15 | patternContentUnits: 'userSpaceOnUse' 16 | // angle: <0 - 360> 17 | // patternTransform: 18 | }, 19 | 20 | _addShapes: L.Util.falseFn, 21 | _update: L.Util.falseFn, 22 | 23 | initialize: function (options) { 24 | this._shapes = {}; 25 | L.setOptions(this, options); 26 | }, 27 | 28 | onAdd: function (map) { 29 | this._map = map.target ? map.target : map; 30 | this._map._initDefRoot(); 31 | 32 | // Create the DOM Object for the pattern. 33 | this._initDom(); 34 | 35 | // Any shapes that were added before this was added to the map need to have their onAdd called. 36 | for (var i in this._shapes) { 37 | this._shapes[i].onAdd(this); 38 | } 39 | 40 | // Call any children that want to add their own shapes. 41 | this._addShapes(); 42 | 43 | // Add the DOM Object to the DOM Tree 44 | this._addDom(); 45 | this.redraw(); 46 | 47 | if (this.getEvents) { 48 | this._map.on(this.getEvents(), this); 49 | } 50 | this.fire('add'); 51 | this._map.fire('patternadd', {pattern: this}); 52 | }, 53 | 54 | onRemove: function () { 55 | this._removeDom(); 56 | }, 57 | 58 | redraw: function () { 59 | if (this._map) { 60 | this._update(); 61 | for (var i in this._shapes) { 62 | this._shapes[i].redraw(); 63 | } 64 | } 65 | return this; 66 | }, 67 | 68 | setStyle: function (style) { 69 | L.setOptions(this, style); 70 | if (this._map) { 71 | this._updateStyle(); 72 | this.redraw(); 73 | } 74 | return this; 75 | }, 76 | 77 | addTo: function (map) { 78 | map.addPattern(this); 79 | return this; 80 | }, 81 | 82 | remove: function () { 83 | return this.removeFrom(this._map); 84 | }, 85 | 86 | removeFrom: function (map) { 87 | if (map) { 88 | map.removePattern(this); 89 | } 90 | return this; 91 | } 92 | }); 93 | 94 | L.Map.addInitHook(function () { 95 | this._patterns = {}; 96 | }); 97 | 98 | L.Map.include({ 99 | addPattern: function (pattern) { 100 | var id = L.stamp(pattern); 101 | if (this._patterns[id]) { return pattern; } 102 | this._patterns[id] = pattern; 103 | 104 | this.whenReady(pattern.onAdd, pattern); 105 | return this; 106 | }, 107 | 108 | removePattern: function (pattern) { 109 | var id = L.stamp(pattern); 110 | if (!this._patterns[id]) { return this; } 111 | 112 | if (this._loaded) { 113 | pattern.onRemove(this); 114 | } 115 | 116 | if (pattern.getEvents) { 117 | this.off(pattern.getEvents(), pattern); 118 | } 119 | 120 | delete this._patterns[id]; 121 | 122 | if (this._loaded) { 123 | this.fire('patternremove', {pattern: pattern}); 124 | pattern.fire('remove'); 125 | } 126 | 127 | pattern._map = null; 128 | return this; 129 | }, 130 | 131 | hasPattern: function (pattern) { 132 | return !!pattern && (L.stamp(pattern) in this._patterns); 133 | } 134 | }); 135 | -------------------------------------------------------------------------------- /src/PatternCircle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.PatternCircle is the implementation of PatternShape for adding Circles 3 | */ 4 | 5 | L.PatternCircle = L.PatternShape.extend({ 6 | options: { 7 | x: 0, 8 | y: 0, 9 | radius: 0 10 | }, 11 | 12 | _initDom: function () { 13 | this._initDomElement('circle'); 14 | }, 15 | 16 | _updateShape: function () { 17 | if (!this._dom) { return; } 18 | this._dom.setAttribute('cx', this.options.x); 19 | this._dom.setAttribute('cy', this.options.y); 20 | this._dom.setAttribute('r', this.options.radius); 21 | } 22 | }); -------------------------------------------------------------------------------- /src/PatternPath.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.PatternPath is the implementation of PatternShape for adding Paths 3 | */ 4 | 5 | L.PatternPath = L.PatternShape.extend({ 6 | // options: { 7 | // d: 8 | // }, 9 | 10 | _initDom: function () { 11 | this._initDomElement('path'); 12 | }, 13 | 14 | _updateShape: function () { 15 | if (!this._dom) { return; } 16 | this._dom.setAttribute('d', this.options.d); 17 | } 18 | }); -------------------------------------------------------------------------------- /src/PatternRect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.PatternRect is the implementation of PatternShape for adding Rectangles 3 | */ 4 | 5 | L.PatternRect = L.PatternShape.extend({ 6 | options: { 7 | x: 0, 8 | y: 0, 9 | width: 10, 10 | height: 10, 11 | // rx: x radius for rounded corners 12 | // ry: y radius for rounded corners 13 | }, 14 | 15 | _initDom: function () { 16 | this._initDomElement('rect'); 17 | }, 18 | 19 | _updateShape: function () { 20 | if (!this._dom) { return; } 21 | this._dom.setAttribute('x', this.options.x); 22 | this._dom.setAttribute('y', this.options.y); 23 | this._dom.setAttribute('width', this.options.width); 24 | this._dom.setAttribute('height', this.options.height); 25 | if (this.options.rx) { this._dom.setAttribute('rx', this.options.rx); } 26 | if (this.options.ry) { this._dom.setAttribute('ry', this.options.ry); } 27 | } 28 | }); -------------------------------------------------------------------------------- /src/PatternShape.SVG.js: -------------------------------------------------------------------------------- 1 | 2 | L.PatternShape.SVG_NS = 'http://www.w3.org/2000/svg'; 3 | 4 | L.PatternShape = L.PatternShape.extend({ 5 | _createElement: function (name) { 6 | return document.createElementNS(L.PatternShape.SVG_NS, name); 7 | }, 8 | 9 | _initDom: L.Util.falseFn, 10 | _updateShape: L.Util.falseFn, 11 | 12 | _initDomElement: function (type) { 13 | this._dom = this._createElement(type); 14 | if (this.options.className) { 15 | L.DomUtil.addClass(this._dom, this.options.className); 16 | } 17 | this._updateStyle(); 18 | }, 19 | 20 | _addDom: function () { 21 | this._pattern._dom.appendChild(this._dom); 22 | }, 23 | 24 | _updateStyle: function () { 25 | var dom = this._dom, 26 | options = this.options; 27 | 28 | if (!dom) { return; } 29 | 30 | if (options.stroke) { 31 | dom.setAttribute('stroke', options.color); 32 | dom.setAttribute('stroke-opacity', options.opacity); 33 | dom.setAttribute('stroke-width', options.weight); 34 | dom.setAttribute('stroke-linecap', options.lineCap); 35 | dom.setAttribute('stroke-linejoin', options.lineJoin); 36 | 37 | if (options.dashArray) { 38 | dom.setAttribute('stroke-dasharray', options.dashArray); 39 | } else { 40 | dom.removeAttribute('stroke-dasharray'); 41 | } 42 | 43 | if (options.dashOffset) { 44 | dom.setAttribute('stroke-dashoffset', options.dashOffset); 45 | } else { 46 | dom.removeAttribute('stroke-dashoffset'); 47 | } 48 | } else { 49 | dom.setAttribute('stroke', 'none'); 50 | } 51 | 52 | if (options.fill) { 53 | if (options.fillPattern) { 54 | dom.setAttribute('fill', 'url(#' + L.stamp(options.fillPattern) + ")"); 55 | } 56 | else { 57 | dom.setAttribute('fill', options.fillColor || options.color); 58 | } 59 | dom.setAttribute('fill-opacity', options.fillOpacity); 60 | dom.setAttribute('fill-rule', options.fillRule || 'evenodd'); 61 | } else { 62 | dom.setAttribute('fill', 'none'); 63 | } 64 | 65 | dom.setAttribute('pointer-events', options.pointerEvents || (options.interactive ? 'visiblePainted' : 'none')); 66 | } 67 | }); 68 | 69 | -------------------------------------------------------------------------------- /src/PatternShape.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.PatternShape is the base class that is used to define the shapes in Patterns. 3 | */ 4 | 5 | L.PatternShape = L.Class.extend({ 6 | 7 | options: { 8 | stroke: true, 9 | color: '#3388ff', 10 | weight: 3, 11 | opacity: 1, 12 | lineCap: 'round', 13 | lineJoin: 'round', 14 | // dashArray: null 15 | // dashOffset: null 16 | 17 | // fill: false 18 | // fillColor: same as color by default 19 | fillOpacity: 0.2, 20 | fillRule: 'evenodd', 21 | // fillPattern: L.Pattern 22 | }, 23 | 24 | initialize: function (options) { 25 | L.setOptions(this, options); 26 | }, 27 | 28 | // Called when the parent Pattern get's added to the map, 29 | // or when added to a Pattern that is already on the map. 30 | onAdd: function (pattern) { 31 | this._pattern = pattern; 32 | if (this._pattern._dom) { 33 | this._initDom(); // This function is implemented by it's children. 34 | this._addDom(); 35 | } 36 | }, 37 | 38 | addTo: function (pattern) { 39 | pattern.addShape(this); 40 | return this; 41 | }, 42 | 43 | redraw: function () { 44 | if (this._pattern) { 45 | this._updateShape(); // This function is implemented by it's children. 46 | } 47 | return this; 48 | }, 49 | 50 | setStyle: function (style) { 51 | L.setOptions(this, style); 52 | if (this._pattern) { 53 | this._updateStyle(); 54 | } 55 | return this; 56 | }, 57 | 58 | setShape: function (shape) { 59 | this.options = L.extend({}, this.options, shape); 60 | this._updateShape(); 61 | }, 62 | }); 63 | 64 | L.Pattern.include({ 65 | addShape: function (shape) { 66 | var id = L.stamp(shape); 67 | if (this._shapes[id]) { return shape; } 68 | this._shapes[id] = shape; 69 | shape.onAdd(this); 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /src/StripePattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | * L.StripePattern is an implementation of Pattern that creates stripes. 3 | */ 4 | 5 | L.StripePattern = L.Pattern.extend({ 6 | 7 | options: { 8 | weight: 4, 9 | spaceWeight: 4, 10 | color: '#000000', 11 | spaceColor: '#ffffff', 12 | opacity: 1.0, 13 | spaceOpacity: 0.0 14 | }, 15 | 16 | _addShapes: function () { 17 | this._stripe = new L.PatternPath({ 18 | stroke: true, 19 | weight: this.options.weight, 20 | color: this.options.color, 21 | opacity: this.options.opacity 22 | }); 23 | 24 | this._space = new L.PatternPath({ 25 | stroke: true, 26 | weight: this.options.spaceWeight, 27 | color: this.options.spaceColor, 28 | opacity: this.options.spaceOpacity 29 | }); 30 | 31 | this.addShape(this._stripe); 32 | this.addShape(this._space); 33 | 34 | this._update(); 35 | }, 36 | 37 | _update: function () { 38 | this._stripe.options.d = 'M0 ' + this._stripe.options.weight / 2 + ' H ' + this.options.width; 39 | this._space.options.d = 'M0 ' + (this._stripe.options.weight + this._space.options.weight / 2) + ' H ' + this.options.width; 40 | }, 41 | 42 | setStyle: L.Pattern.prototype.setStyle 43 | }); 44 | 45 | L.stripePattern = function (options) { 46 | return new L.StripePattern(options); 47 | }; -------------------------------------------------------------------------------- /src/copyright.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.pattern, Provides tools to set the backgrounds of vector shapes in Leaflet to be patterns. 3 | https://github.com/teastman/Leaflet.pattern 4 | (c) 2015, Tyler Eastman 5 | */ 6 | --------------------------------------------------------------------------------