From a6dbaa2dea813f52bcdc334166f0e0a9dade8d2c Mon Sep 17 00:00:00 2001 From: Aloshi Date: Fri, 4 Oct 2013 18:24:41 -0500 Subject: [PATCH] Moved Font.h/.cpp to the "resources" directory (since it's a Reloadable). --- CMakeLists.txt | 4 +- src/Renderer_draw_gl.cpp | 2 +- src/Renderer_init.cpp | 2 +- src/Window.h | 2 +- src/components/ButtonComponent.h | 2 +- src/components/DateTimeComponent.h | 2 +- src/components/GuiDetectDevice.cpp | 2 +- src/components/GuiInputConfig.cpp | 2 +- src/components/OptionListComponent.h | 2 +- src/components/SwitchComponent.cpp | 2 +- src/components/TextComponent.h | 2 +- src/components/TextEditComponent.cpp | 2 +- src/components/TextListComponent.h | 2 +- src/components/ThemeComponent.h | 2 +- src/{ => resources}/Font.cpp | 1122 +++++++++++++------------- src/{ => resources}/Font.h | 242 +++--- 16 files changed, 697 insertions(+), 697 deletions(-) rename src/{ => resources}/Font.cpp (95%) rename src/{ => resources}/Font.h (94%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 766f14169..9cd614a12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,6 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FolderData.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Font.h ${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.h @@ -186,6 +185,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/TheArchiveScraper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugiconfig.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h ${CMAKE_CURRENT_SOURCE_DIR}/data/Resources.h @@ -193,7 +193,6 @@ set(ES_HEADERS set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FolderData.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Font.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.cpp @@ -240,6 +239,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBScraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/TheArchiveScraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp diff --git a/src/Renderer_draw_gl.cpp b/src/Renderer_draw_gl.cpp index d851129b2..1a6727961 100644 --- a/src/Renderer_draw_gl.cpp +++ b/src/Renderer_draw_gl.cpp @@ -2,7 +2,7 @@ #include "Renderer.h" #include GLHEADER #include -#include "Font.h" +#include "resources/Font.h" #include #include "Log.h" #include diff --git a/src/Renderer_init.cpp b/src/Renderer_init.cpp index 4158ba6cc..3b098baee 100644 --- a/src/Renderer_init.cpp +++ b/src/Renderer_init.cpp @@ -1,7 +1,7 @@ #include "Renderer.h" #include "platform.h" #include GLHEADER -#include "Font.h" +#include "resources/Font.h" namespace Renderer { diff --git a/src/Window.h b/src/Window.h index 610ca817b..d568bbda3 100644 --- a/src/Window.h +++ b/src/Window.h @@ -5,7 +5,7 @@ #include "InputManager.h" #include "resources/ResourceManager.h" #include -#include "Font.h" +#include "resources/Font.h" class Window { diff --git a/src/components/ButtonComponent.h b/src/components/ButtonComponent.h index b0651b271..5c135fc4a 100644 --- a/src/components/ButtonComponent.h +++ b/src/components/ButtonComponent.h @@ -2,7 +2,7 @@ #include "../GuiComponent.h" #include -#include "../Font.h" +#include "../resources/Font.h" #include "NinePatchComponent.h" class ButtonComponent : public GuiComponent diff --git a/src/components/DateTimeComponent.h b/src/components/DateTimeComponent.h index 1f5359978..451e9fd3b 100644 --- a/src/components/DateTimeComponent.h +++ b/src/components/DateTimeComponent.h @@ -2,7 +2,7 @@ #include "../GuiComponent.h" #include -#include "../Font.h" +#include "../resources/Font.h" class DateTimeComponent : public GuiComponent { diff --git a/src/components/GuiDetectDevice.cpp b/src/components/GuiDetectDevice.cpp index 79cf2c74b..8bd59400c 100644 --- a/src/components/GuiDetectDevice.cpp +++ b/src/components/GuiDetectDevice.cpp @@ -1,7 +1,7 @@ #include "GuiDetectDevice.h" #include "../Window.h" #include "../Renderer.h" -#include "../Font.h" +#include "../resources/Font.h" #include "GuiInputConfig.h" #include #include diff --git a/src/components/GuiInputConfig.cpp b/src/components/GuiInputConfig.cpp index 050a6122b..3d8439c8f 100644 --- a/src/components/GuiInputConfig.cpp +++ b/src/components/GuiInputConfig.cpp @@ -1,7 +1,7 @@ #include "GuiInputConfig.h" #include "../Window.h" #include "../Renderer.h" -#include "../Font.h" +#include "../resources/Font.h" #include "GuiGameList.h" #include "../Log.h" diff --git a/src/components/OptionListComponent.h b/src/components/OptionListComponent.h index f64392875..7e4956764 100644 --- a/src/components/OptionListComponent.h +++ b/src/components/OptionListComponent.h @@ -1,7 +1,7 @@ #pragma once #include "../GuiComponent.h" -#include "../Font.h" +#include "../resources/Font.h" #include #include #include "../Renderer.h" diff --git a/src/components/SwitchComponent.cpp b/src/components/SwitchComponent.cpp index 96ae1d0fb..dcffca3e5 100644 --- a/src/components/SwitchComponent.cpp +++ b/src/components/SwitchComponent.cpp @@ -1,6 +1,6 @@ #include "SwitchComponent.h" #include "../Renderer.h" -#include "../Font.h" +#include "../resources/Font.h" #include "../Window.h" SwitchComponent::SwitchComponent(Window* window, bool state) : GuiComponent(window), mState(state) diff --git a/src/components/TextComponent.h b/src/components/TextComponent.h index b828db803..641d5cb4a 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -2,7 +2,7 @@ #define _TEXTCOMPONENT_H_ #include "../GuiComponent.h" -#include "../Font.h" +#include "../resources/Font.h" class TextComponent : public GuiComponent { diff --git a/src/components/TextEditComponent.cpp b/src/components/TextEditComponent.cpp index 048a4ca03..f8b88ea98 100644 --- a/src/components/TextEditComponent.cpp +++ b/src/components/TextEditComponent.cpp @@ -1,6 +1,6 @@ #include "TextEditComponent.h" #include "../Log.h" -#include "../Font.h" +#include "../resources/Font.h" #include "../Window.h" #include "../Renderer.h" #include "ComponentListComponent.h" diff --git a/src/components/TextListComponent.h b/src/components/TextListComponent.h index 1c1f13f52..df9bb222d 100644 --- a/src/components/TextListComponent.h +++ b/src/components/TextListComponent.h @@ -2,7 +2,7 @@ #define _TEXTLISTCOMPONENT_H_ #include "../Renderer.h" -#include "../Font.h" +#include "../resources/Font.h" #include "../GuiComponent.h" #include "../InputManager.h" #include diff --git a/src/components/ThemeComponent.h b/src/components/ThemeComponent.h index e364ba3df..7a3057860 100644 --- a/src/components/ThemeComponent.h +++ b/src/components/ThemeComponent.h @@ -6,7 +6,7 @@ #include "../GuiComponent.h" #include "../pugiXML/pugixml.hpp" #include "../AudioManager.h" -#include "../Font.h" +#include "../resources/Font.h" //This class loads an XML-defined list of GuiComponents. class ThemeComponent : public GuiComponent diff --git a/src/Font.cpp b/src/resources/Font.cpp similarity index 95% rename from src/Font.cpp rename to src/resources/Font.cpp index b49b4fe1d..b463160b2 100644 --- a/src/Font.cpp +++ b/src/resources/Font.cpp @@ -1,561 +1,561 @@ -#include "Font.h" -#include -#include -#include -#include "Renderer.h" -#include -#include "Log.h" - -FT_Library Font::sLibrary; -bool Font::libraryInitialized = false; - -int Font::getDpiX() { return 96; } -int Font::getDpiY() { return 96; } - -int Font::getSize() const { return mSize; } - -std::map< std::pair, std::weak_ptr > Font::sFontMap; - -static std::string default_font_path = ""; - -std::string Font::getDefaultPath() -{ - if(!default_font_path.empty()) - return default_font_path; - - const int fontCount = 4; - -#ifdef WIN32 - std::string fonts[] = {"DejaVuSerif.ttf", - "Arial.ttf", - "Verdana.ttf", - "Tahoma.ttf" }; - - //build full font path - TCHAR winDir[MAX_PATH]; - GetWindowsDirectory(winDir, MAX_PATH); -#ifdef UNICODE - char winDirChar[MAX_PATH*2]; - char DefChar = ' '; - WideCharToMultiByte(CP_ACP, 0, winDir, -1, winDirChar, MAX_PATH, &DefChar, NULL); - std::string fontPath(winDirChar); -#else - std::string fontPath(winDir); -#endif - fontPath += "\\Fonts\\"; - //prepend to font file names - for(int i = 0; i < fontCount; i++) - { - fonts[i] = fontPath + fonts[i]; - } -#else - std::string fonts[] = {"/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf", - "/usr/share/fonts/TTF/DejaVuSerif.ttf", - "/usr/share/fonts/dejavu/DejaVuSerif.ttf", - "font.ttf" }; -#endif - - for(int i = 0; i < fontCount; i++) - { - if(boost::filesystem::exists(fonts[i])) - { - default_font_path = fonts[i]; - return fonts[i]; - } - } - - LOG(LogError) << "Error - could not find the default font!"; - - return ""; -} - -void Font::initLibrary() -{ - if(FT_Init_FreeType(&sLibrary)) - { - LOG(LogError) << "Error initializing FreeType!"; - }else{ - libraryInitialized = true; - } -} - -Font::Font(int size, const std::string& path) : fontScale(1.0f), mSize(size), mPath(path) -{ - reload(ResourceManager::getInstance()); -} - -Font::~Font() -{ - deinit(); -} - -void Font::reload(std::shared_ptr& rm) -{ - init(rm->getFileData(mPath)); -} - -void Font::unload(std::shared_ptr& rm) -{ - deinit(); -} - -std::shared_ptr Font::get(int size, const std::string& path) -{ - if(path.empty()) - { - LOG(LogError) << "Tried to get font with no path!"; - return std::shared_ptr(); - } - - std::pair def(path, size); - auto foundFont = sFontMap.find(def); - if(foundFont != sFontMap.end()) - { - if(!foundFont->second.expired()) - return foundFont->second.lock(); - } - - std::shared_ptr font = std::shared_ptr(new Font(size, path)); - sFontMap[def] = std::weak_ptr(font); - ResourceManager::getInstance()->addReloadable(font); - return font; -} - -void Font::init(ResourceData data) -{ - if(!libraryInitialized) - initLibrary(); - - mMaxGlyphHeight = 0; - - buildAtlas(data); -} - -void Font::deinit() -{ - if(textureID) - { - glDeleteTextures(1, &textureID); - textureID = 0; - } -} - -void Font::buildAtlas(ResourceData data) -{ - if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face)) - { - LOG(LogError) << "Error creating font face!"; - return; - } - - //FT_Set_Char_Size(face, 0, size * 64, getDpiX(), getDpiY()); - FT_Set_Pixel_Sizes(face, 0, mSize); - - //find the size we should use - FT_GlyphSlot g = face->glyph; - int w = 0; - int h = 0; - - /*for(int i = 32; i < 128; i++) - { - if(FT_Load_Char(face, i, FT_LOAD_RENDER)) - { - fprintf(stderr, "Loading character %c failed!\n", i); - continue; - } - - w += g->bitmap.width; - h = std::max(h, g->bitmap.rows); - }*/ - - //the max size (GL_MAX_TEXTURE_SIZE) is like 3300 - w = 2048; - h = 512; - - textureWidth = w; - textureHeight = h; - - //create the texture - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_2D, textureID); - - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); - - //copy the glyphs into the texture - int x = 0; - int y = 0; - int maxHeight = 0; - for(int i = 32; i < 128; i++) - { - if(FT_Load_Char(face, i, FT_LOAD_RENDER)) - continue; - - //prints rendered texture to the console - /*std::cout << "uploading at x: " << x << ", w: " << g->bitmap.width << " h: " << g->bitmap.rows << "\n"; - - for(int k = 0; k < g->bitmap.rows; k++) - { - for(int j = 0; j < g->bitmap.width; j++) - { - if(g->bitmap.buffer[g->bitmap.width * k + j]) - std::cout << "."; - else - std::cout << " "; - } - std::cout << "\n"; - }*/ - - if(x + g->bitmap.width >= textureWidth) - { - x = 0; - y += maxHeight + 1; //leave one pixel of space between glyphs - maxHeight = 0; - } - - if(g->bitmap.rows > maxHeight) - maxHeight = g->bitmap.rows; - - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); - - - charData[i].texX = x; - charData[i].texY = y; - charData[i].texW = g->bitmap.width; - charData[i].texH = g->bitmap.rows; - charData[i].advX = (float)g->metrics.horiAdvance / 64.0f; - charData[i].advY = (float)g->metrics.vertAdvance / 64.0f; - charData[i].bearingX = (float)g->metrics.horiBearingX / 64.0f; - charData[i].bearingY = (float)g->metrics.horiBearingY / 64.0f; - - if(charData[i].texH > mMaxGlyphHeight) - mMaxGlyphHeight = charData[i].texH; - - x += g->bitmap.width + 1; //leave one pixel of space between glyphs - } - - glBindTexture(GL_TEXTURE_2D, 0); - - FT_Done_Face(face); - - if((y + maxHeight) >= textureHeight) - { - //failed to create a proper font texture - LOG(LogWarning) << "Font \"" << mPath << "\" with size " << mSize << " exceeded max texture size! Trying again..."; - //try a 3/4th smaller size and redo initialization - fontScale *= 1.25f; - mSize = (int)(mSize * (1.0f / fontScale)); - deinit(); - init(data); - } -} - - -void Font::drawText(std::string text, const Eigen::Vector2f& offset, unsigned int color) -{ - TextCache* cache = buildTextCache(text, offset[0], offset[1], color); - renderTextCache(cache); - delete cache; -} - -void Font::renderTextCache(TextCache* cache) -{ - if(!textureID) - { - LOG(LogError) << "Error - tried to draw with Font that has no texture loaded!"; - return; - } - - if(cache == NULL) - { - LOG(LogError) << "Attempted to draw NULL TextCache!"; - return; - } - - glBindTexture(GL_TEXTURE_2D, textureID); - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - glVertexPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].pos); - glTexCoordPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].tex); - glColorPointer(4, GL_UNSIGNED_BYTE, 0, cache->colors); - - glDrawArrays(GL_TRIANGLES, 0, cache->vertCount); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); -} - -Eigen::Vector2f Font::sizeText(std::string text) const -{ - float lineWidth = 0.0f; - float highestWidth = 0.0f; - - float y = (float)getHeight(); - - for(unsigned int i = 0; i < text.length(); i++) - { - unsigned char letter = text[i]; - - if(letter == '\n') - { - if(lineWidth > highestWidth) - highestWidth = lineWidth; - - lineWidth = 0.0f; - y += getHeight(); - } - - if(letter < 32 || letter >= 128) - letter = 127; - - lineWidth += charData[letter].advX * fontScale; - } - - if(lineWidth > highestWidth) - highestWidth = lineWidth; - - return Eigen::Vector2f(highestWidth, y); -} - -int Font::getHeight() const -{ - return (int)(mMaxGlyphHeight * 1.5f * fontScale); -} - - -void Font::drawCenteredText(std::string text, float xOffset, float y, unsigned int color) -{ - Eigen::Vector2f pos = sizeText(text); - - pos[0] = (Renderer::getScreenWidth() - pos.x()); - pos[0] = (pos.x() / 2) + (xOffset / 2); - pos[1] = y; - - drawText(text, pos, color); -} - -//this could probably be optimized -//draws text and ensures it's never longer than xLen -void Font::drawWrappedText(std::string text, const Eigen::Vector2f& offset, float xLen, unsigned int color) -{ - text = wrapText(text, xLen); - drawText(text, offset, color); -} - -//the worst algorithm ever written -//breaks up a normal string with newlines to make it fit xLen -std::string Font::wrapText(std::string text, float xLen) const -{ - std::string out; - - std::string line, word, temp; - size_t space, newline; - - Eigen::Vector2f textSize; - - while(text.length() > 0 || !line.empty()) //while there's text or we still have text to render - { - space = text.find(' ', 0); - if(space == std::string::npos) - space = text.length() - 1; - - word = text.substr(0, space + 1); - - //check if the next word contains a newline - newline = word.find('\n', 0); - if(newline != std::string::npos) - { - //get everything up to the newline - word = word.substr(0, newline); - text.erase(0, newline + 1); - }else{ - text.erase(0, space + 1); - } - - temp = line + word; - - textSize = sizeText(temp); - - //if we're on the last word and it'll fit on the line, just add it to the line - if((textSize.x() <= xLen && text.length() == 0) || newline != std::string::npos) - { - line = temp; - word = ""; - } - - //if the next line will be too long or we're on the last of the text, render it - if(textSize.x() > xLen || text.length() == 0 || newline != std::string::npos) - { - //output line now - out += line + '\n'; - - //move the word we skipped to the next line - line = word; - }else{ - //there's still space, continue building the line - line = temp; - } - } - - if(!out.empty() && newline == std::string::npos) //chop off the last newline if we added one - out.erase(out.length() - 1, 1); - - return out; -} - -Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen) const -{ - text = wrapText(text, xLen); - return sizeText(text); -} - -Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, int cursor) const -{ - std::string wrappedText = wrapText(text, xLen); - - float lineWidth = 0.0f; - float y = 0.0f; - - unsigned int stop = (unsigned int)cursor; - unsigned int wrapOffset = 0; - for(unsigned int i = 0; i < stop; i++) - { - unsigned char wrappedLetter = wrappedText[i + wrapOffset]; - unsigned char letter = text[i]; - - if(wrappedLetter == '\n' && letter != '\n') - { - //this is where the wordwrap inserted a newline - //reset lineWidth and increment y, but don't consume a cursor character - lineWidth = 0.0f; - y += getHeight(); - - wrapOffset++; - i--; - continue; - } - - if(letter == '\n') - { - lineWidth = 0.0f; - y += getHeight(); - continue; - } - - if(letter < 32 || letter >= 128) - letter = 127; - - lineWidth += charData[letter].advX * fontScale; - } - - return Eigen::Vector2f(lineWidth, y); -} - -//============================================================================================================= -//TextCache -//============================================================================================================= - -TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color) -{ - if(!textureID) - { - LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!"; - return NULL; - } - - const int triCount = text.length() * 2; - const int vertCount = triCount * 3; - TextCache::Vertex* vert = new TextCache::Vertex[vertCount]; - GLubyte* colors = new GLubyte[vertCount * 4]; - - //texture atlas width/height - float tw = (float)textureWidth; - float th = (float)textureHeight; - - float x = offsetX; - float y = offsetY + mMaxGlyphHeight * 1.1f * fontScale; //padding (another 0.5% is added to the bottom through the sizeText function) - - int charNum = 0; - for(int i = 0; i < vertCount; i += 6, charNum++) - { - unsigned char letter = text[charNum]; - - if(letter == '\n') - { - y += (float)getHeight(); - x = offsetX; - memset(&vert[i], 0, 6 * sizeof(TextCache::Vertex)); - continue; - } - - if(letter < 32 || letter >= 128) - letter = 127; //print [X] if character is not standard ASCII - - //the glyph might not start at the cursor position, but needs to be shifted a bit - const float glyphStartX = x + charData[letter].bearingX * fontScale; - //order is bottom left, top right, top left - vert[i + 0].pos << glyphStartX, y + (charData[letter].texH - charData[letter].bearingY) * fontScale; - vert[i + 1].pos << glyphStartX + charData[letter].texW * fontScale, y - charData[letter].bearingY * fontScale; - vert[i + 2].pos << glyphStartX, vert[i + 1].pos.y(); - - Eigen::Vector2i charTexCoord(charData[letter].texX, charData[letter].texY); - Eigen::Vector2i charTexSize(charData[letter].texW, charData[letter].texH); - - vert[i + 0].tex << charTexCoord.x() / tw, (charTexCoord.y() + charTexSize.y()) / th; - vert[i + 1].tex << (charTexCoord.x() + charTexSize.x()) / tw, charTexCoord.y() / th; - vert[i + 2].tex << vert[i + 0].tex.x(), vert[i + 1].tex.y(); - - //next triangle (second half of the quad) - vert[i + 3].pos = vert[i + 0].pos; - vert[i + 4].pos = vert[i + 1].pos; - vert[i + 5].pos[0] = vert[i + 1].pos.x(); - vert[i + 5].pos[1] = vert[i + 0].pos.y(); - - vert[i + 3].tex = vert[i + 0].tex; - vert[i + 4].tex = vert[i + 1].tex; - vert[i + 5].tex[0] = vert[i + 1].tex.x(); - vert[i + 5].tex[1] = vert[i + 0].tex.y(); - - x += charData[letter].advX * fontScale; - } - - TextCache::CacheMetrics metrics = { sizeText(text) }; - TextCache* cache = new TextCache(vertCount, vert, colors, metrics); - if(color != 0x00000000) - cache->setColor(color); - - return cache; -} - -TextCache::TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m) : vertCount(verts), verts(v), colors(c), metrics(m) -{ -} - -TextCache::~TextCache() -{ - delete[] verts; - delete[] colors; -} - -void TextCache::setColor(unsigned int color) -{ - Renderer::buildGLColorArray(const_cast(colors), color, vertCount); -} +#include "Font.h" +#include +#include +#include +#include "../Renderer.h" +#include +#include "../Log.h" + +FT_Library Font::sLibrary; +bool Font::libraryInitialized = false; + +int Font::getDpiX() { return 96; } +int Font::getDpiY() { return 96; } + +int Font::getSize() const { return mSize; } + +std::map< std::pair, std::weak_ptr > Font::sFontMap; + +static std::string default_font_path = ""; + +std::string Font::getDefaultPath() +{ + if(!default_font_path.empty()) + return default_font_path; + + const int fontCount = 4; + +#ifdef WIN32 + std::string fonts[] = {"DejaVuSerif.ttf", + "Arial.ttf", + "Verdana.ttf", + "Tahoma.ttf" }; + + //build full font path + TCHAR winDir[MAX_PATH]; + GetWindowsDirectory(winDir, MAX_PATH); +#ifdef UNICODE + char winDirChar[MAX_PATH*2]; + char DefChar = ' '; + WideCharToMultiByte(CP_ACP, 0, winDir, -1, winDirChar, MAX_PATH, &DefChar, NULL); + std::string fontPath(winDirChar); +#else + std::string fontPath(winDir); +#endif + fontPath += "\\Fonts\\"; + //prepend to font file names + for(int i = 0; i < fontCount; i++) + { + fonts[i] = fontPath + fonts[i]; + } +#else + std::string fonts[] = {"/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf", + "/usr/share/fonts/TTF/DejaVuSerif.ttf", + "/usr/share/fonts/dejavu/DejaVuSerif.ttf", + "font.ttf" }; +#endif + + for(int i = 0; i < fontCount; i++) + { + if(boost::filesystem::exists(fonts[i])) + { + default_font_path = fonts[i]; + return fonts[i]; + } + } + + LOG(LogError) << "Error - could not find the default font!"; + + return ""; +} + +void Font::initLibrary() +{ + if(FT_Init_FreeType(&sLibrary)) + { + LOG(LogError) << "Error initializing FreeType!"; + }else{ + libraryInitialized = true; + } +} + +Font::Font(int size, const std::string& path) : fontScale(1.0f), mSize(size), mPath(path) +{ + reload(ResourceManager::getInstance()); +} + +Font::~Font() +{ + deinit(); +} + +void Font::reload(std::shared_ptr& rm) +{ + init(rm->getFileData(mPath)); +} + +void Font::unload(std::shared_ptr& rm) +{ + deinit(); +} + +std::shared_ptr Font::get(int size, const std::string& path) +{ + if(path.empty()) + { + LOG(LogError) << "Tried to get font with no path!"; + return std::shared_ptr(); + } + + std::pair def(path, size); + auto foundFont = sFontMap.find(def); + if(foundFont != sFontMap.end()) + { + if(!foundFont->second.expired()) + return foundFont->second.lock(); + } + + std::shared_ptr font = std::shared_ptr(new Font(size, path)); + sFontMap[def] = std::weak_ptr(font); + ResourceManager::getInstance()->addReloadable(font); + return font; +} + +void Font::init(ResourceData data) +{ + if(!libraryInitialized) + initLibrary(); + + mMaxGlyphHeight = 0; + + buildAtlas(data); +} + +void Font::deinit() +{ + if(textureID) + { + glDeleteTextures(1, &textureID); + textureID = 0; + } +} + +void Font::buildAtlas(ResourceData data) +{ + if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face)) + { + LOG(LogError) << "Error creating font face!"; + return; + } + + //FT_Set_Char_Size(face, 0, size * 64, getDpiX(), getDpiY()); + FT_Set_Pixel_Sizes(face, 0, mSize); + + //find the size we should use + FT_GlyphSlot g = face->glyph; + int w = 0; + int h = 0; + + /*for(int i = 32; i < 128; i++) + { + if(FT_Load_Char(face, i, FT_LOAD_RENDER)) + { + fprintf(stderr, "Loading character %c failed!\n", i); + continue; + } + + w += g->bitmap.width; + h = std::max(h, g->bitmap.rows); + }*/ + + //the max size (GL_MAX_TEXTURE_SIZE) is like 3300 + w = 2048; + h = 512; + + textureWidth = w; + textureHeight = h; + + //create the texture + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); + + //copy the glyphs into the texture + int x = 0; + int y = 0; + int maxHeight = 0; + for(int i = 32; i < 128; i++) + { + if(FT_Load_Char(face, i, FT_LOAD_RENDER)) + continue; + + //prints rendered texture to the console + /*std::cout << "uploading at x: " << x << ", w: " << g->bitmap.width << " h: " << g->bitmap.rows << "\n"; + + for(int k = 0; k < g->bitmap.rows; k++) + { + for(int j = 0; j < g->bitmap.width; j++) + { + if(g->bitmap.buffer[g->bitmap.width * k + j]) + std::cout << "."; + else + std::cout << " "; + } + std::cout << "\n"; + }*/ + + if(x + g->bitmap.width >= textureWidth) + { + x = 0; + y += maxHeight + 1; //leave one pixel of space between glyphs + maxHeight = 0; + } + + if(g->bitmap.rows > maxHeight) + maxHeight = g->bitmap.rows; + + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); + + + charData[i].texX = x; + charData[i].texY = y; + charData[i].texW = g->bitmap.width; + charData[i].texH = g->bitmap.rows; + charData[i].advX = (float)g->metrics.horiAdvance / 64.0f; + charData[i].advY = (float)g->metrics.vertAdvance / 64.0f; + charData[i].bearingX = (float)g->metrics.horiBearingX / 64.0f; + charData[i].bearingY = (float)g->metrics.horiBearingY / 64.0f; + + if(charData[i].texH > mMaxGlyphHeight) + mMaxGlyphHeight = charData[i].texH; + + x += g->bitmap.width + 1; //leave one pixel of space between glyphs + } + + glBindTexture(GL_TEXTURE_2D, 0); + + FT_Done_Face(face); + + if((y + maxHeight) >= textureHeight) + { + //failed to create a proper font texture + LOG(LogWarning) << "Font \"" << mPath << "\" with size " << mSize << " exceeded max texture size! Trying again..."; + //try a 3/4th smaller size and redo initialization + fontScale *= 1.25f; + mSize = (int)(mSize * (1.0f / fontScale)); + deinit(); + init(data); + } +} + + +void Font::drawText(std::string text, const Eigen::Vector2f& offset, unsigned int color) +{ + TextCache* cache = buildTextCache(text, offset[0], offset[1], color); + renderTextCache(cache); + delete cache; +} + +void Font::renderTextCache(TextCache* cache) +{ + if(!textureID) + { + LOG(LogError) << "Error - tried to draw with Font that has no texture loaded!"; + return; + } + + if(cache == NULL) + { + LOG(LogError) << "Attempted to draw NULL TextCache!"; + return; + } + + glBindTexture(GL_TEXTURE_2D, textureID); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].pos); + glTexCoordPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].tex); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, cache->colors); + + glDrawArrays(GL_TRIANGLES, 0, cache->vertCount); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); +} + +Eigen::Vector2f Font::sizeText(std::string text) const +{ + float lineWidth = 0.0f; + float highestWidth = 0.0f; + + float y = (float)getHeight(); + + for(unsigned int i = 0; i < text.length(); i++) + { + unsigned char letter = text[i]; + + if(letter == '\n') + { + if(lineWidth > highestWidth) + highestWidth = lineWidth; + + lineWidth = 0.0f; + y += getHeight(); + } + + if(letter < 32 || letter >= 128) + letter = 127; + + lineWidth += charData[letter].advX * fontScale; + } + + if(lineWidth > highestWidth) + highestWidth = lineWidth; + + return Eigen::Vector2f(highestWidth, y); +} + +int Font::getHeight() const +{ + return (int)(mMaxGlyphHeight * 1.5f * fontScale); +} + + +void Font::drawCenteredText(std::string text, float xOffset, float y, unsigned int color) +{ + Eigen::Vector2f pos = sizeText(text); + + pos[0] = (Renderer::getScreenWidth() - pos.x()); + pos[0] = (pos.x() / 2) + (xOffset / 2); + pos[1] = y; + + drawText(text, pos, color); +} + +//this could probably be optimized +//draws text and ensures it's never longer than xLen +void Font::drawWrappedText(std::string text, const Eigen::Vector2f& offset, float xLen, unsigned int color) +{ + text = wrapText(text, xLen); + drawText(text, offset, color); +} + +//the worst algorithm ever written +//breaks up a normal string with newlines to make it fit xLen +std::string Font::wrapText(std::string text, float xLen) const +{ + std::string out; + + std::string line, word, temp; + size_t space, newline; + + Eigen::Vector2f textSize; + + while(text.length() > 0 || !line.empty()) //while there's text or we still have text to render + { + space = text.find(' ', 0); + if(space == std::string::npos) + space = text.length() - 1; + + word = text.substr(0, space + 1); + + //check if the next word contains a newline + newline = word.find('\n', 0); + if(newline != std::string::npos) + { + //get everything up to the newline + word = word.substr(0, newline); + text.erase(0, newline + 1); + }else{ + text.erase(0, space + 1); + } + + temp = line + word; + + textSize = sizeText(temp); + + //if we're on the last word and it'll fit on the line, just add it to the line + if((textSize.x() <= xLen && text.length() == 0) || newline != std::string::npos) + { + line = temp; + word = ""; + } + + //if the next line will be too long or we're on the last of the text, render it + if(textSize.x() > xLen || text.length() == 0 || newline != std::string::npos) + { + //output line now + out += line + '\n'; + + //move the word we skipped to the next line + line = word; + }else{ + //there's still space, continue building the line + line = temp; + } + } + + if(!out.empty() && newline == std::string::npos) //chop off the last newline if we added one + out.erase(out.length() - 1, 1); + + return out; +} + +Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen) const +{ + text = wrapText(text, xLen); + return sizeText(text); +} + +Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, int cursor) const +{ + std::string wrappedText = wrapText(text, xLen); + + float lineWidth = 0.0f; + float y = 0.0f; + + unsigned int stop = (unsigned int)cursor; + unsigned int wrapOffset = 0; + for(unsigned int i = 0; i < stop; i++) + { + unsigned char wrappedLetter = wrappedText[i + wrapOffset]; + unsigned char letter = text[i]; + + if(wrappedLetter == '\n' && letter != '\n') + { + //this is where the wordwrap inserted a newline + //reset lineWidth and increment y, but don't consume a cursor character + lineWidth = 0.0f; + y += getHeight(); + + wrapOffset++; + i--; + continue; + } + + if(letter == '\n') + { + lineWidth = 0.0f; + y += getHeight(); + continue; + } + + if(letter < 32 || letter >= 128) + letter = 127; + + lineWidth += charData[letter].advX * fontScale; + } + + return Eigen::Vector2f(lineWidth, y); +} + +//============================================================================================================= +//TextCache +//============================================================================================================= + +TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color) +{ + if(!textureID) + { + LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!"; + return NULL; + } + + const int triCount = text.length() * 2; + const int vertCount = triCount * 3; + TextCache::Vertex* vert = new TextCache::Vertex[vertCount]; + GLubyte* colors = new GLubyte[vertCount * 4]; + + //texture atlas width/height + float tw = (float)textureWidth; + float th = (float)textureHeight; + + float x = offsetX; + float y = offsetY + mMaxGlyphHeight * 1.1f * fontScale; //padding (another 0.5% is added to the bottom through the sizeText function) + + int charNum = 0; + for(int i = 0; i < vertCount; i += 6, charNum++) + { + unsigned char letter = text[charNum]; + + if(letter == '\n') + { + y += (float)getHeight(); + x = offsetX; + memset(&vert[i], 0, 6 * sizeof(TextCache::Vertex)); + continue; + } + + if(letter < 32 || letter >= 128) + letter = 127; //print [X] if character is not standard ASCII + + //the glyph might not start at the cursor position, but needs to be shifted a bit + const float glyphStartX = x + charData[letter].bearingX * fontScale; + //order is bottom left, top right, top left + vert[i + 0].pos << glyphStartX, y + (charData[letter].texH - charData[letter].bearingY) * fontScale; + vert[i + 1].pos << glyphStartX + charData[letter].texW * fontScale, y - charData[letter].bearingY * fontScale; + vert[i + 2].pos << glyphStartX, vert[i + 1].pos.y(); + + Eigen::Vector2i charTexCoord(charData[letter].texX, charData[letter].texY); + Eigen::Vector2i charTexSize(charData[letter].texW, charData[letter].texH); + + vert[i + 0].tex << charTexCoord.x() / tw, (charTexCoord.y() + charTexSize.y()) / th; + vert[i + 1].tex << (charTexCoord.x() + charTexSize.x()) / tw, charTexCoord.y() / th; + vert[i + 2].tex << vert[i + 0].tex.x(), vert[i + 1].tex.y(); + + //next triangle (second half of the quad) + vert[i + 3].pos = vert[i + 0].pos; + vert[i + 4].pos = vert[i + 1].pos; + vert[i + 5].pos[0] = vert[i + 1].pos.x(); + vert[i + 5].pos[1] = vert[i + 0].pos.y(); + + vert[i + 3].tex = vert[i + 0].tex; + vert[i + 4].tex = vert[i + 1].tex; + vert[i + 5].tex[0] = vert[i + 1].tex.x(); + vert[i + 5].tex[1] = vert[i + 0].tex.y(); + + x += charData[letter].advX * fontScale; + } + + TextCache::CacheMetrics metrics = { sizeText(text) }; + TextCache* cache = new TextCache(vertCount, vert, colors, metrics); + if(color != 0x00000000) + cache->setColor(color); + + return cache; +} + +TextCache::TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m) : vertCount(verts), verts(v), colors(c), metrics(m) +{ +} + +TextCache::~TextCache() +{ + delete[] verts; + delete[] colors; +} + +void TextCache::setColor(unsigned int color) +{ + Renderer::buildGLColorArray(const_cast(colors), color, vertCount); +} diff --git a/src/Font.h b/src/resources/Font.h similarity index 94% rename from src/Font.h rename to src/resources/Font.h index 9298b3195..d04f25083 100644 --- a/src/Font.h +++ b/src/resources/Font.h @@ -1,121 +1,121 @@ -#ifndef _FONT_H_ -#define _FONT_H_ - -#include -#include "platform.h" -#include GLHEADER -#include -#include FT_FREETYPE_H -#include -#include "resources/ResourceManager.h" - -class TextCache; - -#define FONT_SIZE_SMALL ((unsigned int)(0.035f * Renderer::getScreenHeight())) -#define FONT_SIZE_MEDIUM ((unsigned int)(0.045f * Renderer::getScreenHeight())) -#define FONT_SIZE_LARGE ((unsigned int)(0.1f * Renderer::getScreenHeight())) - -//A TrueType Font renderer that uses FreeType and OpenGL. -//The library is automatically initialized when it's needed. -class Font : public IReloadable -{ -public: - static void initLibrary(); - - static std::shared_ptr get(int size, const std::string& path = getDefaultPath()); - - ~Font(); - - FT_Face face; - - //contains sizing information for every glyph. - struct charPosData { - int texX; - int texY; - int texW; - int texH; - - float advX; //!& rm) override; - void reload(std::shared_ptr& rm) override; - - int getSize() const; - - static std::string getDefaultPath(); -private: - static int getDpiX(); - static int getDpiY(); - - static FT_Library sLibrary; - static bool libraryInitialized; - - static std::map< std::pair, std::weak_ptr > sFontMap; - - Font(int size, const std::string& path); - - void init(ResourceData data); - void deinit(); - - void buildAtlas(ResourceData data); //Builds a "texture atlas," one big OpenGL texture with glyphs 32 to 128. - - int textureWidth; //OpenGL texture width - int textureHeight; //OpenGL texture height - int mMaxGlyphHeight; - float fontScale; //! 1.0 if the font would be to big for the texture - - int mSize; - const std::string mPath; -}; - -class TextCache -{ -public: - struct Vertex - { - Eigen::Vector2f pos; - Eigen::Vector2f tex; - }; - - struct CacheMetrics - { - Eigen::Vector2f size; - } metrics; - - void setColor(unsigned int color); - - TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m); - ~TextCache(); - - int vertCount; - Vertex* verts; - GLubyte* colors; -}; - -#endif +#ifndef _FONT_H_ +#define _FONT_H_ + +#include +#include "../platform.h" +#include GLHEADER +#include +#include FT_FREETYPE_H +#include +#include "ResourceManager.h" + +class TextCache; + +#define FONT_SIZE_SMALL ((unsigned int)(0.035f * Renderer::getScreenHeight())) +#define FONT_SIZE_MEDIUM ((unsigned int)(0.045f * Renderer::getScreenHeight())) +#define FONT_SIZE_LARGE ((unsigned int)(0.1f * Renderer::getScreenHeight())) + +//A TrueType Font renderer that uses FreeType and OpenGL. +//The library is automatically initialized when it's needed. +class Font : public IReloadable +{ +public: + static void initLibrary(); + + static std::shared_ptr get(int size, const std::string& path = getDefaultPath()); + + ~Font(); + + FT_Face face; + + //contains sizing information for every glyph. + struct charPosData { + int texX; + int texY; + int texW; + int texH; + + float advX; //!& rm) override; + void reload(std::shared_ptr& rm) override; + + int getSize() const; + + static std::string getDefaultPath(); +private: + static int getDpiX(); + static int getDpiY(); + + static FT_Library sLibrary; + static bool libraryInitialized; + + static std::map< std::pair, std::weak_ptr > sFontMap; + + Font(int size, const std::string& path); + + void init(ResourceData data); + void deinit(); + + void buildAtlas(ResourceData data); //Builds a "texture atlas," one big OpenGL texture with glyphs 32 to 128. + + int textureWidth; //OpenGL texture width + int textureHeight; //OpenGL texture height + int mMaxGlyphHeight; + float fontScale; //! 1.0 if the font would be to big for the texture + + int mSize; + const std::string mPath; +}; + +class TextCache +{ +public: + struct Vertex + { + Eigen::Vector2f pos; + Eigen::Vector2f tex; + }; + + struct CacheMetrics + { + Eigen::Vector2f size; + } metrics; + + void setColor(unsigned int color); + + TextCache(int verts, Vertex* v, GLubyte* c, const CacheMetrics& m); + ~TextCache(); + + int vertCount; + Vertex* verts; + GLubyte* colors; +}; + +#endif