Science #3 is still on the backburner (there is a lot to test and so little spare time), but I needed text working nicely for my LMaG prototyping, so you get code! We’re just going to go over the basics this time. Handling boundaries and multiple lines is tricky and I want to make sure my implementation is perfect before releasing it on you.
Rendering text is usually done in one of two ways. The first way is a bitmap font, where you have an image similar to this:
Then you use a mathematical formula and a function like DrawPartial() to only draw one character at a time. There’s a couple reasons we aren’t using a bitmap font. Firstly, putting together a custom bitmap font takes a fair amount of work, and if you need to make changes or add in new characters you need to either do another fair bit of coding or artsy stuff. There are certain advantages to using bitmap fonts, such as having customized characters like the ones in the example image, and you are essentially reusing your drawing code. You also don’t have to deal with the insanity of font licensing.
In comparison, using a font interpreter (like SDL_TTF, which is the one we are going to use), takes a font file (a .ttf you can find many of on your computer or the internet, hence the name) and does all the fancy math for you and you automatically have access to any characters in the font file. It also handles things like kerning which can be quite hard to do properly and quite easy to mess up. You pay a bit more in computational run time (which can be very dangerous if you don’t know what you’re doing), but you save a fair bit of time.
SDL_TTF is one of the more finicky SDL libraries. There are definitely a lot more ways to cause a segfault than I’d like, and additional ways to cause undocumented behavior. A lot of these are tied to passing NULLs where you shouldn’t, which means lots of custom functions with additional testing; but there are also some cases where you’ve declared a font but haven’t loaded anything into it(or you just freed it) that can cause segfaults. Those are a lot harder to test for, and trying to do so might cause the segfault you are trying to avoid.
Loads a font from a file using TTF_OpenFont(). Another one that’s fairly short and self explanitory. Trying to use a font without having loaded a proper ttf font in will end in tears.
Cleans up the memory associated with the font using TTF_CloseFont(). Also NULLs out the pointer to prevent some of the worst issues that can happen. I am not happy with the double pointer
So now we get into the actual meaty part of the ttf class. This is the function that actually renders the text and outputs it to a surface.
There’s quite a bit going on here.
We’re passing it a number of variables. We have the destination surface, the destination x and the destination y. You should always render to an intermediate surface and then blit that surface to the screen every frame instead of re-rendering it. Re-rendering the text and blitting it to the screen every frame will tank your FPS like nobody’s business. The only way to be more inefficient is to open and close the font every frame.
Next up we have the font data. The pointer to the font, and the red green blue alpha channels (allowing for any color on the fly).
The last two items are the actual text string we are rendering and the ellipses allows arguments within the string so we can mimic printf functionality. Eg. (“Bob: %i %s”,integer,string).
Now we’re into the actual function code. Right off the bat we make sure that it’s not a NULL, because that is gaurenteed to segfault. We declare the variables we’re gonna use in this function. Of particular note is text_surface. This is where we’ll be storing the rendered text temporarily, which means it is a potential memory leak if we don’t remember to clean it up.
The va_* code is just compiling the disparate parts of the string together. I’m not going to go into any detail with this stuff as it’s C libraries and I treat it as a black box anyway. If you’re interested, a good starting point can be found here.
The next part is the heart and soul of this function. We use TTF_RenderText_Solid() and store the results in text_surface. There is also TTF_RenderText_Shaded() and TTF_RenderText_Blended() but I’ve never really had a use for them. You may feel differently.
Everything else is just blitting the rendered text to the dest surface at the x,y coordinates listed and freeing our temporary surface so we don’t create a memory leak.