Comparing version 2.1.0 to 3.0.0
@@ -8,13 +8,16 @@ "use strict"; | ||
if (system.args.length !== 4) { | ||
console.error("Usage: converter.js source dest resize"); | ||
var PREFIX = "data:image/svg+xml;base64,"; | ||
if (system.args.length !== 2) { | ||
console.error("Usage: converter.js resize"); | ||
phantom.exit(); | ||
} else { | ||
convert(system.args[1], system.args[2], system.args[3]); | ||
convert(system.args[1]); | ||
} | ||
function convert(source, dest, resize) { | ||
function convert(resize) { | ||
var page = webpage.create(); | ||
var sourceBase64 = system.stdin.readLine(); | ||
page.open(source, function (status) { | ||
page.open(PREFIX + sourceBase64, function (status) { | ||
if (status !== "success") { | ||
@@ -27,39 +30,21 @@ console.error("Unable to load the source file."); | ||
try { | ||
if (resize[0] === "{") { | ||
if (resize !== "undefined") { | ||
resize = JSON.parse(resize); | ||
var width = resize.width; | ||
var height = resize.height; | ||
if (width === undefined || height === undefined) { | ||
var dims = getSvgDimensions(page); | ||
if (width === undefined && height === undefined) { | ||
width = dims.width; | ||
height = dims.height; | ||
} else if (width === undefined) { | ||
var widthScale = height / dims.height; | ||
width = dims.width * widthScale; | ||
} else if (height === undefined) { | ||
var heightScale = width / dims.width; | ||
height = dims.height * heightScale; | ||
} | ||
} | ||
width = Math.round(width); | ||
height = Math.round(height); | ||
page.viewportSize = { | ||
width: width, | ||
height: height | ||
}; | ||
setSvgDimensions(page, width, height); | ||
} else { | ||
var scale = Number(resize); | ||
var dimensions = getSvgDimensions(page); | ||
page.viewportSize = { | ||
width: Math.round(dimensions.width * scale), | ||
height: Math.round(dimensions.height * scale) | ||
}; | ||
if (dimensions.shouldScale) { | ||
page.zoomFactor = scale; | ||
} | ||
setSVGDimensions(page, resize.width, resize.height); | ||
} | ||
var dimensions = getSVGDimensions(page); | ||
if (!dimensions) { | ||
console.error("Width or height could not be determined from either the source file or the supplied " + | ||
"dimensions"); | ||
phantom.exit(); | ||
return; | ||
} | ||
page.viewportSize = { | ||
width: dimensions.width, | ||
height: dimensions.height | ||
}; | ||
} catch (e) { | ||
console.error("Unable to calculate dimensions."); | ||
console.error("Unable to calculate or set dimensions."); | ||
console.error(e); | ||
@@ -70,11 +55,13 @@ phantom.exit(); | ||
// This delay is I guess necessary for the resizing to happen? | ||
setTimeout(function () { | ||
page.render(dest, { format: "png" }); | ||
phantom.exit(); | ||
}, 0); | ||
var result = "data:image/png;base64," + page.renderBase64("PNG"); | ||
system.stdout.write(result); | ||
phantom.exit(); | ||
}); | ||
} | ||
function setSvgDimensions(page, width, height) { | ||
function setSVGDimensions(page, width, height) { | ||
if (width === undefined && height === undefined) { | ||
return; | ||
} | ||
return page.evaluate(function (width, height) { | ||
@@ -84,21 +71,17 @@ /* global document: true */ | ||
var viewBoxWidth = el.viewBox.animVal.width; | ||
var viewBoxHeight = el.viewBox.animVal.height; | ||
var usesViewBox = viewBoxWidth && viewBoxHeight; | ||
if (width !== undefined) { | ||
el.setAttribute("width", width + "px"); | ||
} else { | ||
el.removeAttribute("width"); | ||
} | ||
if (!usesViewBox) { | ||
var bbox = el.getBBox(); | ||
var bX = Math.round(bbox.x); | ||
var bY = Math.round(bbox.y); | ||
var bWidth = Math.round(bbox.width); | ||
var bHeight = Math.round(bbox.height); | ||
el.setAttribute("viewBox", bX + " " + bY + " " + bWidth + " " + bHeight); | ||
if (height !== undefined) { | ||
el.setAttribute("height", height + "px"); | ||
} else { | ||
el.removeAttribute("height"); | ||
} | ||
el.setAttribute("width", width + "px"); | ||
el.setAttribute("height", height + "px"); | ||
}, width, height); | ||
} | ||
function getSvgDimensions(page) { | ||
function getSVGDimensions(page) { | ||
return page.evaluate(function () { | ||
@@ -108,44 +91,25 @@ /* global document: true */ | ||
var el = document.documentElement; | ||
var bbox = el.getBBox(); | ||
var width = parseFloat(el.getAttribute("width")); | ||
var widthIsPercent = /%\s*$/.test(el.getAttribute("width") || ""); // Phantom doesn't have endsWith | ||
var height = parseFloat(el.getAttribute("height")); | ||
var heightIsPercent = /%\s*$/.test(el.getAttribute("height") || ""); | ||
var width = !widthIsPercent && parseFloat(el.getAttribute("width")); | ||
var height = !heightIsPercent && parseFloat(el.getAttribute("height")); | ||
var hasWidthOrHeight = width || height; | ||
if (width && height) { | ||
return { width: width, height: height }; | ||
} | ||
var viewBoxWidth = el.viewBox.animVal.width; | ||
var viewBoxHeight = el.viewBox.animVal.height; | ||
var usesViewBox = viewBoxWidth && viewBoxHeight; | ||
if (usesViewBox) { | ||
if (widthIsPercent) { | ||
width = viewBoxWidth / 100 * width; | ||
} | ||
if (heightIsPercent) { | ||
height = viewBoxHeight / 100 * height; | ||
} | ||
if (width && !height) { | ||
height = width * viewBoxHeight / viewBoxWidth; | ||
} | ||
if (height && !width) { | ||
width = height * viewBoxWidth / viewBoxHeight; | ||
} | ||
if (!width && !height) { | ||
width = viewBoxWidth; | ||
height = viewBoxHeight; | ||
} | ||
if (width && viewBoxHeight) { | ||
return { width: width, height: width * viewBoxHeight / viewBoxWidth }; | ||
} | ||
if (!width) { | ||
width = bbox.width + bbox.x; | ||
if (height && viewBoxWidth) { | ||
return { width: height * viewBoxWidth / viewBoxHeight, height: height }; | ||
} | ||
if (!height) { | ||
height = bbox.height + bbox.y; | ||
} | ||
var shouldScale = (hasWidthOrHeight && !widthIsPercent && !heightIsPercent) || !usesViewBox; | ||
return { width: width, height: height, shouldScale: shouldScale }; | ||
return null; | ||
}); | ||
} |
"use strict"; | ||
const path = require("path"); | ||
const childProcess = require("pn/child_process"); | ||
var path = require("path"); | ||
var execFile = require("child_process").execFile; | ||
const phantomjsCmd = require("phantomjs").path; | ||
const converterFileName = path.resolve(__dirname, "./converter.js"); | ||
var phantomjsCmd = require("phantomjs").path; | ||
var converterFileName = path.resolve(__dirname, "./converter.js"); | ||
const PREFIX = "data:image/png;base64,"; | ||
module.exports = function svgToPng(sourceFileName, destFileName, resize, cb) { | ||
if (typeof resize === "function") { | ||
cb = resize; | ||
resize = 1.0; | ||
} else if (typeof resize === "object") { | ||
resize = JSON.stringify(resize); | ||
module.exports = (sourceBuffer, resize) => { | ||
const cp = childProcess.execFile(phantomjsCmd, getPhantomJSArgs(resize), { maxBuffer: Infinity }); | ||
writeBufferInChunks(cp.stdin, sourceBuffer); | ||
return cp.promise.then(processResult); | ||
}; | ||
module.exports.sync = (sourceBuffer, resize) => { | ||
const result = childProcess.spawnSync(phantomjsCmd, getPhantomJSArgs(resize), { | ||
input: sourceBuffer.toString("base64") | ||
}); | ||
return processResult(result); | ||
} | ||
function getPhantomJSArgs(resize) { | ||
return [ | ||
converterFileName, | ||
resize === undefined ? "undefined" : JSON.stringify(resize) | ||
]; | ||
} | ||
function writeBufferInChunks(writableStream, buffer) { | ||
const asString = buffer.toString("base64"); | ||
const INCREMENT = 1024; | ||
writableStream.cork(); | ||
for (let offset = 0; offset < asString.length; offset += INCREMENT) { | ||
writableStream.write(asString.substring(offset, offset + INCREMENT)); | ||
} | ||
writableStream.end("\n"); // so that the PhantomJS side can use readLine() | ||
} | ||
var args = [converterFileName, sourceFileName, destFileName, resize]; | ||
execFile(phantomjsCmd, args, function (err, stdout, stderr) { | ||
if (err) { | ||
cb(err); | ||
} else if (stdout.length > 0) { // PhantomJS always outputs to stdout. | ||
cb(new Error(stdout.toString().trim())); | ||
} else if (stderr.length > 0) { // But hey something else might get to stderr. | ||
cb(new Error(stderr.toString().trim())); | ||
} else { | ||
cb(null); | ||
} | ||
}); | ||
}; | ||
function processResult(result) { | ||
const stdout = result.stdout.toString(); | ||
if (stdout.startsWith(PREFIX)) { | ||
return new Buffer(stdout.substring(PREFIX.length), "base64"); | ||
} | ||
if (stdout.length > 0) { | ||
// PhantomJS always outputs to stdout. | ||
throw new Error(stdout.replace(/\r/g, "").trim()); | ||
} | ||
const stderr = result.stderr.toString(); | ||
if (stderr.length > 0) { | ||
// But hey something else might get to stderr. | ||
throw new Error(stderr.replace(/\r/g, "").trim()); | ||
} | ||
throw new Error("No data received from the PhantomJS child process"); | ||
} |
{ | ||
"name": "svg2png", | ||
"description": "A SVG to PNG converter, using PhantomJS", | ||
"version": "2.1.0", | ||
"description": "Converts SVGs to PNGs, using PhantomJS", | ||
"version": "3.0.0", | ||
"author": "Domenic Denicola <d@domenic.me> (https://domenic.me)", | ||
@@ -9,4 +9,6 @@ "license": "WTFPL", | ||
"main": "lib/svg2png.js", | ||
"bin": "bin/svg2png-cli.js", | ||
"files": [ | ||
"lib/" | ||
"lib/", | ||
"bin/" | ||
], | ||
@@ -19,9 +21,14 @@ "scripts": { | ||
"dependencies": { | ||
"phantomjs": "^1.9.18" | ||
"phantomjs": "^1.9.19", | ||
"pn": "^1.0.0", | ||
"yargs": "^3.31.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.4.1", | ||
"chai-as-promised": "^5.2.0", | ||
"jshint": "^2.8.0", | ||
"mocha": "^2.3.3" | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^2.3.4", | ||
"rimraf": "^2.5.0" | ||
} | ||
} |
@@ -6,30 +6,27 @@ # SVG-to-PNG Converter Using PhantomJS | ||
```js | ||
svg2png("source.svg", "dest.png", function (err) { | ||
// PNGs for everyone! | ||
}); | ||
const pn = require("pn"); // https://www.npmjs.com/package/pn | ||
const svg2png = require("svg2png"); | ||
pn.readFile("source.svg") | ||
.then(svg2png) | ||
.then(buffer => fs.writeFile("dest.png", buffer)) | ||
.catch(e => console.error(e)); | ||
``` | ||
Maybe you need to scale the image while converting? We can do that too: | ||
In the above example, we use the `width` and `height` attributes specified in the SVG file to automatically determine the size of the SVG. You can also explicitly set the size: | ||
```js | ||
svg2png("source.svg", "dest.png", 1.2, function (err) { | ||
// 1.2×-sized PNGs for everyone! | ||
}); | ||
svg2png(sourceBuffer, { width: 300, height: 400 }) | ||
.then(buffer => ...) | ||
.catch(e => console.error(e)); | ||
``` | ||
The scale factor is relative to the SVG's `viewbox` or `width`/`height` attributes, for the record. | ||
Maybe you need an image with exact dimensions: | ||
This is especially useful for images without `width` or `height`s. You can even specify just one of them and (if the image has an appropriate `viewBox`) the other will be set to scale. | ||
```js | ||
svg2png("source.svg", "dest.png", { width: 200, height: 150 }, function (err) { | ||
// 200x150 pixel sized PNGs for everyone! | ||
}); | ||
``` | ||
## Sync variant | ||
The image will be centered and zoomed to best-fit but not stretched. You can also provide just a single dimension and the other one will be inferred automatically: | ||
There's also a sync variant, for use in your shell scripts: | ||
```js | ||
svg2png("source.svg", "dest.png", { width: 300 }, function (err) { | ||
// 300 pixel-wide PNGs for everyone! | ||
}); | ||
const outputBuffer = svg2png.sync(sourceBuffer, optionalWidthAndOrHeight); | ||
``` | ||
@@ -39,15 +36,38 @@ | ||
svg2png is built on the latest in [PhantomJS][] technology to render your SVGs using a headless WebKit instance. I have | ||
found this to produce much more accurate renderings than other solutions like GraphicsMagick or Inkscape. Plus, it's | ||
easy to install cross-platform due to the excellent [phantomjs][package] npm package—you don't even need to have | ||
PhantomJS in your `PATH`. | ||
svg2png is built on the latest in [PhantomJS](http://phantomjs.org/) technology to render your SVGs using a headless WebKit instance. I have found this to produce much more accurate renderings than other solutions like GraphicsMagick or Inkscape. Plus, it's easy to install cross-platform due to the excellent [phantomjs](https://npmjs.org/package/phantomjs) npm package—you don't even need to have PhantomJS in your `PATH`. | ||
[PhantomJS]: http://phantomjs.org/ | ||
[package]: https://npmjs.org/package/phantomjs | ||
Rendering isn't perfect; we have a number of issues that are [blocked on PhantomJS](https://github.com/domenic/svg2png/labels/blocked%20on%20phantomjs) getting its act together and releasing a cross-platform version with updated WebKit. | ||
## Exact resizing behavior | ||
Previous versions of svg2png attempted to infer a good size based on the `width`, `height`, and `viewBox` attributes. As of our 3.0 release, we attempt to stick as close to the behavior of loading a SVG file in your browser as possible. The rules are: | ||
- Any `width` or `height` attributes that are in percentages are ignored and do not count for the subsequent rules. | ||
- The dimensions option `{ width, height }` overrides any `width` or `height` attributes in the SVG file, including for the subsequent rules. If a key is missing from the dimensions object (i.e. `{ width }` or `{ height }`) the corresponding attribute in the SVG file will be deleted. | ||
- `with` and `height` attributes without a `viewBox` attribute cause the output to be of those dimensions; this might crop the image or expand it with empty space to the bottom and to the right. | ||
- `width` and/or `height` attributes with a `viewBox` attribute cause the image to scale to those dimensions. If the ratio does not match the `viewBox`'s aspect ratio, the image will be expanded and centered with empty space in the extra dimensions. When a `viewBox` is present, one of either `width` or `height` can be omitted, with the missing one inferred from the `viewBox`'s aspect ratio. | ||
- When there are neither `width` nor `height` attributes, the promise rejects. | ||
One thing to note is that svg2png does not and cannot stretch your images to new aspect ratios. | ||
## CLI | ||
[@skyzyx][] made [a CLI version][] of this; you should go check it out if you're into using the command line. | ||
This package comes with a CLI version as well; you can install it globally with `npm install svg2png -g`. Use it as follows: | ||
[@skyzyx]: https://github.com/skyzyx | ||
[a CLI version]: https://github.com/skyzyx/svg2png-cli | ||
``` | ||
$ svg2png --help | ||
Converts SVGs to PNGs, using PhantomJS | ||
svg2png input.svg [--output=output.png] [--width=300] [--height=150] | ||
Options: | ||
-o, --output The output filename; if not provided, will be inferred [string] | ||
-w, --width The output file width, in pixels [string] | ||
-h, --height The output file height, in pixels [string] | ||
--help Show help [boolean] | ||
--version Show version number [boolean] | ||
``` | ||
## Node.js requirements | ||
svg2png uses the latest in ES2015 features, and as such requires a recent version of Node.js. Only the 5.x series is supported; anything lower than 5.0.0 which happens to work might break in any patch revision of svg2png and should not be used. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
11855
6
170
72
3
6
2
+ Addedpn@^1.0.0
+ Addedyargs@^3.31.0
+ Addedcamelcase@2.1.1(transitive)
+ Addedcliui@3.2.0(transitive)
+ Addedcode-point-at@1.1.0(transitive)
+ Addeddecamelize@1.2.0(transitive)
+ Addedinvert-kv@1.0.0(transitive)
+ Addedis-fullwidth-code-point@1.0.0(transitive)
+ Addedlcid@1.0.0(transitive)
+ Addednumber-is-nan@1.0.1(transitive)
+ Addedos-locale@1.4.0(transitive)
+ Addedpn@1.1.0(transitive)
+ Addedstring-width@1.0.2(transitive)
+ Addedwindow-size@0.1.4(transitive)
+ Addedwrap-ansi@2.1.0(transitive)
+ Addedy18n@3.2.2(transitive)
+ Addedyargs@3.32.0(transitive)
Updatedphantomjs@^1.9.19