I'm using an SDL_Window
and SDL_Renderer
.
Is it possible to use SDL_TTF
with SDL_Render/SDL_Window
? If so, how?
Yep, it is possible, given that you have a renderer and a window plus you don't really have any thoughts on dabbling with surfaces then you might want to mind on creating texture, here is a sample code
//this opens a font style and sets a size
TTF_Font* Sans = TTF_OpenFont("Sans.ttf", 24);
// this is the color in rgb format,
// maxing out all would give you the color white,
// and it will be your text's color
SDL_Color White = {255, 255, 255};
// as TTF_RenderText_Solid could only be used on
// SDL_Surface then you have to create the surface first
SDL_Surface* surfaceMessage =
TTF_RenderText_Solid(Sans, "put your text here", White);
// now you can convert it into a texture
SDL_Texture* Message = SDL_CreateTextureFromSurface(renderer, surfaceMessage);
SDL_Rect Message_rect; //create a rect
Message_rect.x = 0; //controls the rect's x coordinate
Message_rect.y = 0; // controls the rect's y coordinte
Message_rect.w = 100; // controls the width of the rect
Message_rect.h = 100; // controls the height of the rect
// (0,0) is on the top left of the window/screen,
// think a rect as the text's box,
// that way it would be very simple to understand
// Now since it's a texture, you have to put RenderCopy
// in your game loop area, the area where the whole code executes
// you put the renderer's name first, the Message,
// the crop size (you can ignore this if you don't want
// to dabble with cropping), and the rect which is the size
// and coordinate of your texture
SDL_RenderCopy(renderer, Message, NULL, &Message_rect);
// Don't forget to free your surface and texture
SDL_FreeSurface(surfaceMessage);
SDL_DestroyTexture(Message);
I tried to explain the code line by line, you don't see any window right there since I already assumed that you knew how to initialize a renderer which would give me an idea that you also know how to initialize a window, then all you need is the idea on how to initialize a texture.
Minor questions here, did your window open? was it colored black? if so then my thoughts were right, if not, then you can just ask me and I could change this code to implement the whole section which consists of a renderer and a window.
SDL_ttf minimal runnable example
Not super efficient, but easy to integrate. For efficiency see: How to render fonts and text with SDL2 efficiently?
Kept in a separate repo than the main SDL source, but hosted on the same official server, so should be fine: http://hg.libsdl.org/SDL_ttf/
Newlines won't work. You have to work with line heights.
Compile and run:
sudo apt-get install -y libsdl2-dev
gcc -lSDL2 -lSDL2_ttf -o ttf ttf.c
./ttf /usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf
You must pass the path of a TTF font file to the program.
ttf.c
#include <stdlib.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#define WINDOW_WIDTH 300
#define WINDOW_HEIGHT (WINDOW_WIDTH)
/*
- x, y: upper left corner.
- texture, rect: outputs.
*/
void get_text_and_rect(SDL_Renderer *renderer, int x, int y, char *text,
TTF_Font *font, SDL_Texture **texture, SDL_Rect *rect) {
int text_width;
int text_height;
SDL_Surface *surface;
SDL_Color textColor = {255, 255, 255, 0};
surface = TTF_RenderText_Solid(font, text, textColor);
*texture = SDL_CreateTextureFromSurface(renderer, surface);
text_width = surface->w;
text_height = surface->h;
SDL_FreeSurface(surface);
rect->x = x;
rect->y = y;
rect->w = text_width;
rect->h = text_height;
}
int main(int argc, char **argv) {
SDL_Event event;
SDL_Rect rect1, rect2;
SDL_Renderer *renderer;
SDL_Texture *texture1, *texture2;
SDL_Window *window;
char *font_path;
int quit;
if (argc == 1) {
font_path = "FreeSans.ttf";
} else if (argc == 2) {
font_path = argv[1];
} else {
fprintf(stderr, "error: too many arguments\n");
exit(EXIT_FAILURE);
}
/* Inint TTF. */
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_WIDTH, 0, &window, &renderer);
TTF_Init();
TTF_Font *font = TTF_OpenFont(font_path, 24);
if (font == NULL) {
fprintf(stderr, "error: font not found\n");
exit(EXIT_FAILURE);
}
get_text_and_rect(renderer, 0, 0, "hello", font, &texture1, &rect1);
get_text_and_rect(renderer, 0, rect1.y + rect1.h, "world", font, &texture2, &rect2);
quit = 0;
while (!quit) {
while (SDL_PollEvent(&event) == 1) {
if (event.type == SDL_QUIT) {
quit = 1;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
/* Use TTF textures. */
SDL_RenderCopy(renderer, texture1, NULL, &rect1);
SDL_RenderCopy(renderer, texture2, NULL, &rect2);
SDL_RenderPresent(renderer);
}
/* Deinit TTF. */
SDL_DestroyTexture(texture1);
SDL_DestroyTexture(texture2);
TTF_Quit();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}
Tested in Ubuntu 16.04, SDL 2.0.4.
Yes it is. You create a surface with the text you want and then convert it to a texture that you can render.
Some sample code from one of my projects:
std::string score_text = "score: " + std::to_string(score);
SDL_Color textColor = { 255, 255, 255, 0 };
SDL_Surface* textSurface = TTF_RenderText_Solid(font, score_text.c_str(), textColor);
SDL_Texture* text = SDL_CreateTextureFromSurface(renderer, textSurface);
int text_width = textSurface->w;
int text_height = textSurface->h;
SDL_FreeSurface(textSurface);
SDL_Rect renderQuad = { 20, win_height - 30, text_width, text_height };
SDL_RenderCopy(renderer, text, NULL, &renderQuad);
SDL_DestroyTexture(text);
This assumes you've properly initialized SDL_ttf and loaded a font. In the example score
is an int. The screen gets cleared and rendered to somewhere else (I didn't include that part).
For a full working example, check out the tutorial for SDL_ttf in SDL2 at Lazy Foo.
Since there are some people struggling with more complex code, I've included my own snippet here to help some beginners like myself. This will just show a red screen with a black hello world. Don't forget to add -lsdl2 and -lsdl2_ttf on your build and include the Verdana.ttf font on the same folder.
#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
const char* TITLE = "Hello World SDL2 + TTF";
const int WIDTH = 1280, HEIGHT = 720;
int main() {
SDL_Window *window = SDL_CreateWindow(TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Event windowEvent;
TTF_Init();
TTF_Font *verdanaFont = TTF_OpenFont("Verdana.ttf", 128);
SDL_Color textColor = { 0, 0, 0, 255 };
SDL_Surface *textSurface = TTF_RenderText_Solid(verdanaFont, "Hello World", textColor);
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
SDL_Rect textRect;
textRect.x = WIDTH - textSurface->w * 0.5;
textRect.y = HEIGHT - textSurface->h * 0.5;
textRect.w = textSurface->w;
textRect.h = textSurface->h;
SDL_FreeSurface(textSurface);
TTF_Quit();
bool isRunning = true;
while (isRunning) {
while(SDL_PollEvent(&windowEvent)) {
switch (windowEvent.type) {
case SDL_QUIT:
isRunning = false;
break;
}
}
SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
SDL_RenderPresent(renderer);
}
SDL_DestroyTexture(textTexture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}
If you're trying to do this from Powershell on Windows, you'll soon find out that this is a real PITA. Until now...
I've just spent the hours to debug all the details to get this to work, when you want to insist using Clang++
and SDL2
to render a native Windows window with text.
There are 3 things you need to install; LLVM, SDL2, SDL2_ttf
. Then you have to ensure your program will find your libraries, headers and fonts. This is basically summarized in the following program here:
//---------------------------------------------------------------------
// Name: HelloSDL2.cpp
// Author: EAML
// Date: 2021-05-16
//
// Description:
// A minimal PoC for producing a native SDL2 Windows app that can
// be ran from either Windows Explorer or from Powershell console.
// It's designed to use minimal command line, compiler options,
// and dependencies... It will display a gray window for 2 sec's.
//
// Dependencies:
// [1] LLVM Clang++ compiler package
// [2] SDL2 Libraries (DLL's) and Header files (*.h)
// [3] TTF Libraries (DLL's) and Header files (*.h)
//
// Notes:
// There is a slight variation in the bahaviour, depending on:
// (a) if you compile as a Windows GUI: the text will not show.
// (b) if you compile as a console CLI: text will show in both terminal and/or in a 2nd new window
// (c) You may need to use "main()" for console and "WinMain()" for GUI...
// (c) to install on Linux, use packages: clang, libsdl2-dev
// (d) Someone said: #define SDL_MAIN_HANDLED ...
//
// To Run:
// cp .\SDL2\lib\x64\SDL2.dll C:\Windows\. # For SDL2
// cp .\SDL2_ttf\lib\x64\*.dll C:\Windows\. # For SDL2 TTF
// cp C:\Windows\Fonts\arial.ttf . # Get a font...
//
// For a CLI version, with console output in 2nd Window:
// # clang++.exe -std=c++11 main.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:console
//
// For a GUI version, without any console output:
// # clang++.exe -std=c++11 main.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:windows
//
// References:
// [1] https://github.com/llvm/llvm-project/releases
// [2] http://www.libsdl.org/release/SDL2-devel-2.0.14-VC.zip
// [3] https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-devel-2.0.15-VC.zip
// [4] https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html
// [5] http://www.sdltutorials.com/sdl-ttf
//---------------------------------------------------------------------
//#include <SDL2/SDL.h>
#include "SDL2/include/SDL.h"
#include "SDL2_ttf/include/SDL_ttf.h"
#include <stdio.h>
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define WINDOW_TITLE "Hello SDL2!"
//#define WINDOW_TEXT "Hello World!"
void drawText ( SDL_Surface* screen, char* string, int size, int x, int y, SDL_Color fgC, SDL_Color bgC) {
// Remember to call TTF_Init(), TTF_Quit(), before/after using this function.
TTF_Font* font = TTF_OpenFont("arial.ttf", size);
if(!font) {
printf("[ERROR] TTF_OpenFont() Failed with: %s\n", TTF_GetError());
exit(2);
}
TTF_SetFontStyle(font, TTF_STYLE_BOLD);
//SDL_Surface* textSurface = TTF_RenderText_Solid(font, string, fgC);
SDL_Surface* textSurface = TTF_RenderText_Shaded(font, string, fgC, bgC);
SDL_Rect textLocation = { x, y, 0, 0 };
SDL_BlitSurface(textSurface, NULL, screen, &textLocation);
SDL_FreeSurface(textSurface);
TTF_CloseFont(font);
//printf("Oh My Goodness, an error : %s\n", TTF_GetError()); return 1;
}
int main(int argc, char* args[]) {
SDL_Window* window = NULL; // The window we are rendering to
SDL_Surface* screenSurface = NULL; // The surface contained by the window
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow(WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == NULL) {
printf( "Window could not be created! SDL Error: %s\n", SDL_GetError());
return 1;
}
screenSurface = SDL_GetWindowSurface(window);
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0x80, 0x80, 0x80)); // Set a gray background canvas
SDL_UpdateWindowSurface(window);
//-----------------------------------------------------
// Draw the Text
//-----------------------------------------------------
if(TTF_Init() == -1) {
printf("[ERROR] TTF_Init() Failed with: %s\n", TTF_GetError());
exit(2);
}
SDL_Color fgC1 = { 0xff,0xff,0xff }, bgC1 = {0x00,0x00,0xa0}; // white text on blue background
SDL_Color fgC2 = { 0x00,0x00,0x00 }, bgC2 = {0xff,0x00,0xff}; // black text on magenta background
drawText( screenSurface, (char*) "Hello World! @ (x=50, y=100)", 18, 50,100, fgC1, bgC1); // 18 pt @ (x=100,y=150)
drawText( screenSurface, (char*) "arial.ttf @ (x=200, y=150)", 16, 200,150, fgC2, bgC2); // 16 pt @ (x=100,y=150)
SDL_UpdateWindowSurface(window);
TTF_Quit();
//-----------------------------------------------------
// Get some info...
//-----------------------------------------------------
SDL_version compiled;
SDL_version linked;
SDL_version ttfv;
SDL_VERSION(&compiled);
SDL_GetVersion(&linked);
SDL_TTF_VERSION(&ttfv);
printf("Compiled using SDL version : %d.%d.%d \n", compiled.major, compiled.minor, compiled.patch);
printf("and linked with SDL version : %d.%d.%d \n", linked.major, linked.minor, linked.patch);
printf("and using SDL_TTF version : %d.%d.%d \n", ttfv.major, ttfv.minor, ttfv.patch);
SDL_Delay(3000); // Wait 3 seconds
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
The result of running the code above is this:
How to get to this point?
Install LLVM for Windows:
[x] add to Windows PATH
.If you didn't add LLVM to your Windows PATH, then at least add it temporarily and manually.
$env:path += ";C:\Program Files\LLVM\bin"
Install SDL2 for Windows: Download and extract the SDL2 & SDL2_ttf runtime binaries (*.dll) and header libraries (found in [2,3]) and put them into separate SDL folder(s) in the same directory as your C++ file.
You should now have something like:
# tree --dirsfirst ./SDL2{,_ttf} -P *.h
./SDL2
├── include
│ ├── begin_code.h
│ ├── close_code.h
│ ├── SDL.h
│ ├── SDL_assert.h
...
│ ├── SDL_version.h
│ ├── SDL_video.h
│ └── SDL_vulkan.h
└── lib
./SDL2_ttf
└── include
└── SDL_ttf.h
# tree --dirsfirst ./SDL2{,_ttf}/lib -P *.dll
./SDL2/lib
├── x64
│ └── SDL2.dll
└── x86
└── SDL2.dll
./SDL2_ttf/lib
├── x64
│ ├── libfreetype-6.dll
│ ├── SDL2_ttf.dll
│ └── zlib1.dll
└── x86
├── libfreetype-6.dll
├── SDL2_ttf.dll
└── zlib1.dll
C:\Windows\
, unless you know how to make clang++.exe able to find them. (I wasn't able...)cd C:\path\to\main.cpp
cp .\SDL2\lib\x64\SDL2.dll C:\Windows\. # For SDL2
cp .\SDL2_ttf\lib\x64\*.dll C:\Windows\. # For SDL2 TTF
cp C:\Windows\Fonts\arial.ttf . # Get a font...
Download the above SDL2 "Hello World" Windows program.
Compile the program with:
clang++.exe -std=c++11 main2.cpp -o main.exe -L .\SDL2\lib\x64\ -L .\SDL2_ttf\lib\x64\ -I .\SDL2_ttf\include\ -I .\SDL2\include\ -lShell32 -lSDL2main -lSDL2 -lSDL2_ttf -Wno-narrowing -Xlinker /subsystem:windows
The order of how the libraries are placed, seem to have an importance. Make sure it's like above.
Also, notice the two different -Xlinker
options:
/subsystem:windows # This give you only one window but no console output
/subsystem:console # This give you console output, but in a 2nd window when in GUI
To see other linker optins, use:
link.exe /link
link.exe /lib
# The most relevant are:
/DLL
/ENTRY:symbol
/LIBPATH:dir
/MACHINE:{ARM|ARM64|EBC|X64|X86}
/SUBSYSTEM:{CONSOLE | NATIVE | POSIX | WINDOWS | WINDOWSCE |...}
/VERBOSE
You are now good to go!
Download References