#47 - FreeTypeFont C++ class for OpenGL
Date: 2019-02-09 12:00 - C++
Class to create a font to draw text using the freetype font library in C++ with OpenGL.
//FreeTypeFont.h
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include <memory>
#include <string>
#include <vector>
namespace YourNamespace {
class Shader;
class FreeTypeFont {
static YourNamespace::Shader fontShader;
static bool makeChar(FT_Face face, char ch, std::vector<float>& data, GLuint texId, int* advance);
static void updateMVP();
public:
static std::shared_ptr<FreeTypeFont> create(const std::string& fontFile, unsigned int fontHeight);
private:
GLuint* textures;
int* advanceX;
GLuint vaoId;
GLuint vboId;
int _height;
public:
void render(float x, float y, const std::string& text, glm::vec4& color = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
int length(const std::string& text);
int height() const { return _height; }
~FreeTypeFont();
};
}
//FreeTypeFont.cpp
#include "FreeTypeFont.h"
#include <glm/gtc/matrix_transform.hpp>
#include "Logger.h"
#include "Shader.h"
#include "Application.h"
#include "EventManager.h"
#include "Event.h"
#include "FTLibraryErrors.h"
using namespace YourNamespace;
inline int next_p2(int a) {
int rval = 1;
while (rval < a)
rval <<= 1;
return rval;
}
Shader FreeTypeFont::fontShader;
bool FreeTypeFont::makeChar(FT_Face face, char ch, std::vector<float>& data, GLuint texId, int* advance) {
FT_Error error = 0;
if (error = FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_FORCE_AUTOHINT)) {
LOG_ERROR("FT_Load_Glyph failed: " + FreeType::getError(error));
return false;
}
FT_Glyph glyph;
if (error = FT_Get_Glyph(face->glyph, &glyph)) {
LOG_ERROR("FT_Get_Glyph failed: " + FreeType::getError(error));
return false;
}
if (glyph->format != FT_GLYPH_FORMAT_BITMAP) {
if (error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
LOG_ERROR("FT_Glyph_To_Bitmap failed: " + FreeType::getError(error));
return false;
}
}
FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph;
FT_Bitmap& bitmap = bitmapGlyph->bitmap;
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
int width = next_p2( bitmap.width );
int height = next_p2( bitmap.rows );
GLubyte* expanded_data = new GLubyte[width * height];
for (int j = 0; j < height; j++) {
for(int i = 0; i < width; i++){
expanded_data[i+j*width] = (i>=bitmap.width || j>=bitmap.rows) ? 0 : bitmap.buffer[i + bitmap.width*j];
}
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, expanded_data);
delete[] expanded_data;
float x = (float)bitmap.width / (float)width;
float y = (float)bitmap.rows / (float)height;
float tx = (float)bitmapGlyph->left;
float ty = - (float)bitmapGlyph->top;
GLfloat vertexData[] = {
tx, ty, 0.0f, 0.0f,
tx, ty + (float)bitmap.rows, 0.0f, y,
tx + (float)bitmap.width, ty, x, 0.0f,
tx + (float)bitmap.width, ty + (float)bitmap.rows, x, y
};
int size = sizeof(vertexData) / sizeof(float);
for (int i = 0; i < size; i++)
data.push_back(vertexData[i]);
*(advance) = face->glyph->advance.x >> 6;
FT_Done_Glyph(glyph);
return true;
}
std::shared_ptr<FreeTypeFont> FreeTypeFont::create(const std::string& fontFile, unsigned int fontHeight) {
FT_Error error = 0;
FT_Library library;
if (error = FT_Init_FreeType(&library)) {
LOG_ERROR("Error initializing FreeType Library: " + FreeType::getError(error));
return nullptr;
}
FT_Face face;
if (error = FT_New_Face(library, fontFile.c_str(), 0, &face)) {
LOG_ERROR("Error loading font file: " + FreeType::getError(error));
FT_Done_FreeType(library);
return nullptr;
}
FT_Set_Char_Size(face, fontHeight << 6, fontHeight << 6, 96, 96);
GLuint * textures = new GLuint[128];
int* advanceX = new int[128];
GLuint vaoId, vboId;
std::vector<float> data;
glGenTextures(128, textures);
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
for(unsigned char i = 0; i < 128; i++) {
if (!makeChar(face, i, data, textures[i], &advanceX[i])) {
FT_Done_Face(face);
FT_Done_FreeType(library);
return nullptr;
}
}
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, (GLvoid*)8);
glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(float), (GLvoid*)(&(*data.begin())), GL_STATIC_DRAW);
FT_Done_Face(face);
FT_Done_FreeType(library);
if (!fontShader.compiled()) {
fontShader.setAttribLocation("vertexPosition", 0);
fontShader.setAttribLocation("vertexTexCoords", 1);
if (!fontShader.loadFromFile(GL_VERTEX_SHADER, "shaders/fontShader.vert") ||
!fontShader.loadFromFile(GL_FRAGMENT_SHADER, "shaders/fontShader.frag") ||
!fontShader.createLinkProgram()) {
LOG_FATAL("Failed to create shader for FreeTypeFont!");
throw std::runtime_error("Failed to create shader for FreeTypeFont!");
}
fontShader.use();
fontShader.uniform("tex0", 0);
fontShader.uniform("color", 1.0f, 0.0f, 0.0f, 1.0f);
updateMVP();
EventManager::default()->addHandler(HANDLER_FOR(WindowResize), [] (Event* ev) {
updateMVP();
});
}
std::shared_ptr<FreeTypeFont> font = std::make_shared<FreeTypeFont>();
font->textures = textures;
font->advanceX = advanceX;
font->vaoId = vaoId;
font->vboId = vboId;
font->_height = fontHeight;
return font;
}
void FreeTypeFont::updateMVP() {
if (!fontShader.compiled())
return;
glm::mat4 MVP = glm::ortho(0.0f, (float)Application::windowWidth(), (float)Application::windowHeight(), 0.0f);
fontShader.use();
fontShader.uniform("MVP", MVP);
}
void FreeTypeFont::render(float x, float y, const std::string& text, glm::vec4& color) {
glBindVertexArray(vaoId);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
fontShader.use();
fontShader.uniform("translate", x, y);
fontShader.uniform("color", color);
glActiveTexture(GL_TEXTURE0);
for (size_t i = 0; i < text.length(); i++) {
char ch = text[i];
glBindTexture(GL_TEXTURE_2D, textures[ch]);
glDrawArrays(GL_TRIANGLE_STRIP, ch*4, 4);
fontShader.uniform("translate", (float)(x += advanceX[ch]), y);
}
glBindTexture(GL_TEXTURE_2D, NULL);
glDisable(GL_BLEND);
}
int FreeTypeFont::length(const std::string& text) {
int k = 0;
for (size_t i = 0; i < text.length(); i++)
k += advanceX[text[i]];
return k;
}
FreeTypeFont::~FreeTypeFont() {
glDeleteTextures(128, textures);
delete[] textures;
delete[] advanceX;
}
// FTLibraryErrors.h
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include <string>
#include <vector>
#undef __FTERRORS_H__
#define FT_ERROR_START_LIST ;
#define FT_ERROR_END_LIST ;
#define FT_ERRORDEF(e, v, s) errors[v] = s;
class FTErrors {
public:
std::map<int, std::string> errors;
FTErrors() {
#include FT_ERRORS_H
}
};
namespace FreeType {
FTErrors ft_errors;
std::string getError(FT_Error error) {
return ft_errors.errors[error];
}
}