I think these should be all the required includes. Of course you must link against freetype and opengl in order for everything to work.
#include <iostream> #include <vector> #include <string> #include <gl/gl.h> #include <gl/glu.h> #include <ft2build.h> #include <freetype/freetype.h> #include <freetype/ftglyph.h> #include <boost/shared_ptr.hpp>
Declare a couple of constants to hold the number of characters to process and the sizes of the textures.
const uint32_t m_number_of_chars = 512; const uint32_t m_texture_max_width = 2048;
With this function you calculate the next power of two of a given number. You need this to make the height of the texture be a power of two.
inline int next_p2 ( int a )
{
int rval=1;
while(rval<a) rval<<=1;
return rval;
}
We use these structs/classes to manage the Vertex Arrays / VBOs
struct vertex_node
{
vertex_node():x(0.0f),y(0.0f),z(0.0f){}
float x,y,z;
};
struct uv_node
{
uv_node():u(0.0f),v(0.0f){}
float u,v;
};
template <class T>
class buffer
{
public:
buffer()
{
data = NULL;
size = 0;
name = 0;
}
virtual ~buffer()
{
deallocate();
}
void allocate(const uint32_t& amount)
{
deallocate();
size = amount;
data = new T[size];
}
void deallocate()
{
delete [] data;
data = NULL;
}
T* data;
uint32_t name;
uint32_t size;
};
typedef buffer<vertex_node> vertex;
typedef boost::shared_ptr<vertex> vertex_ptr;
typedef buffer<uv_node> uv;
typedef boost::shared_ptr<uv> uv_ptr;
This is a strip of my texture class with the relevant code to this example
class texture
{
public:
uint32_t m_id;
bool create_two_channel (uint32_t width, uint32_t height, uint8_t* data)
{
glGenTextures(1, &m_id);
glBindTexture( GL_TEXTURE_2D, m_id);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data );
}
};
typedef boost::shared_ptr<texture> texture_ptr;
This class holds the information we need to draw the text and the texture
class font
{
public:
struct char_info_t
{
int x;
int y;
int width;
int height;
int left;
int top;
int advance;
int row;
uv_node uv[4];
vertex_node v[4];
uint8_t* bitmap;
};
struct font_info_t
{
int max_height;
std::vector<font::char_info_t> ch;
};
font_info_t info;
texture_ptr texture;
private:
};
typedef boost::shared_ptr<font> font_ptr;
This enormous bunch is the code that loads the TTF font. It opens the file from a memory buffer. This buffer contains the file exactly as it is on the disk. I leave to you the code to load the file to the buffer. If you can follow all this code, you can also write that for yourself. :)
font_ptr load_font(const std::string& font_file_path, uint32_t font_size)
{
/* HERE YOU MUST LOAD YOUR TTF FILE INTO THE BUFFER */
uint32_t file_size =
uint8_t* file_buffer =
/* HERE YOU MUST LOAD YOUR TTF FILE INTO THE BUFFER */
font_ptr pFont = font_ptr(new font());
// Initialize Freetype
FT_Library ft_library;
FT_Face ft_face;
if (FT_Init_FreeType( &ft_library ))
{
throw std::runtime_error("FT_Init_FreeType failed");
}
// Load the requested face
if (FT_New_Memory_Face( ft_library, (const FT_Byte*)file_buffer, file_size, 0, &ft_face ))
{
throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");
}
// Set the font size
FT_Set_Char_Size( ft_face, font_size<< 6, font_size << 6, 96, 96);
//Initialize the char_info array
pFont->info.ch.resize(m_number_of_chars);
pFont->info.max_height = 0;
// Variables to hold the size the texture must have to hold all the bitmaps
int max_width = 0;
int max_rows = 0;
// Create all the characters
for(unsigned int ch=0; ch<m_number_of_chars; ch++)
{
//Load the Glyph for our character.
if(FT_Load_Glyph( ft_face, FT_Get_Char_Index( ft_face, ch ), FT_LOAD_NO_HINTING ))
{
throw std::runtime_error("FT_Load_Glyph failed");
}
//Move the face's glyph into a Glyph object.
FT_Glyph glyph;
if(FT_Get_Glyph( ft_face->glyph, &glyph ))
{
throw std::runtime_error("FT_Get_Glyph failed");
}
//Convert the glyph to a bitmap.
FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
//This reference will make accessing the bitmap easier
FT_Bitmap& bitmap=bitmap_glyph->bitmap;
//Get a pointer to the char info for easy access
font::char_info_t* char_info = &pFont->info.ch[ch];
char_info->width = bitmap.width;
char_info->height = bitmap.rows;
//Allocate memory for the bitmap data.
char_info->bitmap = new uint8_t[2 * char_info->width * char_info->height];
uint8_t* char_bmp = char_info->bitmap;
// Fill the bitmap data
for(int j=0; j <char_info->height;j++)
{
for(int i=0; i < char_info->width; i++)
{
char_bmp[2*(i+j*char_info->width)]= char_bmp[2*(i+j*char_info->width)+1] =
(i>=bitmap.width || j>=bitmap.rows) ?
0 : bitmap.buffer[i + bitmap.width*j];
}
}
// Accumulate the width of the bitmaps. Increase the rows if we reached the width of the texture
max_width += char_info->width;
if (max_width>m_texture_max_width-1)
{
max_width = char_info->width;
max_rows++;
}
// Determine what's the maximum height a given character might have
if (char_info->height>pFont->info.max_height)
{
pFont->info.max_height = char_info->height;
}
// Store information about this character. We will use this to print it.
char_info->row = max_rows;
char_info->left = bitmap_glyph->left;
char_info->top = bitmap_glyph->top;
char_info->advance = ft_face->glyph->advance.x >> 6;
char_info->x = max_width - char_info->width;
}
// Multiply the maximum height a character might have by the amount of rows and calculate the proper (power of two) height the texture must have.
int texture_height = next_p2(pFont->info.max_height*(max_rows+1));
// Create the temporary buffer for the texture
uint8_t* texture_data = new uint8_t[m_texture_max_width*texture_height*2];
// Fill the texture, set the vertex and uv array values and delete the bitmap
for(unsigned int ch=0; ch<m_number_of_chars; ch++)
{
font::char_info_t* char_info = &pFont->info.ch[ch];
char_info->y = pFont->info.max_height*char_info->row;
fill_texture_data(ch, &pFont->info, m_texture_max_width, texture_data);
//(x,h)
char_info->uv[0].u = (float)char_info->x/m_texture_max_width;
char_info->uv[0].v = (float)(char_info->y+char_info->height)/texture_height;
char_info->v[0].x = 0.0f;
char_info->v[0].y = char_info->height;
//(x,y)
char_info->uv[1].u = (float)char_info->x/m_texture_max_width;
char_info->uv[1].v = (float)char_info->y/texture_height;
char_info->v[1].x = 0.0f;
char_info->v[1].y = 0.0f;
//(w,y)
char_info->uv[2].u = (float)(char_info->x+char_info->width)/m_texture_max_width;
char_info->uv[2].v = (float)(float)char_info->y/texture_height;
char_info->v[2].x = char_info->width;
char_info->v[2].y = 0.0f;
//(w,h)
char_info->uv[3].u = (float)(char_info->x+char_info->width)/m_texture_max_width;
char_info->uv[3].v = (float)(char_info->y+char_info->height)/texture_height;
char_info->v[3].x = char_info->width;
char_info->v[3].y = char_info->height;
delete [] pFont->info.ch[ch].bitmap;
}
// Create the texture and delete the temporary buffer
pFont->texture = texture_ptr(new texture);
pFont->texture->create_two_channel(m_texture_max_width, texture_height, texture_data);
delete [] texture_data;
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_library);
return pFont;
}
This function copies a specific char bitmap into the texture buffer at the proper location.
void fill_texture_data(uint32_t ch, font::font_info_t* font, uint32_t texture_width, uint8_t* texture_data)
{
font::char_info_t* char_info = &font->ch[ch];
uint8_t* char_bmp = char_info->bitmap;
int bmp_pos = 0;
int tex_pos = 0;
int char_x = char_info->x;
int char_y = char_info->y;
int char_width = char_info->width;
int char_height = char_info->height;
for(int bmp_y=0; bmp_y<char_height; bmp_y++)
{
for(int bmp_x=0; bmp_x<char_width; bmp_x++)
{
bmp_pos = 2 * ( bmp_x + bmp_y * char_width);
tex_pos = 2 * ( (char_x + bmp_x) + ( (char_y + bmp_y) * texture_width) );
texture_data[tex_pos] = char_bmp[bmp_pos];
texture_data[tex_pos+1] = char_bmp[bmp_pos+1];
}
}
}
With this class you can build a VertexArray for the string you want to draw.
class text_buffer
{
public:
void set(std::wstring& str, font_ptr& font)
{
int size = str.size();
va = vertex_ptr(new vertex());
va->allocate(size*4);
uv = vertex_ptr(new uv());
uv->allocate(size*4);
int advance = 0;
int i = 0;
std::wstring::iterator c = str.begin();
std::wstring::iterator const tmp_end = str.end();
font::char_info_t* ci;
// Fill vertex data
for(; c != tmp_end; ++c)
{
ci = &font->info.ch[*c];
va->data[i+0].x = ci->left+advance+ci->v[0].x;
va->data[i+0].y = ci->v[0].y + (font->info.max_height-ci->top);
va->data[i+1].x = ci->left+advance+ci->v[1].x;
va->data[i+1].y = ci->v[1].y + (font->info.max_height-ci->top);
va->data[i+2].x = ci->left+advance+ci->v[2].x;
va->data[i+2].y = ci->v[2].y + (font->info.max_height-ci->top);
va->data[i+3].x = ci->left+advance+ci->v[3].x;
va->data[i+3].y = ci->v[3].y + (font->info.max_height-ci->top);
advance += ci->advance;
i+=4;
}
// Fill UV data
i = 0;
c = str.begin();
for(; c != tmp_end; ++c)
{
ci = &font->info.ch[*c];
uv->data[i+0].u = ci->uv[0].u;
uv->data[i+0].v = ci->uv[0].v;
uv->data[i+1].u = ci->uv[1].u;
uv->data[i+1].v = ci->uv[1].v;
uv->data[i+2].u = ci->uv[2].u;
uv->data[i+2].v = ci->uv[2].v;
uv->data[i+3].u = ci->uv[3].u;
uv->data[i+3].v = ci->uv[3].v;
i+=4;
}
}
uv_ptr uv;
vertex_ptr va;
};
typedef boost::shared_ptr<text_buffer> text_buffer_ptr;
Finally, this is how you use the text_buffer class to construct a vertex array representing the string you want to draw.
int main()
{
/* INITIALIZE OPENGL */
font_ptr pFont = load_font("arial.ttf", 18);
text_buffer_ptr tbuff = text_buffer_ptr(new text_buffer());
std::wstring str = L"Hello World";
tbuff->set(str, pFont);
/* HERE YOU DRAW YOUR VERTEX ARRAY */
return 0;
}
Hope you find this useful.
Note: Most of the FreeType loading code is taken from Nehe’s Freetype article.
Note2: The code assumes the screen origin is located at the top-left corner. (See the method text_buffer::set, where it sets the position of the quads using the (font->info.max_height-ci->top) formula
#1 by feanor on October 19, 2012 - 5:49 pm
How do you draw the text?
If you draw it with glDrawArrays or similar how do you create the buffers?
Beside, nice code thanks for sharing that.
#2 by damianpaz on October 19, 2012 - 7:35 pm
You can draw the buffer using VAs or VBOs. You need to set an ortographic view with origin at the top-left corner.
Enable texturing, alpha blending, disable lighting, and bind the buffers *uv* and *va* of the class text_buffer, with:
// IF VBO { glBindBufferARB( GL_ARRAY_BUFFER_ARB, text_buffer->uv->name ); glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL ); glBindBufferARB( GL_ARRAY_BUFFER_ARB, text_buffer->va->name ); glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL ); } // ELSE VA { glTexCoordPointer( 2, GL_FLOAT, 0, text_buffer->uv->data ); glVertexPointer( 3, GL_FLOAT, 0, text_buffer->va->data ); } // And then glDrawArrays( GL_QUADS, 0, buffer->size );If you use VBO’s then you must upload them to the video card and save the id of the buffer. I use the member variable *name* of the class buffer for that.
#3 by feanor on October 20, 2012 - 5:54 am
Thanks! It works perfect!
#4 by Anton on January 25, 2013 - 10:55 pm
Shouldn’t line 11 in text_buffer be
m_uv = uv_ptr(new uv()); // uv_ptr not vertext_ptr and rename of m_uv prevents name collision with uv
?
#5 by damianpaz on January 27, 2013 - 9:04 am
Good call. I’ll update the post. Thanks.