Briefly: What’s a font?
A font represents the style how the letters of a word or sentence appear (e.g. Arial, New Times Roman, and so on). These style informations are saved in so-called font files. There are different standards how to save the font into a file, but the most important font file standard is the TrueType Font standard, thus the name SDL2_ttf. SDL2_ttf is capable to work with TrueType fonts. The typical file extension of TrueType font files is “.ttf”. FreeType 2.0 is no font file standard but a software library to render TrueType font files.
Fonts in Applications
In the last chapters you have heard a lot about the creation of textures from image files and how to manipulate and draw to them. But there is nearly no application out there where you go out without some text messages. Of course, you could print some text to an image and load this image to your application to introduce text to it. But what if you like to implement a chat feature to your application where text messages are created dynamically? Or you like the user to insert his name for a highscore list? There are thousands of other circumstances where you need dynamically generated texts.
The SDL2_ttf Unit
Here comes the SDL2_ttf unit into play. The ability to do this is not implemented in the native SDL 2.0 library itself but the SDL project provides an official extension called SDL2_ttf to render TrueType fonts based on the FreeType project and their FreeType 2.0 release.
As for the work with SDL2_image you will need to add some library files to your system to use fonts since SDL2_ttf isn’t part of native SDL 2.0.
- download the most recent version of the Runtime Binaries of the SDL2_ttf library for your system
- install the library according to your system (Win32/64, Linux, Mac OS X)
SDL2_ttf Installing Instructions for Windows
Download the corresponding SDL2_ttf package depending on your system (32 bit or 64 bit) and extract the zip file. You will end up with a SDL2_ttf.dll and two more dlls (zlib1.dll, libfreetype-6.dll) which are necessary for support of compression and FreeType routines. Copy all these files to your system folder, e.g. for Windows XP or Windows 8.1 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files into the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.
If you are running on a different platform (e.g. Mac OS X, Linux, …), please check the link given above for further information on SDL2_ttf installation.
The Road to Texts in SDL 2.0
Before jumping right into the code, let’s discuss the two concepts of SDL 2.0 of saving images in memory again. We prefer to work with textures because they have all the advantages we discussed in prior chapters, especially in Chapter – Surfaces and Textures. Anyway, in SDL2_ttf there are several functions to load a text as a surface (details later). Hence we need to convert the surface into a texture as known. Look at the following diagram to understand the way we need to go:
So according to the diagram the way to go is as follows, if you have a text in mind (left quadrat in diagram), first create a SDL_Surface by using one of the functions SDL2_ttf provides (lower path to quad at the bottom in the diagram). Next you convert this SDL_Surface into a SDL_Texture by the function SDL_CreateTextureFromSurface (go straight from the buttom quad to the upper quad in the diagram). Finally render the text as you like to the screen.
Sidenote, “blitting” and SDL_Flip are not possible in SDL2 anymore for good (performance) reasons.
Let’s jump into the code now .
program SDL_Fonts; uses SDL2, SDL2_ttf; var sdlSurface1 : PSDL_Surface; ttfFont : PTTF_Font; sdlColor1, sdlColor2 : TSDL_Color; sdlWindow1 : PSDL_Window; sdlRenderer : PSDL_Renderer; sdlTexture1 : PSDL_Texture; begin //initilization of video subsystem if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT; sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN); if sdlWindow1 = nil then HALT; sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0); if sdlRenderer = nil then HALT; //initialization of TrueType font engine and loading of a font if TTF_Init = -1 then HALT; ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40); TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or TTF_STYLE_ITALIC); TTF_SetFontOutline(ttfFont, 1); TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL); //define colors by RGB values sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0; sdlColor2.r := 0; sdlColor2.g := 255; sdlColor2.b := 255; //rendering a text to a SDL_Surface sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2); //convert SDL_Surface to SDL_Texture sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1); //rendering of the texture SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil); SDL_RenderPresent(sdlRenderer); SDL_Delay(5000); //cleaning procedure TTF_CloseFont(ttfFont); TTF_Quit; SDL_FreeSurface(sdlSurface1); SDL_DestroyTexture(sdlTexture1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow(sdlWindow1); //shutting down video subsystem SDL_Quit; end.
The final result should behave like this: The text “Hello World!” appears for five seconds in italics and underlined. The text is red and the background is cyan. The following screenshot gives an impression what is to be expected.
Let’s begin with the initial lines of code.
program SDL_Fonts; uses SDL2, SDL2_ttf;
The program is called “SDL_Fonts” and we will need unit SDL2_ttf additional to SDL2 to have access to the TrueType font engine.
var sdlSurface1 : PSDL_Surface; ttfFont : PTTF_Font; sdlColor1, sdlColor2 : TSDL_Color; sdlWindow1 : PSDL_Window; sdlRenderer : PSDL_Renderer; sdlTexture1 : PSDL_Texture; begin //initilization of video subsystem if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT; sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN); if sdlWindow1 = nil then HALT; sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0); if sdlRenderer = nil then HALT;
Then we are preparing for some new types of variables. First of all we have a pointer variable called “sdlSurface1” which is of type PSDL_Surface and points at a SDL_Surface. The text message we provide will be rendered into this SDL_Surface first.
Next we find a PTTF_Font variable called “ttfFont” which points at TTF_Font that holds the font’s data. The font’s data itself is usually stored in a font file.
Finally there are two color variables “sdlColor1” and “sdlColor” of pointer type PSDL_Color. These pointers point at records which contain color data as composition of a r, b and g value. The exact definition will be shown when they are used in the code.
Well, the next variables are known from previous chapters. Also the initialization of SDL 2.0 and the creation of the window and the renderer are known already. So we should proceed to the next code chunk now.
//initialization of TrueType font engine and loading of a font if TTF_Init = -1 then HALT; ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40); TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or TTF_STYLE_ITALIC); TTF_SetFontOutline(ttfFont, 1); TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);
To inialize the TrueType font engine
without any arguments is called which returns 0 on success and -1 on error. There is no specific error code returned.
TTF_OpenFont(_file: PAnsiChar; ptsize: Integer): PTTF_Font
prepares a font. The first parameter “_file” asks for the absolute file path of the font. The parameter “ptsize” asks for an integer value which determines the size of the font. The larger the value, the larger the letters will appear finally (just as known from text editors). Anyway, if you choose too large size the largeste size will be chosen.
Styling the text in SDL 2.0
TTF_SetFontStyle(font: PTTF_Font; style: Integer),
TTF_SetFontOutline(font: PTTF_Font; outline: Integer)
TTF_SetFontHinting(font: PTTF_Font; hinting: Integer)
further shaping of the appearence of the text can be performed. All the procedures need to know as first argument to which font they should be applied, so “ttfFont” in our case. Then the style can be set by the constants shown in the following table, which are kind of self-explanatory. By OR’ing the style constants you can even combine them as shown in the example code. The text shall be in italics and underlined.
- No application of a certain style
- No application of a certain style
- Set a bold style
- Set letters in italics
- Have the text underlined
- Have the text stroken through
The outline is simply set by a number. The larger the number, the larger the outline of the letters will appear. If you don’t need an outline, the argument has to be 0.
The hinting is set similar to the style by pre-defined constants shown in the following table. The hinting setting influences the appearance of the letters and text. In general it should lead to sharper letters, anyway, which setting is best for a certain situation and display device may differ. So if you are unsure what setting to use you should choose TTF_HINTING_NORMAL. If you don’t call this procedure this setting is the default anyway.
- Normal hinting is applied
- Light hinting is applied
- I guess monospaced characters, so all the characters have the same space between each other
- Have the text underlined
All of these three procedure have a counter-function which returns the set style, outline and hinting as an integer number, defined as
TTF_GetFontStyle(font: PTTF_Font): Integer,
TTF_GetFontOutline(font: PTTF_Font): Integer,
TTF_GetFontHinting(font: PTTF_Font): Integer.
Obviously the only parameter to be set is the font whose style, outline or hinting you like to know. These functions aren’t demonstrated in the sample though.
Colouring the Text using TSDL_Color/PSDL_Color
The next lines of code are waiting for us.
//define colors by RGB values sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0; sdlColor2.r := 0; sdlColor2.g := 255; sdlColor2.b := 255;
Here we are starting to consider some coloring of the text. The first color set up in “sdlColor1” of kind TSDL_Color will be the color of the letters. The second color set up in “sdlColor2” will be the background color of the letters. Let’s have a look at the definition of TSDL_Color:
TSDL_Color and RGB triples
TSDL_Color = record r: UInt8; g: UInt8; b: UInt8; a: UInt8; end;
The TSDL_Color record has four fields. For additive color mixing you often have a red share, a green share and a blue share (RGB triple). These three fundamental colors are capable of generating any other color by mixing in the respective proportions. E.g. 100% red and 0% green and 0% blue will lead to red. But 100% red, 100% green and 0% blue will lead to yellow. If all three colours are 100% you will get white. If all of them are 0% you will get black. You may notice the variable type UInt8 which means 8bit unsigned integer. From this follows that the values can range between 0 and 255 where 0 equals 0% and 255 equals 100% of the corresponding color. So 2563 = 16,777,216 individual colors can be created.
The a field is for the alpha value, which determines the share of transparency. It is set to opaque (value 255) by default.
//rendering a text to a SDL_Surface sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2);
Finally a text, e.g. the famous “Hello World!” with the chosen colors has to be created. There are several functions to do so and just one of them is chosen for demonstration how the creation generally works. The chosen function is
TTF_RenderText_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
It requires four arguments and returns a PSDL_Surface (no PSDL_Texture!), a pointer to a SDL_Surface. The first argument is the font you would like to use. Next is the actual text. The text can be directly written as shown in the example but also could be stored in a variable of type PAnsiChar.
PAnsiChar and Pascal Strings
Usually String variables are used in Pascal programming language, so why here we have a PAnsiChar? Well, since SDL is written in C/C++, and there typically null-terminated strings of characters are used to store and handle text, this is kept for better compatibility and easier translation of SDL 2.0 for Pascal compilers. In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer String variables.
Last but not least there are the two arguments “fg” and “bg” which translates to foreground and background. Here we insert the colors we defined before. That’s it.
Text Quality and Rendering Speed
Okay, as mentioned before there are more functions to create a SDL_Surface with text. In general there are three groups of functions according to their properties. The general naming scheme is as follows:
Those which have the suffix “Solid” are very fast created but their quality is low. Use them when rendering fast changing text (e.g. the score of a pinball simulation). The second group has the suffix “Shaded”. They are slower rendered and have a background color but have much better quality. The last group of functions have the suffix “Blended”. They are of high quality but slow to be rendered. Use them for more static text which doesn’t change a lot.
For each group of quality and functions you find “Text”, “UTF8”, “UNICODE” and “Glyph” in the encoding part of the functions name right after “TTF_Render”. “Text”, “UTF8” and “UNICODE” are three different types of encoding of the text. “Text” corresponds to Latin1 encoding, “UTF8” to UTF-8 encoding and “UNICODE” to Unicode encoding. Which type to choose depends on what type of characters (e.g. Cyrillic letters, Latin characters, Chinese characters) you are going to use. If you are unsure which of these functions to use, go with the “UNICODE” version.
For rendering a single character by its Unicode code, use the function which contains “Glyph” as suffix to “TTF_Render”.
Finally I’d like to mention a special function which is just available in high quality “Blended” mode. It has the suffix “_Blended_Wrapped”. Additional to the usual parameters there is a parameter wrapLength of type UInt32 (32 bit unsigned integer). Here you can have an integer value which determines the amount of pixels until the text will be word-wrapped. So in our case with a width of the window of 500 pixels the setting of wrapLength to 250 for example would result in a word-wrap when a word would exceed 250 pixels.
Overview: Rendering modes
The following list summarizes all the functions and most important properties for the three differen rendering modes.
- transparency by colorkey (0 pixel)
- very fast but low quality
- 8-bit palettized RGB surface
- TTF_RenderText_Solid(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
- TTF_RenderUTF8_Solid(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
- TTF_RenderUNICODE_Solid(font: PTTF_Font; text: PUInt16; fg: TSDL_Color): PSDL_Surface
- TTF_RenderGlyph_Solid(font: PTTF_Font; ch: UInt16; fg: TSDL_Color): PSDL_Surface
- slower than solid rendering, but high quality
- 8-bit palettized RGB surface
- TTF_RenderText_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
- TTF_RenderUTF8_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
- TTF_RenderUNICODE_Shaded(font: PTTF_Font; text: PUInt16; fg, bg: TSDL_Color): PSDL_Surface
- TTF_RenderGlyph_Shaded(font: PTTF_Font; ch: UInt16; fg, bg: TSDL_Color): PSDL_Surface
- transparency (alpha channel)
- slow but very high quality
- 32-bit unpalettized (RGBA) surface
- TTF_RenderText_Blended(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
- TTF_RenderUTF8_Blended(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
- TTF_RenderUNICODE_Blended(font: PTTF_Font; text: UInt16; fg: TSDL_Color): PSDL_Surface
- TTF_RenderText_Blended_Wrapped(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color; wrapLength: UInt32): PSDL_Surface
- TTF_RenderGlyph_Blended(font: PTTF_Font; ch: UInt16; fg: TSDL_Color): PSDL_Surface
All the functions will return nil on failure to create a SDL_Surface.
Converting a SDL_Surface into a SDL_Texture
Okay, now let’s proceed to the next lines of code.
//convert SDL_Surface to SDL_Texture sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1); //rendering of the texture SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil); SDL_RenderPresent(sdlRenderer); SDL_Delay(5000);
In the previous table you saw all the functions to generate SDL_Surfaces with a certain text. Now we need to transform it into a SDL_Texture. The function to do so by SDL_CreateTextureFromSurface as known.
Then the result will be rendered to the window. For simplicity the third and fourth argument for SDL_RenderCopy() are nil which means that the SDL_Texture with the text will be stretched to the window. The rendered result is shown for 5000 ms (5 seconds).
We can proceed to the cleaning process.
//cleaning procedure TTF_CloseFont(ttfFont); TTF_Quit; SDL_FreeSurface(sdlSurface1); SDL_DestroyTexture(sdlTexture1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow(sdlWindow1); //shutting down video subsystem SDL_Quit;
All the allocated memory has to be free’d now. The font is free’d by procedure
and the TrueType engine is quit by procedure
“sdlSurface1” is free’d by procedure
and the texture, renderer and window as known. After that SDL 2.0 is shut down.
Remark: DO NOT create text surfaces on the fly!
As for the loading of bitmap files, while it is possible to directly load the surface to the texture without declaring a surface by combining the surface creation with the texture creation as follows:
sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2));
Don’t do this, because you have no handle (the surface pointer) to free the surface memory afterwards.
Have fun working with texts :-).
5 thoughts on “SDL2_ttf: Rendering Fonts and Texts”
When using the SDL_RenderCopy() function, is there a way of working out the last parameter (the destination rectangle, dstrect) so that it fits the text string / font size, rather than stretching to fill the whole window?
Sure, you create a rectangle with the width/height dimensions from sdlSurface1 (in the example code). The x/y values will determine where the text is “rendercopied” finally.
Hope this helps.
Ukrainian ttranslation avaliable here: https://lazarus-games.blogspot.com/p/9-sdl2ttf.html
unfortunately to my knowledge there are no finished SDL2/Pascal GUIs libraries available at the moment. Nevertheless, you may have a look at this project: https://github.com/Free-Pascal-meets-SDL-Website/SimpleGUI which rather provides what you are looking for.
Please let me know if this helps.
I am new to Lazarus Pascal and SDL, but after one day of trial and error I was able to put this lesson in a procedure and make the Hello window appear by clicking a button on the lazarus pascal form. Do you plan on making any lessons on how to integrate forms and controls with SDL or do you know of any references?