I'm facing a challenge that I hope someone can assist me with:
My objective is to save an inline SVG as a PNG image.
The issue arises from the fact that the SVG contains foreignObject
elements with webfont icons (including Fontawesome and custom ones created with Icomoon).
In my attempt to export the SVG as PNG, I followed the method outlined in Save inline SVG as JPEG/PNG/SVG.
To ensure the correct display of the SVG, I copied all computed styles to a duplicate svg Element as inline styles. However, since font icons are only embedded in HTML as ::before
elements, they were excluded during the export process due to their inability to work as inline styles.
To resolve this, I inserted the unicode of the icon glyph as the innerHTML
of the i
tag and allowed SVG to treat it as normal text.
The challenge emerged when the exported image lacked access to the fonts containing the icons. In an effort to address this, I tried the workaround discussed in Including fonts when converting SVG to PNG, embedding the base64 encoded version of the icon font within the SVG's defs --> style
tags. However, I encountered an obstacle as direct usage of btoa()
was not feasible due to the inclusion of the icons' unicode characters. To tackle this, I adopted the initial solution recommended in MDN Web Docs.
Despite these efforts, the icons still fail to appear in the exported image, leaving me without further solutions...
Here is how the web version of the SVG appears: https://i.sstatic.net/P0VZT.png
This is what the exported version looks like: https://i.sstatic.net/HzAPa.png
Below is the code snippet for exporting:
download(selector: string, name: string): void {
const svg = <HTMLElement>document.querySelector(selector + ' svg')
const svgCopy = svg.cloneNode(true) as HTMLElement
this.computedToInline(svg, svgCopy)
const canvas = <HTMLCanvasElement>document.querySelector('canvas')
canvas.width = svg.getBoundingClientRect().width
canvas.height = svg.getBoundingClientRect().height
const ctx = canvas.getContext('2d')
if (!ctx) return
const data = new XMLSerializer().serializeToString(svgCopy)
const image = new Image()
const encodedData = btoa(unescape(encodeURIComponent(data)))
const url = 'data:image/svg+xml;base64,' + encodedData
image.onload = () => {
ctx.drawImage(image, 0, 0)
window.URL.revokeObjectURL(url)
const imageURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream')
this.triggerDownload(imageURI, name)
}
image.src = url
}
computedToInline(element: HTMLElement, elementCopy: HTMLElement): void {
if (element.hasChildNodes()) {
for (let i = 0; i < element.children.length; i++) {
this.computedToInline(
element.children[i] as HTMLElement,
elementCopy.children[i] as HTMLElement
)
}
}
const computedStyles = getComputedStyle(element)
for (let i = 0; i < computedStyles.length; i++) {
const style = computedStyles[i]
elementCopy.style.setProperty(style, computedStyles.getPropertyValue(style))
}
if (element.classList.contains('icon')) {
elementCopy.style.font = '900 14px "Custom Icon Font"'
// Visual representation purpose only - the text ICON is added
elementCopy.innerHTML = 'ICON\ue900'
}
}
The font in the SVG is embedded as follows: Base64 encoded font embedded in SVG https://i.sstatic.net/9hGgB.png