(perhaps pronounced oh'ghoul-foot)
Table of Contents
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.
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.
Here is an overview of the features of this library:
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.
Outline | The outline of the characters is rendered as a set of polylines. | | |||
Filled | The characters are rendered as tessellated polygons. | | |||
Solid | The characters are rendered as extruded solids using the GLE Extrusion and Tubing library. | | |||
Monochrome | The characters are rasterized in monochrome format by the FreeType library and drawn using glBitmap . | | |||
Grayscale | The characters are rasterized in antialiased format by the FreeType library and drawn with a solid background using glDrawPixels . | | |||
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. | | |||
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. | |
Each style has its advantages and disadvantages. This table tries to give you some guidance about which mode to pick.
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. |
| |||||||||||||||||||||||||||||||||||||||
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. |
| |||||||||||||||||||||||||||||||||||||||
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. |
| |||||||||||||||||||||||||||||||||||||||
Monochrome | Fast. Looks best at small sizes. Works over any background pattern. | Limited to a single color. Limited transformations. |
| |||||||||||||||||||||||||||||||||||||||
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. |
| |||||||||||||||||||||||||||||||||||||||
Translucent | Looks good at all sizes. Works over any background pattern. | Generally requires a reasonably accelerated alpha blending capability in OpenGL. Limited transformations. |
| |||||||||||||||||||||||||||||||||||||||
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. |
|
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:
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.
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.
One of the goals of this library is ease of use, so hopefully the tutorial will not have to be very long!
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; }
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!" ); }
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; }
Try out these modifications to the tutorial programs.
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.Here is a list of the things OGLFT doesn't do currently, sorted roughly in order of importance. Feel free to recommend additional functionality.
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.
Class member variables are suffixed with an _ to distinguish them from automatic variables and method arguments.
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.
Here are a couple of packages which you might find faster, easier to use or more robust.