This is part 2 of my SDL series using C. If you missed part 1 you can find it here.📌Â
Displaying Text Cheat Sheet
Displaying Text in SDL2 requires a very similar set of operations to how we handled creating a window and renderer in part 1. But before we get to displaying text, we need to include the appropriate header. Since SDL2 is designed to be modular, the libraries that work with text are in their own separate headers/files. To work with text, you will need SDL_ttf.h
. You can either include it and the appropriate files directly in your project, linking them at compile time, or if you are on Linux like I am, use your package manager to install them globally. While we won’t be going over the specifics for how to set up SDL in this tutorial, the SDL Wiki provides a page on how to properly set up SDL that is pretty thorough. Now, for those of you who just want to see the code, here is the entire source.
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
#define MY_FONT "/usr/share/fonts/truetype/freefont/FreeSans.ttf"
int main(){
if (SDL_Init(SDL_INIT_VIDEO) < 0){
printf("Couldn't initialize SDL: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
// Initialize SDL_ttf
if (TTF_Init() < 0) {
printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
SDL_Quit();
return EXIT_FAILURE;
}
SDL_Window *window = SDL_CreateWindow("Drawing Text", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, 0);
if (!window){
printf("Failed to open %d x %d window: %s\n", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_GetError());
return EXIT_FAILURE;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
if (!renderer){
printf("Failed to create renderer: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
SDL_SetRenderDrawColor(renderer, 255,255,255,255);
SDL_RenderClear(renderer);
TTF_Font *font = TTF_OpenFont(MY_FONT, 64); // specify the path to your font file and font size
if (!font){
printf("Failed to load font: %s\n", TTF_GetError());
return EXIT_FAILURE;
}
// Create surface with rendered text
SDL_Color textColor = {0, 0, 0, 255}; // black color
SDL_Surface *textSurface = TTF_RenderText_Solid(font, "Hello World!", textColor);
if (!textSurface) {
printf("Failed to create text surface: %s\n", TTF_GetError());
return EXIT_FAILURE;
}
// Create texture from surface
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture){
printf("Failed to create text texture: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
// Render text
SDL_Rect textRect = {50, 50, textSurface->w, textSurface->h}; // rectangle where the text is drawn
SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
SDL_RenderPresent(renderer);
SDL_Delay(2000);
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
SDL_Quit();
return EXIT_SUCCESS;
}
Starting off we #include <SDL2/SDL_ttf.h>
 which gives us access to functions that help us display text. Since the ttf module works with fonts, which are normally .ttf files, we need a font to properly display the text in our Window. You can either download a font from the internet, or use a .ttf file that you have on your computer. To keep it simple, I’ll just use one of the default ones that comes on my computer. At the top of my program, I've created a define that simply points to a default font
#define MY_FONT "/usr/share/fonts/truetype/freefont/FreeSans.ttf"
On macOS you should be able to find some .ttf files located atÂ
/Library/Fonts
 and on WindowsÂC:\Windows\Fonts
Following the same paradigm we used to initialize our video module, we have to initialize our ttf library. The same pattern is used, where we check to see if the return value of the TTF_Init function is less than zero, and if it is, we display an error to the user
// Initialize SDL_ttf
if (TTF_Init() < 0) {
printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
SDL_Quit();
return EXIT_FAILURE;
}
If the ttf library was installed and linked correctly then we can attempt to load a font using TTF_OpenFont. Again we want to check that the font was actually loaded and we do that with our if statement
TTF_Font *font = TTF_OpenFont(MY_FONT, 64); // specify the path to your font file and font size
if (!font){
printf("Failed to load font: %s\n", TTF_GetError());
return EXIT_FAILURE;
}
Now that we've loaded our font we need to create a text surface using TTF_RenderText_Solid This surface is essentially an image containing the rendered text. Since this is an operation that can fail, we want to make sure that we check this using an if statement like we've done before
// Create surface with rendered text
SDL_Color textColor = {0, 0, 0, 255}; // black color
SDL_Surface *textSurface = TTF_RenderText_Solid(font, "Hello World!", textColor);
if (!textSurface) {
printf("Failed to create text surface: %s\n", TTF_GetError());
return EXIT_FAILURE;
}
The next step uses SDL_CreateTextureFromSurface to convert our SDL_Surface
 (your rendered text) into an SDL_Texture
. This is necessary because modern SDL2 uses the GPU for rendering, and textures are the GPU-friendly way of handling images. This conversion process uploads the surface data to the GPU memory, enabling efficient rendering. This operation can also fail, so it is important to check that it doesn't before doing anything with the texture
// Create texture from surface
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture){
printf("Failed to create text texture: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
Finally, we call SDL_RenderCopy. This function tells SDL2 to copy the text texture to the renderer. The SDL_Rect
 textRect
that is defined is used to position and size the text on the screen.
It is important to understand that nothing will be drawn on the screen yet. It's more like preparing what needs to be drawn. The actual drawing happens when you call SDL_RenderPresent.
// Render text
SDL_Rect textRect = {50, 50, textSurface->w, textSurface->h}; // rectangle where the text is drawn
SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
If all goes well then when you run the code, you should see some text on the screen. With that, you are well on your way to learning the core components of using the SDL2 library. With just the information that you’ve learned from part 1 and part 2, you should be able to make a graphical version of the guess my number game, a common beginner programming task, but with an SDL flavored twist. You can even create a rudimentary text adventure, if you were so inclined.
One final note. Between this and the last tutorial, you may be seeing a lot of repetitive code that might be better off abstracted away in functions. That’s good, and means you are getting familiar with the patterns in the library. Opinions about the structure of code should be based off the needs of the code base. While I’m keeping it simple for these tutorials, eventually we should abstract away some common patterns.
FAQ
Why Not Draw Directly to the Renderer?
SDL2 uses two main types of objects for drawing:Â SDL_Surface
 and SDL_Texture
. SDL_Surface
 is a CPU-based bitmap, useful for manipulating pixel data directly. SDL_Texture
, on the other hand, is GPU-based. Modern SDL2 applications use the GPU for rendering (via SDL_Renderer
), which is faster and more efficient but requires textures, not surfaces.
The reason you can't directly render the font to the renderer is that SDL2_ttf
 is designed to work with surfaces, as historically SDL handled rendering with surfaces before textures and GPU rendering were introduced.
Does that mean that SDL2 can render just the surface instead of the texture if I don't want to use the GPU?
Yes, SDL2 can render using just an SDL_Surface
 without converting it to an SDL_Texture
 if you prefer or need to render without using the GPU. This approach is known as "software rendering" and it's done entirely by the CPU, bypassing the GPU.
Software rendering with SDL_Surface
 was the primary method used in SDL before SDL2 introduced hardware-accelerated rendering with SDL_Texture
 and SDL_Renderer
. While software rendering is less efficient and slower than hardware accelerated rendering, it can be useful in certain scenarios, like on systems with limited or no GPU capabilities. Instead of using a renderer like we are using you would instead create a SDL_Surface
 with SDL_GetWindowSurface
SDL_Surface *windowSurface = SDL_GetWindowSurface(window);
Call To Action 📣
Hi 👋 my name is Diego Crespo and I like to talk about technology, niche programming languages, and AI. I have a Twitter and a Mastodon, if you’d like to follow me on other social media platforms. If you liked the article, consider liking and subscribing. And if you haven’t why not check out another article of mine listed below! Thank you for reading and giving me a little of your valuable time. A.M.D.G