1

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: Inline SVG as seen inside the web app

Here is how the exported version looks like: Exported picture of SVG

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 enter image description here

Pranav Singh
  • 17,079
  • 30
  • 77
  • 104
MarSim
  • 13
  • 4

1 Answers1

0

Not sure about the approach you are following from another answer. I earlier used saveSvgAsPng npm package, it works like charm.

  1. Install package
    npm install save-svg-as-png
  1. Use it by hmtl id of element you want to export as image like :
    saveSvgAsPng(document.getElementById("IdOfHTMLDiv"), "ImageName.png");

You can also export as svgAsDataUri or svgAsPngUri

For adding fonts, you can use options like

const imageOptions = {
      fonts: [
        {
          url: 'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2',
          format: 'application/font-woff2',
          text: "@font-face {font-family: 'Roboto';  font-style: normal;  font-weight: 400; src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}",
        },
        {
          url: 'https://fonts.gstatic.com/s/sharetechmono/v9/J7aHnp1uDWRBEqV98dVQztYldFcLowEFA87Heg.woff2',
          format: 'application/font-woff2',
          text: "@font-face {font-family: 'Share Tech Mono'; font-style: normal; font-weight: 400; src: local('Share Tech Mono'), local('ShareTechMono-Regular'), url(https://fonts.gstatic.com/s/sharetechmono/v9/J7aHnp1uDWRBEqV98dVQztYldFcLowEFA87Heg.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }",
        }
      ],
    };

svgAsPngUri(document.getElementById("diagram"), imageOptions).then(uri => ...)

Refer Github Page for more details

Pranav Singh
  • 17,079
  • 30
  • 77
  • 104