103

I want to defer font loading on my site inspired by deferred font loading logic for Smashing Magazine.

Main part of this is converting fonts to base64 and preparing your CSS file. My steps so far:

  1. Pick fonts on Google Web Fonts and download them.
  2. Use Font Squirrel Webfont Generator to convert downloaded TTF files to CSS file with base64 embedded WOFF fonts (Expert options -> CSS -> Base64 Encode).
  3. Load CSS file async (not important here).

CSS snippet for Open Sans Bold:

@font-face {
  font-family: 'Open Sans';
  src: url(data:application/x-font-woff;charset=utf-8;base64,<base64_encoded>) format('woff');
  font-weight: 700;
  font-style: normal;
}

The problem is, that converted fonts look a lot different. Take a look at Open Sans Bold: GWF vs base64 rendering comparison

Especially notice accents being way off and absolutely horrible letter a. Other font families and variants look very noticeably different as well (size and shape distortions, etc.).


So the question is: How do you properly encode TTF files from Google Web Fonts (or other source) to base64 format and use it in a way that the result is identical to the original file?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Jan Peša
  • 6,760
  • 4
  • 27
  • 32
  • 1
    Found an [online tool](https://amio.github.io/embedded-google-fonts/) for converting Google fonts to Base64 – vsync Aug 29 '22 at 08:12

5 Answers5

150

In the Font Squirrel Expert options, make sure to set the 'TrueType Hinting' option to 'Keep Existing'. Either of the other options will cause the TrueType instructions (hints) to be modified, which will in turn affect the rendering of the font.

Alternatively, if you're happy with the rendering of the font directly from GWF, you can just take that file and do the base64 encoding yourself. In OS X or Linux, use the built-in base64 command in Terminal/shell:

$ base64 -i myfont.ttf -o fontbase64.txt

For Windows, you'll need to download a program to encode in base64 (there are several free/Open Source tools available). Copy the contents of that file, then use in your CSS as:

@font-face {
    font-family: 'myfont';
    src: url(data:font/truetype;charset=utf-8;base64,<<copied base64 string>>) format('truetype');
    font-weight: normal;
    font-style: normal;
}

(Note that you may need to make some adjustments to the various @font-face info to match your particular font data; this is just an example template)

djangodude
  • 5,362
  • 3
  • 27
  • 39
  • 29
    I noticed it is important to keep the `src:` line - including the base64 string - one single line. The base64 command might insert line breaks by default. That can be disabled using the `-w` parameter: `base64 -w 0 font.ttf > font_base64.txt`. (for the base64 utility from GNU coreutils) – zpea Apr 26 '17 at 02:34
  • can we do direct ttf to base64 or need to convert woff file ? – Bhupinder kumar Jul 06 '18 at 07:19
  • I remember the built-in base64 command working as described here, but now for whatever reason the parameters now require you to provide `-i` before the file path (stdin is now the default input), so `base64 -i font.ttf > font_base64.txt` instead of `base64 font.ttf > font_base64.txt`. – Steven Jul 11 '23 at 18:44
  • Thanks, I updated the answer to use `-i` for input and `-o` for output (instead of `>` redirect, which should still work but `-o` is probably a better way to do it). – djangodude Jul 12 '23 at 20:53
102

Use this code snippet to base64 encode your font directly in the browser (OS independent, no need to install anything)

function base64convert (files) {
  console.clear()
  const reader = new FileReader()
  reader.onload = (e) => {
    console.log(e.target.result)
  }
  reader.readAsDataURL(files[0])
}
<input type="file" onchange="base64convert(this.files)">

Then copy the output and paste it into your CSS:

@font-face {
    font-family: 'myfont';
    src: url("<<copied base64 string>>");
}
Ilyich
  • 4,966
  • 3
  • 39
  • 27
  • Also see [FileReader.readAsDataURL](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) – djvg Jul 18 '22 at 12:28
3

A much simpler way to get the base64 code for Google Fonts is to use the following tool: https://amio.github.io/embedded-google-fonts/

input the URL to your font and you get the base64 code back straight away :)

Oli C
  • 1,120
  • 13
  • 36
0

Simple Nodejs Script That works for .woff fonts. Just change the extension to your font files extension and it will work with other extensions as well

const { readdirSync, mkdir, existsSync, readFileSync, writeFileSync } = require("fs")
const { resolve } = require("path")

const woffDirPath=resolve(".", "public", "assets", "fonts", "Typold", "woff")
const files = readdirSync(woffDirPath)
const base64Path = resolve(".", "public", "assets", "fonts", "Typold", "base64")
if (!existsSync(base64Path)) mkdir(base64Path, (err) => {
    console.log("Error on dir creattion", err);
});

for (let file of files) {
    if (file.includes(".woff")) {
        const fileData = readFileSync(resolve(woffDirPath, file), { encoding: "base64" })
        writeFileSync(resolve(base64Path, file.replace(".woff", ".txt")), fileData)
    }
}
console.log("done");
Mr Peter
  • 1
  • 2
  • Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. **Would you kindly [edit] your answer to include additional details for the benefit of the community?** – Jeremy Caney Jul 27 '23 at 00:31
0

In addition to the previous answers – some warnings:

Dedicated Font converters might drop features or change data

Generators/Converters like fontquirrel or transfonter will actually parse and rebuild your font file.

This process might also introduce changes due to optimization settings like hinting data that impacts font rendering.

2023: Many converters like fontsquirrel and transfonter don't support variable fonts

When using these tools to get a base64 data URL you might lose variable font features (design axes related data is stripped) when generating a data URL.

That doesn't mean, you shouldn't use these converters at all - they mostly work well if

  • you thoroughly check all conversion presets
  • you need static font support and need to subset the glyph range

A generic base64 converter won't manipulate any font data

As demonstrated by Ilyich: You can use any base64 encoder. (E.g browserlings converter.

Here's another JS helper example based on these steps:

  • fetch external CSS URL
  • find font URLs via regex
  • fetch all URLs as a blob()
  • convert blob to base64 data URL
  • replace external URLs with base64 data URLs

inputUrl.addEventListener("input", async(e) => {
  let url = e.currentTarget.value;
  let css = await getdataUrlsFromCss(url)
  // output and download button
  fontCss.value = css;
  btnDownload.href = URL.createObjectURL(new Blob([css]));
});

// init
inputUrl.dispatchEvent(new Event("input"));


async function getdataUrlsFromCss(url) {
  // fetch external css
  let css = await (await fetch(url)).text();

  // find external urls in css via regex
  let urls = css.match(/https:\/\/[^)]+/g);

  for (let i = 0; i < urls.length; i++) {
    let url = urls[i];

    // fetch font file
    let blob = await (await await fetch(url)).blob();

    // create base64 string
    let base64 = await blobToBase64(blob);

    //replace urls with data url
    css = css.replaceAll(url, base64);
  }

  return css;
}


/**
 * fetched blob to base64
 */
function blobToBase64(blob) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  });
}
body {
  font-family: sans-serif
}

legend {
  font-weight: bold;
}

fieldset {
  margin-bottom: 1em;
}

fieldset input,
fieldset textarea {
  border: none
}

input {
  width: 100%;
  display: block;
  margin-bottom: 1em;
}

textarea {
  width: 100%;
  min-height: 20em;
}

.btn-default {
  text-decoration: none;
  border: 1px solid #000;
  background: #ccc;
  color: #000;
  font-weight: bold;
  padding: 0.3em;
}
<h1>Fonts to base64</h1>
<fieldset>
  <legend>Enter CSS Url</legend>
  <input type="text" id="inputUrl" value="https://fonts.googleapis.com/css2?family=Open+Sans:ital@0;1&display=swap">
</fieldset>

<fieldset>
  <legend>New Css</legend>
  <textarea id="fontCss"></textarea>
  <p><a class="btn-default" id="btnDownload" href="#" download="fontface.css">Download css</a></p>
</fieldset>

For testing: Codepen example

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34