I’ve dealt with font scaling all my damn career, it’s just one of those annoying things that seems to keep coming up no matter where I work. I’ve seen it implemented a bunch of different ways in that time but it’s always ended up kind of quirkly/awkward, with annoying jumps in the visuals as you move between the font breakpoints.
Anyway, I had this idea I posted a while back (Font runtime rendering?) to select the closest font size, then scale the texture in between those breakpoints so it looks smooth, without having to go down any complicated paths like Signed Distance Field fonts. It’s been a while, but I finally got around to prototyping it Mostly because it came up at work and there was some question as to whether or not the scaling would look like garbage.
If you run this, you can left-click and drag to move the text around, and left-click and drag the handle in the bottom right to resize. I only bothered to implement scaling to width, but it’s easy enough to do the same for height.
The end result actually looks fairly good. When scaling between font sizes at small sizes, there’s a few jumps, but I think this could actually be mitigated by having less font breakponts in the lower end. Right now I cache every font size for Calibri that paint .net told me existed, but I think if I went straight from 8 to 20, it might go a little more cleanly. For any single static font though, you don’t really notice. Also, the scaling for really small fonts (ie, when you’re scaling smaller than a font size of 8, which is the minimum for Calibri apparently), it starts to get hard to read. This is probably obvious because there’s just not enough pixels to create readable text past a certain point.
If you’re looking at this project, the files that will probably be of most interest to you are…
My intent here was to get a proof of concept going, so the code isn’t super optimized. About the only concession to optimization I made was to cache the texture that gets generated after a size change, so you only draw that cached texture.
Some optimizations that one could look into are…
- FontBank exposes two methods, GetNextSizeLarger and GetNextSizeSmaller. This takes in a FontDescription and will search the FontBank for the next available size (in whatever direction) that is stored in the FontBank for that font. Right now this is just a linear search that starts at the beginning of the list and iterates until it finds a matching size, then takes the next size in either direction. There are much more intelligent ways to go about this
- SimpleFontScaler uses a SpriteFont.MeasureString as the basis of its comparison. I used to know the underlying mechanism by which this worked but have since forgotten. Either way, I suspect it gets the size of each glyph in the SpriteFont for each character in the string and builds it that way. Since these glyphs are pre-rendered by the Content Pipeline, this isn’t too bad, but it could still be considered slow by some. Another option to consider might be to simply average the size of every glyph in the SpriteFont, cache it somewhere, and just multiply it the number of characters in the string to get a ballpark string size. Since we’re scaling the end result anyway, and since that scaled result looks fine, we probably don’t need to be super precise on this measurement. Just do a quick calculation to get close and let bitmap scaling do the rest.
- Not so much an optimization, but more of a convenience issue… I had to create a SpriteFont for each breakpoint I wanted to use. This was a pain in the butt… just for Calibri. If you need a lot of fonts, you probably want to create some kind of tool to generate these for you, or look into a method of generating sprite fonts at runtime. There are also other font rendering solutions that could be used.
I’m sure there are many more places that things could be cleaned up and the performance increases, but those are the key ones that stuck at as I was implementing. I just didn’t care enough to overcome them because my intent was to see how the scaling looked
Anyway, that’s enough rambling from me. The project is there should anybody find this kind of thing useful.