aboutsummaryrefslogtreecommitdiff
path: root/node_modules/svgo/lib
diff options
context:
space:
mode:
authorruki <waruqi@gmail.com>2018-11-08 00:38:48 +0800
committerruki <waruqi@gmail.com>2018-11-07 21:53:09 +0800
commit26105034da4fcce7ac883c899d781f016559310d (patch)
treec459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/svgo/lib
parent2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff)
downloadxmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz
xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip
switch to vuepress
Diffstat (limited to 'node_modules/svgo/lib')
-rw-r--r--node_modules/svgo/lib/svgo.js80
-rw-r--r--node_modules/svgo/lib/svgo/coa.js581
-rw-r--r--node_modules/svgo/lib/svgo/config.js212
-rw-r--r--node_modules/svgo/lib/svgo/js2svg.js352
-rw-r--r--node_modules/svgo/lib/svgo/jsAPI.js298
-rw-r--r--node_modules/svgo/lib/svgo/plugins.js98
-rw-r--r--node_modules/svgo/lib/svgo/svg2js.js187
-rw-r--r--node_modules/svgo/lib/svgo/tools.js147
8 files changed, 1955 insertions, 0 deletions
diff --git a/node_modules/svgo/lib/svgo.js b/node_modules/svgo/lib/svgo.js
new file mode 100644
index 00000000..c593023f
--- /dev/null
+++ b/node_modules/svgo/lib/svgo.js
@@ -0,0 +1,80 @@
+'use strict';
+
+/**
+ * SVGO is a Nodejs-based tool for optimizing SVG vector graphics files.
+ *
+ * @see https://github.com/svg/svgo
+ *
+ * @author Kir Belevich <kir@soulshine.in> (https://github.com/deepsweet)
+ * @copyright © 2012 Kir Belevich
+ * @license MIT https://raw.githubusercontent.com/svg/svgo/master/LICENSE
+ */
+
+var CONFIG = require('./svgo/config.js'),
+ SVG2JS = require('./svgo/svg2js.js'),
+ PLUGINS = require('./svgo/plugins.js'),
+ JSAPI = require('./svgo/jsAPI.js'),
+ JS2SVG = require('./svgo/js2svg.js');
+
+var SVGO = module.exports = function(config) {
+
+ this.config = CONFIG(config);
+
+};
+
+SVGO.prototype.optimize = function(svgstr, callback) {
+ if (this.config.error) return callback(this.config);
+
+ var _this = this,
+ config = this.config,
+ maxPassCount = config.multipass ? 10 : 1,
+ counter = 0,
+ prevResultSize = Number.POSITIVE_INFINITY,
+ optimizeOnceCallback = function(svgjs) {
+
+ if (svgjs.error) {
+ callback(svgjs);
+ return;
+ }
+
+ if (++counter < maxPassCount && svgjs.data.length < prevResultSize) {
+ prevResultSize = svgjs.data.length;
+ _this._optimizeOnce(svgjs.data, optimizeOnceCallback);
+ } else {
+ callback(svgjs);
+ }
+
+ };
+
+ _this._optimizeOnce(svgstr, optimizeOnceCallback);
+
+};
+
+SVGO.prototype._optimizeOnce = function(svgstr, callback) {
+ var config = this.config;
+
+ SVG2JS(svgstr, function(svgjs) {
+
+ if (svgjs.error) {
+ callback(svgjs);
+ return;
+ }
+
+ svgjs = PLUGINS(svgjs, config.plugins);
+
+ callback(JS2SVG(svgjs, config.js2svg));
+
+ });
+};
+
+/**
+ * The factory that creates a content item with the helper methods.
+ *
+ * @param {Object} data which passed to jsAPI constructor
+ * @returns {JSAPI} content item
+ */
+SVGO.prototype.createContentItem = function(data) {
+
+ return new JSAPI(data);
+
+};
diff --git a/node_modules/svgo/lib/svgo/coa.js b/node_modules/svgo/lib/svgo/coa.js
new file mode 100644
index 00000000..6b482231
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/coa.js
@@ -0,0 +1,581 @@
+/* jshint quotmark: false */
+'use strict';
+
+require('colors');
+
+var FS = require('fs'),
+ PATH = require('path'),
+ SVGO = require('../svgo.js'),
+ YAML = require('js-yaml'),
+ PKG = require('../../package.json'),
+ mkdirp = require('mkdirp'),
+ encodeSVGDatauri = require('./tools.js').encodeSVGDatauri,
+ decodeSVGDatauri = require('./tools.js').decodeSVGDatauri,
+ regSVGFile = /\.svg$/;
+
+/**
+ * Command-Option-Argument.
+ *
+ * @see https://github.com/veged/coa
+ */
+module.exports = require('coa').Cmd()
+ .helpful()
+ .name(PKG.name)
+ .title(PKG.description)
+ .opt()
+ .name('version').title('Version')
+ .short('v').long('version')
+ .only()
+ .flag()
+ .act(function() {
+ // output the version to stdout instead of stderr if returned
+ process.stdout.write(PKG.version + '\n');
+ // coa will run `.toString` on the returned value and send it to stderr
+ return '';
+ })
+ .end()
+ .opt()
+ .name('input').title('Input file, "-" for STDIN')
+ .short('i').long('input')
+ .val(function(val) {
+ return val || this.reject("Option '--input' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('string').title('Input SVG data string')
+ .short('s').long('string')
+ .end()
+ .opt()
+ .name('folder').title('Input folder, optimize and rewrite all *.svg files')
+ .short('f').long('folder')
+ .val(function(val) {
+ return val || this.reject("Option '--folder' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('output').title('Output file or folder (by default the same as the input), "-" for STDOUT')
+ .short('o').long('output')
+ .val(function(val) {
+ return val || this.reject("Option '--output' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('precision').title('Set number of digits in the fractional part, overrides plugins params')
+ .short('p').long('precision')
+ .val(function(val) {
+ return !isNaN(val) ? val : this.reject("Option '--precision' must be an integer number");
+ })
+ .end()
+ .opt()
+ .name('config').title('Config file or JSON string to extend or replace default')
+ .long('config')
+ .val(function(val) {
+ return val || this.reject("Option '--config' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('disable').title('Disable plugin by name')
+ .long('disable')
+ .arr()
+ .val(function(val) {
+ return val || this.reject("Option '--disable' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('enable').title('Enable plugin by name')
+ .long('enable')
+ .arr()
+ .val(function(val) {
+ return val || this.reject("Option '--enable' must have a value.");
+ })
+ .end()
+ .opt()
+ .name('datauri').title('Output as Data URI string (base64, URI encoded or unencoded)')
+ .long('datauri')
+ .val(function(val) {
+ return val || this.reject("Option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'");
+ })
+ .end()
+ .opt()
+ .name('multipass').title('Enable multipass')
+ .long('multipass')
+ .flag()
+ .end()
+ .opt()
+ .name('pretty').title('Make SVG pretty printed')
+ .long('pretty')
+ .flag()
+ .end()
+ .opt()
+ .name('indent').title('Indent number when pretty printing SVGs')
+ .long('indent')
+ .val(function(val) {
+ return !isNaN(val) ? val : this.reject("Option '--indent' must be an integer number");
+ })
+ .end()
+ .opt()
+ .name('quiet').title('Only output error messages, not regular status messages')
+ .short('q').long('quiet')
+ .flag()
+ .end()
+ .opt()
+ .name('show-plugins').title('Show available plugins and exit')
+ .long('show-plugins')
+ .flag()
+ .end()
+ .arg()
+ .name('input').title('Alias to --input')
+ .end()
+ .arg()
+ .name('output').title('Alias to --output')
+ .end()
+ .act(function(opts, args) {
+
+ var input = args && args.input ? args.input : opts.input,
+ output = args && args.output ? args.output : opts.output,
+ config = {};
+
+ // --show-plugins
+ if (opts['show-plugins']) {
+
+ showAvailablePlugins();
+ process.exit(0);
+
+ }
+
+ // w/o anything
+ if (
+ (!input || input === '-') &&
+ !opts.string &&
+ !opts.stdin &&
+ !opts.folder &&
+ process.stdin.isTTY
+ ) return this.usage();
+
+
+ // --config
+ if (opts.config) {
+
+ // string
+ if (opts.config.charAt(0) === '{') {
+ try {
+ config = JSON.parse(opts.config);
+ } catch (e) {
+ console.error("Error: Couldn't parse config JSON.");
+ console.error(String(e));
+ return;
+ }
+
+ // external file
+ } else {
+ var configPath = PATH.resolve(opts.config);
+ try {
+ // require() adds some weird output on YML files
+ config = JSON.parse(FS.readFileSync(configPath, 'utf8'));
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ console.error('Error: couldn\'t find config file \'' + opts.config + '\'.');
+ return;
+ } else if (err.code === 'EISDIR') {
+ console.error('Error: directory \'' + opts.config + '\' is not a config file.');
+ return;
+ }
+ config = YAML.safeLoad(FS.readFileSync(configPath, 'utf8'));
+
+ if (!config || Array.isArray(config)) {
+ console.error('Error: invalid config file \'' + opts.config + '\'.');
+ return;
+ }
+ }
+ }
+
+ }
+
+ // --quiet
+ if (opts.quiet) {
+ config.quiet = opts.quiet;
+ }
+
+ // --precision
+ if (opts.precision) {
+ config.floatPrecision = Math.max(0, parseInt(opts.precision));
+ }
+
+ // --disable
+ if (opts.disable) {
+ changePluginsState(opts.disable, false, config);
+ }
+
+ // --enable
+ if (opts.enable) {
+ changePluginsState(opts.enable, true, config);
+ }
+
+ // --multipass
+ if (opts.multipass) {
+
+ config.multipass = true;
+
+ }
+
+ // --pretty
+ if (opts.pretty) {
+
+ config.js2svg = config.js2svg || {};
+ config.js2svg.pretty = true;
+ if (opts.indent) {
+ config.js2svg.indent = parseInt(opts.indent, 10);
+ }
+
+ }
+
+ // --output
+ if (opts.output) {
+ config.output = opts.output;
+ }
+
+ // --folder
+ if (opts.folder) {
+ optimizeFolder(opts.folder, config, output);
+
+ return;
+ }
+
+ // --input
+ if (input) {
+
+ // STDIN
+ if (input === '-') {
+
+ var data = '';
+
+ process.stdin.pause();
+
+ process.stdin
+ .on('data', function(chunk) {
+ data += chunk;
+ })
+ .once('end', function() {
+ optimizeFromString(data, config, opts.datauri, input, output);
+ })
+ .resume();
+
+ // file
+ } else {
+
+ FS.readFile(input, 'utf8', function(err, data) {
+ if (err) {
+ if (err.code === 'EISDIR')
+ optimizeFolder(input, config, output);
+ else if (err.code === 'ENOENT')
+ console.error('Error: no such file or directory \'' + input + '\'.');
+ else
+ console.error(err);
+ return;
+ }
+ optimizeFromString(data, config, opts.datauri, input, output);
+ });
+
+ }
+
+ // --string
+ } else if (opts.string) {
+
+ opts.string = decodeSVGDatauri(opts.string);
+
+ optimizeFromString(opts.string, config, opts.datauri, input, output);
+
+ }
+ });
+
+function optimizeFromString(svgstr, config, datauri, input, output) {
+
+ var startTime = Date.now(config),
+ time,
+ inBytes = Buffer.byteLength(svgstr, 'utf8'),
+ outBytes,
+ svgo = new SVGO(config);
+
+ svgo.optimize(svgstr, function(result) {
+
+ if (result.error) {
+ console.error(result.error);
+ return;
+ }
+
+ if (datauri) {
+ result.data = encodeSVGDatauri(result.data, datauri);
+ }
+
+ outBytes = Buffer.byteLength(result.data, 'utf8');
+ time = Date.now() - startTime;
+
+ // stdout
+ if (output === '-' || (!input || input === '-') && !output) {
+
+ process.stdout.write(result.data + '\n');
+
+ // file
+ } else {
+
+ // overwrite input file if there is no output
+ if (!output && input) {
+ output = input;
+ }
+
+ if (!config.quiet) {
+ console.log('\r');
+ }
+
+ saveFileAndPrintInfo(config, result.data, output, inBytes, outBytes, time);
+
+ }
+
+ });
+
+}
+
+function saveFileAndPrintInfo(config, data, path, inBytes, outBytes, time) {
+
+ FS.writeFile(path, data, 'utf8', function() {
+ if (config.quiet) {
+ return;
+ }
+
+ // print time info
+ printTimeInfo(time);
+
+ // print optimization profit info
+ printProfitInfo(inBytes, outBytes);
+
+ });
+
+}
+
+function printTimeInfo(time) {
+ console.log('Done in ' + time + ' ms!');
+
+}
+
+function printProfitInfo(inBytes, outBytes) {
+
+ var profitPercents = 100 - outBytes * 100 / inBytes;
+
+ console.log(
+ (Math.round((inBytes / 1024) * 1000) / 1000) + ' KiB' +
+ (profitPercents < 0 ? ' + ' : ' - ') +
+ String(Math.abs((Math.round(profitPercents * 10) / 10)) + '%').green + ' = ' +
+ (Math.round((outBytes / 1024) * 1000) / 1000) + ' KiB\n'
+ );
+
+}
+
+/**
+ * Change plugins state by names array.
+ *
+ * @param {Array} names plugins names
+ * @param {Boolean} state active state
+ * @param {Object} config original config
+ * @return {Object} changed config
+ */
+function changePluginsState(names, state, config) {
+
+ // extend config
+ if (config.plugins) {
+
+ names.forEach(function(name) {
+
+ var matched,
+ key;
+
+ config.plugins.forEach(function(plugin) {
+
+ // get plugin name
+ if (typeof plugin === 'object') {
+ key = Object.keys(plugin)[0];
+ } else {
+ key = plugin;
+ }
+
+ // if there is such a plugin name
+ if (key === name) {
+ // don't replace plugin's params with true
+ if (typeof plugin[key] !== 'object' || !state) {
+ plugin[key] = state;
+ }
+
+ // mark it as matched
+ matched = true;
+ }
+
+ });
+
+ // if not matched and current config is not full
+ if (!matched && !config.full) {
+
+ var obj = {};
+
+ obj[name] = state;
+
+ // push new plugin Object
+ config.plugins.push(obj);
+
+ matched = true;
+
+ }
+
+ });
+
+ // just push
+ } else {
+
+ config.plugins = [];
+
+ names.forEach(function(name) {
+ var obj = {};
+
+ obj[name] = state;
+
+ config.plugins.push(obj);
+ });
+
+ }
+
+ return config;
+
+}
+
+function optimizeFolder(dir, config, output) {
+
+ var svgo = new SVGO(config);
+
+ if (!config.quiet) {
+ console.log('Processing directory \'' + dir + '\':\n');
+ }
+
+ // absoluted folder path
+ var path = PATH.resolve(dir);
+
+ // list folder content
+ FS.readdir(path, function(err, files) {
+
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ if (!files.length) {
+ console.log('Directory \'' + dir + '\' is empty.');
+ return;
+ }
+
+ var i = 0,
+ found = false;
+
+ function optimizeFile(file) {
+
+ // absoluted file path
+ var filepath = PATH.resolve(path, file);
+ var outfilepath = output ? PATH.resolve(output, file) : filepath;
+
+ // check if file name matches *.svg
+ if (regSVGFile.test(filepath)) {
+
+ found = true;
+ FS.readFile(filepath, 'utf8', function(err, data) {
+
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ var startTime = Date.now(),
+ time,
+ inBytes = Buffer.byteLength(data, 'utf8'),
+ outBytes;
+
+ svgo.optimize(data, function(result) {
+
+ if (result.error) {
+ console.error(result.error);
+ return;
+ }
+
+ outBytes = Buffer.byteLength(result.data, 'utf8');
+ time = Date.now() - startTime;
+
+ writeOutput();
+
+ function writeOutput() {
+ FS.writeFile(outfilepath, result.data, 'utf8', report);
+ }
+
+ function report(err) {
+
+ if (err) {
+ if (err.code === 'ENOENT') {
+ mkdirp(output, writeOutput);
+ return;
+ } else if (err.code === 'ENOTDIR') {
+ console.error('Error: output \'' + output + '\' is not a directory.');
+ return;
+ }
+ console.error(err);
+ return;
+ }
+
+ if (!config.quiet) {
+ console.log(file + ':');
+
+ // print time info
+ printTimeInfo(time);
+
+ // print optimization profit info
+ printProfitInfo(inBytes, outBytes);
+ }
+
+ //move on to the next file
+ if (++i < files.length) {
+ optimizeFile(files[i]);
+ }
+
+ }
+
+ });
+
+ });
+
+ }
+ //move on to the next file
+ else if (++i < files.length) {
+ optimizeFile(files[i]);
+ } else if (!found) {
+ console.log('No SVG files have been found.');
+ }
+
+
+ }
+
+ optimizeFile(files[i]);
+
+ });
+
+}
+
+
+var showAvailablePlugins = function () {
+
+ var svgo = new SVGO(),
+ // Flatten an array of plugins grouped per type and sort alphabetically
+ list = Array.prototype.concat.apply([], svgo.config.plugins).sort(function(a, b) {
+ return a.name > b.name ? 1 : -1;
+ });
+
+ console.log('Currently available plugins:');
+
+ list.forEach(function (plugin) {
+ console.log(' [ ' + plugin.name.green + ' ] ' + plugin.description);
+ });
+
+ //console.log(JSON.stringify(svgo, null, 4));
+};
diff --git a/node_modules/svgo/lib/svgo/config.js b/node_modules/svgo/lib/svgo/config.js
new file mode 100644
index 00000000..093067d4
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/config.js
@@ -0,0 +1,212 @@
+'use strict';
+
+var FS = require('fs');
+var yaml = require('js-yaml');
+
+var EXTEND = require('whet.extend');
+
+/**
+ * Read and/or extend/replace default config file,
+ * prepare and optimize plugins array.
+ *
+ * @param {Object} [config] input config
+ * @return {Object} output config
+ */
+module.exports = function(config) {
+
+ var defaults;
+ config = typeof config == 'object' && config || {};
+
+ if (config.plugins && !Array.isArray(config.plugins)) {
+ return { error: 'Error: Invalid plugins list. Provided \'plugins\' in config should be an array.' };
+ }
+
+ if (config.full) {
+ defaults = config;
+
+ if (Array.isArray(defaults.plugins)) {
+ defaults.plugins = preparePluginsArray(defaults.plugins);
+ }
+ } else {
+ defaults = EXTEND({}, yaml.safeLoad(FS.readFileSync(__dirname + '/../../.svgo.yml', 'utf8')));
+ defaults.plugins = preparePluginsArray(defaults.plugins);
+ defaults = extendConfig(defaults, config);
+ }
+
+ if ('floatPrecision' in config && Array.isArray(defaults.plugins)) {
+ defaults.plugins.forEach(function(plugin) {
+ if (plugin.params && ('floatPrecision' in plugin.params)) {
+ // Don't touch default plugin params
+ plugin.params = EXTEND({}, plugin.params, { floatPrecision: config.floatPrecision });
+ }
+ });
+ }
+
+ if (Array.isArray(defaults.plugins)) {
+ defaults.plugins = optimizePluginsArray(defaults.plugins);
+ }
+
+ return defaults;
+
+};
+
+/**
+ * Require() all plugins in array.
+ *
+ * @param {Array} plugins input plugins array
+ * @return {Array} input plugins array of arrays
+ */
+function preparePluginsArray(plugins) {
+
+ var plugin,
+ key;
+
+ return plugins.map(function(item) {
+
+ // {}
+ if (typeof item === 'object') {
+
+ key = Object.keys(item)[0];
+
+ // custom
+ if (typeof item[key] === 'object' && item[key].fn && typeof item[key].fn === 'function') {
+ plugin = setupCustomPlugin(key, item[key]);
+
+ } else {
+
+ plugin = EXTEND({}, require('../../plugins/' + key));
+
+ // name: {}
+ if (typeof item[key] === 'object') {
+ plugin.params = EXTEND({}, plugin.params || {}, item[key]);
+ plugin.active = true;
+
+ // name: false
+ } else if (item[key] === false) {
+ plugin.active = false;
+
+ // name: true
+ } else if (item[key] === true) {
+ plugin.active = true;
+ }
+
+ plugin.name = key;
+ }
+
+ // name
+ } else {
+
+ plugin = EXTEND({}, require('../../plugins/' + item));
+ plugin.name = item;
+
+ }
+
+ return plugin;
+
+ });
+
+}
+
+/**
+ * Extend plugins with the custom config object.
+ *
+ * @param {Array} plugins input plugins
+ * @param {Object} config config
+ * @return {Array} output plugins
+ */
+function extendConfig(defaults, config) {
+
+ var key;
+
+ // plugins
+ if (config.plugins) {
+
+ config.plugins.forEach(function(item) {
+
+ // {}
+ if (typeof item === 'object') {
+
+ key = Object.keys(item)[0];
+
+ // custom
+ if (typeof item[key] === 'object' && item[key].fn && typeof item[key].fn === 'function') {
+ defaults.plugins.push(setupCustomPlugin(key, item[key]));
+
+ } else {
+ defaults.plugins.forEach(function(plugin) {
+
+ if (plugin.name === key) {
+ // name: {}
+ if (typeof item[key] === 'object') {
+ plugin.params = EXTEND({}, plugin.params || {}, item[key]);
+ plugin.active = true;
+
+ // name: false
+ } else if (item[key] === false) {
+ plugin.active = false;
+
+ // name: true
+ } else if (item[key] === true) {
+ plugin.active = true;
+ }
+ }
+ });
+ }
+
+ }
+
+ });
+
+ }
+
+ defaults.multipass = config.multipass;
+
+ // svg2js
+ if (config.svg2js) {
+ defaults.svg2js = config.svg2js;
+ }
+
+ // js2svg
+ if (config.js2svg) {
+ defaults.js2svg = config.js2svg;
+ }
+
+ return defaults;
+
+}
+
+/**
+ * Setup and enable a custom plugin
+ *
+ * @param {String} plugin name
+ * @param {Object} custom plugin
+ * @return {Array} enabled plugin
+ */
+function setupCustomPlugin(name, plugin) {
+ plugin.active = true;
+ plugin.params = EXTEND({}, plugin.params || {});
+ plugin.name = name;
+
+ return plugin;
+}
+
+/**
+ * Try to group sequential elements of plugins array.
+ *
+ * @param {Object} plugins input plugins
+ * @return {Array} output plugins
+ */
+function optimizePluginsArray(plugins) {
+
+ var prev;
+
+ return plugins.reduce(function(plugins, item) {
+ if (prev && item.type == prev[0].type) {
+ prev.push(item);
+ } else {
+ plugins.push(prev = [item]);
+ }
+ return plugins;
+ }, []);
+
+}
diff --git a/node_modules/svgo/lib/svgo/js2svg.js b/node_modules/svgo/lib/svgo/js2svg.js
new file mode 100644
index 00000000..5d19cc8d
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/js2svg.js
@@ -0,0 +1,352 @@
+'use strict';
+
+var EOL = require('os').EOL,
+ EXTEND = require('whet.extend'),
+ textElem = require('../../plugins/_collections.js').elemsGroups.textContent.concat('title');
+
+var defaults = {
+ doctypeStart: '<!DOCTYPE',
+ doctypeEnd: '>',
+ procInstStart: '<?',
+ procInstEnd: '?>',
+ tagOpenStart: '<',
+ tagOpenEnd: '>',
+ tagCloseStart: '</',
+ tagCloseEnd: '>',
+ tagShortStart: '<',
+ tagShortEnd: '/>',
+ attrStart: '="',
+ attrEnd: '"',
+ commentStart: '<!--',
+ commentEnd: '-->',
+ cdataStart: '<![CDATA[',
+ cdataEnd: ']]>',
+ textStart: '',
+ textEnd: '',
+ indent: 4,
+ regEntities: /[&'"<>]/g,
+ regValEntities: /[&"<>]/g,
+ encodeEntity: encodeEntity,
+ pretty: false,
+ useShortTags: true
+};
+
+var entities = {
+ '&': '&amp;',
+ '\'': '&apos;',
+ '"': '&quot;',
+ '>': '&gt;',
+ '<': '&lt;',
+ };
+
+/**
+ * Convert SVG-as-JS object to SVG (XML) string.
+ *
+ * @param {Object} data input data
+ * @param {Object} config config
+ *
+ * @return {Object} output data
+ */
+module.exports = function(data, config) {
+
+ return new JS2SVG(config).convert(data);
+
+};
+
+function JS2SVG(config) {
+
+ if (config) {
+ this.config = EXTEND(true, {}, defaults, config);
+ } else {
+ this.config = defaults;
+ }
+
+ var indent = this.config.indent;
+ if (typeof indent == 'number' && !isNaN(indent)) {
+ this.config.indent = '';
+ for (var i = indent; i-- > 0;) this.config.indent += ' ';
+ } else if (typeof indent != 'string') {
+ this.config.indent = ' ';
+ }
+
+ if (this.config.pretty) {
+ this.config.doctypeEnd += EOL;
+ this.config.procInstEnd += EOL;
+ this.config.commentEnd += EOL;
+ this.config.cdataEnd += EOL;
+ this.config.tagShortEnd += EOL;
+ this.config.tagOpenEnd += EOL;
+ this.config.tagCloseEnd += EOL;
+ this.config.textEnd += EOL;
+ }
+
+ this.indentLevel = 0;
+ this.textContext = null;
+
+}
+
+function encodeEntity(char) {
+ return entities[char];
+}
+
+/**
+ * Start conversion.
+ *
+ * @param {Object} data input data
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.convert = function(data) {
+
+ var svg = '';
+
+ if (data.content) {
+
+ this.indentLevel++;
+
+ data.content.forEach(function(item) {
+
+ if (item.elem) {
+ svg += this.createElem(item);
+ } else if (item.text) {
+ svg += this.createText(item.text);
+ } else if (item.doctype) {
+ svg += this.createDoctype(item.doctype);
+ } else if (item.processinginstruction) {
+ svg += this.createProcInst(item.processinginstruction);
+ } else if (item.comment) {
+ svg += this.createComment(item.comment);
+ } else if (item.cdata) {
+ svg += this.createCDATA(item.cdata);
+ }
+
+ }, this);
+
+ }
+
+ this.indentLevel--;
+
+ return {
+ data: svg,
+ info: {
+ width: this.width,
+ height: this.height
+ }
+ };
+
+};
+
+/**
+ * Create indent string in accordance with the current node level.
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createIndent = function() {
+
+ var indent = '';
+
+ if (this.config.pretty && !this.textContext) {
+ for (var i = 1; i < this.indentLevel; i++) {
+ indent += this.config.indent;
+ }
+ }
+
+ return indent;
+
+};
+
+/**
+ * Create doctype tag.
+ *
+ * @param {String} doctype doctype body string
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createDoctype = function(doctype) {
+
+ return this.config.doctypeStart +
+ doctype +
+ this.config.doctypeEnd;
+
+};
+
+/**
+ * Create XML Processing Instruction tag.
+ *
+ * @param {Object} instruction instruction object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createProcInst = function(instruction) {
+
+ return this.config.procInstStart +
+ instruction.name +
+ ' ' +
+ instruction.body +
+ this.config.procInstEnd;
+
+};
+
+/**
+ * Create comment tag.
+ *
+ * @param {String} comment comment body
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createComment = function(comment) {
+
+ return this.config.commentStart +
+ comment +
+ this.config.commentEnd;
+
+};
+
+/**
+ * Create CDATA section.
+ *
+ * @param {String} cdata CDATA body
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createCDATA = function(cdata) {
+
+ return this.createIndent() +
+ this.config.cdataStart +
+ cdata +
+ this.config.cdataEnd;
+
+};
+
+/**
+ * Create element tag.
+ *
+ * @param {Object} data element object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createElem = function(data) {
+
+ // beautiful injection for obtaining SVG information :)
+ if (
+ data.isElem('svg') &&
+ data.hasAttr('width') &&
+ data.hasAttr('height')
+ ) {
+ this.width = data.attr('width').value;
+ this.height = data.attr('height').value;
+ }
+
+ // empty element and short tag
+ if (data.isEmpty()) {
+ if (this.config.useShortTags) {
+ return this.createIndent() +
+ this.config.tagShortStart +
+ data.elem +
+ this.createAttrs(data) +
+ this.config.tagShortEnd;
+ } else {
+ return this.createIndent() +
+ this.config.tagShortStart +
+ data.elem +
+ this.createAttrs(data) +
+ this.config.tagOpenEnd +
+ this.config.tagCloseStart +
+ data.elem +
+ this.config.tagCloseEnd;
+ }
+ // non-empty element
+ } else {
+ var tagOpenStart = this.config.tagOpenStart,
+ tagOpenEnd = this.config.tagOpenEnd,
+ tagCloseStart = this.config.tagCloseStart,
+ tagCloseEnd = this.config.tagCloseEnd,
+ openIndent = this.createIndent(),
+ textIndent = '',
+ processedData = '',
+ dataEnd = '';
+
+ if (this.textContext) {
+ tagOpenStart = defaults.tagOpenStart;
+ tagOpenEnd = defaults.tagOpenEnd;
+ tagCloseStart = defaults.tagCloseStart;
+ tagCloseEnd = defaults.tagCloseEnd;
+ openIndent = '';
+ } else if (data.isElem(textElem)) {
+ if (this.config.pretty) {
+ textIndent += openIndent + this.config.indent;
+ }
+ this.textContext = data;
+ }
+
+ processedData += this.convert(data).data;
+
+ if (this.textContext == data) {
+ this.textContext = null;
+ if (this.config.pretty) dataEnd = EOL;
+ }
+
+ return openIndent +
+ tagOpenStart +
+ data.elem +
+ this.createAttrs(data) +
+ tagOpenEnd +
+ textIndent +
+ processedData +
+ dataEnd +
+ this.createIndent() +
+ tagCloseStart +
+ data.elem +
+ tagCloseEnd;
+
+ }
+
+};
+
+/**
+ * Create element attributes.
+ *
+ * @param {Object} elem attributes object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createAttrs = function(elem) {
+
+ var attrs = '';
+
+ elem.eachAttr(function(attr) {
+
+ if (attr.value !== undefined) {
+ attrs += ' ' +
+ attr.name +
+ this.config.attrStart +
+ String(attr.value).replace(this.config.regValEntities, this.config.encodeEntity) +
+ this.config.attrEnd;
+ }
+ else {
+ attrs += ' ' +
+ attr.name;
+ }
+
+
+ }, this);
+
+ return attrs;
+
+};
+
+/**
+ * Create text node.
+ *
+ * @param {String} text text
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createText = function(text) {
+
+ return this.createIndent() +
+ this.config.textStart +
+ text.replace(this.config.regEntities, this.config.encodeEntity) +
+ (this.textContext ? '' : this.config.textEnd);
+
+};
diff --git a/node_modules/svgo/lib/svgo/jsAPI.js b/node_modules/svgo/lib/svgo/jsAPI.js
new file mode 100644
index 00000000..1e2f55fb
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/jsAPI.js
@@ -0,0 +1,298 @@
+'use strict';
+
+var EXTEND = require('whet.extend');
+
+var JSAPI = module.exports = function(data, parentNode) {
+ EXTEND(this, data);
+ if (parentNode) {
+ Object.defineProperty(this, 'parentNode', {
+ writable: true,
+ value: parentNode
+ });
+ }
+};
+
+/**
+ * Perform a deep clone of this node.
+ *
+ * @return {Object} element
+ */
+JSAPI.prototype.clone = function() {
+ var node = this;
+ var nodeData = {};
+
+ Object.keys(node).forEach(function(key) {
+ if (key !== 'content') {
+ nodeData[key] = node[key];
+ }
+ });
+
+ // Deep-clone node data
+ // This is still faster than using EXTEND(true…)
+ nodeData = JSON.parse(JSON.stringify(nodeData));
+
+ // parentNode gets set to a proper object by the parent clone,
+ // but it needs to be true/false now to do the right thing
+ // in the constructor.
+ var clonedNode = new JSAPI(nodeData, !!node.parentNode);
+
+ if (node.content) {
+ clonedNode.content = node.content.map(function(childNode) {
+ var clonedChild = childNode.clone();
+ clonedChild.parentNode = clonedNode;
+ return clonedChild;
+ });
+ }
+
+ return clonedNode;
+};
+
+/**
+ * Determine if item is an element
+ * (any, with a specific name or in a names array).
+ *
+ * @param {String|Array} [param] element name or names arrays
+ * @return {Boolean}
+ */
+JSAPI.prototype.isElem = function(param) {
+
+ if (!param) return !!this.elem;
+
+ if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1);
+
+ return !!this.elem && this.elem === param;
+
+};
+
+/**
+ * Renames an element
+ *
+ * @param {String} name new element name
+ * @return {Object} element
+ */
+JSAPI.prototype.renameElem = function(name) {
+
+ if (name && typeof name === 'string')
+ this.elem = this.local = name;
+
+ return this;
+
+};
+
+/**
+ * Determine if element is empty.
+ *
+ * @return {Boolean}
+ */
+ JSAPI.prototype.isEmpty = function() {
+
+ return !this.content || !this.content.length;
+
+};
+
+/**
+ * Changes content by removing elements and/or adding new elements.
+ *
+ * @param {Number} start Index at which to start changing the content.
+ * @param {Number} n Number of elements to remove.
+ * @param {Array|Object} [insertion] Elements to add to the content.
+ * @return {Array} Removed elements.
+ */
+ JSAPI.prototype.spliceContent = function(start, n, insertion) {
+
+ if (arguments.length < 2) return [];
+
+ if (!Array.isArray(insertion))
+ insertion = Array.apply(null, arguments).slice(2);
+
+ insertion.forEach(function(inner) { inner.parentNode = this }, this);
+
+ return this.content.splice.apply(this.content, [start, n].concat(insertion));
+
+
+};
+
+/**
+ * Determine if element has an attribute
+ * (any, or by name or by name + value).
+ *
+ * @param {String} [name] attribute name
+ * @param {String} [val] attribute value (will be toString()'ed)
+ * @return {Boolean}
+ */
+ JSAPI.prototype.hasAttr = function(name, val) {
+
+ if (!this.attrs || !Object.keys(this.attrs).length) return false;
+
+ if (!arguments.length) return !!this.attrs;
+
+ if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString();
+
+ return !!this.attrs[name];
+
+};
+
+/**
+ * Determine if element has an attribute by local name
+ * (any, or by name or by name + value).
+ *
+ * @param {String} [localName] local attribute name
+ * @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
+ * @return {Boolean}
+ */
+ JSAPI.prototype.hasAttrLocal = function(localName, val) {
+
+ if (!this.attrs || !Object.keys(this.attrs).length) return false;
+
+ if (!arguments.length) return !!this.attrs;
+
+ var callback;
+
+ switch (val != null && val.constructor && val.constructor.name) {
+ case 'Number': // same as String
+ case 'String': callback = stringValueTest; break;
+ case 'RegExp': callback = regexpValueTest; break;
+ case 'Function': callback = funcValueTest; break;
+ default: callback = nameTest;
+ }
+ return this.someAttr(callback);
+
+ function nameTest(attr) {
+ return attr.local === localName;
+ }
+
+ function stringValueTest(attr) {
+ return attr.local === localName && val == attr.value;
+ }
+
+ function regexpValueTest(attr) {
+ return attr.local === localName && val.test(attr.value);
+ }
+
+ function funcValueTest(attr) {
+ return attr.local === localName && val(attr.value);
+ }
+
+};
+
+/**
+ * Get a specific attribute from an element
+ * (by name or name + value).
+ *
+ * @param {String} name attribute name
+ * @param {String} [val] attribute value (will be toString()'ed)
+ * @return {Object|Undefined}
+ */
+ JSAPI.prototype.attr = function(name, val) {
+
+ if (!this.hasAttr() || !arguments.length) return undefined;
+
+ if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined;
+
+ return this.attrs[name];
+
+};
+
+/**
+ * Get computed attribute value from an element
+ *
+ * @param {String} name attribute name
+ * @return {Object|Undefined}
+ */
+ JSAPI.prototype.computedAttr = function(name, val) {
+ /* jshint eqnull: true */
+ if (!arguments.length) return;
+
+ for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode);
+
+ if (val != null) {
+ return elem ? elem.hasAttr(name, val) : false;
+ } else if (elem && elem.hasAttr(name)) {
+ return elem.attrs[name].value;
+ }
+
+};
+
+/**
+ * Remove a specific attribute.
+ *
+ * @param {String|Array} name attribute name
+ * @param {String} [val] attribute value
+ * @return {Boolean}
+ */
+ JSAPI.prototype.removeAttr = function(name, val, recursive) {
+
+ if (!arguments.length) return false;
+
+ if (Array.isArray(name)) name.forEach(this.removeAttr, this);
+
+ if (!this.hasAttr(name)) return false;
+
+ if (!recursive && val && this.attrs[name].value !== val) return false;
+
+ delete this.attrs[name];
+
+ if (!Object.keys(this.attrs).length) delete this.attrs;
+
+ return true;
+
+};
+
+/**
+ * Add attribute.
+ *
+ * @param {Object} [attr={}] attribute object
+ * @return {Object|Boolean} created attribute or false if no attr was passed in
+ */
+ JSAPI.prototype.addAttr = function(attr) {
+ attr = attr || {};
+
+ if (attr.name === undefined ||
+ attr.prefix === undefined ||
+ attr.local === undefined
+ ) return false;
+
+ this.attrs = this.attrs || {};
+ this.attrs[attr.name] = attr;
+
+ return this.attrs[attr.name];
+
+};
+
+/**
+ * Iterates over all attributes.
+ *
+ * @param {Function} callback callback
+ * @param {Object} [context] callback context
+ * @return {Boolean} false if there are no any attributes
+ */
+ JSAPI.prototype.eachAttr = function(callback, context) {
+
+ if (!this.hasAttr()) return false;
+
+ for (var name in this.attrs) {
+ callback.call(context, this.attrs[name]);
+ }
+
+ return true;
+
+};
+
+/**
+ * Tests whether some attribute passes the test.
+ *
+ * @param {Function} callback callback
+ * @param {Object} [context] callback context
+ * @return {Boolean} false if there are no any attributes
+ */
+ JSAPI.prototype.someAttr = function(callback, context) {
+
+ if (!this.hasAttr()) return false;
+
+ for (var name in this.attrs) {
+ if (callback.call(context, this.attrs[name])) return true;
+ }
+
+ return false;
+
+};
diff --git a/node_modules/svgo/lib/svgo/plugins.js b/node_modules/svgo/lib/svgo/plugins.js
new file mode 100644
index 00000000..4e0308da
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/plugins.js
@@ -0,0 +1,98 @@
+'use strict';
+
+/**
+ * Plugins engine.
+ *
+ * @module plugins
+ *
+ * @param {Object} data input data
+ * @param {Object} plugins plugins object from config
+ * @return {Object} output data
+ */
+module.exports = function(data, plugins) {
+
+ plugins.forEach(function(group) {
+
+ switch(group[0].type) {
+ case 'perItem':
+ data = perItem(data, group);
+ break;
+ case 'perItemReverse':
+ data = perItem(data, group, true);
+ break;
+ case 'full':
+ data = full(data, group);
+ break;
+ }
+
+ });
+
+ return data;
+
+};
+
+/**
+ * Direct or reverse per-item loop.
+ *
+ * @param {Object} data input data
+ * @param {Array} plugins plugins list to process
+ * @param {Boolean} [reverse] reverse pass?
+ * @return {Object} output data
+ */
+function perItem(data, plugins, reverse) {
+
+ function monkeys(items) {
+
+ items.content = items.content.filter(function(item) {
+
+ // reverse pass
+ if (reverse && item.content) {
+ monkeys(item);
+ }
+
+ // main filter
+ var filter = true;
+
+ for (var i = 0; filter && i < plugins.length; i++) {
+ var plugin = plugins[i];
+
+ if (plugin.active && plugin.fn(item, plugin.params) === false) {
+ filter = false;
+ }
+ }
+
+ // direct pass
+ if (!reverse && item.content) {
+ monkeys(item);
+ }
+
+ return filter;
+
+ });
+
+ return items;
+
+ }
+
+ return monkeys(data);
+
+}
+
+/**
+ * "Full" plugins.
+ *
+ * @param {Object} data input data
+ * @param {Array} plugins plugins list to process
+ * @return {Object} output data
+ */
+function full(data, plugins) {
+
+ plugins.forEach(function(plugin) {
+ if (plugin.active) {
+ data = plugin.fn(data, plugin.params);
+ }
+ });
+
+ return data;
+
+}
diff --git a/node_modules/svgo/lib/svgo/svg2js.js b/node_modules/svgo/lib/svgo/svg2js.js
new file mode 100644
index 00000000..2c36900c
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/svg2js.js
@@ -0,0 +1,187 @@
+'use strict';
+
+var SAX = require('sax'),
+ JSAPI = require('./jsAPI.js'),
+ entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^\']+)'|"([^\"]+)")\s*>/g;
+
+var config = {
+ strict: true,
+ trim: false,
+ normalize: true,
+ lowercase: true,
+ xmlns: true,
+ position: true
+};
+
+/**
+ * Convert SVG (XML) string to SVG-as-JS object.
+ *
+ * @param {String} data input data
+ * @param {Function} callback
+ */
+module.exports = function(data, callback) {
+
+ var sax = SAX.parser(config.strict, config),
+ root = new JSAPI({ elem: '#document' }),
+ current = root,
+ stack = [root],
+ textContext = null,
+ parsingError = false;
+
+ function pushToContent(content) {
+
+ content = new JSAPI(content, current);
+
+ (current.content = current.content || []).push(content);
+
+ return content;
+
+ }
+
+ sax.ondoctype = function(doctype) {
+
+ pushToContent({
+ doctype: doctype
+ });
+
+ var subsetStart = doctype.indexOf('['),
+ entityMatch;
+
+ if (subsetStart >= 0) {
+ entityDeclaration.lastIndex = subsetStart;
+
+ while ((entityMatch = entityDeclaration.exec(data)) != null) {
+ sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
+ }
+ }
+ };
+
+ sax.onprocessinginstruction = function(data) {
+
+ pushToContent({
+ processinginstruction: data
+ });
+
+ };
+
+ sax.oncomment = function(comment) {
+
+ pushToContent({
+ comment: comment.trim()
+ });
+
+ };
+
+ sax.oncdata = function(cdata) {
+
+ pushToContent({
+ cdata: cdata
+ });
+
+ };
+
+ sax.onopentag = function(data) {
+
+ var elem = {
+ elem: data.name,
+ prefix: data.prefix,
+ local: data.local
+ };
+
+ if (Object.keys(data.attributes).length) {
+ elem.attrs = {};
+
+ for (var name in data.attributes) {
+ elem.attrs[name] = {
+ name: name,
+ value: data.attributes[name].value,
+ prefix: data.attributes[name].prefix,
+ local: data.attributes[name].local
+ };
+ }
+ }
+
+ elem = pushToContent(elem);
+ current = elem;
+
+ // Save info about <text> tag to prevent trimming of meaningful whitespace
+ if (data.name == 'text' && !data.prefix) {
+ textContext = current;
+ }
+
+ stack.push(elem);
+
+ };
+
+ sax.ontext = function(text) {
+
+ if (/\S/.test(text) || textContext) {
+
+ if (!textContext)
+ text = text.trim();
+
+ pushToContent({
+ text: text
+ });
+
+ }
+
+ };
+
+ sax.onclosetag = function() {
+
+ var last = stack.pop();
+
+ // Trim text inside <text> tag.
+ if (last == textContext) {
+ trim(textContext);
+ textContext = null;
+ }
+ current = stack[stack.length - 1];
+
+ };
+
+ sax.onerror = function(e) {
+
+ e.message = 'Error in parsing SVG: ' + e.message;
+ if (e.message.indexOf('Unexpected end') < 0) {
+ throw e;
+ }
+
+ };
+
+ sax.onend = function() {
+
+ if (!this.error) {
+ callback(root);
+ } else {
+ callback({ error: this.error.message });
+ }
+
+ };
+
+ try {
+ sax.write(data);
+ } catch (e) {
+ callback({ error: e.message });
+ parsingError = true;
+ }
+ if (!parsingError) sax.close();
+
+ function trim(elem) {
+ if (!elem.content) return elem;
+
+ var start = elem.content[0],
+ end = elem.content[elem.content.length - 1];
+
+ while (start && start.content && !start.text) start = start.content[0];
+ if (start && start.text) start.text = start.text.replace(/^\s+/, '');
+
+ while (end && end.content && !end.text) end = end.content[end.content.length - 1];
+ if (end && end.text) end.text = end.text.replace(/\s+$/, '');
+
+ return elem;
+
+ }
+
+};
diff --git a/node_modules/svgo/lib/svgo/tools.js b/node_modules/svgo/lib/svgo/tools.js
new file mode 100644
index 00000000..7dd33c07
--- /dev/null
+++ b/node_modules/svgo/lib/svgo/tools.js
@@ -0,0 +1,147 @@
+'use strict';
+
+/**
+ * Encode plain SVG data string into Data URI string.
+ *
+ * @param {String} str input string
+ * @param {String} type Data URI type
+ * @return {String} output string
+ */
+exports.encodeSVGDatauri = function(str, type) {
+
+ var prefix = 'data:image/svg+xml';
+
+ // base64
+ if (!type || type === 'base64') {
+
+ prefix += ';base64,';
+
+ str = prefix + new Buffer(str).toString('base64');
+
+ // URI encoded
+ } else if (type === 'enc') {
+
+ str = prefix + ',' + encodeURIComponent(str);
+
+ // unencoded
+ } else if (type === 'unenc') {
+
+ str = prefix + ',' + str;
+
+ }
+
+ return str;
+
+};
+
+/**
+ * Decode SVG Data URI string into plain SVG string.
+ *
+ * @param {string} str input string
+ * @return {String} output string
+ */
+exports.decodeSVGDatauri = function(str) {
+ var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
+ var match = regexp.exec(str);
+
+ // plain string
+ if (!match) return str;
+
+ var data = match[3];
+
+ // base64
+ if (match[2]) {
+
+ str = new Buffer(data, 'base64').toString('utf8');
+
+ // URI encoded
+ } else if (data.charAt(0) === '%') {
+
+ str = decodeURIComponent(data);
+
+ // unencoded
+ } else if (data.charAt(0) === '<') {
+
+ str = data;
+
+ }
+
+ return str;
+};
+
+exports.intersectArrays = function(a, b) {
+ return a.filter(function(n) {
+ return b.indexOf(n) > -1;
+ });
+};
+
+exports.cleanupOutData = function(data, params) {
+
+ var str = '',
+ delimiter,
+ prev;
+
+ data.forEach(function(item, i) {
+
+ // space delimiter by default
+ delimiter = ' ';
+
+ // no extra space in front of first number
+ if (i === 0) {
+ delimiter = '';
+ }
+
+ // remove floating-point numbers leading zeros
+ // 0.5 → .5
+ // -0.5 → -.5
+ if (params.leadingZero) {
+ item = removeLeadingZero(item);
+ }
+
+ // no extra space in front of negative number or
+ // in front of a floating number if a previous number is floating too
+ if (
+ params.negativeExtraSpace &&
+ (item < 0 ||
+ (String(item).charCodeAt(0) == 46 && prev % 1 !== 0)
+ )
+ ) {
+ delimiter = '';
+ }
+
+ // save prev item value
+ prev = item;
+
+ str += delimiter + item;
+
+ });
+
+ return str;
+
+};
+
+/**
+ * Remove floating-point numbers leading zero.
+ *
+ * @example
+ * 0.5 → .5
+ *
+ * @example
+ * -0.5 → -.5
+ *
+ * @param {Float} num input number
+ *
+ * @return {String} output number as string
+ */
+var removeLeadingZero = exports.removeLeadingZero = function(num) {
+ var strNum = num.toString();
+
+ if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) {
+ strNum = strNum.slice(1);
+ } else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) {
+ strNum = strNum.charAt(0) + strNum.slice(2);
+ }
+
+ return strNum;
+
+};