6

I want to render a FontAwesome icon as an SVG dynamically provide it as an image source using Javascript. I want my output to be something like this

output

PS - I will draw the circle myself. Just need a way to render the icon.

So far, I have tried this approach:

function addSVG() {

  var ele = document.getElementById("svg");

  var svg = `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">
        <text x="4" y="15" style="font-family: FontAwesome" fill="red">&#xf007;</text>
    </svg>`

  var output = 'data:image/svg+xml;base64,' + btoa(svg)

  ele.src = output;
}
addSVG()
<head>
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
</head>

<body>
What it should look like:
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">
    <text x="4" y="15" style="font-family: FontAwesome" fill="red">&#xf007;</text>
  </svg>
What it looks like:
  <img id="svg">
</body>

As you can see, without the Javascript (just using SVG in HTML) it works fine.

Frame91
  • 3,670
  • 8
  • 45
  • 89
Janit Mehta
  • 175
  • 1
  • 10
  • Which version of Font Awesome (looks like 4.4, from the CDN reference)? Have you reviewed this already? https://fontawesome.com/how-to-use/on-the-web/setup/getting-started?using=svg-with-js – Tieson T. Jul 05 '18 at 19:29
  • 1
    Yes, the question of which version of Font Awesome would make a big difference on the answer. Font Awesome 5 has built in methods of rendering SVG icons, so the approach in v5 may be very different than if you were limited to v4. And I would wonder whether it's necessary to implement it as an or if your ultimate goal is simply to get the outcome of an svg icon over a background circle. If the latter, then there are much more straightforward approaches in v5. – mwilkerson Jul 06 '18 at 04:04

4 Answers4

4

Why do you need it in img tag? SVG is an image! You do not need it to put in something else.

May be I do not know something from you, but I think this is just a bad idea from you.

My suggestion: put SVG in HTML with font-awesome icons directly:

Your benefit from this: all modern browsers support it without any limits like in img tags or as background image.

var divSvg = document.getElementById('svg'),
    pics =
    {
        user: {col: '00a', icon: 'f007'},
        home: {col: '08c', icon: 'f015'},
        folder: {col: '88f', icon: 'f07c'},
        gear: {col: '5a0', icon: 'f013'},
        flag: {col: '05a', icon: 'f024'},
        leaf: {col: '080', icon: 'f06c'}
    },
    svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">\
    <text x="4" y="24" fill="COLOR" style="font:24px FontAwesome;cursor:pointer">PIC</text>\
</svg>';

for(var title in pics)
{
    var newSVG = svg.replace('PIC', '&#x'+pics[title].icon+';');
    //we put it in "B" tag to have a title because IE
    //does not support title attribute and title tag in SVG
    divSvg.innerHTML += '<b title="' + title + '">' +
                    newSVG.replace('COLOR', '#'+pics[title].col) + '</b>';
}

divSvg.onclick = function(e)
{
    if(e.target.tagName == 'text')
    {
        document.getElementById('output').innerHTML = 'Was clicked on ' +
                                // we take title from "B" tag:
                                e.target.parentNode.parentNode.title;

        /* if you need you can use:
        switch(e.target.parentNode.parentNode.title)
        {
            case 'user': alert('Here some procedure with user');
            case ...     YOUR CODE
            and so on... YOUR CODE
        }*/
    }
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
<div id="svg"></div><br>
<div id="output">Please click on any icon.</div>
Bharata
  • 13,509
  • 6
  • 36
  • 50
  • The downside of embedding SVG directly in HTML is that you have to serve the content of the entire page as XHTML in order to be compatible, which has slightly different and more strict semantics than HTML, which might break other parts of the page if tags aren't explicitly closed, etc. – Patrick Roberts Jul 06 '18 at 13:54
  • @Bharata Interesting and helpful answer for me – Alexandr_TT Sep 06 '20 at 07:13
3

You can't use an <img> element when referencing an external CSS stylesheet from XML. You need to use an <object> element like this answer recommends, and prepend an <?xml-stylesheet?> processing instruction in order for the SVG+XML blob to be able to find the FontAwesome glyph for the HTML entity &#xf007;.

function addSVG() {
  var ele = document.getElementById("svg");
  var svg = `
    <?xml-stylesheet type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"?>
    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">
        <text x="4" y="15" style="font-family: FontAwesome" fill="red">&#xf007;</text>
    </svg>`
  var output = `data:image/svg+xml;utf8,${svg}`

  ele.type = 'image/svg+xml';
  ele.data = output;
}
addSVG()
<head>
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
</head>

<body>
  What it should look like:
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">
    <text x="4" y="15" style="font-family: FontAwesome" fill="red">&#xf007;</text>
  </svg> What it looks like:
  <object id="svg"></object>
</body>
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
1

You don't need to convert the data into base64. I think the SVG syntax doesn't have any "crazy" characters in it, so it's safe to use.

Set the encoding to UTF-8, and drop the <svg> in there:

'data:image/svg+xml;utf8,<svg ... > ... </svg>'
0

As commented you should first try to find a svg repository (Also described her: "Extracting SVG from Font Awesome".

SVG won't embed any fonts automatically

However you could actually create a self contained svg including the used font.

  1. Find download URL e.g from a cdn like https://www.cdnpkg.com
  2. Download the font
  3. Create a @font-face with a data URL font source via a tool like transfonter - you can also specify a subset to include only the needed glyphs/characters

Example1: svg with embedded font

  <h3>Stand alone SVG</h3>
  
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
    <style>
@font-face {
    font-family: 'FontAwesome';
    src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAM4AAwAAAAAB2gAAALqAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYGYAA8EQgKgSSBPAsMAAE2AiQDFAQgBYhrBzEbnwYRFZyXyH4mZG5HaVZSDpHqn35J3i05r0RQv/bv95yFezfArIE9ADhgtnGpCJuKfy6EnwC3ImZ83+V4KEOEam9MHNuyfeD+rdGUZSQB4P///XqeNSAFvAH8vjMZWDZWidXLI3GaevlggiVGAQ/4AH4S/INug74YVWXlpRcTaANIQsiFrJwV1xXkl/M4CyabdhEXgbfZfmDnLgz4sVGqUQoIAAwQSMjQRSbQRW4ESrmQYjvqGqSz/qT9f9TnRF1LoACtkGMCFgNykAApzwc0l7Q91Gb/ofYHDh1/1arDxdfjxBuooPWl13SXFIIP8ffJV5dfP/XGlQ+2tSgGtDSK60o3lSx1av//4q8+IOz9DcuNXXYvNoSrdzM3Dp9SzRg1Pzg96uxpkmnGaoqVOTFn5sW8uSDu7IupH37IXRsI5FFuu798x5r2k39rtm6Ah/PugOfvzf6w/u7/r6qq7ISmBIIWXVqpv6vrqiL/m1Bkx22HtIhooTiOQJF6Gg4SQmA6gB4xTADGI0BobbwAScNEATLdzRAg19lSAQqdbBKgjIeGeY6gqY0nqLTyNlrr6tP17fTzVybyCquju47QLfYgaReXkBkXN5AbFndRGBLf5ZcwGm4laOqRbqPSJT1Ea6PSB13tTM6qmRIA2/IbdDiyos/KyhZezlkv/9WHnje0rD6Y6LLvgHF0pSwJqJdwvmfRUSjZyYExUKzGSV+2BCQcNsUVRvbDMORKxzpxr2HFn2xJVxKzqIvKsizj6iI7JqO4Wnavr6rBQadXur+uZ5th/JmqKC1t+Q06cJ/9yHPcUNGqbSmGRky3gMC8kan90NvVBzJ0OKN4tWxyHkaWFNawhMvxfItReuI7NFVIJwWKz3fqsOrYggXl5udOsoM4uAS1TrTgadPuzgcTXpeXkGy/NpyhY9MQW2uqxysFXk8m9xPScjxThMjUfUNlirXjUuu8CznOuFQ1hLLrtiRf0Av0HKMvjkUtkfLdDQA=') format('woff2');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

    </style>
    <circle cx="50%" cy="50%" r="50%" fill="red"/>
    <text x="4" y="15" style="font-family: FontAwesome" fill="white">&#xf007;</text>
  </svg>


  <h3>Img with SVG data URL</h3>

<img src="data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cstyle%3E%0A@font-face %7B font-family: 'FontAwesome'; src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAM4AAwAAAAAB2gAAALqAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYGYAA8EQgKgSSBPAsMAAE2AiQDFAQgBYhrBzEbnwYRFZyXyH4mZG5HaVZSDpHqn35J3i05r0RQv/bv95yFezfArIE9ADhgtnGpCJuKfy6EnwC3ImZ83+V4KEOEam9MHNuyfeD+rdGUZSQB4P///XqeNSAFvAH8vjMZWDZWidXLI3GaevlggiVGAQ/4AH4S/INug74YVWXlpRcTaANIQsiFrJwV1xXkl/M4CyabdhEXgbfZfmDnLgz4sVGqUQoIAAwQSMjQRSbQRW4ESrmQYjvqGqSz/qT9f9TnRF1LoACtkGMCFgNykAApzwc0l7Q91Gb/ofYHDh1/1arDxdfjxBuooPWl13SXFIIP8ffJV5dfP/XGlQ+2tSgGtDSK60o3lSx1av//4q8+IOz9DcuNXXYvNoSrdzM3Dp9SzRg1Pzg96uxpkmnGaoqVOTFn5sW8uSDu7IupH37IXRsI5FFuu798x5r2k39rtm6Ah/PugOfvzf6w/u7/r6qq7ISmBIIWXVqpv6vrqiL/m1Bkx22HtIhooTiOQJF6Gg4SQmA6gB4xTADGI0BobbwAScNEATLdzRAg19lSAQqdbBKgjIeGeY6gqY0nqLTyNlrr6tP17fTzVybyCquju47QLfYgaReXkBkXN5AbFndRGBLf5ZcwGm4laOqRbqPSJT1Ea6PSB13tTM6qmRIA2/IbdDiyos/KyhZezlkv/9WHnje0rD6Y6LLvgHF0pSwJqJdwvmfRUSjZyYExUKzGSV+2BCQcNsUVRvbDMORKxzpxr2HFn2xJVxKzqIvKsizj6iI7JqO4Wnavr6rBQadXur+uZ5th/JmqKC1t+Q06cJ/9yHPcUNGqbSmGRky3gMC8kan90NvVBzJ0OKN4tWxyHkaWFNawhMvxfItReuI7NFVIJwWKz3fqsOrYggXl5udOsoM4uAS1TrTgadPuzgcTXpeXkGy/NpyhY9MQW2uqxysFXk8m9xPScjxThMjUfUNlirXjUuu8CznOuFQ1hLLrtiRf0Av0HKMvjkUtkfLdDQA=') format('woff2'); font-weight: normal; font-style: normal; font-display: swap;%0A%7D %3C/style%3E%3Ccircle cx='50%25' cy='50%25' r='50%25' fill='red'/%3E%3Ctext x='4' y='15' style='font-family: FontAwesome' fill='white'%3E&%23xf007;%3C/text%3E%3C/svg%3E" alt="">

The data URL can also be used as css background-image.

Convert icon font to svg <path>

You can also fetch a fontAwesome font file and convert it to a svg using opentype.js.

This way your new icon can also be edited in a graphic application like inkscape, Adobe Illustrator etc.

Example 2: convert icon font

// absolute URL to fontFile
let fontURL =
  "https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/fonts/fontawesome-webfont.woff2";

// font awesome character as entity
let text = decodeHtmlEntity("&#xf007;");

// define size and padding
let fontSize = 100;
let padding = 15;
let iconColor = '#fff';
let bgColor = 'red';

/**
* load font
* convert to svg pathdata
*/
(async () => {
  let font = await loadFont(fontURL);
  
  // get font metrics for positioning and scaling
  let unitsPerEm = font.unitsPerEm;
  let ratio = fontSize / unitsPerEm;
  let ascender = font.ascender;
  let glyph = font.stringToGlyphs(text)[0];
  let bb = glyph.getBoundingBox();
  let xOff = (fontSize - bb.x2 * ratio) / 2;

  // get pathdata
  let width = fontSize + padding;
  let height = fontSize + padding;
  let pathD = font
    .getPath(text, xOff + padding / 2, ascender * ratio + padding / 2, fontSize)
    .toPathData();

  let ns = "http://www.w3.org/2000/svg";
  let svg = document.createElementNS(ns, "svg");
  let circle = document.createElementNS(ns, "circle");
  let path = document.createElementNS(ns, "path");
  //svg.setAttribute('viewBox', `0 0 ${+width.toFixed(3)} ${fontSize}` );
  svg.setAttribute(
    "viewBox",
    `0 0 ${width} ${height}`
  );
  svg.setAttribute("width", width);
  svg.setAttribute("height", height);
  circle.setAttribute("cx", "50%");
  circle.setAttribute("cy", "50%");
  circle.setAttribute("r", "50%");
  circle.setAttribute("fill", bgColor);
  path.setAttribute("d", pathD);
  path.setAttribute("fill", iconColor);
  svg.append(circle);
  svg.append(path);
  preview.append(svg);

  // create dataURL
  let dataURl =
    "data:image/svg+xml, " +
    new XMLSerializer()
      .serializeToString(svg)
      .replaceAll('"', "'")
      .replaceAll("#", "%23");
  imgData.src = dataURl;
})();

/**
 * opentype.js helper
 * Based on @yne's comment
 * https://github.com/opentypejs/opentype.js/issues/183#issuecomment-1147228025
 * will decompress woff2 files
 */
async function loadFont(src, options = {}) {
  let buffer = {};
  let font = {};
  let ext = "woff2";
  let url;

  // 1. is file
  if (src instanceof Object) {
    // get file extension to skip woff2 decompression
    let filename = src.name.split(".");
    ext = filename[filename.length - 1];
    buffer = await src.arrayBuffer();
  }

  // 2. is base64 data URI
  else if (/^data/.test(src)) {
    // is base64
    let data = src.split(";");
    ext = data[0].split("/")[1];

    // create buffer from blob
    let srcBlob = await (await fetch(src)).blob();
    buffer = await srcBlob.arrayBuffer();
  }

  // 3. is url
  else {
    // if google font css - retrieve font src
    if (/googleapis.com/.test(src)) {
      ext = "woff2";
      src = await getGoogleFontUrl(src, options);
    }

    // might be subset - no extension
    let hasExt =
      src.includes(".woff2") ||
      src.includes(".woff") ||
      src.includes(".ttf") ||
      src.includes(".otf")
        ? true
        : false;
    url = src.split(".");
    ext = hasExt ? url[url.length - 1] : "woff2";

    let fetchedSrc = await fetch(src);
    buffer = await fetchedSrc.arrayBuffer();
  }

  // decompress woff2
  if (ext === "woff2") {
    buffer = Uint8Array.from(Module.decompress(buffer)).buffer;
  }

  // parse font
  font = opentype.parse(buffer);
  return font;
}

function decodeHtmlEntity(str) {
  let txt = document.createElement("p");
  txt.innerHTML = str;
  str = txt.textContent;
  txt.remove();
  return str;
}
svg{
  border: 1px solid #ccc;
  width:20em;
  height:auto;
}

img{
  border: 1px solid #ccc;
  width:20em;
}
<h3>SVG</h3>
<div id="preview"></div>
<h3>Img</h3>
<img id="imgData" src="">


<script src="https://unpkg.com/wawoff2@2.0.1/build/decompress_binding.js"></script>
<script src='https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js'></script>
herrstrietzel
  • 11,541
  • 2
  • 12
  • 34