« Back to tutorial listing

— Working with TTF fonts —
Part 1: Basic font rendering

Note: this tutorial assumes knowledge of C, as well as prior tutorials.

Introduction

Text rendering is an important part of most video games, whether it is basic information such as displaying the player's score, to more detailed texts about mission objectives, characters speaking, or in-game lore. Text rendering can be achieved in a number of ways. In SDL2 Shooter, for example, a PNG with monospaced letters and numbers is used for text rendering. However, SDL2 supports rendering using True Type Fonts, allowing us a lot more freedom. This first tutorial will show how to setup and use a True Type font.

Note: you will need to have SDL_TTF 2.0 installed to run this code. More details on getting it can be found at the SDL_TTF homepage:

https://www.libsdl.org/projects/SDL_ttf

With everything installed, Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./ttf01 to run the code. You will see a window open like the one above, with Hello World being rendered in various fashions. Close the window to exit.

Inspecting the code

The two most important files in this tutorial are text.c and demo.c. Before we can look at those, we should first look at init.c. As always, there is an initSDL function, which is called by main. Other than starting SDL and opening a display window, the initSDL function contains a crucial section:


if (TTF_Init() < 0)
{
	printf("Couldn't initialize SDL TTF: %s\n", SDL_GetError());
	exit(1);
}

TTF_Init is required to setup SDL2 TTF so that we can use it. If we don't do this, calls to SDL2 TTF functions will fail (either silently or by segfaulting). As SDL2 TTF is required by this project, we're going to exit if it fails to start. It's worth noting that any time we want to use any SDL2 TTF functions, we need to include the appropriate header file. In both init.h and text.h, the include can be found:


#include "SDL2/SDL_ttf.h"

Also in init.c, we want to close SDL2 TTF when we're done with it. This is done with a call to TTF_Quit, as found in cleanup function call.


TTF_Quit();

With our SDL init step done, we can move onto to setting up the font itself. Turning our attention to text.c, we notice it's not a very large file, at all. In fact, it has just two functions. Let's start from the top. initFonts is where we set up the font we want to use for rendering our text:


void initFonts(void)
{
	font = TTF_OpenFont("font/EnterCommand.ttf", FONT_SIZE);
}

SDL2 TTF's TTF_OpenFont loads a file by filename and size. It should pretty easy to understand this line. FONT_SIZE is just a define that specifies the size of the text that we want. We assign the result to a TTF_Font variable called font. The only other function here is used to actually generate the text we want to use:


SDL_Texture *getTextTexture(char *text)
{
	SDL_Surface *surface;

	surface = TTF_RenderUTF8_Blended(font, text, white);

	return toTexture(surface, 1);
}

The TTF_RenderUTF8_Blended function call is what we need to create out text. The function takes three parameters: the TTF_Font to use, the text we want to generate, and the colour of the text, which is an SDL_Color. We want to the colour to be white, so we pass in a white SDL_Color that we declared at the top of the file. Generating white text will allow us to use SDL's colour modulation functions to change it to any other colour that we desire, so there's really no need to specify anything else here.

One thing to keep in mind is that TTF_RenderUTF8_Blended returns a SDL_Surface, not a texture. We therefore need to convert it into a texture in order to use it. We have a common function for this in textures.c, defined below:


SDL_Texture *toTexture(SDL_Surface *surface, int destroySurface)
{
	SDL_Texture *texture;

	texture = SDL_CreateTextureFromSurface(app.renderer, surface);

	if (destroySurface)
	{
		SDL_FreeSurface(surface);
	}

	return texture;
}

SDL_CreateTextureFromSurface will create an SDL_Texture from our SDL_Surface. Our toTexture function optionally allows us to free the surface we used after the conversion is done, which in most cases we'll want to do, as we're working with SDL_Textures here.

We can now look at actually using and drawing a scene with text rendering. As we've already seen, the text that we create are just SDL_Textures, so drawing is very easy. Let's start by looking at the initDemo function in demo.c. There's little to it:


void initDemo(void)
{
	helloWorld = getTextTexture("Hello World!");

	textAngle = 0;

	app.delegate.logic = logic;
	app.delegate.draw = draw;
}

We're calling our getTextTexture function that we created in text.c, to make a text string called "Hello, World!" and assigning the result to an SDL_Texture variable (called helloWorld). We're also setting a variable called textAngle to 0 (for rotating the text), and setting up our logic and draw delegates. The logic function that we're using simply increments textAngle, so we'll skip that, and look at draw:


static void draw(void)
{
	drawNormalText();

	drawScaledText();

	drawRotatedText();

	drawColouredText();
}

We're going to be rendering our text in 4 different states, to demonstrate what can be done. Looking at the drawNormalText function, we don't see anything unusual:


static void drawNormalText(void)
{
	blit(helloWorld, 50, 50, 0);
}

As helloWorld is just a texture, we can pass it to our blit function to draw it (see draw.c). The same thing can be seen with drawScaledText:


static void drawScaledText(void)
{
	blitScaled(helloWorld, 50, 125, 500, 40);
}

The blitScaled function draws a texture with the adjusted width and height, in this case 500 and 40, producing a stretched piece of text (again, see draw.c). What about rotating the text?


static void drawRotatedText(void)
{
	blitRotated(helloWorld, 60, 300, 90);

	blitRotated(helloWorld, 150, 300, -90);

	blitRotated(helloWorld, 600, 300, textAngle);
}

Again, no surprises here. Our blitRotated function takes the helloWorld texture and coordinates, as well as an angle, and draws it. The final call will be spinning the "Hello, World!" text about its center, since we're incrementing the textAngle variable each frame.

Coloured text? Not a problem:


static void drawColouredText(void)
{
	SDL_SetTextureColorMod(helloWorld, 255, 0, 0);

	blit(helloWorld, 275, 200, 0);

	SDL_SetTextureColorMod(helloWorld, 0, 255, 0);

	blit(helloWorld, 275, 250, 0);

	SDL_SetTextureColorMod(helloWorld, 0, 0, 255);

	blit(helloWorld, 275, 300, 0);

	SDL_SetTextureColorMod(helloWorld, 255, 255, 255);
	SDL_SetTextureAlphaMod(helloWorld, 64);

	blit(helloWorld, 275, 350, 0);

	SDL_SetTextureAlphaMod(helloWorld, 255);
}

SDL_SetTextureColorMod can be used to change the colour of a texture, by specifying the texture to use, and the RGB values to set it to (values 0 - 255). Remember that when we created the text, we made it white. This makes it easier to shift the colours how we want. If we were to make it a different colour when we made it, shifting the colours would be more difficult, due to the existing RGB values. In the above function, we're drawing the "Hello, World!" text as red, green, and blue. We're also changing the alpha value of the final piece of text using SDL_SetTextureAlphaMod (in our example, our white text will be faded to look grey). Before finishing with our colour and alpha shifts, we're resetting the texture's colours and alpha to their default by calling SDL_SetTextureColorMod and SDL_SetTextureAlphaMod, and setting all values to 255.

As you can see from all of the above, drawing the text is as easy as working with a regular texture.

That's our introduction to SDL2 TTF done. But you've probably noticed there's a small problem. While we can generate the text we want to use quite easily, it's not exactly very practical. If one were to be making, for example, an adventure game, which would involve a lot of text, we'd need to keep generating blocks of text to use, and the freeing the texture data when it's no longer in use. This means some micromanagement to ensure we're not leaking memory by forgetting to free things. It could also mean a performance hit whenever we want to generate these text textures.

In the next tutorial, we'll look at a somewhat better way to do this.

Purchase

The source code for all parts of this tutorial (including assets) is available for purchase:

From itch.io

It is also available as part of the SDL2 tutorial bundle:

Desktop site