/* 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: ... for some fonts and licenses visit: =http://www.dafont.com/en/font.php= !! IMPORTANT !! When using ttf fonts clear the license issues prior to adding them to orxonox. This is really important, because we do not want to offend anyone. */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_FONT #include "text_engine.h" using namespace std; #include #include #include #include "graphics_engine.h" #include "resource_manager.h" #include "p_node.h" #include "vector.h" #include "debug.h" #include "list.h" //////////// /// TEXT /// //////////// /** \brief creates a new Text Element \param font the Font to render this text in \param type The renderType to display this font in this constructor is private, because the user should initialize a text with the TextEngine. */ Text::Text(Font* font, int type) { this->setClassID(CL_TEXT, "Text"); // initialize this Text this->bindNode = NULL; this->font = font; this->text = NULL; this->alignment = TEXT_DEFAULT_ALIGNMENT; this->texture = 0; this->blending = 1.0f; this->setType(type); this->setPosition(0, 0); this->setText(FONT_DEFAULT_TEXT); } /** \brief deletes a Text out of memory This also ereases the text from the textList of the TextEngine */ Text::~Text(void) { TextEngine::getInstance()->deleteText(this); } /** \brief tells the Text, that it should folow a PNode \param bindNode: the node to bind this text to */ void Text::setBindNode(PNode* bindNode) { this->bindNode = bindNode; } /** \brief sets the Type of this Text \param type the type to set. */ void Text::setType(int type) { if (this->font->font) this->type = type; else this->type = TEXT_DYNAMIC; } /** \brief Sets a new Text to the font \param text the new text to set */ void Text::setText(const char* text) { if (this->text) delete []this->text; this->text = new char[strlen(text)+1]; strcpy(this->text, text); // setting up the Text-Width if DYNAMIC if (this->type == TEXT_DYNAMIC) { Glyph** glyphArray = this->font->getGlyphArray(); int width = 0; char* tmpText = this->text; while (*tmpText != '\0') { if(glyphArray[*tmpText]) { width += glyphArray[*tmpText]->width; } tmpText++; } this->posSize.w = width; } } /** \brief sets a Position. \param x the x-position in pixels from the left border \param y the y-position in pixels from the top border */ void Text::setPosition(int x, int y) { this->posSize.x = x; this->posSize.y = y; } /** \brief sets the text-alignment \param alignment the alignment to set */ void Text::setAlignment(TEXT_ALIGNMENT alignment) { this->alignment = alignment; } /** \brief sets a new color to the font \param r Red \param g Green \param b Blue */ void Text::setColor(Uint8 r, Uint8 g, Uint8 b) { this->color.r = r; this->color.g = g; this->color.b = b; } /** \brief creates a texture out of the given parameters this has to be called every time by the user, to if changes were made. this is only for TEXT_STATIC-mode */ void Text::createTexture(void) { SDL_Surface* tmpSurf; if (this->texture) glDeleteTextures(1, &this->texture); if (likely(this->font != NULL)) tmpSurf = TTF_RenderText_Blended(this->font->font, this->text, this->color); if (tmpSurf) this->texture = loadTexture(tmpSurf, &this->texCoord); this->posSize.w = tmpSurf->w; this->posSize.h = tmpSurf->h; SDL_FreeSurface(tmpSurf); } /** \brief draws the Font */ void Text::draw(void) const { // setting the Position of this Text. Vector pos; if (this->alignment == TEXT_ALIGN_SCREEN_CENTER) { pos.x = GraphicsEngine::getInstance()->getResolutionX()/2 + this->posSize.x; pos.y = GraphicsEngine::getInstance()->getResolutionY()/2 + this->posSize.y; pos.z = 0; } else if (this->bindNode) { GLdouble x = this->bindNode->getAbsCoor().x; GLdouble y = this->bindNode->getAbsCoor().y; GLdouble z = this->bindNode->getAbsCoor().z; GLdouble tmp[3]; gluProject(x, y, z, GraphicsEngine::modMat, GraphicsEngine::projMat, GraphicsEngine::viewPort, tmp, tmp+1, tmp+2); pos.x = tmp[0] + this->posSize.x; pos.y = GraphicsEngine::getInstance()->getResolutionY() - tmp[1] + this->posSize.y; pos.z = tmp[2]; } else { pos.x = this->posSize.x; pos.y = this->posSize.y; pos.z = 0; } // setting the Blending effects glColor4f(1.0f,1.0f,1.0f, this->blending); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glPushMatrix(); // transform for alignment. if (this->alignment == TEXT_ALIGN_RIGHT) glTranslatef(-this->posSize.w, 0, 0); else if (this->alignment == TEXT_ALIGN_CENTER || this->alignment == TEXT_ALIGN_SCREEN_CENTER) glTranslatef(-this->posSize.w/2, 0, 0); // drawing this Text. if(type == TEXT_STATIC) { glBindTexture(GL_TEXTURE_2D, this->texture); glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); glTexCoord2f(this->texCoord.minU, this->texCoord.minV); glVertex2f(pos.x, pos.y ); glTexCoord2f(this->texCoord.maxU, this->texCoord.minV); glVertex2f(pos.x + this->posSize.w, pos.y ); glTexCoord2f(this->texCoord.maxU, this->texCoord.maxV); glVertex2f(pos.x + this->posSize.w, pos.y + this->posSize.h); glTexCoord2f(this->texCoord.minU, this->texCoord.maxV); glVertex2f(pos.x, pos.y + this->posSize.h); glEnd(); } else //(if type == TEXT_DYNAMIC) { Glyph** glyphArray = this->font->getGlyphArray(); glBindTexture(GL_TEXTURE_2D, this->font->getFastTextureID()); // glEnable(GL_TEXTURE_2D); glTranslatef(pos.x, pos.y, 0); char* tmpText = this->text; while (*tmpText != '\0') { if(glyphArray[*tmpText]) { glCallList(glyphArray[*tmpText]->displayList); glTranslatef(glyphArray[*tmpText]->width, 0, 0); } tmpText++; } } glPopMatrix(); } /** \brief prints out some nice debug information about this text */ void Text::debug(void) const { PRINT(0)("=== TEXT: %s ===\n", this->text); if (this->bindNode) PRINT(0)("is bind to %s; ref=%p\n", this->bindNode->getName(), this->bindNode); PRINT(0)("Relative Position: (%d::%d)\n", this->posSize.x, this->posSize.y); PRINT(0)("Color: %d %d %d\n", this->color.r, this->color.g, this->color.b); } //////////// /// UTIL /// //////////// /** \brief Loads a Font from an SDL_surface into a texture. \param surface The surface to make the texture of \param texCoord The texture coordinates of the 4 corners of the texture \returns the ID of the texture */ GLuint Text::loadTexture(SDL_Surface *surface, TexCoord* texCoord) { GLuint texture; int w, h; SDL_Surface *image; SDL_Rect area; Uint32 saved_flags; Uint8 saved_alpha; /* Use the surface width and height expanded to powers of 2 */ w = powerOfTwo(surface->w); h = powerOfTwo(surface->h); if (texCoord) { texCoord->minU = 0.0f; texCoord->minV = 0.0f; texCoord->maxU = (GLfloat)surface->w / w; texCoord->maxV = (GLfloat)surface->h / h; } 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; } /* Save the alpha blending attributes */ saved_flags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK); saved_alpha = surface->format->alpha; if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) { SDL_SetAlpha(surface, 0, 0); } /* 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); /* Restore the alpha blending attributes */ if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) { SDL_SetAlpha(surface, saved_flags, saved_alpha); } /* Create an OpenGL texture for the image */ 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, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); SDL_FreeSurface(image); /* No longer needed */ return texture; } /** \brief Quick utility function for texture creation \param input an integer \returns the next bigger 2^n-integer than input */ int Text::powerOfTwo(int input) { int value = 1; while ( value < input ) { value <<= 1; } return value; } //////////// /// FONT /// //////////// /** \brief constructs a Font \param fontFile the File to load the font from \param fontSize the Size of the Font in Pixels \param r Red value of the Font. \param g Green value of the Font. \param b Blue value of the Font. */ Font::Font(const char* fontFile, unsigned int fontSize, Uint8 r, Uint8 g, Uint8 b) { this->setClassID(CL_FONT, "Font"); // setting default values. this->font = NULL; this->fontFile = NULL; this->glyphArray = NULL; this->fastTextureID = 0; this->setSize(fontSize); this->setFont(fontFile); this->setStyle("c");//TTF_STYLE_NORMAL); this->setFastColor(r, g, b); this->fastTextureID = this->createFastTexture(); } /** \brief destructs a font */ Font::~Font(void) { // deleting the List of all Texts // deleting all Glyphs if (this->glyphArray) { for (int i = 0; i < FONT_HIGHEST_KNOWN_CHAR; i++) delete this->glyphArray[i]; delete []this->glyphArray; } // erease this font out of the memory. if (likely(this->font != NULL)) TTF_CloseFont(this->font); } /** \brief 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::setFont(const char* fontFile) { if (!this->fontFile) { this->setName(fontFile); this->fontFile = new char[strlen(fontFile)+1]; strcpy(this->fontFile, fontFile); this->font = TTF_OpenFont(this->fontFile, 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; } } /** \brief 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"); } /** \brief Sets a new Size to the font \param fontSize The new Size in pixels. */ void Font::setSize(unsigned int fontSize) { this->fontSize = fontSize; } /** \brief sets a new color to the font \param r Red \param g Green \param b Blue */ void Font::setFastColor(Uint8 r, Uint8 g, Uint8 b) { this->fastColor.r = r; this->fastColor.g = g; this->fastColor.b = b; } /** \returns the maximum height of the Font, if the font was initialized, 0 otherwise */ int Font::getMaxHeight(void) { 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(void) { 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(void) { 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; } GLuint Font::createFastTexture(void) { /* 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/3; //!< \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 <= 127; i++) { SDL_Surface* glyphSurf = NULL; Glyph* tmpGlyph; if (tmpGlyph = this->glyphArray[i]) { if (tmpGlyph->height > maxLineHeight) maxLineHeight = tmpGlyph->height; if (tmpRect.x+tmpGlyph->width > 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)) glyphSurf = TTF_RenderGlyph_Blended(this->font, i, this->fastColor); 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->width)/(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, 0); glTexCoord2f(tmpTexCoord.minU, tmpTexCoord.maxV); glVertex2d(0, tmpGlyph->height); glTexCoord2f(tmpTexCoord.maxU, tmpTexCoord.maxV); glVertex2d(tmpGlyph->width, tmpGlyph->height); glTexCoord2f(tmpTexCoord.maxU, tmpTexCoord.minV); glVertex2d(tmpGlyph->width, 0); glEnd(); glEndList(); SDL_FreeSurface(glyphSurf); tmpRect.x += tmpGlyph->width + 1; // Outputting Glyphs to BMP-files. /* char outname[64]; if (i < 10) sprintf( outname, "glyph-00%d.bmp", i ); else if (i <100) sprintf( outname, "glyph-0%d.bmp", i ); else sprintf( outname, "glyph-%d.bmp", 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; } /** \brief 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(void) { int i; int x,y; // the counters int maxLineHeight; int size = 32; // starting Value, we have to start somewhere 32 seems reasonable. bool sizeOK = false; Glyph* tmpGlyph; while (!sizeOK) { x = 0; y = 0; maxLineHeight = 0; for (i = 0; i < FONT_HIGHEST_KNOWN_CHAR; i++) { if(tmpGlyph = this->glyphArray[i]) { // getting the height of the highest Glyph in the Line. if (tmpGlyph->height > maxLineHeight) maxLineHeight = tmpGlyph->height; if (x + tmpGlyph->width > size) { x = 0; y = y + maxLineHeight; maxLineHeight = 0; } if (y + maxLineHeight + 1 > size) break; x += tmpGlyph->width + 1; } } if (i == FONT_HIGHEST_KNOWN_CHAR) sizeOK = true; else size *= 2; } return size; } /** \brief a simple function to get some interesting information about this class */ void Font::debug(void) { // 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"); } /////////////////// /// TEXT-ENGINE /// /////////////////// /** \brief standard constructor */ TextEngine::TextEngine () { this->setClassID(CL_TEXT_ENGINE, "TextEngine"); this->setName("TextEngine"); this->enableFonts(); this->textList = new tList; } /** \brief the singleton reference to this class */ TextEngine* TextEngine::singletonRef = NULL; /** \brief standard deconstructor */ TextEngine::~TextEngine () { this->disableFonts(); delete this->textList; TextEngine::singletonRef = NULL; } /** \brief function to enable TTF_Fonts */ void TextEngine::enableFonts(void) { if (!TTF_WasInit()) { if(TTF_Init()==-1) PRINTF(1)("TTF_Init: %s\n", TTF_GetError()); TextEngine::checkVersion(); } else PRINTF(4)("Fonts already initialized\n"); } /** \brief function to disable TTF_fonts */ void TextEngine::disableFonts(void) { if (TTF_WasInit()) { TTF_Quit(); } else PRINTF(4)("Fonts were not initialized.\n"); } /** \brief creates a new Text with a certain font. \see Font::Font \see Text::Text */ Text* TextEngine::createText(const char* fontFile, unsigned int fontSize, int textType, Uint8 r, Uint8 g, Uint8 b) { Font* tmpFont; Text* newText; Vector tmpVec; tmpVec = Vector(r, g, b); tmpFont = (Font*)ResourceManager::getInstance()->load(fontFile, TTF, RP_GAME, &fontSize, &tmpVec); if (!tmpFont) { PRINTF(2)("Font %s could not be loaded, probably file not found\n", fontFile); return NULL; } newText = new Text(tmpFont, TEXT_DYNAMIC); textList->add(newText); return newText; } /** \brief removes a Text from the List \param text: the text to delete this only ereases allocated memory, and removes the text The normal way to call it, is through "delete text;" So you do not have to concetn yourselves with this. */ void TextEngine::deleteText(Text* text) { ResourceManager::getInstance()->unload(text->font); textList->remove(text); } /** \brief deletes all the Text, and tries to delete all allocated fonts */ void TextEngine::flush(void) { tIterator* textIterator = textList->getIterator(); Text* text = textIterator->nextElement(); while( text != NULL) { delete text; text = textIterator->nextElement(); } delete textIterator; } /** \brief draws all the Texts that have been initialized */ void TextEngine::draw(void) const { // entering 3D-mode GraphicsEngine::enter2DMode(); // drawing all the texts tIterator* textIterator = textList->getIterator(); Text* text = textIterator->nextElement(); while( text != NULL) { text->draw(); text = textIterator->nextElement(); } delete textIterator; // retruning to the previous mode GraphicsEngine::leave2DMode(); } /** \brief outputs some nice Debug information \todo there should also be something outputted about Font */ void TextEngine::debug(void) const { PRINT(0)("+-------------------------------+\n"); PRINT(0)("+ TEXT ENGINE DEBUG INFORMATION +\n"); PRINT(0)("+-------------------------------+\n"); PRINT(0)("Reference: %p; Text Counts: %d\n", this, this->textList->getSize()); tIterator* textIterator = textList->getIterator(); Text* text = textIterator->nextElement(); while( text != NULL) { text->debug(); text = textIterator->nextElement(); } delete textIterator; PRINT(0)("+---------------------------TE--+\n"); } /** \brief checks if the compiled version and the local version of SDL_ttf match. \returns true if match, false otherwise */ bool TextEngine::checkVersion(void) { SDL_version compile_version; SDL_version link_version; TTF_VERSION(&compile_version); link_version = *TTF_Linked_Version(); if (compile_version.major == link_version.major && compile_version.minor == link_version.minor && compile_version.patch == link_version.patch) { return true; } else { PRINTF(2)("compiled with SDL_ttf version: %d.%d.%d\n", compile_version.major, compile_version.minor, compile_version.patch); PRINTF(2)("running with SDL_ttf version: %d.%d.%d\n", link_version.major, link_version.minor, link_version.patch); return false; } }