Text styles using SDL2_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
strikthrough
You can combine these in any way you like. We’ll start off 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 us 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
12345 void TTF_SetFontStyle(TTF_Font *font,int style)
Arguments :
-
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 do a simple example
123 // Init TTF and load a fontTTF_Init();TTF_Font font = TTF_LoadFont("font.ttf", 16);
Any text rendered at this point will be normal with no font styles
1 TTF_SetFontStyle( font, TTF_STYLE_BOLD );
Any text rendered at this point will be bold
1 TTF_SetFontStyle( font, TTF_STYLE_ITALIC );
Any text rendered at this point will be in italics, but not bold
1 TTF_SetFontStyle( font, TTF_STYLE_NORMAL );
Any text rendered at this point will be normal with no font styles
1 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. To do this, we must first look a bit at the binary number system
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. For now, I’ll just talk a tiny bit about the binary number system. But as I said, I highly recommend understanding it fully to the point where you can convert back and forth between
binary
and decimal
numbers
The binary number system
On a daily basis, we use the
decimal
number system. The binary
numbers system is just a different way of representing numbers. Any numbers can be converted from any other number system. So you can convert binary
numbers to decimal
numbers ( and the other way around ).
A computer stores numbers as individual bits ( 0
‘s and 1
‘s ). They correspond to on
/ off
or true
/ false
.
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 bool
s for the price of a single byte!
Bitwise operations
So how do we use these 8 booleans? As you know, we have the following boolean operations in C++:
and
(&&
)or
(||
)
These work on an entire variable. An 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
operations, 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 byte
s. The result of a bitwise operation is a third variable of the same size. So if we do a bitwise operation between two byte
s, we get a third byte
back as a result.
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 )
We’ll be referring to each digit as a position. So the digits in the first position is 0
in both or bytes. In the second position it’s 1
in both bytes and in the third it’s 0
in the first byte and 1
in the second byte.
Bitwise OR
A bitwise
OR
operation means we look at all positions as check if either of them is 1
if so, we set the digit in that position to 0
. If no digit in that position is 1
, we set it to 0
The operator for bitwise OR
in C++ is |
( just one |, not two )
Here is a simple example of bitwise OR
between two bytes
0101 0011
OR
0110 0010
=
0111 0011
Bitwise AND
In a bitwise
AND
operation, we look at each position and see if both of them are 1
. If so, we set the digit in that position to 1
, otherwise we set it to 0
. So in OR
we set it to 1
if any of the two is 1
, here we only set it to 1
if both are 1
.
The operator for bitwise AND
in C++ is &
( just one &, not two )
Here’s the a simple example :
0101 0011
AND
0110 0010
=
0100 0010
Bitwise XOR
XOR
or exclusive OR
is slightly less known than OR
and AND
. In an XOR
operation, we check if the two values are different. So this is equivalent to !=
in C++.
- (
true
!=false
) =true
- (
true
!=true
) =false
- (
false
!=true
) =true
- (
false
!=false
) =false
Simply put, an XOR
operation is true
if the two parts are different. So in a bitwise XOR
operation, we look at each position and see if the two digits are different. If so we set the digit at that position to 1
, otherwise we set it to 0
.
The operator for bitwise XOR
in C++ is !=
Here is an example :
0101 0011
XOR
0110 0010
=
0011 0001
Bitwise NOT
We also have the a bitwise version of the
NOT
opeartion this is done by using the ~
operator in C++. If we used !
we would get the result of NOT
on the entire variable, not the individual bits which is what we want. 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
The operator for bitwise NOT
in C++ is !
Byte 1 :
NOT 0101 0011
= 1010 1100
Byte 2 :
NOT 0110 0010
= 1001 1101
Setting and checking 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.
Remember 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
OR
operation. So say that we have four bits set to 0
, 0000
and we want to set the bit for bold on it ( 0001
). 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
( value of
OR
0001TTF_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
( value of
OR
0010TTF_STYLE_ITALIC
)
=
0011
(TTF_STYLE_BOLD
andTTF_STYLE_ITALIC
set )
Let’s make a simple function that adds a style to a mask.
12345678910111213141516171819 // Our current mask ( the mask that the font below has )int32_t currentStyle;// The font to set the style onTTF_Font* font;void AddFontStyle( int32_t styleTodAd ){// Update mask// We set mask to the result of the or operation ( currentStyle OR styleToAdd )// In effect : add the style in styleToAddd to currentStylecurrentStyle |= styleToAdd;// Apply fontTTF_SetFontStyle( font, currentStyle );}// Testing it ( by adding font style italic )AddFontStyle( TTF_STYLE_ITALIC );
Unsetting a bit
Sometimes we want to unset 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 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 unchanged. So we need to be able to go :
From
1011
to1010
To do this, we need to use an AND
operation. This is because we can’t turn of a bit using OR
, and XOR
would only flip it back and forth between 0
and 1
But we can’t use AND
with the flag we want to unset alone, because that would keep the flag at 1
and change every other to 0
!
0000 0101
AND
0000 0001
=
0000 0001
This is the opposite of what we want! Wait? Opposite you say? Well how about we use the NOT
operator here to get the opposite result? This works perfectly, because NOT 0000 0001
is 1111 1110
. And, as we saw earlier, doing AND 1
won’t change the element we’re AND
‘ing with. So we get :
0000 0101
AND
1111 1110
=
0000 0100
Success! Only the bit we were trying to unset has changed. So let’s make a function that does this for us :
1234567891011121314151617181920 // Our current mask ( the mask that the font below has )int32_t currentStyle;// The font to set the style onTTF_Font* font;void RemoveFontStyle( int32_t styleToRemove ){// Update mask ( remember : '~' is bitwise NOT )// We set mask to the result of the AND NOT operation ( currentStyle AND NOT styleToRemove )// In effect : add the style in styleToRemove to currentStylecurrentStyle &= ~styleToRemove;// Apply fontTTF_SetFontStyle( font, currentStyle );}// Testing it ( by removing font style bold )// Any other font style will remain as they wereRemoveFontStyle( TTF_STYLE_BOLD );
Checking a bit
To check a bit, we also 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 check for ( in this case, any of the TTF_STYLE_....
values ). So, to check if a text is bold, we do an AND
between our mask and TTF_STYLE_BOLD
:
0011
( our bit mask,TTF_STYLE_BOLD
andTTF_STYLE_ITALIC
set )
AND
0001
=
0001
As you can see, we only check 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 ( only the italic one ), our mask would be 0010
. An AND
between 0010
AND
0001
is false ( they have no bit set to 1
in common ) and the result is 0
aka false
.
So let’s create a function for that too!
1234567891011121314151617 // Our current mask ( the mask that the font below has )int32_t currentStyle;// The font to set the style onTTF_Font* font;void CheckFontStyle( int32_t styleToCheck ){// Update mask ( remember : ~ is bitwise NOT )// We return the result of the AND operation between currentStyle AND styleToCheck// In effect : check if the styleToCheck is setreturn currentStyle & styleToAd;}// Testing it ( by check for font style bold )// Will not change currentStyle in any waybool result = CheckFontStyle( TTF_STYLE_BOLD );
Conclusion
With a little knowledge about binary numbers and bitwise operations, we can easily set, add, remove and check various font styles in
SDL_TTF
.
Since it does involve a little low level code, I made a simple class that does the apparitions for us in a more intuitive way. I strongly suggest using this opposed to “raw” TTF_Fount*
Feel free to comment if you have anything to say or ask questions if anything is unclear. I always appreciate getting comments.
You can also email me : olevegard@headerphile.com