From 16da2eee021f191b6967d216024ee3f865ada3da Mon Sep 17 00:00:00 2001 From: Thaddee Tyl Date: Sun, 5 Jan 2014 11:29:58 +0100 Subject: [PATCH] Allow {png,gif,jpg,pdf} output. --- package.json | 3 ++- phantomjs-svg2png.js | 27 +++++++++++++++++++++++++++ server.js | 36 ++++++++++++++++++++++++++++-------- svg-to-img.js | 22 ++++++++++++++++++++++ 4 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 phantomjs-svg2png.js create mode 100644 svg-to-img.js diff --git a/package.json b/package.json index eb67b28..f1e0bbf 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,10 @@ }, "license": "CC0", "dependencies": { - "dot": ">=1.0.2", + "dot": "~1.0.2", "svgo": "~0.4.2", "canvas": "~1.1.2", + "phantomjs": "~1.9.2-6", "es6-promise": "~0.1.1", "camp": "~13.11.9" }, diff --git a/phantomjs-svg2png.js b/phantomjs-svg2png.js new file mode 100644 index 0000000..481b8bf --- /dev/null +++ b/phantomjs-svg2png.js @@ -0,0 +1,27 @@ +var page = require('webpage').create(); +var system = require('system'); +var svg = system.args[1]; +var svgUrl = 'data:image/svg+xml,' + window.encodeURI(svg); +var tmpFile = system.args[2]; + +page.viewportSize = getSvgDimensions(svg); +page.open(svgUrl, function(status) { + if (status !== 'success') { + console.error('Failed to load the following SVG data:'); + console.error(svgUrl); + phantom.exit(1); + } else { + page.render(tmpFile); + phantom.exit(); + } +}); + +function getSvgDimensions(svg) { + var frag = window.document.createElement('div'); + frag.innerHTML = svg; + var svgRoot = frag.querySelector('svg'); + return { + width: parseFloat(svgRoot.getAttribute('width') || 80), + height: parseFloat(svgRoot.getAttribute('height') || 19) + }; +} diff --git a/server.js b/server.js index ad42928..22454de 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,7 @@ var camp = require('camp').start({ port: process.env.PORT||+process.argv[2]||80 }); var badge = require('./badge.js'); +var svg2img = require('./svg-to-img.js'); var serverStartTime = new Date((new Date()).toGMTString()); // Escapes `t` using the format specified in @@ -18,12 +19,14 @@ function escapeFormat(t) { function sixHex(s) { return /^[0-9a-fA-F]{6}$/.test(s); } -camp.route(/^\/(([^-]|--)+)-(([^-]|--)+)-(([^-]|--)+).svg$/, +camp.route(/^\/(([^-]|--)+)-(([^-]|--)+)-(([^-]|--)+).(svg|png|gif|jpg|pdf)$/, function(data, match, end, ask) { var subject = escapeFormat(match[1]); var status = escapeFormat(match[3]); var color = escapeFormat(match[5]); - ask.res.setHeader('Content-Type', 'image/svg+xml'); + var format = match[7]; + + // Cache management. var cacheDuration = (3600*24*1)|0; // 1 day. ask.res.setHeader('Cache-Control', 'public, max-age=' + cacheDuration); if (+(new Date(ask.req.headers['if-modified-since'])) >= +serverStartTime) { @@ -32,6 +35,8 @@ camp.route(/^\/(([^-]|--)+)-(([^-]|--)+)-(([^-]|--)+).svg$/, return; } ask.res.setHeader('Last-Modified', serverStartTime.toGMTString()); + + // Badge creation. try { var badgeData = {text: [subject, status]}; if (sixHex(color)) { @@ -39,16 +44,31 @@ camp.route(/^\/(([^-]|--)+)-(([^-]|--)+)-(([^-]|--)+).svg$/, } else { badgeData.colorscheme = color; } - badge(badgeData, function(res) { - end(null, {template: streamFromString(res)}); - }); + badge(badgeData, makeSend(format, ask.res, end)); } catch(e) { - badge({text: ["error", "bad badge"], colorscheme: "red"}, function(res) { - end(null, {template: streamFromString(res)}); - }); + badge({text: ["error", "bad badge"], colorscheme: "red"}, + makeSend(format, ask.res, end)); } }); +function makeSend(format, askres, end) { + if (format === 'svg') { + return function(res) { sendSVG(res, askres, end); }; + } else { + return function(res) { sendOther(format, res, askres, end); }; + } +} + +function sendSVG(res, askres, end) { + askres.setHeader('Content-Type', 'image/svg+xml'); + end(null, {template: streamFromString(res)}); +} + +function sendOther(format, res, askres, end) { + askres.setHeader('Content-Type', 'image/' + format); + svg2img(res, format, askres); +} + var stream = require('stream'); function streamFromString(str) { var newStream = new stream.Readable(); diff --git a/svg-to-img.js b/svg-to-img.js new file mode 100644 index 0000000..81f8840 --- /dev/null +++ b/svg-to-img.js @@ -0,0 +1,22 @@ +var fs = require('fs'); +var os = require('os'); +var path = require('path'); +var phantom = require('phantomjs'); +var childProcess = require('child_process'); +var phantomScript = path.join(__dirname, 'phantomjs-svg2png.js'); + +module.exports = function (svg, format, out, cb) { + var tmpFile = path.join(os.tmpdir(), + "svg2img-" + (Math.random()*2147483648|0) + "." + format); + // Conversion to PNG happens in the phantom script. + childProcess.execFile(phantom.path, [phantomScript, svg, tmpFile], + function(err, stdout, stderr) { + if (stdout) { console.log(stdout); } + if (stderr) { console.log(stderr); } + if (err != null) { console.error(err.stack); if (cb) { cb(err); } return; } + var inStream = fs.createReadStream(tmpFile); + inStream.pipe(out); + // Remove the temporary file after use. + inStream.on('end', function() { fs.unlink(tmpFile, cb); }); + }); +};