I’ve been using canvg in the past to convert SVG to PNG, but as you’ll see from the link, it doesn’t support all of SVG capabilities. If you use D3 or any library that likes to work in SVG, then you may come across something that doesn’t work – in my case it was textPath.
Ideally then, I’d like to use the browser to render the SVG instead of canvg, then somehow get at the rasterized result to make a png image out of it, then pass it to Google Sheets. Here’s how.
The Dom has an API method to create an object – createObjectURL(svgtext). The API differs from browser to browser, but here’s how to find it.
var domUrl = window.URL || window.webkitURL || window; if (!domUrl) { throw new Error("(browser doesnt support this)") }The idea is to create that object from the svg code, load it to an image element, then write that image to canvas. We can then use toDataURL() to make a base64 encoded PNG image and we’re done.
First step is to create the url, but that will fail unless the svg code has its namespace defined, so we’ll poke the svg code
// it needs a namespace if (!svgText.match(/xmlns=\"/mi)){ svgText = svgText.replace ('<svg ','<svg xmlns="http://www.w3.org/2000/svg" ') ; }make a blob
// make a blob from the svg var svg = new Blob([svgText], { type: "image/svg+xml;charset=utf-8" });and then create the URL from the blob
var url = domUrl.createObjectURL(svg);next we’ll need a canvas, but we need to deduce the canvas size required by digging around in the svg code.
// figure out the height and width from svg text var match = svgText.match(/height=\"(\d+)/m); var height = match && match[1] ? parseInt(match[1],10) : 200; var match = svgText.match(/width=\"(\d+)/m); var width = match && match[1] ? parseInt(match[1],10) : 200;now we can create a canvas element to temporarily use. I’m also going allow some margin space to be passed over as an argument, so the canvas will need to big enough for the image plus any padding round it.
// create a canvas element to pass through var canvas = document.createElement("canvas"); canvas.width = height+margin*2; canvas.height = width+margin*2; var ctx = canvas.getContext("2d");I’ll need a new image, and when it’s loaded, I can write it to my canvas. As an additional piece of sugar, I’ll allow the image to have an optional background fill – useful if you have a margin or transparent input. If it does have one, then I need a second canvas that can be used to styling to which the first one is written. The whole thing is wrapped in a promise, so it can be resolved passing over the toDataUrl() of the final canvas representation of the svg image.
// create a new image to hold it the converted type var img = new Image; // when the image is loaded we can get it as base64 url img.onload = function() { // draw it to the canvas ctx.drawImage(this, margin, margin); // if it needs some styling, we need a new canvas if (fill) { var styled = document.createElement("canvas"); styled.width = canvas.width; styled.height = canvas.height; var styledCtx = styled.getContext("2d"); styledCtx.save(); styledCtx.fillStyle = fill; styledCtx.fillRect(0,0,canvas.width,canvas.height); styledCtx.strokeRect(0,0,canvas.width,canvas.height); styledCtx.restore(); styledCtx.drawImage (canvas, 0,0); canvas = styled; } // we don't need the original any more domUrl.revokeObjectURL(url); // now we can resolve the promise, passing the base64 url resolve(canvas.toDataURL()); }; // load the image img.src = url;The whole thing can be used like this.
svgToPng (svgCode , marginSize, canvasFillColor) .then (function(data) { // do something with the encode b64 image }) .catch (function (err) { // do something with the error });
Page Content
hide
The code
I’ll report on browser compatibility as I try them out, or let me know if you try them out.
/** * converts an svg string to base64 png using the domUrl * @param {string} svgText the svgtext * @param {number} [margin=0] the width of the border - the image size will be height+margin by width+margin * @param {string} [fill] optionally backgrund canvas fill * @return {Promise} a promise to the bas64 png image */ var svgToPng = function (svgText, margin,fill) { // convert an svg text to png using the browser return new Promise(function(resolve, reject) { try { // can use the domUrl function from the browser var domUrl = window.URL || window.webkitURL || window; if (!domUrl) { throw new Error("(browser doesnt support this)") } // figure out the height and width from svg text var match = svgText.match(/height=\"(\d+)/m); var height = match && match[1] ? parseInt(match[1],10) : 200; var match = svgText.match(/width=\"(\d+)/m); var width = match && match[1] ? parseInt(match[1],10) : 200; margin = margin || 0; // it needs a namespace if (!svgText.match(/xmlns=\"/mi)){ svgText = svgText.replace ('<svg ','<svg xmlns="http://www.w3.org/2000/svg" ') ; } // create a canvas element to pass through var canvas = document.createElement("canvas"); canvas.width = height+margin*2; canvas.height = width+margin*2; var ctx = canvas.getContext("2d"); // make a blob from the svg var svg = new Blob([svgText], { type: "image/svg+xml;charset=utf-8" }); // create a dom object for that image var url = domUrl.createObjectURL(svg); // create a new image to hold it the converted type var img = new Image; // when the image is loaded we can get it as base64 url img.onload = function() { // draw it to the canvas ctx.drawImage(this, margin, margin); // if it needs some styling, we need a new canvas if (fill) { var styled = document.createElement("canvas"); styled.width = canvas.width; styled.height = canvas.height; var styledCtx = styled.getContext("2d"); styledCtx.save(); styledCtx.fillStyle = fill; styledCtx.fillRect(0,0,canvas.width,canvas.height); styledCtx.strokeRect(0,0,canvas.width,canvas.height); styledCtx.restore(); styledCtx.drawImage (canvas, 0,0); canvas = styled; } // we don't need the original any more domUrl.revokeObjectURL(url); // now we can resolve the promise, passing the base64 url resolve(canvas.toDataURL()); }; // load the image img.src = url; } catch (err) { reject('failed to convert svg to png ' + err); } }); };
Why not join our forum, follow the blog or follow me on twitter to ensure you get updates when they are available.