Viser innlegg med etiketten rendering text. Vis alle innlegg
Viser innlegg med etiketten rendering text. Vis alle innlegg

onsdag 30. juli 2014

[ SDL2 - Part 11 ] Text styling

Text styles in TTF


In the last post we looked at how to render text. Now let's take this a step further and change the appearance of the font. There are two ways you can change how the font looks. Font style and font outline.

Fon styles


SDL TTF allows you to set the style of the font. It supports the following font styles

  • bold 
  • italic 
  • underlined 
  • underlined

You can combine these in any way you like. We'll start of with just setting a single font style at a time, and then move on to see how we can apply several of them at once.

Font styles


Settnig font styles in TTF is easy, it just requires a single function. The function let's you set one or more font styles. Let's start off by looking at how to set just one font style

Setting the font style


We can set the font style using the following function
void TTF_SetFontStyle
(
    TTF_Font *font,
    int style
)
The arguments are :
  •  TTF_Font *font - the font to set the style on
  • int style       - the style to set on the font

As you can see, the style parameter is an int and not an enum. I'll get back to why that is later, but for now let's look at the possible values for style, these are all self-explanatory so I won't be adding a description.

  • TTF_STYLE_NORMAL
  • TTF_STYLE_BOLD
  • TTF_STYLE_ITALIC
  • TTF_STYLE_UNDERLINE
  • TTF_STYLE_STRIKETRHOUGH

Any text you render after setting this font style will have the new effect, but it won't change any text you have written with a different style. So when you set the style to TTF_STYLE_BOLD, all text you render from that point and until you set a different style will be bold. And as long as you pass any of the above values to the function, the font will only have the one last style you set.

Let's to a simple example
Init TTF and load a font
TTF_Init();
TTF_Font font = TFF_LoadFont("font.ttf", 16);
Any text rendered at this point will be normal with no font styles
TTF_SetFontStyle( font, TTF_STYLE_BOLD );
Any text rendered at this point will be bold
TTF_SetFontStyle( font, TTF_STYLE_ITALIC );
Any text rendered at this point will be in italics, but not bold
TTF_SetFontStyle( font, TTF_STYLE_NORMAL );
Any text rendered at this point will be normal with no font styles
TTF_SetFontStyle( font, STYLE_UNDERLINE );
Any text rendered at this point will be underlined

As you can see, this is pretty straight forwards. So let's make things a little bit trickier by setting multiple font styles at once. But first we need to take a look at how to combine several flags like TTF_STYLE_ITALICS and TTF_STYLE_BOLD

Binary numbers


In order to learn about how to combine these flags, we need to look at binary numbers first of all. If you don't already know about binary numbers, you should take a look at the above link. It's not crucial, but it is highly recommended to know a little about them. I might create a blog post about them at some point.

A computer stores numbers as individual bits ( 0's and 1's ). They correspond to on / off or true / false. Yup, that's right, we can use the individual bits of any number as a boolean variable.

Let's take a look at an 8 bit binary number ( 1 byte )

1010 0101

As you can see, it has 8 digits. So that's eight different flags. Each of these flags have two different possible values : 0 / 1 or false / true. So that's 8 bools for the price of a single byte!

Bitwise operations


So how do we use these 8 bools? As you know, we have the following boolean operations in C++ :
  • and ( &&
  • or ( ||

These work on an entire variable. And int, for instance will be false if its value is 0, otherwise its true. But there are similar operations that does this on all bits of a variable. These are called bitwise, simply because they operate on a simple byte. To do a bitwise operation, we need two variables of equal size ( same number of digits ), for instance two bytes.

Let's create two bytes, we'll use these for a few examples
Byte 1 : 0101 0011 ( 64 + 16 + 2 + 1 = 85 )
Byte 2 : 0110 0010 ( 32 + 64 + 2 = 98 )
Here is a simple example of bitwise OR between two bytes
0101 0011
OR
0110 0010
=
0111 0011
And here's the AND operation between the same two bytes :
0101 0011
AND
0110 0010
=
0100 0010

We also have the a bitwise version of the NOT opeartion ( ! in C++ ). This operation only takes a single element and flips all bits ( turns 1's into 0's and 0's into 1's. ). Let's test it on our two bytes

Byte 1 :
NOT 0101 0011
=    1010 1100

Byte 2 :
NOT 0110 0010
=    1001 1101
Finally, there's a fourth operation called XOR or exclusive or. XOR is true when the first and second bool are different ( ie one is true, the other false ). It can be compared to the != operator which is true when the left and right sides are different. Here are the four possibilities :
false XOR false = false
false XOR true  = true
true  XOR true  = false
true  XOR false = false

An here is the XOR between our two bytes:
0101 0011
XOR
0110 0010
=
0011 0001

As you can see, the positions where the bits are different is true, the others are false.

Setting and cheking individual bits


So now that we know how to do bitwise operations, we need a way of checking and setting the individual bits. This is done simply by using OR, AND and XOR. Before we take a look at how to do this, let's define a few values to check.

Recall that the different font styles are ints? This is because they are used to perform bitwise operations to set and unset different bits. Here they are again, this time with their values. For simplicity, I'll only list the last four bits ( the others are always 0 ). The values are in decimal with the binary representation in parenthesis

  • TTF_STYLE_NORMAL               = 0 ( 0000 )
  • TTF_STYLE_BOLD                  = 1 ( 0001 )
  • TTF_STYLE_ITALIC                   = 2 ( 0010 )
  • TTF_STYLE_UNDERLINE          = 4 ( 0100 )
  • TTF_STYLE_STRIKETRHOUGH = 8 ( 1000 )

As you can see, they all have only one ( or zero ) bit set. This means we can use AND, OR or XOR on just one bit.

Setting a bit


To set a bit ( without affect any other bit ) we use the ORoperation. So say that we have four 0 bits, 0000 and we want to set the bit for bold on it. In other words, we want the result 0001. What we do is : that we take our original 4 bits ( 0001 ) and set it to the original 4 bits ( 0001 ) OR'ed with the bitmask for bold ( 0001 ) :

0000
OR
0001 ( value of TTF_STYLE_BOLD )
=
0001

Simple as that! This woks for any of the other flags in the same way. They all will end up setting one bit.

Note that this will not change any other bits. If we try set the italics font style on the above variable we get

0001
OR
0010 ( value of TTF_STYLE_ITALIC )
=
0011 ( TTF_STYLE_BOLD and TTF_STYLE_ITALIC set )

Now that we know how to set the bits, we need a way of checking them

Unsetting a bit


Sometimes we want to remove a bit. Say for instance we want to remove the italics from the font above. How do we do that without affection the other values? This is a tiny bit more complex, because it requires two operations. What we are trying to do is the following. Say we have a bitmask ( 0000 1011 ) and we want to unset the bit for bold text, but leave the rest uncanged. So we need to be able to go from
0000 1011
to
0000 1010
Here we need to do an AND operation. This is because we have a bit that we want to set to 0. This would work fine if the bit already 0. But if it was 1, then any OR operation with that bit would be true since an OR operation only requires one of the elements to be 1 in order to return true.

So we need to do an AND operation. But how? If we just used the TTF_STYLE_BOLD value ( 0000 0001 ) as the second element, we would end up with :

0000 0101
AND
0000 0001
=
0000 0001
Which is clearly wrong. So what we need to do is to AND it with a value that would not affect any bits except the one we want to unset. An AND operation with 1 will do this as 0 AND 1 is 0 and 1 AND 1 is 0. And a AND operation with 0 is guaranteed to unset the bit since 0 AND 0 is 0, and 1 AND 0 is 0. So what we need is a bit mask with one 0 ( the value we want to unset ) and the rest 1 ( the values we don't want to change. ) So this leaves us with the value 1111 1110. And AND operation with this value will unset the last bit and leave the others as they were. And since 1111 1110 is the same as 0000 0001 ( TTF_STYLE_BOLD ) with all bits flipped, we can get this value by using the NOT operation :
NOT 0000 0001 ( TTF_STYLE_BOLD )
=    1111 1110

Now that we have our bitmask to AND with we get :
0000 0101
AND
1111 1110
0000 0100

Checking a bit


To check a bit, we need to use the bitwise AND operation. And since we are only checking and not setting, we don't have to store the value anywhere which means we don't have to worry about changing anything.

To check a bitmask, simply do an AND operation with the value you want to change for ( in this cae, any of the TTF_STYLE_.... values ). So, to check if a text is bold, we do an AND between our mask an TTF_STYLE_BOLD :

0011 ( our bit mask, TTF_STYLE_BOLD and TTF_STYLE_ITALIC set )
AND
0001
=
0001

As you can see, we only set the bit that's set in our variable ( TTF_STYLE_ITALIC set ) the others will be 0 no matter what our mask is. The value 0001 is not 0, and thus this evaluates to true and we now know that the font is bold. If our mask didn't have the bold bit set, our mask would be 0010. An AND between 0010 AND 0001 is true ( they have no bit set to one in common ) and the result is 0 aka false.

Putting it all to use


Now that we know about binary number and setting, getting and checking the bits, we can put it into use. Below is a simple class that serves as a wrapper for TTF_Font. It enables to set, get and check for flags using enum. I hope this will make it easier to understand and use TTF_Fonts.



For a full list of my tutorials / posts, click here. Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.

mandag 14. juli 2014

[ SDL2 - Part 10 ] Text rendering.

Rendering text


In the previous parts, we've look at how to render rectangles and images, both with and without transparency. Now it's time to look at how we can render text.


Rendering text is tricky. You'll want to be able to render any font, in any size and preferably every possible character. Luckily, with the SDL ttf library, this is easy.

SDL2_ttf


SDL2_ttf, just like SDL2_image, is an additional library for SDL2. It can use just about every font, and you can set the size too!

What's TTF?


TTF, or TrueType Fonts is a type of fonts developed by Apple and Microsoft in the late 90's. True Type Fonts offers a high degree of control on how the font looks. The internals of TTF fonts and how they work isn't important here. The important part is that they're easy to use, will look really nice ( even scaled up. ) And they're also widely used, so finding fonts shouldn't be a problem.

SDL2 TTF?


As with SDL2_image, SDL2_ttf is a addon for SDL2 that deals with rendering text and makes it very easy. It is based on libfreetype, a library for writing text using TTF fonts. However, it's not very practical to use. SDL2_TTF makes using it a lot easier. But if you do want to use it yourself, you can take a look at their tutorial.

Setting up SDL2_TTF


Setting up SDL2 requires a tiny bit more work than SDL2_image, but don't be scared, it's still very easy. First we need to install the ttf library.

Installation


Installing SDL2_ttf is done exactly like SDL2_image. Just replace SDL2_image with SDL2_ttf

Linux


For Linux you can use need to install -lSDL2_ttf or -libSDL2_ttf or -SDL2_ttf ( the actual name might be different in different distributions. )

The linker flag is -lSDL2_ttf

The process is more or less identical to that of setting up SDL2_image.

If you can't find SDL2_ttf in any repositories and it's not installed by default, you might have to compile it yourself. For more information, see my blog post about setting up SDL2.

Windows


Similar to setting up SDL2 base.

The difference is that you have to download the development files for SDL2_ttf

And similarly add SDL2_ttf.lib to library includes and add SDL2_ttf.lib to the library flags ( where you previously added SDL2_image.lb )

And with that, it should work.

Mac


See the first part of my tutorial. Just install SDL2_ttf instead of SDL2

Initialization


Unlike SDL2_image does need to be initialized. Why? Because libfreetype, the library that SDL2_ttf builds upon needs to be initlaized, so naturally SDL_ttf needs to be initalized too.

Initializing SDL2_ttf requires a single function :
int TTF_Init()
Just like SDL_Init(Uint32 flags) this function returns -1 on error.

And just like with SDL_Init(Uint32 flags), we should print an error if the function fails. SDL2_TTF has its own function for printing errors :

char *TTF_GetError()
This means our routine for initializing SDL2_ttf will be the same as SDL2, just with the two functions above ( see full code for details. )

Loading fonts


This is the central structure of SDL2_ttf. It holds the font itself, the size and some other style information ( I'll go into this in the next part ). So, in order for us to use an TTF_Font we need to load it. This is done using a load function :
TTF_Font *TTF_OpenFont
(
    const char *file,
    int ptsize
)
So, the arguments are
  • const char *file - a pointer to the .ttf file
  • int ptsize - the size of the font
The function returns a NULL pointer of it can't find the file, or there is another error ( like SDL2_ttf isn't initialized. So this too should be handled by priting the error using TTF_GetError(), just like when initializing ttf

Cleaning up fonts


Just like we with SDL_Texture* and SDL_Surface*, we need to clean our fonts when done. This is just as easy for TTF_Fonts as with SDL_Texture* and SDL_Surface*. We simply call a function that does it for us :
TTF_CloseFont
(
   TTF_Font* font
);

Rendering text


There are three functions you can use to render text, depending on what you want. Let's start with the first one :

TTF_RenderText_Solid


This function is used for quick and simple rendering of a text, using a specific font and a font color. The background of this is transparent. Here's the signature:
SDL_Surface *TTF_RenderText_Solid
(
    TTF_Font *font,
    const char *text,
    SDL_Color fg
)
The arguments are :
  •  TTF_Font *font - the font to use
  • const char *text - the text to render
  • SDL_Color fg -  the color to use for the text

The function returns the finished SDL_Surface*, or NULL if something went wrong ( like supplying a NULL pointer for font )

The result will look something like this :


TTF_RenderText_Blended


This function has the exact same signature as TTF_RenderText_Solid, so I'll just show it without explaining it parameter by parameter :
SDL_Surface *TTF_RenderText_Blended
(
    TTF_Font *font,
    const char *text,
    SDL_Color fg
)
So what's the difference between TTF_RenderText_Solid and TTF_RenderText_Blended? The difference is that TTF_RenderText_Solid is very quick, but TTF_RenderText_Blended produces a better result. In our game, we won't be updating our text surfaces all that often, and there's not a lot of them either, so TTF_RenderText_Blended is a good choice.

Here's what TTF_RenderText_Blended looks like :

And here's a comparison between TTF_RenderText_Solid and TTF_RenderText_Blended :

The difference is not huge, but in the actual game it will be more clear. And the difference might also vary from font to font.

The third version is a tiny bit different :

TTF_RenderText_Shaded


This function will render the text, but with a specified background color.
SDL_Surface *TTF_RenderText_Solid
(
    TTF_Font *font,
    const char *text,
    SDL_Color fg,
    SDL_Color bg
)
The arguments are :
  •  TTF_Font *font - the font to use
  • const char *text - the text to render
  • SDL_Color fg -  the color to use for the text
  • SDL_Color bg -  the color to use for the background

So it's almost the same as the other two, just with a third argument for the background color. The return value is also the same as the other two.

The result will look something like this :

An example


Below is a simple example that should run and compile out of the box. For compilation details, look below.



Running it!


Running it is just as simple as with SDL2_image. So that means compilation on Windows is already set up when you installed TTF

Linux / Mac


If you are compiling using the compiler, you have to add -lSDL2_ttf to the compile string like so :
clang++ main.cpp -std=c++11 -o Game -lSDL2 -lSDL2_image -lSDL2_ttf

If you want to run it, you simply do
./Game

Updated game code


I have done a bit of cleaning up in the game code. I've added a new Texture class for text, cleaned up include, removed ( and added ) comments, improve delta calculation++ Everything should be explained in comments, but, of course, if you have any questions of any kinds, just comment or contact me, I'll be happy to help.

You can find the code here.


For a full list of my tutorials / posts, click here.

Feel free to comment if you have anything to say, any suggestion or any questions. I always appreciate getting comments.