Greg's Thoughts


Self Hosted Fonts

One day I noticed that the blog looked weird on Windows. The header, body and code text were all the wrong font. This is typically an easy to fix problem. The default font that I use is Noto Sans, which looks nice and coincidentally is the default font for Linux Mint 21.2, which is, by even further coincidence, the OS that I use.

Noto Sans, Noto Serif and Noto Sans Mono are easily available by Google Fonts. That would allow users to skip downloading when they've already got the font and it's like what, 6 lines of CSS in my <head> tag? Easy enough. Blog post over. Thanks for reading!

















But there's a problem.

Using Google Fonts requires using external resources; it means that in order for the site to be accessible in Europe I should be notifying users that Google has access to their IP addresses, and that means that my site is less cool. I want this site to be self hosted, without external JS like JQuery, external fonts or libraries. That is, to strip down to the basics of HTML and CSS and build out something quality from scratch.

Using Google Fonts to get TrueType font files

First we'll need to get the fonts. There's a few ways to do this. We can download the font from Google Fonts, but those files are megabyte ZIPs of about 400KiB TrueType TTF files. Each of the TTF files includes glyphs for all languages across the Unicode spectrum. Since Noto itself is supposed to avoid any missing symbols, there are quite a few!

Can we do better?

Well, I don't need all of the symbols. I write in English, which uses the latin alphabet. If I can find libraries with just those glyphs, I'd be cooking with gas.

How do I build Noto Fonts?

For this I'm assuming you're using a debian-based system. In particular, I used Linux Mint 21.2. If you're using Windows or Mac to host the fonts, things should work, but instructions for building are elsewhere. You may want to skip some trouble by using GreatWizard's notosans-fontface project as a starting point. It already contains each of the necessary formats, along with integrations for SCSS, TypeScript and all the other cool web things.

What dependencies do I need for Noto?

You'll want to grab the project for the language you want. I grabbed the latin one. You'll also need venv. I cloned the repo to $HOME/src/notofonts, but any directory will do.

sudo apt install python3-venv
mkdir -p ~/src/notofonts
cd ~/src/notofonts
git clone https://github.com/notofonts/latin-greek-cyrillic

Building Noto

Once we have those, we can make build using the Makefile inside latin-greek-cyrillic:

cd latin-greek-cyrillic
make build

This will kick off the build process for the fonts. It took quite a while on my machine, so go ahead and step away to get a nice hot cup of tea.

Once that's done, we should have fonts sitting in latin-greek-cyrillic/fonts

Hosting

Noto's builder will generate Noto Sans, Noto Sans Mono and Noto Serif fonts as a bunch of TTF files. It makes hinted, unhinted and variable versions of the same font. MDN has some good coverage of what variable fonts can do.

Now that we have these files, the task is to define a @font-face css rule pointing at our TTF files. There's a few more things we want.

If users have Noto installed, they should not have to download the files, so we should take advantage of the local rules if we can. We should be free to use any weight or style allowed by the font.

So let's take a look at style/fonts.css:

@font-face {
  font-family: "Noto Sans";
  src:
    local("Noto Sans ThinItalic"),
    local("NotoSans-ThinItalic"),
    url("/fonts/NotoSans/unhinted/ttf/NotoSans-ThinItalic.ttf") format("truetype");
  font-weight: 100;
  font-style: italic;
  display: swap;
}

All of the Noto Sans in fonts.css are font-family: "Noto Sans";. This can actually be anything; when testing I was using font-family: "Glaka Sans"; as a placeholder to check whether the fonts were being fetched correctly. It just needs to match the font-family entry on our regular elements' CSS.

Next we have src, it supports a comma-delimited list of sources. I search for three things:

  • The conveniently named version of the font
  • The postscript name of the font
  • Finally, the path to the font file from our website.

The first two local entries can be found using fc-scan to read the TTF files we built:

$ fc-scan NotoSans-ThinItalic.ttf
Pattern has 24 elts (size 32)
	family: "Noto Sans"(s) "Noto Sans Thin"(s)
	style: "Thin Italic"(s) "Italic"(s)
	fullname: "Noto Sans Thin Italic"(s)
	width: 100(f)(s)
	fontformat: "TrueType"(s)
	postscriptname: "NotoSans-ThinItalic"(s)

I edited out the redundant parts, but it should be pretty clear what these values are supposed to be; weight, italic, format and postscript name are listed. @font-face even supports unicode-range in case you're using an emoji set or want to specify more languages.

Anyway, just repeat that 45 times, 9 times for each weight on a @font-face; and again for italic. Thankfully Noto Sans Mono doesn't have italic.

Further Improvements: WOFF, WOFF2

Instead of doing all of this yourself, you could pretty easily use GreatWizard's notosans-fontface project, it would have saved a lot of time, but again, this is a DIY Hobby blog. Using cake from the box is not the goal.

I'm pretty happy with the results here, Windows rendering looks good and the site still loads quickly. I don't leak information about my visitors to other sites, and I have full control over what my users see.

But I could certainly do more; fonts could be compressed into the smaller woff and woff2 formats, using TTF as a backup for browsers which don't support those. I'd like to have scripted this out to generate these, along with the CSS file.

Thanks for reading. If you have questions or comments, ask me on Fosstodon. I'll answer what I can.