/****************************************************************************** * File - glText.cpp * Author - Joey Pollack * Date - 2021/09/07 (y/m/d) * Mod Date - 2021/09/07 (y/m/d) * Description - Used to load and manage fonts. Renders text using freetype * and openGL. ******************************************************************************/ #include "glText.h" #include #include #include #include #include FT_FREETYPE_H namespace lunarium { int glText::LoadFont(const char* fontFile, const char* name, float size, int weight) { if (!mTextShader.IsBuilt()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "Unable to load font because no text rendering shaders are loaded"); return -1; } FT_Library ft; if (FT_Init_FreeType(&ft)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "FREETYPE: Could not init FreeType Library"); return -1; } FT_Face face; if (FT_New_Face(ft, fontFile, 0, &face)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "FREETYPE: Failed to load font from file: %s", fontFile); return -1; } return CreateFont(name, ft, face, size, weight); } int glText::LoadFont(const unsigned char* fontData, int dataSize, const char* name, float size, int weight) { if (!mTextShader.IsBuilt()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "Unable to load font because no text rendering shaders are loaded"); return -1; } FT_Library ft; if (FT_Init_FreeType(&ft)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "FREETYPE: Could not init FreeType Library"); return -1; } FT_Face face; if (FT_New_Memory_Face(ft, fontData, dataSize, 0, &face)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "FREETYPE: Failed to load font from buffer. Font name: %s", name); return -1; } return CreateFont(name, ft, face, size, weight); } int glText::CreateFont(const char* name, FT_Library ft, FT_Face face, float size, int weight) { FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Disable byte-alignment restriction // NOTE: Testing code float sizes[128] = { 0.0f }; mFonts.push_back(Font()); int fontIdx = (int)(mFonts.size() - 1); mFonts.back().Name = name; for (GLubyte c = 0; c < 128; c++) { // Load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "FREETYTPE: Failed to load Glyph %c from font: %s", c, name); continue; } // Generate texture GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer ); // Set texture options glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Now store character for later use Character character = { texture, { (float)face->glyph->bitmap.width, (float)face->glyph->bitmap.rows }, glm::vec2((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top), (int)face->glyph->bitmap.rows - face->glyph->bitmap_top, (unsigned int)face->glyph->advance.x }; mFonts.back().CharSet.insert(std::pair(c, character)); // Find Max (positive) Down Shift if (character.DownShift > 0 && ((int)mFonts.back().MaxDownShift) < character.DownShift) { mFonts.back().MaxDownShift = character.DownShift; } // Determines the distance from the top down to the baseline if (character.Size.Height == character.Bearing.y && mFonts.back().MaxHeight < character.Size.Height) { mFonts.back().MaxHeight = (unsigned int)character.Size.Height; } } FT_Done_Face(face); FT_Done_FreeType(ft); return fontIdx; } OpRes glText::Initialize() { glGenVertexArrays(1, &mTextVAO); glGenBuffers(1, &mTextVBO); glBindVertexArray(mTextVAO); glBindBuffer(GL_ARRAY_BUFFER, mTextVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // Load text shaders mTextShader.SetSource(glShader::TYPE_VERTEX, VertShader, false); mTextShader.SetSource(glShader::TYPE_FRAGMENT, FragShader, false); if (!mTextShader.BuildProgram()) { return OpRes::Fail("Unable to build text shader program. Fonts will not load and text will not render."); } return OpRes::OK(); } void glText::DrawString(int fontID, const char* text, glm::vec2 position, Color color, float scale, glm::mat4 projection) { // HACK: There might be a better way to specify no limit to the bottom right DrawString(fontID, text, position, glm::vec2(9000000.0f, 9000000.0f), color, scale, projection); } void glText::DrawString(int fontID, const char* text, glm::vec2 topLeft, glm::vec2 botRight, Color color, float scale, glm::mat4 projection) { if (!mTextShader.IsBuilt()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "Unable to draw string because no text rendering shaders are loaded"); return; } if (fontID < 0 || fontID >= mFonts.size()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "Invalid font ID specified for DrawString: %d", fontID); return; } //glEnable(GL_BLEND); //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // NOTE: String drawing code based on code from: https://learnopengl.com/In-Practice/Text-Rendering mTextShader.MakeActive(); mTextShader.SetUniformMatrix("projection", 1, glm::value_ptr(projection)); mTextShader.SetUniformf("textColor", { color.Red, color.Green, color.Blue, color.Alpha }); glActiveTexture(GL_TEXTURE0); glBindVertexArray(mTextVAO); float lineSize = (mFonts[fontID].MaxHeight + mFonts[fontID].MaxDownShift) * scale; float curX = topLeft.x; float curY = topLeft.y; // Iterate through all characters int len = (int)strlen(text); for (int c = 0; c < len; c++) { // if the last character was a space then we must // be starting a new word if (c > 0 && ' ' == text[c - 1]) { // check the total advance of this word to see if // it would go beyond the right boundry GLfloat totalAdvance = 0; for (int i = c; text[i] != ' ' && text[i] != '\0'; i++) { Character ch = mFonts[fontID].CharSet[text[c]]; totalAdvance += (ch.Advance >> 6) * scale; } if (curX + totalAdvance > botRight.x) { curX = topLeft.x; curY += lineSize; } if (curY + lineSize > botRight.y) { break; } } Character ch = mFonts[fontID].CharSet[text[c]]; GLfloat xpos = curX + ch.Bearing.x * scale; GLfloat ypos = curY + (mFonts[fontID].MaxHeight - ch.Size.Height) * scale; // Apply a down offset if the char should go below the baseline ypos += (ch.DownShift * scale); // NOTE: The following code does not work because it assumes that positive y is the upward direction // //GLfloat test_ypos = curY + (ch.Size.Height - ch.Bearing.Y) * scale; // GLfloat w = ch.Size.Width * scale; GLfloat h = ch.Size.Height * scale; // Update VBO for each character GLfloat vertices[6][4] = { { xpos, ypos + h, 0.0, 1.0 }, { xpos + w, ypos, 1.0, 0.0 }, { xpos, ypos, 0.0, 0.0 }, { xpos, ypos + h, 0.0, 1.0 }, { xpos + w, ypos + h, 1.0, 1.0 }, { xpos + w, ypos, 1.0, 0.0 } }; // Render glyph texture over quad glBindTexture(GL_TEXTURE_2D, ch.TextureID); // Update content of VBO memory glBindBuffer(GL_ARRAY_BUFFER, mTextVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); // Render quad glDrawArrays(GL_TRIANGLES, 0, 6); // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) curX += (ch.Advance >> 6) * scale; // Bitshift by 6 to get value in pixels (2^6 = 64) } glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } }