svg-export.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * svg-export.js - Javascript SVG parser and renderer on Canvas
  3. * version 1.0.0
  4. * MIT Licensed
  5. * Sharon Choong (https://sharonchoong.github.io/about.html)
  6. * https://sharonchoong.github.io/svg-export
  7. *
  8. */
  9. (function (global, factory) {
  10. /*global globalThis a*/
  11. typeof exports === "object" && typeof module !== "undefined" ? factory(exports) :
  12. typeof define === "function" && define.amd ? define(["exports"], factory) :
  13. (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.svgExport = global.svgExport || {}));
  14. } (this, (function (exports) {
  15. "use strict";
  16. var version = "1.0.0";
  17. var _options = {};
  18. function getSvgElement(svg) {
  19. var div = document.createElement("div");
  20. div.className = "tempdiv-svg-exportJS";
  21. if (typeof svg === "string") {
  22. div.insertAdjacentHTML("beforeend", svg.trim());
  23. svg = div.firstChild;
  24. }
  25. if (!svg.nodeType || svg.nodeType !== 1) {
  26. //console.log("Error svg-export: The input svg was not recognized");
  27. return null;
  28. }
  29. var svgClone = svg.cloneNode(true);
  30. svgClone.style.display = null;
  31. div.appendChild(svgClone);
  32. div.style.visibility = "hidden";
  33. div.style.display = "table";
  34. div.style.position = "absolute";
  35. document.body.appendChild(div);
  36. return svgClone;
  37. }
  38. function setPdfOptions(options) {
  39. if (options && options.pdfOptions)
  40. {
  41. Object.keys(_options.pdfOptions).forEach(function(opt) {
  42. if (options.pdfOptions.hasOwnProperty(opt) && typeof options.pdfOptions[opt] === typeof _options.pdfOptions[opt]) {
  43. if (options.pdfOptions[opt] === "") { return; }
  44. _options.pdfOptions[opt] = options.pdfOptions[opt];
  45. }
  46. });
  47. if (!_options.pdfOptions.pageLayout.margin) {
  48. _options.pdfOptions.pageLayout.margin = 50;
  49. }
  50. if (!_options.pdfOptions.pageLayout.margins) {
  51. _options.pdfOptions.pageLayout.margins = {};
  52. }
  53. }
  54. _options.pdfOptions.pageLayout.margins.top = _options.pdfOptions.pageLayout.margins.top || _options.pdfOptions.pageLayout.margin;
  55. _options.pdfOptions.pageLayout.margins.bottom = _options.pdfOptions.pageLayout.margins.bottom || _options.pdfOptions.pageLayout.margin;
  56. _options.pdfOptions.pageLayout.margins.left = _options.pdfOptions.pageLayout.margins.left || _options.pdfOptions.pageLayout.margin;
  57. _options.pdfOptions.pageLayout.margins.right = _options.pdfOptions.pageLayout.margins.top || _options.pdfOptions.pageLayout.margin;
  58. delete _options.pdfOptions.pageLayout.margin;
  59. if (!(options && _options.pdfOptions.pageLayout.size)) {
  60. _options.pdfOptions.pageLayout.size = [
  61. Math.max(300, _options.width) + _options.pdfOptions.pageLayout.margins.left + _options.pdfOptions.pageLayout.margins.right,
  62. Math.max(300, _options.height) + _options.pdfOptions.pageLayout.margins.top + _options.pdfOptions.pageLayout.margins.bottom +
  63. (_options.pdfOptions.addTitleToPage ? _options.pdfOptions.pdfTitleFontSize * 2 + 10: 0) +
  64. (_options.pdfOptions.chartCaption !== "" ? _options.pdfOptions.pdfCaptionFontSize * 4 + 10: 0)
  65. ];
  66. }
  67. }
  68. function setOptions(svgElement, options) {
  69. //initialize options
  70. _options = {
  71. originalWidth: 100,
  72. originalHeight: 100,
  73. width: 100,
  74. height: 100,
  75. scale: 1,
  76. useCSS: true,
  77. transparentBackgroundReplace: "white",
  78. pdfOptions: {
  79. customFonts: [],
  80. pageLayout: { margin: 50, margins: {} },
  81. addTitleToPage: true,
  82. chartCaption: "",
  83. pdfTextFontFamily: "Helvetica",
  84. pdfTitleFontSize: 20,
  85. pdfCaptionFontSize: 14
  86. }
  87. };
  88. //original size
  89. _options.originalHeight = svgElement.style.getPropertyValue("height").indexOf("%") !== -1
  90. || (svgElement.getAttribute("height") && svgElement.getAttribute("height").indexOf("%") !== -1 )
  91. ? svgElement.getBBox().height * _options.scale
  92. : svgElement.getBoundingClientRect().height * _options.scale;
  93. _options.originalWidth = svgElement.style.getPropertyValue("width").indexOf("%") !== -1
  94. || (svgElement.getAttribute("width") && svgElement.getAttribute("width").indexOf("%") !== -1 )
  95. ? svgElement.getBBox().width * _options.scale
  96. : svgElement.getBoundingClientRect().width * _options.scale;
  97. //custom options
  98. if (options && options.scale && typeof options.scale === "number") {
  99. _options.scale = options.scale;
  100. }
  101. if (!options || !options.height) {
  102. _options.height = _options.originalHeight * _options.scale;
  103. }
  104. else if (typeof options.height === "number") {
  105. _options.height = options.height * _options.scale;
  106. }
  107. if (!options || !options.width) {
  108. _options.width = _options.originalWidth * _options.scale;
  109. }
  110. else if (typeof options.width === "number") {
  111. _options.width = options.width * _options.scale;
  112. }
  113. if (options && options.useCSS === false) {
  114. _options.useCSS = false;
  115. }
  116. if (options && options.transparentBackgroundReplace) {
  117. _options.transparentBackgroundReplace = options.transparentBackgroundReplace;
  118. }
  119. setPdfOptions(options);
  120. }
  121. function useCSSfromComputedStyles(element, elementClone) {
  122. if (typeof getComputedStyle !== "function"){
  123. //console.log("Warning svg-export: this browser is not able to get computed styles");
  124. return;
  125. }
  126. element.childNodes.forEach(function(child, index){
  127. if (child.nodeType === 1/*Node.ELEMENT_NODE*/) {
  128. useCSSfromComputedStyles(child, elementClone.childNodes[parseInt(index, 10)]);
  129. }
  130. });
  131. var compStyles = window.getComputedStyle(element);
  132. if (compStyles.length > 0) {
  133. for (const compStyle of compStyles){
  134. if (["width", "height", "inline-size", "block-size"].indexOf(compStyle) === -1 ) {
  135. elementClone.style.setProperty(compStyle, compStyles.getPropertyValue(compStyle));
  136. }
  137. };
  138. }
  139. }
  140. function setupSvg(svgElement, originalSvg, asString)
  141. {
  142. if (typeof asString === "undefined") { asString = true; }
  143. if (_options.useCSS && typeof originalSvg === "object") {
  144. useCSSfromComputedStyles(originalSvg, svgElement);
  145. svgElement.style.display = null;
  146. }
  147. svgElement.style.width = null;
  148. svgElement.style.height = null;
  149. svgElement.setAttribute("width", _options.width);
  150. svgElement.setAttribute("height", _options.height);
  151. svgElement.setAttribute("preserveAspectRatio", "none");
  152. svgElement.setAttribute("viewBox", "0 0 " + (_options.originalWidth) + " " + (_options.originalHeight));
  153. var elements = document.getElementsByClassName("tempdiv-svg-exportJS");
  154. while(elements.length > 0){
  155. elements[0].parentNode.removeChild(elements[0]);
  156. }
  157. //get svg string
  158. if (asString)
  159. {
  160. var serializer = new XMLSerializer();
  161. //setting currentColor to black matters if computed styles are not used
  162. var svgString = serializer.serializeToString(svgElement).replace(/currentColor/g, "black");
  163. //add namespaces
  164. if (!svgString.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
  165. svgString = svgString.replace(/^<svg/, "<svg xmlns=\"http://www.w3.org/2000/svg\"");
  166. }
  167. if (!svgString.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) {
  168. svgString = svgString.replace(/^<svg/, "<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
  169. }
  170. return svgString;
  171. }
  172. return svgElement;
  173. }
  174. function getCustomFonts(fontUrls) {
  175. var promises = [];
  176. fontUrls.forEach(function(fontUrl) {
  177. var promise = new Promise(function(resolve, reject) {
  178. var req = new XMLHttpRequest();
  179. req.onreadystatechange = function() {
  180. if (req.readyState === 4 && req.status === 200) {
  181. resolve(req.response);
  182. }
  183. };
  184. req.open("GET", fontUrl, true);
  185. req.responseType = "arraybuffer";
  186. req.send(null);
  187. });
  188. promises.push(promise);
  189. });
  190. return promises;
  191. }
  192. function triggerDownload(uri, name, canvas) {
  193. name = name.replace(/[/\\?%*:|"<>]/g, "_");
  194. if (navigator.msSaveBlob) {
  195. var binary = (decodeURIComponent(uri.split(",")[1])), array = [];
  196. var mimeString = uri.split(",")[0].split(":")[1].split(";")[0];
  197. for (var i = 0; i < binary.length; i++) {
  198. array.push(binary.charCodeAt(i));
  199. }
  200. var blob = null;
  201. if (canvas != null) {
  202. blob = canvas.msToBlob();
  203. } else {
  204. blob = new Blob([new Uint8Array(array)], { type: mimeString });
  205. }
  206. return navigator.msSaveBlob(blob, name);
  207. } else {
  208. var link = document.createElement("a");
  209. link.download = name;
  210. link.href = uri;
  211. document.body.appendChild(link);
  212. link.click();
  213. document.body.removeChild(link);
  214. }
  215. }
  216. function downloadSvg(svg, svgName, options) {
  217. var svgElement = getSvgElement(svg);
  218. if (!svgElement) { return; }
  219. if (svgName == null) {
  220. svgName = "chart";
  221. }
  222. //get svg element
  223. setOptions(svgElement, options);
  224. var svgString = setupSvg(svgElement, svg);
  225. //add xml declaration
  226. svgString = "<?xml version=\"1.0\" standalone=\"no\"?>\r\n" + svgString;
  227. //convert svg string to URI data scheme.
  228. var url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgString);
  229. triggerDownload(url, svgName + ".svg");
  230. }
  231. function downloadRaster(svg, svgName, options, imageType) {
  232. //check dependency and values
  233. if (typeof canvg !== "object")
  234. {
  235. //console.log("Error svg-export: PNG/JPEG export requires Canvg.js");
  236. return;
  237. }
  238. imageType = imageType.toLowerCase().replace("jpg", "jpeg");
  239. if (imageType !== "png" && imageType !== "jpeg") {
  240. imageType = "png";
  241. }
  242. var svgElement = getSvgElement(svg);
  243. if (!svgElement) { return; }
  244. if (svgName == null) {
  245. svgName = "chart";
  246. }
  247. //get canvas and svg element.
  248. var canvas = document.createElement("canvas");
  249. if (!(options && (options.width || options.height))) {
  250. if (!options) {
  251. options = {};
  252. }
  253. options.scale = 10;
  254. }
  255. setOptions(svgElement, options);
  256. var svgString = setupSvg(svgElement, svg);
  257. if (imageType === "jpeg")
  258. {
  259. //change transparent background to white
  260. svgString = svgString.replace(">", "><rect x=\"0\" y=\"0\" width=\"" + _options.width + "\" height=\"" + _options.height
  261. + "\" fill=\"" + _options.transparentBackgroundReplace + "\"/>");
  262. }
  263. var ctx = canvas.getContext("2d");
  264. canvg.Canvg.fromString(ctx, svgString).start();
  265. var image = canvas.toDataURL("image/" + imageType);
  266. triggerDownload(image, svgName + "." + imageType, canvas);
  267. }
  268. function downloadPng(svg, svgName, options) {
  269. downloadRaster(svg, svgName, options, "png");
  270. }
  271. function downloadJpeg(svg, svgName, options) {
  272. downloadRaster(svg, svgName, options, "jpeg");
  273. }
  274. function fillPDFDoc(doc, svgName, svg) {
  275. // -title
  276. if (_options.pdfOptions.addTitleToPage){
  277. doc.font(_options.pdfOptions.pdfTextFontFamily)
  278. .fontSize(_options.pdfOptions.pdfTitleFontSize)
  279. .text(svgName,
  280. {
  281. width: _options.pdfOptions.pageLayout.size[0] - _options.pdfOptions.pageLayout.margins.left - _options.pdfOptions.pageLayout.margins.right
  282. });
  283. }
  284. // -svg
  285. SVGtoPDF(doc, svg, _options.pdfOptions.pageLayout.margins.left, doc.y + 10,
  286. { width: _options.width, height: _options.height, preserveAspectRatio: "none", useCSS: _options.useCSS });
  287. // -caption
  288. if (_options.pdfOptions.chartCaption !== ""){
  289. doc.font(_options.pdfOptions.pdfTextFontFamily)
  290. .fontSize(_options.pdfOptions.pdfCaptionFontSize)
  291. .text(_options.pdfOptions.chartCaption, _options.pdfOptions.pageLayout.margins.left,
  292. _options.pdfOptions.pageLayout.size[1] - _options.pdfOptions.pageLayout.margins.bottom - _options.pdfOptions.pdfCaptionFontSize * 4,
  293. {
  294. width: _options.pdfOptions.pageLayout.size[0] - _options.pdfOptions.pageLayout.margins.left - _options.pdfOptions.pageLayout.margins.right
  295. });
  296. }
  297. }
  298. function downloadPdf(svg, svgName, options) {
  299. //check dependency and values
  300. if (typeof PDFDocument !== "function" || typeof SVGtoPDF !== "function" || typeof blobStream !== "function")
  301. {
  302. //console.log("Error svg-export: PDF export requires PDFKit.js, blob-stream and SVG-to-PDFKit");
  303. return;
  304. }
  305. var svgElement = getSvgElement(svg);
  306. if (!svgElement) { return; }
  307. if (svgName == null) {
  308. svgName = "chart";
  309. }
  310. //get svg element
  311. setOptions(svgElement, options);
  312. var svgCloned = setupSvg(svgElement, svg, false);
  313. //create PDF doc
  314. var doc = new PDFDocument(_options.pdfOptions.pageLayout);
  315. var stream = doc.pipe(blobStream());
  316. // -custom fonts
  317. if (_options.pdfOptions.customFonts.length > 0){
  318. var promises = getCustomFonts(_options.pdfOptions.customFonts.map(function(d) { return d.url; }));
  319. Promise.all(promises).then(function(fonts) {
  320. fonts.forEach(function(font, index) {
  321. var thisPdfOptions = _options.pdfOptions.customFonts[parseInt(index, 10)];
  322. //this ensures that the font fallbacks are removed from inline CSS that contain custom fonts, as fonts with fallbacks are not parsed correctly by SVG-to-PDFKit
  323. var fontStyledElements = svgCloned.querySelectorAll("[style*=\"" +thisPdfOptions.fontName + "\"]");
  324. fontStyledElements.forEach(function(element) {
  325. element.style.fontFamily = thisPdfOptions.fontName;
  326. });
  327. if ((thisPdfOptions.url.indexOf(".ttc") !== -1 || thisPdfOptions.url.indexOf(".dfont") !== -1) && thisPdfOptions.styleName) {
  328. doc.registerFont(thisPdfOptions.fontName, font, thisPdfOptions.styleName);
  329. }
  330. else {
  331. doc.registerFont(thisPdfOptions.fontName, font);
  332. }
  333. });
  334. fillPDFDoc(doc, svgName, svgCloned);
  335. doc.end();
  336. });
  337. } else {
  338. fillPDFDoc(doc, svgName, svgCloned);
  339. doc.end();
  340. }
  341. stream.on("finish", function() {
  342. var url = stream.toBlobURL("application/pdf");
  343. triggerDownload(url, svgName + ".pdf");
  344. });
  345. }
  346. exports.version = version;
  347. exports.downloadSvg = downloadSvg;
  348. exports.downloadPng = downloadPng;
  349. exports.downloadJpeg = downloadJpeg;
  350. exports.downloadPdf = downloadPdf;
  351. Object.defineProperty(exports, "__esModule", { value: true });
  352. })
  353. ));