unit UFreeType;

{$IFDEF FPC}
  {$mode delphi}{$H+}
{$ENDIF}

interface

uses
  FreeType,
  dglOpenGL,
  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.
