Tutorial de SDL – Parte 16 – Fontes TrueType

Continuando nossa série de artigo traduzido do site lazyfoo, veremos agora uma maneira de renderizar texto com SDL usando a biblioteca SDL_ttf. Essa biblioteca permite que você crie imagens a partir de fontes TrueType que podem ser usadas para criar texturas a partir de texto.

//Using SDL, SDL_image, SDL_ttf, standard IO, math, and strings
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <stdio.h>
#include <string>
#include <cmath>

Para fazer uso da biblioteca SDL_ttf, você precisa configurar a biblioteca de extensão SDL_ttf da mesma forma que fizemos com a biblioteca SDL_image. Como anteriormente, é somente uma questão de ter arquivos de cabeçalho, de biblioteca e binários nos locais apropriados, além de configurar seu compilador para trabalhar com todos esse arquivos.

//Texture wrapper class
class LTexture
{
 public:
 //Initializes variables
 LTexture();
 //Deallocates memory
 ~LTexture();
 //Loads image at specified path
 bool loadFromFile( std::string path );
 //Creates image from font string
 bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
 //Deallocates texture
 void free();
 //Set color modulation
 void setColor( Uint8 red, Uint8 green, Uint8 blue );
 //Set blending
 void setBlendMode( SDL_BlendMode blending );
 //Set alpha modulation
 void setAlpha( Uint8 alpha );
 //Renders texture at given point
 void render( int x, int y, SDL_Rect* clip = NULL, double angle = 0.0, SDL_Point* center = NULL, SDL_RendererFlip flip = SDL_FLIP_NONE );
 //Gets image dimensions
 int getWidth();
 int getHeight();
 private:
 //The actual hardware texture
 SDL_Texture* mTexture;
 //Image dimensions
 int mWidth;
 int mHeight;
};

Aqui adicionamos outra função à classe de textura com o nome loadFromRenderedText. O jeito da biblioteca SDL_ttf trabalhar é criar uma nova imagem a partir de uma fonte e uma cor. Em nosso exemplo, no caso de nossa classe de textura, isso tudo significa que iremos carregar a imagem a partir de um texto renderizado ao invés de um arquivo.

//The window we'll be rendering to
SDL_Window* gWindow = NULL;
//The window renderer
SDL_Renderer* gRenderer = NULL;
//Globally used font
TTF_Font *gFont = NULL;
//Rendered texture
LTexture gTextTexture;

Neste e em artigos futuros, estaremos usando uma fonte global para nossas renderizações de texto. No SDL_ttf, o tipo de dados para fontes de texto é o TTF_Font.
Também temos um textura que será gerada a partir da fonte.

bool LTexture::loadFromRenderedText( std::string textureText, SDL_Color textColor )
{
 //Get rid of preexisting texture
 free();
 //Render text surface
 SDL_Surface* textSurface = TTF_RenderText_Solid( gFont, textureText.c_str(), textColor );
 if( textSurface == NULL )
 {
 printf( "Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError() );
 }
 else
 {
 //Create texture from surface pixels
 mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface );
 if( mTexture == NULL )
 {
 printf( "Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError() );
 }
 else
 {
 //Get image dimensions
 mWidth = textSurface->w;
 mHeight = textSurface->h;
 }
 //Get rid of old surface
 SDL_FreeSurface( textSurface );
 }
 //Return success
 return mTexture != NULL;
}

Aqui é onde de fato criamos a textura que iremos renderizar a partir da fonte. Essa função recebe a string de texto que queremos renderizar e a cor que queremos usar para a renderização. Com tudo isso, essa função basicamente funciona da mesma forma que o carregamento a partir de um arquivo, apenas que dessa vez estamos usando um SDL_Surface criado por SDL_ttf ao invés de um arquivo.
Depois de liberar qualquer textura pré-existente, carregamos uma superfície usando TTF_RenderText_Solid. Isso cria uma superfície com uma cor sólida com a fonte, texto e cor informada. Se a superfície for criada com sucesso, ciamos uma textura a partir dela da mesma forma que fizemos quando carregamos a superfície de um arquivo. Depois que a textura do texto é criada, podemos renderiza-la como qualquer outra textura.
Existem outras maneiras de renderizar texto que são mais suaves. Experimente com os tipos diferentes de renderização listadas na documentação do SDL_ttf.

 //Initialize PNG loading
 int imgFlags = IMG_INIT_PNG;
 if( !( IMG_Init( imgFlags ) & imgFlags ) )
 {
 printf( "SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError() );
 success = false;
 }
 //Initialize SDL_ttf
 if( TTF_Init() == -1 )
 {
 printf( "SDL_ttf could not initialize! SDL_ttf Error: %s\n", TTF_GetError() );
 success = false;
 }

Da mesma forma que no caso da biblioteca SDL_image, temos que inicializar SDL_ttf ou o carregamento de fontes e as funções de renderização não irão funcionar de forma apropriada.  Iniciamos SDL_ttf usando TTF_init. Podemos checar por error usando TTF_GetError().

bool loadMedia()
{
 //Loading success flag
 bool success = true;
 //Open the font
 gFont = TTF_OpenFont( "16_true_type_fonts/lazy.ttf", 28 );
 if( gFont == NULL )
 {
 printf( "Failed to load lazy font! SDL_ttf Error: %s\n", TTF_GetError() );
 success = false;
 }
 else
 {
 //Render text
 SDL_Color textColor = { 0, 0, 0 };
 if( !gTextTexture.loadFromRenderedText( "The quick brown fox jumps over the lazy dog", textColor ) )
 {
 printf( "Failed to render text texture!\n" );
 success = false;
 }
 }
 return success;
}

Em nossa função de carregamento, carregamos nossa fonte usando a função TTF_OpenFont. Essa função recebe o caminho para o arquivo da fonte e um tamanho do ponto para o qual queremos renderiza-la.
Se a fonte for carregada com sucesso, queremos carregar agora a textura do texto usando nossa método de carregamento. Como regra geral, você quer minimizar a quantidade de tempo gasto para renderizar texto. Apenas re-renderize quando você realmente precise e como estamos usando a mesma superfície de texto em todo o programa, iremos renderiza-lo apenas uma vez.

 //While application is running
 while( !quit )
 {
 //Handle events on queue
 while( SDL_PollEvent( &e ) != 0 )
 {
 //User requests quit
 if( e.type == SDL_QUIT )
 {
 quit = true;
 }
 }
 //Clear screen
 SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
 SDL_RenderClear( gRenderer );
 //Render current frame
 gTextTexture.render( ( SCREEN_WIDTH - gTextTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gTextTexture.getHeight() ) / 2 );
 //Update screen
 SDL_RenderPresent( gRenderer );
 }

Como você pode ver,  após a renderização da textura do texto podemos continuar a renderização com fazemos com qualquer outra textura.
Baixe os arquivos de mídia e código fonte desse artigo aqui.