├── .jshintignore ├── .prettierrc ├── .eslintignore ├── .npmrc ├── .gitignore ├── test ├── mocha.opts ├── test.svg └── Inkscape.js ├── .prettierignore ├── .editorconfig ├── .eslintrc ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── package.json ├── README.md ├── .jshintrc ├── lib └── Inkscape.js └── CHANGELOG.md /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | testdata 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | save-exact = false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /coverage/ 3 | /.nyc_output/ 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --timeout 20000 3 | --check-leaks 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /coverage/ 3 | /.nyc_output/ 4 | 5 | # Don't fight npm i --save 6 | /package.json 7 | 8 | # Generated 9 | /CHANGELOG.md 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "prettier"], 3 | "plugins": ["import", "mocha"], 4 | "env": { 5 | "mocha": true 6 | }, 7 | "rules": { 8 | "prefer-template": "error", 9 | "mocha/no-exclusive-tests": "error", 10 | "mocha/no-nested-tests": "error", 11 | "mocha/no-identical-title": "error", 12 | "prefer-const": [ 13 | "error", 14 | { 15 | "destructuring": "all", 16 | "ignoreReadBeforeAssign": false 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Andreas Lind 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the author nor the names of contributors may 15 | be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 19 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 'on': 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-18.04 9 | name: Node ${{ matrix.node }} 10 | strategy: 11 | matrix: 12 | node: 13 | - '12' 14 | - '14' 15 | - '16' 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Setup node 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node }} 22 | - run: sudo add-apt-repository -y ppa:inkscape.dev/stable && sudo apt-get update && sudo apt-get install -y inkscape && inkscape --version 23 | - run: npm install 24 | - run: npm test 25 | 26 | test-targets: 27 | runs-on: ubuntu-18.04 28 | name: ${{ matrix.targets.name }} 29 | strategy: 30 | matrix: 31 | targets: 32 | - name: 'Lint' 33 | target: 'lint' 34 | - name: 'Coverage' 35 | target: 'coverage' 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Setup node 40 | uses: actions/setup-node@v1 41 | with: 42 | node-version: '14' 43 | - run: sudo add-apt-repository -y ppa:inkscape.dev/stable && sudo apt-get update && sudo apt-get install -y inkscape && inkscape --version 44 | - run: npm install 45 | - run: npm run ${{ matrix.targets.target }} 46 | - name: Upload coverage 47 | uses: coverallsapp/github-action@master 48 | with: 49 | github-token: ${{ secrets.GITHUB_TOKEN }} 50 | if: ${{ matrix.targets.target == 'coverage' }} 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inkscape", 3 | "version": "3.1.1", 4 | "description": "The inkscape utility as a readable/writable stream", 5 | "main": "lib/Inkscape.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "async": "^3.0.0", 11 | "gettemporaryfilepath": "^1.0.0", 12 | "semver": "^7.3.2" 13 | }, 14 | "devDependencies": { 15 | "coveralls": "^3.0.2", 16 | "eslint": "^8.6.0", 17 | "eslint-config-prettier": "^8.3.0", 18 | "eslint-config-standard": "^17.0.0", 19 | "eslint-plugin-import": "^2.26.0", 20 | "eslint-plugin-mocha": "^10.0.1", 21 | "eslint-plugin-n": "^15.1.0", 22 | "eslint-plugin-node": "^11.0.0", 23 | "eslint-plugin-promise": "^6.0.0", 24 | "eslint-plugin-standard": "^5.0.0", 25 | "mocha": "^8.3.0", 26 | "nyc": "^15.0.0", 27 | "offline-github-changelog": "^2.0.0", 28 | "prettier": "~2.5.0", 29 | "sinon": "^12.0.1", 30 | "unexpected": "^12.0.0", 31 | "unexpected-sinon": "^11.0.0", 32 | "unexpected-stream": "^5.0.0" 33 | }, 34 | "scripts": { 35 | "lint": "eslint . && prettier --check '**/*.{js,json,md}'", 36 | "prettier": "prettier --write '**/*.{js,md}'", 37 | "test": "mocha", 38 | "test:ci": "npm run coverage", 39 | "coverage": "nyc --reporter=lcov --reporter=text --all -- npm test && echo google-chrome coverage/lcov-report/index.html", 40 | "preversion": "offline-github-changelog --next=${npm_new_version} > CHANGELOG.md && git add CHANGELOG.md" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/papandreou/node-inkscape.git" 45 | }, 46 | "keywords": [ 47 | "inkscape", 48 | "svg", 49 | "png", 50 | "image", 51 | "optimization", 52 | "stream", 53 | "filter", 54 | "read/write", 55 | "duplex" 56 | ], 57 | "author": "Andreas Lind ", 58 | "license": "BSD-3-Clause", 59 | "nyc": { 60 | "include": [ 61 | "lib/**" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-inkscape 2 | 3 | [![NPM version](https://badge.fury.io/js/inkscape.svg)](http://badge.fury.io/js/inkscape) 4 | [![Build Status](https://travis-ci.org/papandreou/node-inkscape.svg?branch=master)](https://travis-ci.org/papandreou/node-inkscape) 5 | [![Coverage Status](https://coveralls.io/repos/papandreou/node-inkscape/badge.svg)](https://coveralls.io/r/papandreou/node-inkscape) 6 | [![Dependency Status](https://david-dm.org/papandreou/node-inkscape.svg)](https://david-dm.org/papandreou/node-inkscape) 7 | 8 | The inkscape command line utility as a readable/writable stream. This 9 | is handy for situations where you don't want to worry about writing 10 | the input to disc and reading the output afterwards. 11 | 12 | The constructor optionally takes an array of command line options for 13 | the `inkscape` binary: 14 | 15 | ```javascript 16 | var Inkscape = require('inkscape'), 17 | svgToPdfConverter = new Inkscape(['--export-pdf', '--export-width=1024']); 18 | 19 | sourceStream.pipe(svgToPdfConverter).pipe(destinationStream); 20 | ``` 21 | 22 | Import type can also be fed to the constructor (converting PDF to PNG): 23 | 24 | ```javascript 25 | var Inkscape = require('inkscape'), 26 | pdfToPngConverter = new Inkscape([ 27 | '--export-png', 28 | '--export-width=1024', 29 | '--import-pdf', 30 | ]); 31 | 32 | sourceStream.pipe(pdfToPngConverter).pipe(destinationStream); 33 | ``` 34 | 35 | Inkscape as a web service (converts to a PNG): 36 | 37 | ```javascript 38 | var Inkscape = require('inkscape'), 39 | http = require('http'); 40 | 41 | http 42 | .createServer(function (req, res) { 43 | if (req.headers['content-type'] === 'image/svg') { 44 | res.writeHead(200, { 'Content-Type': 'image/png' }); 45 | req.pipe(new Inkscape(['-e'])).pipe(res); 46 | } else { 47 | res.writeHead(400); 48 | res.end('Feed me an SVG!'); 49 | } 50 | }) 51 | .listen(1337); 52 | ``` 53 | 54 | ## Installation 55 | 56 | Make sure you have node.js and npm installed, and that the `inkscape` binary is in your PATH, then run: 57 | 58 | ``` 59 | npm install inkscape 60 | ``` 61 | 62 | ## Releases 63 | 64 | [Changelog](https://github.com/papandreou/node-inkscape/blob/master/CHANGELOG.md) 65 | 66 | ## License 67 | 68 | 3-clause BSD license -- see the `LICENSE` file for details. 69 | -------------------------------------------------------------------------------- /test/test.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gnome Symbolic Icon Theme 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | Gnome Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | // Defaults as of jshint edition 2011-04-16 2 | 3 | { 4 | // If the scan should stop on first error. 5 | "passfail": false, 6 | // Maximum errors before stopping. 7 | "maxerr": 50, 8 | 9 | 10 | // Predefined globals 11 | 12 | // If the standard browser globals should be predefined. 13 | "browser": false, 14 | // If the Node.js environment globals should be predefined. 15 | "node": false, 16 | // If the Rhino environment globals should be predefined. 17 | "rhino": false, 18 | // If CouchDB globals should be predefined. 19 | "couch": false, 20 | // If the Windows Scripting Host environment globals should be predefined. 21 | "wsh": false, 22 | 23 | // If jQuery globals should be predefined. 24 | "jquery": false, 25 | // If Prototype and Scriptaculous globals should be predefined. 26 | "prototypejs": false, 27 | // If MooTools globals should be predefined. 28 | "mootools": false, 29 | // If Dojo Toolkit globals should be predefined. 30 | "dojo": false, 31 | 32 | // Custom predefined globals. 33 | "predef": ["process", "module", "exports", "require", "Buffer", "define"], 34 | 35 | // Development 36 | 37 | // If debugger statements should be allowed. 38 | "debug": false, 39 | // If logging globals should be predefined (console, alert, etc.). 40 | "devel": false, 41 | 42 | 43 | // ECMAScript 5 44 | 45 | // If ES5 syntax should be allowed. 46 | "es5": false, 47 | // Require the "use strict"; pragma. 48 | "strict": false, 49 | // If global "use strict"; should be allowed (also enables strict). 50 | "globalstrict": false, 51 | 52 | 53 | // The Good Parts 54 | 55 | // If automatic semicolon insertion should be tolerated. 56 | "asi": false, 57 | // If line breaks should not be checked, e.g. `return [\n] x`. 58 | "laxbreak": false, 59 | // If bitwise operators (&, |, ^, etc.) should not be allowed. 60 | "bitwise": false, 61 | // If assignments inside if, for and while should be allowed. Usually 62 | // conditions and loops are for comparison, not assignments. 63 | "boss": false, 64 | // If curly braces around all blocks should be required. 65 | "curly": false, 66 | // If === should be required. 67 | "eqeqeq": true, 68 | // If == null comparisons should be tolerated. 69 | "eqnull": false, 70 | // If eval should be allowed. 71 | "evil": false, 72 | // If ExpressionStatement should be allowed as Programs. 73 | "expr": false, 74 | // If `for in` loops must filter with `hasOwnPrototype`. 75 | "forin": false, 76 | // If immediate invocations must be wrapped in parens, e.g. 77 | // `( function(){}() );`. 78 | "immed": true, 79 | // If use before define should not be tolerated. 80 | "latedef": false, 81 | // If functions should be allowed to be defined within loops. 82 | "loopfunc": true, 83 | // If arguments.caller and arguments.callee should be disallowed. 84 | "noarg": false, 85 | // If the . should not be allowed in regexp literals. 86 | "regexp": false, 87 | // If unescaped first/last dash (-) inside brackets should be tolerated. 88 | "regexdash": false, 89 | // If script-targeted URLs should be tolerated. 90 | "scripturl": false, 91 | // If variable shadowing should be tolerated. 92 | "shadow": false, 93 | // If `new function () { ... };` and `new Object;` should be tolerated. 94 | "supernew": false, 95 | // If variables should be declared before used. 96 | "undef": true, 97 | // If variables defined but not used are checked. 98 | "unused": "vars", 99 | // If `this` inside a non-constructor function is valid. 100 | "validthis": false, 101 | // If smarttabs should be tolerated 102 | // (http://www.emacswiki.org/emacs/SmartTabs). 103 | "smarttabs": false, 104 | // If the `__proto__` property should be allowed. 105 | "proto": false, 106 | // If one case switch statements should be allowed. 107 | "onecase": false, 108 | // If non-standard (but widely adopted) globals should be predefined. 109 | "nonstandard": false, 110 | // Allow multiline strings. 111 | "multistr": false, 112 | // If line breaks should not be checked around commas. 113 | "laxcomma": false, 114 | // If semicolons may be ommitted for the trailing statements inside of a 115 | // one-line blocks. 116 | "lastsemic": false, 117 | // If the `__iterator__` property should be allowed. 118 | "iterator": false, 119 | // If only function scope should be used for scope tests. 120 | "funcscope": false, 121 | // If es.next specific syntax should be allowed. 122 | "esnext": false, 123 | 124 | 125 | // Style preferences 126 | 127 | // If constructor names must be capitalized. 128 | "newcap": true, 129 | // If empty blocks should be disallowed. 130 | "noempty": false, 131 | // If using `new` for side-effects should be disallowed. 132 | "nonew": false, 133 | // If names should be checked for leading or trailing underscores 134 | // (object._attribute would be disallowed). 135 | "nomen": false, 136 | // If only one var statement per function should be allowed. 137 | "onevar": false, 138 | // If increment and decrement (`++` and `--`) should not be allowed. 139 | "plusplus": true, 140 | // If square bracket syntax for property access is tolerated. 141 | "sub": true, 142 | // If trailing whitespace rules apply. 143 | "trailing": true, 144 | // If strict whitespace rules apply. 145 | "white": true, 146 | // Specify indentation. 147 | "indent": 4 148 | } 149 | -------------------------------------------------------------------------------- /lib/Inkscape.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process'); 2 | const semver = require('semver'); 3 | const Stream = require('stream').Stream; 4 | const fs = require('fs'); 5 | const util = require('util'); 6 | const getTemporaryFilePath = require('gettemporaryfilepath'); 7 | const async = require('async'); 8 | 9 | const execFileAsync = util.promisify(childProcess.execFile); 10 | 11 | let inkscapePromise = null; 12 | 13 | function checkInkscape() { 14 | if (inkscapePromise === null) { 15 | inkscapePromise = _checkInkscape(); 16 | } 17 | 18 | return inkscapePromise; 19 | } 20 | 21 | // check if the installed inkscape version is compatible (>= 1.0.0) 22 | async function _checkInkscape() { 23 | const versionChild = await execFileAsync('inkscape', ['--version']); 24 | const versionMatch = versionChild.stdout 25 | .toString('utf8') 26 | .match(/Inkscape ([0-9.-]+) /); 27 | const versionOutput = 28 | semver.coerce(versionMatch ? versionMatch[1] : '0') || '0.0.0'; 29 | if (semver.lt(versionOutput, '1.0.0')) { 30 | throw new Error(`inkscape: unsupported version ${versionOutput}`); 31 | } 32 | } 33 | 34 | class Inkscape extends Stream { 35 | constructor(inkscapeArgs, { outputFormat = null, inputFormat = null } = {}) { 36 | super(); 37 | 38 | this.writable = true; 39 | this.readable = true; 40 | 41 | this.hasEnded = false; 42 | 43 | this.outputFormat = outputFormat; 44 | 45 | // This value will be overwritten by applicable Inkscape CLI args. 46 | // 47 | let setOutputFormat = false; 48 | 49 | this.inputFormat = inputFormat; 50 | 51 | // This value will be overwritten by applicable Inkscape CLI args. 52 | // 53 | let setInputFormat = false; 54 | 55 | this.inkscapeArgs = []; 56 | 57 | this.guiMode = false; 58 | 59 | if ( 60 | inkscapeArgs && 61 | inkscapeArgs.some( 62 | (inkscapeArg) => 63 | inkscapeArg === '--with-gui' || /^--verb/.test(inkscapeArg) 64 | ) 65 | ) { 66 | this.inkscapeArgs.push('--with-gui'); 67 | this.guiMode = true; 68 | } 69 | 70 | (inkscapeArgs || []).forEach((inkscapeArg) => { 71 | const matchInkscapeArg = inkscapeArg.match( 72 | /^(-l|-p|--export-plain-svg|--export-type)(?:=(.*)|$)$/ 73 | ); 74 | if (matchInkscapeArg) { 75 | if ( 76 | matchInkscapeArg[1] === '--export-type' && 77 | /svg|png|ps|eps|pdf/.test(matchInkscapeArg[2]) 78 | ) { 79 | this.outputFormat = matchInkscapeArg[2]; 80 | setOutputFormat = true; 81 | } else if ( 82 | matchInkscapeArg[1] === '-l' || 83 | matchInkscapeArg[1] === '--export-plain-svg' 84 | ) { 85 | this.outputFormat = 'svg'; 86 | setOutputFormat = true; 87 | this.inkscapeArgs.push('--export-plain-svg'); 88 | } else { 89 | throw new Error( 90 | `Internal error: Unable to parse switch: ${matchInkscapeArg[1]}` 91 | ); 92 | } 93 | } else { 94 | this.inkscapeArgs.push(inkscapeArg); 95 | } 96 | }); 97 | 98 | // Set output format as applicable 99 | // 100 | if (setOutputFormat || outputFormat) { 101 | if (this.guiMode) { 102 | throw new Error( 103 | `Cannot use ${setOutputFormat} when --verb=... is in use. Please use --FileSave instead` 104 | ); 105 | } 106 | this.inkscapeOutputFilePath = getTemporaryFilePath({ 107 | suffix: `.${this.outputFormat}`, 108 | }); 109 | setOutputFormat = false; 110 | } 111 | 112 | // Set input format as applicable 113 | // 114 | if (setInputFormat || inputFormat) { 115 | this.inkscapeInputFilePath = getTemporaryFilePath({ 116 | suffix: `.${this.inputFormat || 'svg'}`, 117 | }); 118 | this.inkscapeArgs.push(this.inkscapeInputFilePath); 119 | setInputFormat = false; 120 | } 121 | if (!this.outputFormat && !this.guiMode) { 122 | this.outputFormat = 'png'; 123 | this.inkscapeArgs.push(`--export-type=png`); 124 | } 125 | 126 | this.filesToCleanUp = []; 127 | 128 | if (!this.guiMode) { 129 | this.inkscapeOutputFilePath = getTemporaryFilePath({ 130 | suffix: `.${this.outputFormat}`, 131 | }); 132 | this.inkscapeArgs.push( 133 | `--export-filename=${this.inkscapeOutputFilePath}` 134 | ); 135 | 136 | this.filesToCleanUp.push(this.inkscapeOutputFilePath); 137 | } 138 | 139 | if (!this.inputFormat) { 140 | this.inputFormat = 'svg'; 141 | this.inkscapeInputFilePath = getTemporaryFilePath({ suffix: '.svg' }); 142 | this.inkscapeArgs.push(this.inkscapeInputFilePath); 143 | } 144 | 145 | if (this.guiMode) { 146 | this.inkscapeOutputFilePath = this.inkscapeInputFilePath; 147 | } 148 | 149 | this.commandLine = `inkscape${ 150 | this.inkscapeArgs ? ` ${this.inkscapeArgs.join(' ')}` : '' 151 | }`; // For debugging 152 | } 153 | 154 | _error(err) { 155 | if (!this.hasEnded) { 156 | this.hasEnded = true; 157 | this.cleanUp(); 158 | this.emit('error', err); 159 | } 160 | } 161 | 162 | cleanUp() { 163 | if (this.readStream) { 164 | this.readStream.destroy(); 165 | this.readStream = null; 166 | } 167 | if (this.writeStream) { 168 | this.writeStream.destroy(); 169 | this.writeStream = null; 170 | } 171 | if (this.inkscapeProcess) { 172 | this.inkscapeProcess.kill(); 173 | this.inkscapeProcess = null; 174 | } 175 | async.each(this.filesToCleanUp, fs.unlink, () => {}); 176 | } 177 | 178 | destroy() { 179 | this.cleanUp(); 180 | this.hasEnded = true; 181 | } 182 | 183 | write(chunk) { 184 | if (this.hasEnded) { 185 | return; 186 | } 187 | if (!this.writeStream) { 188 | this.filesToCleanUp.push(this.inkscapeInputFilePath); 189 | this.writeStream = fs.createWriteStream(this.inkscapeInputFilePath); 190 | this.writeStream.on('error', (err) => { 191 | this.cleanUp(); 192 | this._error(err); 193 | }); 194 | } 195 | 196 | this.writeStream.write(chunk); 197 | } 198 | 199 | end(chunk) { 200 | if (this.hasEnded) { 201 | return; 202 | } 203 | if (chunk) { 204 | this.write(chunk); 205 | } 206 | this.writeStream.end(); 207 | this.writable = false; 208 | this.writeStream.once('close', async () => { 209 | if (this.hasEnded) { 210 | return; 211 | } 212 | 213 | try { 214 | await checkInkscape(); 215 | } catch (e) { 216 | this._error(e); // signal error 217 | return; 218 | } 219 | 220 | this.inkscapeProcess = childProcess.spawn('inkscape', this.inkscapeArgs, { 221 | windowsHide: true, 222 | }); 223 | const stdoutChunks = []; 224 | const stderrChunks = []; 225 | 226 | this.inkscapeProcess.stdout.on('data', (chunk) => { 227 | stdoutChunks.push(chunk); 228 | }); 229 | 230 | this.inkscapeProcess.stderr.on('data', (chunk) => { 231 | stderrChunks.push(chunk); 232 | }); 233 | 234 | function getStdoutAndStderrAsText() { 235 | return `STDOUT: ${Buffer.concat(stdoutChunks).toString( 236 | 'ascii' 237 | )}\nSTDERR: ${Buffer.concat(stderrChunks).toString('ascii')}`; 238 | } 239 | 240 | this.inkscapeProcess.once('error', this._error.bind(this)); 241 | 242 | this.inkscapeProcess.once('exit', (exitCode) => { 243 | this.inkscapeProcess = null; 244 | if (this.hasEnded) { 245 | return; 246 | } 247 | if (exitCode > 0) { 248 | return this._error( 249 | new Error( 250 | `${ 251 | this.commandLine 252 | } exited with a non-zero exit code: ${exitCode}\n${getStdoutAndStderrAsText()}` 253 | ) 254 | ); 255 | } 256 | fs.stat(this.inkscapeOutputFilePath, (err, stats) => { 257 | if (err) { 258 | this.filesToCleanUp.splice( 259 | this.filesToCleanUp.indexOf(this.inkscapeOutputFilePath), 260 | 1 261 | ); 262 | return this._error( 263 | new Error( 264 | `inkscape ${this.inkscapeArgs.join( 265 | ' ' 266 | )} did not write an output file.\n${getStdoutAndStderrAsText()}` 267 | ) 268 | ); 269 | } 270 | this.readStream = fs.createReadStream(this.inkscapeOutputFilePath); 271 | if (this.isPaused) { 272 | this.readStream.pause(); 273 | } 274 | this.readStream.on('data', (chunk) => { 275 | this.emit('data', chunk); 276 | }); 277 | this.readStream.on('end', () => { 278 | this.cleanUp(); 279 | this.emit('end'); 280 | }); 281 | }); 282 | }); 283 | }); 284 | } 285 | 286 | // Proxy pause and resume to the underlying readStream if it has been 287 | // created, otherwise just keep track of the paused state: 288 | pause() { 289 | this.isPaused = true; 290 | if (this.readStream) { 291 | this.readStream.pause(); 292 | } 293 | } 294 | 295 | resume() { 296 | this.isPaused = false; 297 | if (this.readStream) { 298 | this.readStream.resume(); 299 | } 300 | } 301 | } 302 | 303 | module.exports = Inkscape; 304 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v3.1.1 (2020-11-14) 2 | 3 | - [#50](https://github.com/papandreou/node-inkscape/pull/50) \#48: Input file path is now correctly populated. ([jonathan.thompson](mailto:jonathan.thompson@nimbusnine.co), [schrodingersket](mailto:jonathan@urbaneinnovation.com)) 4 | 5 | ### v3.1.0 (2020-11-12) 6 | 7 | #### Pull requests 8 | 9 | - [#49](https://github.com/papandreou/node-inkscape/pull/49) \#48: Added default arguments to `Inkscape` constructor to explicitly … ([jonathan.thompson](mailto:jonathan.thompson@nimbusnine.co)) 10 | - [#47](https://github.com/papandreou/node-inkscape/pull/47) Upgrade eslint-config-standard to version 16.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 11 | - [#46](https://github.com/papandreou/node-inkscape/pull/46) Upgrade eslint-config-standard to version 15.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 12 | - [#45](https://github.com/papandreou/node-inkscape/pull/45) Upgrade prettier to version 2.1.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 13 | 14 | #### Commits to master 15 | 16 | - [Change to an options object, fix test](https://github.com/papandreou/node-inkscape/commit/574322e53bd931ee878058fed243475cab416a12) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 17 | 18 | ### v3.0.0 (2020-08-11) 19 | 20 | #### Pull requests 21 | 22 | - [#41](https://github.com/papandreou/node-inkscape/pull/41) Port the module to inkscape 1.0 ([Alex J Burke](mailto:alex@alexjeffburke.com), [Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 23 | - [#44](https://github.com/papandreou/node-inkscape/pull/44) Upgrade eslint-plugin-mocha to version 8.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 24 | 25 | #### Commits to master 26 | 27 | - [Unsupport node.js 8 \(semver-major\)](https://github.com/papandreou/node-inkscape/commit/33280f1cfb27f023b5f2e4740f469d5172ec9419) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 28 | 29 | ### v2.1.0 (2020-07-22) 30 | 31 | #### Pull requests 32 | 33 | - [#40](https://github.com/papandreou/node-inkscape/pull/40) Upgrade eslint-plugin-mocha to version 7.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 34 | - [#39](https://github.com/papandreou/node-inkscape/pull/39) Upgrade eslint to version 7.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 35 | - [#36](https://github.com/papandreou/node-inkscape/pull/36) Upgrade sinon to version 9.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 36 | - [#35](https://github.com/papandreou/node-inkscape/pull/35) Upgrade mocha to version 7.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 37 | - [#34](https://github.com/papandreou/node-inkscape/pull/34) Upgrade eslint-plugin-node to version 11.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 38 | - [#32](https://github.com/papandreou/node-inkscape/pull/32) Upgrade nyc to version 15.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 39 | - [#33](https://github.com/papandreou/node-inkscape/pull/33) Upgrade sinon to version 8.0.1 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 40 | - [#30](https://github.com/papandreou/node-inkscape/pull/30) Upgrade eslint-plugin-node to version 10.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 41 | - [#29](https://github.com/papandreou/node-inkscape/pull/29) Upgrade eslint-plugin-mocha to version 6.1.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 42 | - [#28](https://github.com/papandreou/node-inkscape/pull/28) Upgrade eslint-config-standard to version 14.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 43 | - [#25](https://github.com/papandreou/node-inkscape/pull/25) Upgrade eslint-config-standard to version 13.0.1 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 44 | - [#24](https://github.com/papandreou/node-inkscape/pull/24) Upgrade eslint-config-prettier to version 6.0.0 ([depfu[bot]](mailto:23717796+depfu[bot]@users.noreply.github.com)) 45 | - [#22](https://github.com/papandreou/node-inkscape/pull/22) Upgrade eslint-config-prettier to version 5.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 46 | - [#20](https://github.com/papandreou/node-inkscape/pull/20) Upgrade async to version 3.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 47 | - [#21](https://github.com/papandreou/node-inkscape/pull/21) Upgrade unexpected-stream to version 4.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 48 | - [#18](https://github.com/papandreou/node-inkscape/pull/18) Upgrade nyc to version 14.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 49 | - [#17](https://github.com/papandreou/node-inkscape/pull/17) Upgrade mocha to version 6.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 50 | - [#15](https://github.com/papandreou/node-inkscape/pull/15) Upgrade sinon to version 7.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 51 | - [#14](https://github.com/papandreou/node-inkscape/pull/14) Upgrade unexpected-stream to version 3.0.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 52 | 53 | #### Commits to master 54 | 55 | - [Pass windowsHide:true to childProcess.spawn](https://github.com/papandreou/node-inkscape/commit/9efa5a17e600300c9dab77afa4cd2f2e6bd41fc4) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 56 | - [Remove unnecessary env var in coverage script](https://github.com/papandreou/node-inkscape/commit/18f3aab1b83fe48d3efe6661220d8c562a4e222c) ([Andreas Lind](mailto:andreas.lind@peakon.com)) 57 | - [Update Travis setup, run on node.js 12 and don't run lint on the others](https://github.com/papandreou/node-inkscape/commit/258ec6b2f2b62f3d78ba16b960a10d00a9ca8e4b) ([Andreas Lind](mailto:andreas.lind@peakon.com)) 58 | - [prettier --write '\*\*\/\*.{js,md}'](https://github.com/papandreou/node-inkscape/commit/491191b11fdfa4e9603ee2d5a10d5a52d6b5fed2) ([Andreas Lind](mailto:andreas.lind@peakon.com)) 59 | - [Also require prettier formatting for .md files](https://github.com/papandreou/node-inkscape/commit/123081cb6bcf718fd169f4a58b4321bef916dd2f) ([Andreas Lind](mailto:andreas.lind@peakon.com)) 60 | - [+14 more](https://github.com/papandreou/node-inkscape/compare/v2.0.0...v2.1.0) 61 | 62 | ### v2.0.0 (2018-10-14) 63 | 64 | #### Pull requests 65 | 66 | - [#12](https://github.com/papandreou/node-inkscape/pull/12) Upgrade mocha to version 5.2.0 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 67 | - [#13](https://github.com/papandreou/node-inkscape/pull/13) Upgrade sinon to version 6.3.5 ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 68 | 69 | #### Commits to master 70 | 71 | - [Update async to version 2.6.1](https://github.com/papandreou/node-inkscape/commit/35215e4a210c3d5c27789e86c6143fee83582605) ([depfu[bot]](mailto:depfu[bot]@users.noreply.github.com)) 72 | - [Fix indentation glitch in package.json](https://github.com/papandreou/node-inkscape/commit/104c49ca32e50a07fa4e60778aa6fe38694891de) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 73 | - [Use Buffer.from instead of new Buffer in test suite, avoiding deprecation warning](https://github.com/papandreou/node-inkscape/commit/99d8c1ba97b872f2dc0d8d68f19b845e4d3236a3) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 74 | - [Fix unused require](https://github.com/papandreou/node-inkscape/commit/f1bd3c5e026123dea9534b93036bcf47edd792dc) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 75 | - [eslint --fix .](https://github.com/papandreou/node-inkscape/commit/a9357c092e3d171206e9bc59c05ffef4e20992de) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 76 | - [+18 more](https://github.com/papandreou/node-inkscape/compare/v1.2.0...v2.0.0) 77 | 78 | ### v1.2.0 (2017-11-07) 79 | 80 | - [Only add --without-gui when non --verb=... args are present](https://github.com/papandreou/node-inkscape/commit/51ba1a26e66d0a2def94dc706881f13883b5af47) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com)) 81 | - [LICENSE: Fix author's name :\)](https://github.com/papandreou/node-inkscape/commit/0407d6b640b5c77e84062befc8356ace8a7b5a4a) ([Andreas Lind](mailto:andreas@one.com)) 82 | - [package.json: Use a valid SPDX license identifier](https://github.com/papandreou/node-inkscape/commit/5309111153b1a94f0be9df6086b146545db3ce80) ([Andreas Lind](mailto:andreas@one.com)) 83 | 84 | ### v1.1.1 (2017-04-27) 85 | 86 | - [Update gettemporaryfilepath to 1.0.0, silencing os.tmpDir warning](https://github.com/papandreou/node-inkscape/commit/a721466900d4b25e6f0620b677a6f58ee06a0dd0) ([Andreas Lind](mailto:andreas@one.com)) 87 | 88 | ### v1.1.0 (2016-12-03) 89 | 90 | - [Clean up, increase test coverage.](https://github.com/papandreou/node-inkscape/commit/d9a7fa9d26f200b0bc5368c7ec36b3a7c50c9898) ([Andreas Lind](mailto:andreas@one.com)) 91 | - [Correcting syntax errors](https://github.com/papandreou/node-inkscape/commit/a49063ad265e64f63fe63e5f955e9d576c96c0a8) ([David Tekan](mailto:tekda@digitus.itk.ppke.hu)) 92 | - [Adding input file to the end](https://github.com/papandreou/node-inkscape/commit/16e00195f9a395b793b95e74aec6eec97a3bf062) ([David Tekan](mailto:tekda@digitus.itk.ppke.hu)) 93 | - [Adding other parameters also](https://github.com/papandreou/node-inkscape/commit/4fb9f7506f39657c485fdf860243d2252d67fead) ([David Tekan](mailto:tekda@digitus.itk.ppke.hu)) 94 | - [Extending the documentation with the import parameter](https://github.com/papandreou/node-inkscape/commit/e63a4e5fc67cd184a7c5ee37afdc6db42371b254) ([David Tekan](mailto:tekda@digitus.itk.ppke.hu)) 95 | - [+2 more](https://github.com/papandreou/node-inkscape/compare/v1.0.0...v1.1.0) 96 | 97 | ### v1.0.0 (2016-03-31) 98 | 99 | - [Implement destroy method, harden the code against leaking resources when errors occur.](https://github.com/papandreou/node-inkscape/commit/9a75f0029af15fbeba67f9b4b67fd40e85b21031) ([Andreas Lind](mailto:andreas@one.com)) 100 | - [.jshintrc: Disable es3 mode.](https://github.com/papandreou/node-inkscape/commit/baad8ed344c635a646a17ae56583f643b66126fc) ([Andreas Lind](mailto:andreas@one.com)) 101 | - [Fixed author's name :\)](https://github.com/papandreou/node-inkscape/commit/9932b5a293af5e554f1a7055168c4cfead6462bf) ([Andreas Lind](mailto:andreas@one.com)) 102 | - [Add mocha.opts with a higher timeout. Hopefully this will fix the build.](https://github.com/papandreou/node-inkscape/commit/5064b853024aac40d13d36d84f12bba1bffbb66c) ([Andreas Lind](mailto:andreas@one.com)) 103 | - [Fixed the README badges.](https://github.com/papandreou/node-inkscape/commit/8bc0022d1dcef8b1886599d43ca0a816f34acee2) ([Andreas Lind](mailto:andreas@one.com)) 104 | - [+1 more](https://github.com/papandreou/node-inkscape/compare/v0.0.5...v1.0.0) 105 | 106 | ### v0.0.5 (2014-05-28) 107 | 108 | - [Update mocha to 1.20.0 and unexpected to 3.2.0.](https://github.com/papandreou/node-inkscape/commit/2ecd88211a26b7e11ee94c5bac416fd77a311333) ([Andreas Lind Petersen](mailto:andreas@one.com)) 109 | - [Avoid emitting the 'error' event more than once.](https://github.com/papandreou/node-inkscape/commit/6f60b78ab9af058068f36f100143ba500eea9417) ([Andreas Lind Petersen](mailto:andreas@one.com)) 110 | - [Expose the command line as a public property.](https://github.com/papandreou/node-inkscape/commit/594dc4173f974d25feb67df8b111ac6bb0bf2093) ([Andreas Lind Petersen](mailto:andreas@one.com)) 111 | - [Replace expect.js with unexpected and update mocha.](https://github.com/papandreou/node-inkscape/commit/4a792af9de87e05e1fc62c056f0cdff2b0f8f74a) ([Andreas Lind Petersen](mailto:andreas@one.com)) 112 | 113 | ### v0.0.4 (2014-03-24) 114 | 115 | - [Forward errors from the childProcess \(such as ENOENT when the binary is missing\) to the Inkscape instance.](https://github.com/papandreou/node-inkscape/commit/4025d6ac4dd1f586d68d1f132dd1a747134f9a16) ([Andreas Lind Petersen](mailto:andreas@one.com)) 116 | - [package.json: Fixed typo in keywords.](https://github.com/papandreou/node-inkscape/commit/cbc45b21e81bc40e4c67989b1b46125bd74a1f23) ([Andreas Lind Petersen](mailto:andreas@one.com)) 117 | - [package.json: Indent with 2 spaces.](https://github.com/papandreou/node-inkscape/commit/8baa157ffa999cf95f31984cf59a205a3bdcfce5) ([Andreas Lind Petersen](mailto:andreas@one.com)) 118 | 119 | ### v0.0.3 120 | 121 | - [Release 0.0.3.](https://github.com/papandreou/node-inkscape/commit/c873cf375d3decb58ea319e77d883bc538c8d0df) ([Andreas Lind Petersen](mailto:andreas@one.com)) 122 | - [Fixed cleaning up of temporary files on disc.](https://github.com/papandreou/node-inkscape/commit/3b28faa7d969a7442db92d3ee7105c7c4e9fc5f3) ([Andreas Lind Petersen](mailto:andreas@one.com)) 123 | 124 | ### v0.0.2 125 | 126 | - [Release 0.0.2.](https://github.com/papandreou/node-inkscape/commit/1fdd1940fcb305137ca992415b3de3f79077cab1) ([Andreas Lind Petersen](mailto:andreas@one.com)) 127 | - [Improved error messages when inkscape doesn't produce an output file or exits with a non-zero exit code.](https://github.com/papandreou/node-inkscape/commit/cae3998081baa0675109cadc95cbeb98008fd443) ([Andreas Lind Petersen](mailto:andreas@one.com)) 128 | 129 | ### v0.0.1 130 | 131 | - [Initial commit, release v0.0.1.](https://github.com/papandreou/node-inkscape/commit/0c395b67f62e15735991674d49da98abc1a4664b) ([Andreas Lind Petersen](mailto:andreas@one.com)) 132 | -------------------------------------------------------------------------------- /test/Inkscape.js: -------------------------------------------------------------------------------- 1 | const expect = require('unexpected') 2 | .clone() 3 | .use(require('unexpected-stream')) 4 | .use(require('unexpected-sinon')); 5 | const sinon = require('sinon'); 6 | const Inkscape = require('../lib/Inkscape'); 7 | const pathModule = require('path'); 8 | const fs = require('fs'); 9 | 10 | describe('Inkscape', () => { 11 | it('should allow for explicitly specifying an output type', () => { 12 | expect( 13 | new Inkscape([], { outputFormat: 'foo' }).outputFormat, 14 | 'to equal', 15 | 'foo' 16 | ); 17 | }); 18 | 19 | it('should allow for explicitly specifying an input type', () => { 20 | expect( 21 | new Inkscape([], { inputFormat: 'bar' }).inputFormat, 22 | 'to equal', 23 | 'bar' 24 | ); 25 | }); 26 | 27 | it('should set input/output file paths when only inputFormat is defined', () => { 28 | const sut = new Inkscape([], { inputFormat: 'bar' }); 29 | expect(sut.inkscapeInputFilePath, 'not to equal', undefined); 30 | expect(sut.inkscapeOutputFilePath, 'not to equal', undefined); 31 | }); 32 | 33 | it('should set input/output file paths when only outputFormat is defined', () => { 34 | const sut = new Inkscape([], { outputFormat: 'bar' }); 35 | expect(sut.inkscapeOutputFilePath, 'not to equal', undefined); 36 | expect(sut.inkscapeInputFilePath, 'not to equal', undefined); 37 | }); 38 | 39 | it('should set input/output file paths when inputFormat and outputFormat are defined', () => { 40 | const sut = new Inkscape([], { outputFormat: 'bar', inputFormat: 'foo' }); 41 | expect(sut.inkscapeOutputFilePath, 'not to equal', undefined); 42 | expect(sut.inkscapeInputFilePath, 'not to equal', undefined); 43 | }); 44 | 45 | it('should detect the output format as png if --export-type=png is specified', () => { 46 | expect(new Inkscape(['--export-type=png']).outputFormat, 'to equal', 'png'); 47 | }); 48 | 49 | it('should detect the output format as pdf if --export-type=pdf is specified', () => { 50 | expect(new Inkscape(['--export-type=pdf']).outputFormat, 'to equal', 'pdf'); 51 | }); 52 | 53 | it('should detect the output format as eps if --export-type=eps is specified', () => { 54 | expect(new Inkscape(['--export-type=eps']).outputFormat, 'to equal', 'eps'); 55 | }); 56 | 57 | it('should detect the output format as ps if --export-type=ps is specified', () => { 58 | expect(new Inkscape(['--export-type=ps']).outputFormat, 'to equal', 'ps'); 59 | }); 60 | 61 | it('should detect the output format as svg if --export-type=svg is specified', () => { 62 | expect(new Inkscape(['--export-type=svg']).outputFormat, 'to equal', 'svg'); 63 | }); 64 | 65 | it('should detect the output format as svg if --export-plain-svg is specified', () => { 66 | expect( 67 | new Inkscape(['--export-plain-svg']).outputFormat, 68 | 'to equal', 69 | 'svg' 70 | ); 71 | }); 72 | 73 | it('should inject --export-plain-svg argument when -l is specified', () => { 74 | expect( 75 | new Inkscape(['-l']).inkscapeArgs, 76 | 'to contain', 77 | '--export-plain-svg' 78 | ); 79 | }); 80 | 81 | it('should reject -p argument', () => { 82 | expect( 83 | () => new Inkscape(['-p']).inkscapeArgs, 84 | 'to throw', 85 | 'Internal error: Unable to parse switch: -p' 86 | ); 87 | }); 88 | 89 | it('should set default PNG arguments when non were supplied', () => { 90 | const inkscape = new Inkscape(); 91 | 92 | expect(inkscape.outputFormat, 'to equal', 'png'); 93 | expect(inkscape.inkscapeArgs, 'to satisfy', [ 94 | '--export-type=png', 95 | /^--export-filename=/, 96 | expect.it('to be a string'), 97 | ]); 98 | }); 99 | 100 | it('should produce a PNG when run without arguments', () => { 101 | const inkscape = new Inkscape(); 102 | return expect( 103 | fs 104 | .createReadStream(pathModule.resolve(__dirname, 'test.svg')) 105 | .pipe(inkscape), 106 | 'to yield output satisfying when decoded as', 107 | 'binary', 108 | 'to match', 109 | /^\x89PNG/ 110 | ); 111 | }); 112 | 113 | it('should produce an SVG with the -l argument', () => { 114 | const inkscape = new Inkscape(['-l']); 115 | 116 | return expect( 117 | fs 118 | .createReadStream(pathModule.resolve(__dirname, 'test.svg')) 119 | .pipe(inkscape), 120 | 'to yield output satisfying when decoded as', 121 | 'utf-8', 122 | 'to satisfy', 123 | expect.it('to begin with', ' { 128 | const inkscape = new Inkscape(); 129 | 130 | function fail() { 131 | throw new Error('Inkscape emitted data while it was paused!'); 132 | } 133 | inkscape.pause(); 134 | inkscape.on('data', fail).on('error', () => {}); 135 | 136 | expect(inkscape.outputFormat, 'to equal', 'png'); 137 | fs.createReadStream(pathModule.resolve(__dirname, 'test.svg')).pipe( 138 | inkscape 139 | ); 140 | 141 | return expect.promise((run) => { 142 | setTimeout( 143 | run(() => { 144 | inkscape.removeListener('data', fail); 145 | 146 | inkscape.resume(); 147 | return expect(inkscape, 'to yield output satisfying', { 148 | length: expect.it('to be greater than', 0), 149 | }); 150 | }), 151 | 1000 152 | ); 153 | }); 154 | }); 155 | 156 | it('should emit an error if an invalid image is processed', (done) => { 157 | const inkscape = new Inkscape(); 158 | 159 | inkscape 160 | .on('error', () => { 161 | done(); 162 | }) 163 | .on('data', (chunk) => { 164 | done(new Error('Inkscape emitted data when an error was expected')); 165 | }) 166 | .on('end', (chunk) => { 167 | done(new Error('Inkscape emitted end when an error was expected')); 168 | }); 169 | 170 | inkscape.end(Buffer.from('qwvopeqwovkqvwiejvq', 'utf-8')); 171 | }); 172 | 173 | it('should emit a single error if an invalid command line is specified', (done) => { 174 | const inkscape = new Inkscape(['-vqve']); 175 | 176 | let seenError = false; 177 | 178 | inkscape 179 | .on('error', () => { 180 | expect( 181 | inkscape.commandLine, 182 | 'to match', 183 | /inkscape -vqve --export-type=png --export-filename=.*?\.png .*?\.svg$/ 184 | ); 185 | if (seenError) { 186 | done(new Error('More than one error event was emitted')); 187 | } else { 188 | seenError = true; 189 | setTimeout(done, 100); 190 | } 191 | }) 192 | .on('data', (chunk) => { 193 | done(new Error('inkscape emitted data when an error was expected')); 194 | }) 195 | .on('end', (chunk) => { 196 | done(new Error('inkscape emitted end when an error was expected')); 197 | }); 198 | 199 | inkscape.end(Buffer.from('qwvopeqwovkqvwiejvq', 'utf-8')); 200 | }); 201 | 202 | describe('#destroy', () => { 203 | describe('when called before the fs.WriteStream is created', () => { 204 | it('should not create the fs.WriteStream or launch the inkscape process', () => { 205 | const inkscape = new Inkscape(); 206 | fs.createReadStream(pathModule.resolve(__dirname, 'test.svg')).pipe( 207 | inkscape 208 | ); 209 | inkscape.destroy(); 210 | return expect.promise((run) => { 211 | setTimeout( 212 | run(() => { 213 | expect(inkscape, 'to satisfy', { 214 | writeStream: expect.it('to be falsy'), 215 | inkscapeProcess: expect.it('to be falsy'), 216 | }); 217 | }), 218 | 10 219 | ); 220 | }); 221 | }); 222 | }); 223 | 224 | describe('when called while the fs.WriteStream is active', () => { 225 | it('should abort the fs.WriteStream and remove the temporary file', () => { 226 | const inkscape = new Inkscape(); 227 | fs.createReadStream(pathModule.resolve(__dirname, 'test.svg')).pipe( 228 | inkscape 229 | ); 230 | 231 | return expect.promise((run) => { 232 | setTimeout( 233 | run(function waitForWriteStream() { 234 | const writeStream = inkscape.writeStream; 235 | if (inkscape.writeStream) { 236 | inkscape.destroy(); 237 | expect(inkscape.writeStream, 'to be falsy'); 238 | sinon.spy(writeStream, 'end'); 239 | sinon.spy(writeStream, 'write'); 240 | setTimeout( 241 | run(() => { 242 | expect( 243 | [writeStream.end, writeStream.write], 244 | 'to have calls satisfying', 245 | [] 246 | ); 247 | }), 248 | 10 249 | ); 250 | } else { 251 | setTimeout(run(waitForWriteStream), 0); 252 | } 253 | }), 254 | 0 255 | ); 256 | }); 257 | }); 258 | }); 259 | 260 | describe('when called while the inkscape process is running', () => { 261 | it('should kill the inkscape process and remove the temporary file', () => { 262 | const inkscape = new Inkscape(); 263 | fs.createReadStream(pathModule.resolve(__dirname, 'test.svg')).pipe( 264 | inkscape 265 | ); 266 | 267 | sinon.spy(fs, 'unlink'); 268 | return expect 269 | .promise((run) => { 270 | setTimeout( 271 | run(function waitForInkscapeProcess() { 272 | const inkscapeProcess = inkscape.inkscapeProcess; 273 | if (inkscape.inkscapeProcess) { 274 | sinon.spy(inkscapeProcess, 'kill'); 275 | expect(inkscape.filesToCleanUp, 'to satisfy', [ 276 | expect.it('to be a string'), 277 | expect.it('to be a string'), 278 | ]); 279 | const filesToCleanUp = [].concat(inkscape.filesToCleanUp); 280 | inkscape.destroy(); 281 | expect( 282 | [inkscapeProcess.kill, fs.unlink], 283 | 'to have calls satisfying', 284 | () => { 285 | inkscapeProcess.kill(); 286 | fs.unlink( 287 | filesToCleanUp[0], 288 | expect.it('to be a function') 289 | ); 290 | fs.unlink( 291 | filesToCleanUp[1], 292 | expect.it('to be a function') 293 | ); 294 | } 295 | ); 296 | expect(inkscape.inkscapeProcess, 'to be falsy'); 297 | } else { 298 | setTimeout(run(waitForInkscapeProcess), 0); 299 | } 300 | }), 301 | 0 302 | ); 303 | }) 304 | .finally(() => { 305 | fs.unlink.restore(); 306 | }); 307 | }); 308 | }); 309 | 310 | describe('when called while streaming from the temporary output file', () => { 311 | it('should kill the inkscape process and remove the temporary output file', () => { 312 | const inkscape = new Inkscape(); 313 | fs.createReadStream(pathModule.resolve(__dirname, 'test.svg')).pipe( 314 | inkscape 315 | ); 316 | inkscape.pause(); 317 | sinon.spy(fs, 'unlink'); 318 | return expect 319 | .promise((run) => { 320 | setTimeout( 321 | run(function waitForReadStream() { 322 | const readStream = inkscape.readStream; 323 | if (readStream) { 324 | sinon.spy(readStream, 'destroy'); 325 | expect(inkscape.inkscapeProcess, 'to be falsy'); 326 | expect(inkscape.filesToCleanUp, 'to satisfy', [ 327 | expect.it('to be a string'), 328 | expect.it('to be a string'), 329 | ]); 330 | const filesToCleanUp = [].concat(inkscape.filesToCleanUp); 331 | inkscape.destroy(); 332 | expect( 333 | [fs.unlink, readStream.destroy], 334 | 'to have calls satisfying', 335 | () => { 336 | readStream.destroy(); 337 | fs.unlink( 338 | filesToCleanUp[0], 339 | expect.it('to be a function') 340 | ); 341 | fs.unlink( 342 | filesToCleanUp[1], 343 | expect.it('to be a function') 344 | ); 345 | } 346 | ); 347 | } else { 348 | setTimeout(run(waitForReadStream), 0); 349 | } 350 | }), 351 | 0 352 | ); 353 | }) 354 | .finally(() => { 355 | fs.unlink.restore(); 356 | }); 357 | }); 358 | }); 359 | }); 360 | 361 | describe('when utilizing verbs', () => { 362 | it('should operate in GUI mode when --verb is specified', () => { 363 | const inkscape = new Inkscape([ 364 | '--verb=EditDeselect', 365 | '--select=layer9', 366 | '--verb=SelectionUnion', 367 | '--verb=EditDelete', 368 | '--verb=FileSave', 369 | '--verb=FileClose', 370 | '--verb=FileQuit', 371 | ]); 372 | expect(inkscape.commandLine, 'to contain', '--with-gui'); 373 | }); 374 | 375 | // Doesn't seem to work on Travis, probably due to no X being installed 376 | if (!process.env.CI) { 377 | it('should treat the input file as the output file (assuming --verb=FileSave)', () => { 378 | const inkscape = new Inkscape([ 379 | '--verb=EditDeselect', 380 | '--select=layer9', 381 | '--verb=SelectionUnion', 382 | '--verb=EditDelete', 383 | '--verb=FileSave', 384 | '--verb=FileClose', 385 | '--verb=FileQuit', 386 | ]); 387 | 388 | return expect( 389 | fs 390 | .createReadStream(pathModule.resolve(__dirname, 'test.svg')) 391 | .pipe(inkscape), 392 | 'to yield output satisfying when decoded as', 393 | 'utf-8', 394 | 'to satisfy', 395 | expect 396 | .it('to begin with', '