Download Font Texture
Download csv File
Graphics_Text.h
#ifndef GRAPHICS_TEXT_H
#define GRAPHICS_TEXT_H
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <list>
#include <string>
struct Text_Entry
{
std::string Text;
glm::vec2 UpperLeftBound, LowerRightBound;
glm::vec4 Color;
float Size;
};
namespace Graphics_Text
{
bool Initialize(const std::string& texture_file, const std::string& csv_file, GLuint texture_unit);
void CleanUp();
void Render(const std::list<Text_Entry>& text_entries);
};
#endif // GRAPHICS_TEXT_H
Graphics_Text.cpp
#include "Graphics_Text.h"
#include <SOIL.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>
#include <cstring>
#include "Shader.h"
#define GRAPHICS_TEXT_MAX_CHARS 1000
#define GRAPHICS_TEXT_MIN_FONTSIZE +0.01f
using namespace std;
using namespace glm;
namespace Graphics_Text
{
GLuint program = 0;
GLuint vertexshader = 0;
GLuint fragmentshader = 0;
GLuint vertexarray = 0;
GLuint vertexbuffer = 0;
GLuint texture = 0;
ivec2 texture_size(0, 0);
bool is_initialized = false;
struct Vertex_Font {
vec2 Position, TexCoord;
vec4 Color;
};
struct Character_Data {
vec2 TexCoord;
vec2 Size;
float Ratio;
} font_data[256];
struct Character {
vector<Vertex_Font> Vertices{ 6 };
Character(unsigned char c, const vec2& position, float fontsize, const vec4& color)
{
Vertices[0] = { position + vec2(0, -fontsize), font_data[c].TexCoord + vec2(0, font_data[c].Size.y), color };
Vertices[1] = { position + vec2(fontsize * font_data[c].Ratio, -fontsize), font_data[c].TexCoord + vec2(font_data[c].Size.x, font_data[c].Size.y), color };
Vertices[2] = { position + vec2(fontsize * font_data[c].Ratio, 0), font_data[c].TexCoord + vec2(font_data[c].Size.x, 0), color };
Vertices[3] = { position + vec2(0, -fontsize), font_data[c].TexCoord + vec2(0, font_data[c].Size.y), color };
Vertices[4] = { position + vec2(fontsize * font_data[c].Ratio, 0), font_data[c].TexCoord + vec2(font_data[c].Size.x, 0), color };
Vertices[5] = { position + vec2(0, 0), font_data[c].TexCoord + vec2(0, 0), color };
}
};
}
bool Graphics_Text::Initialize(const std::string& texture_file, const std::string& csv_file, GLuint texture_unit)
{
/* open text data file */
fstream f(csv_file, ios::in);
if (!f.good())
{
cout << "ERROR: " << __FILE__<< " " << __LINE__ << endl;
cout << "\tcant open file: " << csv_file << endl << endl;
return false;
}
/* parse text data file */
unsigned int index = 0;
unsigned int font_height = 0;
unsigned int font_width[256] = { 0 };
while (f.good())
{
string line;
getline(f, line);
size_t offset = line.find("Font Height,");
if (offset != string::npos)
{
offset += string("Font Height,").length();
string str_character_height = line.substr(offset, line.length() - offset);
font_height = strtoll(str_character_height.c_str(), 0, 10);
}
offset = line.find("Base Width,");
if (offset != string::npos)
{
offset += string("Base Width,").length();
string str_character_width = line.substr(offset, line.length() - offset);
font_width[index++] = strtoll(str_character_width.c_str(), 0, 10);
}
}
/* load font texture */
int texture_channels = 0;
unsigned char* image = SOIL_load_image(texture_file.c_str(), &texture_size.x, &texture_size.y, &texture_channels, 4);
if (!image)
{
cout << "ERROR: " << __FILE__<< " " << __LINE__ << endl;
cout << "\tcant load texture: " << texture_file << endl << endl;
return false;
}
/* generate character data */
for (unsigned int i = 0; i < 256; i++)
{
ivec2 location = ivec2(i % 16, i / 16);
font_data[i].TexCoord = vec2(location.x / 16.0f, location.y / 16.0f);
font_data[i].Size = vec2((float)font_width[i] / texture_size.x, (float)font_height / texture_size.y);
font_data[i].Ratio = (float)font_width[i] / font_height;
}
/* create all objects */
program = glCreateProgram();
vertexshader = glCreateShader(GL_VERTEX_SHADER);
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
glGenVertexArrays(1, &vertexarray);
glGenBuffers(1, &vertexbuffer);
glGenTextures(1, &texture);
/* setup font texture */
glBindTexture(GL_TEXTURE_2D, texture);
/* we only need it to check weather a texel is part of the character or not, so the internal format can be the smallest: 8 bits, 1 channel */
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, texture_size.x, texture_size.y);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture_size.x, texture_size.y, GL_RGBA, GL_UNSIGNED_BYTE, image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
/* setup vertex array */
glBindVertexArray(vertexarray);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex_Font), reinterpret_cast<GLvoid*>(offsetof(Vertex_Font, Position)));
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex_Font), reinterpret_cast<GLvoid*>(offsetof(Vertex_Font, Color)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
/* setup vertex buffer */
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex_Font) * GRAPHICS_TEXT_MAX_CHARS * 6, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
/* setup program */
string vertexshader_source = {
"#version 450 core\n"
"layout (location = 0, component = 0) in vec2 in_position;"
"layout (location = 0, component = 2) in vec2 in_texcoord;"
"layout (location = 1) in vec4 in_color;"
"out VS_FS {"
" smooth vec2 texcoord;"
" smooth vec4 color;"
"} vs_out;"
"void main() {"
" gl_Position = vec4(in_position, 0, 1);"
" vs_out.texcoord = in_texcoord;"
" vs_out.color = in_color;"
"}"
};
string fragmentshader_source = {
"#version 450 core\n"
"layout (location = 0) uniform sampler2D FontTexture;"
"in VS_FS {"
" smooth vec2 texcoord;"
" smooth vec4 color;"
"} fs_in;"
"layout (location = 0) out vec4 out_color;"
"void main() {"
/* check if this texel is part of the font */
" if (texture(FontTexture, fs_in.texcoord).r < 0.5f)"
" discard;"
" out_color = fs_in.color;"
"}"
};
is_initialized = true;
if (!CompileShader(vertexshader, vertexshader_source))
is_initialized = false;
if (!CompileShader(fragmentshader, fragmentshader_source))
is_initialized = false;
if (!LinkProgram(program, { vertexshader, fragmentshader }))
is_initialized = false;
/* bind texture */
if (is_initialized)
{
glProgramUniform1i(program, 0, texture_unit);
glBindTextureUnit(texture_unit, texture);
}
else
CleanUp();
/* successfully initialized */
return is_initialized;
}
void Graphics_Text::CleanUp()
{
is_initialized = false;
/* destroy objects */
glDeleteProgram(program);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteVertexArrays(1, &vertexarray);
glDeleteBuffers(1, &vertexbuffer);
glDeleteTextures(1, &texture);
program = 0;
vertexshader = 0;
fragmentshader = 0;
}
void Graphics_Text::Render(const std::list<Text_Entry>& text_entries)
{
if (!is_initialized)
return;
/* generate vertex data for all text entries */
vector<Vertex_Font> vertices;
vertices.reserve(GRAPHICS_TEXT_MAX_CHARS * 6);
/* for each text entry ... */
for (auto& entry : text_entries)
{
/* query text field, font color, font size */
vec2 base_position = entry.UpperLeftBound;
vec4 color = entry.Color;
const float font_size = entry.Size;
/* check entry */
if (entry.UpperLeftBound.x >= entry.LowerRightBound.x)
continue;
if (entry.UpperLeftBound.y <= entry.LowerRightBound.y)
continue;
if (font_size < GRAPHICS_TEXT_MIN_FONTSIZE)
continue;
vec2 nextposition = base_position;
for (unsigned int i = 0; i < entry.Text.length(); i++)
{
if ((vertices.size() + 6) > (GRAPHICS_TEXT_MAX_CHARS * 6))
break;
/* character */
unsigned char c = (unsigned char)entry.Text[i];
/* set position */
vec2 position = nextposition;
/* advance position right */
if (c == '\t')
{
nextposition.x += font_size * 2;
continue;
}
nextposition += vec2(font_size * font_data[c].Ratio, 0);
if (nextposition.y < entry.LowerRightBound.y)
break;
/* advance position down */
if (c == '\n')
{
nextposition.x = entry.UpperLeftBound.x;
nextposition.y -= font_size;
continue;
}
if (nextposition.x > entry.LowerRightBound.x)
{
nextposition.x = entry.UpperLeftBound.x;
nextposition.y -= font_size;
/* re-process this character */
i--;
continue;
}
Character ch(c, position, font_size, color);
vertices.insert(vertices.end(), ch.Vertices.begin(), ch.Vertices.end());
}
}
if (vertices.size() < 6)
return;
/* fill vertex buffer */
GLvoid* ptr_vertices = glMapNamedBuffer(vertexbuffer, GL_WRITE_ONLY);
if (!ptr_vertices)
return;
memcpy(ptr_vertices, vertices.data(), vertices.size() * sizeof(Vertex_Font));
glUnmapNamedBuffer(vertexbuffer);
/* disable depth test, culling, blending, etc ... temporarily */
GLboolean depthtest = GL_FALSE, culling = GL_FALSE, blending = GL_FALSE;
glGetBooleanv(GL_DEPTH_TEST, &depthtest);
glGetBooleanv(GL_CULL_FACE, &culling);
glGetBooleanv(GL_BLEND, &blending);
if (depthtest)
glDisable(GL_DEPTH_TEST);
if (culling)
glDisable(GL_CULL_FACE);
if (blending)
glDisable(GL_BLEND);
/* draw text */
glUseProgram(program);
glBindVertexArray(vertexarray);
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
glBindVertexArray(0);
glUseProgram(0);
/* re-enable depth test, culling, blending, etc ... if needed */
if (depthtest)
glEnable(GL_DEPTH_TEST);
if (culling)
glEnable(GL_CULL_FACE);
if (blending)
glEnable(GL_BLEND);
}