├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── bench └── bench.js ├── docs ├── assets │ ├── anchor.js │ ├── bass-addons.css │ ├── bass.css │ ├── fonts │ │ ├── EOT │ │ │ ├── SourceCodePro-Bold.eot │ │ │ └── SourceCodePro-Regular.eot │ │ ├── LICENSE.txt │ │ ├── OTF │ │ │ ├── SourceCodePro-Bold.otf │ │ │ └── SourceCodePro-Regular.otf │ │ ├── TTF │ │ │ ├── SourceCodePro-Bold.ttf │ │ │ └── SourceCodePro-Regular.ttf │ │ ├── WOFF │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff │ │ │ │ └── SourceCodePro-Regular.otf.woff │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff │ │ │ │ └── SourceCodePro-Regular.ttf.woff │ │ ├── WOFF2 │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff2 │ │ │ │ └── SourceCodePro-Regular.otf.woff2 │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff2 │ │ │ │ └── SourceCodePro-Regular.ttf.woff2 │ │ ├── source-code-pro.css │ │ ├── source-code-pro │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── WOFF │ │ │ │ └── OTF │ │ │ │ │ └── SourceCodePro-Regular.otf.woff │ │ │ └── source-code-pro.css │ │ └── source-sans-pro │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── WOFF │ │ │ └── OTF │ │ │ │ ├── SourceSansPro-Bold.otf.woff │ │ │ │ ├── SourceSansPro-Light.otf.woff │ │ │ │ ├── SourceSansPro-Regular.otf.woff │ │ │ │ └── SourceSansPro-Semibold.otf.woff │ │ │ ├── bower.json │ │ │ └── source-sans-pro.css │ ├── github.css │ ├── site.js │ ├── split.css │ ├── split.js │ └── style.css └── index.html ├── index.mjs ├── package.json └── test └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "extends": [ 12 | "eslint:recommended" 13 | ], 14 | "rules": { 15 | "dot-notation": "error", 16 | "eqeqeq": ["error", "smart"], 17 | "indent": ["off", 4], 18 | "keyword-spacing": "error", 19 | "linebreak-style": ["error", "unix"], 20 | "no-caller": "error", 21 | "no-catch-shadow": "error", 22 | "no-div-regex": "error", 23 | "no-extend-native": "error", 24 | "no-extra-bind": "error", 25 | "no-floating-decimal": "error", 26 | "no-implied-eval": "error", 27 | "no-invalid-this": "error", 28 | "no-iterator": "error", 29 | "no-labels": "error", 30 | "no-label-var": "error", 31 | "no-lone-blocks": "error", 32 | "no-loop-func": "error", 33 | "no-multi-str": "error", 34 | "no-native-reassign": "error", 35 | "no-new": "error", 36 | "no-new-func": "error", 37 | "no-new-wrappers": "error", 38 | "no-octal": "error", 39 | "no-octal-escape": "error", 40 | "no-process-env": "error", 41 | "no-proto": "error", 42 | "no-return-assign": "off", 43 | "no-script-url": "error", 44 | "no-self-compare": "error", 45 | "no-sequences": "error", 46 | "no-shadow": "off", 47 | "no-shadow-restricted-names": "error", 48 | "no-throw-literal": "error", 49 | "no-unneeded-ternary": "error", 50 | "no-unused-expressions": "error", 51 | "no-unexpected-multiline": "error", 52 | "no-unused-vars": "warn", 53 | "no-void": "error", 54 | "no-warning-comments": "warn", 55 | "no-with": "error", 56 | "no-use-before-define": ["off", "nofunc"], 57 | "semi": ["error", "always"], 58 | "semi-spacing": "error", 59 | "space-unary-ops": "error", 60 | "wrap-regex": "off", 61 | "quotes": ["error", "single"] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .esm-cache 3 | .vscode/ 4 | .nyc_output/ 5 | coverage/ 6 | index.js 7 | node_modules/ 8 | npm-debug.log 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .esm-cache 3 | .vscode/ 4 | .nyc_output/ 5 | .eslintrc 6 | .travis.yml 7 | 8 | bench/ 9 | coverage/ 10 | docs/ 11 | node_modules/ 12 | test/ 13 | 14 | npm-debug.log 15 | package-lock.json 16 | CODE_OF_CONDUCT.md 17 | RELEASE.md 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | - "10" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | _Breaking changes, which may affect downstream projects are marked with a_ :warning: 2 | 3 | 4 | ## 3.2.0 5 | ##### 2018-Jul-12 6 | * Replace legacy Rollup `jsnext:main` with now standard `module` ([#91]) 7 | * :warning: Drop support for Node 4 8 | 9 | [#91]: https://github.com/mapbox/shelf-pack/issues/91 10 | 11 | ## 3.1.0 12 | ##### 2017-Aug-25 13 | * Provide shrink as a public method ([#90]) 14 | 15 | [#90]: https://github.com/mapbox/shelf-pack/issues/90 16 | 17 | ## 3.0.0 18 | ##### 2017-Feb-12 19 | * :warning: shelf-pack is now a scoped package under the @mapbox namespace 20 | 21 | ## 2.0.1 22 | ##### 2016-Aug-11 23 | * Remember original size of free bins and use that for packing free space ([#29]) 24 | 25 | [#29]: https://github.com/mapbox/shelf-pack/issues/29 26 | 27 | ## 2.0.0 28 | ##### 2016-Aug-08 29 | * Avoid id collisions by updating `maxId` if a numeric `id` is supplied ([#28]) 30 | * Skip free bins if they are more wasteful than free shelves ([#25]) 31 | * Prefer numeric Bin ids over strings (3x perf boost) 32 | * :warning: Remove convenience `width`, `height` properties from Bin object, use only `w`, `h` 33 | * Reference counting (see [#20] or README) 34 | * Each bin now gets a unique id. An id can be passed as optional param to the 35 | `packOne()` function, otherwise a numeric id will be generated. 36 | * Bins are automatically reference counted (i.e. a newly packed Bin will have a `refcount` of 1). 37 | * Functions `ref(bin)` and `unref(bin)` track which bins are being used. 38 | * When a Bin's `refcount` decrements to 0, the Bin will be marked as free, 39 | and its space may be reused by the packing code. 40 | 41 | [#28]: https://github.com/mapbox/shelf-pack/issues/28 42 | [#25]: https://github.com/mapbox/shelf-pack/issues/25 43 | [#20]: https://github.com/mapbox/shelf-pack/issues/20 44 | 45 | ## 1.1.0 46 | ##### 2016-Jul-15 47 | * Release as ES6 module alongside UMD build, add `jsnext:main` to `package.json` 48 | 49 | ## 1.0.0 50 | ##### 2016-Mar-29 51 | * :warning: Rename `allocate()` -> `packOne()` for API consistency 52 | * Add `autoResize` option ([#7]) 53 | * Add `clear()` method 54 | * Generate API docs ([#9]) 55 | * Add `pack()` batch allocator 56 | * Add benchmarks ([#2]) 57 | * :warning: Return `null` instead of `{-1,-1}` on failed allocation ([#1]) 58 | 59 | [#9]: https://github.com/mapbox/shelf-pack/issues/9 60 | [#7]: https://github.com/mapbox/shelf-pack/issues/7 61 | [#2]: https://github.com/mapbox/shelf-pack/issues/2 62 | [#1]: https://github.com/mapbox/shelf-pack/issues/1 63 | 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | #### Guidelines 4 | 5 | * Please be respectful to one another. 6 | * Many contributors are volunteering their time. We might not get to your 7 | issue right away. Be patient. 8 | * Other contributors have different backgrounds and perspectives than you do. 9 | Diversity is a community strength. 10 | * We will disagree sometimes. That's ok. When this happens, assume that the 11 | person with whom you disagree is a smart person with good reasons for believing 12 | something different. 13 | * Everyone has bad days. If you find yourself about to be mean to someone, 14 | take a break and cool off. This project will still be here later. 15 | * When you make a mistake, apologize. 16 | 17 | 18 | #### We will not tolerate 19 | 20 | * Sexualized language or imagery 21 | * Hate speech 22 | * Personal attacks 23 | * Trolling or insulting/derogatory comments 24 | * Public or private harassment 25 | * Publishing other's private information 26 | * Plagiarism 27 | * Other unethical or unprofessional conduct 28 | 29 | #### Enforcement 30 | 31 | Project maintainers commit themselves to fairly enforcing this Code of Conduct. 32 | This means we may: 33 | 34 | * Edit or delete harmful comments, code, issues, or other contributions 35 | * Ban harmful users from the project, temporarily or permanently 36 | 37 | This Code of Conduct applies both within project spaces and in public spaces when an 38 | individual is representing the project or its community. 39 | 40 | Instances of unacceptable behavior may be reported privately to the project maintainers. 41 | 42 | #### See Also 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org). 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Mapbox 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose 6 | with or without fee is hereby granted, provided that the above copyright notice 7 | and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 15 | THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/%40mapbox%2Fshelf-pack.svg)](https://badge.fury.io/js/%40mapbox%2Fshelf-pack) 2 | [![Build Status](https://secure.travis-ci.org/mapbox/shelf-pack.svg)](http://travis-ci.org/mapbox/shelf-pack) 3 | [![Coverage Status](https://coveralls.io/repos/github/mapbox/shelf-pack/badge.svg?branch=master)](https://coveralls.io/github/mapbox/shelf-pack?branch=master) 4 | 5 | ## shelf-pack 6 | 7 | A 2D rectangular [bin packing](https://en.wikipedia.org/wiki/Bin_packing_problem) 8 | data structure that uses the Shelf Best Height Fit heuristic. 9 | 10 | 11 | ### What is it? 12 | 13 | `shelf-pack` is a library for packing little rectangles into a big rectangle. This sounds simple enough, 14 | but finding an optimal packing is a problem with [NP-Complete](https://en.wikipedia.org/wiki/NP-completeness) 15 | complexity. One useful application of bin packing is to assemble icons or glyphs into a sprite texture. 16 | 17 | There are many ways to approach the bin packing problem, but `shelf-pack` uses the Shelf Best 18 | Height Fit heuristic. It works by dividing the total space into "shelves", each with a certain height. 19 | The allocator packs rectangles onto whichever shelf minimizes the amount of wasted vertical space. 20 | 21 | `shelf-pack` is simple, fast, and works best when the rectangles have similar heights (icons and glyphs 22 | are like this). It is not a generalized bin packer, and can potentially waste a lot of space if the 23 | rectangles vary significantly in height. 24 | 25 | 26 | ### How fast is it? 27 | 28 | Really fast! `shelf-pack` is several orders of magnitude faster than the more general 29 | [`bin-pack`](https://www.npmjs.com/package/bin-pack) library. 30 | 31 | ```bash 32 | > npm run bench 33 | 34 | ShelfPack single allocate fixed size bins x 1,610 ops/sec ±1.21% (90 runs sampled) 35 | ShelfPack single allocate random width bins x 1,475 ops/sec ±1.00% (89 runs sampled) 36 | ShelfPack single allocate random height bins x 1,458 ops/sec ±1.00% (90 runs sampled) 37 | ShelfPack single allocate random height and width bins x 1,346 ops/sec ±0.96% (89 runs sampled) 38 | ShelfPack batch allocate fixed size bins x 1,522 ops/sec ±1.06% (86 runs sampled) 39 | ShelfPack batch allocate random width bins x 1,427 ops/sec ±1.06% (89 runs sampled) 40 | ShelfPack batch allocate random height bins x 1,350 ops/sec ±1.63% (90 runs sampled) 41 | ShelfPack batch allocate random height and width bins x 1,257 ops/sec ±1.02% (89 runs sampled) 42 | BinPack batch allocate fixed size bins x 2.21 ops/sec ±6.60% (10 runs sampled) 43 | BinPack batch allocate random width bins x 0.50 ops/sec ±2.25% (6 runs sampled) 44 | BinPack batch allocate random height bins x 0.51 ops/sec ±1.97% (6 runs sampled) 45 | BinPack batch allocate random height and width bins x 0.51 ops/sec ±1.37% (6 runs sampled) 46 | ``` 47 | 48 | 49 | ### Usage 50 | 51 | #### Basic Usage 52 | 53 | ```js 54 | var ShelfPack = require('@mapbox/shelf-pack'); 55 | 56 | // Initialize the sprite with a width and height.. 57 | var sprite = new ShelfPack(64, 64); 58 | 59 | // Pack bins one at a time.. 60 | for (var i = 0; i < 5; i++) { 61 | // packOne() accepts parameters: `width`, `height`, `id` 62 | // and returns a single allocated Bin object.. 63 | // `id` is optional - if you skip it, shelf-pack will make up a number for you.. 64 | // (Protip: numeric ids are much faster than string ids) 65 | 66 | var bin = sprite.packOne(32, 32); 67 | console.log(bin || 'out of space'); 68 | } 69 | 70 | /* output: 71 | Bin { id: 1, x: 0, y: 0, w: 32, h: 32, maxw: 32, maxh: 32, refcount: 1 } 72 | Bin { id: 2, x: 32, y: 0, w: 32, h: 32, maxw: 32, maxh: 32, refcount: 1 } 73 | Bin { id: 3, x: 0, y: 32, w: 32, h: 32, maxw: 32, maxh: 32, refcount: 1 } 74 | Bin { id: 4, x: 32, y: 32, w: 32, h: 32, maxw: 32, maxh: 32, refcount: 1 } 75 | out of space 76 | */ 77 | 78 | // Clear sprite and start over.. 79 | sprite.clear(); 80 | 81 | // Or, resize sprite by passing larger dimensions.. 82 | sprite.resize(128, 128); // width, height 83 | 84 | ``` 85 | 86 | 87 | #### Batch packing 88 | 89 | ```js 90 | var ShelfPack = require('@mapbox/shelf-pack'); 91 | 92 | // If you don't want to think about the size of the sprite, 93 | // the `autoResize` option will allow it to grow as needed.. 94 | var sprite = new ShelfPack(10, 10, { autoResize: true }); 95 | 96 | // Bins can be allocated in batches.. 97 | // Each requested bin should have `w`, `h` (or `width`, `height`) properties.. 98 | var requests = [ 99 | { id: 'a', width: 10, height: 10 }, 100 | { id: 'b', width: 10, height: 12 }, 101 | { id: 'c', w: 10, h: 12 }, 102 | { id: 'd', w: 10, h: 10 } 103 | ]; 104 | 105 | // pack() returns an Array of packed Bin objects.. 106 | var results = sprite.pack(requests); 107 | 108 | results.forEach(function(bin) { 109 | console.log(bin); 110 | }); 111 | 112 | /* output: 113 | Bin { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 114 | Bin { id: 'b', x: 0, y: 10, w: 10, h: 12, maxw: 10, maxh: 12, refcount: 1 } 115 | Bin { id: 'c', x: 10, y: 10, w: 10, h: 12, maxw: 10, maxh: 12, refcount: 1 } 116 | Bin { id: 'd', x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 117 | */ 118 | 119 | // If you don't mind letting ShelfPack modify your objects, 120 | // the `inPlace` option will decorate your bin objects with `x` and `y` properties. 121 | // Fancy! 122 | var myBins = [ 123 | { id: 'e', width: 12, height: 24 }, 124 | { id: 'f', width: 12, height: 12 }, 125 | { id: 'g', w: 10, h: 10 } 126 | ]; 127 | 128 | sprite.pack(myBins, { inPlace: true }); 129 | myBins.forEach(function(bin) { 130 | console.log(bin); 131 | }); 132 | 133 | /* output: 134 | { id: 'e', width: 12, height: 24, x: 0, y: 22 } 135 | { id: 'f', width: 12, height: 12, x: 20, y: 10 } 136 | { id: 'g', w: 10, h: 10, x: 20, y: 0 } 137 | */ 138 | 139 | ``` 140 | 141 | #### Reference Counting 142 | 143 | ```js 144 | var ShelfPack = require('@mapbox/shelf-pack'); 145 | 146 | // Initialize the sprite with a width and height.. 147 | var sprite = new ShelfPack(64, 64); 148 | 149 | // Allocated bins are automatically reference counted. 150 | // They start out having a refcount of 1. 151 | [100, 101, 102].forEach(function(id) { 152 | var bin = sprite.packOne(16, 16, id); 153 | console.log(bin); 154 | }); 155 | 156 | /* output: 157 | Bin { id: 100, x: 0, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 1 } 158 | Bin { id: 101, x: 16, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 1 } 159 | Bin { id: 102, x: 32, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 1 } 160 | */ 161 | 162 | // If you try to pack the same id again, shelf-pack will not re-pack it. 163 | // Instead, it will increment the reference count automatically.. 164 | var bin102 = sprite.packOne(16, 16, 102); 165 | console.log(bin102); 166 | 167 | /* output: 168 | Bin { id: 102, x: 32, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 2 } 169 | */ 170 | 171 | // You can also manually increment the reference count.. 172 | var bin101 = sprite.getBin(101); 173 | sprite.ref(bin101); 174 | console.log(bin101); 175 | 176 | /* output: 177 | Bin { id: 101, x: 16, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 2 } 178 | */ 179 | 180 | // ...and decrement it! 181 | var bin100 = sprite.getBin(100); 182 | sprite.unref(bin100); 183 | console.log(bin100); 184 | 185 | /* output: 186 | Bin { id: 100, x: 0, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 0 } 187 | */ 188 | 189 | // Bins with a refcount of 0 are considered free space. 190 | // Next time a bin is packed, shelf-back tries to reuse free space first. 191 | // See how Bin 103 gets allocated at [0,0] - Bin 100's old spot! 192 | var bin103 = sprite.packOne(16, 15, 103); 193 | console.log(bin103); 194 | 195 | /* output: 196 | Bin { id: 103, x: 0, y: 0, w: 16, h: 15, maxw: 16, maxh: 16, refcount: 1 } 197 | */ 198 | 199 | // Bin 103 may be smaller (16x15) but it knows 16x16 was its original size. 200 | // If that space becomes free again, a 16x16 bin will still fit there. 201 | sprite.unref(bin103) 202 | var bin104 = sprite.packOne(16, 16, 104); 203 | console.log(bin104); 204 | 205 | /* output: 206 | Bin { id: 104, x: 0, y: 0, w: 16, h: 16, maxw: 16, maxh: 16, refcount: 1 } 207 | */ 208 | 209 | ``` 210 | 211 | 212 | ### Documentation 213 | 214 | Complete API documentation is here: http://mapbox.github.io/shelf-pack/ 215 | 216 | 217 | ### See also 218 | 219 | J. Jylänky, "A Thousand Ways to Pack the Bin - A Practical 220 | Approach to Two-Dimensional Rectangle Bin Packing," 221 | http://clb.demon.fi/files/RectangleBinPack.pdf, 2010 222 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Release Checklist 2 | 3 | #### Update version, docs, tag, and publish 4 | - [ ] git checkout master 5 | - [ ] npm install 6 | - [ ] Update CHANGELOG 7 | - [ ] Update version number in `package.json` 8 | - [ ] npm run docs 9 | - [ ] git add . 10 | - [ ] git commit -m 'vA.B.C' 11 | - [ ] git tag vA.B.C 12 | - [ ] git push origin master vA.B.C 13 | - [ ] npm publish 14 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Benchmark = require('benchmark'); 4 | var ShelfPack = require('../.'); 5 | var BinPack = require('bin-pack'); 6 | 7 | var N = 10000; 8 | var dim = 10000; 9 | var sizes = [12, 16, 20, 24]; 10 | 11 | function randSize() { 12 | return sizes[Math.floor(Math.random() * sizes.length)]; 13 | } 14 | 15 | // generate data 16 | var fixedBoth = [], 17 | randWidth = [], 18 | randHeight = [], 19 | randBoth = [], 20 | w, h; 21 | 22 | for (var i = 0; i < N; i++) { 23 | w = randSize(); 24 | h = randSize(); 25 | fixedBoth.push({ width: 12, height: 12 }); 26 | randWidth.push({ width: w, height: 12 }); 27 | randHeight.push({ width: 12, height: h }); 28 | randBoth.push({ width: w, height: h }); 29 | } 30 | 31 | 32 | var suite = new Benchmark.Suite(); 33 | 34 | suite 35 | .add('ShelfPack single allocate fixed size bins', function() { 36 | var pack = new ShelfPack(dim, dim); 37 | var ok = true; 38 | for (var j = 0; j < N; j++) { 39 | ok = pack.packOne(fixedBoth[j].width, fixedBoth[j].height); 40 | if (!ok) throw new Error('out of space'); 41 | } 42 | }) 43 | .add('ShelfPack single allocate random width bins', function() { 44 | var pack = new ShelfPack(dim, dim); 45 | var ok = true; 46 | for (var j = 0; j < N; j++) { 47 | ok = pack.packOne(randWidth[j].width, randWidth[j].height); 48 | if (!ok) throw new Error('out of space'); 49 | } 50 | }) 51 | .add('ShelfPack single allocate random height bins', function() { 52 | var pack = new ShelfPack(dim, dim); 53 | var ok = true; 54 | for (var j = 0; j < N; j++) { 55 | ok = pack.packOne(randHeight[j].width, randHeight[j].height); 56 | if (!ok) throw new Error('out of space'); 57 | } 58 | }) 59 | .add('ShelfPack single allocate random height and width bins', function() { 60 | var pack = new ShelfPack(dim, dim); 61 | var ok = true; 62 | for (var j = 0; j < N; j++) { 63 | ok = pack.packOne(randBoth[j].width, randBoth[j].height); 64 | if (!ok) throw new Error('out of space'); 65 | } 66 | }) 67 | .add('ShelfPack batch allocate fixed size bins', function() { 68 | new ShelfPack(dim, dim).pack(fixedBoth); 69 | }) 70 | .add('ShelfPack batch allocate random width bins', function() { 71 | new ShelfPack(dim, dim).pack(randWidth); 72 | }) 73 | .add('ShelfPack batch allocate random height bins', function() { 74 | new ShelfPack(dim, dim).pack(randHeight); 75 | }) 76 | .add('ShelfPack batch allocate random height and width bins', function() { 77 | new ShelfPack(dim, dim).pack(randBoth); 78 | }) 79 | .add('BinPack batch allocate fixed size bins', function() { 80 | BinPack(fixedBoth); 81 | }) 82 | .add('BinPack batch allocate random width bins', function() { 83 | BinPack(randWidth); 84 | }) 85 | .add('BinPack batch allocate random height bins', function() { 86 | BinPack(randHeight); 87 | }) 88 | .add('BinPack batch allocate random height and width bins', function() { 89 | BinPack(randBoth); 90 | }) 91 | .on('cycle', function(event) { 92 | /* eslint-disable no-console */ 93 | if (event.target.error) { 94 | console.log(event.target.name + ': ERROR ' + event.target.error); 95 | } else { 96 | console.log(String(event.target)); 97 | } 98 | /* eslint-enable no-console */ 99 | }) 100 | .run(); 101 | -------------------------------------------------------------------------------- /docs/assets/anchor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AnchorJS - v4.0.0 - 2017-06-02 3 | * https://github.com/bryanbraun/anchorjs 4 | * Copyright (c) 2017 Bryan Braun; Licensed MIT 5 | */ 6 | /* eslint-env amd, node */ 7 | 8 | // https://github.com/umdjs/umd/blob/master/templates/returnExports.js 9 | (function(root, factory) { 10 | 'use strict'; 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define([], factory); 14 | } else if (typeof module === 'object' && module.exports) { 15 | // Node. Does not work with strict CommonJS, but 16 | // only CommonJS-like environments that support module.exports, 17 | // like Node. 18 | module.exports = factory(); 19 | } else { 20 | // Browser globals (root is window) 21 | root.AnchorJS = factory(); 22 | root.anchors = new root.AnchorJS(); 23 | } 24 | })(this, function() { 25 | 'use strict'; 26 | function AnchorJS(options) { 27 | this.options = options || {}; 28 | this.elements = []; 29 | 30 | /** 31 | * Assigns options to the internal options object, and provides defaults. 32 | * @param {Object} opts - Options object 33 | */ 34 | function _applyRemainingDefaultOptions(opts) { 35 | opts.icon = opts.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'. 36 | opts.visible = opts.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always' & 'touch' 37 | opts.placement = opts.hasOwnProperty('placement') 38 | ? opts.placement 39 | : 'right'; // Also accepts 'left' 40 | opts.class = opts.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name. 41 | // Using Math.floor here will ensure the value is Number-cast and an integer. 42 | opts.truncate = opts.hasOwnProperty('truncate') 43 | ? Math.floor(opts.truncate) 44 | : 64; // Accepts any value that can be typecast to a number. 45 | } 46 | 47 | _applyRemainingDefaultOptions(this.options); 48 | 49 | /** 50 | * Checks to see if this device supports touch. Uses criteria pulled from Modernizr: 51 | * https://github.com/Modernizr/Modernizr/blob/da22eb27631fc4957f67607fe6042e85c0a84656/feature-detects/touchevents.js#L40 52 | * @returns {Boolean} - true if the current device supports touch. 53 | */ 54 | this.isTouchDevice = function() { 55 | return !!( 56 | 'ontouchstart' in window || 57 | (window.DocumentTouch && document instanceof DocumentTouch) 58 | ); 59 | }; 60 | 61 | /** 62 | * Add anchor links to page elements. 63 | * @param {String|Array|Nodelist} selector - A CSS selector for targeting the elements you wish to add anchor links 64 | * to. Also accepts an array or nodeList containing the relavant elements. 65 | * @returns {this} - The AnchorJS object 66 | */ 67 | this.add = function(selector) { 68 | var elements, 69 | elsWithIds, 70 | idList, 71 | elementID, 72 | i, 73 | index, 74 | count, 75 | tidyText, 76 | newTidyText, 77 | readableID, 78 | anchor, 79 | visibleOptionToUse, 80 | indexesToDrop = []; 81 | 82 | // We reapply options here because somebody may have overwritten the default options object when setting options. 83 | // For example, this overwrites all options but visible: 84 | // 85 | // anchors.options = { visible: 'always'; } 86 | _applyRemainingDefaultOptions(this.options); 87 | 88 | visibleOptionToUse = this.options.visible; 89 | if (visibleOptionToUse === 'touch') { 90 | visibleOptionToUse = this.isTouchDevice() ? 'always' : 'hover'; 91 | } 92 | 93 | // Provide a sensible default selector, if none is given. 94 | if (!selector) { 95 | selector = 'h2, h3, h4, h5, h6'; 96 | } 97 | 98 | elements = _getElements(selector); 99 | 100 | if (elements.length === 0) { 101 | return this; 102 | } 103 | 104 | _addBaselineStyles(); 105 | 106 | // We produce a list of existing IDs so we don't generate a duplicate. 107 | elsWithIds = document.querySelectorAll('[id]'); 108 | idList = [].map.call(elsWithIds, function assign(el) { 109 | return el.id; 110 | }); 111 | 112 | for (i = 0; i < elements.length; i++) { 113 | if (this.hasAnchorJSLink(elements[i])) { 114 | indexesToDrop.push(i); 115 | continue; 116 | } 117 | 118 | if (elements[i].hasAttribute('id')) { 119 | elementID = elements[i].getAttribute('id'); 120 | } else if (elements[i].hasAttribute('data-anchor-id')) { 121 | elementID = elements[i].getAttribute('data-anchor-id'); 122 | } else { 123 | tidyText = this.urlify(elements[i].textContent); 124 | 125 | // Compare our generated ID to existing IDs (and increment it if needed) 126 | // before we add it to the page. 127 | newTidyText = tidyText; 128 | count = 0; 129 | do { 130 | if (index !== undefined) { 131 | newTidyText = tidyText + '-' + count; 132 | } 133 | 134 | index = idList.indexOf(newTidyText); 135 | count += 1; 136 | } while (index !== -1); 137 | index = undefined; 138 | idList.push(newTidyText); 139 | 140 | elements[i].setAttribute('id', newTidyText); 141 | elementID = newTidyText; 142 | } 143 | 144 | readableID = elementID.replace(/-/g, ' '); 145 | 146 | // The following code builds the following DOM structure in a more effiecient (albeit opaque) way. 147 | // ''; 148 | anchor = document.createElement('a'); 149 | anchor.className = 'anchorjs-link ' + this.options.class; 150 | anchor.href = '#' + elementID; 151 | anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID); 152 | anchor.setAttribute('data-anchorjs-icon', this.options.icon); 153 | 154 | if (visibleOptionToUse === 'always') { 155 | anchor.style.opacity = '1'; 156 | } 157 | 158 | if (this.options.icon === '\ue9cb') { 159 | anchor.style.font = '1em/1 anchorjs-icons'; 160 | 161 | // We set lineHeight = 1 here because the `anchorjs-icons` font family could otherwise affect the 162 | // height of the heading. This isn't the case for icons with `placement: left`, so we restore 163 | // line-height: inherit in that case, ensuring they remain positioned correctly. For more info, 164 | // see https://github.com/bryanbraun/anchorjs/issues/39. 165 | if (this.options.placement === 'left') { 166 | anchor.style.lineHeight = 'inherit'; 167 | } 168 | } 169 | 170 | if (this.options.placement === 'left') { 171 | anchor.style.position = 'absolute'; 172 | anchor.style.marginLeft = '-1em'; 173 | anchor.style.paddingRight = '0.5em'; 174 | elements[i].insertBefore(anchor, elements[i].firstChild); 175 | } else { 176 | // if the option provided is `right` (or anything else). 177 | anchor.style.paddingLeft = '0.375em'; 178 | elements[i].appendChild(anchor); 179 | } 180 | } 181 | 182 | for (i = 0; i < indexesToDrop.length; i++) { 183 | elements.splice(indexesToDrop[i] - i, 1); 184 | } 185 | this.elements = this.elements.concat(elements); 186 | 187 | return this; 188 | }; 189 | 190 | /** 191 | * Removes all anchorjs-links from elements targed by the selector. 192 | * @param {String|Array|Nodelist} selector - A CSS selector string targeting elements with anchor links, 193 | * OR a nodeList / array containing the DOM elements. 194 | * @returns {this} - The AnchorJS object 195 | */ 196 | this.remove = function(selector) { 197 | var index, 198 | domAnchor, 199 | elements = _getElements(selector); 200 | 201 | for (var i = 0; i < elements.length; i++) { 202 | domAnchor = elements[i].querySelector('.anchorjs-link'); 203 | if (domAnchor) { 204 | // Drop the element from our main list, if it's in there. 205 | index = this.elements.indexOf(elements[i]); 206 | if (index !== -1) { 207 | this.elements.splice(index, 1); 208 | } 209 | // Remove the anchor from the DOM. 210 | elements[i].removeChild(domAnchor); 211 | } 212 | } 213 | return this; 214 | }; 215 | 216 | /** 217 | * Removes all anchorjs links. Mostly used for tests. 218 | */ 219 | this.removeAll = function() { 220 | this.remove(this.elements); 221 | }; 222 | 223 | /** 224 | * Urlify - Refine text so it makes a good ID. 225 | * 226 | * To do this, we remove apostrophes, replace nonsafe characters with hyphens, 227 | * remove extra hyphens, truncate, trim hyphens, and make lowercase. 228 | * 229 | * @param {String} text - Any text. Usually pulled from the webpage element we are linking to. 230 | * @returns {String} - hyphen-delimited text for use in IDs and URLs. 231 | */ 232 | this.urlify = function(text) { 233 | // Regex for finding the nonsafe URL characters (many need escaping): & +$,:;=?@"#{}|^~[`%!'<>]./()*\ 234 | var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\]/g, 235 | urlText; 236 | 237 | // The reason we include this _applyRemainingDefaultOptions is so urlify can be called independently, 238 | // even after setting options. This can be useful for tests or other applications. 239 | if (!this.options.truncate) { 240 | _applyRemainingDefaultOptions(this.options); 241 | } 242 | 243 | // Note: we trim hyphens after truncating because truncating can cause dangling hyphens. 244 | // Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 245 | urlText = text 246 | .trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 247 | .replace(/\'/gi, '') // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 248 | .replace(nonsafeChars, '-') // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-" 249 | .replace(/-{2,}/g, '-') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-" 250 | .substring(0, this.options.truncate) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-" 251 | .replace(/^-+|-+$/gm, '') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated" 252 | .toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated" 253 | 254 | return urlText; 255 | }; 256 | 257 | /** 258 | * Determines if this element already has an AnchorJS link on it. 259 | * Uses this technique: http://stackoverflow.com/a/5898748/1154642 260 | * @param {HTMLElemnt} el - a DOM node 261 | * @returns {Boolean} true/false 262 | */ 263 | this.hasAnchorJSLink = function(el) { 264 | var hasLeftAnchor = 265 | el.firstChild && 266 | (' ' + el.firstChild.className + ' ').indexOf(' anchorjs-link ') > -1, 267 | hasRightAnchor = 268 | el.lastChild && 269 | (' ' + el.lastChild.className + ' ').indexOf(' anchorjs-link ') > -1; 270 | 271 | return hasLeftAnchor || hasRightAnchor || false; 272 | }; 273 | 274 | /** 275 | * Turns a selector, nodeList, or array of elements into an array of elements (so we can use array methods). 276 | * It also throws errors on any other inputs. Used to handle inputs to .add and .remove. 277 | * @param {String|Array|Nodelist} input - A CSS selector string targeting elements with anchor links, 278 | * OR a nodeList / array containing the DOM elements. 279 | * @returns {Array} - An array containing the elements we want. 280 | */ 281 | function _getElements(input) { 282 | var elements; 283 | if (typeof input === 'string' || input instanceof String) { 284 | // See https://davidwalsh.name/nodelist-array for the technique transforming nodeList -> Array. 285 | elements = [].slice.call(document.querySelectorAll(input)); 286 | // I checked the 'input instanceof NodeList' test in IE9 and modern browsers and it worked for me. 287 | } else if (Array.isArray(input) || input instanceof NodeList) { 288 | elements = [].slice.call(input); 289 | } else { 290 | throw new Error('The selector provided to AnchorJS was invalid.'); 291 | } 292 | return elements; 293 | } 294 | 295 | /** 296 | * _addBaselineStyles 297 | * Adds baseline styles to the page, used by all AnchorJS links irregardless of configuration. 298 | */ 299 | function _addBaselineStyles() { 300 | // We don't want to add global baseline styles if they've been added before. 301 | if (document.head.querySelector('style.anchorjs') !== null) { 302 | return; 303 | } 304 | 305 | var style = document.createElement('style'), 306 | linkRule = 307 | ' .anchorjs-link {' + 308 | ' opacity: 0;' + 309 | ' text-decoration: none;' + 310 | ' -webkit-font-smoothing: antialiased;' + 311 | ' -moz-osx-font-smoothing: grayscale;' + 312 | ' }', 313 | hoverRule = 314 | ' *:hover > .anchorjs-link,' + 315 | ' .anchorjs-link:focus {' + 316 | ' opacity: 1;' + 317 | ' }', 318 | anchorjsLinkFontFace = 319 | ' @font-face {' + 320 | ' font-family: "anchorjs-icons";' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above 321 | ' src: url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype");' + 322 | ' }', 323 | pseudoElContent = 324 | ' [data-anchorjs-icon]::after {' + 325 | ' content: attr(data-anchorjs-icon);' + 326 | ' }', 327 | firstStyleEl; 328 | 329 | style.className = 'anchorjs'; 330 | style.appendChild(document.createTextNode('')); // Necessary for Webkit. 331 | 332 | // We place it in the head with the other style tags, if possible, so as to 333 | // not look out of place. We insert before the others so these styles can be 334 | // overridden if necessary. 335 | firstStyleEl = document.head.querySelector('[rel="stylesheet"], style'); 336 | if (firstStyleEl === undefined) { 337 | document.head.appendChild(style); 338 | } else { 339 | document.head.insertBefore(style, firstStyleEl); 340 | } 341 | 342 | style.sheet.insertRule(linkRule, style.sheet.cssRules.length); 343 | style.sheet.insertRule(hoverRule, style.sheet.cssRules.length); 344 | style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length); 345 | style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length); 346 | } 347 | } 348 | 349 | return AnchorJS; 350 | }); 351 | -------------------------------------------------------------------------------- /docs/assets/bass-addons.css: -------------------------------------------------------------------------------- 1 | .input { 2 | font-family: inherit; 3 | display: block; 4 | width: 100%; 5 | height: 2rem; 6 | padding: .5rem; 7 | margin-bottom: 1rem; 8 | border: 1px solid #ccc; 9 | font-size: .875rem; 10 | border-radius: 3px; 11 | box-sizing: border-box; 12 | } 13 | -------------------------------------------------------------------------------- /docs/assets/bass.css: -------------------------------------------------------------------------------- 1 | /*! Basscss | http://basscss.com | MIT License */ 2 | 3 | .h1{ font-size: 2rem } 4 | .h2{ font-size: 1.5rem } 5 | .h3{ font-size: 1.25rem } 6 | .h4{ font-size: 1rem } 7 | .h5{ font-size: .875rem } 8 | .h6{ font-size: .75rem } 9 | 10 | .font-family-inherit{ font-family:inherit } 11 | .font-size-inherit{ font-size:inherit } 12 | .text-decoration-none{ text-decoration:none } 13 | 14 | .bold{ font-weight: bold; font-weight: bold } 15 | .regular{ font-weight:normal } 16 | .italic{ font-style:italic } 17 | .caps{ text-transform:uppercase; letter-spacing: .2em; } 18 | 19 | .left-align{ text-align:left } 20 | .center{ text-align:center } 21 | .right-align{ text-align:right } 22 | .justify{ text-align:justify } 23 | 24 | .nowrap{ white-space:nowrap } 25 | .break-word{ word-wrap:break-word } 26 | 27 | .line-height-1{ line-height: 1 } 28 | .line-height-2{ line-height: 1.125 } 29 | .line-height-3{ line-height: 1.25 } 30 | .line-height-4{ line-height: 1.5 } 31 | 32 | .list-style-none{ list-style:none } 33 | .underline{ text-decoration:underline } 34 | 35 | .truncate{ 36 | max-width:100%; 37 | overflow:hidden; 38 | text-overflow:ellipsis; 39 | white-space:nowrap; 40 | } 41 | 42 | .list-reset{ 43 | list-style:none; 44 | padding-left:0; 45 | } 46 | 47 | .inline{ display:inline } 48 | .block{ display:block } 49 | .inline-block{ display:inline-block } 50 | .table{ display:table } 51 | .table-cell{ display:table-cell } 52 | 53 | .overflow-hidden{ overflow:hidden } 54 | .overflow-scroll{ overflow:scroll } 55 | .overflow-auto{ overflow:auto } 56 | 57 | .clearfix:before, 58 | .clearfix:after{ 59 | content:" "; 60 | display:table 61 | } 62 | .clearfix:after{ clear:both } 63 | 64 | .left{ float:left } 65 | .right{ float:right } 66 | 67 | .fit{ max-width:100% } 68 | 69 | .max-width-1{ max-width: 24rem } 70 | .max-width-2{ max-width: 32rem } 71 | .max-width-3{ max-width: 48rem } 72 | .max-width-4{ max-width: 64rem } 73 | 74 | .border-box{ box-sizing:border-box } 75 | 76 | .align-baseline{ vertical-align:baseline } 77 | .align-top{ vertical-align:top } 78 | .align-middle{ vertical-align:middle } 79 | .align-bottom{ vertical-align:bottom } 80 | 81 | .m0{ margin:0 } 82 | .mt0{ margin-top:0 } 83 | .mr0{ margin-right:0 } 84 | .mb0{ margin-bottom:0 } 85 | .ml0{ margin-left:0 } 86 | .mx0{ margin-left:0; margin-right:0 } 87 | .my0{ margin-top:0; margin-bottom:0 } 88 | 89 | .m1{ margin: .5rem } 90 | .mt1{ margin-top: .5rem } 91 | .mr1{ margin-right: .5rem } 92 | .mb1{ margin-bottom: .5rem } 93 | .ml1{ margin-left: .5rem } 94 | .mx1{ margin-left: .5rem; margin-right: .5rem } 95 | .my1{ margin-top: .5rem; margin-bottom: .5rem } 96 | 97 | .m2{ margin: 1rem } 98 | .mt2{ margin-top: 1rem } 99 | .mr2{ margin-right: 1rem } 100 | .mb2{ margin-bottom: 1rem } 101 | .ml2{ margin-left: 1rem } 102 | .mx2{ margin-left: 1rem; margin-right: 1rem } 103 | .my2{ margin-top: 1rem; margin-bottom: 1rem } 104 | 105 | .m3{ margin: 2rem } 106 | .mt3{ margin-top: 2rem } 107 | .mr3{ margin-right: 2rem } 108 | .mb3{ margin-bottom: 2rem } 109 | .ml3{ margin-left: 2rem } 110 | .mx3{ margin-left: 2rem; margin-right: 2rem } 111 | .my3{ margin-top: 2rem; margin-bottom: 2rem } 112 | 113 | .m4{ margin: 4rem } 114 | .mt4{ margin-top: 4rem } 115 | .mr4{ margin-right: 4rem } 116 | .mb4{ margin-bottom: 4rem } 117 | .ml4{ margin-left: 4rem } 118 | .mx4{ margin-left: 4rem; margin-right: 4rem } 119 | .my4{ margin-top: 4rem; margin-bottom: 4rem } 120 | 121 | .mxn1{ margin-left: -.5rem; margin-right: -.5rem; } 122 | .mxn2{ margin-left: -1rem; margin-right: -1rem; } 123 | .mxn3{ margin-left: -2rem; margin-right: -2rem; } 124 | .mxn4{ margin-left: -4rem; margin-right: -4rem; } 125 | 126 | .ml-auto{ margin-left:auto } 127 | .mr-auto{ margin-right:auto } 128 | .mx-auto{ margin-left:auto; margin-right:auto; } 129 | 130 | .p0{ padding:0 } 131 | .pt0{ padding-top:0 } 132 | .pr0{ padding-right:0 } 133 | .pb0{ padding-bottom:0 } 134 | .pl0{ padding-left:0 } 135 | .px0{ padding-left:0; padding-right:0 } 136 | .py0{ padding-top:0; padding-bottom:0 } 137 | 138 | .p1{ padding: .5rem } 139 | .pt1{ padding-top: .5rem } 140 | .pr1{ padding-right: .5rem } 141 | .pb1{ padding-bottom: .5rem } 142 | .pl1{ padding-left: .5rem } 143 | .py1{ padding-top: .5rem; padding-bottom: .5rem } 144 | .px1{ padding-left: .5rem; padding-right: .5rem } 145 | 146 | .p2{ padding: 1rem } 147 | .pt2{ padding-top: 1rem } 148 | .pr2{ padding-right: 1rem } 149 | .pb2{ padding-bottom: 1rem } 150 | .pl2{ padding-left: 1rem } 151 | .py2{ padding-top: 1rem; padding-bottom: 1rem } 152 | .px2{ padding-left: 1rem; padding-right: 1rem } 153 | 154 | .p3{ padding: 2rem } 155 | .pt3{ padding-top: 2rem } 156 | .pr3{ padding-right: 2rem } 157 | .pb3{ padding-bottom: 2rem } 158 | .pl3{ padding-left: 2rem } 159 | .py3{ padding-top: 2rem; padding-bottom: 2rem } 160 | .px3{ padding-left: 2rem; padding-right: 2rem } 161 | 162 | .p4{ padding: 4rem } 163 | .pt4{ padding-top: 4rem } 164 | .pr4{ padding-right: 4rem } 165 | .pb4{ padding-bottom: 4rem } 166 | .pl4{ padding-left: 4rem } 167 | .py4{ padding-top: 4rem; padding-bottom: 4rem } 168 | .px4{ padding-left: 4rem; padding-right: 4rem } 169 | 170 | .col{ 171 | float:left; 172 | box-sizing:border-box; 173 | } 174 | 175 | .col-right{ 176 | float:right; 177 | box-sizing:border-box; 178 | } 179 | 180 | .col-1{ 181 | width:8.33333%; 182 | } 183 | 184 | .col-2{ 185 | width:16.66667%; 186 | } 187 | 188 | .col-3{ 189 | width:25%; 190 | } 191 | 192 | .col-4{ 193 | width:33.33333%; 194 | } 195 | 196 | .col-5{ 197 | width:41.66667%; 198 | } 199 | 200 | .col-6{ 201 | width:50%; 202 | } 203 | 204 | .col-7{ 205 | width:58.33333%; 206 | } 207 | 208 | .col-8{ 209 | width:66.66667%; 210 | } 211 | 212 | .col-9{ 213 | width:75%; 214 | } 215 | 216 | .col-10{ 217 | width:83.33333%; 218 | } 219 | 220 | .col-11{ 221 | width:91.66667%; 222 | } 223 | 224 | .col-12{ 225 | width:100%; 226 | } 227 | @media (min-width: 40em){ 228 | 229 | .sm-col{ 230 | float:left; 231 | box-sizing:border-box; 232 | } 233 | 234 | .sm-col-right{ 235 | float:right; 236 | box-sizing:border-box; 237 | } 238 | 239 | .sm-col-1{ 240 | width:8.33333%; 241 | } 242 | 243 | .sm-col-2{ 244 | width:16.66667%; 245 | } 246 | 247 | .sm-col-3{ 248 | width:25%; 249 | } 250 | 251 | .sm-col-4{ 252 | width:33.33333%; 253 | } 254 | 255 | .sm-col-5{ 256 | width:41.66667%; 257 | } 258 | 259 | .sm-col-6{ 260 | width:50%; 261 | } 262 | 263 | .sm-col-7{ 264 | width:58.33333%; 265 | } 266 | 267 | .sm-col-8{ 268 | width:66.66667%; 269 | } 270 | 271 | .sm-col-9{ 272 | width:75%; 273 | } 274 | 275 | .sm-col-10{ 276 | width:83.33333%; 277 | } 278 | 279 | .sm-col-11{ 280 | width:91.66667%; 281 | } 282 | 283 | .sm-col-12{ 284 | width:100%; 285 | } 286 | 287 | } 288 | @media (min-width: 52em){ 289 | 290 | .md-col{ 291 | float:left; 292 | box-sizing:border-box; 293 | } 294 | 295 | .md-col-right{ 296 | float:right; 297 | box-sizing:border-box; 298 | } 299 | 300 | .md-col-1{ 301 | width:8.33333%; 302 | } 303 | 304 | .md-col-2{ 305 | width:16.66667%; 306 | } 307 | 308 | .md-col-3{ 309 | width:25%; 310 | } 311 | 312 | .md-col-4{ 313 | width:33.33333%; 314 | } 315 | 316 | .md-col-5{ 317 | width:41.66667%; 318 | } 319 | 320 | .md-col-6{ 321 | width:50%; 322 | } 323 | 324 | .md-col-7{ 325 | width:58.33333%; 326 | } 327 | 328 | .md-col-8{ 329 | width:66.66667%; 330 | } 331 | 332 | .md-col-9{ 333 | width:75%; 334 | } 335 | 336 | .md-col-10{ 337 | width:83.33333%; 338 | } 339 | 340 | .md-col-11{ 341 | width:91.66667%; 342 | } 343 | 344 | .md-col-12{ 345 | width:100%; 346 | } 347 | 348 | } 349 | @media (min-width: 64em){ 350 | 351 | .lg-col{ 352 | float:left; 353 | box-sizing:border-box; 354 | } 355 | 356 | .lg-col-right{ 357 | float:right; 358 | box-sizing:border-box; 359 | } 360 | 361 | .lg-col-1{ 362 | width:8.33333%; 363 | } 364 | 365 | .lg-col-2{ 366 | width:16.66667%; 367 | } 368 | 369 | .lg-col-3{ 370 | width:25%; 371 | } 372 | 373 | .lg-col-4{ 374 | width:33.33333%; 375 | } 376 | 377 | .lg-col-5{ 378 | width:41.66667%; 379 | } 380 | 381 | .lg-col-6{ 382 | width:50%; 383 | } 384 | 385 | .lg-col-7{ 386 | width:58.33333%; 387 | } 388 | 389 | .lg-col-8{ 390 | width:66.66667%; 391 | } 392 | 393 | .lg-col-9{ 394 | width:75%; 395 | } 396 | 397 | .lg-col-10{ 398 | width:83.33333%; 399 | } 400 | 401 | .lg-col-11{ 402 | width:91.66667%; 403 | } 404 | 405 | .lg-col-12{ 406 | width:100%; 407 | } 408 | 409 | } 410 | .flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 411 | 412 | @media (min-width: 40em){ 413 | .sm-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 414 | } 415 | 416 | @media (min-width: 52em){ 417 | .md-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 418 | } 419 | 420 | @media (min-width: 64em){ 421 | .lg-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 422 | } 423 | 424 | .flex-column{ -webkit-box-orient:vertical; -webkit-box-direction:normal; -webkit-flex-direction:column; -ms-flex-direction:column; flex-direction:column } 425 | .flex-wrap{ -webkit-flex-wrap:wrap; -ms-flex-wrap:wrap; flex-wrap:wrap } 426 | 427 | .items-start{ -webkit-box-align:start; -webkit-align-items:flex-start; -ms-flex-align:start; -ms-grid-row-align:flex-start; align-items:flex-start } 428 | .items-end{ -webkit-box-align:end; -webkit-align-items:flex-end; -ms-flex-align:end; -ms-grid-row-align:flex-end; align-items:flex-end } 429 | .items-center{ -webkit-box-align:center; -webkit-align-items:center; -ms-flex-align:center; -ms-grid-row-align:center; align-items:center } 430 | .items-baseline{ -webkit-box-align:baseline; -webkit-align-items:baseline; -ms-flex-align:baseline; -ms-grid-row-align:baseline; align-items:baseline } 431 | .items-stretch{ -webkit-box-align:stretch; -webkit-align-items:stretch; -ms-flex-align:stretch; -ms-grid-row-align:stretch; align-items:stretch } 432 | 433 | .self-start{ -webkit-align-self:flex-start; -ms-flex-item-align:start; align-self:flex-start } 434 | .self-end{ -webkit-align-self:flex-end; -ms-flex-item-align:end; align-self:flex-end } 435 | .self-center{ -webkit-align-self:center; -ms-flex-item-align:center; align-self:center } 436 | .self-baseline{ -webkit-align-self:baseline; -ms-flex-item-align:baseline; align-self:baseline } 437 | .self-stretch{ -webkit-align-self:stretch; -ms-flex-item-align:stretch; align-self:stretch } 438 | 439 | .justify-start{ -webkit-box-pack:start; -webkit-justify-content:flex-start; -ms-flex-pack:start; justify-content:flex-start } 440 | .justify-end{ -webkit-box-pack:end; -webkit-justify-content:flex-end; -ms-flex-pack:end; justify-content:flex-end } 441 | .justify-center{ -webkit-box-pack:center; -webkit-justify-content:center; -ms-flex-pack:center; justify-content:center } 442 | .justify-between{ -webkit-box-pack:justify; -webkit-justify-content:space-between; -ms-flex-pack:justify; justify-content:space-between } 443 | .justify-around{ -webkit-justify-content:space-around; -ms-flex-pack:distribute; justify-content:space-around } 444 | 445 | .content-start{ -webkit-align-content:flex-start; -ms-flex-line-pack:start; align-content:flex-start } 446 | .content-end{ -webkit-align-content:flex-end; -ms-flex-line-pack:end; align-content:flex-end } 447 | .content-center{ -webkit-align-content:center; -ms-flex-line-pack:center; align-content:center } 448 | .content-between{ -webkit-align-content:space-between; -ms-flex-line-pack:justify; align-content:space-between } 449 | .content-around{ -webkit-align-content:space-around; -ms-flex-line-pack:distribute; align-content:space-around } 450 | .content-stretch{ -webkit-align-content:stretch; -ms-flex-line-pack:stretch; align-content:stretch } 451 | .flex-auto{ 452 | -webkit-box-flex:1; 453 | -webkit-flex:1 1 auto; 454 | -ms-flex:1 1 auto; 455 | flex:1 1 auto; 456 | min-width:0; 457 | min-height:0; 458 | } 459 | .flex-none{ -webkit-box-flex:0; -webkit-flex:none; -ms-flex:none; flex:none } 460 | .fs0{ flex-shrink: 0 } 461 | 462 | .order-0{ -webkit-box-ordinal-group:1; -webkit-order:0; -ms-flex-order:0; order:0 } 463 | .order-1{ -webkit-box-ordinal-group:2; -webkit-order:1; -ms-flex-order:1; order:1 } 464 | .order-2{ -webkit-box-ordinal-group:3; -webkit-order:2; -ms-flex-order:2; order:2 } 465 | .order-3{ -webkit-box-ordinal-group:4; -webkit-order:3; -ms-flex-order:3; order:3 } 466 | .order-last{ -webkit-box-ordinal-group:100000; -webkit-order:99999; -ms-flex-order:99999; order:99999 } 467 | 468 | .relative{ position:relative } 469 | .absolute{ position:absolute } 470 | .fixed{ position:fixed } 471 | 472 | .top-0{ top:0 } 473 | .right-0{ right:0 } 474 | .bottom-0{ bottom:0 } 475 | .left-0{ left:0 } 476 | 477 | .z1{ z-index: 1 } 478 | .z2{ z-index: 2 } 479 | .z3{ z-index: 3 } 480 | .z4{ z-index: 4 } 481 | 482 | .border{ 483 | border-style:solid; 484 | border-width: 1px; 485 | } 486 | 487 | .border-top{ 488 | border-top-style:solid; 489 | border-top-width: 1px; 490 | } 491 | 492 | .border-right{ 493 | border-right-style:solid; 494 | border-right-width: 1px; 495 | } 496 | 497 | .border-bottom{ 498 | border-bottom-style:solid; 499 | border-bottom-width: 1px; 500 | } 501 | 502 | .border-left{ 503 | border-left-style:solid; 504 | border-left-width: 1px; 505 | } 506 | 507 | .border-none{ border:0 } 508 | 509 | .rounded{ border-radius: 3px } 510 | .circle{ border-radius:50% } 511 | 512 | .rounded-top{ border-radius: 3px 3px 0 0 } 513 | .rounded-right{ border-radius: 0 3px 3px 0 } 514 | .rounded-bottom{ border-radius: 0 0 3px 3px } 515 | .rounded-left{ border-radius: 3px 0 0 3px } 516 | 517 | .not-rounded{ border-radius:0 } 518 | 519 | .hide{ 520 | position:absolute !important; 521 | height:1px; 522 | width:1px; 523 | overflow:hidden; 524 | clip:rect(1px, 1px, 1px, 1px); 525 | } 526 | 527 | @media (max-width: 40em){ 528 | .xs-hide{ display:none !important } 529 | } 530 | 531 | @media (min-width: 40em) and (max-width: 52em){ 532 | .sm-hide{ display:none !important } 533 | } 534 | 535 | @media (min-width: 52em) and (max-width: 64em){ 536 | .md-hide{ display:none !important } 537 | } 538 | 539 | @media (min-width: 64em){ 540 | .lg-hide{ display:none !important } 541 | } 542 | 543 | .display-none{ display:none !important } 544 | 545 | -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/EOT/SourceCodePro-Bold.eot -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/EOT/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /docs/assets/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/OTF/SourceCodePro-Bold.otf -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/OTF/SourceCodePro-Regular.otf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Code Pro'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'), 7 | url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'), 8 | url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'), 9 | url('OTF/SourceCodePro-Regular.otf') format('opentype'), 10 | url('TTF/SourceCodePro-Regular.ttf') format('truetype'); 11 | } 12 | 13 | @font-face{ 14 | font-family: 'Source Code Pro'; 15 | font-weight: 700; 16 | font-style: normal; 17 | font-stretch: normal; 18 | src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'), 19 | url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'), 20 | url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'), 21 | url('OTF/SourceCodePro-Bold.otf') format('opentype'), 22 | url('TTF/SourceCodePro-Bold.ttf') format('truetype'); 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro/README.md: -------------------------------------------------------------------------------- 1 | # Source Code Pro 2 | 3 | Source Code Pro is a set of OpenType fonts that have been designed to work well 4 | in user interface (UI) environments. In addition to a functional OpenType font, this open 5 | source project provides all of the source files that were used to build this OpenType font 6 | by using the AFDKO makeotf tool. 7 | 8 | ## Font installation instructions 9 | 10 | * [Mac OS X](http://support.apple.com/kb/HT2509) 11 | * [Windows](http://windows.microsoft.com/en-us/windows-vista/install-or-uninstall-fonts) 12 | * [Linux/Unix-based systems](https://github.com/adobe-fonts/source-code-pro/issues/17#issuecomment-8967116) 13 | 14 | ## Getting Involved 15 | 16 | Send suggestions for changes to the Source Code OpenType font project maintainer, [Paul D. Hunt](mailto:opensourcefonts@adobe.com?subject=[GitHub] Source Code Pro), for consideration. 17 | 18 | ## Further information 19 | 20 | For information about the design and background of Source Code, please refer to the [official font readme file](http://www.adobe.com/products/type/font-information/source-code-pro-readme.html). 21 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro/WOFF/OTF/SourceCodePro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/source-code-pro/WOFF/OTF/SourceCodePro-Regular.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro/source-code-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Code Pro'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'); 7 | } 8 | 9 | @font-face{ 10 | font-family: 'Source Code Pro'; 11 | font-weight: 500; 12 | font-style: normal; 13 | font-stretch: normal; 14 | src: url('WOFF/OTF/SourceCodePro-Medium.otf.woff') format('woff'); 15 | } 16 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/README.md: -------------------------------------------------------------------------------- 1 | # Source Sans Pro 2 | 3 | Source Sans Pro is a set of OpenType fonts that have been designed to work well 4 | in user interface (UI) environments. In addition to a functional OpenType font, this open 5 | source project provides all of the source files that were used to build this OpenType font 6 | by using the AFDKO makeotf tool. 7 | 8 | ## Font installation instructions 9 | 10 | * [Mac OS X](http://support.apple.com/kb/HT2509) 11 | * [Windows](http://windows.microsoft.com/en-us/windows-vista/install-or-uninstall-fonts) 12 | * [Linux/Unix-based systems](https://github.com/adobe-fonts/source-code-pro/issues/17#issuecomment-8967116) 13 | 14 | ## Getting Involved 15 | 16 | Send suggestions for changes to the Source Sans OpenType font project maintainer, [Paul D. Hunt](mailto:opensourcefonts@adobe.com?subject=[GitHub] Source Sans Pro), for consideration. 17 | 18 | ## Further information 19 | 20 | For information about the design and background of Source Sans, please refer to the [official font readme file](http://www.adobe.com/products/type/font-information/source-sans-pro-readme.html). 21 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Bold.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Light.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Light.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Regular.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Semibold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/shelf-pack/26743f4c3d3caab2ea1a348c988f22f03a179fd8/docs/assets/fonts/source-sans-pro/WOFF/OTF/SourceSansPro-Semibold.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "source-sans-pro", 3 | "version": "2.020R-ro/1.075R-it", 4 | "main": "source-sans-pro.css", 5 | "homepage": "https://github.com/adobe-fonts/source-sans-pro", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/adobe-fonts/source-sans-pro.git" 9 | }, 10 | "authors": [ 11 | { "name": "Paul D. Hunt" } 12 | ], 13 | "description": "Source Sans Pro font family by Adobe", 14 | "license": "SIL OFL 1.1", 15 | "keywords": ["font", "sourcesans", "sourcesanspro", "source sans", "source sans pro"], 16 | "ignore": ["**/.*"] 17 | } 18 | -------------------------------------------------------------------------------- /docs/assets/fonts/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Sans Pro'; 3 | font-weight: 300; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('WOFF/OTF/SourceSansPro-Light.otf.woff') format('woff'); 7 | } 8 | 9 | @font-face{ 10 | font-family: 'Source Sans Pro'; 11 | font-weight: 400; 12 | font-style: normal; 13 | font-stretch: normal; 14 | src: url('WOFF/OTF/SourceSansPro-Regular.otf.woff') format('woff'); 15 | } 16 | 17 | @font-face{ 18 | font-family: 'Source Sans Pro'; 19 | font-weight: 600; 20 | font-style: normal; 21 | font-stretch: normal; 22 | src: url('WOFF/OTF/SourceSansPro-Semibold.otf.woff') format('woff'); 23 | } 24 | 25 | @font-face{ 26 | font-family: 'Source Sans Pro'; 27 | font-weight: 700; 28 | font-style: normal; 29 | font-stretch: normal; 30 | src: url('WOFF/OTF/SourceSansPro-Bold.otf.woff') format('woff'); 31 | } 32 | -------------------------------------------------------------------------------- /docs/assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #1184CE; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #ed225d; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-phpdoc, 42 | .hljs-dartdoc, 43 | .tex .hljs-formula { 44 | color: #ed225d; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-id, 49 | .scss .hljs-preprocessor { 50 | color: #900; 51 | font-weight: bold; 52 | } 53 | 54 | .hljs-list .hljs-keyword, 55 | .hljs-subst { 56 | font-weight: normal; 57 | } 58 | 59 | .hljs-class .hljs-title, 60 | .hljs-type, 61 | .vhdl .hljs-literal, 62 | .tex .hljs-command { 63 | color: #458; 64 | font-weight: bold; 65 | } 66 | 67 | .hljs-tag, 68 | .hljs-tag .hljs-title, 69 | .hljs-rules .hljs-property, 70 | .django .hljs-tag .hljs-keyword { 71 | color: #000080; 72 | font-weight: normal; 73 | } 74 | 75 | .hljs-attribute, 76 | .hljs-variable, 77 | .lisp .hljs-body { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /docs/assets/site.js: -------------------------------------------------------------------------------- 1 | /* global anchors */ 2 | 3 | // add anchor links to headers 4 | anchors.options.placement = 'left'; 5 | anchors.add('h3'); 6 | 7 | // Filter UI 8 | var tocElements = document.getElementById('toc').getElementsByTagName('li'); 9 | 10 | document.getElementById('filter-input').addEventListener('keyup', function(e) { 11 | var i, element, children; 12 | 13 | // enter key 14 | if (e.keyCode === 13) { 15 | // go to the first displayed item in the toc 16 | for (i = 0; i < tocElements.length; i++) { 17 | element = tocElements[i]; 18 | if (!element.classList.contains('display-none')) { 19 | location.replace(element.firstChild.href); 20 | return e.preventDefault(); 21 | } 22 | } 23 | } 24 | 25 | var match = function() { 26 | return true; 27 | }; 28 | 29 | var value = this.value.toLowerCase(); 30 | 31 | if (!value.match(/^\s*$/)) { 32 | match = function(element) { 33 | var html = element.firstChild.innerHTML; 34 | return html && html.toLowerCase().indexOf(value) !== -1; 35 | }; 36 | } 37 | 38 | for (i = 0; i < tocElements.length; i++) { 39 | element = tocElements[i]; 40 | children = Array.from(element.getElementsByTagName('li')); 41 | if (match(element) || children.some(match)) { 42 | element.classList.remove('display-none'); 43 | } else { 44 | element.classList.add('display-none'); 45 | } 46 | } 47 | }); 48 | 49 | var items = document.getElementsByClassName('toggle-sibling'); 50 | for (var j = 0; j < items.length; j++) { 51 | items[j].addEventListener('click', toggleSibling); 52 | } 53 | 54 | function toggleSibling() { 55 | var stepSibling = this.parentNode.getElementsByClassName('toggle-target')[0]; 56 | var icon = this.getElementsByClassName('icon')[0]; 57 | var klass = 'display-none'; 58 | if (stepSibling.classList.contains(klass)) { 59 | stepSibling.classList.remove(klass); 60 | icon.innerHTML = '▾'; 61 | } else { 62 | stepSibling.classList.add(klass); 63 | icon.innerHTML = '▸'; 64 | } 65 | } 66 | 67 | function showHashTarget(targetId) { 68 | if (targetId) { 69 | var hashTarget = document.getElementById(targetId); 70 | // new target is hidden 71 | if ( 72 | hashTarget && 73 | hashTarget.offsetHeight === 0 && 74 | hashTarget.parentNode.parentNode.classList.contains('display-none') 75 | ) { 76 | hashTarget.parentNode.parentNode.classList.remove('display-none'); 77 | } 78 | } 79 | } 80 | 81 | function scrollIntoView(targetId) { 82 | // Only scroll to element if we don't have a stored scroll position. 83 | if (targetId && !history.state) { 84 | var hashTarget = document.getElementById(targetId); 85 | if (hashTarget) { 86 | hashTarget.scrollIntoView(); 87 | } 88 | } 89 | } 90 | 91 | function gotoCurrentTarget() { 92 | showHashTarget(location.hash.substring(1)); 93 | scrollIntoView(location.hash.substring(1)); 94 | } 95 | 96 | window.addEventListener('hashchange', gotoCurrentTarget); 97 | gotoCurrentTarget(); 98 | 99 | var toclinks = document.getElementsByClassName('pre-open'); 100 | for (var k = 0; k < toclinks.length; k++) { 101 | toclinks[k].addEventListener('mousedown', preOpen, false); 102 | } 103 | 104 | function preOpen() { 105 | showHashTarget(this.hash.substring(1)); 106 | } 107 | 108 | var split_left = document.querySelector('#split-left'); 109 | var split_right = document.querySelector('#split-right'); 110 | var split_parent = split_left.parentNode; 111 | var cw_with_sb = split_left.clientWidth; 112 | split_left.style.overflow = 'hidden'; 113 | var cw_without_sb = split_left.clientWidth; 114 | split_left.style.overflow = ''; 115 | 116 | Split(['#split-left', '#split-right'], { 117 | elementStyle: function(dimension, size, gutterSize) { 118 | return { 119 | 'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)' 120 | }; 121 | }, 122 | gutterStyle: function(dimension, gutterSize) { 123 | return { 124 | 'flex-basis': gutterSize + 'px' 125 | }; 126 | }, 127 | gutterSize: 20, 128 | sizes: [33, 67] 129 | }); 130 | 131 | // Chrome doesn't remember scroll position properly so do it ourselves. 132 | // Also works on Firefox and Edge. 133 | 134 | function updateState() { 135 | history.replaceState( 136 | { 137 | left_top: split_left.scrollTop, 138 | right_top: split_right.scrollTop 139 | }, 140 | document.title 141 | ); 142 | } 143 | 144 | function loadState(ev) { 145 | if (ev) { 146 | // Edge doesn't replace change history.state on popstate. 147 | history.replaceState(ev.state, document.title); 148 | } 149 | if (history.state) { 150 | split_left.scrollTop = history.state.left_top; 151 | split_right.scrollTop = history.state.right_top; 152 | } 153 | } 154 | 155 | window.addEventListener('load', function() { 156 | // Restore after Firefox scrolls to hash. 157 | setTimeout(function() { 158 | loadState(); 159 | // Update with initial scroll position. 160 | updateState(); 161 | // Update scroll positions only after we've loaded because Firefox 162 | // emits an initial scroll event with 0. 163 | split_left.addEventListener('scroll', updateState); 164 | split_right.addEventListener('scroll', updateState); 165 | }, 1); 166 | }); 167 | 168 | window.addEventListener('popstate', loadState); 169 | -------------------------------------------------------------------------------- /docs/assets/split.css: -------------------------------------------------------------------------------- 1 | .gutter { 2 | background-color: #f5f5f5; 3 | background-repeat: no-repeat; 4 | background-position: 50%; 5 | } 6 | 7 | .gutter.gutter-vertical { 8 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); 9 | cursor: ns-resize; 10 | } 11 | 12 | .gutter.gutter-horizontal { 13 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 14 | cursor: ew-resize; 15 | } 16 | -------------------------------------------------------------------------------- /docs/assets/split.js: -------------------------------------------------------------------------------- 1 | /*! Split.js - v1.3.5 */ 2 | // https://github.com/nathancahill/Split.js 3 | // Copyright (c) 2017 Nathan Cahill; Licensed MIT 4 | 5 | (function(global, factory) { 6 | typeof exports === 'object' && typeof module !== 'undefined' 7 | ? (module.exports = factory()) 8 | : typeof define === 'function' && define.amd 9 | ? define(factory) 10 | : (global.Split = factory()); 11 | })(this, function() { 12 | 'use strict'; 13 | // The programming goals of Split.js are to deliver readable, understandable and 14 | // maintainable code, while at the same time manually optimizing for tiny minified file size, 15 | // browser compatibility without additional requirements, graceful fallback (IE8 is supported) 16 | // and very few assumptions about the user's page layout. 17 | var global = window; 18 | var document = global.document; 19 | 20 | // Save a couple long function names that are used frequently. 21 | // This optimization saves around 400 bytes. 22 | var addEventListener = 'addEventListener'; 23 | var removeEventListener = 'removeEventListener'; 24 | var getBoundingClientRect = 'getBoundingClientRect'; 25 | var NOOP = function() { 26 | return false; 27 | }; 28 | 29 | // Figure out if we're in IE8 or not. IE8 will still render correctly, 30 | // but will be static instead of draggable. 31 | var isIE8 = global.attachEvent && !global[addEventListener]; 32 | 33 | // This library only needs two helper functions: 34 | // 35 | // The first determines which prefixes of CSS calc we need. 36 | // We only need to do this once on startup, when this anonymous function is called. 37 | // 38 | // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow: 39 | // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167 40 | var calc = 41 | ['', '-webkit-', '-moz-', '-o-'] 42 | .filter(function(prefix) { 43 | var el = document.createElement('div'); 44 | el.style.cssText = 'width:' + prefix + 'calc(9px)'; 45 | 46 | return !!el.style.length; 47 | }) 48 | .shift() + 'calc'; 49 | 50 | // The second helper function allows elements and string selectors to be used 51 | // interchangeably. In either case an element is returned. This allows us to 52 | // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`. 53 | var elementOrSelector = function(el) { 54 | if (typeof el === 'string' || el instanceof String) { 55 | return document.querySelector(el); 56 | } 57 | 58 | return el; 59 | }; 60 | 61 | // The main function to initialize a split. Split.js thinks about each pair 62 | // of elements as an independant pair. Dragging the gutter between two elements 63 | // only changes the dimensions of elements in that pair. This is key to understanding 64 | // how the following functions operate, since each function is bound to a pair. 65 | // 66 | // A pair object is shaped like this: 67 | // 68 | // { 69 | // a: DOM element, 70 | // b: DOM element, 71 | // aMin: Number, 72 | // bMin: Number, 73 | // dragging: Boolean, 74 | // parent: DOM element, 75 | // isFirst: Boolean, 76 | // isLast: Boolean, 77 | // direction: 'horizontal' | 'vertical' 78 | // } 79 | // 80 | // The basic sequence: 81 | // 82 | // 1. Set defaults to something sane. `options` doesn't have to be passed at all. 83 | // 2. Initialize a bunch of strings based on the direction we're splitting. 84 | // A lot of the behavior in the rest of the library is paramatized down to 85 | // rely on CSS strings and classes. 86 | // 3. Define the dragging helper functions, and a few helpers to go with them. 87 | // 4. Loop through the elements while pairing them off. Every pair gets an 88 | // `pair` object, a gutter, and special isFirst/isLast properties. 89 | // 5. Actually size the pair elements, insert gutters and attach event listeners. 90 | var Split = function(ids, options) { 91 | if (options === void 0) options = {}; 92 | 93 | var dimension; 94 | var clientDimension; 95 | var clientAxis; 96 | var position; 97 | var paddingA; 98 | var paddingB; 99 | var elements; 100 | 101 | // All DOM elements in the split should have a common parent. We can grab 102 | // the first elements parent and hope users read the docs because the 103 | // behavior will be whacky otherwise. 104 | var parent = elementOrSelector(ids[0]).parentNode; 105 | var parentFlexDirection = global.getComputedStyle(parent).flexDirection; 106 | 107 | // Set default options.sizes to equal percentages of the parent element. 108 | var sizes = 109 | options.sizes || 110 | ids.map(function() { 111 | return 100 / ids.length; 112 | }); 113 | 114 | // Standardize minSize to an array if it isn't already. This allows minSize 115 | // to be passed as a number. 116 | var minSize = options.minSize !== undefined ? options.minSize : 100; 117 | var minSizes = Array.isArray(minSize) 118 | ? minSize 119 | : ids.map(function() { 120 | return minSize; 121 | }); 122 | var gutterSize = options.gutterSize !== undefined ? options.gutterSize : 10; 123 | var snapOffset = options.snapOffset !== undefined ? options.snapOffset : 30; 124 | var direction = options.direction || 'horizontal'; 125 | var cursor = 126 | options.cursor || 127 | (direction === 'horizontal' ? 'ew-resize' : 'ns-resize'); 128 | var gutter = 129 | options.gutter || 130 | function(i, gutterDirection) { 131 | var gut = document.createElement('div'); 132 | gut.className = 'gutter gutter-' + gutterDirection; 133 | return gut; 134 | }; 135 | var elementStyle = 136 | options.elementStyle || 137 | function(dim, size, gutSize) { 138 | var style = {}; 139 | 140 | if (typeof size !== 'string' && !(size instanceof String)) { 141 | if (!isIE8) { 142 | style[dim] = calc + '(' + size + '% - ' + gutSize + 'px)'; 143 | } else { 144 | style[dim] = size + '%'; 145 | } 146 | } else { 147 | style[dim] = size; 148 | } 149 | 150 | return style; 151 | }; 152 | var gutterStyle = 153 | options.gutterStyle || 154 | function(dim, gutSize) { 155 | return (obj = {}), (obj[dim] = gutSize + 'px'), obj; 156 | var obj; 157 | }; 158 | 159 | // 2. Initialize a bunch of strings based on the direction we're splitting. 160 | // A lot of the behavior in the rest of the library is paramatized down to 161 | // rely on CSS strings and classes. 162 | if (direction === 'horizontal') { 163 | dimension = 'width'; 164 | clientDimension = 'clientWidth'; 165 | clientAxis = 'clientX'; 166 | position = 'left'; 167 | paddingA = 'paddingLeft'; 168 | paddingB = 'paddingRight'; 169 | } else if (direction === 'vertical') { 170 | dimension = 'height'; 171 | clientDimension = 'clientHeight'; 172 | clientAxis = 'clientY'; 173 | position = 'top'; 174 | paddingA = 'paddingTop'; 175 | paddingB = 'paddingBottom'; 176 | } 177 | 178 | // 3. Define the dragging helper functions, and a few helpers to go with them. 179 | // Each helper is bound to a pair object that contains it's metadata. This 180 | // also makes it easy to store references to listeners that that will be 181 | // added and removed. 182 | // 183 | // Even though there are no other functions contained in them, aliasing 184 | // this to self saves 50 bytes or so since it's used so frequently. 185 | // 186 | // The pair object saves metadata like dragging state, position and 187 | // event listener references. 188 | 189 | function setElementSize(el, size, gutSize) { 190 | // Split.js allows setting sizes via numbers (ideally), or if you must, 191 | // by string, like '300px'. This is less than ideal, because it breaks 192 | // the fluid layout that `calc(% - px)` provides. You're on your own if you do that, 193 | // make sure you calculate the gutter size by hand. 194 | var style = elementStyle(dimension, size, gutSize); 195 | 196 | // eslint-disable-next-line no-param-reassign 197 | Object.keys(style).forEach(function(prop) { 198 | return (el.style[prop] = style[prop]); 199 | }); 200 | } 201 | 202 | function setGutterSize(gutterElement, gutSize) { 203 | var style = gutterStyle(dimension, gutSize); 204 | 205 | // eslint-disable-next-line no-param-reassign 206 | Object.keys(style).forEach(function(prop) { 207 | return (gutterElement.style[prop] = style[prop]); 208 | }); 209 | } 210 | 211 | // Actually adjust the size of elements `a` and `b` to `offset` while dragging. 212 | // calc is used to allow calc(percentage + gutterpx) on the whole split instance, 213 | // which allows the viewport to be resized without additional logic. 214 | // Element a's size is the same as offset. b's size is total size - a size. 215 | // Both sizes are calculated from the initial parent percentage, 216 | // then the gutter size is subtracted. 217 | function adjust(offset) { 218 | var a = elements[this.a]; 219 | var b = elements[this.b]; 220 | var percentage = a.size + b.size; 221 | 222 | a.size = (offset / this.size) * percentage; 223 | b.size = percentage - (offset / this.size) * percentage; 224 | 225 | setElementSize(a.element, a.size, this.aGutterSize); 226 | setElementSize(b.element, b.size, this.bGutterSize); 227 | } 228 | 229 | // drag, where all the magic happens. The logic is really quite simple: 230 | // 231 | // 1. Ignore if the pair is not dragging. 232 | // 2. Get the offset of the event. 233 | // 3. Snap offset to min if within snappable range (within min + snapOffset). 234 | // 4. Actually adjust each element in the pair to offset. 235 | // 236 | // --------------------------------------------------------------------- 237 | // | | <- a.minSize || b.minSize -> | | 238 | // | | | <- this.snapOffset || this.snapOffset -> | | | 239 | // | | | || | | | 240 | // | | | || | | | 241 | // --------------------------------------------------------------------- 242 | // | <- this.start this.size -> | 243 | function drag(e) { 244 | var offset; 245 | 246 | if (!this.dragging) { 247 | return; 248 | } 249 | 250 | // Get the offset of the event from the first side of the 251 | // pair `this.start`. Supports touch events, but not multitouch, so only the first 252 | // finger `touches[0]` is counted. 253 | if ('touches' in e) { 254 | offset = e.touches[0][clientAxis] - this.start; 255 | } else { 256 | offset = e[clientAxis] - this.start; 257 | } 258 | 259 | // If within snapOffset of min or max, set offset to min or max. 260 | // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both. 261 | // Include the appropriate gutter sizes to prevent overflows. 262 | if (offset <= elements[this.a].minSize + snapOffset + this.aGutterSize) { 263 | offset = elements[this.a].minSize + this.aGutterSize; 264 | } else if ( 265 | offset >= 266 | this.size - (elements[this.b].minSize + snapOffset + this.bGutterSize) 267 | ) { 268 | offset = this.size - (elements[this.b].minSize + this.bGutterSize); 269 | } 270 | 271 | // Actually adjust the size. 272 | adjust.call(this, offset); 273 | 274 | // Call the drag callback continously. Don't do anything too intensive 275 | // in this callback. 276 | if (options.onDrag) { 277 | options.onDrag(); 278 | } 279 | } 280 | 281 | // Cache some important sizes when drag starts, so we don't have to do that 282 | // continously: 283 | // 284 | // `size`: The total size of the pair. First + second + first gutter + second gutter. 285 | // `start`: The leading side of the first element. 286 | // 287 | // ------------------------------------------------ 288 | // | aGutterSize -> ||| | 289 | // | ||| | 290 | // | ||| | 291 | // | ||| <- bGutterSize | 292 | // ------------------------------------------------ 293 | // | <- start size -> | 294 | function calculateSizes() { 295 | // Figure out the parent size minus padding. 296 | var a = elements[this.a].element; 297 | var b = elements[this.b].element; 298 | 299 | this.size = 300 | a[getBoundingClientRect]()[dimension] + 301 | b[getBoundingClientRect]()[dimension] + 302 | this.aGutterSize + 303 | this.bGutterSize; 304 | this.start = a[getBoundingClientRect]()[position]; 305 | } 306 | 307 | // stopDragging is very similar to startDragging in reverse. 308 | function stopDragging() { 309 | var self = this; 310 | var a = elements[self.a].element; 311 | var b = elements[self.b].element; 312 | 313 | if (self.dragging && options.onDragEnd) { 314 | options.onDragEnd(); 315 | } 316 | 317 | self.dragging = false; 318 | 319 | // Remove the stored event listeners. This is why we store them. 320 | global[removeEventListener]('mouseup', self.stop); 321 | global[removeEventListener]('touchend', self.stop); 322 | global[removeEventListener]('touchcancel', self.stop); 323 | 324 | self.parent[removeEventListener]('mousemove', self.move); 325 | self.parent[removeEventListener]('touchmove', self.move); 326 | 327 | // Delete them once they are removed. I think this makes a difference 328 | // in memory usage with a lot of splits on one page. But I don't know for sure. 329 | delete self.stop; 330 | delete self.move; 331 | 332 | a[removeEventListener]('selectstart', NOOP); 333 | a[removeEventListener]('dragstart', NOOP); 334 | b[removeEventListener]('selectstart', NOOP); 335 | b[removeEventListener]('dragstart', NOOP); 336 | 337 | a.style.userSelect = ''; 338 | a.style.webkitUserSelect = ''; 339 | a.style.MozUserSelect = ''; 340 | a.style.pointerEvents = ''; 341 | 342 | b.style.userSelect = ''; 343 | b.style.webkitUserSelect = ''; 344 | b.style.MozUserSelect = ''; 345 | b.style.pointerEvents = ''; 346 | 347 | self.gutter.style.cursor = ''; 348 | self.parent.style.cursor = ''; 349 | } 350 | 351 | // startDragging calls `calculateSizes` to store the inital size in the pair object. 352 | // It also adds event listeners for mouse/touch events, 353 | // and prevents selection while dragging so avoid the selecting text. 354 | function startDragging(e) { 355 | // Alias frequently used variables to save space. 200 bytes. 356 | var self = this; 357 | var a = elements[self.a].element; 358 | var b = elements[self.b].element; 359 | 360 | // Call the onDragStart callback. 361 | if (!self.dragging && options.onDragStart) { 362 | options.onDragStart(); 363 | } 364 | 365 | // Don't actually drag the element. We emulate that in the drag function. 366 | e.preventDefault(); 367 | 368 | // Set the dragging property of the pair object. 369 | self.dragging = true; 370 | 371 | // Create two event listeners bound to the same pair object and store 372 | // them in the pair object. 373 | self.move = drag.bind(self); 374 | self.stop = stopDragging.bind(self); 375 | 376 | // All the binding. `window` gets the stop events in case we drag out of the elements. 377 | global[addEventListener]('mouseup', self.stop); 378 | global[addEventListener]('touchend', self.stop); 379 | global[addEventListener]('touchcancel', self.stop); 380 | 381 | self.parent[addEventListener]('mousemove', self.move); 382 | self.parent[addEventListener]('touchmove', self.move); 383 | 384 | // Disable selection. Disable! 385 | a[addEventListener]('selectstart', NOOP); 386 | a[addEventListener]('dragstart', NOOP); 387 | b[addEventListener]('selectstart', NOOP); 388 | b[addEventListener]('dragstart', NOOP); 389 | 390 | a.style.userSelect = 'none'; 391 | a.style.webkitUserSelect = 'none'; 392 | a.style.MozUserSelect = 'none'; 393 | a.style.pointerEvents = 'none'; 394 | 395 | b.style.userSelect = 'none'; 396 | b.style.webkitUserSelect = 'none'; 397 | b.style.MozUserSelect = 'none'; 398 | b.style.pointerEvents = 'none'; 399 | 400 | // Set the cursor, both on the gutter and the parent element. 401 | // Doing only a, b and gutter causes flickering. 402 | self.gutter.style.cursor = cursor; 403 | self.parent.style.cursor = cursor; 404 | 405 | // Cache the initial sizes of the pair. 406 | calculateSizes.call(self); 407 | } 408 | 409 | // 5. Create pair and element objects. Each pair has an index reference to 410 | // elements `a` and `b` of the pair (first and second elements). 411 | // Loop through the elements while pairing them off. Every pair gets a 412 | // `pair` object, a gutter, and isFirst/isLast properties. 413 | // 414 | // Basic logic: 415 | // 416 | // - Starting with the second element `i > 0`, create `pair` objects with 417 | // `a = i - 1` and `b = i` 418 | // - Set gutter sizes based on the _pair_ being first/last. The first and last 419 | // pair have gutterSize / 2, since they only have one half gutter, and not two. 420 | // - Create gutter elements and add event listeners. 421 | // - Set the size of the elements, minus the gutter sizes. 422 | // 423 | // ----------------------------------------------------------------------- 424 | // | i=0 | i=1 | i=2 | i=3 | 425 | // | | isFirst | | isLast | 426 | // | pair 0 pair 1 pair 2 | 427 | // | | | | | 428 | // ----------------------------------------------------------------------- 429 | var pairs = []; 430 | elements = ids.map(function(id, i) { 431 | // Create the element object. 432 | var element = { 433 | element: elementOrSelector(id), 434 | size: sizes[i], 435 | minSize: minSizes[i] 436 | }; 437 | 438 | var pair; 439 | 440 | if (i > 0) { 441 | // Create the pair object with it's metadata. 442 | pair = { 443 | a: i - 1, 444 | b: i, 445 | dragging: false, 446 | isFirst: i === 1, 447 | isLast: i === ids.length - 1, 448 | direction: direction, 449 | parent: parent 450 | }; 451 | 452 | // For first and last pairs, first and last gutter width is half. 453 | pair.aGutterSize = gutterSize; 454 | pair.bGutterSize = gutterSize; 455 | 456 | if (pair.isFirst) { 457 | pair.aGutterSize = gutterSize / 2; 458 | } 459 | 460 | if (pair.isLast) { 461 | pair.bGutterSize = gutterSize / 2; 462 | } 463 | 464 | // if the parent has a reverse flex-direction, switch the pair elements. 465 | if ( 466 | parentFlexDirection === 'row-reverse' || 467 | parentFlexDirection === 'column-reverse' 468 | ) { 469 | var temp = pair.a; 470 | pair.a = pair.b; 471 | pair.b = temp; 472 | } 473 | } 474 | 475 | // Determine the size of the current element. IE8 is supported by 476 | // staticly assigning sizes without draggable gutters. Assigns a string 477 | // to `size`. 478 | // 479 | // IE9 and above 480 | if (!isIE8) { 481 | // Create gutter elements for each pair. 482 | if (i > 0) { 483 | var gutterElement = gutter(i, direction); 484 | setGutterSize(gutterElement, gutterSize); 485 | 486 | gutterElement[addEventListener]( 487 | 'mousedown', 488 | startDragging.bind(pair) 489 | ); 490 | gutterElement[addEventListener]( 491 | 'touchstart', 492 | startDragging.bind(pair) 493 | ); 494 | 495 | parent.insertBefore(gutterElement, element.element); 496 | 497 | pair.gutter = gutterElement; 498 | } 499 | } 500 | 501 | // Set the element size to our determined size. 502 | // Half-size gutters for first and last elements. 503 | if (i === 0 || i === ids.length - 1) { 504 | setElementSize(element.element, element.size, gutterSize / 2); 505 | } else { 506 | setElementSize(element.element, element.size, gutterSize); 507 | } 508 | 509 | var computedSize = element.element[getBoundingClientRect]()[dimension]; 510 | 511 | if (computedSize < element.minSize) { 512 | element.minSize = computedSize; 513 | } 514 | 515 | // After the first iteration, and we have a pair object, append it to the 516 | // list of pairs. 517 | if (i > 0) { 518 | pairs.push(pair); 519 | } 520 | 521 | return element; 522 | }); 523 | 524 | function setSizes(newSizes) { 525 | newSizes.forEach(function(newSize, i) { 526 | if (i > 0) { 527 | var pair = pairs[i - 1]; 528 | var a = elements[pair.a]; 529 | var b = elements[pair.b]; 530 | 531 | a.size = newSizes[i - 1]; 532 | b.size = newSize; 533 | 534 | setElementSize(a.element, a.size, pair.aGutterSize); 535 | setElementSize(b.element, b.size, pair.bGutterSize); 536 | } 537 | }); 538 | } 539 | 540 | function destroy() { 541 | pairs.forEach(function(pair) { 542 | pair.parent.removeChild(pair.gutter); 543 | elements[pair.a].element.style[dimension] = ''; 544 | elements[pair.b].element.style[dimension] = ''; 545 | }); 546 | } 547 | 548 | if (isIE8) { 549 | return { 550 | setSizes: setSizes, 551 | destroy: destroy 552 | }; 553 | } 554 | 555 | return { 556 | setSizes: setSizes, 557 | getSizes: function getSizes() { 558 | return elements.map(function(element) { 559 | return element.size; 560 | }); 561 | }, 562 | collapse: function collapse(i) { 563 | if (i === pairs.length) { 564 | var pair = pairs[i - 1]; 565 | 566 | calculateSizes.call(pair); 567 | 568 | if (!isIE8) { 569 | adjust.call(pair, pair.size - pair.bGutterSize); 570 | } 571 | } else { 572 | var pair$1 = pairs[i]; 573 | 574 | calculateSizes.call(pair$1); 575 | 576 | if (!isIE8) { 577 | adjust.call(pair$1, pair$1.aGutterSize); 578 | } 579 | } 580 | }, 581 | destroy: destroy 582 | }; 583 | }; 584 | 585 | return Split; 586 | }); 587 | -------------------------------------------------------------------------------- /docs/assets/style.css: -------------------------------------------------------------------------------- 1 | .documentation { 2 | font-family: Helvetica, sans-serif; 3 | color: #666; 4 | line-height: 1.5; 5 | background: #f5f5f5; 6 | } 7 | 8 | .black { 9 | color: #666; 10 | } 11 | 12 | .bg-white { 13 | background-color: #fff; 14 | } 15 | 16 | h4 { 17 | margin: 20px 0 10px 0; 18 | } 19 | 20 | .documentation h3 { 21 | color: #000; 22 | } 23 | 24 | .border-bottom { 25 | border-color: #ddd; 26 | } 27 | 28 | a { 29 | color: #1184CE; 30 | text-decoration: none; 31 | } 32 | 33 | .documentation a[href]:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | a:hover { 38 | cursor: pointer; 39 | } 40 | 41 | .py1-ul li { 42 | padding: 5px 0; 43 | } 44 | 45 | .max-height-100 { 46 | max-height: 100%; 47 | } 48 | 49 | .height-viewport-100 { 50 | height: 100vh; 51 | } 52 | 53 | section:target h3 { 54 | font-weight:700; 55 | } 56 | 57 | .documentation td, 58 | .documentation th { 59 | padding: .25rem .25rem; 60 | } 61 | 62 | h1:hover .anchorjs-link, 63 | h2:hover .anchorjs-link, 64 | h3:hover .anchorjs-link, 65 | h4:hover .anchorjs-link { 66 | opacity: 1; 67 | } 68 | 69 | .fix-3 { 70 | width: 25%; 71 | max-width: 244px; 72 | } 73 | 74 | .fix-3 { 75 | width: 25%; 76 | max-width: 244px; 77 | } 78 | 79 | @media (min-width: 52em) { 80 | .fix-margin-3 { 81 | margin-left: 25%; 82 | } 83 | } 84 | 85 | .pre, pre, code, .code { 86 | font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; 87 | font-size: 14px; 88 | } 89 | 90 | .fill-light { 91 | background: #F9F9F9; 92 | } 93 | 94 | .width2 { 95 | width: 1rem; 96 | } 97 | 98 | .input { 99 | font-family: inherit; 100 | display: block; 101 | width: 100%; 102 | height: 2rem; 103 | padding: .5rem; 104 | margin-bottom: 1rem; 105 | border: 1px solid #ccc; 106 | font-size: .875rem; 107 | border-radius: 3px; 108 | box-sizing: border-box; 109 | } 110 | 111 | table { 112 | border-collapse: collapse; 113 | } 114 | 115 | .prose table th, 116 | .prose table td { 117 | text-align: left; 118 | padding:8px; 119 | border:1px solid #ddd; 120 | } 121 | 122 | .prose table th:nth-child(1) { border-right: none; } 123 | .prose table th:nth-child(2) { border-left: none; } 124 | 125 | .prose table { 126 | border:1px solid #ddd; 127 | } 128 | 129 | .prose-big { 130 | font-size: 18px; 131 | line-height: 30px; 132 | } 133 | 134 | .quiet { 135 | opacity: 0.7; 136 | } 137 | 138 | .minishadow { 139 | box-shadow: 2px 2px 10px #f3f3f3; 140 | } 141 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @mapbox/shelf-pack 3.2.0 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |

@mapbox/shelf-pack

18 |
3.2.0
19 | 24 |
25 | 108 |
109 | 112 |
113 |
114 |
115 | 116 | 117 |
118 | 119 | 120 |
121 | 122 |

123 | ShelfPack 124 |

125 | 126 | 127 | 128 | index.mjs 129 | 130 | 131 |
132 | 133 | 134 |

Create a new ShelfPack bin allocator.

135 |

Uses the Shelf Best Height Fit algorithm from 136 | http://clb.demon.fi/files/RectangleBinPack.pdf

137 | 138 | 139 | 140 |
new ShelfPack(w: number, h: number, options: Object?)
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
Parameters
154 |
155 | 156 |
157 |
158 | w (number 159 | = 64) 160 | Initial width of the sprite 161 | 162 |
163 | 164 |
165 | 166 |
167 |
168 | h (number 169 | = 64) 170 | Initial width of the sprite 171 | 172 |
173 | 174 |
175 | 176 |
177 |
178 | options (Object?) 179 | 180 |
181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 200 | 204 | 205 | 206 | 207 | 208 | 209 |
NameDescription
options.autoResize boolean 197 | 198 | (default false) 199 | If 201 | true 202 | , the sprite will automatically grow 203 |
210 | 211 |
212 | 213 |
214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 |
Example
224 | 225 | 226 |
var sprite = new ShelfPack(64, 64, { autoResize: false });
227 | 228 | 229 | 230 | 231 | 232 | 233 |
Instance Members
234 |
235 | 236 |
237 |
238 |
239 | 240 | pack(bins, options?) 241 |
242 |
243 | 417 |
418 | 419 |
420 |
421 |
422 | 423 | packOne(w, h, id?) 424 |
425 |
426 | 539 |
540 | 541 |
542 |
543 |
544 | 545 | shrink() 546 |
547 |
548 | 599 |
600 | 601 |
602 |
603 |
604 | 605 | getBin(id) 606 |
607 |
608 | 684 |
685 | 686 |
687 |
688 |
689 | 690 | ref(bin) 691 |
692 |
693 | 770 |
771 | 772 |
773 |
774 |
775 | 776 | unref(bin) 777 |
778 |
779 | 857 |
858 | 859 |
860 |
861 |
862 | 863 | clear() 864 |
865 |
866 | 920 |
921 | 922 |
923 |
924 |
925 | 926 | resize(w, h) 927 |
928 |
929 | 1017 |
1018 | 1019 |
1020 | 1021 | 1022 | 1023 | 1024 |
1025 | 1026 | 1027 | 1028 |
1029 | 1030 | 1031 |
1032 | 1033 |

1034 | Bin 1035 |

1036 | 1037 | 1038 | 1039 | index.mjs 1040 | 1041 | 1042 |
1043 | 1044 | 1045 |

Create a new Bin object.

1046 | 1047 | 1048 | 1049 |
new Bin(id: (number | string), x: number, y: number, w: number, h: number, maxw: number?, maxh: number?)
1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 |
Parameters
1063 |
1064 | 1065 |
1066 |
1067 | id ((number | string)) 1068 | Unique id of the bin 1069 | 1070 |
1071 | 1072 |
1073 | 1074 |
1075 |
1076 | x (number) 1077 | Left coordinate of the bin 1078 | 1079 |
1080 | 1081 |
1082 | 1083 |
1084 |
1085 | y (number) 1086 | Top coordinate of the bin 1087 | 1088 |
1089 | 1090 |
1091 | 1092 |
1093 |
1094 | w (number) 1095 | Width of the bin 1096 | 1097 |
1098 | 1099 |
1100 | 1101 |
1102 |
1103 | h (number) 1104 | Height of the bin 1105 | 1106 |
1107 | 1108 |
1109 | 1110 |
1111 |
1112 | maxw (number?) 1113 | Max width of the bin (defaults to 1114 | w 1115 | if not provided) 1116 | 1117 |
1118 | 1119 |
1120 | 1121 |
1122 |
1123 | maxh (number?) 1124 | Max height of the bin (defaults to 1125 | h 1126 | if not provided) 1127 | 1128 |
1129 | 1130 |
1131 | 1132 |
1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 |
Example
1143 | 1144 | 1145 |
var bin = new Bin('a', 0, 0, 12, 16);
1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 |
1155 | 1156 | 1157 |
1158 |
1159 | 1160 | 1161 | 1162 | 1163 | 1164 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | export default ShelfPack; 2 | 3 | 4 | /** 5 | * Create a new ShelfPack bin allocator. 6 | * 7 | * Uses the Shelf Best Height Fit algorithm from 8 | * http://clb.demon.fi/files/RectangleBinPack.pdf 9 | * 10 | * @class ShelfPack 11 | * @param {number} [w=64] Initial width of the sprite 12 | * @param {number} [h=64] Initial width of the sprite 13 | * @param {Object} [options] 14 | * @param {boolean} [options.autoResize=false] If `true`, the sprite will automatically grow 15 | * @example 16 | * var sprite = new ShelfPack(64, 64, { autoResize: false }); 17 | */ 18 | function ShelfPack(w, h, options) { 19 | options = options || {}; 20 | this.w = w || 64; 21 | this.h = h || 64; 22 | this.autoResize = !!options.autoResize; 23 | this.shelves = []; 24 | this.freebins = []; 25 | this.stats = {}; 26 | this.bins = {}; 27 | this.maxId = 0; 28 | } 29 | 30 | 31 | /** 32 | * Batch pack multiple bins into the sprite. 33 | * 34 | * @param {Object[]} bins Array of requested bins - each object should have `width`, `height` (or `w`, `h`) properties 35 | * @param {number} bins[].w Requested bin width 36 | * @param {number} bins[].h Requested bin height 37 | * @param {Object} [options] 38 | * @param {boolean} [options.inPlace=false] If `true`, the supplied bin objects will be updated inplace with `x` and `y` properties 39 | * @returns {Bin[]} Array of allocated Bins - each Bin is an object with `id`, `x`, `y`, `w`, `h` properties 40 | * @example 41 | * var bins = [ 42 | * { id: 1, w: 12, h: 12 }, 43 | * { id: 2, w: 12, h: 16 }, 44 | * { id: 3, w: 12, h: 24 } 45 | * ]; 46 | * var results = sprite.pack(bins, { inPlace: false }); 47 | */ 48 | ShelfPack.prototype.pack = function(bins, options) { 49 | bins = [].concat(bins); 50 | options = options || {}; 51 | 52 | var results = [], 53 | w, h, id, allocation; 54 | 55 | for (var i = 0; i < bins.length; i++) { 56 | w = bins[i].w || bins[i].width; 57 | h = bins[i].h || bins[i].height; 58 | id = bins[i].id; 59 | 60 | if (w && h) { 61 | allocation = this.packOne(w, h, id); 62 | if (!allocation) { 63 | continue; 64 | } 65 | if (options.inPlace) { 66 | bins[i].x = allocation.x; 67 | bins[i].y = allocation.y; 68 | bins[i].id = allocation.id; 69 | } 70 | results.push(allocation); 71 | } 72 | } 73 | 74 | this.shrink(); 75 | 76 | return results; 77 | }; 78 | 79 | 80 | /** 81 | * Pack a single bin into the sprite. 82 | * 83 | * Each bin will have a unique identitifer. 84 | * If no identifier is supplied in the `id` parameter, one will be created. 85 | * Note: The supplied `id` is used as an object index, so numeric values are fastest! 86 | * 87 | * Bins are automatically refcounted (i.e. a newly packed Bin will have a refcount of 1). 88 | * When a bin is no longer needed, use the `ShelfPack.unref` function to mark it 89 | * as unused. When a Bin's refcount decrements to 0, the Bin will be marked 90 | * as free and its space may be reused by the packing code. 91 | * 92 | * @param {number} w Width of the bin to allocate 93 | * @param {number} h Height of the bin to allocate 94 | * @param {number|string} [id] Unique identifier for this bin, (if unsupplied, assume it's a new bin and create an id) 95 | * @returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties, or `null` if allocation failed 96 | * @example 97 | * var results = sprite.packOne(12, 16, 'a'); 98 | */ 99 | ShelfPack.prototype.packOne = function(w, h, id) { 100 | var best = { freebin: -1, shelf: -1, waste: Infinity }, 101 | y = 0, 102 | bin, shelf, waste, i; 103 | 104 | // if id was supplied, attempt a lookup.. 105 | if (typeof id === 'string' || typeof id === 'number') { 106 | bin = this.getBin(id); 107 | if (bin) { // we packed this bin already 108 | this.ref(bin); 109 | return bin; 110 | } 111 | if (typeof id === 'number') { 112 | this.maxId = Math.max(id, this.maxId); 113 | } 114 | } else { 115 | id = ++this.maxId; 116 | } 117 | 118 | // First try to reuse a free bin.. 119 | for (i = 0; i < this.freebins.length; i++) { 120 | bin = this.freebins[i]; 121 | 122 | // exactly the right height and width, use it.. 123 | if (h === bin.maxh && w === bin.maxw) { 124 | return this.allocFreebin(i, w, h, id); 125 | } 126 | // not enough height or width, skip it.. 127 | if (h > bin.maxh || w > bin.maxw) { 128 | continue; 129 | } 130 | // extra height or width, minimize wasted area.. 131 | if (h <= bin.maxh && w <= bin.maxw) { 132 | waste = (bin.maxw * bin.maxh) - (w * h); 133 | if (waste < best.waste) { 134 | best.waste = waste; 135 | best.freebin = i; 136 | } 137 | } 138 | } 139 | 140 | // Next find the best shelf.. 141 | for (i = 0; i < this.shelves.length; i++) { 142 | shelf = this.shelves[i]; 143 | y += shelf.h; 144 | 145 | // not enough width on this shelf, skip it.. 146 | if (w > shelf.free) { 147 | continue; 148 | } 149 | // exactly the right height, pack it.. 150 | if (h === shelf.h) { 151 | return this.allocShelf(i, w, h, id); 152 | } 153 | // not enough height, skip it.. 154 | if (h > shelf.h) { 155 | continue; 156 | } 157 | // extra height, minimize wasted area.. 158 | if (h < shelf.h) { 159 | waste = (shelf.h - h) * w; 160 | if (waste < best.waste) { 161 | best.freebin = -1; 162 | best.waste = waste; 163 | best.shelf = i; 164 | } 165 | } 166 | } 167 | 168 | if (best.freebin !== -1) { 169 | return this.allocFreebin(best.freebin, w, h, id); 170 | } 171 | 172 | if (best.shelf !== -1) { 173 | return this.allocShelf(best.shelf, w, h, id); 174 | } 175 | 176 | // No free bins or shelves.. add shelf.. 177 | if (h <= (this.h - y) && w <= this.w) { 178 | shelf = new Shelf(y, this.w, h); 179 | return this.allocShelf(this.shelves.push(shelf) - 1, w, h, id); 180 | } 181 | 182 | // No room for more shelves.. 183 | // If `autoResize` option is set, grow the sprite as follows: 184 | // * double whichever sprite dimension is smaller (`w1` or `h1`) 185 | // * if sprite dimensions are equal, grow width before height 186 | // * accomodate very large bin requests (big `w` or `h`) 187 | if (this.autoResize) { 188 | var h1, h2, w1, w2; 189 | 190 | h1 = h2 = this.h; 191 | w1 = w2 = this.w; 192 | 193 | if (w1 <= h1 || w > w1) { // grow width.. 194 | w2 = Math.max(w, w1) * 2; 195 | } 196 | if (h1 < w1 || h > h1) { // grow height.. 197 | h2 = Math.max(h, h1) * 2; 198 | } 199 | 200 | this.resize(w2, h2); 201 | return this.packOne(w, h, id); // retry 202 | } 203 | 204 | return null; 205 | }; 206 | 207 | 208 | /** 209 | * Called by packOne() to allocate a bin by reusing an existing freebin 210 | * 211 | * @private 212 | * @param {number} index Index into the `this.freebins` array 213 | * @param {number} w Width of the bin to allocate 214 | * @param {number} h Height of the bin to allocate 215 | * @param {number|string} id Unique identifier for this bin 216 | * @returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties 217 | * @example 218 | * var bin = sprite.allocFreebin(0, 12, 16, 'a'); 219 | */ 220 | ShelfPack.prototype.allocFreebin = function (index, w, h, id) { 221 | var bin = this.freebins.splice(index, 1)[0]; 222 | bin.id = id; 223 | bin.w = w; 224 | bin.h = h; 225 | bin.refcount = 0; 226 | this.bins[id] = bin; 227 | this.ref(bin); 228 | return bin; 229 | }; 230 | 231 | 232 | /** 233 | * Called by `packOne() to allocate bin on an existing shelf 234 | * 235 | * @private 236 | * @param {number} index Index into the `this.shelves` array 237 | * @param {number} w Width of the bin to allocate 238 | * @param {number} h Height of the bin to allocate 239 | * @param {number|string} id Unique identifier for this bin 240 | * @returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties 241 | * @example 242 | * var results = sprite.allocShelf(0, 12, 16, 'a'); 243 | */ 244 | ShelfPack.prototype.allocShelf = function(index, w, h, id) { 245 | var shelf = this.shelves[index]; 246 | var bin = shelf.alloc(w, h, id); 247 | this.bins[id] = bin; 248 | this.ref(bin); 249 | return bin; 250 | }; 251 | 252 | 253 | /** 254 | * Shrink the width/height of the sprite to the bare minimum. 255 | * Since shelf-pack doubles first width, then height when running out of shelf space 256 | * this can result in fairly large unused space both in width and height if that happens 257 | * towards the end of bin packing. 258 | */ 259 | ShelfPack.prototype.shrink = function() { 260 | if (this.shelves.length > 0) { 261 | var w2 = 0; 262 | var h2 = 0; 263 | 264 | for (var j = 0; j < this.shelves.length; j++) { 265 | var shelf = this.shelves[j]; 266 | h2 += shelf.h; 267 | w2 = Math.max(shelf.w - shelf.free, w2); 268 | } 269 | 270 | this.resize(w2, h2); 271 | } 272 | }; 273 | 274 | 275 | /** 276 | * Return a packed bin given its id, or undefined if the id is not found 277 | * 278 | * @param {number|string} id Unique identifier for this bin, 279 | * @returns {Bin} The requested bin, or undefined if not yet packed 280 | * @example 281 | * var b = sprite.getBin('a'); 282 | */ 283 | ShelfPack.prototype.getBin = function(id) { 284 | return this.bins[id]; 285 | }; 286 | 287 | 288 | /** 289 | * Increment the ref count of a bin and update statistics. 290 | * 291 | * @param {Bin} bin Bin instance 292 | * @returns {number} New refcount of the bin 293 | * @example 294 | * var bin = sprite.getBin('a'); 295 | * sprite.ref(bin); 296 | */ 297 | ShelfPack.prototype.ref = function(bin) { 298 | if (++bin.refcount === 1) { // a new Bin.. record height in stats historgram.. 299 | var h = bin.h; 300 | this.stats[h] = (this.stats[h] | 0) + 1; 301 | } 302 | 303 | return bin.refcount; 304 | }; 305 | 306 | 307 | /** 308 | * Decrement the ref count of a bin and update statistics. 309 | * The bin will be automatically marked as free space once the refcount reaches 0. 310 | * 311 | * @param {Bin} bin Bin instance 312 | * @returns {number} New refcount of the bin 313 | * @example 314 | * var bin = sprite.getBin('a'); 315 | * sprite.unref(bin); 316 | */ 317 | ShelfPack.prototype.unref = function(bin) { 318 | if (bin.refcount === 0) { 319 | return 0; 320 | } 321 | 322 | if (--bin.refcount === 0) { 323 | this.stats[bin.h]--; 324 | delete this.bins[bin.id]; 325 | this.freebins.push(bin); 326 | } 327 | 328 | return bin.refcount; 329 | }; 330 | 331 | 332 | /** 333 | * Clear the sprite. Resets everything and resets statistics. 334 | * 335 | * @example 336 | * sprite.clear(); 337 | */ 338 | ShelfPack.prototype.clear = function() { 339 | this.shelves = []; 340 | this.freebins = []; 341 | this.stats = {}; 342 | this.bins = {}; 343 | this.maxId = 0; 344 | }; 345 | 346 | 347 | /** 348 | * Resize the sprite. 349 | * 350 | * @param {number} w Requested new sprite width 351 | * @param {number} h Requested new sprite height 352 | * @returns {boolean} `true` if resize succeeded, `false` if failed 353 | * @example 354 | * sprite.resize(256, 256); 355 | */ 356 | ShelfPack.prototype.resize = function(w, h) { 357 | this.w = w; 358 | this.h = h; 359 | for (var i = 0; i < this.shelves.length; i++) { 360 | this.shelves[i].resize(w); 361 | } 362 | return true; 363 | }; 364 | 365 | 366 | /** 367 | * Create a new Shelf. 368 | * 369 | * @private 370 | * @class Shelf 371 | * @param {number} y Top coordinate of the new shelf 372 | * @param {number} w Width of the new shelf 373 | * @param {number} h Height of the new shelf 374 | * @example 375 | * var shelf = new Shelf(64, 512, 24); 376 | */ 377 | function Shelf(y, w, h) { 378 | this.x = 0; 379 | this.y = y; 380 | this.w = this.free = w; 381 | this.h = h; 382 | } 383 | 384 | 385 | /** 386 | * Allocate a single bin into the shelf. 387 | * 388 | * @private 389 | * @param {number} w Width of the bin to allocate 390 | * @param {number} h Height of the bin to allocate 391 | * @param {number|string} id Unique id of the bin to allocate 392 | * @returns {Bin} Bin object with `id`, `x`, `y`, `w`, `h` properties, or `null` if allocation failed 393 | * @example 394 | * shelf.alloc(12, 16, 'a'); 395 | */ 396 | Shelf.prototype.alloc = function(w, h, id) { 397 | if (w > this.free || h > this.h) { 398 | return null; 399 | } 400 | var x = this.x; 401 | this.x += w; 402 | this.free -= w; 403 | return new Bin(id, x, this.y, w, h, w, this.h); 404 | }; 405 | 406 | 407 | /** 408 | * Resize the shelf. 409 | * 410 | * @private 411 | * @param {number} w Requested new width of the shelf 412 | * @returns {boolean} true 413 | * @example 414 | * shelf.resize(512); 415 | */ 416 | Shelf.prototype.resize = function(w) { 417 | this.free += (w - this.w); 418 | this.w = w; 419 | return true; 420 | }; 421 | 422 | 423 | /** 424 | * Create a new Bin object. 425 | * 426 | * @class Bin 427 | * @param {number|string} id Unique id of the bin 428 | * @param {number} x Left coordinate of the bin 429 | * @param {number} y Top coordinate of the bin 430 | * @param {number} w Width of the bin 431 | * @param {number} h Height of the bin 432 | * @param {number} [maxw] Max width of the bin (defaults to `w` if not provided) 433 | * @param {number} [maxh] Max height of the bin (defaults to `h` if not provided) 434 | * @example 435 | * var bin = new Bin('a', 0, 0, 12, 16); 436 | */ 437 | function Bin(id, x, y, w, h, maxw, maxh) { 438 | this.id = id; 439 | this.x = x; 440 | this.y = y; 441 | this.w = w; 442 | this.h = h; 443 | this.maxw = maxw || w; 444 | this.maxh = maxh || h; 445 | this.refcount = 0; 446 | } 447 | 448 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/shelf-pack", 3 | "description": "A 2D rectangular bin packing data structure that uses the Shelf Best Height Fit heuristic", 4 | "version": "3.2.0", 5 | "main": "index.js", 6 | "module": "index.mjs", 7 | "license": "ISC", 8 | "author": "Bryan Housel ", 9 | "repository": "mapbox/shelf-pack", 10 | "keywords": [ 11 | "bin packing", 12 | "sprite" 13 | ], 14 | "devDependencies": { 15 | "benchmark": "^2.1.0", 16 | "bin-pack": "1.0.2", 17 | "coveralls": "^3.0.2", 18 | "documentation": "^9.1.1", 19 | "eslint": "^5.11.0", 20 | "rollup": "^0.68.2", 21 | "tap": "^12.1.0" 22 | }, 23 | "engines": { 24 | "node": ">=6.0.0" 25 | }, 26 | "scripts": { 27 | "bench": "npm run build && node bench/bench.js", 28 | "build": "rollup -f umd -n ShelfPack index.mjs --no-indent --no-strict -o index.js", 29 | "docs": "documentation build index.mjs --github --format html --output docs/", 30 | "lint": "eslint index.mjs test/ bench/", 31 | "test": "npm run build && npm run lint && tap --cov test/*.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tap').test; 4 | var ShelfPack = require('../.'); 5 | 6 | test('ShelfPack', function(t) { 7 | 8 | t.test('batch pack()', function(t) { 9 | t.test('batch pack() allocates same height bins on existing shelf', function(t) { 10 | var sprite = new ShelfPack(64, 64), 11 | bins = [ 12 | { id: 'a', width: 10, height: 10 }, 13 | { id: 'b', width: 10, height: 10 }, 14 | { id: 'c', width: 10, height: 10 } 15 | ], 16 | expectedResults = [ 17 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 18 | { id: 'b', x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 19 | { id: 'c', x: 20, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 20 | ]; 21 | 22 | var results = sprite.pack(bins); 23 | t.deepEqual(results, expectedResults); 24 | t.end(); 25 | }); 26 | 27 | t.test('batch pack() allocates larger bins on new shelf', function(t) { 28 | var sprite = new ShelfPack(64, 64), 29 | bins = [ 30 | { id: 'a', width: 10, height: 10 }, 31 | { id: 'b', width: 10, height: 15 }, 32 | { id: 'c', width: 10, height: 20 } 33 | ], 34 | expectedResults = [ 35 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 36 | { id: 'b', x: 0, y: 10, w: 10, h: 15, maxw: 10, maxh: 15, refcount: 1 }, 37 | { id: 'c', x: 0, y: 25, w: 10, h: 20, maxw: 10, maxh: 20, refcount: 1 } 38 | ]; 39 | 40 | var results = sprite.pack(bins); 41 | t.deepEqual(results, expectedResults); 42 | t.end(); 43 | }); 44 | 45 | t.test('batch pack() allocates shorter bins on existing shelf, minimizing waste', function(t) { 46 | var sprite = new ShelfPack(64, 64), 47 | bins = [ 48 | { id: 'a', width: 10, height: 10 }, 49 | { id: 'b', width: 10, height: 15 }, 50 | { id: 'c', width: 10, height: 20 }, 51 | { id: 'd', width: 10, height: 9 } 52 | ], 53 | expectedResults = [ 54 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 55 | { id: 'b', x: 0, y: 10, w: 10, h: 15, maxw: 10, maxh: 15, refcount: 1 }, 56 | { id: 'c', x: 0, y: 25, w: 10, h: 20, maxw: 10, maxh: 20, refcount: 1 }, 57 | { id: 'd', x: 10, y: 0, w: 10, h: 9, maxw: 10, maxh: 10, refcount: 1 } 58 | ]; 59 | 60 | var results = sprite.pack(bins); 61 | t.deepEqual(results, expectedResults); 62 | t.end(); 63 | }); 64 | 65 | t.test('batch pack() accepts `w`, `h` for `width`, `height`', function(t) { 66 | var sprite = new ShelfPack(64, 64), 67 | bins = [ 68 | { id: 'a', w: 10, h: 10 }, 69 | { id: 'b', w: 10, h: 10 }, 70 | { id: 'c', w: 10, h: 10 } 71 | ], 72 | expectedResults = [ 73 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 74 | { id: 'b', x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 75 | { id: 'c', x: 20, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 76 | ]; 77 | 78 | var results = sprite.pack(bins); 79 | t.deepEqual(results, expectedResults); 80 | t.end(); 81 | }); 82 | 83 | t.test('batch pack() adds `x`, `y` properties to bins with `inPlace` option', function(t) { 84 | var sprite = new ShelfPack(64, 64), 85 | bins = [ 86 | { id: 'a', w: 10, h: 10 }, 87 | { id: 'b', w: 10, h: 10 }, 88 | { id: 'c', w: 10, h: 10 } 89 | ], 90 | expectedResults = [ 91 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 92 | { id: 'b', x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 93 | { id: 'c', x: 20, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 94 | ], 95 | expectedBins = [ 96 | { id: 'a', w: 10, h: 10, x: 0, y: 0 }, 97 | { id: 'b', w: 10, h: 10, x: 10, y: 0 }, 98 | { id: 'c', w: 10, h: 10, x: 20, y: 0 } 99 | ]; 100 | 101 | var results = sprite.pack(bins, { inPlace: true }); 102 | t.deepEqual(results, expectedResults); 103 | t.deepEqual(bins, expectedBins); 104 | t.end(); 105 | }); 106 | 107 | t.test('batch pack() skips bins if not enough room', function(t) { 108 | var sprite = new ShelfPack(20, 20), 109 | bins = [ 110 | { id: 'a', w: 10, h: 10 }, 111 | { id: 'b', w: 10, h: 10 }, 112 | { id: 'c', w: 10, h: 30 }, // should skip 113 | { id: 'd', w: 10, h: 10 } 114 | ], 115 | expectedResults = [ 116 | { id: 'a', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 117 | { id: 'b', x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 118 | { id: 'd', x: 0, y: 10, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 } 119 | ], 120 | expectedBins = [ 121 | { id: 'a', w: 10, h: 10, x: 0, y: 0 }, 122 | { id: 'b', w: 10, h: 10, x: 10, y: 0 }, 123 | { id: 'c', w: 10, h: 30 }, 124 | { id: 'd', w: 10, h: 10, x: 0, y: 10 } 125 | ]; 126 | 127 | var results = sprite.pack(bins, { inPlace: true }); 128 | t.deepEqual(results, expectedResults); 129 | t.deepEqual(bins, expectedBins); 130 | t.end(); 131 | }); 132 | 133 | t.test('batch pack() results in minimal sprite width and height', function(t) { 134 | var bins = [ 135 | { id: 'a', width: 10, height: 10 }, 136 | { id: 'b', width: 5, height: 15 }, 137 | { id: 'c', width: 25, height: 15 }, 138 | { id: 'd', width: 10, height: 20 } 139 | ]; 140 | 141 | var sprite = new ShelfPack(10, 10, { autoResize: true }); 142 | sprite.pack(bins); 143 | 144 | // Since shelf-pack doubles width/height when packing bins one by one 145 | // (first width, then height) this would result in a 50x60 sprite here. 146 | // But this can be shrunk to a 30x45 sprite. 147 | t.same([sprite.w, sprite.h], [30, 45]); 148 | 149 | t.end(); 150 | }); 151 | 152 | t.end(); 153 | }); 154 | 155 | 156 | t.test('packOne()', function(t) { 157 | t.test('packOne() allocates bins with numeric id', function(t) { 158 | var sprite = new ShelfPack(64, 64); 159 | var bin = sprite.packOne(10, 10, 1000); 160 | t.deepEqual(bin, { id: 1000, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'packed bin 1000'); 161 | t.deepEqual(bin, sprite.getBin(1000), 'got bin 1000'); 162 | t.end(); 163 | }); 164 | 165 | t.test('packOne() allocates bins with string id', function(t) { 166 | var sprite = new ShelfPack(64, 64); 167 | var bin = sprite.packOne(10, 10, 'foo'); 168 | t.deepEqual(bin, { id: 'foo', x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'packed bin "foo"'); 169 | t.deepEqual(bin, sprite.getBin('foo'), 'got bin "foo"'); 170 | t.end(); 171 | }); 172 | 173 | t.test('packOne() generates incremental numeric ids, if id not provided', function(t) { 174 | var sprite = new ShelfPack(64, 64); 175 | var bin1 = sprite.packOne(10, 10); 176 | var bin2 = sprite.packOne(10, 10); 177 | t.deepEqual(bin1.id, 1, 'packed bin 1'); 178 | t.deepEqual(bin2.id, 2, 'packed bin 2'); 179 | t.end(); 180 | }); 181 | 182 | t.test('packOne() does not generate an id that collides with an existing id', function(t) { 183 | var sprite = new ShelfPack(64, 64); 184 | var bin1 = sprite.packOne(10, 10, 1); 185 | var bin2 = sprite.packOne(10, 10); 186 | t.deepEqual(bin1.id, 1, 'packed bin 1'); 187 | t.deepEqual(bin2.id, 2, 'packed bin 2'); 188 | t.end(); 189 | }); 190 | 191 | t.test('packOne() does not reallocate a bin with existing id', function(t) { 192 | var sprite = new ShelfPack(64, 64); 193 | var bin1 = sprite.packOne(10, 10, 1000); 194 | t.deepEqual(bin1, { id: 1000, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'bin 1000 refcount 1'); 195 | t.deepEqual(bin1, sprite.getBin(1000), 'got bin 1000'); 196 | 197 | var bin2 = sprite.packOne(10, 10, 1000); 198 | t.deepEqual(bin2, { id: 1000, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 2 }, 'bin 1000 refcount 2'); 199 | t.deepEqual(bin1, bin2, 'bin1 and bin2 are the same bin'); 200 | t.end(); 201 | }); 202 | 203 | t.test('packOne() allocates same height bins on existing shelf', function(t) { 204 | var sprite = new ShelfPack(64, 64); 205 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 206 | t.deepEqual(sprite.packOne(10, 10), { id: 2, x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'second 10x10 bin'); 207 | t.deepEqual(sprite.packOne(10, 10), { id: 3, x: 20, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'third 10x10 bin'); 208 | t.end(); 209 | }); 210 | 211 | t.test('packOne() allocates larger bins on new shelf', function(t) { 212 | var sprite = new ShelfPack(64, 64); 213 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'shelf 1, 10x10 bin'); 214 | t.deepEqual(sprite.packOne(10, 15), { id: 2, x: 0, y: 10, w: 10, h: 15, maxw: 10, maxh: 15, refcount: 1 }, 'shelf 2, 10x15 bin'); 215 | t.deepEqual(sprite.packOne(10, 20), { id: 3, x: 0, y: 25, w: 10, h: 20, maxw: 10, maxh: 20, refcount: 1 }, 'shelf 3, 10x20 bin'); 216 | t.end(); 217 | }); 218 | 219 | t.test('packOne() allocates shorter bins on existing shelf, minimizing waste', function(t) { 220 | var sprite = new ShelfPack(64, 64); 221 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'shelf 1, 10x10 bin'); 222 | t.deepEqual(sprite.packOne(10, 15), { id: 2, x: 0, y: 10, w: 10, h: 15, maxw: 10, maxh: 15, refcount: 1 }, 'shelf 2, 10x15 bin'); 223 | t.deepEqual(sprite.packOne(10, 20), { id: 3, x: 0, y: 25, w: 10, h: 20, maxw: 10, maxh: 20, refcount: 1 }, 'shelf 3, 10x20 bin'); 224 | t.deepEqual(sprite.packOne(10, 9), { id: 4, x: 10, y: 0, w: 10, h: 9, maxw: 10, maxh: 10, refcount: 1 }, 'shelf 1, 10x9 bin'); 225 | t.end(); 226 | }); 227 | 228 | t.test('packOne() returns nothing if not enough room', function(t) { 229 | var sprite = new ShelfPack(10, 10); 230 | t.deepEqual(sprite.packOne(10, 10, 1), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 231 | t.notOk(sprite.packOne(10, 10, 2), 'not enough room'); 232 | t.notOk(sprite.shelves[0].alloc(10, 10, 2), 'not enough room on shelf'); 233 | t.end(); 234 | }); 235 | 236 | t.test('packOne() allocates in free bin if possible', function(t) { 237 | var sprite = new ShelfPack(64, 64); 238 | sprite.packOne(10, 10, 1); 239 | sprite.packOne(10, 10, 2); 240 | sprite.packOne(10, 10, 3); 241 | 242 | var bin2 = sprite.getBin(2); 243 | sprite.unref(bin2); 244 | t.deepEqual(sprite.freebins.length, 1, 'freebins length 1'); 245 | t.deepEqual(sprite.freebins[0], bin2, 'bin2 moved to freebins'); 246 | 247 | t.deepEqual(sprite.packOne(10, 10, 4), { id: 4, x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'reused 10x10 free bin for bin4'); 248 | t.deepEqual(sprite.freebins.length, 0, 'freebins length 0'); 249 | t.end(); 250 | }); 251 | 252 | t.test('packOne() allocates new bin in least wasteful free bin', function(t) { 253 | var sprite = new ShelfPack(64, 64); 254 | sprite.packOne(10, 10, 1); 255 | sprite.packOne(10, 15, 2); 256 | sprite.packOne(10, 20, 3); 257 | 258 | sprite.unref(sprite.getBin(1)); 259 | sprite.unref(sprite.getBin(2)); 260 | sprite.unref(sprite.getBin(3)); 261 | 262 | t.deepEqual(sprite.freebins.length, 3, 'freebins length 3'); 263 | t.deepEqual(sprite.packOne(10, 13, 4), { id: 4, x: 0, y: 10, w: 10, h: 13, maxw: 10, maxh: 15, refcount: 1 }, 'reused free bin for 10x13 bin4'); 264 | t.deepEqual(sprite.freebins.length, 2, 'freebins length 2'); 265 | t.end(); 266 | }); 267 | 268 | t.test('packOne() avoids free bin if all are more wasteful than packing on a shelf', function(t) { 269 | var sprite = new ShelfPack(64, 64); 270 | sprite.packOne(10, 10, 1); 271 | sprite.packOne(10, 15, 2); 272 | 273 | sprite.unref(sprite.getBin(2)); 274 | 275 | t.deepEqual(sprite.freebins.length, 1, 'freebins length 1'); 276 | t.deepEqual(sprite.packOne(10, 10, 3), { id: 3, x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'bin3 packs on shelf instead of 10x15 free bin'); 277 | t.deepEqual(sprite.freebins.length, 1, 'freebins still length 1'); 278 | t.end(); 279 | }); 280 | 281 | t.test('packOne() considers max bin dimensions when reusing a free bin', function(t) { 282 | var sprite = new ShelfPack(64, 64); 283 | sprite.packOne(10, 10, 1); 284 | sprite.packOne(10, 15, 2); 285 | sprite.unref(sprite.getBin(2)); 286 | t.deepEqual(sprite.freebins.length, 1, 'freebins length 1'); 287 | 288 | t.deepEqual(sprite.packOne(10, 13, 3), { id: 3, x: 0, y: 10, w: 10, h: 13, maxw: 10, maxh: 15, refcount: 1 }, 'reused free bin for 10x13 bin3'); 289 | t.deepEqual(sprite.freebins.length, 0, 'freebins length 0'); 290 | sprite.unref(sprite.getBin(3)); 291 | t.deepEqual(sprite.freebins.length, 1, 'freebins length back to 1'); 292 | 293 | t.deepEqual(sprite.packOne(10, 14, 4), { id: 4, x: 0, y: 10, w: 10, h: 14, maxw: 10, maxh: 15, refcount: 1 }, 'reused free bin for 10x14 bin4'); 294 | t.deepEqual(sprite.freebins.length, 0, 'freebins length back to 0'); 295 | 296 | t.end(); 297 | }); 298 | 299 | t.end(); 300 | }); 301 | 302 | 303 | t.test('getBin()', function(t) { 304 | t.test('getBin() returns undefined if Bin not found', function(t) { 305 | var sprite = new ShelfPack(64, 64); 306 | t.deepEqual(sprite.getBin(1), undefined, 'undefined bin'); 307 | t.end(); 308 | }); 309 | 310 | t.test('getBin() gets a Bin by numeric id', function(t) { 311 | var sprite = new ShelfPack(64, 64); 312 | var bin = sprite.packOne(10, 10, 1); 313 | t.deepEqual(sprite.getBin(1), bin, 'Bin 1'); 314 | t.end(); 315 | }); 316 | 317 | t.test('getBin() gets a Bin by string id', function(t) { 318 | var sprite = new ShelfPack(64, 64); 319 | var bin = sprite.packOne(10, 10, 'foo'); 320 | t.deepEqual(sprite.getBin('foo'), bin, 'Bin "foo"'); 321 | t.end(); 322 | }); 323 | 324 | t.end(); 325 | }); 326 | 327 | 328 | t.test('ref()', function(t) { 329 | t.test('ref() increments the Bin refcount and updates stats', function(t) { 330 | var sprite = new ShelfPack(64, 64); 331 | var bin1 = sprite.packOne(10, 10, 1); 332 | t.deepEqual(bin1.refcount, 1, 'Bin1 refcount is 1'); 333 | t.deepEqual(sprite.stats, { 10: 1 }, 'one bin of height 10'); 334 | t.deepEqual(sprite.ref(bin1), 2, 'Bin1 refcount is 2'); 335 | t.deepEqual(sprite.stats, { 10: 1 }, 'still one bin of height 10'); 336 | 337 | var bin2 = sprite.packOne(10, 10, 2); 338 | t.deepEqual(bin2.refcount, 1, 'Bin2 refcount is 1'); 339 | t.deepEqual(sprite.stats, { 10: 2 }, 'two bins of height 10'); 340 | t.deepEqual(sprite.ref(bin2), 2, 'Bin2 refcount is 2'); 341 | t.deepEqual(sprite.stats, { 10: 2 }, 'still two bins of height 10'); 342 | 343 | var bin3 = sprite.packOne(10, 15, 3); 344 | t.deepEqual(bin3.refcount, 1, 'Bin3 refcount is 1'); 345 | t.deepEqual(sprite.stats, { 10: 2, 15: 1}, 'two bins of height 10, one bin of height 15'); 346 | t.deepEqual(sprite.ref(bin3), 2, 'Bin3 refcount is 2'); 347 | t.deepEqual(sprite.stats, { 10: 2, 15: 1}, 'still two bins of height 10, one bin of height 15'); 348 | 349 | t.end(); 350 | }); 351 | 352 | t.end(); 353 | }); 354 | 355 | 356 | t.test('unref()', function(t) { 357 | t.test('unref() decrements the Bin refcount and updates stats', function(t) { 358 | var sprite = new ShelfPack(64, 64); 359 | 360 | // setup.. 361 | var bin1 = sprite.packOne(10, 10, 1); 362 | sprite.ref(bin1); 363 | var bin2 = sprite.packOne(10, 10, 2); 364 | sprite.ref(bin2); 365 | var bin3 = sprite.packOne(10, 15, 3); 366 | sprite.ref(bin3); 367 | 368 | t.deepEqual(sprite.unref(bin3), 1, 'Bin3 refcount is 1'); 369 | t.deepEqual(sprite.stats, { 10: 2, 15: 1}, 'two bins of height 10, one bin of height 15'); 370 | t.deepEqual(sprite.freebins.length, 0, 'freebins empty'); 371 | 372 | t.deepEqual(sprite.unref(bin3), 0, 'Bin3 refcount is 0'); 373 | t.deepEqual(sprite.stats, { 10: 2, 15: 0}, 'two bins of height 10, no bins of height 15'); 374 | t.deepEqual(sprite.freebins.length, 1, 'freebins length 1'); 375 | t.deepEqual(sprite.freebins[0], bin3, 'bin3 moved to freebins'); 376 | t.deepEqual(sprite.getBin(3), undefined, 'getBin for Bin3 returns undefined'); 377 | 378 | t.deepEqual(sprite.unref(bin2), 1, 'Bin2 refcount is 1'); 379 | t.deepEqual(sprite.stats, { 10: 2, 15: 0}, 'still two bins of height 10, no bins of height 15'); 380 | 381 | t.deepEqual(sprite.unref(bin2), 0, 'Bin2 refcount is 0'); 382 | t.deepEqual(sprite.stats, { 10: 1, 15: 0}, 'one bin of height 10, no bins of height 15'); 383 | t.deepEqual(sprite.freebins.length, 2, 'freebins length 2'); 384 | t.deepEqual(sprite.freebins[1], bin2, 'bin2 moved to freebins'); 385 | t.deepEqual(sprite.getBin(2), undefined, 'getBin for Bin2 returns undefined'); 386 | 387 | t.end(); 388 | }); 389 | 390 | t.test('unref() does nothing if refcount is already 0', function(t) { 391 | var sprite = new ShelfPack(64, 64); 392 | var bin = sprite.packOne(10, 10, 1); 393 | t.deepEqual(sprite.unref(bin), 0, 'Bin3 refcount is 0'); 394 | t.deepEqual(sprite.stats, { 10: 0}, 'no bins of height 10'); 395 | 396 | t.deepEqual(sprite.unref(bin), 0, 'Bin3 refcount is still 0'); 397 | t.deepEqual(sprite.stats, { 10: 0}, 'still no bins of height 10'); 398 | 399 | t.end(); 400 | }); 401 | 402 | t.end(); 403 | }); 404 | 405 | 406 | t.test('clear()', function(t) { 407 | t.test('clear() succeeds', function(t) { 408 | var sprite = new ShelfPack(10, 10); 409 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 410 | t.notOk(sprite.packOne(10, 10), 'not enough room'); 411 | 412 | sprite.clear(); 413 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 414 | t.end(); 415 | }); 416 | 417 | t.end(); 418 | }); 419 | 420 | 421 | t.test('shrink()', function(t) { 422 | t.test('shrink() succeeds', function(t) { 423 | var sprite = new ShelfPack(20, 20); 424 | sprite.packOne(10, 5); 425 | t.deepEqual([sprite.w, sprite.h], [20, 20]); 426 | 427 | sprite.shrink(); 428 | t.deepEqual([sprite.w, sprite.h], [10, 5]); 429 | t.end(); 430 | }); 431 | 432 | t.end(); 433 | }); 434 | 435 | 436 | t.test('resize()', function(t) { 437 | t.test('resize larger succeeds', function(t) { 438 | var sprite = new ShelfPack(10, 10); 439 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 440 | t.ok(sprite.resize(20, 10)); 441 | t.deepEqual(sprite.packOne(10, 10), { id: 2, x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'second 10x10 bin'); 442 | t.ok(sprite.resize(20, 20)); 443 | t.deepEqual(sprite.packOne(10, 10), { id: 3, x: 0, y: 10, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'third 10x10 bin'); 444 | t.end(); 445 | }); 446 | 447 | t.test('autoResize grows sprite dimensions by width then height', function(t) { 448 | var sprite = new ShelfPack(10, 10, { autoResize: true }); 449 | t.deepEqual(sprite.packOne(10, 10), { id: 1, x: 0, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'first 10x10 bin'); 450 | t.same([sprite.w, sprite.h], [10, 10]); 451 | t.deepEqual(sprite.packOne(10, 10), { id: 2, x: 10, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'second 10x10 bin'); 452 | t.same([sprite.w, sprite.h], [20, 10]); 453 | t.deepEqual(sprite.packOne(10, 10), { id: 3, x: 0, y: 10, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'third 10x10 bin'); 454 | t.same([sprite.w, sprite.h], [20, 20]); 455 | t.deepEqual(sprite.packOne(10, 10), { id: 4, x: 10, y: 10, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'fourth 10x10 bin'); 456 | t.same([sprite.w, sprite.h], [20, 20]); 457 | t.deepEqual(sprite.packOne(10, 10), { id: 5, x: 20, y: 0, w: 10, h: 10, maxw: 10, maxh: 10, refcount: 1 }, 'fifth 10x10 bin'); 458 | t.same([sprite.w, sprite.h], [40, 20]); 459 | t.end(); 460 | }); 461 | 462 | t.test('autoResize accomodates big bin requests', function(t) { 463 | var sprite = new ShelfPack(10, 10, { autoResize: true }); 464 | t.deepEqual(sprite.packOne(20, 10), { id: 1, x: 0, y: 0, w: 20, h: 10, maxw: 20, maxh: 10, refcount: 1 }, '20x10 bin'); 465 | t.same([sprite.w, sprite.h], [40, 10]); 466 | t.deepEqual(sprite.packOne(10, 40), { id: 2, x: 0, y: 10, w: 10, h: 40, maxw: 10, maxh: 40, refcount: 1 }, '40x10 bin'); 467 | t.same([sprite.w, sprite.h], [40, 80]); 468 | t.end(); 469 | }); 470 | 471 | t.end(); 472 | }); 473 | 474 | t.end(); 475 | }); 476 | --------------------------------------------------------------------------------