diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/svgo/lib | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-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.js | 80 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/coa.js | 581 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/config.js | 212 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/js2svg.js | 352 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/jsAPI.js | 298 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/plugins.js | 98 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/svg2js.js | 187 | ||||
| -rw-r--r-- | node_modules/svgo/lib/svgo/tools.js | 147 |
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 = { + '&': '&', + '\'': ''', + '"': '"', + '>': '>', + '<': '<', + }; + +/** + * 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; + +}; |
