Shrinking Font Sizes Down by Subsetting

Feb 16, 2022
 — by 
Kris Kratz
 in 

After designing websites for long enough, any developer will start to work on improving page load times. Too many files, even if they are small, will increase load times… Same with large files, filled with unused information.

You start wanting to optimize the load time. It creates a better user experience. So you restrict the scripts to certain pages where they are needed. You combine the CSS files. You start to look into minification, defer, async, delay, gzip, and the rest. Maybe switching from PNG and JPG to WebP and SVG wherever you can.

Along the way you’ll look see those fonts, listed in you browser’s dev tools network tab. And you might wonder if there’s anything you can do about them, like did.

Luckily, and obviously, there is a lot we can do to manage them.

Static and Variable Fonts

The other thing to realize is there are static fonts with different weights and axis, and variable fonts, that have everything contained in a single file. Variable font files are rather large compared to any individual static font file, but usually smaller than the others in aggregate.

If you’re only using a couple of weights, then you’re going to to do fine just using a couple of static files, but if you want everything available at any given point, then you’re going to want the variable font version.

Font Subsetting.

The process of subsetting creates a new font file with only the glyphs (characters) you want in the file. For example if I’m just writing in plain English with numbers, the Cyrillic and Greek glyphs in the font file are unused, and wasting bandwidth. My site will be more efficient if I just serve the characters my site uses, and remove the rest.

These two websites allow you to subset static fonts for free:

Everything Fonts – https://everythingfonts.com/subsetter

Font Squirrel – https://www.fontsquirrel.com/tools/webfont-generator

If you want to subset a variable font, you need to use software like:

Glyphanger – https://github.com/zachleat/glyphhanger (Instructions for setting it up on MacOS)

FontTools – https://github.com/fonttools/fonttools

Glyphanger is easier, as it’s subsetting features are just a wrapper for pyftsubset that comes with Font Tools, and a required for Glyphanger to function.

Now basic Latin Characters are usually just 95 glyphs, Unicode characters U+0020 to U+007E

FontSquirrel.com adds in a few extra characters to their basic Latin set, U+2013, U+2014, U+2018, U+2019, U+201C, U+201D, and U+2026. These are basically two hyphens, the different curly quotes, the three dots.

They allow you to select other glyphs individually as well, for example you might want to include non-breaking space (HTML:  ), U+00A0, or the symbol for micro (μ) U+00B5.

Font File Formats

First thing is to realize there are different types of font files. TTF, WOFF, and WOFF2 are the most commonly supported today. There are archaic font types like EOT and SVG, but unless you’re developing for Internet Explorer or iOS 2, you can stick to the first three I mentioned.

Setting it all up in CSS

The last piece you need to know, is that when assigning your font face in CSS to provide the unicode-range, so when characters appear, outside of the available glyphs in your custom font subset, the backup or default font is used.

I like to enter all font files in one string with listing each format, in this order WOFF2, WOFF, and TTF. If I list them individually I place them in reverse order, with the WOFF2 file last, because the last one overrides the others in CSS. I don’t understand why every other website puts it at the top and TTF at the bottom. That always loads the TTF in my browsers, and it’s a much larger file.

Also, I use Arial and Sans-Serif as backups for glyphs that aren’t in my font file, and if the browser doesn’t support the font file types I provide.

Here is an example for a static font:

@font-face {
  font-family: Rubik;
  src: url('../fonts/rubik-static/rubik-regular-webfont.woff2') format("woff2"), url('../fonts/rubik-static/rubik-regular-webfont.woff') format("woff"), url('../fonts/rubik-static/rubik-regular-webfont.ttf') format("truetype");
  font-weight: normal;
  unicode-range: U+0020-007E, U+00B5, U+2013, U+2014, U+2018, U+2019, U+201C, U+201D, U+2026;
}
html {
  font-family: Rubik, Arial, Sans-Serif;
}

Here’s an example for a variable font:

@font-face {
  font-family: Rubik VF;
  src: url(../fonts/rubik-variable/Rubik-Italic-VariableFont_wght-subset.woff2) format("woff2"), url(../fonts/rubic-variable/Rubik-Italic-VariableFont_wght-subset.woff) format("woff"), url(../fonts/rubic-variable/Rubik-Italic-VariableFont_wght-subset.ttf) format("truetype");
  font-weight: 1 999;
  font-stretch: 75% 125%;
  font-style: italic;
  unicode-range: U+20-7E, U+A0, U+B5, U+2013, U+2014, U+2018, U+2019, U+201C, U+201D, U+2026;
} 
html {
  font-family: Rubik VF, Arial, Sans-Serif;
}

Finally if I want to use a variable font, but enter a static font as a fallback, I’ll enter it like this:

@font-face {
  font-family: 'Rubik';
  src: url('../fonts/rubik-static/rubik-regular-webfont.woff2') format("woff2"), url('../fonts/rubik-static/rubik-regular-webfont.woff') format("woff"), url('../fonts/rubik-static/rubik-regular-webfont.ttf') format("truetype");
  font-weight: 400;
  unicode-range: U+0020-007E, U+00B5, U+2013, U+2014, U+2018, U+2019, U+201C, U+201D, U+2026;
}
@font-face {
  font-family: 'Rubik';
  src: url('../fonts/rubik-static/rubik-bold-webfont.woff2') format("woff2"), url('../fonts/rubik-static/rubik-bold-webfont.woff') format("woff"), url('../fonts/rubik-static/rubik-bold-webfont.ttf') format("truetype");
  font-weight: bold;
  unicode-range: U+0020-007E, U+00B5, U+2013, U+2014, U+2018, U+2019, U+201C, U+201D, U+2026;
}
@font-face {
  font-family: 'Rubik VF';
  src: url(../fonts/rubik-variable/Rubik-VariableFont_wght-subset.woff2) format("woff2"), url(../fonts/rubic-variable/Rubik-VariableFont_wght-subset.woff) format("woff"), url(../fonts/rubic-variable/Rubik-VariableFont_wght-subset.ttf) format("truetype");
  font-weight: 1 999;
  font-stretch: 75% 125%;
  font-style: normal;
  unicode-range: U+20-7E,U+A0,U+B5,U+2013,U+2014,U+2018,U+2019,U+201C,U+201D,U+2026;
}
@font-face {
  font-family: 'Rubik VF';
  src: url(../fonts/rubik-variable/Rubik-Italic-VariableFont_wght-subset.woff2) format("woff2"), url(../fonts/rubic-variable/Rubik-Italic-VariableFont_wght-subset.woff) format("woff"), url(../fonts/rubic-variable/Rubik-Italic-VariableFont_wght-subset.ttf) format("truetype");
  font-weight: 1 999;
  font-stretch: 75% 125%;
  font-style: italic;
  unicode-range: U+20-7E,U+A0,U+B5,U+2013,U+2014,U+2018,U+2019,U+201C,U+201D,U+2026;
} 
html {
  font-family: Rubik, Arial, Sans-Serif;
}
@supports (font-variation-settings: normal) {
  font-family: Rubik VF, Arial, Sans-Serif;
}

When setting up a variable font, if the file is very large, and you don’t need to require that font face. A good work around is to prevent it loading on devices that have reduced data activated. This is especially useful for variable fonts because the file size can be massive. Take Inter for example, the uncompressed variable font file (Inter.ttf v 3.19) weighs in at 805 KB!!!

That is a massive font file.

So lets help out people with reduced data turned on, and save our server the bandwidth too.

@media (prefers-reduced-data: no-preference) {
  @font-face {
    font-family: Inter;
    src: url('../fonts/Inter.ttf');
  }
}

The Results

You can see from my results below, the font subset file sizes for Inter are significantly smaller. The WOFF2 file weighs in at a meager 54 KB, about 7% of the original TTF. That is over a 90% reduction. Exactly what I want.

Massive size reduction

Simply converting the entire Inter variable font from TTF to WOFF2 reduces the file size a little more than 50%. Inter includes a lot of features and character sets, that results in a huge file. So subsetting is practical and effective.

The Command

The terminal command I use to convert the font:

glyphhanger --subset=inter.ttf --US_ASCII --string --css --formats=ttf,woff2,woff --whitelist=U+2013,U+2014,U+2018,U+2019,U+201C,U+201D,U+2026,U+00A0,U+00B5 

–subset selects the font file

–US_ASCII is the subset for standard US letters, numbers, and punctuation

–whitelist I added in some more characters that are used in my project

–string produces a list of Unicode characters for me to see

–css produces a css file and prints the css to the terminal

–formats sets the file types I want it to produce

A note on Google Fonts

Google fonts has an useful API that can subset for you automatically, however, I prefer to provide the font directly from my site (and CDN) so I didn’t dive too deeply into customizing the use of Google Fonts through CSS. In my brief tests I noticed Google Fonts converted variable fonts to static ones. I tested with Inter Variable, for basic Latin, and several weights, but it gave me back only a normal font weight in various file formats. Note, when using Google Fonts it is recommended to setup “preconnect” tags in the page head to two google font domains.

Wrapping Up

It’s easy to understand after you go through this process a couple of times.

  1. Decide on how many font weights and styles your project needs
  2. Subset the fonts and generate the font file formats you need
  3. Upload the fonts to your server, and type up the CSS.
  4. Look at the results in your browser’s dev tools and a page speed analysis.
  5. Rejoice!

Well, that’s wraps up my brief introduction to improving user experience on your website by subsetting fonts.

Have fun!