#include <qpainter.h>
#define TEXT_GAP 5
+// Two texture components for texmapped fonts: luminance and alpha
+#define NB_TEX_COMP 2
+// A font is split into rows each containing 32 characters
+#define TEX_ROW_LEN 32
+// Gap in pixels between two character rows in a font texture
+#define TEX_ROW_GAP 2
-GLboolean TFLoaded = GL_FALSE;
-
-GLdouble modelMatrix[16], projMatrix[16];
-GLint viewport[4];
-GLdouble winx, winy, winz;
-GLint status;
-
-GLViewer_TexFont* staticGlFont;
-
-//static float text_scale_factor = 32.;
+GLfloat modelMatrix[16];
//================================================================
// Class : GLViewer_TexFont
// Purpose :
//=======================================================================
GLViewer_TexFont::GLViewer_TexFont()
+: myMaxRowWidth( 0 ), myFontHeight( 0 )
{
myQFont = QFont::defaultFont();
- QFontMetrics aFM( myQFont );
- myWidths = new int[LastSymbolNumber - FirstSymbolNumber+1];
- myPositions = new int[LastSymbolNumber - FirstSymbolNumber+1];
mySeparator = 2;
- for( int k = FirstSymbolNumber, aWidth = 0; k <= LastSymbolNumber; k++ )
- {
- myWidths[ k - FirstSymbolNumber ] = aFM.width( k );
- myPositions[ k - FirstSymbolNumber ] = aWidth;
- aWidth += myWidths[ k - FirstSymbolNumber ] + 2;//mySeparator;
- }
-
- myTexFontWidth = 0;
- myTexFontHeight = 0;
myIsResizeable = false;
- //myMinMagFilter = GL_NEAREST;
- myMinMagFilter = GL_LINEAR_ATTENUATION ;
+ myMinMagFilter = GL_LINEAR;
+
+ init();
}
//======================================================================
// Purpose :
//=======================================================================
GLViewer_TexFont::GLViewer_TexFont( QFont* theFont, int theSeparator, bool theIsResizeable, GLuint theMinMagFilter )
+: myMaxRowWidth( 0 ), myFontHeight( 0 )
{
myQFont = *theFont;
- QFontMetrics aFM( myQFont );
- myWidths = new int[LastSymbolNumber - FirstSymbolNumber+1];
- myPositions = new int[LastSymbolNumber - FirstSymbolNumber+1];
mySeparator = theSeparator;
- for( int k = FirstSymbolNumber, aWidth = 0; k <= LastSymbolNumber; k++ )
- {
- myWidths[ k - FirstSymbolNumber ] = aFM.width( k );
- myPositions[ k - FirstSymbolNumber ] = aWidth;
- aWidth += myWidths[ k - FirstSymbolNumber ] + 2;//mySeparator;
- }
-
- myTexFontWidth = 0;
- myTexFontHeight = 0;
myIsResizeable = theIsResizeable;
myMinMagFilter = theMinMagFilter;
-
+
+ init();
}
//======================================================================
delete[] myWidths;
delete[] myPositions;
}
+
+//======================================================================
+// Function: init
+// Purpose :
+//=======================================================================
+void GLViewer_TexFont::init()
+{
+ myNbSymbols = LastSymbolNumber - FirstSymbolNumber + 1;
+
+ // It is unsafe to draw all characters in a single row -
+ // this leads to problems on some graphic cards with small GL_MAX_TEXTURE_SIZE.
+ // So splitting the characters into rows each containing 32 characters (or less).
+ // Assuming contant height of each row (64 pixels) to simplify texture mapping.
+ // However, this can be improved if necessary.
+ QFontMetrics aFM( myQFont );
+ myFontHeight = aFM.height();
+
+ myWidths = new int[myNbSymbols];
+ myPositions = new int[myNbSymbols];
+
+ for( int i = 0, k = FirstSymbolNumber, aWidth = 0; i < myNbSymbols; i++, k++ )
+ {
+ // is it time to start a new row?
+ if ( !( i % TEX_ROW_LEN ) )
+ {
+ if( aWidth > myMaxRowWidth )
+ myMaxRowWidth = aWidth;
+ aWidth = 0;
+ }
+ myWidths[i] = aFM.width( k );
+ myPositions[i] = aWidth;
+ aWidth += myWidths[i] + 2;
+ }
+
+ myTexFontWidth = 0;
+ myTexFontHeight = 0;
+}
//======================================================================
// Function: generateTexture
// Purpose :
//=======================================================================
-void GLViewer_TexFont::generateTexture()
+bool GLViewer_TexFont::generateTexture()
{
- QFontMetrics aFM( myQFont );
-
GLViewer_TexFindId aFindFont;
aFindFont.myFontFamily = myQFont.family();//myQFont.toString();
aFindFont.myIsBold = myQFont.bold();
}
else
{
- QString aStr;
- int pixelsWidth = 0;
- int pixelsHight = aFM.height();
- myTexFontWidth = 64;
+ // Adding some pixels to have a gap between rows
+ int aRowPixelHeight = myFontHeight + TEX_ROW_GAP;
+ int aDescent = QFontMetrics( myQFont ).descent();
+
+ int aNumRows = myNbSymbols / TEX_ROW_LEN;
+ if ( myNbSymbols % TEX_ROW_LEN )
+ aNumRows++;
+ int pixelsHight = aNumRows * aRowPixelHeight;
+
+ myTexFontWidth = 64;
myTexFontHeight = 64;
-
- pixelsWidth = myWidths[LastSymbolNumber - FirstSymbolNumber] +
- myPositions[LastSymbolNumber - FirstSymbolNumber];
- while( myTexFontWidth < pixelsWidth )
- myTexFontWidth = myTexFontWidth * 2;
+ while( myTexFontWidth < myMaxRowWidth )
+ myTexFontWidth <<= 1;
while( myTexFontHeight < pixelsHight )
- myTexFontHeight = myTexFontHeight * 2;
+ myTexFontHeight <<= 1;
+
+ // Checking whether the texture dimensions for the requested font
+ // do not exceed the maximum size supported by the OpenGL implementation
+ int maxSize;
+ glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxSize );
+ if ( myTexFontWidth > maxSize || myTexFontHeight > maxSize )
+ return false;
QPixmap aPixmap( myTexFontWidth, myTexFontHeight );
aPixmap.fill( QColor( 0, 0, 0) );
QPainter aPainter( &aPixmap );
aPainter.setFont( myQFont );
- for( int l = 0/*, gap = 0*/; l < LastSymbolNumber - FirstSymbolNumber; l++ )
+ int row;
+ for( int l = 0; l < myNbSymbols; l++ )
{
+ row = l / TEX_ROW_LEN;
QString aLetter;
aLetter += (char)(FirstSymbolNumber + l);
aPainter.setPen( QColor( 255,255,255) );
- aPainter.drawText ( myPositions[l], pixelsHight, aLetter );
+ aPainter.drawText( myPositions[l], ( row + 1 ) * aRowPixelHeight - aDescent, aLetter );
}
QImage aImage = aPixmap.convertToImage();
- char* pixels = new char[myTexFontWidth * myTexFontHeight * 2];
+
+ //int qqq = 0;
+ //if (qqq)
+ // aImage.save("w:\\work\\CATHARE\\texture.png", "PNG");
+
+ char* pixels = new char[myTexFontWidth * myTexFontHeight * NB_TEX_COMP];
for( int i = 0; i < myTexFontHeight; i++ )
{
if( aRed != 0 || aGreen != 0 || aBlue != 0 )
{
- pixels[i * myTexFontWidth * 2 + j * 2] = (GLubyte)( (aRed + aGreen + aBlue)/3 );
- pixels[i * myTexFontWidth * 2 + j * 2 + 1]= (GLubyte) 255;
+ pixels[i * myTexFontWidth * NB_TEX_COMP + j * NB_TEX_COMP] = (GLubyte)( (aRed + aGreen + aBlue)/3 );
+ pixels[i * myTexFontWidth * NB_TEX_COMP + j * NB_TEX_COMP + 1]= (GLubyte) 255;
}
else
{
- pixels[i * myTexFontWidth * 2 + j * 2] = (GLubyte) 0;
- pixels[i * myTexFontWidth * 2 + j * 2 + 1]= (GLubyte) 0;
+ pixels[i * myTexFontWidth * NB_TEX_COMP + j * NB_TEX_COMP] = (GLubyte) 0;
+ pixels[i * myTexFontWidth * NB_TEX_COMP + j * NB_TEX_COMP + 1]= (GLubyte) 0;
}
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &myTexFont);
glBindTexture(GL_TEXTURE_2D, myTexFont);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myMinMagFilter);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myMinMagFilter);
- glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_INTENSITY,
TexFontBase.insert( aFindFont, aTexture );
}
+ return true;
}
//======================================================================
//=======================================================================
void GLViewer_TexFont::drawString( QString theStr, GLdouble theX , GLdouble theY, GLfloat theScale )
{
- double aXScale = 1., aYScale = 1.;
- // store attributes
- glPushAttrib( GL_ENABLE_BIT | GL_TEXTURE_BIT );
+ // Adding some pixels to have a gap between rows
+ int aRowPixelHeight = myFontHeight + TEX_ROW_GAP;
+ float aXScale = 1.f, aYScale = 1.f;
if ( !myIsResizeable )
{
- glGetDoublev (GL_MODELVIEW_MATRIX, modelMatrix);
+ glGetFloatv (GL_MODELVIEW_MATRIX, modelMatrix);
aXScale = modelMatrix[0];
aYScale = modelMatrix[5];
- }
-
- glEnable(GL_TEXTURE_2D);
+ }
+ else if ( theScale > 0.f )
+ {
+ aXScale = aXScale / theScale;
+ aYScale = aYScale / theScale;
+ }
- glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myMinMagFilter);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myMinMagFilter);
+ // store attributes
+ glPushAttrib( GL_ENABLE_BIT | GL_TEXTURE_BIT );
+ glEnable(GL_TEXTURE_2D);
glPixelTransferi(GL_MAP_COLOR, 0);
glAlphaFunc(GL_GEQUAL, 0.05F);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, myTexFont);
- glBegin(GL_QUADS);
-
- if ( myIsResizeable && theScale > 0. )
- {
- aXScale = aXScale / theScale;
- aYScale = aYScale / theScale;
- }
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
- theY = theY - ( myTexFontHeight - QFontMetrics( myQFont ).height() ) / aYScale;
-
+ glBegin(GL_QUADS);
- double aLettBegin, aLettEnd, aDY = ( myTexFontHeight - 1 ) / aYScale, aDX;
+ float aLettBeginS, aLettEndS, aLettBeginT, aLettEndT;
+ float aDY = ( aRowPixelHeight - 1 ) / aYScale, aDX;
char aLetter;
- int aLettIndex;
+ int aLettIndex, row;
for( int i = 0; i < theStr.length(); i++ )
{
aLetter = theStr.data()[i];
aLettIndex = (int)aLetter - FirstSymbolNumber;
+ row = aLettIndex / TEX_ROW_LEN;
- aLettBegin = (double)myPositions[aLettIndex] / ( (double)myTexFontWidth - 1. );
- aLettEnd = aLettBegin + ( (double)myWidths[aLettIndex] - 1. ) / ( (double)myTexFontWidth - 1. );
+ aLettBeginS = (float)myPositions[aLettIndex] / ( (float)myTexFontWidth - 1.f );
+ aLettEndS = aLettBeginS + ( (float)myWidths[aLettIndex] - 1.f ) / ( (float)myTexFontWidth - 1.f );
+ aLettBeginT = ( myTexFontHeight - ( row + 1 ) * aRowPixelHeight ) / ( (float)myTexFontHeight - 1.f );
+ aLettEndT = aLettBeginT + ( (float)aRowPixelHeight - 1.f ) / ( (float)myTexFontHeight - 1.f );
- aDX = ( (double)myWidths[aLettIndex] - 1. ) / aXScale;
+ aDX = ( (float)myWidths[aLettIndex] - 1.f ) / aXScale;
- glTexCoord2d( aLettBegin, 0.0 ); glVertex3d( theX, theY, 1.0 );
- glTexCoord2d( aLettBegin, 1.0 ); glVertex3d( theX, theY + aDY, 1.0 );
- glTexCoord2d( aLettEnd, 1.0 ); glVertex3d( theX + aDX, theY + aDY, 1.0 );
- glTexCoord2d( aLettEnd, 0.0 ); glVertex3d( theX + aDX, theY, 1.0 );
+ glTexCoord2f( aLettBeginS, aLettBeginT ); glVertex3f( theX, theY, 1.f );
+ glTexCoord2f( aLettBeginS, aLettEndT ); glVertex3f( theX, theY + aDY, 1.f );
+ glTexCoord2f( aLettEndS, aLettEndT ); glVertex3f( theX + aDX, theY + aDY, 1.f );
+ glTexCoord2f( aLettEndS, aLettBeginT ); glVertex3f( theX + aDX, theY, 1.f );
theX += aDX + mySeparator / aXScale;
}
if( theFormat != DTF_BITMAP )
{
GLViewer_TexFont aTexFont( theFont, theSeparator, theFormat == DTF_TEXTURE_SCALABLE, GL_LINEAR );
- aTexFont.generateTexture();
+ // Font texture was not found or generated --> cannot draw text
+ if ( !aTexFont.generateTexture() )
+ return;
+
if ( theFormat == DTF_TEXTURE_SCALABLE )
aTexFont.drawString( text, xPos, yPos, textScale() );
else