I hope someone can help me with this problem:
The goal is to save an inline SVG as an PNG image.
The problem is that said SVG has foreignObject
elements containing webfont icons (from Fontawesome as well as custom ones created with Icomoon).
To export the SVG as PNG I used the approach described in Save inline SVG as JPEG/PNG/SVG.
To get the right view of the SVG I copied all computed styles to a copy of the svg Element as inline styles (copy to not pollute the original one). But since font icons are only embedded in HTML as ::before
elements and those do not work as inline styles they were left out when exporting the SVG.
My workaround was putting the unicode of the icon glyph as innerHTML
of the i
tag and let SVG handle it like normal text.
The problem here was that the exported image did not have access to the fonts containing the icons. So I tried using the workaround discribed in Including fonts when converting SVG to PNG and embed the base64 encoded version of the icon font inside the svgs defs --> style
tags. First problem with this approach was that I couldn't use btoa()
directly because I added the icons unicode characters but for this is used the first solution described in MDN Web Docs.
But after all this I still can't get the icons to show up on the exported image and I'm out of ideas...
Here is how the web version of the SVG looks like:
Here is how the exported version looks like:
Here is the code 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"'
// the text ICON is just added for visualisation purpose
elementCopy.innerHTML = 'ICON\ue900'
}
}
The font in the SVG is embedded like so:
Base64 encoded font embedded in SVG