Custom Fonts
How to build, set up, and use custom fonts.
The Text
component enables rendering text using multi-channel signed distance functions (MSDF). By default, uikit provides the Inter font. A custom font can be converted from a .ttf
file to an MSDF representation as a JSON and a corresponding texture using msdf-bmfont-xml.
How to set up custom fonts?
This example shows how to compile the Roboto
font family with the weight medium
.
The first step is to download a .ttf
file for the font family with the correct weights. After downloading the font to roboto.ttf
, the overlaps need to be removed.
This is necessary because msdf-bmfont has a problem with overlapping paths, creating weird artificats.
Linux
fontforge -lang=ff -c 'Open($1); SelectAll(); RemoveOverlap(); Generate($2)' roboto.ttf fixed-roboto.ttf
Windows
- Install FontForge.
- Open the
.ttf
font. - Select all the characters using
CTRL+A
or navigating toEdit > Select > Select All
. - Remove overlap using
CTRL+Shift+O
or navigating toElement > Overlap > Remove Overlap
. - Generate fonts using
CTRL+Shift+G
or navigating toFile > Generate font(s)
in Truetype (.ttf
) font.
Tip: give a new name to the new generated font.
Generating the msdf font
Next, we use msdf-bmfont
to convert the .ttf
file to a texture and a .json
file. For that we need the FontForge generated font and a charset file containing all the characters we want to include in our msdf-font.
npx msdf-bmfont -f json fixed-roboto.ttf -i charset.txt -m 256,512 -o public/roboto -s 48
example charset.txt:
!\"#$%&'()*+,-./0123456789:;<=>?@ÄÖÜABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`äöüabcdefghijklmnopqrstuvwxyz{|}~ߧ
Only a single texture file is supported by uikit, so make sure the generated texture is a single file. Otherwise adjust the texture by increasing the resolution or by decreasing the font size.
Inlining the texture
If you are using some kind of hashes in your filenames, you won't be able to use the separate texture. In that case you need to inline the texture in the .json
file. Here's a sample script to do it:
import { writeFile } from "fs/promises";
import generateBMFont from "msdf-bmfont-xml";
const charset =
"’|Wj@$()[]{}/\\w%MQm0fgipqy!#&123456789?ABCDEFGHIJKLNOPRSTUVXYZbdhkl;t<>aceos:nruvxz~+=_^*-\"',`. €£";
generateBMFont(
"src/assets/fonts/Inter-Bold.woff",
{
smallSize: true,
charset,
outputType: "json",
},
async (
error: Error | undefined,
textures: { filename: string; texture: Buffer }[],
font: { filename: string; data: string }
) => {
if (error) {
throw error;
}
const pages = await Promise.all(
textures.map((texture) => "data:image/png;base64," + texture.texture.toString("base64"))
);
const json = JSON.parse(font.data);
json.pages = pages;
await writeFile(font.filename, JSON.stringify(json));
}
);
Implementing the generated font
Lastly, we add the font family via the FontFamilyProvider
. It's necessary to host the .json
file and the texture on the same URL and provide the URL to the .json
file to the FontFamilyProvider
.
Repeat the previous process for other weights, such as bold, to support different weights.
<FontFamilyProvider
roboto={{
medium: "url-to-medium.json",
bold: "url-to-bold.json",
}}
>
<Text fontFamily="roboto">Test123</Text>
</FontFamilyProvider>