I’ll go through the process of slimming down Iosevka to use in your site. I’ll show how you build Iosevka and also how you use pyftsubset
to select only a subset of the unicode characters that you use on your site.
Originally, I downloaded the full PkgWebFont-Iosevka-33.2.2.zip
from the archive. I then unzipped it and checked its logical size.
aidenfoxivey@MacBookPro ~/D/woff2> wc -c iosevka-regular.woff2
1491940 iosevka-regular.woff2
This is no good!
Let’s assume that we have a 4G connection that with 4 Mbps down. This means it can move 500000 bytes per second.
$$ \frac{1491940 \textrm{ bytes}}{500000 \textrm{ bytes per second}} \approx 2.98388 \textrm{ seconds} $$
In the grand scheme of things, 3 seconds is barely anything to worry about, but this is the web. Here, things are usually measured in milliseconds.
“Wait!” I hear you say, “but you’re forgetting about compression!”
Okay, maybe I am forgetting about compression a little bit. Thanks to web servers, we usually can assume that data is not transmitted at full size. In NGINX, (a popular web server), this is a configurable feature.
This compression isn’t free though, since the webserver must usually compress at runtime.
That said, let’s first ask whether we actually need to retain the entire font file before we offload the work of compression onto the webserver.
Another thing to consider is that the client (the computer you’re connecting to the site) only needs to collect the font file once, since it can have it cached for use later.
Given those two facts though, we still need to acknowledge that the first load is the most important one. Most people aren’t going to be visiting my site that often.
A related mitigation for this problem is using a provider like Google Fonts. Font providers like Google Fonts cache fonts for you on Google content delivery networks. When someone uses their computer to visit your site, their computer’s browser will reach out to Google Fonts to download this. If it has already downloaded that font, then it will not need to continue because it’s already cached in the browser.
Suppose, though, that we care about those who are visiting the site for the first time and do not have the benefit of having fast Wi-Fi. For those people, it will make a substantial difference to cut down the amount of data that our font is taking up while being transmitted.
Naively, and from an Anglo-centric perspective, one can imagine that fonts are simply a mapping from each English character, number, and punctuation mark to some bitmap.
Keep in mind though that there are now many languages supported by fonts as well as ligatures and special font features that can be turned on optionally. It’s also worth mentioning that there’s a feature called “hinting”, which is used to display the font on relatively low resolution displays.
Some of these features will be important, but others, like the ability to render Zulu characters, are not (at least for me). Unicode, which is the way to encode human languages on a computer, also includes symbols. Many of these symbols are not going to be necessary, perhaps for your blog and certainly not for mine.
First, let’s step through how you can build Iosevka.
Run git clone https://github.com/be5invis/Iosevka.git
first.
Now, let’s create a file called private-build-plans.toml
.
Here’s what I put in mine:
[buildPlans.iosevka-web]
family = "Iosevka Web"
spacing = "normal"
serifs = "sans"
noCvSs = true
exportGlyphNames = false
noLigation = true
[buildPlans.iosevka-web.variants]
inherits = "ss04"
[buildPlans.iosevka-web.weights.regular]
shape = 400
menu = 400
css = 400
[buildPlans.iosevka-web.weights.bold]
shape = 700
menu = 700
css = 700
noCvSs
means “don’t build all the stylistic sets”, which should cut down on including bits that won’t be displayed. (We won’t be allowing the user to choose which font style we want unless they use their own custom CSS.)
I chose the “ss04” variant of the font since I preferred displaying the asterisk to be vertically centred. Other than that, I chose this “stylistic set” arbitrarily. You can choose another one if you’d prefer it.
I also set noLigation
, which prevents building ligatures for the font. Ligatures are a way of displaying multiple adjacent characters that is not just putting them side by side. For example, putting the characters =>
can be rendered as an arrow instead of “equals less-than”. It’s worth considering that these sets are not always suited for the kind of source code you might write. There are also non code related ligatures, but I won’t consider them for the sake of this article.
Now, why do I avoid using ligatures in the blog?
I’m not opposed to using them in general, but I do find that it can obscure meaning for people who haven’t heard of ligatures before. They may mistake them for a complicated unicode character, which goes contrary to my goal. I also do not want to fiddle with them when I write in a computer language that uses a different set of ligatures to be sensibly represented.
Let’s continue on to building it - you’ll need to have ttfautohint and nodejs installed to successfully build Iosevka. You’ll then run npm install
and build with npm run build -- ttf::iosevka-web
.
Weirdly, Iosevka took a long-ish time to build. I’m not actually sure why it’s so CPU intensive. My guess is that the answer is just nodejs
, but I won’t speculate.
aidenfoxivey@MacBookPro ~/s/Iosevka (main)> sysctl -a | grep machdep.cpu
machdep.cpu.cores_per_package: 8
machdep.cpu.core_count: 8
machdep.cpu.logical_per_package: 8
machdep.cpu.thread_count: 8
machdep.cpu.brand_string: Apple M1 Pro
One thing to keep in mind is that building the WOFF2 versions of the fonts requires building the TTF fonts. As a result, building the TTF fonts are a strict subset of the work required to build the WOFF2 fonts.
If you read the specification for WOFF2, you’ll notice that this format is actually just a container for other formats, but with compression. This is exactly why there are TTF fonts being built before the WOFF2 fonts are built. In order to build the WOFF2 fonts, we need to build the TTF fonts, compress them, and add metadata.
Let’s see how large the files are now that we’ve built them. We should keep in mind that this is after compression has been added to them as per the WOFF2 format.
aidenfoxivey@MacBookPro ~/s/Iosevka (main)> wc -c dist/iosevka-web/WOFF2/iosevka-web-regular.woff2
463152 dist/iosevka-web/WOFF2/iosevka-web-regular.woff2
This is better! This should take under a second to send over the internet based on the speed we calculated earlier. That said, it’s still suspiciously large. Let’s investigate further to see whether we can cut down the size.
Let’s say that we want to figure out what unicode character ranges we are already using right now. Let’s use glyphhanger to scan your current website. (We will imagine that it’s example.com)
You can do something like:
glyphhanger http://example.com
This gives us a range of unicode characters. For my website, the unicode range is U+A,U+20-23,U+25-5F,U+61-7E,U+B1,U+2019,U+201C,U+201D
.
We then go about using pyftsubset
pyftsubset \
dist/iosevka-web/TTF/iosevka-web-regular.ttf \
--output-file="iosevka-regular.woff2" \
--flavor=woff2 \
--layout-features="kern,clig" \
--unicodes="U+A,U+20-23,U+25-5F,U+61-7E,U+B1,U+2019,U+201C,U+201D"
I ran this command and got the outputted iosevka-regular.woff2
! Note that we do not have to build the WOFF2 font originally because we use the TTF font in building the subset.
(Iosevka) aidenfoxivey@MacBookPro ~/s/Iosevka (main)> wc -c iosevka-regular.woff2
9096 iosevka-regular.woff2
Woah! That’s a fantastic reduction down to about 9 kilobytes.
All in all, our techniques gave us a 99% decrease in the total amount of data sent over the wire!