How not to make a font


Rob Koch | @TheOnlyEmployee

April 8, 2021

Executive Summary

I had to make a new font because no other font fit my needs for scannability and readability.

Used Pixialart initially, but ended up at FontStruct.

We've all been there: you've made a service that allows text to be rendered in the pattern of a QR code, but it looks kind of blocky, and therefore hard to read.

Early version of text in the code pattern It's supposed to say

It's not bad, it kind of works, but certain characters look like each other: 0 and O, i, I, L, 1, and l - the usual suspects.


I needed to find (or make) a font that satisfied two essential requirements: it should be readable and scannable when used as part of the code pattern of a QR code.

Allow me to spoil things though and say that I didn't find such a font and had to make one.


Anything with a resolution higher than 4x3 should work. This should not be hard to acheive.

Note that we want the text to take up the same amount of space in the QR code, so each character's dimensions will still be 4 blocks by 3 blocks, but we will increase the fidelity of each block, such that each block is subdivided into a 4x4 grid itself.


Aside from strict pixel fonts, no other font cares about maximially covering cells in a square grid, which is exactly what we need to do to ensure QR codes that have text using this font are still scannable.

QR codes are composed of light and dark blocks, which correspond to 0s and 1s, respectively. For characters in our font, we need to ensure light blocks remain as unobstructed as possible while dark blocks are as occluded as possible. I aimed for discrepancies no greater than 25%. That is to say, if a block is supposed to be dark, it should be at least 75% covered; if it's light, it should be no more than 25% covered.

We'll call this correctness. We want to our blocks to be as correct as possible so that the QR code is still very scannable. I also tried to make sure that the center of the block was mostly untouched; I think there are only a few instances of any of the center 4 pixels in a block being changed.

3 in the old font 3 in the old font
What the number 3 looks like in the new font 3 in the new font. Most characters are 3 blocks wide and 4 tall.
What the number 3 looks like in the new font 3 with example blocks highlighted.
Highlighted blockBinary valueLight or DarkCorrectness

In the above example for the number 3, the red block represents a 0 (and is therefore light) and has no error; the orange block also represents a 0 and is 93.75% correct because of the dark pixel in the bottom right; the blue block represents a 1 and is 81% correct because of the light pixels in the bottom right.


I only wanted to increase the fidelity with which I rendered my characters. So just a little more detail was what I was going for. I did not need perfect curves, nor would I even be totally sure how to render curvy characters in my QR codes given my current process.

Instead of 1 pixel per block, I wanted 16 or 64 pixels per block. I went with 16 because, honestly, 64 would have given me too much leeway in making things look good, and I didn't want to spend too much time on it, especially as a non-designer.

Also, the font is not monospaced as one might expect. I wanted to eke out as much space from characters as I can, given the area constraints I'm working with in a QR code.

Emergent findings

Something interesting also emerged as I was working on the font; I discovered that for the large majority of symbols, I could stay within my guidelines of no more than 25% error, and usually less than 20%, and still have good-looking symbols.

Let's take capital Z as an example.

Capital Z in the old font Capital Z in the old font.
Capital Z Capital Z in the new font.
Capital Z with '1' blocks highlighted 1 blocks highlighted. Any given block is at least 80% correct.

I thought diagonal elements would be hard (and to be honest they kind of are, depending on how steep they are), but I was able to make do for a lot of characters.

I also quickly learned to trust my eye. In the words of Designing with FontForge:

As you test the design, you will have to trust your perceptions and design somewhat practically. Much of type design requires that you make letters similar and that you repeat forms.

It is tempting to assume that if you measure the parts and the spaces between the glyphs, then you will get reliable results. While very useful, this approach has real limitations. You should expect to make adjustments if something looks wrong to you. Furthermore, you should feel confident that making changes until it “looks right” is the correct thing to do.

I was definitely making changes until things "looked right," after initially placing pixels where I thought things "should" go.


While googling around for pixel fonts and the like, I came across VT323, a font "created from the glyphs of the DEC VT320 text terminal." Interesting, though still not super useful to me right now. The README did point me to FontForge, which I had never heard of.

However, I quickly decided that I was getting too deep into font making and away from "I just need to add some pixels to my existing 'font.'"

So I started looking for pixel drawing tools. Pixilart is where I ended up, which is an aptly named tool for creating pixel art. I just made a new 24x40 canvas for each character and plucked away with the pencil tool.

This is where the title for this post comes from - it's unlikely that this is what I should have done in terms of font-designing, but it's pretty much exactly what I needed and I didn't get bogged down in learning new tools.

I later discovered FontStruct, which is even better suited to what I initially set out to do. I used it to make qr.ttf, which can be found here and seen below.

The resultant font

Future work

I think the font looks good enough right now, but if we were working with an even higher fidelity (higher than 16px per block), or got away from pixel-placing altogether, we could have a normal looking scannable font that is even less blocky.

Missing characters

There are a lot of missing symbols that could be added, like $ % ^ & # @.