/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: ... */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_FONT #include "font.h" #include "text.h" #ifdef HAVE_SDL_IMAGE_H #include #else #include #endif #include "font.xpm" #include "debug.h" #include "compiler.h" using namespace std; /** * constructs a Font * @param fontFile the File to load the font from * @param fontSize the Size of the Font in Pixels */ Font::Font(const char* fontFile, unsigned int fontSize) { this->init(); this->setSize(fontSize); if (fontFile != NULL) this->loadFont(fontFile); this->setStyle("c");//TTF_STYLE_NORMAL); this->fastTextureID = this->createFastTexture(); // this->createAsciiImage("test.bmp"); } /** * constructs a Font * @param fontFile the File to load the font from * @param fontSize the Size of the Font in Pixels */ Font::Font(char** xpmArray) { this->init(); // this->setSize(fontSize); SDL_Surface* image = NULL; if (xpmArray != NULL) image = IMG_ReadXPMFromArray(xpmArray); if (image != NULL) { this->loadFontFromSDL_Surface(image); SDL_FreeSurface(image); } else PRINTF(1)("loading from surface failed: %s\n", IMG_GetError()); } /** * destructs a font * this releases the memory a font uses to be opened. * deletes the glLists, and the TTF-handler, if present. */ Font::~Font() { // deleting all Glyphs if (this->glyphArray != NULL) { for (int i = 0; i < FONT_HIGHEST_KNOWN_CHAR; i++) { if (this->glyphArray[i] != NULL) { glDeleteLists(this->glyphArray[i]->displayList, 1); delete this->glyphArray[i]; } } delete[] this->glyphArray; } // erease this font out of the memory. if (likely(this->font != NULL)) TTF_CloseFont(this->font); } /** * initializes a Font (with default values) */ void Font::init() { this->setClassID(CL_FONT, "Font"); // setting default values. this->font = NULL; this->glyphArray = NULL; this->fastTextureID = 0; } /** * sets The Font. * @param fontFile The file containing the font. * @returns true if loaded, false if something went wrong, or if a font was loaded before. */ bool Font::loadFont(const char* fontFile) { if (!this->getName()) { this->setName(fontFile); this->font = TTF_OpenFont(this->getName(), this->fontSize); if(!this->font) { PRINTF(1)("TTF_OpenFont: %s\n", TTF_GetError()); return false; } else return true; } else { PRINTF(2)("Font already initialized, unable to change it now.\n"); return false; } } /** * loads a font From an XPM-array. * @param xpmArray the array of the XPM to load the font from. */ bool Font::loadFontFromSDL_Surface(SDL_Surface* surface) { // loading to a texture. if(surface == NULL) return false; this->fastTextureID = Text::loadTexture(surface, NULL); // initializing the Glyphs. if (this->glyphArray == NULL) { float cx,cy; Glyph* glyph; this->glyphArray = new Glyph*[FONT_HIGHEST_KNOWN_CHAR]; for (int i = 0; i < FONT_HIGHEST_KNOWN_CHAR; i++) { glyph = this->glyphArray[i] = new Glyph; glyph->displayList = glGenLists(1); if (!glIsList(glyph->displayList)) { PRINTF(2)("Error creating glList for Font character %c\n", i); this->glyphArray[i] = NULL; delete glyph; continue; } cx=(float)(i%16)/16.0f; // X Position Of Current Character cy=(float)(i/16)/16.0f; // Y Position Of Current Character glNewList(glyph->displayList, GL_COMPILE); // Start Building A List glBegin(GL_QUADS); // Use A Quad For Each Character glTexCoord2f(cx, cy+0.001f); // Texture Coord (Bottom Left) glVertex2d(0,-16); // Vertex Coord (Bottom Left) glTexCoord2f(cx+0.0625f, cy+0.001f); // Texture Coord (Bottom Right) glVertex2i(16,-16); // Vertex Coord (Bottom Right) glTexCoord2f(cx+0.0625f, cy+0.0625f); // Texture Coord (Top Right) glVertex2i(16,0); // Vertex Coord (Top Right) glTexCoord2f(cx, cy+0.0625f); // Texture Coord (Top Left) glVertex2i(0,0); // Vertex Coord (Top Left) glEnd(); // Done Building Our Quad (Character) // glTranslated(12,0,0); // Move To The Right Of The Character glEndList(); // Done Building The Display List this->glyphArray[i]->width = 12; } } return true; } /** * sets a specific renderStyle * @param renderStyle the Style to render: a char-array containing: i: italic, b: bold, u, underline */ void Font::setStyle(const char* renderStyle) { this->renderStyle = TTF_STYLE_NORMAL; for (int i = 0; i < strlen(renderStyle); i++) if (strncmp(renderStyle+i, "b", 1) == 0) this->renderStyle |= TTF_STYLE_BOLD; else if (strncmp(renderStyle+i, "i", 1) == 0) this->renderStyle |= TTF_STYLE_ITALIC; else if (strncmp(renderStyle+i, "u", 1) == 0) this->renderStyle |= TTF_STYLE_UNDERLINE; if (likely(this->font != NULL)) TTF_SetFontStyle(this->font, this->renderStyle); else PRINTF(2)("Font was not initialized, please do so before setting the Font-Style.\n"); } /** * Sets a new Size to the font * @param fontSize The new Size in pixels. */ void Font::setSize(unsigned int fontSize) { this->fontSize = fontSize; } Font* Font::defaultFont = NULL; void Font::createAsciiImage(const char* fileName) { if (this->font == NULL) return; int height = this->getMaxHeight(); // SDL_Color tmpColor = {0, 0, 0}; // Surface definition. SDL_Rect tmpRect; // this represents a Rectangle for blitting. SDL_Surface* tmpSurf = SDL_CreateRGBSurface(SDL_SWSURFACE, height*16, height*16, 32, #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */ 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); tmpRect.x = 0; tmpRect.y = 0; tmpRect.w = tmpSurf->w; tmpRect.h = tmpSurf->h; SDL_SetClipRect(tmpSurf, &tmpRect); int maxLineHeight = 0; int posX, posY; // all the interessting Glyphs for (posY = 0; posY < 16; posY++) { for (posX = 0; posX < 16; posX++) { SDL_Surface* glyphSurf = NULL; if (likely(this->font != NULL)) { SDL_Color white = {255, 255, 255}; glyphSurf = TTF_RenderGlyph_Blended(this->font, posX+16*posY, white); } if( glyphSurf != NULL ) { tmpRect.x = height*posX; tmpRect.y = height*posY; SDL_SetAlpha(glyphSurf, 0, 0); SDL_BlitSurface(glyphSurf, NULL, tmpSurf, &tmpRect); SDL_FreeSurface(glyphSurf); // Outputting Glyphs to BMP-files. /* char outname[512]; if (i < 10) sprintf( outname, "%s-glyph-00%d.bmp", this->getName(), i ); else if (i <100) sprintf( outname, "%s-glyph-0%d.bmp", this->getName(), i ); else sprintf( outname, "%s-glyph-%d.bmp", this->getName(), i ); SDL_SaveBMP(tmpSurf, outname);*/ } } } SDL_SaveBMP(tmpSurf, fileName); SDL_FreeSurface(tmpSurf); } /** * initializes the default font */ void Font::initDefaultFont() { if (Font::defaultFont == NULL) Font::defaultFont = new Font(font_xpm); } /** * deletes the default font */ void Font::removeDefaultFont() { if (Font::defaultFont != NULL) delete Font::defaultFont; Font::defaultFont = NULL; } /** * @returns the maximum height of the Font, if the font was initialized, 0 otherwise */ int Font::getMaxHeight() { if (likely (this->font != NULL)) return TTF_FontHeight(this->font); else return 0; } /** * @returns the maximum ascent of the Font, if the font was initialized, 0 otherwise the ascent is the pixels of the font above the baseline */ int Font::getMaxAscent() { if (likely(this->font != NULL)) return TTF_FontAscent(this->font); else return 0; } /** * @returns the maximum descent of the Font, if the font was initialized, 0 otherwise the descent is the pixels of the font below the baseline */ int Font::getMaxDescent() { if (likely(this->font != NULL)) return TTF_FontDescent(this->font); else return 0; } /** * @param character The character to get info about. * @returns a Glyph struct of a character. This Glyph is a pointer, and MUST be deleted by the user.. This only works for horizontal fonts. see http://freetype.sourceforge.net/freetype2/docs/tutorial/step2.html for more info about vertical Fonts */ Glyph* Font::getGlyphMetrics(Uint16 character) { Glyph* rg = new Glyph; rg->character = character; if (likely (this->font!= NULL)) TTF_GlyphMetrics(this->font, rg->character, &rg->minX, &rg->maxX, &rg->minY, &rg->maxY, &rg->advance); rg->height = rg->maxY - rg->minY; rg->width = rg->maxX - rg->minX; rg->bearingX = (rg->advance - rg->width) / 2; rg->bearingY = rg->maxY; return rg; } /** * creates a Fast-Texture of this Font */ GLuint Font::createFastTexture() { /* interesting GLYPHS: * 32: space * 33-47: Special Characters. * 48-57: 0-9 * 58-63: some more special chars (minor) * 65-90: A-Z * 97-122: a-z */ int numberOfGlyphs = 91; this->initGlyphs(32, numberOfGlyphs); this->glyphArray[32]->width = fontSize/2; //!< @todo find out the real size of a Space int rectSize = this->findOptimalFastTextureSize(); // setting default values. (maybe not needed afterwards) SDL_Color tmpColor; tmpColor.r = tmpColor.g = tmpColor.b = 0; // Surface definition. SDL_Rect tmpRect; // this represents a Rectangle for blitting. SDL_Surface* tmpSurf = SDL_CreateRGBSurface(SDL_SWSURFACE, rectSize, rectSize, 32, #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */ 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); tmpRect.x = 0; tmpRect.y = 0; tmpRect.w = tmpSurf->w; tmpRect.h = tmpSurf->h; SDL_SetClipRect(tmpSurf, &tmpRect); int maxLineHeight = 0; // all the interessting Glyphs for (int i = 0; i < 128; i++) { SDL_Surface* glyphSurf = NULL; Glyph* tmpGlyph; if (tmpGlyph = this->glyphArray[i]) { if (tmpGlyph->height > maxLineHeight) maxLineHeight = tmpGlyph->height; if (tmpRect.x+tmpGlyph->advance > tmpSurf->w) { tmpRect.x = 0; tmpRect.y = tmpRect.y + maxLineHeight + 1; maxLineHeight = 0; } if (tmpRect.y + maxLineHeight > tmpSurf->h) { PRINTF(1)("Protection, so font cannot write over the boundraries error (this should not heappen\n"); break; } // reading in the new Glyph if (likely(this->font != NULL)) { SDL_Color white = {255, 255, 255}; glyphSurf = TTF_RenderGlyph_Blended(this->font, i, white); } if( glyphSurf != NULL ) { SDL_SetAlpha(glyphSurf, 0, 0); SDL_BlitSurface(glyphSurf, NULL, tmpSurf, &tmpRect); TexCoord tmpTexCoord; tmpTexCoord.minU = (float)tmpRect.x/(float)tmpSurf->w; tmpTexCoord.maxU = (float)(tmpRect.x + tmpGlyph->advance)/(float)tmpSurf->w; tmpTexCoord.minV = (float)(tmpRect.y)/(float)tmpSurf->w; tmpTexCoord.maxV = (float)(tmpRect.y+tmpGlyph->height)/(float)tmpSurf->w; tmpGlyph->displayList = glGenLists(1); glNewList(tmpGlyph->displayList, GL_COMPILE); glBegin(GL_QUADS); glTexCoord2f(tmpTexCoord.minU, tmpTexCoord.minV); glVertex2d(0, - tmpGlyph->bearingY); glTexCoord2f(tmpTexCoord.minU, tmpTexCoord.maxV); glVertex2d(0, tmpGlyph->height - tmpGlyph->bearingY); glTexCoord2f(tmpTexCoord.maxU, tmpTexCoord.maxV); glVertex2d(tmpGlyph->width, tmpGlyph->height - tmpGlyph->bearingY); glTexCoord2f(tmpTexCoord.maxU, tmpTexCoord.minV); glVertex2d(tmpGlyph->width, - tmpGlyph->bearingY); glEnd(); glEndList(); SDL_FreeSurface(glyphSurf); tmpRect.x += tmpGlyph->advance; // Outputting Glyphs to BMP-files. /* char outname[512]; if (i < 10) sprintf( outname, "%s-glyph-00%d.bmp", this->getName(), i ); else if (i <100) sprintf( outname, "%s-glyph-0%d.bmp", this->getName(), i ); else sprintf( outname, "%s-glyph-%d.bmp", this->getName(), i ); SDL_SaveBMP(tmpSurf, outname);*/ } } } GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); 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, tmpSurf->w, tmpSurf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmpSurf->pixels); SDL_FreeSurface(tmpSurf); return texture; } /** * stores Glyph Metrics in an Array. * @param from The Glyph to start from. * @param count The number of Glyphs to start From. */ void Font::initGlyphs(Uint16 from, Uint16 count) { /* initialize the Array, and set all its entries to NULL * only if the Glyph-array has not been initialized */ if (!this->glyphArray) { this->glyphArray = new Glyph*[FONT_HIGHEST_KNOWN_CHAR]; for (int i = 0; i < FONT_HIGHEST_KNOWN_CHAR; i++) this->glyphArray[i] = NULL; } Uint16 lastGlyph = from + count; for (int i = from; i <= lastGlyph; i++) { // setting up all the Glyphs we like. glyphArray[i] = getGlyphMetrics(i); } return; } /** * @returns the optimal size to use as the texture size @todo: this algorithm can be a lot more faster, althought it does not really matter within the init-context, and 128 glyphs. This function searches for a 2^n sizes texture-size, this is for openGL-version < 1.2 compatibility ( and because it is realy easy like this :)) */ int Font::findOptimalFastTextureSize() { int i; int x,y; // the counters int maxLineHeight = this->getMaxHeight(); unsigned int size = 32; // starting Value, we have to start somewhere 32 seems reasonable. (take any small enough 2^i number) bool sizeOK = false; Glyph* tmpGlyph; while (!sizeOK) { x = 0; y = 0; for (i = 0; i <= FONT_HIGHEST_KNOWN_CHAR; i++) { if((tmpGlyph = this->glyphArray[i]) != NULL) { // getting the height of the highest Glyph in the Line. if (tmpGlyph->height > maxLineHeight) maxLineHeight = tmpGlyph->height; if (x + tmpGlyph->advance > size) { x = 0; y = y + maxLineHeight; //maxLineHeight = 0; } if (y + maxLineHeight + 1 > size) break; x += tmpGlyph->advance; } } if (i >= FONT_HIGHEST_KNOWN_CHAR-1 || size > 8192) sizeOK = true; else size *= 2; } return size; } /** * a simple function to get some interesting information about this class */ void Font::debug() { // print the loaded font's style int style; if (likely(this->font != NULL)) style = TTF_GetFontStyle(this->font); PRINTF(0)("The font style is:"); if(style==TTF_STYLE_NORMAL) PRINTF(0)(" normal"); else { if(style&TTF_STYLE_BOLD) PRINTF(0)(" bold"); if(style&TTF_STYLE_ITALIC) PRINTF(0)(" italic"); if(style&TTF_STYLE_UNDERLINE) PRINTF(0)(" underline"); } PRINTF(0)("\n"); }