Main Page   Namespace List   Class Hierarchy   Compound List   File List   Namespace Members   Compound Members  

OGLFT: OpenGL-FreeType Library

0.8

(perhaps pronounced oh'ghoul-foot)

Table of Contents

Introduction

This C++ library supplies an interface between the fonts on your system and an OpenGL or Mesa application. It uses the excellent FreeType library to read font faces from their files and renders text strings as OpenGL primitives.

OGLFT is hosted on SourceForge. If you have questions or comments, try out the OLGFT mailing list.

Important Note

Versions 2.0.9 through 2.1.2 of the FreeType library contain a bug which prevents rotated text from being displayed properly. OGLFT makes no attempt to work around this.

Features

Here is an overview of the features of this library:

Examples

You draw text by creating a instance of a style object and then calling the draw() function with the string to render.

// Create a 24 point monochrome raster face
OGLFT::Monochrome* face = new OGLFT::Monochrome( "/path/to/font", 24 );
// ... later, after OpenGL is initialized
face->draw( x, y, "Here is some text" );

This table describes the various possible styles in which text can be rendered.

Style
Description
Example
Outline The outline of the characters is rendered as a set of polylines.
outline.png
Filled The characters are rendered as tessellated polygons.
filled.png
Solid The characters are rendered as extruded solids using the GLE Extrusion and Tubing library.
solid.png
Monochrome The characters are rasterized in monochrome format by the FreeType library and drawn using glBitmap.
monochrome.png
Grayscale The characters are rasterized in antialiased format by the FreeType library and drawn with a solid background using glDrawPixels.
grayscale.png
Translucent The characters are rasterized in antialiased format by the FreeType library and drawn using glDrawPixels. The difference from Grayscale is that the antialiased grayscale levels are used as an alpha-mask so that glyphs can be properly blended into the background.
translucent.png
Texture Each character is rasterized by the FreeType library. The raster image is then converted to alpha-masked texture map. You can pick either monochrome, grayscale or translucent textures.
texture_monochrome.png
texture_grayscale.png
texture_translucent.png

Each style has its advantages and disadvantages. This table tries to give you some guidance about which mode to pick.

Style
Advantages
Disadvantages
Typical Frame Rates (FPS)
Outline Relatively quick to draw. Easily manipulated by modeling transformations. I can't really think of any particular use for it. Maybe for limning Filled characters? Doesn't work well at small sizes due to lack of drop-out control.
Immed.
Draw
Cached
Glyph
Display
List
121135136
8399100
263568598
Filled Useful mostly for capping the solid. Easily manipulated by modeling transformations. Can make interesting use of texture maps. I also use this for printing. Doesn't work well at small sizes due to lack of drop-out control.
Immed.
Draw
Cached
Glyph
Display
List
44116117
256566
36547563
Solid Make very fancy demonstrations of animated text! Easily manipulated by modeling transformations. Lots of drawing involved, can be slow. Again, looks best at larger sizes.
Immed.
Draw
Cached
Glyph
Display
List
237272
123232
16150150
Monochrome Fast. Looks best at small sizes. Works over any background pattern. Limited to a single color. Limited transformations.
Immed.
Draw
Cached
Glyph
Display
List
101168169
66131133
112505530
Grayscale Standard way to draw text which looks good independent of size. Only slightly slower than monochrome (unaccelerated, anyway). Limited to single foreground and background color. Limited transformations.
Immed.
Draw
Cached
Glyph
Display
List
78123124
467879
519091
Translucent Looks good at all sizes. Works over any background pattern. Generally requires a reasonably accelerated alpha blending capability in OpenGL. Limited transformations.
Immed.
Draw
Cached
Glyph
Display
List
74114115
437071
395960
Texture Combines the speed of a raster font with most of the modeling transformation capabilities of the vector styles. Looks better at small sizes than vector styles. Generally requires a reasonably accelerated texture mapping capability in OpenGL. Maximum point size may be limited by texture map implementation.
Monochrome
Immed.
Draw
Cached
Glyph
Display
List
99127128
648788
188914913
Grayscale
Immed.
Draw
Cached
Glyph
Display
List
99126128
638788
187913913
Translucent
Immed.
Draw
Cached
Glyph
Display
List
99126128
638687
188913913

Frame Rate Notes: FPS is Frames Per Second. These results were generated with the speedtest program. The string "ABCDEFGHIJKLMNOPQRSTUVWXYZ" was drawn in each style in each of three modes:

  1. immediate drawing, which includes the FreeType rasterization and any OpenGL processing each time the glyph is rendered,
  2. with the glyph images cached as display lists as they are first encountered (the default drawing mode),
  3. with the whole string stored as a display list.
The first row of each table is for Mesa 3.5.1 running on a 700MHz PIII (with MMX/SSE extensions) at 16BPP. The second row is for Mesa 3.5.1 running on a 400MHz PII (with MMX extensions) at 16BPP. The third row is for DRI/Mesa 3.4.2 acceleration running on an Voodoo 3500 on a 400MHz PII. The first output from speedtest is the empty screen; it includes only the clear of the color and depth buffers. For the PIII with Mesa 3.5.1, the clear rate was 190 FPS. For the PII, the clear rate was 158 FPS for Mesa 3.5.1 and about 991 FPS for DRI/Mesa 3.4.2.

The most important result shown by the table (other than it helps to buy faster hardware) is that there is very little to be gained from compiling drawn strings into display lists. If it is convenient to do so, however, it won't reduce performance. Also, it appears that the textured drawing modes have the best all-around performance.

Demos

OGLFT currently comes with three demo programs. demo draws a string in each of the supported styles. You can use the a/A, s/S, d/D keys to rotate the glyphs clockwise/counter-clockwise around each axis. f/F rotates the entire string. r resets all the faces back to their default orientation. Note that glyphs drawn in the raster styles are not rotated around the X and Y axes.

demo2 shows how to use a FreeType FT_Face structure that is already open. The demo requires a Multiple Master Type 1 font in order to work. A couple of multiple master fonts are available in Adobe's Acrobat Reader distribution if you want to check this out.

demo3 is a 1 minute animation showing most of the features in OGLFT. This demo requires the Qt toolkit.

Tutorial

One of the goals of this library is ease of use, so hopefully the tutorial will not have to be very long!

Basic Rendering

Here are the pieces of a complete GLUT application which renders a well-known string in the center of the window. (See the tutorial1.c file.)

#include <GL/glut.h>
#include <OGLFT.h> // Note: this will depend on where you've installed OGLFT

// A Face variable of the desired style
OGLFT::Monochrome* monochrome;

void init ( const char* filename )
{
  // Create a new face given the font filename and a size

  monochrome = new OGLFT::Monochrome( filename, 36 );

  // Always check to make sure the face was properly constructed

  if ( monochrome == 0 || !monochrome->isValid() ) {
     cerr << "Could not construct face from " << filename << endl;
     return;
  }

  // Set the face color to red

  monochrome->setForegroundColor( 1., 0., 0. );

  // For the raster styles, it is essential that the pixel store
  // unpacking alignment be set to 1

  glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

  // Set the window's background color

  glClearColor( .75, .75, .75, 1. );
}

Note that no OpenGL calls are made when a face is constructed, so the initialization of monochrome could have been done in main() as well.

Drawing a string is very simple. Here is a typical GLUT display routine.

static void display ( void )
{
  // First clear the window ...
  glClear( GL_COLOR_BUFFER_BIT );
  // ... then draw the string
  monochrome->draw( 0., 250., "Hello, World!" );
}

This will display the string "Hello, World!" in red about half way up the left side of the window.

Here is the remaining infrastructure of the GLUT program.

static void reshape ( int width, int height )
{
  glViewport( 0, 0, width, height );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( 0, width, 0, height, -1, 1 );

  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();
}

int main ( int argc, char* argv[] )
{
  // Check to be sure the user specified something as a font file name

  if ( argc != 2 ) {
    cerr << "usage: " << argv[0] << " fontfile" << endl;
    return 1;
  }

  // Standard GLUT setup commands

  glutInit( &argc, argv );
  glutInitWindowSize( 500, 500 );
  glutInitDisplayMode( GLUT_RGB ); // Note: OGLFT really only works in RGB mode
  glutCreateWindow( argv[0] );

  init( argv[1] );

  glutReshapeFunc( reshape );
  glutDisplayFunc( display );
  glutMainLoop();

  return 0;
}

Simple Animation

Now let's make something with a little more life to it. We'll animate the text spinning around in the center of the window. The first modification is to add a periodic callback to the GLUT event loop. This is called the idle routine. Change the last four lines of main() to be (see tutorial2.c ):

... same code as before
  glutReshapeFunc( reshape );
  glutDisplayFunc( display );
  glutIdleFunc( idle );
  glutMainLoop();

  return 0;
}

The idle() function does two things: it updates the angle that the text is rotated through and it tells GLUT to redraw the screen after idle() exits.

static void idle ( void )
{
  // Retrieve the current value of the string's rotation and increment
  // it by 4 degrees

  monochrome->setStringRotation( monochrome->stringRotation() + 4 );

  glutPostRedisplay();
}

Next we need to add a line to init() to tell the Face to render the text with center justification; that way, the text will rotate about it's own center point.

void init ( const char* filename )
{
... same code as before
  // Set the face color to red

  monochrome->setForegroundColor( 1., 0., 0. );

  // Use centered justification

  monochrome->setHorizontalJustification( OGLFT::Face::CENTER );
... same code as before

Finally, it would look best of the text itself was centered in the window; so, we change the draw() command to draw at the center of the window.

static void display ( void )
{
  // First clear the window ...
  glClear( GL_COLOR_BUFFER_BIT );
  // ... then draw the string
  monochrome->draw( 250., 250., "Hello, World!" );
}

Fancy Animation

If you use the polygonal styles (Outline, Filled or Solid), you can supply the Face with your own OpenGL display list to execute before each glyph is rendered. To demonstrate this, we're going to set "Hello, World!" adrift at sea by varying its baseline with a traveling sine function.

First, we need to construct a Solid face and develop the sequence of transformations to apply. Note that the Solid style looks best when it is lighted. Also, it will not look correct unless the depth test is enabled. So, both of these features are enabled in init() as well. (See tutorial3.c )

#include <cmath>
#include <ctime>
#include <vector> // The STL vector
#include <algorithm> // The STL algorithms
#include <GL/glut.h>
#include <OGLFT.h> // Note: this will depend on where you've installed OGLFT

// A Face variable of the desired style
OGLFT::Solid* solid;

// The Bounding Box for the string
OGLFT::BBox bbox;

// A vector of OpenGL display list names
OGLFT::DisplayLists dlists;

// A vector of displacements defining the ocean
struct vertex {
  float y;
  float nx, ny;
};
vector< vertex > ocean_vertices;

void init ( const char* filename )
{
  // Create a new face given the font filename and a size

  solid = new OGLFT::Solid( filename, 36 );

  // Always check to make sure the face was properly constructed

  if ( solid == 0 || !solid->isValid() ) {
     cerr << "Could not construct face from " << filename << endl;
     return;
  }

  const float AMPLITUDE = 25.;
  GLuint dlist = glGenLists( 2*13 );

  // The per character display lists are executed before the glyph is
  // rendered; so, the first display list must contain an absolute
  // transformation, but the subsequent ones must contain relative
  // transformations. However, we need complete sets of both transformations,
  // starting with a place holder for the first (absolute) transformation

  dlists.push_back( 0 );

  // Next, generate a sequence of relative displacements

  for ( int i = 0; i < 13; i++ ) {
    float dy = AMPLITUDE * ( sin( (i+1) * 2 * M_PI / 13 ) - sin( i * 2 * M_PI / 13 ) );

    glNewList( dlist, GL_COMPILE );
    glTranslatef( 0., dy, 0. );
    glEndList();

    dlists.push_back( dlist );

    dlist++;
  }

  // Next, generate a sequence of absolute displacements

  for ( int i = 0; i < 13; i++ ) {
    float y = AMPLITUDE * sin( i * 2 * M_PI / 13 );

    glNewList( dlist, GL_COMPILE );
    glTranslatef( 0., y, 0. );
    glEndList();

    dlists.push_back( dlist );

    dlist++;
  }

  // Finally, copy the first absolute displacement into the first element
  // of the display list vector

  dlists[0] = dlists[13+1];

  // Use centered justification

  solid->setHorizontalJustification( OGLFT::Face::CENTER );

  // Make the glyphs rather thick

  solid->setDepth( 10. );

  // Apply the per character display lists

  solid->setCharacterDisplayLists( dlists );

  // Get the size of the string so we can draw an appropriate ocean

  bbox = solid->measure( "Hello, World!" );

  // Make it (sea) green

  solid->setForegroundColor( 143./255., 188./255., 143./255. );

  // Set the window's background color

  glClearColor( .5, .5, .5, 1. );

  // Build an "ocean" for the characters to float upon (a higher resolution
  // version of the absolute displacements which we'll cycle through)

  for ( int i = 0; i <= 52; i++ ) {
    float s = sin( i * 2 * M_PI / 52 );
    float c = cos( i * 2 * M_PI / 52 );
    vertex v;
    v.y = AMPLITUDE * s;
    v.nx = c;
    v.ny = s;
    ocean_vertices.push_back( v );
  }

  // Enable lighting and the depth test

  glEnable( GL_LIGHTING );
  glEnable( GL_LIGHT0 );
  glEnable( GL_COLOR_MATERIAL );
  glEnable( GL_DEPTH_TEST );
}

The redisplay routine first draws the string, which is relatively straight-forward. Then it draws the ocean; it uses the bounding box information to figure out the width of the text.

static int offset = 0;

static void display ( void )
{
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glPushMatrix();

  solid->draw( 250., 250., "Hello, World!" );

  glTranslatef( 254.- ( bbox.x_max_ + bbox.x_min_ ) / 2., 255., 0. );

  glBegin( GL_QUAD_STRIP );

  glColor3f( 0., 0., 1. );

  for ( int i=0; i<=52; i++ ) {
    float x = i * ( bbox.x_max_ - bbox.x_min_ ) / 52;

    glNormal3f( ocean_vertices[(i+offset)%53].nx,
                ocean_vertices[(i+offset)%53].ny,
                0 );

    glVertex3f( x, ocean_vertices[(i+offset)%53].y, -100. );
    glVertex3f( x, ocean_vertices[(i+offset)%53].y, 100. );
  }

  offset = offset < 48 ? offset+4 : 0;

  glEnd();

  glPopMatrix();
  glutSwapBuffers();
}

The reshape() function is modified slightly to rotate the string out of the XY plane. Otherwise, it would look two-dimensional!

static void reshape ( int width, int height )
{
  glViewport( 0, 0, width, height );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( 0, width, 0, height, -200, 200 );

  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();

  // Rotate the model slightly out of the XY plane so it looks 3D
  // (note we rotate the model instead of the view so that the lighting
  // is fixed relative to the view instead of the model)

  glTranslatef( width/2, height/2, 0. );
  glRotatef( 25., 1., 0., 0. );
  glRotatef( 25., 0., 1., 0. );
  glTranslatef( -width/2, -height/2, 0. );
}

The animation routine does three things: it rotates the display lists in the face so the characters appear to move up and down, it tells GLUT to redraw the scene when the routine returns and it pauses briefly in order to slow down the animation.

static void idle ( void )
{
  // Use the STL rotate algorithm to animate the transformation display lists.
  // First, rotate the lists containing the relative displacements

  OGLFT::DLI first = solid->characterDisplayLists().begin()+1;
  OGLFT::DLI next = first + 1;
  OGLFT::DLI last = first + 13;

  rotate( first, next, last );

  // Next, rotate the the lists containing the absolute displacements

  first = solid->characterDisplayLists().begin() + 13 + 1;
  next = first + 1;
  last = first + 13;

  rotate( first, next, last );

  // Finally, copy the current absolute displacement into the leading element

  solid->characterDisplayLists()[0] = solid->characterDisplayLists()[13+1];

  glutPostRedisplay();

  // Too fast even without acceleration
  struct timespec request = { 0, 80000000 };
  nanosleep( &request, 0 );
}

int main ( int argc, char* argv[] )
{
  // Check to be sure the user specified something as a font file name

  if ( argc != 2 ) {
    cerr << "usage: " << argv[0] << " fontfile" << endl;
    return 1;
  }

  // Standard GLUT setup commands

  glutInit( &argc, argv );
  glutInitWindowSize( 500, 500 );
  glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE );
  glutCreateWindow( argv[0] );

  init( argv[1] );

  glutReshapeFunc( reshape );
  glutDisplayFunc( display );
  glutIdleFunc( idle );

  glutMainLoop();

  return 0;
}

Assignments

Try out these modifications to the tutorial programs.

  1. Modify tutorial3.cpp so that it takes the string to display as a command line argument and appropriately computes the displacement transformations and the ocean size.
  2. Modify the per character display lists so that the glyphs are simultaneously rotated such that they are always normal to the surface of the ocean.

To Do...

Here is a list of the things OGLFT doesn't do currently, sorted roughly in order of importance. Feel free to recommend additional functionality.

Miscellaneous Programming Notes

If you feel so inclined as to look at the code, here are a few notes it might be helpful to know about in advance.

Style

Class member variables are suffixed with an _ to distinguish them from automatic variables and method arguments.

Glyph Decomposition

When FreeType decomposes (i.e., traverses) a vector face's glyph's outline, it does so as a series of callbacks to functions of the form moveTo, lineTo, conicTo, and cubicTo. Each of these functions is passed a single point in space and, if necessary, additional control point information. As the glyph's outline is traversed, the call to moveTo signals the start of an independent contour. Otherwise, there is no way to predict when a contour is going to terminate.

In generating the outline, filled and solid glyphs, the points on the contour are collected and passed to the various routines which generated OpenGL drawing commands (directly to glVertex3, to the GLU tessellation routines, and gleExtrusion, respectively). The GLU tessellation routines in particular are more efficient if the first and last points on the contours are not repeated. In the code, this is handled by buffering the point supplied to moveTo and only adding points for the lineTo and {arc}To calls.

Other Software

Here are a couple of packages which you might find faster, easier to use or more robust.


Copyright © 2002 lignum Computing, Inc., oglft@lignumcomputing.com


Hosted by SourceForge Logo


Generated at Fri Jul 12 10:47:15 2002 for OGLFT by doxygen1.2.8.1 written by Dimitri van Heesch, © 1997-2001