A Font Library for OpenGL |
by Steve Baker |
/* Declarations */ fntRenderer texout ; /* Load some fonts */ fntTexFont TimesRoman ( "times_roman.txf" ) ; fntTexFont Courier ( "courier.txf" ) ; /* Select a font and pointsize to render with... */ texout . setFont ( & TimesRoman ) ; texout . setPointSize ( 24 ) ; /* Print "Hello" and "World" */ texout . begin () ; texout . start2f ( 50.0f, 80.0f ) ; texout . puts ( "Hello" ) ; texout . start2f ( 50.0f, 50.0f ) ; texout . puts ( "World" ) ; texout . end () ;
class fntFont -- An abstract base class from which all kinds of font representations could be derived. class fntTexFont -- A fntFont that uses texture mapping. class fntRenderer -- A class that draws text using a fntFont.
fntFont
class all describe fonts.
This is an abstract base class from which fntTexFont
is derived.
Other fntFont
sub-classes could also be derived in the future.
(This means that you cannot declare a fntFont - but all classes derived
from fntFont will obey this description.)
This class appears quite complex since constructing and/or
querying the contents of a texture font is a complicated
business - and consequently, there is a lot of API to
support your ability to do that. Fortunately, most FNT
applications will just load a font from disk and use
fntFont::begin()
/
fntFont::puts()
/
fntFont::end()
functionality.
class fntFont { public: fntFont () ; ~fntFont () ; void putch ( sgVec3 curpos, float pointsize, float slant, char c ) ; void puts ( sgVec3 curpos, float pointsize, float slant, char *s ) ; void begin () ; void end () ; void getBBox ( char *s, float pointsize, float slant, float *left, float *right, float *bot , float *top ) ; int load ( char *fname, GLenum mag = GL_NEAREST, GLenum min = GL_LINEAR_MIPMAP_LINEAR ) ; void setFixedPitch ( int fix ) ; int isFixedPitch () ; void setWidth ( float w ) ; void setGap ( float g ) ; float getWidth () ; float getGap () ; int hasGlyph ( char c ) ; } ;
fntFont::getBBox()
returns the top, bottom, left and
right extents (in OpenGL units) of the string 's' if it were drawn at the
specified pointsize and slant. This routine knows how to deal with
newline characters.
fntFont::putch()
draws the character 'c' at the
cursor position specified be 'curpos' (with the specified pointsize and slant)
and advances the cursor
to the right hand edge of the character it just rendered.
fntFont::puts()
draws the entire string 's' at the
cursor position specified be 'curpos'
(with the specified pointsize and slant) and advances the cursor
to the right hand edge of the last character it rendered.
Both putch and puts will automatically switch the case of letters from upper to lower or vice versa if the required character is not present in the font but the letter with the reverse case is present. Other missing characters will simply generate no output and won't update the cursor position. If a space character is not defined in the font, then a half-pointsize gap will be generated instead.
puts (but NOT putch) knows how to deal with newline characters, it drops the text down to the next line - leaving a one-third pointsize gap between lines.
fntFont::begin()/end()
since redundant mode changes are
costly in OpenGL, applications may optionally call fntFont::begin()
before rendering some text using a specified font and call fntFont::end()
at the end. Just like a glBegin()
/glEnd()
pair,
there are some fairly restrictive rules about what you can do between
a fntFont::begin()
and a fntFont::end()
:
glBegin()
/glEnd()
.
fntFont
objects or to fntRenderer
objects.
fntFont::begin()
between another
fntFont::begin()
/fntFont::end()
pair.
You may call fntFont::putch()
and/or
fntFont::puts()
without
entering a fntFont::begin()
/fntFont::end()
state - but if you do that, each call will result in
OpenGL state switching - which may well be redundant.
int load ( char *filename )
loads a font from disk,
returns TRUE on success, FALSE for failure.
int load ( char *filename, GLenum mag = GL_NEAREST,
GLenum min = GL_LINEAR_MIPMAP_LINEAR )
...you can optionally
specify the OpenGL texture magnification and minification filters
- this is sometimes necessary to get the clearest possible text
at certain point sizes. Experiment!
void setFixedPitch ( int fixed )
if 'fixed' is TRUE, forces
the font to be fixed-pitch (so each letter or 'Glyph' is a standard width),
if 'fixed' is false then variable character widths are possible.
void setWidth ( float w )
For a fixed width font, this
sets the width of each character. If the actual characters are wider
than this, they will overlap, if they are narrower then there will be
a gap.
void setGap ( float g )
Set the gap between letters, can be
negative or positive.
The following routines allow you to query the font's setup:
int isFixedPitch () float getWidth () float getGap ()
int hasGlyph()
returns TRUE if the font contains a
glyph (graphic image) for a given ASCII character, FALSE otherwise.
Fonts that have only uppercase (or only lowercase) letters will still
return TRUE for corresponding characters of the opposite case because
the putch and puts routines will automatically case-convert in that
case.
fntTexFont
is inherited from fntFont
.
All functions of a fntFont are implemented in fntTextFont using
a texture map.
class fntTexFont { public: fntTexFont () ; fntTexFont ( char *fname, GLenum mag = GL_NEAREST, GLenum min = GL_LINEAR_MIPMAP_LINEAR ) ; ~fntTexFont () ; void putch ( sgVec3 curpos, float pointsize, float slant, char c ) ; void puts ( sgVec3 curpos, float pointsize, float slant, char *s ) ; void begin () ; void end () ; void getBBox ( char *s, float pointsize, float slant, float *left, float *right, float *bot , float *top ) ; int load ( char *fname, GLenum mag = GL_NEAREST, GLenum min = GL_LINEAR_MIPMAP_LINEAR ) ; void setFixedPitch ( int fix ) ; int isFixedPitch () ; void setWidth ( float w ) ; void setGap ( float g ) ; float getWidth () ; float getGap () ; void setGlyph ( char c, float tex_left, float tex_right, float tex_bot , float tex_top , float vtx_left, float vtx_right, float vtx_bot , float vtx_top ) ; int getGlyph ( char c, float *tex_left = NULL, float *tex_right = NULL, float *tex_bot = NULL, float *tex_top = NULL, float *vtx_left = NULL, float *vtx_right = NULL, float *vtx_bot = NULL, float *vtx_top = NULL) ; } ;
int load ( char *filename )
loads a font from disk
using the extension of the filename to determine what format it is
stored in. Currently, the only supported format is Mark Kilgards 'texfont'
format - which has the '.txf' filename extension. Returns TRUE for
success, FALSE for failure.
Details of the TXF file format can be found in
Marks's document about Textured Fonts
Just as with all fntFont derived classes, you can optionally specify the OpenGL texture filter options for the loaded texture.
Each font is made up of zero or more 'Glyphs' (character shapes) packed into a single texture map. If you didn't use 'load' to load the font from disk, you'll have to define where each one lies on the map and how big the quadrilateral it is to be drawn on is:
void setGlyph ( char c, float tex_left, float tex_right, float tex_bot , float tex_top , float vtx_left, float vtx_right, float vtx_bot , float vtx_top ) ;Where 'c' is the character we are defining, 'tex_*' is the left, right, top and bottom of the image of that character in the texture map. Since texture coordinates are in the range 0..1, these will typically be quite small numbers. 'vtx_*' is the left, right, top and bottom of the character's rectangle in a coordinate system that has (0,0) at the baseline of the character cell, and (1,1) at the top-right corner of the tallest, widest character in the font. Hence, a lower-case 'y' would have a negative 'vtx_bot'.
You can also query all this information:
int getGlyph ( char c, float *tex_left, float *tex_right, float *tex_bot , float *tex_top , float *vtx_left, float *vtx_right, float *vtx_bot , float *vtx_top ) ;
fntFont::getGlyph()
returns TRUE if the character has
been defined in this font, FALSE otherwise.
fntRenderer
class for all their text needs.
class fntRenderer { public: fntRenderer () void start3fv ( sgVec3 pos ) ; void start2fv ( sgVec2 pos ) ; void start2f ( float x, float y ) ; void start3f ( float x, float y, float z ) ; void getCursor ( float *x, float *y, float *z ) fntFont *getFont () ; void setFont ( fntFont *f ) ; void begin () ; void end () ; void putch ( char c ) ; void puts ( char *s ) ; } ;
fntRenderer::setFont()
is used to tell the renderer which
font is current. fntRenderer::getFont()
lets you find out
which font it is. You may not call fntRenderer::setFont()
between a fntRenderer::begin()
and fntRenderer::end()
pair.
By default, uppercase characters of all fonts are one OpenGL unit high.
You can make characters larger or smaller by setting
fntRenderer::setPointSize()
. You can also slant the characters
to form italic or oublique fonts using fntRenderer::setSlant()
.
(The slant measures by how many OpenGL units the tops of uppercase
characters are sloped to the right). You can call either setPointSize or
setSlant between fntRenderer::begin()
and
fntRenderer::end()
.
The various kinds of fntRendered::start*()
calls are
akin to the OpenGL glVertex*
commands and they determine where
the next chunk of text will be drawn. Since this coordinate is
updated as text is drawn, you'll need to call
fntRenderer::getCursor()
to
find out where the next chunk of text will be drawn. You can
call fntRenderer::start*()
,
fntRenderer::putch()
and
fntRenderer::puts()
between
fntRenderer::begin()
/fntRenderer::end()
calls.
Check Marks's document about Textured Fonts for further information. Of particular interest is Mark's "gentexfont" program that can create a TXF format font from an X-windows font.
You can find out which X-fonts are stored on your machine using
the /usr/X11/bin/xlsfonts
program.
There are over a dozen sample TXF fonts stored
in examples/src/fnt/data
.
Steve J. Baker. <sjbaker1@airmail.net> |