UNPKG

15.9 kBJavaScriptView Raw
1'use strict';
2
3(function () {
4 var out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this || window;
5 if (typeof define !== 'undefined') define('save-svg-as-png', [], function () {
6 return out$;
7 });
8 out$.default = out$;
9
10 var xmlns = 'http://www.w3.org/2000/xmlns/';
11 var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp "&#160;">]>';
12 var urlRegex = /url\(["']?(.+?)["']?\)/;
13 var fontFormats = {
14 woff2: 'font/woff2',
15 woff: 'font/woff',
16 otf: 'application/x-font-opentype',
17 ttf: 'application/x-font-ttf',
18 eot: 'application/vnd.ms-fontobject',
19 sfnt: 'application/font-sfnt',
20 svg: 'image/svg+xml'
21 };
22
23 var isElement = function isElement(obj) {
24 return obj instanceof HTMLElement || obj instanceof SVGElement;
25 };
26 var requireDomNode = function requireDomNode(el) {
27 if (!isElement(el)) throw new Error('an HTMLElement or SVGElement is required; got ' + el);
28 };
29 var requireDomNodePromise = function requireDomNodePromise(el) {
30 return new Promise(function (resolve, reject) {
31 if (isElement(el)) resolve(el);else reject(new Error('an HTMLElement or SVGElement is required; got ' + el));
32 });
33 };
34 var isExternal = function isExternal(url) {
35 return url && url.lastIndexOf('http', 0) === 0 && url.lastIndexOf(window.location.host) === -1;
36 };
37
38 var getFontMimeTypeFromUrl = function getFontMimeTypeFromUrl(fontUrl) {
39 var formats = Object.keys(fontFormats).filter(function (extension) {
40 return fontUrl.indexOf('.' + extension) > 0;
41 }).map(function (extension) {
42 return fontFormats[extension];
43 });
44 if (formats) return formats[0];
45 console.error('Unknown font format for ' + fontUrl + '. Fonts may not be working correctly.');
46 return 'application/octet-stream';
47 };
48
49 var arrayBufferToBase64 = function arrayBufferToBase64(buffer) {
50 var binary = '';
51 var bytes = new Uint8Array(buffer);
52 for (var i = 0; i < bytes.byteLength; i++) {
53 binary += String.fromCharCode(bytes[i]);
54 }return window.btoa(binary);
55 };
56
57 var getDimension = function getDimension(el, clone, dim) {
58 var v = el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim] || clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim)) || el.getBoundingClientRect()[dim] || parseInt(clone.style[dim]) || parseInt(window.getComputedStyle(el).getPropertyValue(dim));
59 return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
60 };
61
62 var getDimensions = function getDimensions(el, clone, width, height) {
63 if (el.tagName === 'svg') return {
64 width: width || getDimension(el, clone, 'width'),
65 height: height || getDimension(el, clone, 'height')
66 };else if (el.getBBox) {
67 var _el$getBBox = el.getBBox(),
68 x = _el$getBBox.x,
69 y = _el$getBBox.y,
70 _width = _el$getBBox.width,
71 _height = _el$getBBox.height;
72
73 return {
74 width: x + _width,
75 height: y + _height
76 };
77 }
78 };
79
80 var reEncode = function reEncode(data) {
81 return decodeURIComponent(encodeURIComponent(data).replace(/%([0-9A-F]{2})/g, function (match, p1) {
82 var c = String.fromCharCode('0x' + p1);
83 return c === '%' ? '%25' : c;
84 }));
85 };
86
87 var uriToBlob = function uriToBlob(uri) {
88 var byteString = window.atob(uri.split(',')[1]);
89 var mimeString = uri.split(',')[0].split(':')[1].split(';')[0];
90 var buffer = new ArrayBuffer(byteString.length);
91 var intArray = new Uint8Array(buffer);
92 for (var i = 0; i < byteString.length; i++) {
93 intArray[i] = byteString.charCodeAt(i);
94 }
95 return new Blob([buffer], { type: mimeString });
96 };
97
98 var query = function query(el, selector) {
99 if (!selector) return;
100 try {
101 return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
102 } catch (err) {
103 console.warn('Invalid CSS selector "' + selector + '"', err);
104 }
105 };
106
107 var detectCssFont = function detectCssFont(rule, href) {
108 // Match CSS font-face rules to external links.
109 // @font-face {
110 // src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
111 // }
112 var match = rule.cssText.match(urlRegex);
113 var url = match && match[1] || '';
114 if (!url || url.match(/^data:/) || url === 'about:blank') return;
115 var fullUrl = url.startsWith('../') ? href + '/../' + url : url.startsWith('./') ? href + '/.' + url : url;
116 return {
117 text: rule.cssText,
118 format: getFontMimeTypeFromUrl(fullUrl),
119 url: fullUrl
120 };
121 };
122
123 var inlineImages = function inlineImages(el) {
124 return Promise.all(Array.from(el.querySelectorAll('image')).map(function (image) {
125 var href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
126 if (!href) return Promise.resolve(null);
127 if (isExternal(href)) {
128 href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
129 }
130 return new Promise(function (resolve, reject) {
131 var canvas = document.createElement('canvas');
132 var img = new Image();
133 img.crossOrigin = 'anonymous';
134 img.src = href;
135 img.onerror = function () {
136 return reject(new Error('Could not load ' + href));
137 };
138 img.onload = function () {
139 canvas.width = img.width;
140 canvas.height = img.height;
141 canvas.getContext('2d').drawImage(img, 0, 0);
142 image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
143 resolve(true);
144 };
145 });
146 }));
147 };
148
149 var cachedFonts = {};
150 var inlineFonts = function inlineFonts(fonts) {
151 return Promise.all(fonts.map(function (font) {
152 return new Promise(function (resolve, reject) {
153 if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);
154
155 var req = new XMLHttpRequest();
156 req.addEventListener('load', function () {
157 // TODO: it may also be worth it to wait until fonts are fully loaded before
158 // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
159 var fontInBase64 = arrayBufferToBase64(req.response);
160 var fontUri = font.text.replace(urlRegex, 'url("data:' + font.format + ';base64,' + fontInBase64 + '")') + '\n';
161 cachedFonts[font.url] = fontUri;
162 resolve(fontUri);
163 });
164 req.addEventListener('error', function (e) {
165 console.warn('Failed to load font from: ' + font.url, e);
166 cachedFonts[font.url] = null;
167 resolve(null);
168 });
169 req.addEventListener('abort', function (e) {
170 console.warn('Aborted loading font from: ' + font.url, e);
171 resolve(null);
172 });
173 req.open('GET', font.url);
174 req.responseType = 'arraybuffer';
175 req.send();
176 });
177 })).then(function (fontCss) {
178 return fontCss.filter(function (x) {
179 return x;
180 }).join('');
181 });
182 };
183
184 var cachedRules = null;
185 var styleSheetRules = function styleSheetRules() {
186 if (cachedRules) return cachedRules;
187 return cachedRules = Array.from(document.styleSheets).map(function (sheet) {
188 try {
189 return { rules: sheet.cssRules, href: sheet.href };
190 } catch (e) {
191 console.warn('Stylesheet could not be loaded: ' + sheet.href, e);
192 return {};
193 }
194 });
195 };
196
197 var inlineCss = function inlineCss(el, options) {
198 var _ref = options || {},
199 selectorRemap = _ref.selectorRemap,
200 modifyStyle = _ref.modifyStyle,
201 modifyCss = _ref.modifyCss,
202 fonts = _ref.fonts;
203
204 var generateCss = modifyCss || function (selector, properties) {
205 var sel = selectorRemap ? selectorRemap(selector) : selector;
206 var props = modifyStyle ? modifyStyle(properties) : properties;
207 return sel + '{' + props + '}\n';
208 };
209 var css = [];
210 var detectFonts = typeof fonts === 'undefined';
211 var fontList = fonts || [];
212 styleSheetRules().forEach(function (_ref2) {
213 var rules = _ref2.rules,
214 href = _ref2.href;
215
216 if (!rules) return;
217 Array.from(rules).forEach(function (rule) {
218 if (typeof rule.style != 'undefined') {
219 if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));else if (detectFonts && rule.cssText.match(/^@font-face/)) {
220 var font = detectCssFont(rule, href);
221 if (font) fontList.push(font);
222 } else css.push(rule.cssText);
223 }
224 });
225 });
226
227 return inlineFonts(fontList).then(function (fontCss) {
228 return css.join('\n') + fontCss;
229 });
230 };
231
232 out$.prepareSvg = function (el, options, done) {
233 requireDomNode(el);
234
235 var _ref3 = options || {},
236 _ref3$left = _ref3.left,
237 left = _ref3$left === undefined ? 0 : _ref3$left,
238 _ref3$top = _ref3.top,
239 top = _ref3$top === undefined ? 0 : _ref3$top,
240 w = _ref3.width,
241 h = _ref3.height,
242 _ref3$scale = _ref3.scale,
243 scale = _ref3$scale === undefined ? 1 : _ref3$scale,
244 _ref3$responsive = _ref3.responsive,
245 responsive = _ref3$responsive === undefined ? false : _ref3$responsive;
246
247 return inlineImages(el).then(function () {
248 var clone = el.cloneNode(true);
249 clone.style.backgroundColor = (options || {}).backgroundColor || el.style.backgroundColor;
250
251 var _getDimensions = getDimensions(el, clone, w, h),
252 width = _getDimensions.width,
253 height = _getDimensions.height;
254
255 if (el.tagName !== 'svg') {
256 if (el.getBBox) {
257 clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
258 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
259 svg.appendChild(clone);
260 clone = svg;
261 } else {
262 console.error('Attempted to render non-SVG element', el);
263 return;
264 }
265 }
266
267 clone.setAttribute('version', '1.1');
268 clone.setAttribute('viewBox', [left, top, width, height].join(' '));
269 if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/2000/svg');
270 if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlns, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');
271
272 if (responsive) {
273 clone.removeAttribute('width');
274 clone.removeAttribute('height');
275 clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
276 } else {
277 clone.setAttribute('width', width * scale);
278 clone.setAttribute('height', height * scale);
279 }
280
281 Array.from(clone.querySelectorAll('foreignObject > *')).forEach(function (foreignObject) {
282 if (!foreignObject.getAttribute('xmlns')) foreignObject.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/1999/xhtml');
283 });
284
285 return inlineCss(el, options).then(function (css) {
286 var style = document.createElement('style');
287 style.setAttribute('type', 'text/css');
288 style.innerHTML = '<![CDATA[\n' + css + '\n]]>';
289
290 var defs = document.createElement('defs');
291 defs.appendChild(style);
292 clone.insertBefore(defs, clone.firstChild);
293
294 var outer = document.createElement('div');
295 outer.appendChild(clone);
296 var src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');
297
298 if (typeof done === 'function') done(src, width, height);else return { src: src, width: width, height: height };
299 });
300 });
301 };
302
303 out$.svgAsDataUri = function (el, options, done) {
304 requireDomNode(el);
305 return out$.prepareSvg(el, options).then(function (_ref4) {
306 var src = _ref4.src,
307 width = _ref4.width,
308 height = _ref4.height;
309
310 var svgXml = 'data:image/svg+xml;base64,' + window.btoa(reEncode(doctype + src));
311 if (typeof done === 'function') {
312 done(svgXml, width, height);
313 }
314 return svgXml;
315 });
316 };
317
318 out$.svgAsPngUri = function (el, options, done) {
319 requireDomNode(el);
320
321 var _ref5 = options || {},
322 _ref5$encoderType = _ref5.encoderType,
323 encoderType = _ref5$encoderType === undefined ? 'image/png' : _ref5$encoderType,
324 _ref5$encoderOptions = _ref5.encoderOptions,
325 encoderOptions = _ref5$encoderOptions === undefined ? 0.8 : _ref5$encoderOptions,
326 canvg = _ref5.canvg;
327
328 var convertToPng = function convertToPng(_ref6) {
329 var src = _ref6.src,
330 width = _ref6.width,
331 height = _ref6.height;
332
333 var canvas = document.createElement('canvas');
334 var context = canvas.getContext('2d');
335 var pixelRatio = window.devicePixelRatio || 1;
336
337 canvas.width = width * pixelRatio;
338 canvas.height = height * pixelRatio;
339 canvas.style.width = canvas.width + 'px';
340 canvas.style.height = canvas.height + 'px';
341 context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
342
343 if (canvg) canvg(canvas, src);else context.drawImage(src, 0, 0);
344
345 var png = void 0;
346 try {
347 png = canvas.toDataURL(encoderType, encoderOptions);
348 } catch (e) {
349 if (typeof SecurityError !== 'undefined' && e instanceof SecurityError || e.name === 'SecurityError') {
350 console.error('Rendered SVG images cannot be downloaded in this browser.');
351 return;
352 } else throw e;
353 }
354 if (typeof done === 'function') done(png, canvas.width, canvas.height);
355 return Promise.resolve(png);
356 };
357
358 if (canvg) return out$.prepareSvg(el, options).then(convertToPng);else return out$.svgAsDataUri(el, options).then(function (uri) {
359 return new Promise(function (resolve, reject) {
360 var image = new Image();
361 image.onload = function () {
362 return resolve(convertToPng({
363 src: image,
364 width: image.width,
365 height: image.height
366 }));
367 };
368 image.onerror = function () {
369 reject('There was an error loading the data URI as an image on the following SVG\n' + window.atob(uri.slice(26)) + 'Open the following link to see browser\'s diagnosis\n' + uri);
370 };
371 image.src = uri;
372 });
373 });
374 };
375
376 out$.download = function (name, uri) {
377 if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);else {
378 var saveLink = document.createElement('a');
379 if ('download' in saveLink) {
380 saveLink.download = name;
381 saveLink.style.display = 'none';
382 document.body.appendChild(saveLink);
383 try {
384 var blob = uriToBlob(uri);
385 var url = URL.createObjectURL(blob);
386 saveLink.href = url;
387 saveLink.onclick = function () {
388 return requestAnimationFrame(function () {
389 return URL.revokeObjectURL(url);
390 });
391 };
392 } catch (e) {
393 console.error(e);
394 console.warn('Error while getting object URL. Falling back to string URL.');
395 saveLink.href = uri;
396 }
397 saveLink.click();
398 document.body.removeChild(saveLink);
399 } else {
400 window.open(uri, '_temp', 'menubar=no,toolbar=no,status=no');
401 }
402 }
403 };
404
405 out$.saveSvg = function (el, name, options) {
406 return requireDomNodePromise(el).then(function (el) {
407 return out$.svgAsDataUri(el, options || {});
408 }).then(function (uri) {
409 return out$.download(name, uri);
410 });
411 };
412
413 out$.saveSvgAsPng = function (el, name, options) {
414 return requireDomNodePromise(el).then(function (el) {
415 return out$.svgAsPngUri(el, options || {});
416 }).then(function (uri) {
417 return out$.download(name, uri);
418 });
419 };
420})();
\No newline at end of file