diff --git a/examples/fromFile.js b/examples/fromFile.js new file mode 100644 index 00000000..a8211327 --- /dev/null +++ b/examples/fromFile.js @@ -0,0 +1,26 @@ +var SVGO = require('../lib/svgo'), + svgo = new SVGO(/*{ custom config object }*/); + +svgo + // optimize SVG file + .fromFile('../examples/test.svg') + // get optimized result + .then(function(min) { + + console.log(min); + // output: + // { + // // optimized SVG data string + // data: 'test' + // // additional info such as width/height and start/end bytes length + // info: { + // width: '10', + // height: '20', + // startBytes: 59, + // endBytes: 38 + // } + // } + + }) + // end promises chain + .done(); diff --git a/examples/fromStream.js b/examples/fromStream.js new file mode 100644 index 00000000..4872765a --- /dev/null +++ b/examples/fromStream.js @@ -0,0 +1,31 @@ +var FS = require('fs'), + PATH = require('path'), + path = PATH.resolve(__dirname, '../examples/test.svg'), + fileStream = FS.createReadStream(path, { encoding: 'utf8' }); + +var SVGO = require('../lib/svgo'), + svgo = new SVGO(/*{ custom config object }*/); + +svgo + // optimize stream + .fromStream(fileStream) + // get optimized result + .then(function(min) { + + console.log(min); + // output: + // { + // // optimized SVG data string + // data: 'test' + // // additional info such as width/height and start/end bytes length + // info: { + // width: '10', + // height: '20', + // startBytes: 59, + // endBytes: 38 + // } + // } + + }) + // end promises chain + .done(); diff --git a/examples/fromString.js b/examples/fromString.js new file mode 100644 index 00000000..88fa42f2 --- /dev/null +++ b/examples/fromString.js @@ -0,0 +1,26 @@ +var SVGO = require('../lib/svgo'), + svgo = new SVGO(/*{ custom config object }*/); + +svgo + // optimize SVG data string + .fromString('test') + // get optimized result + .then(function(min) { + + console.log(min); + // output: + // { + // // optimized SVG data string + // data: 'test' + // // additional info such as width/height and start/end bytes length + // info: { + // width: '10', + // height: '20', + // startBytes: 52, + // endBytes: 38 + // } + // } + + }) + // end promises chain + .done(); diff --git a/examples/test.svg b/examples/test.svg new file mode 100644 index 00000000..c539cd40 --- /dev/null +++ b/examples/test.svg @@ -0,0 +1,3 @@ + + test + diff --git a/lib/svgo.js b/lib/svgo.js index 3a84c13c..6e08469d 100644 --- a/lib/svgo.js +++ b/lib/svgo.js @@ -11,10 +11,14 @@ */ var INHERIT = require('inherit'), + Q = require('q'), + FS = require('fs'), + PATH = require('path'), CONFIG = require('./svgo/config'), SVG2JS = require('./svgo/svg2js'), PLUGINS = require('./svgo/plugins'), - JS2SVG = require('./svgo/js2svg'); + JS2SVG = require('./svgo/js2svg'), + decodeSVGDatauri = require('./svgo/tools').decodeSVGDatauri; /** * @class SVGO. @@ -34,27 +38,61 @@ module.exports = INHERIT(/** @lends SVGO.prototype */{ }, - /** - * Main optimize function. - * - * @param {String} svgdata input data - * - * @return {String} output data deferred promise - */ - optimize: function(svgdata) { + fromString: function(str) { + + str = decodeSVGDatauri(str); return this.config .then(function(config) { - return SVG2JS(svgdata, config.svg2js) + return SVG2JS(str, config.svg2js) .then(function(jsdata) { - return JS2SVG(PLUGINS(jsdata, config.plugins), config.js2svg); + var out = JS2SVG(PLUGINS(jsdata, config.plugins), config.js2svg); + + out.info.startBytes = Buffer.byteLength(str, 'utf-8'); + out.info.endBytes = Buffer.byteLength(out.data, 'utf-8'); + + return out; }); }); + }, + + fromStream: function(stream) { + + var deferred = Q.defer(), + inputData = [], + self = this; + + stream.pause(); + + stream + .on('data', function(chunk) { + inputData.push(chunk); + }) + .once('end', function() { + deferred.resolve(inputData.join()); + }) + .resume(); + + return deferred.promise + .then(function(str) { + + return self.fromString(str); + + }); + + }, + + fromFile: function(path) { + + path = PATH.resolve(__dirname, path); + + return this.fromStream(FS.createReadStream(path, { encoding: 'utf8' })); + } }); diff --git a/lib/svgo/coa.js b/lib/svgo/coa.js index 58ae41a2..c153e088 100644 --- a/lib/svgo/coa.js +++ b/lib/svgo/coa.js @@ -1,9 +1,8 @@ -var Q = require('q'), - FS = require('fs'), +var FS = require('fs'), UTIL = require('util'), SVGO = require('../svgo'), info = JSON.parse(require('fs').readFileSync(__dirname + '/../../package.json')), - datauriPrefix = 'data:image/svg+xml;base64,'; + encodeSVGDatauri = require('./tools').encodeSVGDatauri; /** * Command-Option-Argument. @@ -26,17 +25,19 @@ module.exports = require('coa').Cmd() }) .end() .opt() - .name('input').title('Input: stdin (default) | filename | Data URI base64 string') + .name('input').title('Input file, "-" for STDIN') .short('i').long('input') - .def(process.stdin) .val(function(val) { return val || this.reject('Option --input must have a value.'); }) .end() .opt() - .name('output').title('Output: stdout (default) | filename') + .name('string').title('Input SVG data string') + .short('s').long('string') + .end() + .opt() + .name('output').title('Output file (by default the same as the input), "-" for STDOUT') .short('o').long('output') - .def(process.stdout) .val(function(val) { return val || this.reject('Option --output must have a value.'); }) @@ -74,11 +75,6 @@ module.exports = require('coa').Cmd() .long('pretty') .flag() .end() - .opt() - .name('test').title('Make a visual comparison of two files (PhantomJS pre-required)') - .long('test') - .flag() - .end() .arg() .name('input').title('Alias to --input') .end() @@ -89,123 +85,71 @@ module.exports = require('coa').Cmd() var input = args && args.input ? args.input : opts.input, output = args && args.output ? args.output : opts.output, - inputData = [], - deferred = Q.defer(), + string = opts.string, startTime = Date.now(), - endTime, - startBytes, - endBytes; + svgo; - // https://github.com/joyent/node/issues/2130 - process.stdin.pause(); + if ( + (!input || input === '-') && + !string && + !opts.stdin && + process.stdin.isTTY + ) return this.usage(); - // if value is a string and not a Data URI string - if (typeof input === 'string' && !isDatauri(input)) { - // then create stream - input = FS.createReadStream(input, { encoding: 'utf8' }); - input.pause(); + // --string + if (string) { + svgo = new SVGO({ coa: opts }).fromString(string); } - // if run as just 'svgo' display help and exit - if (input.isTTY) return this.usage(); - - // datauri base64 string - if (typeof input === 'string') { - deferred.resolve(convertDatauriInput(input)); - // stdin or file stream - } else { - input - .on('data', function(chunk) { - inputData.push(chunk); - }) - .once('end', function() { - deferred.resolve(convertDatauriInput(inputData.join())); - }) - .resume(); + // STDIN + else if (input === '-') { + svgo = new SVGO({ coa: opts }).fromStream(process.stdin); } - return deferred.promise - .then(function(svg) { - startBytes = Buffer.byteLength(svg, 'utf-8'); + // file + else if (input) { + svgo = new SVGO({ coa: opts }).fromFile(input); + } - return new SVGO({ coa: opts }).optimize(svg); - }) - .then(function(svgmin) { - endTime = Date.now(); - endBytes = Buffer.byteLength(svgmin.data, 'utf-8'); + return svgo.then(function(svgmin) { - // --datauri - if (opts.datauri) { - // convert to Data URI base64 string - svgmin.data = datauriPrefix + new Buffer(svgmin.data).toString('base64'); - } - - if (typeof output === 'string') { - output = FS.createWriteStream(output, { encoding: 'utf8' }); + // --datauri + if (opts.datauri) { + // convert to Data URI base64 string + svgmin.data = encodeSVGDatauri(svgmin.data); + } + + // stdout + if (output === '-' || (input === '-' && !output)) { + process.stdout.write(svgmin.data + '\n'); + } + + // file + else { + + // if input is from file - overwrite it + if (!output && input) { + output = input; } + // output file + output = FS.createWriteStream(output, { encoding: 'utf8' }); output.write(svgmin.data); + output.end(); - if (output === process.stdout) { - output.write('\n'); - } else { - output.end(); + // print time info + printTimeInfo(startTime, Date.now()); - // print time info - printTimeInfo(startTime, endTime); + // print optimization profit info + printProfitInfo(svgmin.info.startBytes, svgmin.info.endBytes); - // print optimization profit info - printProfitInfo(startBytes, endBytes); + } - // --test - if (opts.test) { - // make a visual comparison of two files with PhantomJS and print info - printPhantomTestInfo( - input.path, - output.path, - svgmin.info.width || 500, - svgmin.info.height || 500 - ); - } - } - - }) - .done(); + }) + .done(); }); -/** - * Check if string is a Data URI base64. - * - * @param {String} str input string - * - * @return {Boolean} - */ -function isDatauri(str) { - - return str.substring(0, 26) === datauriPrefix; - -} - -/** - * Convert Data URI base64 string to plain SVG. - * - * @param {String} str input string - * - * @return {String} output string - */ -function convertDatauriInput(str) { - - // if datauri base64 string - if (isDatauri(str)) { - // then convert - str = new Buffer(str.substring(26), 'base64').toString('utf8'); - } - - return str; - -} - /** * Print time info. * @@ -235,40 +179,3 @@ function printProfitInfo(inBytes, outBytes) { ); } - -/** - * Make a visual comparison of two files with PhantomJS and print info. - * - * @param {String} file1 file1 path - * @param {String} file2 file2 path - * @param {Number} width width - * @param {Number} height height - */ -function printPhantomTestInfo(file1, file2, width, height) { - - var PHANTOM = require('./phantom'); - - UTIL.print('Visual comparison: '); - - PHANTOM.test(file1, file2, width, height) - .then(function(code) { - - var answer; - - if (code === 1) { - answer = 'OK'; - } else if (code === 2) { - answer = 'Oops, files are not visually identical!\n\nIf you do not see any visual differences with your eyes then ALL IS OK and there are only very minor floating-point numbers rounding errors in some browsers.\nBut if you see significant errors then things are bad :), please create an issue at https://github.com/svg/svgo/issues'; - } else if (code === 3) { - answer = 'Error while rendering SVG\nPlease create an issue at https://github.com/svg/svgo/issues'; - } else if (code === 127) { - answer = 'Error, you need to install PhantomJS first http://phantomjs.org/download.html'; - } else { - answer = 'Error, something went wrong\nPlease create an issue at https://github.com/svg/svgo/issues'; - } - - UTIL.print(answer + '\n\n'); - - }); - -} diff --git a/lib/svgo/phantom.js b/lib/svgo/phantom.js deleted file mode 100644 index a6a3d6dd..00000000 --- a/lib/svgo/phantom.js +++ /dev/null @@ -1,30 +0,0 @@ -var PATH = require('path'), - Q = require('Q'), - spawn = require('child_process').spawn; - -/** - * Two SVG files rendering and visually comparing with PhantomJS. - * - * @see http://phantomjs.org/ - * @see /lib/phantom_test.js - * - * @param {String} file1 file1 - * @param {String} file2 file2 - * @param {Number} width viewport width - * @param {Number} height viewport height - * - * @return {Object} deferred promise with exit code - */ -exports.test = function(file1, file2, width, height) { - - var phantomTestFile = PATH.resolve(__dirname, './phantom_test.js'), - phantom = spawn('phantomjs', [phantomTestFile, file1, file2, width, height]), - deferred = Q.defer(); - - phantom.on('exit', function(code) { - deferred.resolve(code); - }); - - return deferred.promise; - -}; diff --git a/lib/svgo/phantom_test.js b/lib/svgo/phantom_test.js deleted file mode 100644 index 0a4cfb46..00000000 --- a/lib/svgo/phantom_test.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Two SVG files rendering and visually comparing with PhantomJS. - * - * @see http://phantomjs.org/ - * @see https://github.com/ariya/phantomjs/wiki/Screen-Capture - */ -var page = require('webpage').create(), - file1 = phantom.args[0], - file2 = phantom.args[1], - width = phantom.args[2], - height = phantom.args[3]; - -page.viewportSize = { - width: width, - height: height -}; - -open(file1, function(out1) { - open(file2, function(out2) { - if (out1.length === out2.length && out1 === out2) { - phantom.exit(1); - } else { - phantom.exit(2); - } - }); -}); - -function open(file, callback) { - - page.open(file, function(status) { - - if (status !== 'success') { - phantom.exit(3); - } else { - callback(page.renderBase64('PNG')); - } - - }); - -} diff --git a/lib/svgo/svg2js.js b/lib/svgo/svg2js.js index 9fed6eee..7fd6407e 100644 --- a/lib/svgo/svg2js.js +++ b/lib/svgo/svg2js.js @@ -8,12 +8,12 @@ var UTIL = require('util'), * * @module svg2js * - * @param {String} svgdata input data + * @param {String} svg input data * @param {Object} config sax xml parser config * * @return {Object} output data deferred promise */ -module.exports = function(svgdata, config) { +module.exports = function(svg, config) { var deferred = Q.defer(), sax = SAX.parser(config.strict, config), @@ -119,7 +119,7 @@ module.exports = function(svgdata, config) { }; - sax.write(svgdata).close(); + sax.write(svg).close(); return deferred.promise; diff --git a/lib/svgo/tools.js b/lib/svgo/tools.js index a7d0f03e..39d41517 100644 --- a/lib/svgo/tools.js +++ b/lib/svgo/tools.js @@ -1,3 +1,33 @@ +var datauriSVGPrefix = exports.datauriSVGPrefix = 'data:image/svg+xml;base64,'; + +/** + * Encode plain SVG data string into Data URI base64 string. + * + * @param {String} str input string + * + * @return {String} output string + */ +exports.encodeSVGDatauri = function(str) { + + return datauriSVGPrefix + new Buffer(str).toString('base64'); + +}; + +/** + * Decode SVG Data URI base64 string into plain SVG data string. + * + * @param {string} str input string + * + * @return {String} output string + */ +exports.decodeSVGDatauri = function(str) { + + if (str.substring(0, 26) !== datauriSVGPrefix) return str; + + return new Buffer(str.substring(26), 'base64').toString('utf8'); + +}; + exports.extend = require('node.extend'); exports.flattenOneLevel = function(array) { diff --git a/test/plugins/_index.js b/test/plugins/_index.js index 3f562826..6d09fc7c 100644 --- a/test/plugins/_index.js +++ b/test/plugins/_index.js @@ -66,7 +66,7 @@ function getResult(name, index) { .then(function(input) { mySVGO.enableOnlyOne(name); - return mySVGO.optimize(input.toString()); + return mySVGO.fromString(input.toString()); }) .then(function(min) { return readFile(name + '.' + index +'.should.svg')