From 5ca1c67cba93ec93afeec600f50ac0386039c600 Mon Sep 17 00:00:00 2001 From: James Turk Date: Sun, 3 Jul 2005 05:20:49 +0000 Subject: [PATCH] post GLFT_Font integration, pre ResourceManagement revisions --- include/video/Font.hpp | 56 ++++++- include/video/FontResourceManager.hpp | 31 +++- include/video/Texture.hpp | 4 +- src/video/Font.cpp | 165 +++++++++++++++++++-- src/video/FontResourceManager.cpp | 206 ++++++++++++++++++++++++-- src/video/VideoCore.cpp | 4 +- test/Font_test.cpp | 19 +-- 7 files changed, 428 insertions(+), 57 deletions(-) diff --git a/include/video/Font.hpp b/include/video/Font.hpp index 2a0bc24..1896224 100644 --- a/include/video/Font.hpp +++ b/include/video/Font.hpp @@ -5,7 +5,7 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: Font.hpp,v 1.1 2005/06/29 04:30:40 cozman Exp $ +// $Id: Font.hpp,v 1.2 2005/07/03 05:20:49 cozman Exp $ #ifndef PHOTON_VIDEO_FONT_HPP #define PHOTON_VIDEO_FONT_HPP @@ -18,6 +18,9 @@ namespace photon namespace video { +class StreamFlusher { }; +std::ostream& operator<<(std::ostream& os, const StreamFlusher& rhs); + // Class: Font // Simple OO wrapper around a TrueType font that can be drawn to textures and // rendered via OpenGL. @@ -31,11 +34,31 @@ namespace video // - ostream& << Font class Font: public ResourceManaged { + +// Resource Creation +public: + // Function: addResource + // Define a new named font resource, works just like + // except that it takes a size for the font. + // + // Parameters: + // name - Name to give to font resource. + // path - Path of font file. + // size - Point size for the font. + static void addResource(const std::string& name, const std::string& path, + uint size); + + // Function: addResource + // Define a new unaliased font resource (name == path). Works just like + // except that it takes a size for the font. + // + // Parameters:. + // path - Path of font file. + // size - Point size for the font. + static void addResource(const std::string& path, uint size); // Group: (Con/De)structors public: - - // Function: Font // Default constructor, initalizes internal state of Font. Font(); @@ -63,21 +86,40 @@ public: // Function: open // Opens an TrueType font. // - // Loading is done via FTGL. + // Loading is done via FreeType. // // Parameters: // name - Name of the Font to open. void open(const std::string& name); + + bool isValid() const; Font& operator=(const Font &rhs); operator bool() const; -// Group: Writing +// Group: Drawing public: - void write(const std::string& str); + void drawText(float x, float y, const char *str, ...) const; + void drawText(float x, float y, const std::string& str) const; + + std::ostream& beginDraw(float x, float y); + StreamFlusher endDraw(); + +// Group: Font Metrics +public: + unsigned int calcStringWidth(const std::string& str) const; + unsigned int getHeight() const; private: - FTFont* font_; + // font data + uint texID_; + uint listBase_; + std::vector widths_; + ubyte height_; + // stream drawing stuff + std::ostringstream ss_; + float drawX_; + float drawY_; }; } diff --git a/include/video/FontResourceManager.hpp b/include/video/FontResourceManager.hpp index 4b9d1b1..3bf0de9 100644 --- a/include/video/FontResourceManager.hpp +++ b/include/video/FontResourceManager.hpp @@ -5,15 +5,15 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: FontResourceManager.hpp,v 1.1 2005/06/29 04:30:40 cozman Exp $ +// $Id: FontResourceManager.hpp,v 1.2 2005/07/03 05:20:49 cozman Exp $ #ifndef PHOTON_VIDEO_FONTRESOURCEMANAGER_HPP #define PHOTON_VIDEO_FONTRESOURCEMANAGER_HPP #include "ResourceManager.hpp" -#include "FTGL/FTGL.h" -#include "FTGL/FTFont.h" +#include +#include FT_FREETYPE_H namespace photon { @@ -23,17 +23,38 @@ namespace video class FontResource : public Resource { public: - FTFont* font; + uint texID; + uint listBase; + std::vector widths; + ubyte height; }; class FontResourceManager : public ResourceManager { public: - void getFontData(const std::string& name, FTFont*& font); + FontResourceManager(); + ~FontResourceManager(); + + void getFontData(const std::string& name, uint& texID, uint& listBase, + std::vector& widths, ubyte& height); private: + + // defined but not implemented virtual void loadResource(FontResource &res, const std::string& name); + + FontResource newResource(const std::string& name, const std::string& path, + uint size); + + virtual void loadResource(FontResource &res, const std::string& name, + uint size); virtual void freeResource(FontResource &res); + +private: + FT_Library library_; +public: + static const unsigned int SPACE = 32; + static const unsigned int NUM_CHARS = 96; }; } diff --git a/include/video/Texture.hpp b/include/video/Texture.hpp index 4801b63..f7e0626 100644 --- a/include/video/Texture.hpp +++ b/include/video/Texture.hpp @@ -5,7 +5,7 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: Texture.hpp,v 1.2 2005/06/13 07:05:28 cozman Exp $ +// $Id: Texture.hpp,v 1.3 2005/07/03 05:20:49 cozman Exp $ #ifndef PHOTON_VIDEO_TEXTURE_HPP #define PHOTON_VIDEO_TEXTURE_HPP @@ -31,7 +31,7 @@ namespace video // - Texture = Texture // - bool : True if texture is loaded, false if not. // - ostream& << Texture -class Texture: public ResourceManaged +class Texture : public ResourceManaged { // Group: (Con/De)structors diff --git a/src/video/Font.cpp b/src/video/Font.cpp index 441d3b0..7423480 100644 --- a/src/video/Font.cpp +++ b/src/video/Font.cpp @@ -5,26 +5,53 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: Font.cpp,v 1.1 2005/06/29 04:30:40 cozman Exp $ +// $Id: Font.cpp,v 1.2 2005/07/03 05:20:49 cozman Exp $ #include "video/Font.hpp" +#include +#include +#include +#include +#include namespace photon { namespace video { - -Font::Font() -{ } - -Font::Font(const Font &rhs) : - ResourceManaged(rhs) + +std::ostream& operator<<(std::ostream& os, const StreamFlusher& rhs) { - resMgr_.getFontData(getName(), font_); + return os.flush(); } -Font::Font(const std::string& name) +void Font::addResource(const std::string& name, const std::string& path, + uint size) +{ + resMgr_.newResource(name,path,size); +} + +void Font::addResource(const std::string& path, uint size) +{ + resMgr_.newResource(path,path,size); +} + +Font::Font() : + texID_(0), listBase_(0), // initalize GL variables to zero + widths_(FontResourceManager::NUM_CHARS), // make room for 96 widths + height_(0), drawX_(0), drawY_(0) +{ +} + +Font::Font(const Font &rhs) : + ResourceManaged(rhs), + drawX_(0), drawY_(0) +{ + resMgr_.getFontData(getName(), texID_, listBase_, widths_, height_); +} + +Font::Font(const std::string& name) : + drawX_(0), drawY_(0) { open(name); } @@ -32,7 +59,12 @@ Font::Font(const std::string& name) void Font::open(const std::string& name) { ResourceManaged::open(name); - resMgr_.getFontData(getName(), font_); + resMgr_.getFontData(getName(), texID_, listBase_, widths_, height_); +} + +bool Font::isValid() const +{ + return glIsTexture(texID_) == GL_TRUE; } Font& Font::operator=(const Font &rhs) @@ -40,19 +72,124 @@ Font& Font::operator=(const Font &rhs) if(&rhs != this) { ResourceManaged::operator=(rhs); - resMgr_.getFontData(getName(), font_); + resMgr_.getFontData(getName(), texID_, listBase_, widths_, height_); } return *this; } Font::operator bool() const { - return font_ != 0; + return isValid(); } -void Font::write(const std::string& str) +void Font::drawText(float x, float y, const char *str, ...) const { - font_->Render(str.c_str()); + if(!isValid()) + { + throw PreconditionException("Invalid Font::drawText call."); + } + + std::va_list args; + char buf[1024]; + + va_start(args,str); + std::vsnprintf(buf, 1024, str, args); // avoid buffer overflow + va_end(args); + + glBindTexture(GL_TEXTURE_2D, texID_); + glPushMatrix(); + glTranslated(x,y,0); + for(unsigned int i=0; i < std::strlen(buf); ++i) + { + // ch-SPACE = DisplayList offset + unsigned char ch( buf[i] - FontResourceManager::SPACE ); + // replace characters outside the valid range with undrawable + if(ch > FontResourceManager::NUM_CHARS) + { + // last character is 'undrawable' + ch = FontResourceManager::NUM_CHARS-1; + } + glCallList(listBase_+ch); // calculate list to call + } + + // Alternative, ignores undrawables (no noticable speed difference) + //glListBase(listBase_-32); + //glCallLists(static_cast(std::strlen(buf)), GL_UNSIGNED_BYTE, buf); + + glPopMatrix(); +} + +void Font::drawText(float x, float y, const std::string& str) const +{ + if(!isValid()) + { + throw PreconditionException("Invalid Font::drawText call."); + } + + glBindTexture(GL_TEXTURE_2D, texID_); + glPushMatrix(); + glTranslated(x,y,0); + for(std::string::const_iterator i = str.begin(); i != str.end(); ++i) + { + // ch-SPACE = DisplayList offset + unsigned char ch( *i - FontResourceManager::SPACE ); + // replace characters outside the valid range with undrawable + if(ch > FontResourceManager::NUM_CHARS) + { + // last character is 'undrawable' + ch = FontResourceManager::NUM_CHARS-1; + } + glCallList(listBase_+ch); // calculate list to call + } + + // Alternative, ignores undrawables (no noticable speed difference) + //glListBase(listBase_-32); + //glCallLists(static_cast(std::strlen(buf)), GL_UNSIGNED_BYTE, buf); + + glPopMatrix(); +} + +std::ostream& Font::beginDraw(float x, float y) +{ + // clear the string and store the draw-position + ss_.str(""); + drawX_ = x; + drawY_ = y; + return ss_; +} + +StreamFlusher Font::endDraw() +{ + drawText(drawX_, drawY_, ss_.str()); // draw the string + ss_.str(""); // clear the buffer + return StreamFlusher(); +} + +unsigned int Font::calcStringWidth(const std::string& str) const +{ + if(!isValid()) + { + throw PreconditionException("Invalid Font::calcStringWidth call."); + } + unsigned int width=0; + + // iterate through widths of each char and accumulate width of string + for(std::string::const_iterator i = str.begin(); i < str.end(); ++i) + { + width += widths_[static_cast(*i) - + FontResourceManager::SPACE]; + } + + return width; +} + +unsigned int Font::getHeight() const +{ + if(!isValid()) + { + throw PreconditionException("Invalid Font::getHeight call."); + } + return height_; } } diff --git a/src/video/FontResourceManager.cpp b/src/video/FontResourceManager.cpp index c3eba3f..d0cd6f1 100644 --- a/src/video/FontResourceManager.cpp +++ b/src/video/FontResourceManager.cpp @@ -5,47 +5,227 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: FontResourceManager.cpp,v 1.1 2005/06/29 04:30:40 cozman Exp $ +// $Id: FontResourceManager.cpp,v 1.2 2005/07/03 05:20:49 cozman Exp $ #include "video/FontResourceManager.hpp" #include "util/FileBuffer.hpp" -#include "FTGL/FTGLTextureFont.h" - - namespace photon { namespace video { + +FontResourceManager::FontResourceManager() +{ + if(FT_Init_FreeType(&library_) != 0) + { + throw APIError("Could not initialize FreeType2 library."); + } +} + +FontResourceManager::~FontResourceManager() +{ + FT_Done_FreeType(library_); +} -void FontResourceManager::getFontData(const std::string& name, FTFont*& font) +void FontResourceManager::getFontData(const std::string& name, uint& texID, + uint& listBase, + std::vector& widths, + ubyte& height) { FontResource resource( getResource(name) ); - font = resource.font; + texID = resource.texID; + listBase = resource.listBase; + widths = resource.widths; + height = resource.height; } void FontResourceManager::loadResource(FontResource &res, const std::string& path) { - util::FileBuffer buf(path); + throw Error("loadResource(FontResource&, const std::string& is undefined " + " for Font. A size must be specified."); +} + +FontResource FontResourceManager::newResource(const std::string& name, + const std::string& path, + uint size) +{ + FontResource resource; + resource.name = name; + resource.path = path; + + try + { + // attempt to load + loadResource(resource, path, size); + } + catch(ResourceException& e) + { + // rethrow any exceptions with specific information + throw ResourceException("Could not load " + path + " as " + name + + ": " + e.getDesc()); + } + + resourceMap_[name] = resource; // add the resource to resourceMap +} + +void FontResourceManager::loadResource(FontResource &res, + const std::string& path, uint size) +{ + const size_t MARGIN = 3; + res.widths.resize(NUM_CHARS); + + // Step 1: Open the font using FreeType // + util::FileBuffer buf(path); std::vector data = buf.getData(); - //res.font = new FTGLTextureFont((ubyte*)&data[0],data.size()); - res.font = new FTGLTextureFont("/usr/share/fonts/truetype/freefont/FreeMono.ttf"); + FT_Face face; - if(!res.font || res.font->Error() != 0) + if(FT_New_Memory_Face(library_, (ubyte*)&data[0], data.size(), 0, &face) + != 0) { - throw APIError("Failed to create FTGLTextureFont"); + throw APIError("Could not load font file."); + } + + // Abort if this is not a scalable font. + if(!(face->face_flags & FT_FACE_FLAG_SCALABLE) || + !(face->face_flags & FT_FACE_FLAG_HORIZONTAL)) + { + throw ResourceException("Invalid font: Error setting font size."); } - assert( res.font->FaceSize(6) ); + // Set the font size + FT_Set_Pixel_Sizes(face, size, 0); + + // Step 2: Find maxAscent/Descent to calculate imageHeight // + size_t imageHeight = 0; + size_t imageWidth = 256; + int maxDescent = 0; + int maxAscent = 0; + size_t lineSpace = imageWidth - MARGIN; + size_t lines = 1; + size_t charIndex; + + for(unsigned int ch = 0; ch < NUM_CHARS; ++ch) + { + // Look up the character in the font file. + charIndex = FT_Get_Char_Index(face, ch+SPACE); + + // Render the current glyph. + FT_Load_Glyph(face, charIndex, FT_LOAD_RENDER); + + res.widths[ch] = (face->glyph->metrics.horiAdvance >> 6) + MARGIN; + // If the line is full go to the next line + if(res.widths[ch] > lineSpace) + { + lineSpace = imageWidth - MARGIN; + ++lines; + } + lineSpace -= res.widths[ch]; + + maxAscent = std::max(face->glyph->bitmap_top, maxAscent); + maxDescent = std::max(face->glyph->bitmap.rows - + face->glyph->bitmap_top, maxDescent); + } + + res.height = maxAscent + maxDescent; // calculate height_ for text + + // Compute how high the texture has to be. + size_t neededHeight = (maxAscent + maxDescent + MARGIN) * lines + MARGIN; + // Get the first power of two in which it will fit + imageHeight = 16; + while(imageHeight < neededHeight) + { + imageHeight <<= 1; + } + + // Step 3: Generation of the actual texture // + + // create and zero the memory + unsigned char* image = new unsigned char[imageHeight * imageWidth]; + std::memset(image, 0, imageHeight * imageWidth); + + // These are the position at which to draw the next glyph + size_t x = MARGIN; + size_t y = MARGIN + maxAscent; + float texX1, texX2, texY1, texY2; // used for display list + + res.listBase = glGenLists(NUM_CHARS); // generate the lists for filling + + // Drawing loop + for(unsigned int ch = 0; ch < NUM_CHARS; ++ch) + { + size_t charIndex = FT_Get_Char_Index(face, ch+SPACE); + + // Render the glyph + FT_Load_Glyph(face, charIndex, FT_LOAD_DEFAULT); + FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + + // See whether the character fits on the current line + if(res.widths[ch] > imageWidth - x) + { + x = MARGIN; + y += (maxAscent + maxDescent + MARGIN); + } + + // calculate texture coordinates of the character + texX1 = static_cast(x) / imageWidth; + texX2 = static_cast(x+res.widths[ch]) / imageWidth; + texY1 = static_cast(y - maxAscent) / imageHeight; + texY2 = texY1 + static_cast(res.height) / imageHeight; + + // generate the character's display list + glNewList(res.listBase + ch, GL_COMPILE); + glBegin(GL_QUADS); + glTexCoord2f(texX1,texY1); glVertex2i(0, 0); + glTexCoord2f(texX2,texY1); glVertex2i(res.widths[ch], 0); + glTexCoord2f(texX2,texY2); glVertex2i(res.widths[ch], res.height); + glTexCoord2f(texX1,texY2); glVertex2i(0, res.height); + glEnd(); + glTranslatef(res.widths[ch],0,0); // translate forward + glEndList(); + + // copy image generated by FreeType to the texture + for(int row = 0; row < face->glyph->bitmap.rows; ++row) + { + for(int pixel = 0; pixel < face->glyph->bitmap.width; ++pixel) + { + // set pixel at position to intensity (0-255) at the position + image[(x + face->glyph->bitmap_left + pixel) + + (y - face->glyph->bitmap_top + row) * imageWidth] = + face->glyph->bitmap.buffer[pixel + + row * face->glyph->bitmap.pitch]; + } + } + + x += res.widths[ch]; + } + + // generate the OpenGL texture from the byte array + glGenTextures(1, &res.texID); + glBindTexture(GL_TEXTURE_2D, res.texID); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, imageWidth, imageHeight, 0, + GL_ALPHA, GL_UNSIGNED_BYTE, image); + + delete[] image; // now done with the image memory + FT_Done_Face(face); // free the face data } void FontResourceManager::freeResource(FontResource &res) { - delete res.font; + if(glIsList(res.listBase)) + { + glDeleteLists(res.listBase, NUM_CHARS); + } + if(glIsTexture(res.texID)) + { + glDeleteTextures(1, &res.texID); + } } } diff --git a/src/video/VideoCore.cpp b/src/video/VideoCore.cpp index 32defe5..e3732d9 100644 --- a/src/video/VideoCore.cpp +++ b/src/video/VideoCore.cpp @@ -5,7 +5,7 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: VideoCore.cpp,v 1.6 2005/06/29 04:30:40 cozman Exp $ +// $Id: VideoCore.cpp,v 1.7 2005/07/03 05:20:49 cozman Exp $ #include "video/VideoCore.hpp" @@ -13,7 +13,6 @@ #include "GL/gl.h" #include "GL/glu.h" -#include "FTGL/FTLibrary.h" namespace photon { @@ -25,7 +24,6 @@ VideoCore::VideoCore() : viewportWidth_(0), viewportHeight_(0) { initOpenGL(); - FTLibrary::Instance().Error(); // ignoring error, fix this } VideoCore::~VideoCore() diff --git a/test/Font_test.cpp b/test/Font_test.cpp index 2c76cfb..3b410e0 100644 --- a/test/Font_test.cpp +++ b/test/Font_test.cpp @@ -5,7 +5,7 @@ // James Turk (jpt2433@rit.edu) // // Version: -// $Id: Font_test.cpp,v 1.1 2005/06/29 04:30:40 cozman Exp $ +// $Id: Font_test.cpp,v 1.2 2005/07/03 05:20:49 cozman Exp $ #include "photon.hpp" using namespace photon; @@ -34,7 +34,7 @@ public: font.open("font"); } - + void update() { static double t=0; @@ -48,17 +48,10 @@ public: video.clear(); - glColor4ub(255,0,0,255); - glEnable(GL_TEXTURE_2D); - glScaled(1.0/.75,1,1); - font.write("he"); - glBegin(GL_QUADS); - glTexCoord2f(0,0); glVertex2f(150,200); - glTexCoord2f(1,0); glVertex2f(300,350); - glTexCoord2f(1,1); glVertex2f(150,300); - glTexCoord2f(0,1); glVertex2f(200,350); - glEnd(); - glColor4ub(255,255,255,255); + font.drawText(0, 0, "Photon"); + font.drawText(font.calcStringWidth("Photon"), font.getHeight(), + "FPS: %.0f", app.getFramerate() ); + font.beginDraw(200, 200) << "another font" << font.endDraw(); } private: