SDF curved text

 from Red Blob Games’s Blog
Blog post: 9 Oct 2024

Over the last few posts I wrote about things I did to improve font quality, such as antialiasing and combining distance fields to merge outlines and halos. But I want to “pop up the stack” a bit and talk about one of the bigger goals for this project. I want to render text in styles that I’ve seen in maps, both online and offline, both fantasy and real. In particular, I want to apply spacing, rotation, and curvature to the labels.

Sample map
Sample map with labels

These are common in cartography, not only in fantasy maps like Tolkein’s but also in real-world maps. Eduard Imhof’s classic 1975 paper, Positioning Names on Maps[1] has a ton of great advice on how to position labels, and not only recommends curving text, but also sketches out examples:

Imhof paper, figures 40,41 showing label placement on a mountain passImhof paper, figure 42 showing label placement along rivers
Clear areal association often requires bending and spreading a name so that it is stretched as much as possible across the horizontal axis of an area.
Figures 40, 41, 42 from Imhof’s paper

I’ve had this paper sitting on my computer desktop since 2011. And I’m sure I was interested in this topic long before then. I will be re-reading it again before writing a label placement algorithm. Both Apple Maps and Google Maps use curved text, as shown in these examples:

curved labels on Apple Mapscurved labels on Google Maps
Screenshots: curved labels on Apple and Google maps

Fantasy maps used curved text too. Tolkein’s Lord of the Rings maps used curved text for areas and rivers. Scott Turner’s inspirational blog includes a post Labeling the Coast part two[2] in which he shows how he analyzed the coastline to find a suitable curved arc, and also that he liked curved arcs better than more complex paths. There are also posts on Cartographers Guild[3] about when and how to use curved labels.

So I want to implement curved labels.

To figure out how to make text flow along a curve, I first sketched it out on paper, then made a standalone interactive widget. There are three cases to handle:

  1. Positive curvature: the text should be positioned along the baseline.
  2. Zero curvature: the text is not curved.
  3. Negative curvature: the text should be positioned along the ascender line.
Three curvature cases: >0, =0, <0

If I always curve at the baseline, the tops of the characters are too close together. That’s why I have to curve at the ascender line instead of the baseline when the curvature is negative:

Upwards curvature at baseline makes letters too close together

After calculating the geometry, there are two ways I know of to render the curved text.

  1. “Curving” or “Wrapping”: Position and rotate each letter along a curved path, then render them onto the intermediate combined distance field.
  2. “Shaping” or “Warping”: Draw the letters first onto the intermediate combined distance field, then distort the entire label into a curved shape.

One advantage of warping over wrapping is that it allows for many more effects, such as these:

More shapes to fit the text to

However, whenever stretching the label non-uniformly, the distance field gets distorted. In this example the white halo on the left side (The) is much larger vertically than horizontally. I can find a way to fix this but I decided not to pursue it, since I was primarily interested in curved text and not arbitary warpings:

Trapezoid shows the distance field is non-uniform

Google’s guide[4] says wrapping generally works better than warping, but I ended up trying warping first, and decided I liked it well enough. However, in the last post I had said next time I might choose to not use an intermediate combined distance field. I think without that step, it would be easier to use wrapping than warping.

Using distance fields, I can apply the outline, halo, and antialiasing after the curving step. I’m very happy with the way the curved labels turned out.

Another sample map
Sample map with labels

Email me , or tweet @redblobgames, or comment: