SDF Fonts: Appendix

 from Red Blob Games
1 Feb 2026

These are topics that didn’t fit on the main page.

 1  Layout#

In msdf-atlas-gen, the JSON file contains information about the size and layout of each character (“glyph”):

jq '.glyphs[] | select(.unicode == 65)' \
          assets/FiraSans-Regular.json
{
  "unicode": 65,
  "advance": 0.57300000000000006,
  "planeBounds": {
    "left": -0.055197810998398253,
    "bottom": -0.051254671649759741,
    "right": 0.62819781099839844,
    "top": 0.76882007474639624
  },
  "atlasBounds": {
    "left": 0.5,
    "bottom": 34.5,
    "right": 20.5,
    "top": 58.5
  }
}

What do these fields mean? From msdf-atlas-gen’s documentation[1]:

The JSON also contains general information about the font in the metrics field, but I won’t be using them for the examples on this page:

jq '.metrics' assets/FiraSans-Regular.json
{
  "emSize": 1,
  "lineHeight": 1.2,
  "ascender": 0.93500000000000005,
  "descender": -0.26500000000000001,
  "underlineY": -0.10000000000000001,
  "underlineThickness": 0.050000000000000003
}

I will need to use information about the atlas from the atlas field:

jq '.atlas' assets/FiraSans-Regular.json
{
  "type": "sdf",
  "distanceRange": 2.9265625000000002,
  "distanceRangeMiddle": 0,
  "size": 29.265625,
  "width": 192,
  "height": 192,
  "yOrigin": "bottom"
}

We’ll multiply the abstract “em” units by the font size to get sizes in pixels. The first step is to determine where to draw the character relative to the cursor position:

  1. Use planeBounds (multiplied by font size) to determine the area to draw on the screen (blue shaded area). This will be larger than the character itself to leave room for outline effects.
  2. The cursor location is at x. Note that parts of the letter may be drawn to the left of x, especially once outlines and glow effects are included.
  3. The text baseline is at y. Some characters like g will extend below the baseline.

Visually:

left right top bottom y x

The second step is to generate rectangles where the characters will be drawn. Let’s construct a layout for the word dig which gets turned into three glyphs, d, i, g. Use advance (multiplied by font size) to increment x to draw the next character, i. There will be overlap between the drawing areas for d and i. Visually:

d's advance i's advance y x

Suppose we have a string and a starting x,y position. We can calculate the rectangles needed for each character, as well as the corresponding atlas locations:

Let’s calculate the layout boxes for each character:

(interactive!)

There can be overlap, and it will be more the larger your distance range (aemrange in msdfgen, differently named in each SDF library). The extra space is where the outlines, glow effects, and drop shadows go. Most characters extend slightly below the baseline but descenders like g extend well below the baseline.

I didn’t explore these topics:

 2  SDF vs MSDF#

One downside of SDF fonts is mentioned in the original papers from 2006 and 2007: the corners of shapes end up rounded instead of sharp. The 2006 paper uses a relatively complex data structure to calculate the SDF in a way that keeps corners sharp. The 2007 paper suggests using multiple channels but say “As it stands, we like the rounded style of this text” so they didn’t go into details about how to keep corners sharp. The 2016 paper goes into extensive detail about how to use multiple channels.

Screenshot showing how corners of an SDF rendered font can be round
256✕256 SDF font atlas, showing rounded corners

To get sharper corners, we can either:

  1. Use a higher resolution SDF:

    1✕ texture size
    4✕ texture size
    16✕ texture size
    Screenshot of SDF with 256x256 font atlas Screenshot of SDF with 512x512 font atlas Screenshot of SDF with 1024x1024 atlas
    SDF font atlas, increasing resolution from 256 to 1024
  2. Use a multi channel distance field (MSDF, 4 channels instead of 1):

    SDF 1 byte/pixel
    MSDF 4 byte/pixel
    Screenshot of SDF with 256x256 font atlas Screenshot of MSDF with 256 atlas
    SDF vs MSDF at 256x256

It’s not surprising that using more texture memory increases the quality, but for a given amount of texture memory, which approach is better? Let’s compare 256✕256 MSDF (4 bytes per pixel) with 512✕512 SDF (1 byte per pixel):

SDF with 256 KiB
MSDF with 256 KiB
Screenshot of SDF with 512x512 atlas Screenshot of MSDF with 256x256 atlas
SDF vs MSF with same texture memory

The SDF has smoother curves, especially noticeable on the dot of the “i”. The MSDF has sharper corners. Neither one is a clear win. At a higher resolution, the MSDF looks better:

SDF with 576 KiB
MSDF with 576 KiB
Screenshot of SDF with 768x768 atlas Screenshot of MSDF with 384x384 atlas
SDF vs MSDF with same texture memory
SDF with 1024 KiB
MSDF with 1024 KiB
Screenshot of SDF with 1024x1024 atlas Screenshot of MSDF with 512x512 atlas
SDF vs MSDF with same texture memory

Rounded corners look better for some of the effects like glow and shadow.

 3  Asymmetric range#

For outline/glow effects we need distances farther away from the font. In the above examples I’ve been using --aemrange -0.05 +0.05. The range depends on what effects we want:

A larger range uses more of the “value space” for outlines and less for the main font, which reduces its quality:

-0.25 to +0.25 aemrange
-0.05 to +0.05 aemrange
Screenshot of SDF with -0.25 to +0.25 aemrange Screenshot of SDF with -0.05 to +0.05 aemrange
SDF with thick (0.5) vs thin (0.1) distance range

If the font looks bad, make sure 2/scale < atlas.distanceRange < 128 (see issue 11 on msdf-atlas-gen[6]), where atlas.distanceRange = (aemrange[1] - aemrange[0]) * atlas.size and scale is the ratio of the on-screen size to the size in the atlas. For example, if the letter A in the atlas is 32px tall, and you want to render at 16px, then scale is 0.5 and distanceRange needs to be at least 4.

The best range depends on what effects you want. Plan to experiment to find a good range for your needs. I explored this topic more on my blog.

 4  Antialiasing#

I tried various supersampling and gamma settings here:

Size: MSDF: Gamma: Supersample:

 5  Using msdfgen#

I’m using msdfgen[17], a c++ command line tool. It generates both SDF and MSDF output. The companion project msdf-atlas-gen[18] generates a font atlas and a JSON file with the layout information. On Windows, get a binary from msdf-atlas-gen/releases[19]. On Linux or Mac, compile it from source. First install cmake and vcpkg:

  1. On Mac,

    cd ~/Projects/src
       
    brew install cmake vcpkg
    export VCPKG_ROOT="$HOME/vcpkg"
    git clone https://github.com/microsoft/vcpkg $VCPKG_ROOT
    
  2. On Ubuntu Linux,

    sudo apt update
    sudo apt -y install curl git cmake zip build-essential pkg-config libfreetype-dev
    export VCPKG_ROOT="$HOME/vcpkg"
    git clone https://github.com/microsoft/vcpkg.git $VCPKG_ROOT
    (cd $VCPKG_ROOT; ./bootstrap-vcpkg.sh)
    
  3. On Fedora Linux,

    sudo yum install -y git vcpkg zip cmake
    source /etc/profile
    git clone https://github.com/microsoft/vcpkg.git $VCPKG_ROOT
    
  4. On Amazon Linux,

    sudo yum install -y git vcpkg zip cmake
    export VCPKG_ROOT="$HOME/vcpkg"
    git clone https://github.com/microsoft/vcpkg.git $VCPKG_ROOT
    (cd $VCPKG_ROOT; ./bootstrap-vcpkg.sh)
    

Then build the binary with:

git clone https://github.com/Chlumsky/msdf-atlas-gen.git
cd msdf-atlas-gen
git submodule update --init
cmake .
make

If that fails with a skia error, try:

cmake -DMSDF_ATLAS_USE_SKIA=OFF .
make

To update the software later:

cd msdf-atlas-gen
git pull
git submodule update
cmake . --fresh
make

I used this command to generate the texture atlas (.png file) and the layout data (.json file):

mkdir -p assets
~/Projects/src/msdf-atlas-gen/bin/msdf-atlas-gen \
    --font fonts/FiraSans-Regular.otf \
    --type sdf --aemrange -0.05 +0.05 --dimensions 192 192 \
    --imageout  assets/FiraSans-Regular.png \
    --json      assets/FiraSans-Regular.json

I used a low resolution SDF for the diagrams on this page. Depending on what sizes you render at and what effects you want, you will want a higher resolution and/or MSDF instead of SDF. See the section on rounded fonts for a comparison.

 6  My pages#

The goal of my project was to increase the contrast between the text and the map https://somethingaboutmaps.wordpress.com/2021/06/01/on-edges/[20]

Cartography community calls several of these “halos”:

While I was learning and experiment, I wrote several pages and blog posts:

 7  TODO Other pages#

{TODO need to organize and prune this list}

https://github.com/Blatko1/awesome-msdf[24] - is indeed awesome as its name suggests - lots of resources, including shaders with thickness, outlines, glow, drop shadow, gamma correction, plus links to discussions

wrappers around msdfgen:

- msdf-atlas-gen[25]

other implementations

other links

non distance field based approaches:

Other shaders I looked at:

Email me , or comment here: