svg-to-png.js
109 lines
| 1 | async function svgToPng(svgElement) { |
| 2 | const { width, height } = svgDimensions(svgElement); |
| 3 | const svgClone = svgElement.cloneNode(true); |
| 4 | |
| 5 | inlineComputedSvgStyles(svgElement, svgClone); |
| 6 | svgClone.setAttribute("xmlns", "http://www.w3.org/2000/svg"); |
| 7 | svgClone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); |
| 8 | svgClone.setAttribute("width", width); |
| 9 | svgClone.setAttribute("height", height); |
| 10 | |
| 11 | // 1. Get SVG data |
| 12 | const svgData = new XMLSerializer().serializeToString(svgClone); |
| 13 | const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" }); |
| 14 | const url = URL.createObjectURL(svgBlob); |
| 15 | |
| 16 | // 2. Load SVG into image (wrapped in promise) |
| 17 | const img = await new Promise((resolve, reject) => { |
| 18 | const image = new Image(); |
| 19 | image.onload = () => resolve(image); |
| 20 | image.onerror = reject; |
| 21 | image.src = url; |
| 22 | }); |
| 23 | |
| 24 | // 3. Create canvas and draw |
| 25 | const canvas = document.createElement("canvas"); |
| 26 | canvas.width = width; |
| 27 | canvas.height = height; |
| 28 | |
| 29 | const ctx = canvas.getContext("2d"); |
| 30 | ctx.drawImage(img, 0, 0, width, height); |
| 31 | |
| 32 | URL.revokeObjectURL(url); |
| 33 | |
| 34 | // 4. Create PNG image element |
| 35 | const pngImage = new Image(); |
| 36 | pngImage.src = canvas.toDataURL("image/png"); |
| 37 | |
| 38 | return pngImage; |
| 39 | } |
| 40 | |
| 41 | function svgDimensions(svgElement) { |
| 42 | const renderedSize = svgElement.getBoundingClientRect(); |
| 43 | const viewBox = svgElement.viewBox.baseVal; |
| 44 | const attributeSize = { |
| 45 | width: svgElement.width.baseVal.value, |
| 46 | height: svgElement.height.baseVal.value, |
| 47 | }; |
| 48 | const viewBoxSize = { |
| 49 | width: viewBox ? viewBox.width : 0, |
| 50 | height: viewBox ? viewBox.height : 0, |
| 51 | }; |
| 52 | |
| 53 | return { |
| 54 | width: firstPositiveSize(renderedSize.width, attributeSize.width, viewBoxSize.width, 300), |
| 55 | height: firstPositiveSize(renderedSize.height, attributeSize.height, viewBoxSize.height, 150), |
| 56 | }; |
| 57 | } |
| 58 | |
| 59 | function firstPositiveSize(...sizes) { |
| 60 | const size = sizes.find((size) => size > 0); |
| 61 | |
| 62 | return Math.ceil(size); |
| 63 | } |
| 64 | |
| 65 | function inlineComputedSvgStyles(sourceSvg, targetSvg) { |
| 66 | const sourceElements = [sourceSvg, ...sourceSvg.querySelectorAll("*")]; |
| 67 | const targetElements = [targetSvg, ...targetSvg.querySelectorAll("*")]; |
| 68 | const properties = [ |
| 69 | "clip-rule", |
| 70 | "color", |
| 71 | "display", |
| 72 | "dominant-baseline", |
| 73 | "fill", |
| 74 | "fill-opacity", |
| 75 | "fill-rule", |
| 76 | "font-family", |
| 77 | "font-size", |
| 78 | "font-style", |
| 79 | "font-weight", |
| 80 | "opacity", |
| 81 | "stroke", |
| 82 | "stroke-dasharray", |
| 83 | "stroke-dashoffset", |
| 84 | "stroke-linecap", |
| 85 | "stroke-linejoin", |
| 86 | "stroke-miterlimit", |
| 87 | "stroke-opacity", |
| 88 | "stroke-width", |
| 89 | "text-anchor", |
| 90 | "vector-effect", |
| 91 | "visibility", |
| 92 | ]; |
| 93 | |
| 94 | sourceElements.forEach((sourceElement, index) => { |
| 95 | const targetElement = targetElements[index]; |
| 96 | const computedStyle = getComputedStyle(sourceElement); |
| 97 | |
| 98 | properties.forEach((property) => { |
| 99 | const value = computedStyle.getPropertyValue(property); |
| 100 | |
| 101 | if (value) { |
| 102 | targetElement.setAttribute(property, value); |
| 103 | } |
| 104 | }); |
| 105 | }); |
| 106 | } |
| 107 | |
| 108 | module.exports = { svgToPng }; |
| 109 |