From 7ca87a98e7b6eb058e9394b8fc8a03ab71801461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 30 Oct 2019 12:32:36 +0100 Subject: [PATCH 1/3] move sprite generation code to lib folder --- index.js => lib/generate.js | 0 package.json | 8 ++++---- test/{test.js => generate.test.js} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename index.js => lib/generate.js (100%) rename test/{test.js => generate.test.js} (100%) diff --git a/index.js b/lib/generate.js similarity index 100% rename from index.js rename to lib/generate.js diff --git a/package.json b/package.json index dd33fad..93d03b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mapbox/spritezero", "version": "6.1.2", - "main": "index.js", + "main": "./lib/generate.js", "description": "small opinionated sprites", "author": "Tom MacWright", "license": "ISC", @@ -31,10 +31,10 @@ "node": ">=8.0.0" }, "scripts": { - "docs": "documentation build index.js --lint --github --format html --output docs/", - "lint": "eslint index.js test/", + "docs": "documentation build lib --lint --github --format html --output docs/", + "lint": "eslint lib/ test/", "regenerate-fixtures": "node test/regenerate.js", - "test": "npm run lint && tap --cov test/test.js", + "test": "npm run lint && tap --cov test/*.test.js", "postpublish": "greenkeeper-postpublish" } } diff --git a/test/test.js b/test/generate.test.js similarity index 100% rename from test/test.js rename to test/generate.test.js From 541173df55cc6f6975a8d39531e5c9bba9dc6c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 28 Oct 2019 13:00:37 +0100 Subject: [PATCH 2/3] parse and validate metadata for stretchable icons from SVGs --- .eslintrc | 3 +- docs/index.html | 307 ++++++++++- index.js | 9 + lib/extract-svg-metadata.js | 197 +++++++ lib/validate-svg-metadata.js | 78 +++ package-lock.json | 505 ++++++++++++++++-- package.json | 8 +- .../svg-metadata/ae-national-3-affinity.svg | 10 + .../svg-metadata/cn-nths-expy-2-affinity.svg | 16 + .../cn-nths-expy-2-inkscape-plain.svg | 59 ++ .../shield-illustrator-rotated-reversed.svg | 14 + .../shield-illustrator-rotated-translated.svg | 14 + .../shield-illustrator-rotated.svg | 12 + .../svg-metadata/shield-illustrator.svg | 10 + test/fixture/svg-metadata/shield-rotated.svg | 11 + test/metadata.test.js | 263 +++++++++ 16 files changed, 1459 insertions(+), 57 deletions(-) create mode 100644 index.js create mode 100644 lib/extract-svg-metadata.js create mode 100644 lib/validate-svg-metadata.js create mode 100644 test/fixture/svg-metadata/ae-national-3-affinity.svg create mode 100644 test/fixture/svg-metadata/cn-nths-expy-2-affinity.svg create mode 100644 test/fixture/svg-metadata/cn-nths-expy-2-inkscape-plain.svg create mode 100644 test/fixture/svg-metadata/shield-illustrator-rotated-reversed.svg create mode 100644 test/fixture/svg-metadata/shield-illustrator-rotated-translated.svg create mode 100644 test/fixture/svg-metadata/shield-illustrator-rotated.svg create mode 100644 test/fixture/svg-metadata/shield-illustrator.svg create mode 100644 test/fixture/svg-metadata/shield-rotated.svg create mode 100644 test/metadata.test.js diff --git a/.eslintrc b/.eslintrc index 860640b..ccfd80a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "env": { - "node": true + "node": true, + "es6": true }, "extends": [ "eslint:recommended" diff --git a/docs/index.html b/docs/index.html index b5fead3..d268704 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,6 +24,26 @@

@@ -83,6 +113,178 @@

+
+ + +
+

+ extractMetadata +

+ + + lib/extract-svg-metadata.js + + +
+ + +

Parses a SVG document and extracts metadata from its shapes and paths.

+ + +
extractMetadata(img: [Object], callback: Function): Metadata
+ + + + + + + + + + +
Parameters
+
+ +
+
+ img ([Object]) +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
img.svg [(Buffer | string)] + A string of the SVG. +
img.pixelRatio [number] + Ratio of a 72dpi screen pixel to the destination pixel density +
+ +
+ +
+
+ callback (Function) Accepts two arguments, +err + and +metadata + Object + +
+ +
+ +
+ + + + + + +
Returns
+ Metadata: + metadata An object with the extracted information. + + + + + + + + + + + + + + +
+ + + + +
+ + +
+

+ Metadata +

+ + + lib/extract-svg-metadata.js + + +
+ + +

A Metadata objects stores information about how an image can be stretched in a non-linear

+

The keys of the Object are the icon ids. +The values of the Object are the structured data about each icon.

+ + +
Metadata
+ + + + + + + + + + + + + + + + + + +
Example
+ + +
{
+   {
+     "content": [ 2, 5, 18, 11 ],
+     "stretch-x": [ [3, 7], [14, 18] ],
+     "stretch-y": [ [ 5, 11 ] ]
+   }
+}
+ + + + + + + + +
+ + + +
@@ -91,8 +293,8 @@

generateLayout

- - index.js + + lib/generate.js
@@ -227,8 +429,8 @@

generateLayoutUnique

- - index.js + + lib/generate.js
@@ -366,8 +568,8 @@

generateImage

- - index.js + + lib/generate.js @@ -441,8 +643,8 @@

ImgLayout

- - index.js + + lib/generate.js @@ -509,8 +711,8 @@

DataLayout

- - index.js + + lib/generate.js @@ -562,6 +764,91 @@

+ + + + + +
+ + +
+

+ validateMetadata +

+ + + lib/validate-svg-metadata.js + + +
+ + +

Validates metadata that is parsed from an SVG metadata

+ + +
validateMetadata(img: Object, metadata: Metadata): (null | Error)
+ + + + + + + + + + +
Parameters
+
+ +
+
+ img (Object) An image object with +width + and +height +. + +
+ +
+ +
+
+ metadata (Metadata) A metadata object. + +
+ +
+ +
+ + + + + + +
Returns
+ (null | Error): + err An +Error + object if validation fails, +null + otherwise. + + + + + + + + + + + + + +
diff --git a/index.js b/index.js new file mode 100644 index 0000000..04679cb --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +const generate = require('./lib/generate'); + +exports.generateLayout = generate.generateLayout; +exports.generateLayoutUnique = generate.generateLayoutUnique; +exports.generateImage = generate.generateImage; + +exports.extractMetadata = require('./lib/extract-svg-metadata'); + +exports.validateMetadata = require('./lib/validate-svg-metadata'); diff --git a/lib/extract-svg-metadata.js b/lib/extract-svg-metadata.js new file mode 100644 index 0000000..cf8dce8 --- /dev/null +++ b/lib/extract-svg-metadata.js @@ -0,0 +1,197 @@ +const createConfig = require('svgo/lib/svgo/config'); +const convertSVG = require('svgo/lib/svgo/svg2js'); +const createSVG = require('svgo/lib/svgo/js2svg'); +const applyPlugins = require('svgo/lib/svgo/plugins'); +const computePathBounds = require('svg-boundings').path; + +// Transforms `id="mapbox-icon-*"` into `mapbox:icon="*"` so that the `convertPathData` step +// resolves transforms. +const renameMapboxIconMetadata = { + type: 'perItem', + fn: function(item) { + if (item.hasAttr('id')) { + const id = item.attr('id'); + if (id.value.startsWith('mapbox-icon-')) { + item.removeAttr('id'); + item.addAttr({ + name: 'mapbox:icon', + value: id.value.substring(12), + prefix: '', + local: 'mapbox:icon', + }); + } + } + + return item; + } +}; + +/** + * Decrease accuracy of floating-point numbers + * in path data keeping a specified number of decimals. + * Smart rounds values like 2.3491 to 2.35 instead of 2.349. + * Doesn't apply "smartness" if the number precision fits already. + * + * Taken from svgo at https://github.com/svg/svgo/blob/72db8eb/plugins/convertPathData.js#L773 + */ +function strongRound(value, precision) { + if (!precision) { + precision = 3; + } + const error = +Math.pow(0.1, precision).toFixed(precision); + if (+value.toFixed(precision) !== value) { + var rounded = +value.toFixed(precision - 1); + value = +Math.abs(rounded - value).toFixed(precision + 1) >= error ? + +value.toFixed(precision) : + rounded; + } + return value; +} + +// Plugin for use with svgo that extracts metadata from paths. +const extractMapboxIconMetadata = { + type: 'perItem', + fn: function (item, params, info) { + if (item.isElem('path') && item.hasAttr('mapbox:icon') && item.hasAttr('d')) { + const metadata = item.attr('mapbox:icon'); + const bounds = computePathBounds({ d: item.attr('d').value }, true); + + // Make sure that we actually have a valid bounding box. + if (!Number.isFinite(bounds.left) || !Number.isFinite(bounds.right) || + !Number.isFinite(bounds.top) || !Number.isFinite(bounds.bottom)) { + return true; + } + + // Scale bounds by pixel ratio. + bounds.left = strongRound(bounds.left * info.pixelRatio); + bounds.top = strongRound(bounds.top * info.pixelRatio); + bounds.right = strongRound(bounds.right * info.pixelRatio); + bounds.bottom = strongRound(bounds.bottom * info.pixelRatio); + + if (metadata.value.startsWith('stretch-x')) { + (info.metadata['stretch-x'] = info.metadata['stretch-x'] || []).push([bounds.left, bounds.right]); + } else if (metadata.value.startsWith('stretch-y')) { + (info.metadata['stretch-y'] = info.metadata['stretch-y'] || []).push([bounds.top, bounds.bottom]); + } else if (metadata.value === 'stretch') { + (info.metadata['stretch-x'] = info.metadata['stretch-x'] || []).push([bounds.left, bounds.right]); + (info.metadata['stretch-y'] = info.metadata['stretch-y'] || []).push([bounds.top, bounds.bottom]); + } else if (metadata.value === 'content') { + info.metadata.content = [bounds.left, bounds.top, bounds.right, bounds.bottom]; + } + } + + return true; + } +}; + +const prepareConfig = createConfig({ + full: true, + plugins: [ + { 'cleanupIDs': { + // Remove all IDs first so that `convertPathData` resolves transforms. + preservePrefixes: ['mapbox-icon-'] } + }, + { renameMapboxIconMetadata }, + 'convertShapeToPath' + ] +}); + +const collapseConfig = createConfig({ + full: true, + plugins: [ + 'convertTransform', + 'moveGroupAttrsToElems', + 'collapseGroups' + ] +}); + +const extractConfig = createConfig({ + full: true, + plugins: [ + { 'convertPathData': { + // svg-boundings has trouble parsing consecutive numbers without space and without a leading + // zero. Therefore, we force leading zeros. Note that this option confusingly has to be set + // to _false_ to produce leading zeros. + // See https://github.com/kfitfk/svg-boundings/issues/2 + leadingZero: false + } }, + { extractMapboxIconMetadata } + ] +}); + +/** + * Parses a SVG document and extracts metadata from its shapes and paths. + * + * @param {Object} [img] + * @param {Buffer|string} [img.svg] A string of the SVG. + * @param {number} [img.pixelRatio] Ratio of a 72dpi screen pixel to the destination pixel density + * @param {Function} callback Accepts two arguments, `err` and `metadata` Object + * @return {Metadata} metadata An object with the extracted information. + */ +function extractMetadata(img, callback) { + try { + const svg = Buffer.isBuffer(img.svg) ? img.svg.toString('utf-8') : img.svg; + + convertSVG(svg, function (result) { + if (result.error) { + return callback(result.error); + } + + // We'll add the metadata that we extract in the svgo plugin above to this object. + const info = { + metadata: {}, + pixelRatio: Number.isFinite(img.pixelRatio) && img.pixelRatio || 1 + }; + + let previousSVG; + let svg = img.svg; + + try { + // A few initial plugins prepare the data, e.g. by moving IDs to custom attributes. + result = applyPlugins(result, info, prepareConfig.plugins); + + // We'll walk through the plugins that collapse groups until there are no changes to the output. + do { + previousSVG = svg; + result = applyPlugins(result, info, collapseConfig.plugins); + svg = createSVG(result).data; + } while (previousSVG !== svg); + + // Finally, we'll run through the plugins that actually read and parse the metadata. + result = applyPlugins(result, info, extractConfig.plugins); + } catch (err) { + return callback(err); + } + + // Sort stretches ascendingly. + for (const key of ['stretch-x', 'stretch-y']) { + if (info.metadata[key]) { + info.metadata[key].sort((a, b) => a[0] - b[0]); + } + } + + callback(null, info.metadata); + }); + } catch (err) { + return callback(err); + } +} + +module.exports = extractMetadata; + +/** + * A `Metadata` objects stores information about how an image can be stretched in a non-linear + * + * The keys of the Object are the icon ids. + * The values of the Object are the structured data about each icon. + * + * @typedef {Object} Metadata + * @example + * { + * { + * "content": [ 2, 5, 18, 11 ], + * "stretch-x": [ [3, 7], [14, 18] ], + * "stretch-y": [ [ 5, 11 ] ] + * } + * } + */ diff --git a/lib/validate-svg-metadata.js b/lib/validate-svg-metadata.js new file mode 100644 index 0000000..e6c3b29 --- /dev/null +++ b/lib/validate-svg-metadata.js @@ -0,0 +1,78 @@ +/** + * Validates metadata that is parsed from an SVG metadata + * + * @param {Object} img An image object with `width` and `height`. + * @param {Metadata} metadata A metadata object. + * @return {null|Error} err An `Error` object if validation fails, `null` otherwise. + */ +function validateMetadata(img, metadata) { + if (!img || typeof img !== 'object') { + return new Error('image is invalid'); + } + + if (!metadata || typeof metadata !== 'object') { + return new Error('image has invalid metadata'); + } + + if (typeof img.width !== 'number' || img.width <= 0) { + return new Error('image has invalid width'); + } + + if (typeof img.height !== 'number' || img.height <= 0) { + return new Error('image has invalid height'); + } + + if ('content' in metadata) { + const content = metadata.content; + if (!Array.isArray(content) || content.length !== 4 || + typeof content[0] !== 'number' || typeof content[1] !== 'number' || + typeof content[2] !== 'number' || typeof content[3] !== 'number') { + return new Error('image content area must be an array of 4 numbers'); + } + + if (content[0] >= content[2] || content[1] >= content[3]) { + return new Error('image content area must be positive'); + } + + if (content[0] < 0 || content[2] > img.width || + content[1] < 0 || content[3] > img.height) { + return new Error('image content area must be within image bounds'); + } + } + + for (const key of ['stretch-x', 'stretch-y']) { + if (key in metadata) { + const stretches = metadata[key]; + if (!Array.isArray(stretches)) { + return new Error(`image ${key} zones must be an array`); + } + + for (const zone of stretches) { + if (!Array.isArray(zone) || zone.length !== 2 || + typeof zone[0] !== 'number' || typeof zone[1] !== 'number') { + return new Error(`image ${key} zone must consist of two numbers`); + } + + if (zone[0] >= zone[1]) { + return new Error(`image ${key} zone may not be zero-size`); + } + + if (zone[0] < 0 || zone[1] > img.width) { + return new Error(`image ${key} zone must be within image bounds`); + } + } + + for (let i = 1; i < stretches.length; i++) { + // Make sure that the previous stretch's end coordinate is + // smaller than this stretch's begin coordinate. Expects that + // stretch zones are sorted ascendingly by their first coordinate. + if (stretches[i][0] <= stretches[i - 1][1]) { + return new Error(`image ${key} zones may not overlap`); + } + } + } + } + + return null; +} +module.exports = validateMetadata; diff --git a/package-lock.json b/package-lock.json index 43bf056..26bc0ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,12 @@ "resolved": "https://registry.npmjs.org/@mapbox/shelf-pack/-/shelf-pack-3.0.0.tgz", "integrity": "sha1-ROKEyDNu7aHp27sdYZVMcOJuV2Y=" }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -1391,6 +1397,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -1568,9 +1580,9 @@ } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "circular-json": { "version": "0.3.3", @@ -1657,6 +1669,48 @@ "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", "dev": true }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1678,6 +1732,21 @@ "object-visit": "^1.0.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -1928,6 +1997,67 @@ "boom": "2.x.x" } }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.33", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.33.tgz", + "integrity": "sha512-SPt57bh5nQnpsTBsx/IXbO14sRc9xXu5MtMAVuo0BaQQmyf0NupNPPSoMaqiAF5tDFafYsTkfeH4Q/HCKXkg4w==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.5.3" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + } + } + }, "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", @@ -1983,6 +2113,15 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -2172,6 +2311,40 @@ "yargs": "^4.3.1" } }, + "dom-serializer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -2230,6 +2403,12 @@ "once": "^1.4.0" } }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, "env-paths": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", @@ -2245,6 +2424,35 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { "version": "0.10.50", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", @@ -2695,11 +2903,11 @@ "dev": true }, "fs-minipass": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", - "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -3262,6 +3470,12 @@ } } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "function-loop": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.2.tgz", @@ -3584,6 +3798,15 @@ } } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -3593,6 +3816,18 @@ "ansi-regex": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -3749,9 +3984,9 @@ "dev": true }, "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" } @@ -3893,6 +4128,12 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -3902,6 +4143,12 @@ "kind-of": "^3.0.2" } }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-decimal": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", @@ -4055,6 +4302,15 @@ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, "is-relative": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", @@ -4085,6 +4341,15 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4399,13 +4664,13 @@ } }, "mapnik": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mapnik/-/mapnik-4.2.1.tgz", - "integrity": "sha512-xRGIzwPu3n9T1L8V0izZ7694nWMiHxe9PGy4ghL4PDr/koIqCN0JDD9wsrvt52m53ZZC+zdeXFtuS84JqU74lQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/mapnik/-/mapnik-4.3.1.tgz", + "integrity": "sha512-ialXG9fqjR/M+c7Duq6kRI5CAU2L/YWYNYOjW+tatwgZeU9hTqyZkuXhDAL7G3Kk5vE/SqDRaQ9Cxe3N+bOUXA==", "requires": { "mapnik-vector-tile": "2.2.1", - "nan": "~2.10.0", - "node-pre-gyp": "~0.10.0" + "nan": "~2.14.0", + "node-pre-gyp": "~0.13.0" } }, "mapnik-vector-tile": { @@ -4451,6 +4716,12 @@ "unist-util-visit": "^1.1.0" } }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4522,20 +4793,20 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mixin-deep": { @@ -4631,9 +4902,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stream": { "version": "0.0.5", @@ -4642,9 +4913,9 @@ "dev": true }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "nanomatch": { "version": "1.2.13", @@ -4723,9 +4994,9 @@ } }, "node-pre-gyp": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", - "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -4793,9 +5064,9 @@ "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" }, "npm-packlist": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", + "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -4823,6 +5094,15 @@ "set-blocking": "~2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -7493,6 +7773,18 @@ } } }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -7510,6 +7802,16 @@ } } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -7537,6 +7839,18 @@ } } }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7886,6 +8200,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, "qs": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", @@ -9006,6 +9326,12 @@ "tweetnacl": "~0.14.0" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", @@ -9109,6 +9435,26 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -9190,6 +9536,63 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "svg-boundings": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/svg-boundings/-/svg-boundings-2.0.3.tgz", + "integrity": "sha512-gRy3nJF4sM9+qdCqvihvTntghRY7U1vG9JnkcCaMt2Kk5v+htS0+ydnm80HO4/AsYowwYY4JATgTe6qXDIRKeA==" + }, + "svgo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz", + "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.33", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "table": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", @@ -9317,17 +9720,17 @@ } }, "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "text-table": { @@ -9729,6 +10132,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -9819,6 +10228,16 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -10045,9 +10464,9 @@ "dev": true }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yapool": { "version": "1.0.0", diff --git a/package.json b/package.json index 93d03b0..8d6cb64 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mapbox/spritezero", "version": "6.1.2", - "main": "./lib/generate.js", + "main": "./index.js", "description": "small opinionated sprites", "author": "Tom MacWright", "license": "ISC", @@ -14,10 +14,11 @@ "json" ], "dependencies": { - "json-stable-stringify": "^1.0.1", "@mapbox/shelf-pack": "~3.0.0", - "mapnik": "3.x || 4.x", + "json-stable-stringify": "^1.0.1", + "mapnik": "^4.3.1", "queue-async": "^1.2.1", + "svg-boundings": "^2.0.3", "xtend": "^4.0.1" }, "devDependencies": { @@ -25,6 +26,7 @@ "eslint": "^3.9.0", "glob": "^7.1.0", "greenkeeper-postpublish": "^1.0.1", + "svgo": "^1.3.0", "tap": "^10.1.0" }, "engines": { diff --git a/test/fixture/svg-metadata/ae-national-3-affinity.svg b/test/fixture/svg-metadata/ae-national-3-affinity.svg new file mode 100644 index 0000000..5a554a8 --- /dev/null +++ b/test/fixture/svg-metadata/ae-national-3-affinity.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/test/fixture/svg-metadata/cn-nths-expy-2-affinity.svg b/test/fixture/svg-metadata/cn-nths-expy-2-affinity.svg new file mode 100644 index 0000000..7b83e43 --- /dev/null +++ b/test/fixture/svg-metadata/cn-nths-expy-2-affinity.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test/fixture/svg-metadata/cn-nths-expy-2-inkscape-plain.svg b/test/fixture/svg-metadata/cn-nths-expy-2-inkscape-plain.svg new file mode 100644 index 0000000..0acbea5 --- /dev/null +++ b/test/fixture/svg-metadata/cn-nths-expy-2-inkscape-plain.svg @@ -0,0 +1,59 @@ + + + + + + image/svg+xml + + + cn-nths-expy-2 + + + + + + + cn-nths-expy-2 + + + + + + + + + diff --git a/test/fixture/svg-metadata/shield-illustrator-rotated-reversed.svg b/test/fixture/svg-metadata/shield-illustrator-rotated-reversed.svg new file mode 100644 index 0000000..2beb690 --- /dev/null +++ b/test/fixture/svg-metadata/shield-illustrator-rotated-reversed.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixture/svg-metadata/shield-illustrator-rotated-translated.svg b/test/fixture/svg-metadata/shield-illustrator-rotated-translated.svg new file mode 100644 index 0000000..638766b --- /dev/null +++ b/test/fixture/svg-metadata/shield-illustrator-rotated-translated.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/test/fixture/svg-metadata/shield-illustrator-rotated.svg b/test/fixture/svg-metadata/shield-illustrator-rotated.svg new file mode 100644 index 0000000..f74e663 --- /dev/null +++ b/test/fixture/svg-metadata/shield-illustrator-rotated.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/test/fixture/svg-metadata/shield-illustrator.svg b/test/fixture/svg-metadata/shield-illustrator.svg new file mode 100644 index 0000000..8cd4456 --- /dev/null +++ b/test/fixture/svg-metadata/shield-illustrator.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/test/fixture/svg-metadata/shield-rotated.svg b/test/fixture/svg-metadata/shield-rotated.svg new file mode 100644 index 0000000..d6a049d --- /dev/null +++ b/test/fixture/svg-metadata/shield-rotated.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/metadata.test.js b/test/metadata.test.js new file mode 100644 index 0000000..f720235 --- /dev/null +++ b/test/metadata.test.js @@ -0,0 +1,263 @@ +const test = require('tap').test; +const fs = require('fs'); + +const extractMetadata = require('../lib/extract-svg-metadata'); +const validateMetadata = require('../lib/validate-svg-metadata'); + +test('image without metadata', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg/aerialway-24.svg`, 'utf-8') + }, function(err, metadata) { + t.error(err); + t.deepEqual(metadata, {}, 'does not have metadata'); + t.end(); + }); +}); + +test('image with nested metadata', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/cn-nths-expy-2-affinity.svg`, 'utf-8') + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + 'stretch-x': [[4, 16]], + 'stretch-y': [[5, 16]], + content: [2, 5, 18, 18] + }); + t.end(); + }); +}); + +test('image exported by Illustrator', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/shield-illustrator.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + content: [4, 8, 14, 14], + 'stretch-y': [[8, 14]], + 'stretch-x': [[4, 14]] + }); + t.end(); + }); +}); + +test('image exported by Illustrator, rotated', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/shield-illustrator-rotated.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + content: [3.703, 5.806, 12.189, 14.291], + 'stretch-y': [[10.58, 14.257]], + 'stretch-x': [[3.73, 9.528]] + }); + t.end(); + }); +}); + +test('image exported by Illustrator, rotated + translated', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/shield-illustrator-rotated-translated.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + content: [4.242, 7.07, 11.313, 14.142], + 'stretch-y': [[10.606, 14.142]], + 'stretch-x': [[4.242, 9.192]] + }); + t.end(); + }); +}); + +test('image exported by Illustrator, rotated + reversed', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/shield-illustrator-rotated-reversed.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + content: [6, 8, 12, 12], + 'stretch-y': [[8, 12]], + 'stretch-x': [[6, 12]] + }); + t.end(); + }); +}); + +test('image with one stretch rect', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/cn-nths-expy-2-inkscape-plain.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + 'stretch-x': [[3, 17]], + 'stretch-y': [[5, 17]], + }); + t.end(); + }); +}); + +test('image with multiple stretch zones', function(t) { + extractMetadata({ + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/ae-national-3-affinity.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + 'stretch-x': [[5, 7], [20, 22]], + 'content': [3, 7, 23, 18] + }); + t.end(); + }); +}); + +test('image with multiple stretch zones and higher pixelRatio', function(t) { + extractMetadata({ + pixelRatio: 2, + svg: fs.readFileSync(`${__dirname}/fixture/svg-metadata/ae-national-3-affinity.svg`) + }, function(err, metadata) { + t.error(err); + t.ok(metadata); + t.deepEqual(metadata, { + 'stretch-x': [[10, 14], [40, 44]], + 'content': [6, 14, 46, 36] + }); + t.end(); + }); +}); + +test('invalid svg', function(t) { + extractMetadata({ svg: '' }, function(err) { + t.match(err, { message: /Unclosed root tag/ }); + t.end(); + }); +}); + + +test('invalid images', function(t) { + t.test('content area without height', function(t) { + extractMetadata({ svg: '' }, function(err, metadata) { + t.error(err); + t.deepEqual(metadata, {}); + t.end(); + }); + }); + + t.test('invalid mapbox-icon-* ID', function(t) { + extractMetadata({ svg: '' }, function(err, metadata) { + t.error(err); + t.deepEqual(metadata, {}); + t.end(); + }); + }); + + t.test('no path data', function(t) { + extractMetadata({ svg: '' }, function(err, metadata) { + t.error(err); + t.deepEqual(metadata, {}); + t.end(); + }); + }); + + + t.test('invalid path data', function(t) { + extractMetadata({ svg: '' }, function(err, metadata) { + t.error(err); + t.deepEqual(metadata, {}); + t.end(); + }); + }); + + t.end(); +}); + +test('valid metadata', function(t) { + const img = { width: 24, height: 18 }; + + t.error(validateMetadata(img, {})); + + t.error(validateMetadata(img, { content: [ 2, 2, 22, 16 ] })); + t.error(validateMetadata(img, { content: [ 0, 0, 24, 18 ] })); + + t.error(validateMetadata(img, { 'stretch-x': [] })); + t.error(validateMetadata(img, { 'stretch-x': [[10, 14]] })); + t.error(validateMetadata(img, { 'stretch-y': [[8, 10]] })); + + t.end(); +}); + + +test('invalid metadata', function(t) { + t.match(validateMetadata(), { message: 'image is invalid' }, 'rejects missing object'); + t.match(validateMetadata({}), { message: 'image has invalid metadata' }, 'rejects missing object'); + + t.match(validateMetadata({}, {}), { message: 'image has invalid width' }, 'rejects missing width'); + t.match(validateMetadata({ width: 0 }, {}), { message: 'image has invalid width' }, 'rejects zero width'); + t.match(validateMetadata({ width: -3 }, {}), { message: 'image has invalid width' }, 'rejects negative width'); + t.match(validateMetadata({ width: 32 }), {}, { message: 'image has invalid height' }, 'rejects missing height'); + t.match(validateMetadata({ width: 32, height: {} }, {}), { message: 'image has invalid height' }, 'rejects height as object'); + t.match(validateMetadata({ width: 32, height: -32 }, {}), { message: 'image has invalid height' }, 'rejects negative height'); + + const img = { width: 24, height: 18 }; + + t.match(validateMetadata(img, { content: {} }), + { message: 'image content area must be an array of 4 numbers' }, 'rejects invalid content area format'); + t.match(validateMetadata(img, { content: [] }), + { message: 'image content area must be an array of 4 numbers' }, 'rejects invalid content area format'); + t.match(validateMetadata(img, { content: [ 1, 2, 3 ] }), + { message: 'image content area must be an array of 4 numbers' }, 'rejects invalid content area format'); + t.match(validateMetadata(img, { content: [ 1, 2, 3, 4, 5 ] }), + { message: 'image content area must be an array of 4 numbers' }, 'rejects invalid content area format'); + t.match(validateMetadata(img, { content: [ 1, 2, 3, true ] }), + { message: 'image content area must be an array of 4 numbers' }, 'rejects invalid content area format'); + + t.match(validateMetadata(img, { content: [ 4, 4, 4, 4 ] }), + { message: 'image content area must be positive' }, 'rejects invalid content area size'); + t.match(validateMetadata(img, { content: [ 4, 4, 2, 2 ] }), + { message: 'image content area must be positive' }, 'rejects invalid content area size'); + t.match(validateMetadata(img, { content: [ 0, 0, 25, 18 ] }), + { message: 'image content area must be within image bounds' }, 'rejects invalid content area size'); + t.match(validateMetadata(img, { content: [ 0, 0, 24, 19 ] }), + { message: 'image content area must be within image bounds' }, 'rejects invalid content area size'); + t.match(validateMetadata(img, { content: [ -1, 0, 24, 18 ] }), + { message: 'image content area must be within image bounds' }, 'rejects invalid content area size'); + t.match(validateMetadata(img, { content: [ 0, -1, 24, 18 ] }), + { message: 'image content area must be within image bounds' }, 'rejects invalid content area size'); + + t.match(validateMetadata(img, { 'stretch-x': {} }), + { message: 'image stretch-x zones must be an array' }, 'rejects invalid stretch-x format'); + t.match(validateMetadata(img, { 'stretch-x': [ 'yes' ] }), + { message: 'image stretch-x zone must consist of two numbers' }, 'rejects invalid stretch-x format'); + t.match(validateMetadata(img, { 'stretch-x': [ [] ] }), + { message: 'image stretch-x zone must consist of two numbers' }, 'rejects invalid stretch-x format'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 4, 4, 4 ] ] }), + { message: 'image stretch-x zone must consist of two numbers' }, 'rejects invalid stretch-x format'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 4, 5 ], [ 6, null ] ] }), + { message: 'image stretch-x zone must consist of two numbers' }, 'rejects invalid stretch-x format'); + + t.match(validateMetadata(img, { 'stretch-x': [ [ 4, 4 ] ] }), + { message: 'image stretch-x zone may not be zero-size' }, 'rejects invalid stretch-x size'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 8, 4 ] ] }), + { message: 'image stretch-x zone may not be zero-size' }, 'rejects invalid stretch-x size'); + t.match(validateMetadata(img, { 'stretch-x': [ [ -2, 2 ] ] }), + { message: 'image stretch-x zone must be within image bounds' }, 'rejects invalid stretch-x size'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 0, 25 ] ] }), + { message: 'image stretch-x zone must be within image bounds' }, 'rejects invalid stretch-x size'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 0, 24.999 ] ] }), + { message: 'image stretch-x zone must be within image bounds' }, 'rejects invalid stretch-x size'); + + t.match(validateMetadata(img, { 'stretch-x': [ [ 0, 2 ], [ 1, 3 ] ] }), + { message: 'image stretch-x zones may not overlap' }, 'rejects overlapping stretch-x zones'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 0, 24 ], [ 8, 16 ] ] }), + { message: 'image stretch-x zones may not overlap' }, 'rejects overlapping stretch-x zones'); + t.match(validateMetadata(img, { 'stretch-x': [ [ 18, 24 ], [ 0, 6 ] ] }), + { message: 'image stretch-x zones may not overlap' }, 'rejects unsorted stretch-x zones'); + + t.end(); +}); From af06f40c3db231b27462938acb1dca938d27b6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 27 Nov 2019 11:18:35 +0100 Subject: [PATCH 3/3] rename mapbox-icon-* to mapbox-* and stretch-x to stretchX --- docs/index.html | 101 ++++++++++++++++-- lib/extract-svg-metadata.js | 40 +++---- lib/validate-svg-metadata.js | 2 +- .../svg-metadata/ae-national-3-affinity.svg | 6 +- .../svg-metadata/cn-nths-expy-2-affinity.svg | 6 +- .../cn-nths-expy-2-inkscape-plain.svg | 2 +- .../shield-illustrator-rotated-reversed.svg | 6 +- .../shield-illustrator-rotated-translated.svg | 6 +- .../shield-illustrator-rotated.svg | 6 +- .../svg-metadata/shield-illustrator.svg | 6 +- test/fixture/svg-metadata/shield-rotated.svg | 6 +- test/metadata.test.js | 94 ++++++++-------- 12 files changed, 182 insertions(+), 99 deletions(-) diff --git a/docs/index.html b/docs/index.html index d268704..0eb7639 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,6 +24,16 @@