The FreeType library for parsing and rasterizing fonts has been around for a long time, as well as operating system specific ways to rasterize glyphs in bitmaps. Some parts of the process have been patented, so the fonts don’t look very appealing under Linux, for example. And subpix-optimized rendering has also been made more difficult for various reasons.
In addition to FreeType, these font libraries are worth a look:
- stb_truetype.h – Single File C Library by Sean Barrett.
- Font-rs – fast font renderer by Raph Levien, written in Rust \o/ and an article describing some aspects of it.
But in essence, the whole idea is still to rasterize glyphs in bitmaps with a certain point size and somehow cache the result.
Caching rasterized glyphs in bitmaps works well enough. If you don’t use many different font sizes, very large font sizes or a large amount of glyphs in combination with different/large font sizes.
A bitmap for different sizes? Signed spacing fields.
A 2007 paper by Chris Green, Improved Alpha-Tested Magnification for Vector Textures and Special Effects, introduced the game development world to the concept of signed distance field textures for vector-like stuffs.
The paper was mainly about solving the problem of “implementing characters and markers in games” and the idea behind it is quite clever. Instead of saving rasterized shapes in a texture, you save a special texture where each pixel represents the distance to the next shape edge. When you render with this texture, a pixel shader can perform simple alpha distortions or complex distance value treatments to get anti-aliasing, contours, etc. The SDF texture can be very small and still be able to display high-resolution line art properly.
Then, of course, people realized that the same approach could work for rendering fonts. Suddenly, rendering smooth glyphs in very large font sizes doesn’t mean that all the (V)RAM has been used for cached textures, the cached glyph SDFs can stay pretty small, while at larger sizes they offer nice edges.
Of course, the SDF approach is not without its disadvantages:
- The calculation of the SDF is not trivially cheap. While you can cache all possible glyphs offline in an SDF texture atlas for most Western languages, this is not useful for other languages due to the sheer amount of possible glyphs.
- Simple SDF has artifacts near complex intersections or corners because it only stores a single distance to the next edge.
- SDF doesn’t quite work with very small font sizes for a similar reason. There it is probably better to simply rasterize the glyph into a normal bitmap.
Anyway, SDFs are a good idea. For some examples or implementations you can have a look at libgdx or TextMeshPro.
The original paper pointed to the idea of storing multiple distances to solve the problem of the sharp corners of the SDF and a recent implementation of this idea is “Multi-Channel Distance Field” by Viktor Chlumsky.
That’s pretty good. However, the “tiny font sizes” and “cost of calculating the (M)SDF” can still be a problem.
Fonts directly on the GPU.
An obvious question is: “Why is this caching done in bitmaps at all? Can’t the GPU just render the glyphs directly? The question is good. However, the answer is not necessarily simple.