Rendering Fonts Quickly on the GPU

outercloud1 pts1 comments

Rendering Fonts Quickly in WebGPU | outercloud.dev

Rendering Fonts Quickly in WebGPU<br>July 12, 2026

Last month I started working on a WebGPU animation renderer so I could render animations directly in the web for this blog. It's not quite done yet, but along the way I've managed to implement a very cool technique for rendering TTF fonts. The technique comes from work by Loop and Blinn, Resolution independent curve rendering using programmable graphics hardware. According to Google, this work was patented by Microsoft in 2005, and just expired in March this year!<br>The technique is actually surprisingly elegant for the GPU and I think it's really cool. In order to understand this technique from scratch, I'm going to break down a little bit of everything, from fonts to graphics rendering and splines.<br>By the way, the animated GIF at the top was rendered using this technique!<br>I also spent a ton of time making little explainer animations for further on in this blog. Let me know if they help!<br>My True Type Font is Telling Lies Again ​<br>Let's start with True Type fonts (.ttf) because they are the one true font format. Open Type fonts (.otf) are almost the exact same structure as True Type fonts, and Web Open Font Format (.woff and .woff2) fonts are essentially just wrappers that compress True Type and Open Type fonts. So, understanding True Type fonts is basically all you need.<br>The format in which TTF encodes its information is frankly over engineered and very boring so I'm going to just completely skip that. You can check out Tomek Czajecki's blog post on TTF file parsing if your interested.<br>Characters in TTF fonts are made of glyphs. Each glyph is made up of paths, which are in turn each made up of curves. The hierarchy sort of looks like this:<br>Font<br>Character<br>Glyphs<br>Paths<br>Curves

Each blue point in the a glyph is a start and end point for a curve. Each red point is a control point. Control points control (haha) the curvature of a curve. You can also see, for example, on the stem of the glyph some of the lines don't have a control point. When reading the curve data, I just add the control point back on the line, keeping it straight.<br>The a glyph is made of two paths. Looking at the image closely, you can see black arrows indicating the direction of each path. When the path is constructed clockwise, it is drawn filled in. When the path his constructed counter clockwise, it cuts out of the filled in area.<br>There's a neat little trick for detecting whether a path is clockwise or counterclockwise.<br>tsfor (const point in points) {<br>area += 1 / 2 * (lastPoint.x * point.y - point.x * lastPoint.y)

if (area > 0) {<br>counterClockwise()<br>} else {<br>clockwise()

This is just some pseudocode since dealing with the path properly is a bit messy. You can see the actual source here. We'll eventually use this to decide wether to use the contour of the shape as a cutout when we generate the triangulated mesh.<br>It's a Spliny Slope ​<br>Each curve in a TTF font is defined by three points: start, end, and control points. This is kind of curve is called a quadratic spline.<br>One really nice way to draw out the curve of a spline is with simple lerps.<br>tsfunction lerp(a: number, b: number, t: number): number {<br>return a + (b - a) * t

Lerp is a super useful and common function in video games and graphics that L inearly intERP olates between two values based on a t from 0 to 1. That looks like this:

A quadratic spline is just a simple combination of three lerps.<br>tsfunction splinePoint(start: Vector2, end: Vector2, control: Vector2, t: number) {<br>const a = new Vector2(lerp(start.x, control.x, t), lerp(start.y, control.y, t))<br>const b = new Vector2(lerp(control.x, end.x, t), lerp(control.y, end.y, t))

return new Vector2(lerp(a.x, b.x, t), lerp(a.y, b.y, t))

This construction is known as Casteljau's Algorithm. While this method is really nice for tracing out a spline's shape, it doesn't help us fill in a curve which we'll need to render our fonts. Instead, we need to talk about triangles and pixelss for second.<br>GPUs Only Want One Thing, and It's Triangles ​<br>A good mental model for thinking about how GPUs render graphics is to think in triangles and pixels. First we create a bunch of triangles on our CPU. Then, we tell the GPU to shade in each pixel on the triangles. When the GPU shades in these pixels, each pixel runs it's own tiny function in isolation.

The function for this triangle might look like:<br>glslfn fragment() {<br>return black;

Since every pixel runs on it's own in isolation, we can't use Casteljau's Algorithm. Casteljau's Algorithm only gives us a position on the spline given a progress number from 0 to 1. One idea might be to calculate where the closes point on the spline is to a pixel. This isn't ideal. If you try to use approximate methods, your shader will break down for certain spline configurations and look strange. An analytical solution does actually exist for quadratic splines, but it's slow. We have to think differently to use the GPU...

fonts control point curve lerp type

Related Articles