Sign in to follow this  
C-Junkie

for reference: Using SDL_ttf with OpenGL

Recommended Posts

Here is some example code that I've been meaning to cook up for awhile, that shows a short example of how to use SDL_ttf to render text for use with OpenGL. NeHe's freetype code seems to involve rendering of each letter into a texture, which is slight overkill for what many people would like to do. (it IS more efficient, however, since it doesn't involve the creation and deletion of textures for dynamic data) Anyway, for reference, a simple method to render text in opengl using SDL_ttf.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <GL/gl.h>
#include <GL/glu.h>

#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>

/* adjust these accordingly */
char fontpath[] = "/path/to/a/font/file.ttf";
int screenwidth = 640;
int screenheight = 480;

int round(double x)
{
	return (int)(x + 0.5);
}

int nextpoweroftwo(int x)
{
	double logbase2 = log(x) / log(2);
	return round(pow(2,ceil(logbase2)));
}

char *init_sdl(SDL_Surface** screen)
{
	if(SDL_Init(SDL_INIT_VIDEO))
		return SDL_GetError();
	atexit(SDL_Quit);
	
	*screen = SDL_SetVideoMode(screenwidth, screenheight, 0, SDL_OPENGL);
	
	SDL_WM_SetCaption("C-Junkie's SDLGL text example", 0);
	
	if(TTF_Init())
		return TTF_GetError();
	atexit(TTF_Quit);
	
	return 0;
}

void SDL_GL_RenderText(char *text, 
                      TTF_Font *font,
                      SDL_Color color,
                      SDL_Rect *location)
{
	SDL_Surface *initial;
	SDL_Surface *intermediary;
	SDL_Rect rect;
	int w,h;
	int texture;
	
	/* Use SDL_TTF to render our text */
	initial = TTF_RenderText_Blended(font, text, color);
	
	/* Convert the rendered text to a known format */
	w = nextpoweroftwo(initial->w);
	h = nextpoweroftwo(initial->h);
	
	intermediary = SDL_CreateRGBSurface(0, w, h, 32, 
			0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);

	SDL_BlitSurface(initial, 0, intermediary, 0);
	
	/* Tell GL about our new texture */
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexImage2D(GL_TEXTURE_2D, 0, 4, w, h, 0, GL_BGRA, 
			GL_UNSIGNED_BYTE, intermediary->pixels );
	
	/* GL_NEAREST looks horrible, if scaled... */
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	

	/* prepare to render our texture */
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture);
	glColor3f(1.0f, 1.0f, 1.0f);
	
	/* Draw a quad at location */
	glBegin(GL_QUADS);
		/* Recall that the origin is in the lower-left corner
		   That is why the TexCoords specify different corners
		   than the Vertex coors seem to. */
		glTexCoord2f(0.0f, 1.0f); 
			glVertex2f(location->x    , location->y);
		glTexCoord2f(1.0f, 1.0f); 
			glVertex2f(location->x + w, location->y);
		glTexCoord2f(1.0f, 0.0f); 
			glVertex2f(location->x + w, location->y + h);
		glTexCoord2f(0.0f, 0.0f); 
			glVertex2f(location->x    , location->y + h);
	glEnd();
	
	/* Bad things happen if we delete the texture before it finishes */
	glFinish();
	
	/* return the deltas in the unused w,h part of the rect */
	location->w = initial->w;
	location->h = initial->h;
	
	/* Clean up */
	SDL_FreeSurface(initial);
	SDL_FreeSurface(intermediary);
	glDeleteTextures(1, &texture);
}

void glEnable2D()
{
	int vPort[4];
  
	glGetIntegerv(GL_VIEWPORT, vPort);
  
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
  
	glOrtho(0, vPort[2], 0, vPort[3], -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void glDisable2D()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();   
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();	
}

void init_gl()
{
	/* Irrelevant stuff for this demo */
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClearDepth(1.0f);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	
	/* Required if you want alpha-blended textures (for our fonts) */
	glBlendFunc(GL_ONE, GL_ONE);
	glEnable(GL_BLEND);
	
	/* Required setup stuff */
	glViewport(0, 0, 800, 600);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f, screenwidth / (float)screenheight, 0.1f, 50.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();	
}

int main()
{
	SDL_Surface *screen;
	TTF_Font* font;
	char *err;
	SDL_Color color;
	SDL_Rect position;
	SDL_Event event;
	int done;
	
	/* Do boring initialization */
	if((err = init_sdl(&screen))) {
		printf("Error while initializing: %s", err);
		return 1;
	}
	
	if(!(font = TTF_OpenFont(fontpath, 20))) {
		printf("Error loading font: %s", TTF_GetError());
		return 1;
	}
	
	init_gl();

	done = 0;
	while(!done) {
		/* render a fun litte quad */
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		glTranslatef(0.0f, 0.0f, -5.0f);
		glDisable(GL_TEXTURE_2D);
		
		glBegin(GL_QUADS);
			glNormal3f(0.0f, 0.0f, 1.0f);
			glColor3f(0.5f, 0.0f, 0.0f); glVertex3f(-1.5f, -1.0f,  1.0f);
			glColor3f(0.0f, 0.5f, 0.0f); glVertex3f( 1.0f, -2.0f,  1.0f);
			glColor3f(0.0f, 0.0f, 0.5f); glVertex3f( 1.5f,  1.0f,  1.0f);
			glColor3f(0.5f, 0.0f, 0.0f); glVertex3f(-2.0f,  1.0f, -1.0f);
		glEnd();
		
		/* Go in HUD-drawing mode */
		glEnable2D();
		glDisable(GL_DEPTH_TEST);
		
		/* Draw some text */
		color.r = 255;
		color.g = 255;
		color.b = 255;
		/** A quick note about position.
		 * Enable2D puts the origin in the lower-left corner of the
		 * screen, which is different from the normal coordinate
		 * space of most 2D api's. position, therefore,
		 * gives the X,Y coordinates of the lower-left corner of the
		 * rectangle **/
		position.x = screenwidth / 3;
		position.y = screenheight / 2;
		SDL_GL_RenderText("Hello, World!", font, color, &position);
		position.y -= position.h;
		SDL_GL_RenderText("A line right underneath", font, color, &position);
		position.y -= position.h;
		SDL_GL_RenderText("Yay text rendering.", font, color, &position);

		/* Come out of HUD mode */
		glEnable(GL_DEPTH_TEST);
		glDisable2D();
		
		/* Show the screen */
		SDL_GL_SwapBuffers( );
		
		/* Wait until you click the X */
	
		SDL_WaitEvent(&event);
		switch(event.type) {
		case SDL_QUIT:
			done = 1;
			break;
		}
	}
	
	/* Clean up (the atexit's take care of the rest) */
	TTF_CloseFont(font);
	
	return 0;
}



Here's a screenshot of what the output should look like Hosted on imageshack I don't remember who I borrowed the Enable/Disable2D functions from.

Share this post


Link to post
Share on other sites
I have a question. Why would you go thru the trouble when there are other font rendering techniques in OpenGL already? What are the advantages of using SDL_ttf over something like FTGL? Thanks :)

Share this post


Link to post
Share on other sites
Is there a way to render multi-lined text (using SDL_tff), or do you have to split it manually and reder it separated?
It seems that SDL_tff (or freetype) doesn't support the newline character. Does anyone knows if i'm worng?

Share this post


Link to post
Share on other sites
Quote:
Original post by bewatched
Is there a way to render multi-lined text (using SDL_tff), or do you have to split it manually and reder it separated?
It seems that SDL_tff (or freetype) doesn't support the newline character. Does anyone knows if i'm worng?
Not that I know of. My example just did three calls to the render function to render three lines.

Share this post


Link to post
Share on other sites
(necro...?)

Yes, it's nice and simple solution, hovewer, rendering each frame the same text on SDL_Surface by TTF_RenderText (which allocates memory -> which is slow), creating new texture, blitting one to another etc. was (in case of my code) horribly slow.

First I decided to add special function that would render text only once, and that rendered texture could be reused. But (by coincidence) I found on SDL mailing list one mail from Bob Pendleton, who has written SDL_TFF + OpenGL text rendering engine, by caching glyphs that were previously used. I've built similiar system, and it's working amazingly fast - renders multiple lines of text (yup, multiline rendering) without losing one frame, while for previous one the ratio was 5 frames / one function call :-/

I could post it here if anyone was interested... but be warned, it's rather big... very big piece of code :-)

Share this post


Link to post
Share on other sites
Ok... you asked for it :-)
Remember though that you probably won't be able to use it after simple copy & paste, there are too many things and references to things that are specific to my engine. If you want to see UML diagram of my font manager, it's here:




Header of file fontMgr.h, contains interface of FontMgr, which is manager responsible for loading, managing, releasing etc. fonts and for passing them to Renderer.



#ifndef FONTMGR_H
#define FONTMGR_H

#include "SDL_ttf.h"
#include <map>
#include <vector>
#include <string>

#include "../../Others/Misc/singleton.h"
#include "tFontObject.h"

// -------------------------------------------------

/*! \file fontMgr.h
\brief File tha contains interface of font system.
*/



//! Use this macro to get access to font system
#define getFontMgr SC::FontMgr::get()

namespace SC
{

// -------------------------------------------------

typedef std :: vector <usint> FontSizeList;

// -------------------------------------------------

//! Singleton class that internally manages loading and accessing fonts
class FontMgr : public Singleton<FontMgr>
{
public:

friend class Renderer;

void Init();
void Shutdown();

// -------------------------------------------------

//! Mod architecture prefix is added after loading, however you can access font
//! using name you've passed here (that is used only during loading);
void LoadFont(const char * _fontName, const FontSizeList & _sizes );

//! Releases only one font _fon
void ReleaseFont(const char * _fontName, const FontSizeList & _sizes);

//! Releases all actually loaded fonts
void ReleaseAllFonts();

// -------------------------------------------------

//! Returns true if specified font _fontName exists at all (if _size == -1)
//! If _size != -1 then it checks for font _fontName with specified _size
//bool FontExists(const char * _fontName, ssint _size = -1);

// -------------------------------------------------

private:

//! Used only by Renderer.
//! Returned value isn't modified but due to problems making one function const-correct rest had to adjust
//! itself (I forgot about mutable :-) )
TGlyphsContainer * GetGlyphsContainer(const char * _name, usint _size);

// -------------------------------------------------

// map of fonts
typedef std :: map <std :: string, TFontObject> FontsMap;
typedef FontsMap :: iterator FontsMapItor;
typedef std :: pair <std :: string, TFontObject> FontPair;

// -------------------------------------------------

FontsMap fonts;

};

}

#endif




Implementation of FontMgr.



#include <string>

#include "fontMgr.h"
#include "../../Others/Logger/logger.h"
#include "../../Others/Misc/misc.h"
#include "../../Layer1/FileIO/genericFileIO.h"
#include "../../Others/Misc/skinSystem.h"

#include "../../Layer1/MemoryMgr/mmgr.h"

using namespace std;

// ------------------------------------------------- start Init

void SC :: FontMgr :: Init()
{

if ( TTF_Init() == -1)
{
string tmp("Couldn't init SDL_TTF subsystem, reason: ");
tmp += TTF_GetError();
logError2("FontMgr", tmp.c_str());
throw false;
}

log2("FontMgr", "Succesfully created.")
}

// ------------------------------------------------- end Init

// ------------------------------------------------- start Shutdown

void SC :: FontMgr :: Shutdown()
{
if (!fonts.empty())
{
logError2("FontMgr", "There are unreleased fonts in the map:")
ReleaseAllFonts();
}

TTF_Quit();
log2("FontMgr", "Succesfully destroyed.")
}

// ------------------------------------------------- end Shutdown

// ------------------------------------------------- start LoadFont

void SC :: FontMgr :: LoadFont(const char * _fontName, const FontSizeList & _sizes )
{

string fullFontName(getSkinSystem.GetDirectoryPrefix());
fullFontName += _fontName;

if (!fileExists(fullFontName.c_str()))
{
string tmp("Couldn't find font file named: ");
tmp += fullFontName;
logError2("FontMgr", tmp.c_str());
return;
}

string msg("Loading font: ");
msg += _fontName;
log2("FontMgr", msg.c_str())

TFontObject * fontPtr = 0;

TFontObject font(fullFontName.c_str());

FontsMapItor fontsItor = fonts.find(_fontName);

if (fontsItor == fonts.end())
fontPtr = &font;
else
fontPtr = &fontsItor->second;


for (usint i = 0; i < _sizes.size(); ++i)
{
if (!fontPtr->LoadFont(_sizes[i]))
{
string errMsg("Couldn't load font: ");
errMsg += fullFontName;
errMsg += ", using specified font size: ";
const char * _fontSize = usintToString(_sizes[i]);
errMsg += _fontSize;
logError2("FontMgr", errMsg.c_str())
delete [] _fontSize;
continue;
}

}

if (fontsItor == fonts.end())
fonts.insert(FontPair(_fontName, font));

}

// ------------------------------------------------- end LoadFont

// ------------------------------------------------- start ReleaseFont

void SC :: FontMgr :: ReleaseFont(const char * _fontName, const FontSizeList & _sizes)
{
FontsMapItor fontsItor = fonts.find(_fontName);

if (fontsItor == fonts.end())
{
string tmp ("Couldn't find font to release it: ");
tmp += _fontName;
logError2("FontMgr", tmp.c_str())
}
else
{

for (usint i = 0; i < _sizes.size(); ++i)
{
fontsItor->second.ReleaseFont(_sizes[i]);
}

if (fontsItor->second.CanBeReleased())
{
fonts.erase(fontsItor);

string tmp ("Released font: ");
tmp += _fontName;
log2("FontMgr", tmp.c_str())
}
}
}

// ------------------------------------------------- end ReleaseFont

// ------------------------------------------------- start ReleaseAllFonts

void SC :: FontMgr :: ReleaseAllFonts()
{
FontsMapItor fontsItor;

beginG("Releasing fonts")

for (fontsItor = fonts.begin(); fontsItor != fonts.end(); ++fontsItor)
{
log2("Font", fontsItor->first.c_str());
fontsItor->second.ForceReleaseAll();
}

endG;

fonts.clear();
}

// ------------------------------------------------- end ReleaseAllFonts

// ------------------------------------------------- start GetGlyphsContainer


SC :: TGlyphsContainer * SC :: FontMgr :: GetGlyphsContainer(const char * _name, usint _size)
{
FontsMapItor fontsItor = fonts.find(_name);

if (fontsItor == fonts.end())
{
string tmp ("Couldn't find font: ");
tmp += _name;
logError2("FontMgr", tmp.c_str())
return 0;
}
else
{
return (fontsItor->second.GetGlyphsContainer(_size));
}

}

// ------------------------------------------------- end GetGlyphsContainer





Header file tFontObject.h, interface of single "font".



#ifndef TFONTOBJECT_H
#define TFONTOBJECT_H

#include <map>
#include <string>

#include "tGlyphsContainer.h"

// -------------------------------------------------

namespace SC
{

// -------------------------------------------------

class TFontObject
{
public:

TFontObject(const char * _fontName) : fontName(_fontName) { }
bool LoadFont(usint _size);

// -------------------------------------------------

void ReleaseFont(usint _size);
void ForceReleaseAll();
bool CanBeReleased() const { return glyphContainers.empty(); }

// -------------------------------------------------

TGlyphsContainer * GetGlyphsContainer( usint _size);


private:

typedef std :: map<usint, TGlyphsContainer> GlyphContainersMap;
typedef GlyphContainersMap :: iterator GlyphContainersMapItor;

GlyphContainersMap glyphContainers;
std :: string fontName;

};

// -------------------------------------------------

}

#endif





Implementation of above.



#include "tFontObject.h"

#include "../../Others/logger/logger.h"

#include "../../Layer1/MemoryMgr/mmgr.h"


// ------------------------------------------------- start LoadFont

bool SC :: TFontObject :: LoadFont(usint _size)
{
GlyphContainersMapItor itor;

itor = glyphContainers.find(_size);

if (itor == glyphContainers.end())
{
TGlyphsContainer newGlyph(fontName.c_str(), _size);

if (!newGlyph.Load())
return false;
else
{
glyphContainers.insert(std :: pair<usint, TGlyphsContainer>(_size, newGlyph) );
return true;
}
}

itor->second.Load();
return true;
}

// ------------------------------------------------- end LoadFont

// ------------------------------------------------- start ReleaseFont

void SC :: TFontObject :: ReleaseFont(usint _size)
{
GlyphContainersMapItor itor = glyphContainers.find(_size);

if (itor == glyphContainers.end())
{
debug("Not found font that had to be released.")
return;
}

if (itor->second.Release())
glyphContainers.erase(itor);

}

// ------------------------------------------------- end ReleaseFont

// ------------------------------------------------- start ForceReleaseAll

void SC :: TFontObject :: ForceReleaseAll()
{
for (GlyphContainersMapItor itor = glyphContainers.begin(); itor != glyphContainers.end(); ++itor)
itor->second.ForceRelease();

glyphContainers.clear();
}

// ------------------------------------------------- end ForceReleaseAll

// ------------------------------------------------- start GetGlyphsContainer

SC :: TGlyphsContainer * SC :: TFontObject :: GetGlyphsContainer( usint _size)
{
GlyphContainersMapItor itor = glyphContainers.find(_size);

if (itor == glyphContainers.end())
return 0;
else return (&(itor->second));
}

// ------------------------------------------------- end GetGlyphsContainer





Now where things are starting to be interesting... tGlyphsContainer.h




#ifndef TGLYPHSCONTAINER_H
#define TGLYPHSCONTAINER_H

#include <vector>
#include <string>

#include <gl/gl.h>

#include "../../Layer1/Renderer/renderer.h"
#include "SDL_ttf.h"

// -------------------------------------------------

namespace SC
{

// -------------------------------------------------

struct GlyphTexture
{
GlyphTexture() : textureID(0) { }

GLuint textureID;
usint width, height;
int charWidth, charHeight;
int advance;
};

// -------------------------------------------------

struct TGlyph
{

void ReleaseTextures() const;

uchar character;

GlyphTexture glyphsSolid[8];
GlyphTexture glyphsBlended[8];
};

// -------------------------------------------------

class TGlyphsContainer
{
public:

TGlyphsContainer(const char * _fontName, usint _size) : refCount(0), font(0), fontName(_fontName), size(_size) { }

// -------------------------------------------------

//! returns false if loading TTF font wasn't successfull
bool Load();

//! returns true if this glyph container can be deleted
bool Release();

//! Releases font even if it's reference counter is != 0.
void ForceRelease();

GlyphTexture * GetGlyphTexture(uchar _character, ulint _style, eTextRenderMode _renderMode);

ssint GetFontDescent() const { return descent; }
usint GetFontLineSkip() const { return lineSkip; }
usint GetFontHeight() const { return TTF_FontHeight(font); } // cache it?


private:

//!
void PrerenderGlyph( uchar _character, ulint _style, eTextRenderMode _renderMode, TGlyph & _glyph);

void CacheGlyph( SDL_Surface * tmp, GlyphTexture & _glyph) const;


std :: vector <TGlyph> glyphs;
std :: string fontName;

uchar refCount;
TTF_Font * font;
usint size;
ssint descent;
usint lineSkip; // vertical distance between multi line text output

};

// -------------------------------------------------


}

#endif




Probably most important file, contains implementation of clasess from tGlyphsContainer.h; they're responsible for aformentioned "glyph caching".



#include "tGlyphsContainer.h"
#include "../../Others/logger/logger.h"

#include "../../Layer1/MemoryMgr/mmgr.h"

// ------------------------------------------------- start ReleaseTextures

void SC :: TGlyph :: ReleaseTextures() const
{
GLuint textures[16];

for (usint i = 0; i < 8; ++i)
textures[i] = glyphsBlended[i].textureID;

for (usint i = 8; i < 16; ++i)
textures[i] = glyphsSolid[i].textureID;

glDeleteTextures(16, textures);

}

// ------------------------------------------------- end ReleaseTextures

// -------------------------------------------------
// TGlyphsContainer
// -------------------------------------------------

// ------------------------------------------------- start Load

bool SC :: TGlyphsContainer :: Load()
{
if (font)
{
++refCount;
return true;
}
else
{
font = TTF_OpenFont(fontName.c_str(), size);

if (!font)
{
return false;
}

descent = TTF_FontDescent(font);
lineSkip = TTF_FontLineSkip(font);
refCount = 1;
return true;
}
}

// ------------------------------------------------- end Load

// ------------------------------------------------- start Release

bool SC :: TGlyphsContainer :: Release()
{
--refCount;

if (refCount == 0)
{
SC_ASSERT(font)
TTF_CloseFont(font);
return true;
}
else return false;
}

// ------------------------------------------------- end Release

// -------------------------------------------------

void SC :: TGlyphsContainer :: ForceRelease()
{
TTF_CloseFont(font);

std :: vector <TGlyph> :: iterator itor;

for ( itor = glyphs.begin(); itor != glyphs.end(); ++itor)
(*itor).ReleaseTextures();

}

// -------------------------------------------------

// ------------------------------------------------- start GetGlyphTexture

SC :: GlyphTexture * SC :: TGlyphsContainer :: GetGlyphTexture(uchar _character, ulint _style, eTextRenderMode _renderMode)
{
std :: vector <TGlyph> :: iterator itor;

for ( itor = glyphs.begin(); itor != glyphs.end(); ++itor)
{

// -------------------------------------------------

if (itor->character == _character)
{
if (_renderMode == trm_Blend)
{

if ((*itor).glyphsBlended[_style].textureID)
return (&(*itor).glyphsBlended[_style]);

else
{
PrerenderGlyph(_character, _style, _renderMode, (*itor));
return (&(*itor).glyphsBlended[_style]);
}

}
else if (_renderMode == trm_Solid)
{

if ((*itor).glyphsSolid[_style].textureID)
return (&(*itor).glyphsSolid[_style]);

else
{
PrerenderGlyph(_character, _style, _renderMode, (*itor));
return (&(*itor).glyphsSolid[_style]);
}

}

SC_ASSERT(!"What, you added new enum value and forgot to update TGlyphsContainer?!")

}

// -------------------------------------------------

}

// we didn't found _character, so it's new glyph, first insert new to vector, then render and finally return

TGlyph newGlyph;
PrerenderGlyph(_character, _style, _renderMode, newGlyph);
newGlyph.character = _character;
glyphs.push_back(newGlyph);

if (_renderMode == trm_Blend)
{
return (&newGlyph.glyphsBlended[_style]);
}
else
if (_renderMode == trm_Solid)
{
return (&newGlyph.glyphsSolid[_style]); // how this can work?! we're returning adress of local variable... lol
}

SC_ASSERT(!"What am I doing here?")
}

// ------------------------------------------------- end GetGlyphTexture

// ------------------------------------------------- start PrerenderGlyph

void SC :: TGlyphsContainer :: PrerenderGlyph( uchar _character, ulint _style, eTextRenderMode _renderMode, TGlyph & _glyph)
{
TTF_SetFontStyle(font, _style);

// it doesn't matter, real color will be later set by Renderer
SDL_Color color = {255, 255, 255, 0};

char letter[2] = { _character, 0};
SDL_Surface * tmp;

if (_renderMode == trm_Solid)
{
tmp = TTF_RenderText_Solid( font, letter, color);
SC_ASSERT(tmp)
CacheGlyph(tmp, _glyph.glyphsSolid[_style]);
TTF_GlyphMetrics(font, (Uint16)_character, 0, 0, 0, 0, &_glyph.glyphsSolid[_style].advance);
}
else
{
tmp = TTF_RenderText_Blended( font, letter, color);
SC_ASSERT(tmp)
CacheGlyph(tmp, _glyph.glyphsBlended[_style]);
TTF_GlyphMetrics(font, (Uint16)_character, 0, 0, 0, 0, &(_glyph.glyphsBlended[_style].advance));
}
}

// ------------------------------------------------- end PrerenderGlyph

// ------------------------------------------------- start RenderGlyphSolid

void SC :: TGlyphsContainer :: CacheGlyph(SDL_Surface * tmp, GlyphTexture & _glyph) const
{
usint w = tmp->w;
usint h = tmp->h;
SDL_Surface * image = tmp;

_glyph.charWidth = w;
_glyph.charHeight = h;

bool IsWidth = IsPowerOfTwo(w);
bool IsHeight = IsPowerOfTwo(h);

if ((!IsWidth) || (!IsHeight))
{
if (!IsWidth) w = PowerOfTwo(w);
if (!IsHeight) h = PowerOfTwo(h);

image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);

if ( image == 0 )
{
logError2("FontMgr", "Couldn't allocate memory for expanding glyph texture to power of 2")
SDL_FreeSurface(tmp);
SC_ASSERT(!"Couldn't allocate memory for expanding texture to power of 2")
return;
}

SDL_Rect area;
area.x = 0;
area.y = 0;
area.w = tmp->w;
area.h = tmp->h;

// copy the tmp into the GL texture image
SDL_BlitSurface(tmp, &area, image, &area);
SDL_FreeSurface(tmp);
tmp = 0;
}

glGenTextures(1, &_glyph.textureID);
glBindTexture(GL_TEXTURE_2D, _glyph.textureID);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);

if (tmp) SDL_FreeSurface(tmp);
else SDL_FreeSurface(image);

_glyph.width = w;
_glyph.height = h;

}


// ------------------------------------------------- end RenderGlyphSolid




And the last (but not least one), some functions from Renderer, they are doing actual rendering to the screen. I'm doing 2d game, so they don't need to change view matrices all the time - I've assumed that when you call them, you have ortographic projection enabled etc.



// ------------------------------------------------- start RenderText

void SC :: Renderer :: RenderText(const char * _text, const char * _fontName, usint _size, const TScrPoint & _position,
const TColor4 & _color, ulint _style, eTextPosition _horizontal,
eTextPosition _vertical, eTextRenderMode _renderMode)
{
SC_ASSERT(_text)
SC_ASSERT(_fontName)
SC_ASSERT(_size > 0)

TGlyphsContainer * glyphs = getFontMgr.GetGlyphsContainer(_fontName, _size);

if (!glyphs)
{
logError2("Renderer", "Couldn't find font to output text.")
return;
}

// -------------------------------------------------

glColor4ub(_color.r, _color.g, _color.b, _color.a);

usint xPos = _position.x;
usint yPos = _position.y;

const char * txtPtr = _text;

// this precaching of glyphs is needed to determine full size of rendered text, it's needed to
// render them properly with given eTextPosition stuff
std :: vector <GlyphTexture*> cachedGlyphs;
TScrPoint renderedText;
TScrPntSet(renderedText, 0, 0);

while (*txtPtr)
{
GlyphTexture * glyph = glyphs->GetGlyphTexture(*txtPtr, _style, _renderMode);
TScrPntAddX(renderedText, renderedText, glyph->advance );
renderedText.y = Max(glyph->charHeight, (int) renderedText.y);

cachedGlyphs.push_back(glyph);
++txtPtr;
}

// ------------------------------------------------- HORIZONTAL JUSTIFICTION

if (_horizontal == tp_Centered) // center text
xPos -= (renderedText.x)/2;
else if (_horizontal == tp_Right) // justify to right
xPos -= renderedText.x;

// ------------------------------------------------- VERTICAL JUSTIFICTION

yPos += glyphs->GetFontDescent();

if (_vertical == tp_Centered) // center text
yPos -= (renderedText.y)/4;
else if (_vertical == tp_Right) // justify to right
yPos -= renderedText.y/2;

// -------------------------------------------------

std :: vector <GlyphTexture*> :: iterator itor = cachedGlyphs.begin();

while (itor != cachedGlyphs.end())
{
GlyphTexture * glyph = (*itor);
SC_ASSERT(glyph)

glBindTexture(GL_TEXTURE_2D, glyph->textureID);

float rightMargin = (float) glyph->charWidth /(float)glyph->width;
float upMargin = (float) glyph->charHeight / (float)glyph->height;

// -------------------------------------------------

glBegin(GL_QUADS);

glTexCoord2f(0.0f, 0.0f);
glVertex3i( xPos, yPos + glyph->charHeight, 1);

glTexCoord2f(0.0f, upMargin);
glVertex3i( xPos, yPos, 1);

glTexCoord2f(rightMargin, upMargin);
glVertex3i( xPos + glyph->charWidth, yPos, 1);

glTexCoord2f(rightMargin, 0.0f);
glVertex3i( xPos + glyph->charWidth, yPos + glyph->charHeight, 1);

glEnd();

// -------------------------------------------------

xPos += glyph->advance;

++itor;
}

glBindTexture(GL_TEXTURE_2D, 0);
boundTexture.id = 0;
glColor4ub(255, 255, 255, 255);
}

// ------------------------------------------------- end RenderText

// ------------------------------------------------- start RenderTextMulti

void SC :: Renderer :: RenderTextMulti(const char * _text, const char * _fontName, usint _size,
const TScrPoint & _position, const TColor4 & _color, ulint _style,
eTextPosition _horizontal, eTextRenderMode _renderMode)
{
SC_ASSERT(_text)
SC_ASSERT(_fontName)
SC_ASSERT(_size > 0)

TGlyphsContainer * glyphs = getFontMgr.GetGlyphsContainer(_fontName, _size);

if (!glyphs)
{
logError2("Renderer", "Couldn't find font to output text.")
return;
}

usint lineSkip = glyphs->GetFontLineSkip();

// -------------------------------------------------

std :: vector < std :: string> vLines; // these are the individual lines of text
std :: string text(_text);

int n = 0;

// Break the string into its lines, remember that it's different from std C++ language: end of line
// is determined by "/n" not "\n" existance!
while( n != -1 )
{
// Get until either '/n' or '\0'
std :: string strSub;
n = text.find( "/n", 0 ); // Find the next "/n"
strSub = text.substr( 0, n);
if( n != -1 )
text = text.substr( n+2, std::string::npos );

vLines.push_back(strSub);
}

// -------------------------------------------------

glColor4ub(_color.r, _color.g, _color.b, _color.a);

usint xPos = _position.x;
usint yPos = _position.y;

yPos += glyphs->GetFontDescent();

for( usint i = 0; i < vLines.size(); i++ )
{
const char * txtPtr = vLines[i].c_str();
xPos = _position.x;

// this precaching of glyphs is needed to determine full size of rendered text, it's needed to
// render them properly with given eTextPosition stuff
std :: vector <GlyphTexture*> cachedGlyphs;
TScrPoint renderedText;
TScrPntSet(renderedText, 0, 0);

while (*txtPtr)
{
GlyphTexture * glyph = glyphs->GetGlyphTexture(*txtPtr, _style, _renderMode);
TScrPntAddX(renderedText, renderedText, glyph->advance );

//renderedText.y = Max(glyph->height, (usint) renderedText.y);
renderedText.y = Max(glyph->charHeight, (int) renderedText.y);

cachedGlyphs.push_back(glyph);
++txtPtr;
}

// ------------------------------------------------- HORIZONTAL JUSTIFICTION

if (_horizontal == tp_Centered) // center text
xPos -= (renderedText.x)/2;
else if (_horizontal == tp_Right) // justify to right
xPos -= renderedText.x;

// -------------------------------------------------

std :: vector <GlyphTexture*> :: iterator itor = cachedGlyphs.begin();

while (itor != cachedGlyphs.end())
{
GlyphTexture * glyph = (*itor);
SC_ASSERT(glyph)

glBindTexture(GL_TEXTURE_2D, glyph->textureID);

float rightMargin = (float) glyph->charWidth /(float)glyph->width;
float upMargin = (float) glyph->charHeight / (float)glyph->height;

// -------------------------------------------------

glBegin(GL_QUADS);

glTexCoord2f(0.0f, 0.0f);
glVertex3i( xPos, yPos + glyph->charHeight, 1);

glTexCoord2f(0.0f, upMargin);
glVertex3i( xPos, yPos, 1);

glTexCoord2f(rightMargin, upMargin);
glVertex3i( xPos + glyph->charWidth, yPos, 1);

glTexCoord2f(rightMargin, 0.0f);
glVertex3i( xPos + glyph->charWidth, yPos + glyph->charHeight, 1);

glEnd();

// -------------------------------------------------

xPos += glyph->advance;

++itor;
}

yPos -= lineSkip;

}

glBindTexture(GL_TEXTURE_2D, 0);
boundTexture.id = 0;
glColor4ub(255, 255, 255, 255);

}

// ------------------------------------------------- end RenderTextMulti

// ------------------------------------------------- start TextWidth

SC :: ulint SC :: Renderer :: TextWidth(const char * _text, const char * _fontName, usint _size, ulint _style,
eTextRenderMode _renderMode)
{
SC_ASSERT(_text)
SC_ASSERT(_fontName)
SC_ASSERT(_size > 0)

TGlyphsContainer * glyphs = getFontMgr.GetGlyphsContainer(_fontName, _size);

if (!glyphs)
{
logError2("Renderer", "Couldn't find font to count text width.")
return 0;
}

// -------------------------------------------------

std :: vector < std :: string> vLines; // these are the individual lines of text
std :: string text(_text);

int n = 0;

// Break the string into its lines, remember that it's different from std C++ language: end of line
// is determined by "/n" not "\n" existance!
while( n != -1 )
{
// Get until either '/n' or '\0'
std :: string strSub;
n = text.find( "/n", 0 ); // Find the next "/n"
strSub = text.substr( 0, n);
if( n != -1 )
text = text.substr( n+2, std::string::npos );

vLines.push_back(strSub);
}

ulint maxRenderedTextWidth = 0;

for (std :: vector<std :: string> :: iterator itor = vLines.begin(); itor != vLines.end(); ++itor)
{
const char * txtPtr = (*itor).c_str();
ulint renderedTextWidth = 0;

// sum width of all letters from _text
while (*txtPtr)
{
GlyphTexture * glyph = glyphs->GetGlyphTexture(*txtPtr, _style, _renderMode);
renderedTextWidth += glyph->advance;
++txtPtr;
}

maxRenderedTextWidth = Max(maxRenderedTextWidth, renderedTextWidth);
}

return maxRenderedTextWidth;
}

// ------------------------------------------------- end TextWidth

// ------------------------------------------------- start TextHeight

SC :: ulint SC :: Renderer :: TextHeight(const char * _text, const char * _fontName, usint _size, ulint _style,
eTextRenderMode _renderMode)
{
SC_ASSERT(_text)
SC_ASSERT(_fontName)
SC_ASSERT(_size > 0)

TGlyphsContainer * glyphs = getFontMgr.GetGlyphsContainer(_fontName, _size);

if (!glyphs)
{
logError2("Renderer", "Couldn't find font to count text width.")
return 0;
}

// -------------------------------------------------

std :: string text(_text);

usint lineHeight = glyphs->GetFontHeight();

int n = 0;
usint found = 1; // 1 not 0 because every string constists at least of one line of text

while( n != -1 )
{
std :: string strSub;
n = text.find( "/n", 0 );

if( n != -1 )
{
text = text.substr( n+1, std::string::npos );
++found;
}
}

return (found * lineHeight);

}

// ------------------------------------------------- end TextHeight





And the really last one :-) test which shows how to use this system:



#include "SC.h"

using namespace SC;

class MyTask : public ITask
{

public:

MyTask() {}
~MyTask() {}

bool Start();
void Update();
void Stop() { }

};


TScrPoint v1, v2;

const usint fontSize = 20;

bool MyTask :: Start()
{

TScrPntSet(v1, 1, 300);

return true;
}

usint i = 400;
eTextPosition pos = tp_Normal;

TColor4 color = { 0, 255, 0, 255 };

#define fontName "font.ttf"
#define fontStyle trm_Blend

// -------------------------------------------------

void MyTask :: Update()
{
getRenderer.ClearBuffer(bt_Color);

TScrPntSet(v1, 400, 300);
getRenderer.RenderTextMulti("Man got his women/nto get his seed/nhe got the power/nshe got the need", fontName, fontSize, v1, color, ts_Underline, tp_Right, fontStyle);


ulint time = getTimer.GetFPSCount();

const char * temp = ulintToString(time);

SetWindowCaption(temp);

delete [] temp;

}

//--------------------------------------------------

MyTask * task;

int main(int argc, char ** argv)
{

InitLib();

getRenderer.SetColorDepth(32);
getRenderer.SetFullscreen(0);
getRenderer.SetScreenSize(800, 600);
getRenderer.EnterGraphicsMode();

InitSubsystems();

FontSizeList sizes;

sizes.push_back(fontSize);
getFontMgr.LoadFont(fontName, sizes);

task = new MyTask;
SC_ASSERT(task)
task -> SetPriority(501);

getKernel.AddTask(task);

getKernel.Execute();

delete task;

getFontMgr.ReleaseFont(fontName, sizes);


getRenderer.CloseGraphicsMode();

ShutdownLib();

return 0;
}





As you can see, 80% of above code isn't responsible for the actual text rendering :-/ rather for loading, releasing, managing fonts, reference counting etc. Anyway, I hope someone will find it usefull.

Share this post


Link to post
Share on other sites
Don't thank me, thank Bob Pendleton :-) without his idea I wouldn't write it.

I've thought that for the sake of completness, I would post here also original font manager written by Bob (it's covered under LGPL)



class Font
{
private:
static const int minGlyph = ' ';
static const int maxGlyph = 126;

static int initCounter;

typedef struct
{
int minx, maxx;
int miny, maxy;
int advance;
SDL_Surface *pic;
GLuint tex;
GLfloat texMinX, texMinY;
GLfloat texMaxX, texMaxY;
} glyph;

int height;
int ascent;
int descent;
int lineSkip;
glyph glyphs[maxGlyph + 1];

unsigned char *address;
int length;
int pointSize;
int style;
float fgRed, fgGreen, fgBlue;
float bgRed, bgGreen, bgBlue;

TTF_Font *ttfFont;

SDL_Color foreground;
SDL_Color background;

void loadChar(char c)
{
GLfloat texcoord[4];
char letter[2] = {0, 0};

if ((minGlyph <= c) &&
(c <= maxGlyph) &&
(NULL == glyphs[((int)c)].pic))
{
SDL_Surface *g0 = NULL;
SDL_Surface *g1 = NULL;

letter[0] = c;

TTF_GlyphMetrics(ttfFont,
(Uint16)c,
&glyphs[((int)c)].minx,
&glyphs[((int)c)].maxx,
&glyphs[((int)c)].miny,
&glyphs[((int)c)].maxy,
&glyphs[((int)c)].advance);

g0 = TTF_RenderText_Shaded(ttfFont,
letter,
foreground,
background);

if (NULL != g0)
{
g1 = SDL_DisplayFormat(g0);
SDL_FreeSurface(g0);
}

if (NULL != g1)
{
glyphs[((int)c)].pic = g1;
glyphs[((int)c)].tex = loadTextureColorKey(g1, texcoord, 0, 0,
0);
glyphs[((int)c)].texMinX = texcoord[0];
glyphs[((int)c)].texMinY = texcoord[1];
glyphs[((int)c)].texMaxX = texcoord[2];
glyphs[((int)c)].texMaxY = texcoord[3];
}
}
}

public:

Font(unsigned char *address,
int length,
int pointSize,
int style,
float fgRed, float fgGreen, float fgBlue,
float bgRed, float bgGreen, float bgBlue):
address(address), length(length),
pointSize(pointSize),
style(style),
fgRed(fgRed), fgGreen(fgGreen), fgBlue(fgBlue),
bgRed(bgRed), bgGreen(bgGreen), bgBlue(bgBlue),
ttfFont(NULL)
{
if (0 == initCounter)
{
if (TTF_Init() < 0)
{
errorExit("Can't init SDL_ttf");
}
}
initCounter++;
initFont();
}

~Font()
{
initCounter--;
if (0 == initCounter)
{
TTF_Quit();
}
}

void initFont()
{
SDL_RWops *src = NULL;
int i;

src = SDL_RWFromMem(address, length);

ttfFont = TTF_OpenFontRW(src, 1, pointSize);
if (NULL == ttfFont)
{
errorExit("Can't open font file");
}

TTF_SetFontStyle(ttfFont, style);

foreground.r = (Uint8)(255 * fgRed);
foreground.g = (Uint8)(255 * fgGreen);
foreground.b = (Uint8)(255 * fgBlue);

background.r = (Uint8)(255 * bgRed);
background.g = (Uint8)(255 * bgGreen);
background.b = (Uint8)(255 * bgBlue);

height = TTF_FontHeight(ttfFont);
ascent = TTF_FontAscent(ttfFont);
descent = TTF_FontDescent(ttfFont);
lineSkip = TTF_FontLineSkip(ttfFont);

for (i = minGlyph; i <= maxGlyph; i++)
{
glyphs[i].pic = NULL;
glyphs[i].tex = 0;
}
}

int getLineSkip()
{
return lineSkip;
}

int getHeight()
{
return height;
}

void textSize(char *text,
SDL_Rect *r)
{
int maxx = 0;
int advance = 0;

r->x = 0;
r->y = 0;
r->w = 0;
r->h = height;

while (0 != *text)
{
if ((minGlyph <= *text) && (*text <= maxGlyph))
{
loadChar(*text);

maxx = glyphs[((int)*text)].maxx;
advance = glyphs[((int)*text)].advance;
r->w += advance;
}

text++;
}

r->w = r->w - advance + maxx;
}

void drawText(char *text, int x, int y)
{
GLfloat left, right;
GLfloat top, bottom;
GLfloat texMinX, texMinY;
GLfloat texMaxX, texMaxY;

glPushAttrib(GL_ALL_ATTRIB_BITS);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

while (0 != *text)
{
if ((minGlyph <= *text) && (*text <= maxGlyph))
{
loadChar(*text);

texMinX = glyphs[((int)*text)].texMinX;
texMinY = glyphs[((int)*text)].texMinY;
texMaxX = glyphs[((int)*text)].texMaxX;
texMaxY = glyphs[((int)*text)].texMaxY;

left = x;
right = x + glyphs[((int)*text)].pic->w;
top = y;
bottom = y + glyphs[((int)*text)].pic->h;

glBindTexture(GL_TEXTURE_2D, glyphs[((int)*text)].tex);

glBegin(GL_TRIANGLE_STRIP);

glTexCoord2f(texMinX, texMinY); glVertex2f( left, top);
glTexCoord2f(texMaxX, texMinY); glVertex2f(right, top);
glTexCoord2f(texMinX, texMaxY); glVertex2f( left, bottom);
glTexCoord2f(texMaxX, texMaxY); glVertex2f(right, bottom);

glEnd();

x += glyphs[((int)*text)].advance;
}

text++;
}

glPopAttrib();
}
};

//-------------------------------------------------

int Font::initCounter = 0;

//-------------------------------------------------

// Create a texture from a surface. Set the alpha according
// to the color key. Pixels that match the color key get an
// alpha of zero while all other pixels get an alpha of
// one.

GLuint loadTextureColorKey(SDL_Surface *surface,
GLfloat *texcoord,
int ckr,
int ckg,
int ckb)
{
GLuint texture;
int w, h;
SDL_Surface *image;
SDL_Rect area;
Uint32 colorkey;

// Use the surface width and height expanded to powers of 2

w = powerOfTwo(surface->w);
h = powerOfTwo(surface->h);
texcoord[0] = 0.0f; // Min X
texcoord[1] = 0.0f; // Min Y
texcoord[2] = (GLfloat)surface->w / w; // Max X
texcoord[3] = (GLfloat)surface->h / h; // Max Y

image = SDL_CreateRGBSurface(
SDL_SWSURFACE,
w, h,
32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN // OpenGL RGBA masks
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000
#else
0xFF000000,
0x00FF0000,
0x0000FF00,
0x000000FF
#endif
);
if (image == NULL)
{
return 0;
}

// Set up so that colorkey pixels become transparent

colorkey = SDL_MapRGBA(image->format, ckr, ckg, ckb, 0);
SDL_FillRect(image, NULL, colorkey);

colorkey = SDL_MapRGBA(surface->format, ckr, ckg, ckb, 0);
SDL_SetColorKey(surface, SDL_SRCCOLORKEY, colorkey);

// Copy the surface into the GL texture image
area.x = 0;
area.y = 0;
area.w = surface->w;
area.h = surface->h;
SDL_BlitSurface(surface, &area, image, &area);

// Create an OpenGL texture for the image

glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
w, h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
image->pixels);

SDL_FreeSurface(image); // No longer needed

return texture;
}
------------------------------------------------




It's much shorter, so it should be easier to follow the code :-)

Share this post


Link to post
Share on other sites
::Sorry for bring topic back up
If GL_BGRA is unidentified put this line in
#include <GL/glext.h>

Share this post


Link to post
Share on other sites
Hello,


How can I render OPENGL textures with SDL_TTF fonts and transparent background?
I compiled the first example and the textures have black background.
Also, I would like how to obtain a HDC (Handle Device Context) of a Main Window that is created with SDL library.


Thank's.

Share this post


Link to post
Share on other sites
Quote:
Original post by cyberpuk
How can I render OPENGL textures with SDL_TTF fonts and transparent background?


That's an OpenGL specific issue, I'm not sure if it's possible, I've tried it in the past using this and couln't get it to come out how I wanted it to, so maybe someone with more OpenGL knowledge can answer that one.

Quote:
Also, I would like how to obtain a HDC (Handle Device Context) of a Main Window that is created with SDL library.


#include <SDL_syswm.h>
#include <windows.h>
...
// global or a static local variable
SDL_SysWMinfo pInfo;
...
// In init function just need to call once
SDL_VERSION(&pInfo.version);
SDL_GetWMInfo(&pInfo);
...
// Get's the DC based on the window's HWND
HWND myHwnd = pInfo.window;
HDC myDC = GetDC(myHwnd);

Share this post


Link to post
Share on other sites
I've been tinkering with Bob Pendleton's SDL_ttf font class and made it support newlines... I also took out a few things that I wasn't using, do forgive... it should be pretty obvious how to mix/match :) [[and I split it into the .h and .cpp, and took out the 'background color', instead using alpha blending, and... not sure what else, TBH, sorry]]

### font.h ###

/** BTW, it is covered by the LGPL ** Bob Pendleton **/
/** http://www.devolution.com/pipermail/sdl/2004-December/066119.html **/

/**

+--------------------------------------+
+ Bob Pendleton: writer and programmer +
+ email: Bob@xxxxxxxxxxxxx +
+ blog: www.Stonewolf.net +
+ web: www.GameProgrammer.com +
+--------------------------------------+

http://www.oreillynet.com/pub/au/1205

Edits by Kaolin Fire ( kfire@xxxxxxxx; web: erif.org )
now takes into account glyph minx so glyphs can overlap as intended
now takes into account newline character

**/


class Font
{
private:
static const int minGlyph = ' ';
static const int maxGlyph = 126;

static int initCounter;

typedef struct
{
int minx, maxx;
int miny, maxy;
int advance;
SDL_Surface *pic;
GLuint tex;
GLfloat texMinX, texMinY;
GLfloat texMaxX, texMaxY;
} glyph;

int height;
int ascent;
int descent;
int lineSkip;
glyph glyphs[maxGlyph + 1];

const char *fontName;
int pointSize;
float fgRed, fgGreen, fgBlue;

TTF_Font *ttfFont;

SDL_Color foreground;

void loadChar(char c);

public:

Font(const char *fontName,
int pointSize,
float fgRed, float fgGreen, float fgBlue);

~Font();

void initFont();

int getLineSkip();

int getHeight();

void textSize(char *text, SDL_Rect *r);

void drawText(char *text, int x, int y);

};



###font.cpp###

#include "font.h"

void Font::loadChar(char c)
{
GLfloat texcoord[4];
char letter[2] = {0, 0};

if ((minGlyph <= c) &&
(c <= maxGlyph) &&
(NULL == glyphs[((int)c)].pic))
{
SDL_Surface *g0 = NULL;
SDL_Surface *g1 = NULL;

letter[0] = c;
TTF_GlyphMetrics(ttfFont,
(Uint16)c,
&glyphs[((int)c)].minx,
&glyphs[((int)c)].maxx,
&glyphs[((int)c)].miny,
&glyphs[((int)c)].maxy,
&glyphs[((int)c)].advance);

g0 = TTF_RenderText_Blended(ttfFont,
letter,
foreground);

if (NULL != g0)
{
g1 = SDL_DisplayFormatAlpha(g0);
SDL_FreeSurface(g0);
}

if (NULL != g1)
{
glyphs[((int)c)].pic = g1;
glyphs[((int)c)].tex = SDL_GL_LoadTexture(g1, texcoord);
glyphs[((int)c)].texMinX = texcoord[0];
glyphs[((int)c)].texMinY = texcoord[1];
glyphs[((int)c)].texMaxX = texcoord[2];
glyphs[((int)c)].texMaxY = texcoord[3];
}
}
}

Font::Font(const char *fontName,
int pointSize,
float fgRed, float fgGreen, float fgBlue):
fontName(fontName),
pointSize(pointSize),
fgRed(fgRed), fgGreen(fgGreen), fgBlue(fgBlue),
ttfFont(NULL)
{
if (0 == initCounter)
{
if (TTF_Init() < 0)
{
//TODO :: errorExit("Can't init SDL_ttf");
printf("Can't init sdl_ttf !?\n");
}
}
initCounter++;
initFont();
}

Font::~Font()
{
initCounter--;
if (0 == initCounter)
{
TTF_Quit();
}
}

void Font::initFont()
{
int i;

ttfFont = TTF_OpenFont(fontName, pointSize);
if (NULL == ttfFont)
{
printf("Can't open font file\n");
//TODO :: errorExit("Can't open font file");
}

foreground.r = (Uint8)(255 * fgRed);
foreground.g = (Uint8)(255 * fgGreen);
foreground.b = (Uint8)(255 * fgBlue);

height = TTF_FontHeight(ttfFont);
ascent = TTF_FontAscent(ttfFont);
descent = TTF_FontDescent(ttfFont);
lineSkip = TTF_FontLineSkip(ttfFont);

for (i = minGlyph; i <= maxGlyph; i++)
{
glyphs[i].pic = NULL;
glyphs[i].tex = 0;
}
}

int Font::getLineSkip()
{
return lineSkip;
}

int Font::getHeight()
{
return height;
}

void Font::textSize(char *text,
SDL_Rect *r)
{
int maxx = 0;
int advance = 0;
int minx = 0;
int w_largest = 0;
char lastchar = 0;

r->x = 0;
r->y = 0;
r->w = 0;
r->h = height;

while (0 != *text)
{
if ((minGlyph <= *text) && (*text <= maxGlyph))
{
lastchar = *text;
if (*text == '\n') {
r->h += lineSkip;
r->w = r->w - advance + maxx;
if (r->w > w_largest) w_largest = r->w;
r->w = 0;
} else {
loadChar(*text);

maxx = glyphs[((int)*text)].maxx;
advance = glyphs[((int)*text)].advance;
r->w += advance;
}
}

text++;
}
if (lastchar != '\n') {
r->w = r->w - advance + maxx;
if (r->w > w_largest) w_largest = r->w;
} else {
r->h -= lineSkip;
}

if (w_largest > r->w) r->w = w_largest;

}

void Font::drawText(char *text, int x, int y)
{
GLfloat left, right;
GLfloat top, bottom;
GLfloat texMinX, texMinY;
GLfloat texMaxX, texMaxY;
GLfloat minx;
GLfloat baseleft = x;

glPushAttrib(GL_ALL_ATTRIB_BITS);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

while (0 != *text) {
if (*text == '\n') {
x = baseleft;
y += lineSkip;
} else if ((minGlyph <= *text) && (*text <= maxGlyph)) {
loadChar(*text);

texMinX = glyphs[((int)*text)].texMinX;
texMinY = glyphs[((int)*text)].texMinY;
texMaxX = glyphs[((int)*text)].texMaxX;
texMaxY = glyphs[((int)*text)].texMaxY;

minx = glyphs[((int)*text)].minx;

left = x + minx;
right = x + glyphs[((int)*text)].pic->w + minx;
top = y;
bottom = y + glyphs[((int)*text)].pic->h;

glBindTexture(GL_TEXTURE_2D, glyphs[((int)*text)].tex);

glBegin(GL_TRIANGLE_STRIP);

glTexCoord2f(texMinX, texMinY); glVertex2f( left, top);
glTexCoord2f(texMaxX, texMinY); glVertex2f(right, top);
glTexCoord2f(texMinX, texMaxY); glVertex2f( left, bottom);
glTexCoord2f(texMaxX, texMaxY); glVertex2f(right, bottom);

glEnd();

x += glyphs[((int)*text)].advance;
}

text++;
}
glPopAttrib();
}

int Font::initCounter = 0;

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Hi,
Sorry for the UP but I want to know how to use this code with Latin specials chars like "é" "ê" "à" ?

Share this post


Link to post
Share on other sites
@kaolin fire
function Font::textSize is not handling newlines properly.
The first condition in the while loop should be corrected to something like:

if (((minGlyph <= *text) && (*text <= maxGlyph)) || *text == '\n')

instead of

if ((minGlyph <= *text) && (*text <= maxGlyph))

Otherwise '\n' won't be processed.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this