Convert SVG to image using a personalized icon font

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

Answer №1

Unsure of the methodology mentioned in another answer. I previously utilized the saveSvgAsPng npm package and it worked perfectly.

  1. Install the package
    npm install save-svg-as-png
  1. Use it by referencing the HTML element ID you wish to export as an image:
    saveSvgAsPng(document.getElementById("IdOfHTMLDiv"), "ImageName.png");

You can also export as svgAsDataUri or svgAsPngUri

To include fonts, you can utilize 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 => ...)

Visit the Github Page for more information

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error in React.tsx Material UI Tab - Avoid using curly braces "{}" as a data type

Currently, I am working on a React TS project and facing an issue while trying to import the Material UI tabs for scrollable functionality. The specific Tabs/Scrollable Tabs example from Material-UI documentation is what I'm referring to: https://mate ...

What could be causing my component to not properly redirect to the Movies Page?

Currently, I am utilizing React-Router v6.4 along with Vite for setting up a React project. I am facing an issue where the routes are not redirecting to the movies component as expected. App.jsx import "./App.css"; import { createBrowserRouter, ...

Using destructuring repeatedly for a single object property

At times, I engage in nested destructuring, where I go more than one level deep. It can be risky, but I always make sure the property exists to avoid encountering an error of undefined. Recently, I came across this scenario: const { match: { ...

ReactJS: The uniqueness of [ this.props ] compared to [ props in this ] is incomprehensible

Inside the componentDidMount method, there is a continuous section of code: console.log('hv props'); for(var i = 0; i<20; i++){ console.log(this); console.log(this.props); } console.log('hv props end'); It is expected that ...

Tracking tool for monitoring progress of HTTP POST requests

Hi, I am still a beginner when it comes to NodeJS and its modules. I could really use some assistance with creating a progress bar for an application I'm working on. Right now, the progress bar only reaches 100% upon completion and I suspect my piping ...

What is the best way to ensure that a div containing lengthy text wraps to the next line as if it were only text overflow?

Is there a way to make a div or span within another div act as text, causing overflow to shift to the next line? I'm unsure of which CSS properties would achieve this effect. I've attempted using overflow-wrap: break-word; word-break: break-al ...

Using Three.js to transform fonts into JSON format from otf and ttf files

Recently, I encountered an issue while trying to use two different fonts in THREE js. One font had an otf extension, while the other was in ttf format. To make them compatible with THREE js, I converted both fonts into json files using a helpful tool calle ...

Issue with data synchronization in SAPUI5 custom control's two-way binding not resolved

As I work on creating a custom Control and binding data to it, I encounter an issue where upon clicking a button, the data in the Model is changed but not reflected in the DOM. Being new to SAP-UI5, I am struggling to identify the root cause of this proble ...

What are the steps to implement cp-react-tree-table in my application?

Recently diving into TypeScript, I decided to explore the demo provided by https://www.npmjs.com/package/cp-react-tree-table in order to incorporate this control into my project. However, I encountered some unexpected information. After conducting a thoro ...

Attempting to eliminate quotation marks from JSON keys that are stored in an array

I am working with an array of JavaScript objects that represent musical notes in my front-end development. To store this data, I had to convert the object array into a JSON string using JSON.stringify(objectArray) before placing it into a hidden input fiel ...

What is the best way to ensure an SVG maintains a fluid width while keeping its height constant?

My goal is to achieve the following: The SVG should dynamically scale its width to occupy 100% of the container's width. The SVG should either stretch or compress when the container's width changes, specifically affecting the wave drawn wit ...

Issues with the creation of an AngularJS table are causing functionality to malfunction

2021 UPDATE: Note: This question was drafted when I was still learning HTML and JS. If you are seeking assistance for an issue, this might not be very useful as my code was messy. Sorry for any inconvenience caused. The question will remain posted in acco ...

Utilizing multiple forms in AngularJS

As I delve into the world of AngularJS, I've managed to create a news page where users can comment on articles. However, I'm not entirely satisfied with the way I use classic JavaScript to dynamically fetch story forms. Is there a cleaner approac ...

Transferring an image file from PHP to JavaScript

I have encountered an issue with transferring images stored as LONGBLOB files in my database to a JS file for dimension comparison with the screen. Here is how I retrieve the images: function fillArrays(){ $idArray = array(); $sql = "SELECT oglas_id,s ...

Demonstrate the proper implementation of a Stepper component using Material UI in a React.js

I am trying to display a responsive screen using "progressive forms" with React.js and Material Ui. I have implemented the Stepper component for this purpose, but when I click the "Next Button", the button is hidden but the next step content with the text ...

Why is the result of this specific JavaScript code a string?

let x = 2, y = x = typeof y; console.log(x); // >> 'string' Could you explain why the value of x ends up being a string in this code snippet? ...

The issue of why iterating over an array of objects does not function properly in React JS

P.S: I have made some modifications to the code below. Extracting the doctor's value from JSON data, updating the state, and then mapping it to print out the values. Initially, there is only one doctor, but in the future, there could be more than one ...

What is the best way to convert a series of sentences into JSON format?

I'm struggling with breaking down sentences. Here is a sample of the data: Head to the dining room. Open the cabinet and grab the bottle of whisky. Move to the kitchen. Open the fridge to get some lemonade for Jason. I am looking to format the outc ...

Heroku, paired with Redis, consistently records information and monitors for socket errors

I have recently implemented Heroku, Redis, and Node.js with Express in my project. I successfully set up caching using Redis in my Node app, but I noticed continuous logs appearing in the Heroku log console in this format: 2024-04-21T18:54:09.000000+00:00 ...

Repetitive points detected in three.js typography

I converted a ttf font to a js font for three.js using Facetype.js, but I am encountering multiple duplicate point errors such as: THREE.Shape: Duplicate point 41.52:10.928 THREE.ShapeUtils: Unable to triangulate polygon! in triangulate() What steps ...