Lazy Foo' Productions

SDL Forums external link SDL2 Tutorials SDL3 Tutorials Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

True Type Fonts

True Type Fonts screenshot

Last Updated: Jun 7th, 2025

Here we're going to show text from TTF fonts using the SDL_ttf library.
/* Headers */
//Using SDL, SDL_image, SDL_ttf and STL string
//Using SDL and STL string
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <string>
Make sure to have SDL_ttf installed and configured in your project and don't forget to include the SDL_ttf header.
/* Class Prototypes */
class LTexture
{
public:
    //Symbolic constant
    static constexpr float kOriginalSize = -1.f;

    //Initializes texture variables
    LTexture();

    //Cleans up texture variables
    ~LTexture();

    //Loads texture from disk
    bool loadFromFile( std::string path );

    #if defined(SDL_TTF_MAJOR_VERSION)
    //Creates texture from text
    bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
    #endif

    //Cleans up texture
    void destroy();

    //Sets color modulation
    void setColor( Uint8 r, Uint8 g, Uint8 b);

    //Sets opacity
    void setAlpha( Uint8 alpha );

    //Sets blend mode
    void setBlending( SDL_BlendMode blendMode );

    //Draws texture
    void render( float x, float y, SDL_FRect* clip = nullptr, float width = kOriginalSize, float height = kOriginalSize, double degrees = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flipMode = SDL_FLIP_NONE );

    //Gets texture attributes
    int getWidth();
    int getHeight();
    bool isLoaded();

private:
    //Contains texture data
    SDL_Texture* mTexture;

    //Texture dimensions
    int mWidth;
    int mHeight;
};
We're going to be adding the loadFromRenderedText which creates a texture from the string/color we provide it.

If you're wondering what that #if defined(SDL_TTF_MAJOR_VERSION) does, it checks if SDL_TTF_MAJOR_VERSION is defined which happens when we include SDL_ttf. This is a way to easily exclude the TTF rendering code from the LTexture because when we don't include SDL_ttf.h, SDL_TTF_MAJOR_VERSION is not defined and it will omit the code sandwiched in the #if defined block. This is an example of a macro (using #include is also a macro) which is code that talks to the compiler.
/* Global Variables */
//The window we'll be rendering to
SDL_Window* gWindow{ nullptr };

//The renderer used to draw to the window
SDL_Renderer* gRenderer{ nullptr };

//Global font
TTF_Font* gFont{ nullptr };

//The texture we're going to render text to
LTexture gTextTexture; 
We have a new data type for our TTF font file we're going to load: TTF_Font.
#if defined(SDL_TTF_MAJOR_VERSION)
bool LTexture::loadFromRenderedText( std::string textureText, SDL_Color textColor )
{
    //Clean up existing texture
    destroy();

    //Load text surface
    if( SDL_Surface* textSurface = TTF_RenderText_Blended( gFont, textureText.c_str(), 0, textColor ); textSurface == nullptr )
    {
        SDL_Log( "Unable to render text surface! SDL_ttf Error: %s\n", SDL_GetError() );
    }
    else
    {
        //Create texture from surface
        if( mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface ); mTexture == nullptr )
        {
            SDL_Log( "Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError() );
        }
        else
        {
            mWidth = textSurface->w;
            mHeight = textSurface->h;
        }

        //Free temp surface
        SDL_DestroySurface( textSurface );
    }
    
    //Return success if texture loaded
    return mTexture != nullptr;
}
#endif
Creating a texture from text works much like doing so from a file, but instead of calling SDL_LoadBMP or IMG_Load to generate the surface, you can use TTF_RenderText_Blended. As you cansee, it otherwise works mostly the same as it does with loading from a file.

TTF_RenderText_Blended is just one way to render a surface from text. There are other ways you can check out in the SDL_ttf documentation.
bool init()
{
    //Initialization flag
    bool success{ true };

    //Initialize SDL
    if( SDL_Init( SDL_INIT_VIDEO ) == false )
    {
        SDL_Log( "SDL could not initialize! SDL error: %s\n", SDL_GetError() );
        success = false;
    }
    else
    {
        //Create window with renderer
        if( SDL_CreateWindowAndRenderer( "SDL3 Tutorial: True Type Fonts", kScreenWidth, kScreenHeight, 0, &gWindow, &gRenderer ) == false )
        {
            SDL_Log( "Window could not be created! SDL error: %s\n", SDL_GetError() );
            success = false;
        }
        else
        {
            //Initialize font loading
            if( TTF_Init() == false )
            {
                SDL_Log( "SDL_ttf could not initialize! SDL_ttf error: %s\n", SDL_GetError() );
                success = false;
            }
        }
    }

    return success;
}
Much like how we have to initialize SDL with SDL_Init, we need to initialize SDL_ttf with TTF_Init.
bool loadMedia()
{
    //File loading flag
    bool success{ true };

    //Load scene font
    std::string fontPath{ "08-true-type-fonts/lazy.ttf" };
    if( gFont = TTF_OpenFont( fontPath.c_str(), 28 ); gFont == nullptr )
    {
        SDL_Log( "Could not load %s! SDL_ttf Error: %s\n", fontPath.c_str(), SDL_GetError() );
        success = false;
    }
    else
    {
        //Load text
        SDL_Color textColor{ 0x00, 0x00, 0x00, 0xFF };
        if( gTextTexture.loadFromRenderedText( "The quick brown fox jumps over the lazy dog", textColor ) == false )
        {
            SDL_Log( "Could not load text texture %s! SDL_ttf Error: %s\n", fontPath.c_str(), SDL_GetError() );
            success = false;
        }
    }

    return success;
}
To open up a TTF font file we call TTF_OpenFont and pass in the path to the font and the size we want to render the font.
void close()
{
    //Clean up textures
    gTextTexture.destroy();
    
    //Free font
    TTF_CloseFont( gFont );
    gFont = nullptr;

    //Destroy window
    SDL_DestroyRenderer( gRenderer );
    gRenderer = nullptr;
    SDL_DestroyWindow( gWindow );
    gWindow = nullptr;

    //Quit SDL subsystems
    TTF_Quit();
    SDL_Quit();
}
When we're done with a font, we free it with TTF_CloseFont. When we want to close SDL_ttf, we call TTF_Quit.
                //Fill the background
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF,  0xFF );
                SDL_RenderClear( gRenderer );

                //Render text
                gTextTexture.render( ( kScreenWidth - gTextTexture.getWidth() ) / 2.f, ( kScreenHeight - gTextTexture.getHeight() ) / 2.f );

                //Update screen
                SDL_RenderPresent( gRenderer );
Finally, here we render the text texture we loaded.

SDL_ttf has other method of rendering text which you can find in the SDL_ttf documentation. We'll be covering some of them in future tutorials, I just wanted to get started with this one because it's the easiest if you already know how to render SDL textures.