h1

OpenGL FreeType Texture Fonts

When one starts out in graphics programming with OpenGL, one is immediately struck by how simple it is to render anything you want.  A dozen lines of code sets up a light source, and with a simple enable, everything you draw while enabled is lit properly.  Surfaces are as easy as glBegin and then defining the vertices of your surface’s geometry followed by glEnd.  You can even directly raster your own pixels to the framebuffer!  But what about text?  Gah!  No font engine is built in to OpenGL, and why should there be?  It’s pretty much an abstraction layer for graphics hardware with a pipeline on top taking your commands.

In this article, I assume you have basic understanding of OpenGL.  You can do simple things like render a cube, use display lists, and know how the framebuffer and pipeline work.  If you don’t you should visit the NeHeGL site for some very good tutorials.

First thing – the following is what my method can look like (this is an old screenshot but gives you a good idea.)

Nice? 😀  You don’t have to use that font.  Any .ttf or freetype compatible font format is game.

So, we should consider our options.  First off, you should know there are several OpenGL font engines out there, so if you don’t care what’s under the hood, forget this article.  The most obvious method available is to get each glyph as a bitmap, then draw the bitmaps.  A glyph is basically a character of the font, like ‘A’ or a dollar sign.  WGL and GLX both have methods for this, but it’s clunky and way too platform dependent.  The heart of this is to draw the pixels straight to the framebuffer.  This is fine and dandy if you want static text that does nothing, and pretty much with no effects besides blending, but if you want more freedom, we can use textured quads.

The first question is, how do we get the images of the glyphs?  There are three basic options.  The simplest one is to store the data for a simple bitmap font statically in arrays.  This requires you to plug away at making arrays (I remember doing this to make fonts as a seven year old, heh 🙂 ) or lift ’em out of SDL_gfx or GLUT, or some other thing that has bitmap fonts stored in static arrays.  Then use glBitmap to draw them (remember that in a bitmap every pixel is defined by just one bit.)  You can also use glDrawPixels if your defined data has more complex pixel information such as RGBA or even luminance-alpha formats and whatnot.  The most commonly used method is to have an image or images to load that contain the glyphs.  But the one I’m going to show you loads a TrueType font, and lets you pick any point size you want.

The library we will use to load the TrueType fonts is freetype, and is well known to be one of the best font renderers out there.  Basically we start up the library and use it to load a font, which only takes literally two lines of code.  Then we set the point size of the font, which for some whacked reason is measured by this library in 64ths of a pixel, so we simply multiply the point size given by the user by 64 to get the proper size.

To hold the data, we will need data structures like this

typedef struct rat_texture_font
{
    float pt,*wids,*hoss;
    int *qvws,*qvhs;
    float *qtws,*qths;
    unsigned int *textures;
} rat_texture_font;

typedef struct rat_glyph_font
{
    float pt;
    FT_Face face;
} rat_glyph_font;

Let’s look at these.  The glyph font storage (bottom) simply has a font face data structure from the freetype library, plus the real point size (pt).  The texture font struct needs more explaining.  As we can see, it has ‘pt’ as a variable, and that stores the point size of the font, which I probably should have already mentioned is the height in pixels.  At the bottom we have an a unsigned int pointer called ‘textures.’  Each pointer in this struct holds a list (usually 255 long) for each glyph in the font.   The ‘texture’ pointer holds the OpenGL texture handles for each glyph.

The ‘wids’ pointer holds the widths of the glyphs, the ‘hoffs’ pointer holds the height offset for each character, (important for low hanging glyphs like ‘g’ and ‘j’,) the ‘qvws’ and ‘qvhs’ variables hold the width and height to draw our quads at.  ‘qtws’ and ‘qths’ give the texture coordinates for the quads.  In order to draw text, we must use these metrics to draw each glyph as a textured quad, where the quad is drawn at the size of the glyph and the texture coordinates give us the correct portion of the texture.  You see, textures must be sized at powers of two, so when we store the images of the glyphs as textures, we have to create some unused space around the glyph that just wont be shown to comply with the power of two rule.  The texture coordinates are basically giving us the coordinate range of the segment of the image to display on the quad.  And every time we draw a character, we advance to the right by wids[glyph] pixels.

I hope these concepts help you in drawing text.  But nothing teaches like example, so here’s my freetype/OpenGL font code.

/*
	This file is part of Floculate.
	Copyright (C) 2008  Bill Whitacre

	Floculate is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Floculate is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <GL/gl.h>

#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftglyph.h>
#include <freetype/ftoutln.h>
#include <freetype/fttrigon.h>

static FT_Library ftlib;

typedef struct rat_texture_font
{
	float pt,*wids,*hoss;
	int *qvws,*qvhs;
	float *qtws,*qths;
	unsigned int *textures;
	float ascend;
} rat_texture_font;

typedef struct rat_glyph_font
{
	float pt;
	FT_Face face;
} rat_glyph_font;

int rat_start_font_system()
{
	return !(FT_Init_FreeType(&ftlib));
}

void rat_stop_font_system()
{
	FT_Done_FreeType(ftlib);
}

rat_glyph_font *rat_glyph_font_load(char *filename,int pt);
rat_texture_font *rat_texture_font_from_glyph_font(rat_glyph_font *font);

void rat_glyph_font_destroy(rat_glyph_font *font);
void rat_texture_font_destroy(rat_texture_font *font);

rat_glyph_font *rat_glyph_font_load(char *filename,int pt)
{
	rat_glyph_font *font=(rat_glyph_font *)malloc(sizeof(rat_glyph_font));
	
	printf("Loading font from file \"%s\" at ptsize %i...",filename,pt);

	// load the font from the file
	if (FT_New_Face(ftlib,filename,0,&(font->face)))
	{
		printf("failed load!\n");
		free((void *)font);
		return NULL;
	}

	// freetype measures fonts in 64ths of pixels, which
	// I will never understand.  6 left bit shift multiplies
	// the pt size by 64.
	FT_Set_Char_Size(font->face,pt<<6,pt<<6,96,96);
	font->pt=pt;
	
	printf("done.\n");
	return font;
}

void rat_glyph_font_destroy(rat_glyph_font *font)
{
	printf("Destroying glyph font...");
	FT_Done_Face(font->face);
	free((void *)font);
	printf("done.\n");
}

inline static unsigned int _pow2(unsigned int i)
{
	register unsigned int p2;
	for (p2=1; p2<i; p2<<=1);
	return p2;
}

static int make_glyph_texture(rat_glyph_font *gf,rat_texture_font *tf,unsigned char ch)
{
	register unsigned int i,j;
	FT_Face face=gf->face;
	unsigned int *textures=tf->textures;
	unsigned int width,height;
	float texx,texy;

	if (FT_Load_Glyph(face,FT_Get_Char_Index(face,ch),FT_LOAD_DEFAULT))
		return 0;

    FT_Glyph glyph;
    if (FT_Get_Glyph(face->glyph,&glyph))
		return 0;

	FT_Glyph_To_Bitmap(&glyph,ft_render_mode_normal,0,1);
    FT_BitmapGlyph bitmap_glyph=(FT_BitmapGlyph)glyph;

	FT_Bitmap bitmap=bitmap_glyph->bitmap;

	width=_pow2(bitmap.width);
	height=_pow2(bitmap.rows);

	GLubyte* expanded_data=(GLubyte *)malloc(sizeof(GLubyte)*2*width*height);

	for (j=0; j<height;j++)
	{
		for (i=0; i<width; i++)
		{
			expanded_data&#91;2*(i+j*width)&#93;=
			expanded_data&#91;2*(i+j*width)+1&#93;=
				(i>=bitmap.width||j>=bitmap.rows)?
				0:bitmap.buffer[i+bitmap.width*j];
		}
	}

    glBindTexture(GL_TEXTURE_2D,textures[ch]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D,0,GL_ALPHA16,width,height,
		0,GL_LUMINANCE_ALPHA,GL_UNSIGNED_BYTE,expanded_data);

    free((void *)expanded_data);
    
	tf->wids[ch]=(float)(face->glyph->advance.x>>6);
	tf->hoss[ch]=(float)((face->glyph->metrics.horiBearingY-face->glyph->metrics.height)>>6);

	tf->qvws[ch]=bitmap.width;
	tf->qvhs[ch]=bitmap.rows;
	
	tf->qtws[ch]=(float)bitmap.width/(float)width;
	tf->qths[ch]=(float)bitmap.rows/(float)height;
	
	return 1;
}

rat_texture_font *rat_texture_font_from_glyph_font(rat_glyph_font *font)
{
	register unsigned char i;
	rat_texture_font *tf=(rat_texture_font *)malloc(sizeof(rat_texture_font));

	tf->pt=font->pt;

	// prepare the OpenGL textures / display lists
	tf->wids=(float *)malloc(sizeof(float)*255);
	tf->hoss=(float *)malloc(sizeof(float)*255);
	tf->qvws=(int *)malloc(sizeof(int)*255);
	tf->qvhs=(int *)malloc(sizeof(int)*255);
	tf->qtws=(float *)malloc(sizeof(float)*255);
	tf->qths=(float *)malloc(sizeof(float)*255);
	tf->textures=(unsigned int *)malloc(sizeof(unsigned int)*255);
	glGenTextures(255,tf->textures);

	for (i=0;i<255;i++)
	{
		if (!make_glyph_texture(font,tf,i))
		{
			glDeleteTextures(255,tf->textures);
			free((void *)tf->textures);
			free((void *)tf->wids);
			free((void *)tf->hoss);
			free((void *)tf->qvws);
			free((void *)tf->qvhs);
			free((void *)tf->qtws);
			free((void *)tf->qths);
			free((void *)tf);
			return NULL;
		}
	}

	return tf;
}

void rat_texture_font_destroy(rat_texture_font *font)
{
	glDeleteTextures(255,font->textures);
	free((void *)font->wids);
	free((void *)font->textures);
	free((void *)font->qvws);
	free((void *)font->qvhs);
	free((void *)font->qtws);
	free((void *)font->qths);
	free((void *)font);
}

static float _textrgba[4]={1.0f,1.0f,1.0f,1.0f};

void rat_set_text_color(float *rgba)
{
	memcpy(_textrgba,rgba,4*sizeof(float));
}

void rat_get_text_color(float *rgba)
{
	memcpy(rgba,_textrgba,4*sizeof(float));
}

float rat_texture_font_height(rat_texture_font *font)
{
	return font->pt;
}

float rat_texture_font_text_length(rat_texture_font *font,char *text)
{
	register float len=0;
	char *ch=text;
	for (; *ch; ch++) len+=font->wids[*ch];
	return len;
}

float rat_texture_font_glyph_length(rat_texture_font *font,char ch)
{
	return font->wids[ch];
}

void rat_texture_font_render_text(rat_texture_font *font,float x,float y,char *text)
{
	char *ch;
	
	glPushAttrib(GL_LIST_BIT|GL_CURRENT_BIT|GL_ENABLE_BIT|GL_TRANSFORM_BIT);	
		glMatrixMode(GL_MODELVIEW);
		glDisable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glColor4fv(_textrgba);

		glPushMatrix();
			glScalef(1,-1,1);
			for (ch=text; *ch; ch++)
			{
				glPushMatrix();
					glTranslatef(x,-y-(font->pt-font->hoss[*ch]),0);
					glBindTexture(GL_TEXTURE_2D,font->textures[*ch]);
					glBegin(GL_QUADS);
						glTexCoord2f(0,0);
						glVertex2f(0,font->qvhs[*ch]);
						
						glTexCoord2f(0,font->qths[*ch]);
						glVertex2f(0,0);
						
						glTexCoord2f(font->qtws[*ch],font->qths[*ch]);
						glVertex2f(font->qvws[*ch],0);
						
						glTexCoord2f(font->qtws[*ch],0);
						glVertex2f(font->qvws[*ch],font->qvhs[*ch]);
					glEnd();
				glPopMatrix();
				glTranslatef(font->wids[*ch],0,0);
			}
		glPopMatrix();
	glPopAttrib();
}

// upper left corner is always zero
void rat_texture_font_render_text_notform(rat_texture_font *font,char *text)
{
	char *ch;
	
	glPushAttrib(GL_LIST_BIT|GL_CURRENT_BIT|GL_ENABLE_BIT|GL_TRANSFORM_BIT);	
		glMatrixMode(GL_MODELVIEW);
		glDisable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glColor4fv(_textrgba);

		glPushMatrix();
			for (ch=text; *ch; ch++)
			{
				glBindTexture(GL_TEXTURE_2D,font->textures[*ch]);
				glBegin(GL_QUADS);
					glTexCoord2f(0,0);
					glVertex2f(0,font->qvhs[*ch]);
					
					glTexCoord2f(0,font->qths[*ch]);
					glVertex2f(0,0);
					
					glTexCoord2f(font->qtws[*ch],font->qths[*ch]);
					glVertex2f(font->qvws[*ch],0);
					
					glTexCoord2f(font->qtws[*ch],0);
					glVertex2f(font->qvws[*ch],font->qvhs[*ch]);
				glEnd();
				glTranslatef(font->wids[*ch],0,0);
			}
		glPopMatrix();
	glPopAttrib();
}

13 comments

  1. Hay how did you get the shiny code boxes?


  2. I used (source language=”C”)(/source). Replace parentheses with square brackets.


  3. cool thanks


  4. I thank you so much. I installed Freetype and had it initialized and everything but was having a hell of a time getting it to actually display something. Thanks to your code I’m up and running. Thanks again. Lifesaver.


  5. you’re very welcome! 🙂


  6. Hey Bill,

    Awesome tutorial thanks a lot for this.

    However, when building this and calling all the correct functions. I get this really weird stretched out textured quad.

    Pictured here: http://img89.imageshack.us/img89/6895/ftfontgly.jpg

    The font Im using is “Arial.ttf” with a point size of 14. It does this to any other fonts I am using also.

    I am also only translating x: 0, y: 0, z: -10

    Its huge!

    Could you give me a reason why its looking like that? Did I forget something?

    Thanks for your time — Jay


    • Definitely a transform issue. This code assumes that you have already set up a pixel perfect ortho 2D projection. So just use glOrtho with your resolution and such first.


  7. Oh okay I understand.

    Can you not use these textured quads in 3D Projection then? That seems a little weird.


  8. Well, you can, but you’d have to scale down accordingly. The units are pts, so a pt 14 font is 14 units tall.


  9. Also, they’d be upside down, since they’re flipped for the ortho mode. That should be curable as well through a negative scaling: glScalef(1,-1,1);


  10. Basically a NeHe ripoff O_O


  11. thanks a lot for this..but for some reason when i tried this nothing comes..cud u pls give an example code to display a string


    • yes… it is mostly from NeHe, except it’s been made more general as a function library.

      guys, you have to have an orthogonal projection when you call the print function to make this work.



Leave a comment