├── README.md ├── base64.js ├── butterfly_test.html ├── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | SVG.toDataURL() 2 | =============== 3 | 4 | The missing `SVG.toDataURL()` for your SVG elements. Supports exporting as SVG+XML and PNG. 5 | 6 | See [original paper][svgopen2010] for more information on interfacing between SVG and Canvas. 7 | 8 | Instructions 9 | ------------ 10 | 11 | 1. Include [canvg] if you need PNG support for unsupported browers (see compatibility below). 12 | 2. `` 13 | 3. See `butterfly_test.html` for full example & compability test. 14 | 15 | ```javascript 16 | mySVGelement.toDataURL("image/png", { 17 | callback: function(data) { 18 | myIMGelement.setAttribute("src", data) 19 | } 20 | }) 21 | ``` 22 | 23 | API documentation inside [svg_todataurl.js](svg_todataurl.js). 24 | 25 | Compatibility 26 | ------------- 27 | 28 | Test your browser with our [butterfly test suite](http://sampumon.github.io/SVG.toDataURL/butterfly_test.html). 29 | 30 | Works in all modern browsers. Some browsers require [canvg] for PNG exporting. 31 | 32 | Compatibility as of October 2017. `+` yes, `vv+` yes since version vv, `-` no support yet. 33 | 34 | Browser E x p o r t i n g f o r m a t 35 | SVG+XML PNG/canvg PNG/native 36 | IE 9+ 9+ edge 37 | Chrome + + 33+ ² 38 | Safari       +       +         7.1+ ³ 39 | Firefox     +       +         11+ ¹ 40 | Opera + + + 41 | 42 | ¹ [Allow SVG-as-an-image to be drawn into a canvas without marking it as write-only](https://bugzilla.mozilla.org/show_bug.cgi?id=672013) 43 | ² [Implement SVGImage::hasSingleSecurityOrigin()](https://bugs.webkit.org/show_bug.cgi?id=119492) 44 | ³ [Test Browser Support for SVG to Canvas](http://designashirt.github.io/svg-canvas-tests/) 45 | 46 | Notes 47 | ----- 48 | 49 | * SVG `` elements only work if their `src` is a dataURL. External images, same domain or not, will not be rendered (but won't cause a security exception either). 50 | 51 | Final 52 | ----- 53 | 54 | Licensed with the permissive MIT license, as follows. 55 | 56 | Copyright (c) 2010,2012 Sampu-mon & Mat-san 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy 59 | of this software and associated documentation files (the "Software"), to deal 60 | in the Software without restriction, including without limitation the rights 61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | copies of the Software, and to permit persons to whom the Software is 63 | furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in 66 | all copies or substantial portions of the Software. 67 | 68 | [canvg]:http://code.google.com/p/canvg/ 69 | [svgopen2010]:http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/ 70 | -------------------------------------------------------------------------------- /base64.js: -------------------------------------------------------------------------------- 1 | // http://www.webtoolkit.info/javascript-base64.html 2 | 3 | var Base64 = { 4 | 5 | // private property 6 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 7 | 8 | // public method for encoding 9 | encode : function (input) { 10 | var output = ""; 11 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 12 | var i = 0; 13 | 14 | input = Base64._utf8_encode(input); 15 | 16 | while (i < input.length) { 17 | 18 | chr1 = input.charCodeAt(i++); 19 | chr2 = input.charCodeAt(i++); 20 | chr3 = input.charCodeAt(i++); 21 | 22 | enc1 = chr1 >> 2; 23 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 24 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 25 | enc4 = chr3 & 63; 26 | 27 | if (isNaN(chr2)) { 28 | enc3 = enc4 = 64; 29 | } else if (isNaN(chr3)) { 30 | enc4 = 64; 31 | } 32 | 33 | output = output + 34 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + 35 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 36 | 37 | } 38 | 39 | return output; 40 | }, 41 | 42 | // public method for decoding 43 | decode : function (input) { 44 | var output = ""; 45 | var chr1, chr2, chr3; 46 | var enc1, enc2, enc3, enc4; 47 | var i = 0; 48 | 49 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 50 | 51 | while (i < input.length) { 52 | 53 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 54 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 55 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 56 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 57 | 58 | chr1 = (enc1 << 2) | (enc2 >> 4); 59 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 60 | chr3 = ((enc3 & 3) << 6) | enc4; 61 | 62 | output = output + String.fromCharCode(chr1); 63 | 64 | if (enc3 != 64) { 65 | output = output + String.fromCharCode(chr2); 66 | } 67 | if (enc4 != 64) { 68 | output = output + String.fromCharCode(chr3); 69 | } 70 | 71 | } 72 | 73 | output = Base64._utf8_decode(output); 74 | 75 | return output; 76 | 77 | }, 78 | 79 | // private method for UTF-8 encoding 80 | _utf8_encode : function (string) { 81 | string = string.replace(/\r\n/g,"\n"); 82 | var utftext = ""; 83 | 84 | for (var n = 0; n < string.length; n++) { 85 | 86 | var c = string.charCodeAt(n); 87 | 88 | if (c < 128) { 89 | utftext += String.fromCharCode(c); 90 | } 91 | else if((c > 127) && (c < 2048)) { 92 | utftext += String.fromCharCode((c >> 6) | 192); 93 | utftext += String.fromCharCode((c & 63) | 128); 94 | } 95 | else { 96 | utftext += String.fromCharCode((c >> 12) | 224); 97 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 98 | utftext += String.fromCharCode((c & 63) | 128); 99 | } 100 | 101 | } 102 | 103 | return utftext; 104 | }, 105 | 106 | // private method for UTF-8 decoding 107 | _utf8_decode : function (utftext) { 108 | var string = ""; 109 | var i = 0; 110 | var c = c1 = c2 = 0; 111 | 112 | while ( i < utftext.length ) { 113 | 114 | c = utftext.charCodeAt(i); 115 | 116 | if (c < 128) { 117 | string += String.fromCharCode(c); 118 | i++; 119 | } 120 | else if((c > 191) && (c < 224)) { 121 | c2 = utftext.charCodeAt(i+1); 122 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 123 | i += 2; 124 | } 125 | else { 126 | c2 = utftext.charCodeAt(i+1); 127 | c3 = utftext.charCodeAt(i+2); 128 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 129 | i += 3; 130 | } 131 | 132 | } 133 | 134 | return string; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /butterfly_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SVG.toDataURL butterfly test 6 | 7 | 8 | 9 | 10 | 13 | 14 | 18 | 19 | 20 | 21 | 36 | 37 | 53 | 54 | 55 | 56 |

57 | The SVG butterfly. 58 | If a second butterfly appears when pressing button, native SVG.toDataURL works in your browser. Check console for details. 59 |

60 |

61 | 62 | Go to DataURL 63 |

64 | 65 | 67 | 69 | 71 | 73 | 74 | 75 | svg butterfly from canvas.toDataURL() 76 | 77 | 78 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | The missing SVG.toDataURL library for your SVG elements. 3 | 4 | Usage: SVGElement.toDataURL( type, { options } ) 5 | 6 | Returns: the data URL, except when using native PNG renderer (needs callback). 7 | 8 | type MIME type of the exported data. 9 | Default: image/svg+xml. 10 | Must support: image/png. 11 | Additional: image/jpeg. 12 | 13 | options is a map of options: { 14 | callback: function(dataURL) 15 | Callback function which is called when the data URL is ready. 16 | This is only necessary when using native PNG renderer. 17 | Default: undefined. 18 | 19 | [the rest of the options only apply when type="image/png" or type="image/jpeg"] 20 | 21 | renderer: "native"|"canvg" 22 | PNG renderer to use. Native renderer¹ might cause a security exception. 23 | Default: canvg if available, otherwise native. 24 | 25 | keepNonSafe: true|false 26 | Export non-safe (image and foreignObject) elements. 27 | This will set the Canvas origin-clean property to false, if this data is transferred to Canvas. 28 | Default: false, to keep origin-clean true. 29 | NOTE: not currently supported and is just ignored. 30 | 31 | keepOutsideViewport: true|false 32 | Export all drawn content, even if not visible. 33 | Default: false, export only visible viewport, similar to Canvas toDataURL(). 34 | NOTE: only supported with canvg renderer. 35 | } 36 | 37 | See original paper¹ for more info on SVG to Canvas exporting. 38 | 39 | ¹ http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/#svg_to_canvas 40 | */ 41 | 42 | module.exports = function toDataURL(_svg, type, options) { 43 | function defaultDebug(s) { 44 | console.log("SVG.toDataURL:", s); 45 | } 46 | var debug = options && options.debug ? options.debug : defaultDebug; 47 | var myCanvg = options && options.canvg ? options.canvg : typeof canvg === 'function' ? canvg : window.canvg; 48 | 49 | function exportSVG() { 50 | var svg_xml = XMLSerialize(_svg); 51 | var svg_dataurl = base64dataURLencode(svg_xml); 52 | debug(type + " length: " + svg_dataurl.length); 53 | 54 | // NOTE double data carrier 55 | if (options.callback) options.callback(svg_dataurl); 56 | return svg_dataurl; 57 | } 58 | 59 | function XMLSerialize(svg) { 60 | 61 | // quick-n-serialize an SVG dom, needed for IE9 where there's no XMLSerializer nor SVG.xml 62 | // s: SVG dom, which is the elemennt 63 | function XMLSerializerForIE(s) { 64 | var out = ""; 65 | 66 | out += "<" + s.nodeName; 67 | for (var n = 0; n < s.attributes.length; n++) { 68 | out += " " + s.attributes[n].name + "=" + "'" + s.attributes[n].value + "'"; 69 | } 70 | 71 | if (s.hasChildNodes()) { 72 | out += ">\n"; 73 | 74 | for (var n = 0; n < s.childNodes.length; n++) { 75 | out += XMLSerializerForIE(s.childNodes[n]); 76 | } 77 | 78 | out += "" + "\n"; 79 | 80 | } else out += " />\n"; 81 | 82 | return out; 83 | } 84 | 85 | 86 | if (window.XMLSerializer) { 87 | debug("using standard XMLSerializer.serializeToString") 88 | return (new XMLSerializer()).serializeToString(svg); 89 | } else { 90 | debug("using custom XMLSerializerForIE") 91 | return XMLSerializerForIE(svg); 92 | } 93 | 94 | } 95 | 96 | function base64dataURLencode(s) { 97 | var b64 = "data:image/svg+xml;base64,"; 98 | 99 | // https://developer.mozilla.org/en/DOM/window.btoa 100 | if (window.btoa) { 101 | debug("using window.btoa for base64 encoding"); 102 | b64 += btoa(s); 103 | } else { 104 | debug("using custom base64 encoder"); 105 | b64 += Base64.encode(s); 106 | } 107 | 108 | return b64; 109 | } 110 | 111 | function exportImage(type) { 112 | var canvas = document.createElement("canvas"); 113 | var ctx = canvas.getContext('2d'); 114 | 115 | // TODO: if (options.keepOutsideViewport), do some translation magic? 116 | 117 | var svg_img = new Image(); 118 | var svg_xml = XMLSerialize(_svg); 119 | svg_img.src = base64dataURLencode(svg_xml); 120 | 121 | svg_img.onload = function() { 122 | debug("exported image size: " + [svg_img.width, svg_img.height]) 123 | canvas.width = svg_img.width; 124 | canvas.height = svg_img.height; 125 | ctx.drawImage(svg_img, 0, 0); 126 | 127 | // SECURITY_ERR WILL HAPPEN NOW 128 | var png_dataurl = canvas.toDataURL(type); 129 | debug(type + " length: " + png_dataurl.length); 130 | 131 | if (options.callback) options.callback( png_dataurl ); 132 | else debug("WARNING: no callback set, so nothing happens."); 133 | } 134 | 135 | svg_img.onerror = function() { 136 | console.log( 137 | "Can't export! Maybe your browser doesn't support " + 138 | "SVG in img element or SVG input for Canvas drawImage?\n" + 139 | "http://en.wikipedia.org/wiki/SVG#Native_support" 140 | ); 141 | } 142 | 143 | // NOTE: will not return anything 144 | } 145 | 146 | function exportImageCanvg(type) { 147 | var canvas = document.createElement("canvas"); 148 | var ctx = canvas.getContext('2d'); 149 | var svg_xml = XMLSerialize(_svg); 150 | 151 | // NOTE: canvg gets the SVG element dimensions incorrectly if not specified as attributes 152 | //debug("detected svg dimensions " + [_svg.clientWidth, _svg.clientHeight]) 153 | //debug("canvas dimensions " + [canvas.width, canvas.height]) 154 | 155 | var keepBB = options.keepOutsideViewport; 156 | if (keepBB) var bb = _svg.getBBox(); 157 | 158 | // NOTE: this canvg call is synchronous and blocks 159 | myCanvg(canvas, svg_xml, { 160 | ignoreMouse: true, ignoreAnimation: true, 161 | offsetX: keepBB ? -bb.x : undefined, 162 | offsetY: keepBB ? -bb.y : undefined, 163 | scaleWidth: keepBB ? bb.width+bb.x : undefined, 164 | scaleHeight: keepBB ? bb.height+bb.y : undefined, 165 | renderCallback: function() { 166 | debug("exported image dimensions " + [canvas.width, canvas.height]); 167 | var png_dataurl = canvas.toDataURL(type); 168 | debug(type + " length: " + png_dataurl.length); 169 | 170 | if (options.callback) options.callback( png_dataurl ); 171 | } 172 | }); 173 | 174 | // NOTE: return in addition to callback 175 | return canvas.toDataURL(type); 176 | } 177 | 178 | // BEGIN MAIN 179 | 180 | if (!type) type = "image/svg+xml"; 181 | if (!options) options = {}; 182 | 183 | if (options.keepNonSafe) debug("NOTE: keepNonSafe is NOT supported and will be ignored!"); 184 | if (options.keepOutsideViewport) debug("NOTE: keepOutsideViewport is only supported with canvg exporter."); 185 | 186 | switch (type) { 187 | case "image/svg+xml": 188 | return exportSVG(); 189 | break; 190 | 191 | case "image/png": 192 | case "image/jpeg": 193 | 194 | if (!options.renderer) { 195 | if (window.canvg) options.renderer = "canvg"; 196 | else options.renderer="native"; 197 | } 198 | 199 | switch (options.renderer) { 200 | case "canvg": 201 | debug("using canvg renderer for png export"); 202 | return exportImageCanvg(type); 203 | break; 204 | 205 | case "native": 206 | debug("using native renderer for png export. THIS MIGHT FAIL."); 207 | return exportImage(type); 208 | break; 209 | 210 | default: 211 | debug("unknown png renderer given, doing noting (" + options.renderer + ")"); 212 | } 213 | 214 | break; 215 | 216 | default: 217 | debug("Sorry! Exporting as '" + type + "' is not supported!") 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svgtodatauri", 3 | "version": "0.0.0", 4 | "description": "Gives your SVG elements the missing toDataURL function (same as Canvas has)", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/graingert/SVG.toDataURL.git" 11 | }, 12 | "keywords": [ 13 | "svg", 14 | "dataurl" 15 | ], 16 | "author": "Samuli Kaipiainen", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/graingert/SVG.toDataURL/issues" 20 | }, 21 | "homepage": "https://github.com/graingert/SVG.toDataURL#readme" 22 | } 23 | --------------------------------------------------------------------------------