While redoing my website from scratch, I was looking to ditch the Google Fonts API for privacy reasons. I ended up downloading the full font from Google Fonts. By full font, I mean a TTF file—which is not appropriate for the web. My goal was to make a web friendly font, or woff2
which is practically supported on all modern browsers since 2018.
Font Faces
Before I share the commands to make woff2
s, I'd like to cover what we have to put in our CSS
file. A font family "font-family
" is the name of the font we will be using. In CSS, the font-family
is like a variable name that points to a collection of font faces. Each font face "@font-face
" specifies what characteristics that configuration supports. Is the font-style
normal
or italic
? Is the font-weight
bold
, normal
, or some some weight number? What characters are supported "unicode-range
" in this @font-face
?
Each @font-face
configuration has a source "src
" which is where the actual woff2
file can be found if the browser determines it needs that @font-face
.
There are more properties like size-adjust
which modify how the font is drawn after it is loaded, however these won't be covered. At least be aware that there are variable fonts which can be tuned with font-variation-settings
on most modern browsers. Most fonts are "static" fonts rather than "variable" fonts, what I share below will be helpful for converting static fonts for your website.
A concrete example
Here's what the Google Font API for Roboto:italic and Roboto returns for a given web request. @font-face
sections for other character sets have been omitted.
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1Mu51xIIzIXKMny.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
As you can see, we have two related @font-face
s identified by the font-family
'Roboto'
. They happen to share the same unicode-range
and font-weight
, but otherwise have different font-style
s. Together, the above four properties form the lookup key to which font src
should be downloaded and used to render a character on screen.
In the above example, there is no bold
@font-face
. Will you get bold text if you used <strong>
? The answer is: it depends on the font. If it does use the same font-family
, it might not have the visual properties you would expect. For that reason, I choose to generate the following variants:
- Regular
- Italic
- Bold
- Bold + Italic
If the font comes with a lighter variant, you are of course able to make a lighter @font-face
for it and its italic form.
Building our own web font
For this example, I will be using the Gloria Hallelujah Handwriting font. Inside, we only have one file, GloriaHallelujah-Regular.ttf
. We will not be getting any intentionally italic or bold web fonts from this file.
There's a handy tool for fonts called fonttools. It runs on python 3. If you do not python 3 installed, then install python. Next, because woff2
requires a compression technology called Brotli, you will need to install brotli with pip3 install brotli
. And last, install fonttools with pip install fonttools
. This will install an important utility called pyftsubset
, which we will use.
The following demonstration includes first: converting from TTF
to woff2
, and second: restricting the character set to have only what we need. I specifically will use the unicode codepoints for 'H', 'e', 'l', and 'o', such that I can say "Hello" in this font. That comes out to be U+0048,U+0065,U+006C,U+006F
, which I wrote while referencing an ASCII table. If you want all of the ASCII range, then the unicodes
parameter could be set to U+0000-U00FF
.
pyftsubset "GloriaHallelujah-Regular.ttf" \
--output-file="custom-font-example.woff2" \
--flavor=woff2 \
--layout-features="*" \
--unicodes="U+0048,U+0065,U+006C,U+006F"
The file that comes out "custom-font-example.woff2"
is only a mere 2068
bytes! That is quite small! Overall, this tool is easy to use, if you use it in exactly this way. Come up with a unicode range list, provide the input file and output file, and keep the flavor
(that is, the format) set to woff2
and layout-features
sot te *
.
There are a lot of options to the pyftsubset tool for more advanced use cases. This should be enough for most people.
Making our CSS
To use this new woff2
font on the web, we need two files: CSS that describes the @font-face
and the font file we made earlier. Below is what that CSS could look like.
@font-face {
font-family: 'Example Font';
font-style: normal;
font-weight: normal;
font-display: swap;
src: url(./example-font.woff2) format('woff2');
unicode-range: U+0048,U+0065,U+006C,U+006F;
}
.example-font {
font-family: 'Example Font', monospace;
font-size: 400%;
text-align: center;
}
I keep the font file in the same folder as the CSS file so it can load easily with a relative URL. While I use a relative URL, in Google's example, src
is set to a full URL. This is fine too, though it requires more connections to be made and can be a hassle if you're using Content-Security-Policy.
Using the new font
Tada! Here's "Hello world"
in our new font!
Hello World
Looks a little off, right? The characters 'W', 'r', and 'd' are being rendered with a monospace font. The 'Example Font'
font-family
literally does not have the characters 'W', 'r', or 'd' available! In this case, the browser falls back to the monospace
font-family
. Plan for what contexts your font will be used in.
Unicode range support
I actually do not recommend encoding only the characters you will display. Instead, select ranges of characters that you plan to support in general. You can always reference Google's Roboto font-family
for unicode-range
s. For latin characters, the list appears to be: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD
. Although, your font might not support all the unicode codepoints you ask for.
To find out what codepoints were missing, use the option --no-ignore-missing-unicodes
. You may get an output like:
fontTools.subset.Subsetter.MissingUnicodesSubsettingError: [ 'U+0000', 'U+0001', 'U+0002', 'U+0003', ...
If you are concerned, make note of what ranges are not available and adjust your @font-face
accordingly. With trial and error, I find that Gloria Hallelujah only supports a unicode range set of U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+20AC, U+2122, U+2212
. I started with the Roboto latin range list, so this may be incomplete. There are better methods to find out the available unicode codepoints. However, to keep the toolbox small in this article, I simply used --no-ignore-missing-unicodes
to demonstrate this.
Wrap up
To support fonts on the web, create a woff2
file using pyftsubset
on a TTF
file with a command like the one above. You may want to create multiple woff2
files for each variant (bold, italic, or a combo thereof) and a corresponding @font-face
. Each @font-face
must link to the woff2
file with its src
and label it appropriately with font-family
, font-style
, font-weight
, and unicode-range
. And finally, set the font-family
on the content you wish to style.