This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

| Fontmap Rendering Class Submitted by

During the developement of my engine there were sometimes situations, when I wanted a bit more textual output than by a console. Ideally some information appers naerby to the object it's related to. To abstract the process of rendering texture mapped fonts using OpenGL primitives I wrote the CFontmap class. To use it include just cfontmap.h and make a instance of CFontmap like CFontmap fontmap; Then you may create a font by calling Fontmap::create(char *fontname, int resolution), where resolution specifies the height of a char in pixels within the font texture map. The text will be printed from the Origin, normal OpenGL transformation will apply. However if you want to set an offset in the XY plane you can do that using CFontmap::set_pos(X, Y); Since the offsets are changed internally during rendering you'll have to do this everytime before you print text with it. Here is a small example programm using GLUT, that increases a counter everytime when the window is about to be redrawn and shows the counter content using CFontmap

    #include <stdio.h
    #include <GL/glut.h
    #include "cfontmap.h"

    void init();
    void reshape(int width, int height);
    void display();

    CFontmap fontmap;

    int main(int argc, char **argv)
    {
    	glutInit(&argc, argv);
    	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);

    	glutCreateWindow(argv[0]);
    	glutReshapeFunc(reshape);
    	glutDisplayFunc(display);
    	glutReshapeWindow(512, 512);
    	init();

    	glutMainLoop();

    	return 0;
    }

    void init()
    {
    	fontmap.create("Arial", 64);
    	fontmap.set_height(0.05);
    	fontmap.set_rendermode(CFontmap::alpha);
    }

    void reshape(int width, int height)
    {
    	glViewport(0, 0, width, height);
    	glMatrixMode(GL_PROJECTION);
    	glLoadIdentity();
    	gluOrtho2D(-1,1,-1,1);
    	glMatrixMode(GL_MODELVIEW);
    	glLoadIdentity();
    }

    void display()
    {

    	static int nCalled=0;
    	glClear(GL_COLOR_BUFFER_BIT);

    	glDisable(GL_TEXTURE_2D);
    	glBegin(GL_QUADS);
    	glColor3f(0.5,0,0);
    	glVertex2f(-1,-1);
    	glColor3f(0,0.5,0);
    	glVertex2f(1,-1);
    	glColor3f(0,0,0.5);
    	glVertex2f(1,1);
    	glColor3f(0.5,0.5,0.5);
    	glVertex2f(-1,1);
    	glEnd();

    	glColor3f(1,1,1);

    	nCalled++;
    	char msg[80];
    	sprintf(msg, "Hello\n%d", nCalled);

    	fontmap.set_pos(0,0);
    	fontmap.set_rendermode(CFontmap::additive);
    	fontmap.print(msg);

    	glFlush();
    	glutSwapBuffers();
    }

Currently browsing [CFontmap.zip] (4,470 bytes) - [CFontmap.cpp] - (11,072 bytes)

    #include <windows.h>
    #include <stdio.h>
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <math.h>

    #include "cfontmap.h"

    // some handy math macros; slow but usefull
    #define LOG(x,base)	(log(x)/log(base))

    /*
    * make_fontmap(..)
    * creates a new font texture map that can be used by CFontmap from the scratch
    * given a fontname the resolution and the other stuff.
    * Also parameterizes a given CFontmap in the right way.
    * NOTE: The iResolution parameter sets the height of a character box in the texture,
    *		but the case is, that the texture size is choosen on the iResolution parameter
    *		and the font height ist scaled to fit. Since texture sizes must be powers of
    *		two (2^x) all Resolutions in [2^x;2^(x+1)] have the same effect.
    *		e.g. [1,1], [2,4], [5,16], [17,32] ...
    * Mipmapping is done by createing a smaller font for each level to take care of
    * - a good look
    * - special font behaviours on smaller sizes if there
    */
    bool make_fontmap(char *csFontname, int iResolution, unsigned int uiChars, unsigned int uiColumns, unsigned int uiRows, CFontmap *pFontmap)
    #ifdef _WIN32
    // The Windoze stuff . . .
    // CreateContext, some error? ( retry? oh please! ), CreateFont, SelectObject some error? ( retry? oh please! ), DeleteFont, DeleteContext
    {
    	printf("make_fontmap(%s, %i, %i, %i, %i, %p)\n", csFontname, iResolution, uiChars, uiColumns, uiRows, pFontmap);

    	HDC hdc=
    		CreateCompatibleDC(wglGetCurrentDC());
    	if(!hdc)
    		return false;
    	HBITMAP hbitmap=
    		CreateCompatibleBitmap(hdc, 1, 1);
    	HBITMAP hbitmapOld=
    		SelectObject(hdc, hbitmap);
    	if((DWORD)hbitmapOld==GDI_ERROR)
    	return false;

    	GLuint uiTexName;// Just a unsigned int of waste when pFontmap!=NULL, but we're safe!
    	GLuint *pTexName=&uiTexName;
    	if(pFontmap!=NULL)
    		pTexName=&pFontmap->m_uiTexName;

    	glGenTextures(1, pTexName);
    	glBindTexture(GL_TEXTURE_2D, *pTexName);

    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    	// First estimate the next integer exponent for 2^x the calc it
    	// Bit shift is a trick to get power of 2 values quickly (integers only)
    	int iTexWidth=(1L<<(int)ceil(LOG(uiColumns*iResolution,2)))*2;
    	int iTexHeight=(1L<<(int)ceil(LOG(uiRows*iResolution,2)))*2;

    	glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
    	glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
    	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
    	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
    	glPixelTransferf(GL_RED_SCALE, 4.0f);

    	// Mipmapping Loop
    	int level=-1;
    	do
    	{
    		iTexWidth/=2;
    		iTexHeight/=2;
    		level++;

    		int const iColumnWidth=iTexWidth/uiColumns;
    		int const iRowHeight=iTexHeight/uiRows;

    		printf("Creating mipmap level %d, width %d, height %d ", level, iTexWidth, iTexHeight);

    		HFONT hfont=
    			CreateFont(	-iRowHeight, 0, 0, 0,
    						500, false, false, false,
    						DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
    						DEFAULT_QUALITY, DEFAULT_PITCH| (csFontname?FF_DONTCARE:FF_SWISS),
    						csFontname);
    		if(!hfont)
    			return false;

    		HFONT hfontOld=SelectObject(hdc, hfont);
    		if((DWORD)hfontOld==GDI_ERROR)
    			return false;

    		// Question: Why is there no OpenGL function to create an empty Texture you can fill with glTexSubImage* ?
    		// Ok, doing it from the back through the body in the eye.
    		// ( translated german phrase for doing something easy in a complex way )
    		BYTE *pData=new BYTE[iTexWidth*iTexHeight];
    		memset(pData, 0x00, iTexWidth*iTexHeight);
    		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);// Needs less memory ;-)
    		glTexImage2D(GL_TEXTURE_2D, level, GL_LUMINANCE, iTexWidth, iTexHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pData);
    		printf("glTexImage2D(...), ");
    		delete[] pData;

    		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);// Format of the GetGlyphOutline bitmap buffer

    		GLYPHMETRICS gm={0,};
    		MAT2 const matrix22_identity={{0,1},{0,0},{0,0},{0,1}};
    		for(unsigned int nRow=0; nRow<uiRows; nRow++)
    		{
    			for(unsigned int nColumn=0; nColumn<uiColumns; nColumn++)
    			{
    				DWORD dwBuffSize=//GGO_GRAY8_BITMAP
    					GetGlyphOutline(hdc, nColumn+nRow*uiColumns, GGO_GRAY8_BITMAP, &gm, 0, NULL, &matrix22_identity);

    				if((signed)dwBuffSize==-1)
    				{
    					fprintf(stderr, "dwBuffSize = GetGlyphOutline(...) didn't succed on character 0x%x='%c'.\n", nColumn+nRow*uiColumns, nColumn+nRow*uiColumns);
    					continue;
    				}

    				if(GetGlyphOutline(hdc, nColumn+nRow*uiColumns, GGO_METRICS, &gm, 0, NULL, &matrix22_identity)==GDI_ERROR)
    				{
    					fprintf(stderr, "GetGlyphOutline(..., &gm, ...) didn't succed on character 0x%x='%c'.\n", nColumn+nRow*uiColumns, nColumn+nRow*uiColumns);
    					continue;
    				}

    				BYTE *pBuff=new BYTE[dwBuffSize];
    				BYTE *pSwapBuff=new BYTE[dwBuffSize];
    				memset(pBuff, 0xff, dwBuffSize);
    				memset(pSwapBuff, 0xff, dwBuffSize);

    				if(GetGlyphOutline(hdc, nColumn+nRow*uiColumns, GGO_GRAY8_BITMAP, &gm, dwBuffSize, pBuff, &matrix22_identity)==GDI_ERROR)
    				{
    					delete[] pBuff;
    					continue;
    				}

    				if(gm.gmBlackBoxY==0)
    				{
    					printf("black box Y zero!!!\n");
    					return false;
    				}
    				unsigned int const uiRowSize=dwBuffSize/gm.gmBlackBoxY;
    				for(unsigned int nY=0; nY<gm.gmBlackBoxY; nY++)
    				{
    					memcpy(pSwapBuff+uiRowSize*nY, pBuff+dwBuffSize-uiRowSize*(nY+1), uiRowSize);
    				}

    				// Placing the glyphs' bitmap in the center of its fontmap cell,
    				// and set, if avilable, the CFontmap's according m_pCBoxes values
    				if(	gm.gmBlackBoxX>0&& gm.gmBlackBoxX<(unsigned)iTexWidth&&
    					gm.gmBlackBoxY>0&& gm.gmBlackBoxY<(unsigned)iTexHeight&&
    					(gm.gmBlackBoxX+iColumnWidth*nColumn+(iColumnWidth-gm.gmBlackBoxX)/2)<(unsigned)iTexWidth&&
    					(gm.gmBlackBoxY+iRowHeight*nRow+(iRowHeight-gm.gmBlackBoxY)/2)<(unsigned)iTexHeight)
    				{
    					glTexSubImage2D(GL_TEXTURE_2D, level,
    									iColumnWidth*nColumn+(iColumnWidth-gm.gmBlackBoxX)/2,
    									iRowHeight*nRow+(iRowHeight-gm.gmBlackBoxY)/2,
    									gm.gmBlackBoxX, gm.gmBlackBoxY,
    									GL_LUMINANCE, GL_UNSIGNED_BYTE,
    									pSwapBuff);
    				}

    				if(pFontmap&&level==0)
    				{
    					if(iColumnWidth==0&&iRowHeight==0)
    					{
    						printf("iColumnWidth and/or iRowHeight zero!!!\n");
    						return false;
    					}
    					CFontmap::SCharBox *pCBox=pFontmap->m_pCBoxes+(nColumn+nRow*uiColumns);
    					pCBox->fXOffset=(float)gm.gmptGlyphOrigin.x/(float)iColumnWidth;
    					pCBox->fYOffset=(float)gm.gmptGlyphOrigin.y/(float)iRowHeight;
    					pCBox->fXInc=(float)gm.gmCellIncX/(float)iColumnWidth;
    					pCBox->fXBox=(float)gm.gmBlackBoxX/(float)iColumnWidth;
    					pCBox->fYBox=(float)gm.gmBlackBoxY/(float)iRowHeight;
    				}

    				delete[] pBuff;
    				delete[] pSwapBuff;
    			}
    		}
    		printf("glTexSubImage2D(...)\n");

    		SelectObject(hdc, hfontOld);
    		DeleteObject(hfont);

    	}while(iTexWidth/2>=1||iTexHeight/2>=1);

    	glPixelTransferf(GL_RED_SCALE, 1.0f);

    	SelectObject(hdc, hbitmapOld);
    	DeleteObject(hbitmap);
    	DeleteDC(hdc);
    	return true;
    	// That was it, not very long or complex but tricky, whew!!!
    }
    #else
    // I's there really an other real GUI system as X11 on the earth?!?
    // Normally I'm using Linux and my so loved X11. But in the moment my XFree4.0
    // server doesn't work correctly. So I wasn't able to implement XFonts 8-(
    {
    	printf(stderr, "make_fontmap(...) sorry, not implemented for X11. xet\n");
    	return false;
    }
    #endif

    // CFontmap implementation
    CFontmap::CFontmap():
    c_uiChars(256), c_uiColumns(16), c_uiRows(16), m_pCBoxes(NULL)
    {
    	m_pCBoxes=new SCharBox[c_uiChars];
    	for(unsigned int i=0; i<c_uiChars; i++)
    	{
    		m_pCBoxes[i].fXOffset=0.0f;
    		m_pCBoxes[i].fYOffset=0.0f;
    		m_pCBoxes[i].fXInc=1.0f;
    		m_pCBoxes[i].fXInc=1.0f;
    	}

    	m_fStartX=0.0;
    	m_fCurrentX=0.0f;
    	m_fCurrentY=0.0f;

    	set_height(1.0f);
    }

    CFontmap::~CFontmap()
    {
    	delete[] m_pCBoxes;
    }

    CFontmap::CFontmap(char *csFontname, int iResolution, float fHeight, ERendermode rendermode):
    c_uiChars(256), c_uiColumns(16), c_uiRows(16), m_pCBoxes(NULL), m_rendermode(alpha)
    {
    	m_pCBoxes=new SCharBox[c_uiChars];

    	create(csFontname, iResolution);
    	set_height(fHeight);
    	set_rendermode(rendermode);

    	m_fStartX=0.0;
    	m_fCurrentX=0.0f;
    	m_fCurrentY=0.0f;
    }

    float CFontmap::set_height(float fHeight)
    {
    	float fOldHeight=m_fHeight;
    	m_fHeight=fHeight;
    	return fOldHeight;
    }

    void CFontmap::set_pos(float fX, float fY)
    {
    	m_fCurrentX=m_fStartX=fX;
    	m_fCurrentY=m_fStartY=fY;
    }

    bool CFontmap::create(char *csFontname, int iResolution)
    {
    	return make_fontmap(csFontname, iResolution, c_uiChars, c_uiColumns, c_uiRows, this);
    }

    // Prints Text beginning at the current position
    void CFontmap::print(char *csText, int iMaxLength)
    {
    	SCharBox sCBox={0,0,1,1};
    	SCharBox *pCBox=&sCBox;

    	glPushAttrib(GL_ALL_ATTRIB_BITS);

    	glDisable(GL_LIGHTING);
    	glEnable(GL_BLEND);
    	switch(m_rendermode)
    	{
    	case additive:
    		glBlendFunc(GL_ONE, GL_ONE);
    		break;
    	case alpha:
    		glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
    		break;
    	case invert:
    		glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
    		break;
    	case invert_additive:
    		glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE);
    		break;
    	case invert_alpha:
    		glBlendFunc(GL_ONE_MINUS_SRC_COLOR, GL_ONE);
    		break;
    	}

    	glBindTexture(GL_TEXTURE_2D, m_uiTexName);
    	glEnable(GL_TEXTURE_2D);

    	char *pC=csText;
    	glBegin(GL_QUADS);
    	while((*pC)!='\0'&&(iMaxLength==-1||(pC-csText)<iMaxLength))
    	{
    		int const iTexCol=(*pC)%c_uiColumns;
    		int const iTexRow=(*pC)/c_uiColumns;

    		pCBox=(m_pCBoxes+(*pC));
    		switch(*pC)
    		{
    		case '\n':
    			m_fCurrentY-=m_fHeight;
    		case '\r':
    			m_fCurrentX=m_fStartX;
    			break;
    		default:

    			float fXOffset=(1.0f-pCBox->fXBox)/2;
    			float fYOffset=(1.0f-pCBox->fYBox)/2;
    			float fYDescentOffset=(pCBox->fYOffset-pCBox->fYBox)*m_fHeight;
    			float fXPitchOffset=(pCBox->fXOffset)*m_fHeight;
    			float fYBox=pCBox->fYBox*m_fHeight;
    			float fXBox=pCBox->fXBox*m_fHeight;

    			glTexCoord2f(	(float)(iTexCol+fXOffset)/(float)c_uiColumns,
    							(float)(iTexRow+fYOffset)/(float)c_uiRows);
    			glVertex2f( m_fCurrentX+fXPitchOffset, m_fCurrentY+fYDescentOffset);

    			glTexCoord2f(	(float)(iTexCol+1-fXOffset)/(float)c_uiColumns,
    							(float)(iTexRow+fYOffset)/(float)c_uiRows);
    			glVertex2f( m_fCurrentX+fXBox+fXPitchOffset, m_fCurrentY+fYDescentOffset);

    			glTexCoord2f(	(float)(iTexCol+1-fXOffset)/(float)c_uiColumns,
    							(float)(iTexRow+1-fYOffset)/(float)c_uiRows);
    			glVertex2f( m_fCurrentX+fXBox+fXPitchOffset, m_fCurrentY+fYBox+fYDescentOffset);

    			glTexCoord2f(	(float)(iTexCol+fXOffset)/(float)c_uiColumns,
    							(float)(iTexRow+1-fYOffset)/(float)c_uiRows);
    			glVertex2f( m_fCurrentX+fXPitchOffset, m_fCurrentY+fYBox+fYDescentOffset);

    			m_fCurrentX+=pCBox->fXInc*m_fHeight;
    			break;
    		}
    		++pC;
    	}
    	glEnd();

    	glPopAttrib();
    }

Currently browsing [CFontmap.zip] (4,470 bytes) - [CFontmap.h] - (1,467 bytes)

    #ifndef CFONTMAP_H
    #define CFONTMAP_H

    /*
    * CFontmap
    * Manages a Fontmap Texture and provides basic text output
    */
    class CFontmap
    {
    public:
    	enum ERendermode{additive, alpha, invert, invert_additive, invert_alpha };
    	enum EOrientation{left, center, right, block};

    	struct SCharBox
    	{
    		float fXOffset;
    		float fYOffset;
    		float fXInc;
    		float fXBox;
    		float fYBox;
    	};

    	CFontmap();
    	~CFontmap();
    	CFontmap(char *csFontname, int iResolution, float fHeight, ERendermode rendermode);

    	float set_height(float fHeight);
    	bool create(char *csFontname, int iResolution=16);
    	void set_pos(float fX, float fY);
    	ERendermode set_rendermode(ERendermode const &rendermode)
    	{
    		ERendermode ret=m_rendermode;
    		m_rendermode=rendermode;
    		return ret;
    	}

    	void print(char *csText, int iMaxLength=-1);

    	friend bool make_fontmap(char *csFontname, int iResolution, unsigned int uiChars, unsigned int uiColumns, unsigned int uiRows, CFontmap *pFontmap);

    protected:
    	unsigned int m_uiTexName;
    	float m_fHeight;
    	float m_fMaxWidth;

    	float m_fStartX;
    	float m_fStartY;
    	float m_fCurrentX;
    	float m_fCurrentY;

    	unsigned int const c_uiChars;
    	unsigned int const c_uiColumns;
    	unsigned int const c_uiRows;

    	SCharBox *m_pCBoxes;

    	ERendermode m_rendermode;
    };

    bool make_fontmap(char *csFontname, int iResolution, unsigned int uiChars, unsigned int uiColumns, unsigned int uiRows, CFontmap *pFontmap);

    #endif/*CFONTMAP_H*/

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.