Subsetting web fonts

Using web fonts can have a big impact on the performance of your website. Subsetting fonts helps to improve it. This is also relevant for the environmental impact websites have.

Good fonts support more than just ASCII characters that are used for english texts. A lot of western languages use accents. Also many languages are not based on the latin alphabet, so many fonts also support, for example, greek and/or cyrillic characters.

Using the full font most probably means that you are loading unused characters. Font files can be as large as 300 KB. If you also use different styles (regular, bold, italic) or different weights, you quickly load up to 1 MB of fonts. And this is multiplied, if you use more than 1 font. A subsetted font is around 20-30 KB for western languages, so this results in a huge saving.

In the past, I often used the google-webfonts-helper to download a latin-1 subset of Google Fonts. Until I discovered that some common characters are not part of latin-1 character set. These included the Euro sign (€), quotes (‘ ’ ‚ “ ” „), dashes (– —) or the ellipsis (…). Also, if the font I wanted or needed to use, wasn’t available as a Google Font, I had to convert it myself.

Subsetting is also very useful if you only need a narrow set of characters. For example if you use a icon font and only use some of the icons. Or if you only want digits, which I needed for creating SVG images on the SVG Placeholder website.

Custom subsetted fonts need to be self-hosted. Self-hosting web fonts is better for performance anyway, because you save DNS requests, SSL negotiations, and HTTP connections. Also using multiple domains is considered to be an anti-pattern with HTTP/2. It also improves privacy and can save you from fines in Germany.

A while ago, I started subsetting fonts on the command line with fontTools.

If you prefer a graphical interface, you can try the Font Squirrel Webfont Generator.

With static site generators you could integrate subfont into your build process.

Required tools

fontTools is a Python library, so you will need to have Python installed.

Windows

For installing (and updating) command line tools, I like to use Scoop. Alternatively you can install Python with Chocolatey or the official Python installer.

scoop install python
pip install fonttools

MacOS

Make sure you already have Homebrew installed.

brew install fonttools

Linux

Your Linux distribution probably has fonttools already pre-packaged. So use the default package manager to install it.

If not, you will have to install it with PIP. You also need to have support for compression with Brotli and Zopfli in Python installed.

pip install fonttools

Subsetting

To subset a font you have to use the pyftsubset command to create Web Open Font Format (WOFF) fonts in version 1 and 2.
pyftsubset font.ttf --flavor=woff --output-file="font.woff" --with-zopfli --unicodes="U+0020-007F,U+00A0-00FF,U+20AC,U+20BC,U+2010,U+2013,U+2014,U+2018,U+2019,U+201A,U+201C,U+201D,U+201E,U+2039,U+203A,U+2026,U+2022"

pyftsubset font.ttf --flavor=woff2 --output-file="font.woff2" --unicodes="U+0020-007F,U+00A0-00FF,U+20AC,U+20BC,U+2010,U+2013,U+2014,U+2018,U+2019,U+201A,U+201C,U+201D,U+201E,U+2039,U+203A,U+2026,U+2022"

If you are using variable fonts, you only have to use the woff2 format. Browsers that don’t support woff2, also don’t support variable fonts.

Below is a table explaining the character ranges that I used. You can adapt them to your own needs.

Character(s)Unicode (range)
Basic LatinU+0020-007F
Latin-1 SupplementU+00A0-00FF
Euro signU+20AC
ApostropheU+20BC
HyphenU+2010
En dashU+2013
Em dashU+2014
Left quoteU+2018
Right quoteU+2019
Single low quoteU+201A
Left double quoteU+201C
Right double quoteU+201D
Double low quoteU+201E
BullitU+2022
EllipsisU+2026
Single left pointing angleU+2039
Single right pointing angleU+203A

More information on Unicode characters can be found on the following pages:

CSS

In your stylesheets you now can use the subsetted font. The woff2 font should be listed before the woff font. That way the woff2 will be loaded, if the browser supports it. Older browsers will load the woff format.

To ensure the correct characters are used, you should declare the unicode-range.

@font-face {
  font-display: swap;
  font-family: "Font Name";
  font-style: normal;
  font-weight: 400;
  src: url('subsetted-font.woff2') format('woff2'),
    url('subsetted-font.woff') format('woff');
  unicode-range: U+0020-007F,U+00A0-00FF,U+20AC,U+2010,U+2013,U+2014,U+2018,U+2019,U+201A,U+201C,U+201D,U+201E,U+2039,U+203A,U+2026,U+2022;
}

Nowadays you can probably just use woff2 only, because all current browsers have support for WOFF 2.0.

For better performance you should add font-display: swap. This causes a flash of unstyled content (FOUC) on inital page load. If you prefer, you can use font-display: optional instead. In this case you get invisible text on initial load, but falls back to the default font, if downloading the web fonts takes too long. Without font-display you will get blank text, until the web fonts have finished downloading.

For variable fonts you only have to use woff2 fonts. You should add woff2 supports variations and woff2-variations format declarations for better browser compatibility.

@font-face {
  font-display: swap;
  font-family: "Variable Font Name";
  src: url(subsetted-variable-font.woff2) format("woff2 supports variations"),
    url(subsetted-variable-font.woff2) format("woff2-variations");
  unicode-range: U+0020-007F,U+00A0-00FF,U+20AC,U+2010,U+2013,U+2014,U+2018,U+2019,U+201A,U+201C,U+201D,U+201E,U+2039,U+203A,U+2026,U+2022;
}