unit UFreeType;
{$IFDEF FPC}
{$mode delphi}{$H+}
{$ENDIF}
interface
uses
FreeType,
gl,
glu,
classes,
sysutils;
type
// This holds all of the information related to any
// freetype font that we want to create.
TFontData = class
h: single; ///< Holds the height of the font.
textures: array of GLuint; ///< Holds the texture id's
list_base: GLuint; ///< Holds the first display list id
// The init function will create a font of
// of the height h from the file fname.
constructor Create(const fname: string; h: cardinal);
// Free all the resources assosiated with the font.
destructor Destroy(); override;
end;
TFreeType = class
public
// The flagship function of the library - this thing will print
// out text at window coordinates x,y, using the font ft_font.
// The current modelview matrix will also be applied to the text.
class procedure print(ft_font: TFontData; x, y: single; const str: string);
end;
implementation
// This function gets the first power of 2 >= the
// int that we pass it.
function next_p2 ( a: integer ): integer; inline;
begin
Result := 1;
while (Result < a) do
Result := Result shl 1;
end;
type
PAGLuint = ^AGLuint;
AGLuint = array[0..High(Word)] of GLuint;
// Create a display list coresponding to the given character.
procedure make_dlist ( face: FT_Face; ch: byte; list_base: GLuint; tex_base: PAGLuint );
var
i, j: integer;
width, height: integer;
glyph: FT_Glyph;
bitmap_glyph: FT_BitmapGlyph;
bitmap: PFT_Bitmap;
expanded_data: array of GLubyte;
x, y: single;
begin
// The first thing we do is get FreeType to render our character
// into a bitmap. This actually requires a couple of FreeType commands:
// Load the Glyph for our character.
if (FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ) <> 0) then
raise Exception.create('FT_Load_Glyph failed');
// Move the face's glyph into a Glyph object.
if (FT_Get_Glyph( face^.glyph, glyph ) <> 0) then
raise Exception.create('FT_Get_Glyph failed');
// Convert the glyph to a bitmap.
FT_Glyph_To_Bitmap( glyph, ft_render_mode_normal, nil, 1 );
bitmap_glyph := FT_BitmapGlyph(glyph);
// This reference will make accessing the bitmap easier
bitmap := @bitmap_glyph^.bitmap;
// Use our helper function to get the widths of
// the bitmap data that we will need in order to create
// our texture.
width := next_p2( bitmap.width );
height := next_p2( bitmap.rows );
// Allocate memory for the texture data.
SetLength(expanded_data, 2 * width * height);
// Here we fill in the data for the expanded bitmap.
// Notice that we are using two channel bitmap (one for
// luminocity and one for alpha), but we assign
// both luminocity and alpha to the value that we
// find in the FreeType bitmap.
// We use the ?: operator so that value which we use
// will be 0 if we are in the padding zone, and whatever
// is the the Freetype bitmap otherwise.
for j := 0 to height-1 do
begin
for i := 0 to width-1 do
begin
if ((i >= bitmap.width) or (j >= bitmap.rows)) then
expanded_data[2*(i+j*width)] := 0
else
expanded_data[2*(i+j*width)] := byte(bitmap.buffer[i + bitmap.width*j]);
expanded_data[2*(i+j*width)+1] := expanded_data[2*(i+j*width)];
end;
end;
// Now we just setup some texture paramaters.
glBindTexture( GL_TEXTURE_2D, tex_base[integer(ch)]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// Here we actually create the texture itself, notice
// that we are using GL_LUMINANCE_ALPHA to indicate that
// we are using 2 channel data.
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, @expanded_data[0] );
//With the texture created, we don't need to expanded data anymore
SetLength(expanded_data, 0);
//So now we can create the display list
glNewList(list_base+ch, GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, tex_base[ch]);
glPushMatrix();
//first we need to move over a little so that
//the character has the right amount of space
//between it and the one before it.
glTranslatef(bitmap_glyph^.left, 0, 0);
//Now we move down a little in the case that the
//bitmap extends past the bottom of the line
//(this is only true for characters like 'g' or 'y'.
glTranslatef(0, bitmap_glyph^.top - bitmap.rows, 0);
//Now we need to account for the fact that many of
//our textures are filled with empty padding space.
//We figure what portion of the texture is used by
//the actual character and store that information in
//the x and y variables, then when we draw the
//quad, we will only reference the parts of the texture
//that we contain the character itself.
x := bitmap.width / width;
y := bitmap.rows / height;
//Here we draw the texturemaped quads.
//The bitmap that we got from FreeType was not
//oriented quite like we would like it to be,
//so we need to link the texture to the quad
//so that the result will be properly aligned.
glBegin(GL_QUADS);
glTexCoord2d(0, 0); glVertex2f(0, bitmap.rows);
glTexCoord2d(0, y); glVertex2f(0, 0);
glTexCoord2d(x, y); glVertex2f(bitmap.width, 0);
glTexCoord2d(x, 0); glVertex2f(bitmap.width, bitmap.rows);
glEnd();
glPopMatrix();
glTranslatef(face^.glyph^.advance.x shr 6, 0, 0);
//increment the raster position as if we were a bitmap font.
//(only needed if you want to calculate text length)
//glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL);
//Finnish the display list
glEndList();
end;
constructor TFontData.Create(const fname: string; h: cardinal);
var
library_: FT_Library;
//The object in which Freetype holds information on a given
//font is called a "face".
face: FT_Face;
i: byte;
begin
//Allocate some memory to store the texture ids.
SetLength(textures, 128);
Self.h := h;
//Create and initilize a freetype font library.
if (FT_Init_FreeType( library_ ) <> 0) then
raise Exception.create('FT_Init_FreeType failed');
//This is where we load in the font information from the file.
//Of all the places where the code might die, this is the most likely,
//as FT_New_Face will die if the font file does not exist or is somehow broken.
if (FT_New_Face( library_, PChar(fname), 0, face ) <> 0) then
raise Exception.create('FT_New_Face failed (there is probably a problem with your font file)');
//For some twisted reason, Freetype measures font size
//in terms of 1/64ths of pixels. Thus, to make a font
//h pixels high, we need to request a size of h*64.
//(h shl 6 is just a prettier way of writting h*64)
FT_Set_Char_Size( face, h shl 6, h shl 6, 96, 96);
//Here we ask opengl to allocate resources for
//all the textures and displays lists which we
//are about to create.
list_base := glGenLists(128);
glGenTextures( 128, @textures[0] );
//This is where we actually create each of the fonts display lists.
for i := 0 to 127 do
make_dlist(face, i, list_base, @textures[0]);
//We don't need the face information now that the display
//lists have been created, so we free the assosiated resources.
FT_Done_Face(face);
//Ditto for the library.
FT_Done_FreeType(library_);
end;
destructor TFontData.Destroy();
begin
glDeleteLists(list_base, 128);
glDeleteTextures(128, @textures[0]);
SetLength(textures, 0);
end;
/// A fairly straight forward function that pushes
/// a projection matrix that will make object world
/// coordinates identical to window coordinates.
procedure pushScreenCoordinateMatrix(); inline;
var
viewport: array [0..3] of GLint;
begin
glPushAttrib(GL_TRANSFORM_BIT);
glGetIntegerv(GL_VIEWPORT, @viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(viewport[0], viewport[2], viewport[1], viewport[3]);
glPopAttrib();
end;
/// Pops the projection matrix without changing the current
/// MatrixMode.
procedure pop_projection_matrix(); inline;
begin
glPushAttrib(GL_TRANSFORM_BIT);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
end;
///Much like Nehe's glPrint function, but modified to work
///with freetype fonts.
class procedure TFreeType.print(ft_font: TFontData; x, y: single; const str: string);
var
font: GLuint;
h: single;
i: cardinal;
lines: TStringList;
modelview_matrix: array[0..15] of single;
begin
// We want a coordinate system where things coresponding to window pixels.
pushScreenCoordinateMatrix();
font := ft_font.list_base;
h := ft_font.h / 0.63; //We make the height about 1.5* that of
lines := TStringList.Create();
ExtractStrings([#13], [], PChar(str), lines);
glPushAttrib(GL_LIST_BIT or GL_CURRENT_BIT or GL_ENABLE_BIT or 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);
glListBase(font);
glGetFloatv(GL_MODELVIEW_MATRIX, @modelview_matrix);
//This is where the text display actually happens.
//For each line of text we reset the modelview matrix
//so that the line's text will start in the correct position.
//Notice that we need to reset the matrix, rather than just translating
//down by h. This is because when each character is
//draw it modifies the current matrix so that the next character
//will be drawn immediatly after it.
for i := 0 to lines.Count-1 do
begin
glPushMatrix();
glLoadIdentity();
glTranslatef(x, y - h*i, 0);
glMultMatrixf(@modelview_matrix);
// The commented out raster position stuff can be useful if you need to
// know the length of the text that you are creating.
// If you decide to use it make sure to also uncomment the glBitmap command
// in make_dlist().
//glRasterPos2f(0,0);
glCallLists(Length(lines[i]), GL_UNSIGNED_BYTE, PChar(lines[i]));
//float rpos[4];
//glGetFloatv(GL_CURRENT_RASTER_POSITION ,rpos);
//float len=x-rpos[0];
glPopMatrix();
end;
glPopAttrib();
pop_projection_matrix();
lines.Free();
end;
end.