1385fab2ee114895b981a632a86c4fcfcec122dc570e512e386126b58b4f39ef0dcc06d16b57ad5eb0ecaa38370ecbc96cc364e1a54237f341dee45182d071 17 KB

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