How to subset fonts with unicode-range

Ever since it became feasible to use more than a limited range of web safe fonts, web designers have been merrily unleashing their creativity, using weird and wonderful typefaces without having to resort to imagery.

And ever since then, the people who care about web performance have been trying to curb some of that enthusiasm. This is because a custom font is an extra resource that the browser has to download for the page to be displayed (different browsers do different things in terms of what they’ll display while waiting for a font to load).

We’ve given a broad outline of how to minimise the impact of custom fonts on performance in a previous post. This time, we’re going to focus on one technique: using the unicode-range CSS descriptor for subsetting.

Subsetting?

If you already know what subsetting is, feel free to skip on to the next section. If not, read on.

The chances are that your website doesn’t contain every character from every alphabet from across the world. So if you’re using a custom font, you probably don’t want it to include a lot of redundant characters you’re never going to need. Subsetting is a way to load only those characters you’re actually going to use on your site. For example, unless your website is in Greek, you don’t want to make visitors download Greek characters.

How does unicode-range work?

The unicode-range descriptor sits inside an @font-face rule. It defines the set of Unicode character code points to which that rule applies.

Here’s a simple example that limits the @font-face rule to the ampersand character (U+0026):

@font-face {
  font-family: 'myfont';
  src: local('myfont'), url(http://an-external-url/fonts/a-custom-font.woff2) format('woff2');
  unicode-range: U+0026;
}

This tells the browser that myfont is needed only when there’s an ampersand on the page.

For example, imagine you need myfont for ampersands in <h1> headings, but you need the other characters to be in Arial.

The <h1> rule in your CSS might look something like this:

h1 {
font-family: myfont, Arial, sans-serif;
}

If the <h1> heading on your web page is:

Apples & pears

then myfont is a) downloaded and b) applied only to the ampersand character.

If the heading is:

Apples and pears

then it isn’t downloaded.

Simple!

Except that it’s only half the story.

In the first example, myfont is applied only to the ampersand, and it’s tempting to infer that this is because only the ampersand character was downloaded from the font file.

However, that’s not the way it works.

On its own, unicode-range can’t do any subsetting. It can’t tell the browser to download only parts of a file (I think some tutorials, such as this one, can leave you with the impression that that’s exactly what it does).

Instead, what you need to do is create a separate, subsetted font file. In this example, the file:

http://an-external-url/fonts/a-custom-font.woff2

would need to contain only the ampersand character.

So what use is unicode-range if all the subsetting is taken care of in the file?

Well, it’s useful mainly because it’s an easy way to use different fonts for different characters in the same element. It’s also useful because it can determine whether the font file is needed at all. In our example, if there’s no ampersand on the page, the file won’t be downloaded (depending on the browser – more on this later). This is helpful if the content of the <h1> heading varies from time to time.

We could take this further. Imagine a website (perhaps a travel site) that’s in English but that occasionally uses characters from other alphabets. We could define multiple @font-face rules, with various subsets of the custom font.

For example:

@font-face {
  font-family: 'myfont';
  src: local('myfont'), url(http://an-external-url/fonts/a-custom-font-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
/* Latin */
}
@font-face {
  font-family: 'myfont';
  src: local('myfont'), url(http://an-external-url/fonts/a-custom-font-greek.woff2) format('woff2');
  unicode-range: U+0370-03FF; /* Greek */
}

In fact, this is exactly what happens when you use Google Fonts. For example, try navigating to http://fonts.googleapis.com/css?family=Open+Sans in Chrome.

Browser support

It’s worth highlighting that browser support for unicode-range is currently limited. It works in Chrome, but in Firefox it has to be enabled (by setting layout.css.unicode-range.enabled to true in about:config). It sort of works in Internet Explorer too – just not in a way that’s helpful for performance. In IE, unicode-range affects which characters from a font are used, not which font files are downloaded. This makes unicode-range redundant for subsetting in IE.

What about Google Fonts?

Google Fonts rather cleverly adapts to the browser and uses unicode-range for subsetting when it can. For example, if you open http://fonts.googleapis.com/css?family=Open+Sans&subset=cyrillic in Chrome it will look exactly the same as http://fonts.googleapis.com/css?family=Open+Sans. In both cases, it actually loads the @font-face rules for multiple character sets, not just the Cyrillic one, and relies on unicode-range for subsetting.

Now try opening these files in Internet Explorer. This time, you’ll get just one @font-face rule, each referring to a different woff file. It’s worth noting that the CSS file you get in Chrome is slightly larger as a result. It gives you access to various subsetted font files (even if you never download them), adding a small performance overhead.

The point to take away…

If your page has the potential to use multiple subsets of a font, unicode-range is a useful way to help you manage them. It’s just important to remember a) that you can’t subset a font just using unicode-range and b) that different browsers do different things with unicode-range.


Alex Painter

Alex is a member of the professional services team at NCC Group Web Performance, helping organisations to deliver a better online experience for their customers.

Leave a Reply

Your email address will not be published. Required fields are marked *