svgink v0.4.0
svgink: Efficiently convert SVG files to PDF or PNG via Inkscape
Table of Contents
Overview
How many times have you clicked File menu / Save a Copy / Save as type / Portable Document Format (*.pdf) / Save / OK / Replace (in Inkscape, or similar sequences in other drawing programs), after every edit you make to an SVG drawing?
svgink makes it easy to convert to PDF any changed SVG files:
svgink --pdf *.svg
# Shorthand:
svgink -p *.svgFor longer figure drawing sessions, you can keep svgink running
and watching for any changes to SVG files, automatically converting
any that you change:
# Watch for changed and new SVG files and autoconvert to PDF.
# (Quotes are necessary to enable detection of new SVG files.)
svgink --watch --pdf '*.svg'
# Watch for changed and new SVG files in any recursive subdirectory
svgink --watch --pdf '**/*.svg'
# Shorthand:
svgink -w -p '*.svg'
svgink -w -p '**/*.svg'
# Press Ctrl+C to abort a running svgink.Usage
Inkscape can convert an SVG file into PDF or PNG using its command-line interface like so:
# One file with custom output filename/directory:
inkscape --export-filename=output.pdf input.svg
# Multiple files with output filename/directory matching input:
inkscape --export-type=pdf filename1.svg filename2.svgThe svgink command-line tool provides a simpler command-line tool to
do the same thing, in particular evading your having to remember the exact
--export option format.
# Basic use, PDF mode:
svgink --pdf filename1.svg filename2.svg
# Basic use, PNG mode:
svgink --png filename1.svg filename2.svg
# Convert to both PDF and PNG:
svgink --pdf --png filename1.svg filename2.svg
# Shorthands for above three commands:
svgink -p filename1.svg filename2.svg
svgink -P filename1.svg filename2.svg
svgink -p -P filename1.svg filename2.svg
# Custom output directories:
svgink -p -o pdf filename1.svg filename2.svg
svgink -p -P --op pdf --oP png filename1.svg filename2.svg
# Force conversion even if SVG files haven't changed:
svgink -p -f filename1.svg filename2.svg
# Continuously watch for pattern of new or changed SVG files:
svgink --watch --pdf '**/*.svg'Efficiency
A major advantage of svgink is that it quickly converts many SVG files.
First, svgink skips converting SVG files that are older than the
corresponding PDF/PNG file (similar to make).
You can override this behavior via the --force command-line option,
which forces all conversions to be done.
This is useful if you update Inkscape, update svgink, or
a conversion failed and somehow generated a bad file.
(Alternatively, you can touch the relevant SVG files
or rm the relevant PDF/PNG files.)
Second, svgink uses Inkscape's
shell protocol
to run a sequence of conversions with a single Inkscape process.
This is much faster than running Inkscape individually on each SVG file
(especially on Windows, where spawning a process takes seconds),
which is what might happen most naturally with conversions driven by make.
Third, svgink runs multiple Inkscape processes to exploit multicore CPUs.
By default, it runs half as many Inkscape processes as there are logical cores
on your machine (to account for typical hyperthreading which presents n
physical cores as 2 n logical cores).
You can override the number of Inkscape processes to use via -j 4 or similar.
Note that the processes are started in sequence rather than in parallel,
so you may not get full job parallelism unless you have enough jobs.
(Why in sequence?
On Windows, starting many Inkscape processes in parallel slows them all down;
and on Linux, starting Inkscape processes is fast enough to not be a big deal.
This behavior also prevents svgink from starting more Inkscape processes than
necessary, in case all jobs complete faster than the startup process.)
Output Sanitization
svgink also tries to make version control easier with compiled PDF outputs
(which are useful to check in to avoid requiring Inkscape to build).
Normally, Inkscape includes a /CreationDate field in the generated PDF,
so each time you build the PDF files, the files change.
By default, svgink strips this date out, so the generated PDF files
should be identical across multiple runs (assuming matching Inkscape versions).
You can turn off this behavior via the --no-sanitize command-line option.
Watch: Automatic Conversion of Changed SVG Files
svgink provides a "watch" mechanism to continuously convert files
whenever they change. Use this when actively editing SVG files.
# Watch matching files
svgink --watch --pdf 'fig*.svg'
# Watch *.svg in all descendant directories
svgink --watch --pdf '**/*.svg'
# Watch a directory, which implicitly watches *.svg in that directory
svgink --watch --pdf figsUse Ctrl+C to stop watching.
In the first two examples, the quotes prevent the shell from expanding
glob patterns (here, * and **) to the current list of matching files.
Without quotes, svgink will just detect changes to the
initial list of matching files.
Adding quotes allows svgink to also detect a new matching file,
e.g., a newly created fignew.svg.
Globs are resolved via node-glob
which supports notation
such as {this,that}, *drawing*.svg, figs/**/*.svg, etc.
On Windows, the preferred path separator for globs is forward slashes (/);
you can also use backward slashes (\), except when they would serve to escape
glob patterns. For example, figs\* will be treated as the literal filename
figs*, while figs/* will be treated as "all files in directory figs".
To detect when globs might match new files, svgink watches all prefix
directories of matched files. This may fail to detect new matching files in
a directory that previously had no matching files; in this case, touch any
matching directory to trigger rechecking, or restart svgink.
Cleaning Up Generated Files
To clean up PDF/PNG files that would be updated/generated by svgink
(similar to make clean), you can specify the --clean command-line option.
For example:
# Delete filename.{pdf,png}
svgink --clean --pdf --png filename.svg
# Delete fig*.pdf
svgink --clean --pdf fig*.svgInstallation
After installing Node (v12+) and installing Inkscape (v1+), you can install this tool via
npm install -g svginkThis should install an svgink command-line tool on your path.
Command-Line Interface
Run svgink to see the supported command-line options:
Usage: svgink (...options and filenames/directories/globs...)
Filenames or glob patterns should specify SVG files.
Directories implicitly refer to *.svg within the directory.
Optional arguments:
-h / --help Show this help message and exit.
-p / --pdf Convert SVG files to PDF via Inkscape
-P / --png Convert SVG files to PNG via Inkscape
-w / --watch Continuously watch for changed files and convert them
-f / --force Force conversion even if output newer than SVG input
-o DIR / --output DIR Write all output files to directory DIR
--op DIR / --output-pdf DIR Write all .pdf files to directory DIR
--oP DIR / --output-png DIR Write all .png files to directory DIR
--clean Delete PDF/PNG files that would be generated
-i PATH / --inkscape PATH Specify PATH to Inkscape binary
--no-sanitize Don't sanitize PDF output by blanking out /CreationDate
--relative Run jobs with relative paths (default uses absolute)
-j N / --jobs N Run up to N Inkscape jobs in parallelThese options are intentionally similar to SVG Tiler.
API
You can also use svgink from your Node programs. For example,
SVG Tiler uses svgink to convert
generated SVG files to other formats.
First, install svgink as a dependency in your project:
npm install svginkThen you can require('svgink') or import ... from 'svgink'.
Examples
Here is a simple example of converting two files:
import {SVGProcessor} from 'svgink';
const processor = new SVGProcessor();
processor.convert('input1.svg', 'output1.pdf')
.then(() => console.log('converted first file'));
processor.convert('input2.svg', 'output2.pdf');
.then(() => console.log('converted second file'));
await processor.close();
console.log('finished all conversions');Here is a more advanced example of converting a blob specification:
import {SVGProcessor} from 'svgink';
const processor = new SVGProcessor();
process.on('converted', (job) =>
console.log(`converted ${job.input} to ${job.output}`)
);
processor.convertGlob('*.svg', ['pdf', 'png']);
await processor.close();
console.log('finished all conversions');Alternatively, you can access the command-line interface (including all printed messages) like so:
import {SVGProcessor} from 'svgink';
main(['-p', '-j', '4', '-o', 'pdf', '*.svg'])
.then(() => console.log('finished all conversions'));SVGProcessor Class
The main interface to the API is via the SVGProcessor class,
which handles spawning one or more Inkscape processes
to convert one or more files or run Inkscape shell jobs.
Create one with new SVGProcessor
which takes an optional settings object.
The resulting instance provides the following methods:
convertGlob(input, formats)queues conversion job(s) whereinputcan be a filename, a directory name (which means "process all.svgfiles in that directory"), or a glob resolving to SVG files. Globs are resolved via node-glob which supports notation such as{this,that},*drawing*.svg,figs/**/*.svg, etc. The outputformatsshould be"pdf",".pdf","png",".png", another format/extension supported by Inkscape, or an array thereof. To be notified of conversions and/or errors, you should listen to the corresponding events.convertTo(input, formats)queues converting one filename to the specified format(s), followed by sanitizing the output. Theinputfile should be SVG. The outputformatsshould be"pdf",".pdf","png",".png", another format/extension supported by Inkscape, or an array thereof. It returns a promise, or an array of promises ifformatsis an array, where each promise resolves to a{skip, stdout, stderr, input, output, inputAbs, outputAbs}object when the conversion and sanitization are complete. Here eitherskipistruemeaning that conversion was skipped because the input was older than the output andsettings.forcewas false, orstdoutandstderrgive the string contents from Inkscape's stdout and stderr for this job, which you should print to display warnings and/or errors. In addition,inputis the original input filename,outputis the generated output filename withformatextension, andinputAbsandoutputAbsare the corresponding absolute paths (unlesssettings.relativeis true). The promise also has anoutputproperty with the output filename in case it's needed earlier.convert(input, output)queues converting one filename to another, followed by sanitizing the output. The input file should be SVG. The output format is determined from the file extension. It returns a promise which resolves to a{stdout, stderr, skip, input, output, inputAbs, outputAbs}object when the conversion and sanitization are complete. The absolute pathsinputAbsandoutputAbsare generated automatically, unlesssettings.relativeis true.run(job)queues a given job. A job can be a string to send to Inkscape directly, an object with ajobstring property, or an object of the form{input: 'input.svg', output: 'output.pdf', inputAbs: '/full/path/input.svg', outputAbs: '/full/path/output.pdf}(whereinputAbsandoutputAbsare optional), but scheduling a conversion in this way will skip sanitization and force conversion (skip modification time checking). Returns a promise which resolves to a{stdout, stderr}object, along with thejoborinput/outputproperties from the given job, when the job is complete.sanitize(output)optionally sanitizes the given output filename. You could override this method to support custom sanitization behavior. It normally returns a promise.watch(inputs, formats)continuously watches for changes to the filename(s) ininputs, and when they change (and settle from changing), converts the file to all format(s) informats(likeconvertTo). Each input ininputscan be a glob or directory name, as inconvertGlob. To be notified of conversions and/or errors, you should listen to the corresponding events. This API does not currently normalize paths to absolute paths, so if you're going to be changingprocess.cwd(), you should normalize yourself.wait()returns a promise which resolves when all jobs are complete. Only onewait()orclose()should be active at once.close()shuts down Inkscape processes once all conversion jobs added so far are done. (Do not add jobs after callingclose().) It returns a promise which resolves when all jobs are complete (though Inkscape processes may still be shutting down).
Inkscape Class
You can also access the shell interface of a single Inkscape process
via the lower-level Inkscape class.
Create one with new Inkscape
which takes an optional settings object.
The resulting instance provides the following methods/attributes:
open(initialUnref)starts the Inkscape process. It returns a promise which resolves when Inkscape is ready for a job (has output a>prompt).initialUnrefspecifies whether to unref the Inkscape process before it finishes starting; set true for secondary Inkscape processes.run(job)sends a given job to the Inkscape process, and returns a promise which resolves to a{stdout, stderr}object, along with thejoborinput/outputproperties from the given job, when Inkscape finishes the job. This method can be called only when Inkscape is ready (after the promise returned byopen()or the last call torun()has resolved).readyis a Boolean variable indicating whether Inkscape is ready for a new job viarun().close()attempts to gently close the Inkscape process via thequitcommand. If this times out, it kills the process via a signal.
Settings
The constructors for SVGProcessor and Inkscape take a single optional
argument, which is a settings object. It can have the following properties:
force: Whether to force conversion, even if SVG file is older than target.relative: Whether to run jobs with relative paths, or resolve to absolute paths. Absolute paths support changing directories between jobs, so this is the default, but relative paths are shorter and might bypass some limitations (such as no;in a path name, or Cygwin quirks).outputDir: Default directory to output files viaconvertTo. Default =nullwhich means same directory as input.outputDirExt: Object mapping from extensions (.pdfor.png) to directory to such output files viaconvertTo. Defaults =nullwhich means to useoutputDir.inkscape: Path to inkscape. Default searches PATH forinkscape.jobs: Maximum number of Inkscapes to run in parallel. Default = half the number of logical CPUs (= number of physical CPU cores assuming hyperthreading).idle: If an Inkscape process sits idle for this many milliseconds, close it. Default = 1 minute. Set tonullto disable.startTimeout: If an Inkscape fails to start shell for this many milliseconds, fail. Default = 1 minute. Set tonullto disable.quitTimeout: If an Inkscape fails to close for this many milliseconds, kill it. Default = 1 second. Set tonullto disable.settle: Wait for an input file to stop changing for this many milliseconds before watch mode triggers conversion. Default = 1 second.sanitize: Whether to sanitize PDF output by blanking out /CreationDate. Default =true.bufferSize: Buffer size for sanitization. Default = 16KB.
The default settings are given by the defaultSettings export.
If you want to override just some settings,
you should duplicate and modify that object. For example:
import {SVGProcessor, defaultSettings} from 'svgink';
const processor = new SVGProcessor({...defaultSettings, jobs: 4});Alternatively, you can modify defaultSettings to affect all future operations:
import {SVGProcessor, defaultSettings} from 'svgink';
defaultSettings.jobs = 4;
const processor = new SVGProcessor();Events
SVGProcessor is an EventEmitter
supporting the following events:
'input'indicates that an input filename has is about to be converted to one or more formats viaconvertTo. This event is most useful in the context ofconvertGlobto determine which (or how many) filenames matched a glob pattern or directory.'converted'indicates that a file has just been successfully converted and sanitized into a single format. The event has one argument, a{skip, stdout, stderr, input, output, inputAbs, outputAbs}object as resolved fromconvertorconvertTo. In particular:inputis the input filename.outputis the output filename.inputAbsis the input absolute path (unlesssettings.relativeis true).outputAbsis the output absolute path (unlesssettings.relativeis true).skipis a Boolean indicating whether conversion was skipped because the input was older than the output andsettings.forcewas falsestdoutandstderrgive the string contents from Inkscape's stdout and stderr for this job, which you should print to display warnings and/or errors. They are absent ifskipistrue.
'cleaned'indicates that a file has been successfully deleted. The event has one argument, a{skip, input, output, inputAbs, outputAbs}object similarly to a'converted'event. In particular,skipis a Boolean indicating whether cleaning was skipped because the output file did not exist.'ran'indicates that a general Inkscape job executed byrun()has successfully completed. The event has one argument, an object withinputandoutputproperties as well as all properties from the job, as resolved fromrun().'error'indicates that something went wrong. The event has one argument, which is anErrorof some sort (oftenInkscapeError), and may haveinputandoutputproperties indicating the relevant filenames for conversion.
Limitations
Feel free to open a GitHub Issue if any of the following limitations pose an issue for you.
- Assumes Inkscape version 1+
- Several
Inkscape conversion options
are not yet represented by command-line options.
For example:
- PS, EPS, EMF, WMF, XAML conversion
- export-dpi for PNG conversion
- export-area-drawing instead of default export-area-page
- The Inkscape
shell protocol
doesn't support filenames containing
;or starting or ending with spaces. It would be possible to work around this, especially without custom output directories, using the command line directly.
Related Work
There are several other packages and tools for interfacing with Inkscape and/or converting SVG files.
- svg2pdf converts a directory of SVGs
to a directory of PDFs, one Inkscape per job, and using threads instead of
async.
svgink's detection of default number of CPUs is based on svg2pdf's. - svink converts SVGs to PNGs,
with support for lots of options corresponding to Inkscape's CLI,
one Inkscape per job, and using threads (instead of async)
only with multiple export IDs.
svgink's name is inspired by svink's. - inkscape-export converts SVG files to PNG at one or more scales (without parallelism), with additional support for multi-frame animations.
- node-inkscape provides a stream interface to an Inkscape process.
- inkscape-cli provides an JavaScript interface to Inkscape's CLI.
- svgexport renders SVG to PNG/JPEG using Puppeteer.
- svg2 renders SVG to PNG using resvg-js.