Each week I pick one or two things to work on. In week 2 of this year, I decided I should update my “hello world” OpenGL+Emscripten code[1] from 2015. It’s boilerplate I use occasionally in other projects. It wasn’t compiling anymore, and I wanted to fix that as well as several other things.
One of the unsolved issues in that starter code was that the fonts never looked good. I was using the stb_truetype[2] library, and got this output:
Look at how H and e are too far apart, and S and D are too close together. I tried various things but couldn’t figure it out, so I had two workarounds at the time:
- Use Omar Cornut’s Dear ImGui[3], which has nice looking font rendering.
- Use a monospace font to hide the spacing problems.
I decided to look into it again. After all, Dear ImGui uses stb_truetype
, so why would my output be so different? After looking through the docs and also several examples, I concluded that my code was all wrong. I tried kerning but that wasn’t the issue. I tried a few different fonts but that wasn’t the issue. One issue on stb_truetype[4] suggests “left side bearing”. I found example code from Justin Meiners[5] that rendered nicely, and it was using left side bearing. But Dear ImGui doesn’t use it. And example code from Benjamin Attal[6] didn’t use it either. I spent many hours studying the docs and examples before I figured out how to rewrite my code[7]. It looks better now (although still not great — look at the gap between m and o):
This awakened my desire to play with font rendering. In particular, back in 2016 I bookmarked https://github.com/Chlumsky/msdfgen[8] with the note:
Multi-channel distance fields can represent sharp corners, unlike Valve’s original paper, which had rounded corners. This is C++ code to implement the algorithm described here: http://computergraphics.stackexchange.com/questions/306/sharp-corners-with-signed-distance-fields-fonts/2151#2151[9] (there’s also a thesis paper)
So I decided week 3’s project would be to try that out! It was a great week. I learned a lot.
Along the way I kept some notes about what commands I ran and what I learned. These notes were originally meant for myself only but I’m making them available because I think sometimes people don’t realize what my learning process is like. My articles especially present everything in the “final form” and readers might think I just write code like that from the start. I don’t! I have lots of bugs and dead ends and misconceptions along the way. I find that keeping a journal (for myself) helps me work through them.
Some high level notes:
- Signed Distance Fields are cool.
- There are some things I don’t understand about gamma correction, supersampling, outlines, antialiasing, etc. I experimented some but I feel like I still don’t understand these topics well.
- Towards the end of the week there were several things I felt like I should have understood much earlier, but didn’t.
- The code to generate the MSDF files may be complex, but the code to render is incredibly simple, probably under 50 lines.
- I should take lots of screenshots and make lots of notes when I’m learning something.
Take a look my notes, as well as an interactive demo I used for testing my knowledge. Random tidbit: the 2403
in the url means year=2024
week=03
. I’ve been using this naming scheme since 2015 for my weekly projects.
After playing with fonts week 2 and week 3, I still had more I wanted to do. But I am trying to keep to a project-per-week rhythm, so I decided to wrap up the basic font rendering and move on. In week 4 I’ll explore outlines and other effects. That’s the next blog post.