Text Renderer is properly joining all of the character pixel data into one long texture! The characters are aligned to the top of the frame (which I think is correct for calculating the uvs for each char)
parent
777b4bd2f3
commit
a3a89a291a
@ -0,0 +1,230 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File - text_renderer.h
|
||||||
|
* Author - Joey Pollack
|
||||||
|
* Date - 2022/08/15 (y/m/d)
|
||||||
|
* Mod Date - 2022/08/15 (y/m/d)
|
||||||
|
* Description - manage fonts and render text
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "text_renderer.h"
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
|
namespace lunarium
|
||||||
|
{
|
||||||
|
OpRes TextRenderer::Initialize()
|
||||||
|
{
|
||||||
|
if (FT_Init_FreeType(&mFTHandle))
|
||||||
|
{
|
||||||
|
return OpRes::Fail("FREETYPE: Could not init FreeType Library");
|
||||||
|
}
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
int TextRenderer::LoadFont(const char* fontFile, const char* name, float size, int weight)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!mFTHandle)
|
||||||
|
{
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "Cannot load font: Freetype handle is invalid");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Face face;
|
||||||
|
if (FT_New_Face(mFTHandle, fontFile, 0, &face))
|
||||||
|
{
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "FREETYPE: Failed to load font from file: %s", fontFile);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateFont(name, face, size, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TextRenderer::LoadFont(const unsigned char* fontData, int dataSize, const char* name, float size, int weight)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!mFTHandle)
|
||||||
|
{
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "Cannot load font: Freetype handle is invalid");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Face face;
|
||||||
|
if (FT_New_Memory_Face(mFTHandle, fontData, dataSize, 0, &face))
|
||||||
|
{
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "FREETYPE: Failed to load font from buffer. Font name: %s", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateFont(name, face, size, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TextRenderer::CreateFont(const char* name, 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);
|
||||||
|
Font& f = mFonts.back();
|
||||||
|
mFonts.back().Name = name;
|
||||||
|
|
||||||
|
u32 max_width = 0;
|
||||||
|
u32 max_height = 0;
|
||||||
|
|
||||||
|
for (u8 c = 0; c < 128; c++)
|
||||||
|
{
|
||||||
|
// Load character glyph
|
||||||
|
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
|
||||||
|
{
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "FREETYTPE: Failed to load Glyph %c from font: %s", c, name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 width = face->glyph->bitmap.width;
|
||||||
|
u32 height = face->glyph->bitmap.rows;
|
||||||
|
u8* pixel_data = new u8[width * height];
|
||||||
|
memcpy(pixel_data, face->glyph->bitmap.buffer, width * height);
|
||||||
|
|
||||||
|
Character character = Character
|
||||||
|
{
|
||||||
|
c, pixel_data, 0, 0, Sizeu { width, height },
|
||||||
|
glm::vec2((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top), // bearing
|
||||||
|
(int)face->glyph->bitmap.rows - face->glyph->bitmap_top, // downshift
|
||||||
|
(unsigned int)face->glyph->advance.x // advance
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
mFonts.back().CharSet.insert(std::pair<u8, Character>(c, character));
|
||||||
|
|
||||||
|
if (width > max_width)
|
||||||
|
{
|
||||||
|
max_width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height > max_height)
|
||||||
|
{
|
||||||
|
max_height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the font texture together
|
||||||
|
u32 single_row_width = f.CharSet.size() * max_width;
|
||||||
|
if (single_row_width > Texture::GetMaxSize())
|
||||||
|
{
|
||||||
|
FreeTempPixelData(f.CharSet);
|
||||||
|
mFonts.pop_back();
|
||||||
|
Logger::Error(LogCategory::GRAPHICS, "Failed to create font texture: texture width (%d) is larger than max texture size: (%d)", single_row_width, Texture::GetMaxSize());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create buffer of the correct size (single row texture for now - this may be improved later)
|
||||||
|
// u32 structured_width = max_width * f.CharSet.size();
|
||||||
|
// u32 structured_height = max_height;
|
||||||
|
// u8** structured_buffer = new u8*[structured_height];
|
||||||
|
|
||||||
|
// for (int i = 0; i < structured_height; i++)
|
||||||
|
// {
|
||||||
|
// structured_buffer[i] = new u8[structured_width];
|
||||||
|
// memset(structured_buffer[i], 0, structured_width);
|
||||||
|
|
||||||
|
// // Copy the current row for each char into the buffer
|
||||||
|
// u8* index = structured_buffer[i];
|
||||||
|
// for (auto iter = f.CharSet.begin(); iter != f.CharSet.end(); iter++)
|
||||||
|
// {
|
||||||
|
// memcpy(index, &iter->second.PixelDataBuffer[iter->second.Size.Width * i], iter->second.Size.Width);
|
||||||
|
// index += max_width;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
u8* texture_buffer = new u8[single_row_width * max_height];
|
||||||
|
memset(texture_buffer, 0, single_row_width * max_height);
|
||||||
|
|
||||||
|
// Copy the pixel data into the proper spot in the buffer
|
||||||
|
u8* buffer_index = texture_buffer;
|
||||||
|
for (int i = 0; i < max_height; i++)
|
||||||
|
{
|
||||||
|
for (auto iter = f.CharSet.begin(); iter != f.CharSet.end(); iter++)
|
||||||
|
{
|
||||||
|
if (i < iter->second.Size.Height)
|
||||||
|
{
|
||||||
|
memcpy(buffer_index, &iter->second.PixelDataBuffer[iter->second.Size.Width * i], iter->second.Size.Width);
|
||||||
|
}
|
||||||
|
buffer_index += max_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeTempPixelData(f.CharSet);
|
||||||
|
|
||||||
|
f.FontTexture = Texture::Create(texture_buffer, single_row_width, max_height, TextureFormat::RED, true);
|
||||||
|
mDebugTexture = f.FontTexture;
|
||||||
|
|
||||||
|
delete[] texture_buffer;
|
||||||
|
|
||||||
|
// DEBUG STUFF:
|
||||||
|
// Character* c = &mFonts.back().CharSet['a'];
|
||||||
|
// mDebugTexture = Texture::Create(c->PixelDataBuffer, c->Size.Width, c->Size.Height, TextureFormat::RED, true);
|
||||||
|
|
||||||
|
FT_Done_Face(face);
|
||||||
|
|
||||||
|
return fontIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TextRenderer::FreeTempPixelData(std::map<u8, Character>& char_set)
|
||||||
|
{
|
||||||
|
for (auto iter = char_set.begin(); iter != char_set.end(); iter++)
|
||||||
|
{
|
||||||
|
delete[] iter->second.PixelDataBuffer;
|
||||||
|
iter->second.PixelDataBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// DEBUG DEBUG DEBUG
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Texture* TextRenderer::GetDebugTexture()
|
||||||
|
{
|
||||||
|
return mDebugTexture;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File - text_renderer.h
|
||||||
|
* Author - Joey Pollack
|
||||||
|
* Date - 2022/08/15 (y/m/d)
|
||||||
|
* Mod Date - 2022/08/15 (y/m/d)
|
||||||
|
* Description - manage fonts and render text
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LUNARIUM_TEXT_RENDERER_H_
|
||||||
|
#define LUNARIUM_TEXT_RENDERER_H_
|
||||||
|
|
||||||
|
#include <core/common_defs.h>
|
||||||
|
#include <utils/op_res.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
struct FT_LibraryRec_;
|
||||||
|
typedef struct FT_LibraryRec_ * FT_Library;
|
||||||
|
|
||||||
|
struct FT_FaceRec_;
|
||||||
|
typedef struct FT_FaceRec_* FT_Face;
|
||||||
|
|
||||||
|
namespace lunarium
|
||||||
|
{
|
||||||
|
class Texture;
|
||||||
|
class TextRenderer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend class Renderer2D;
|
||||||
|
OpRes Initialize();
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
Texture* GetDebugTexture();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
int LoadFont(const char* fontFile, const char* name, float size = 12.0f, int weight = 400);
|
||||||
|
int LoadFont(const unsigned char* fontData, int dataSize, const char* name, float size = 12.0f, int weight = 400);
|
||||||
|
|
||||||
|
private: // HELPERS
|
||||||
|
int CreateFont(const char* name, FT_Face face, float size = 12.0f, int weight = 400);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Texture* mDebugTexture;
|
||||||
|
|
||||||
|
struct Character
|
||||||
|
{
|
||||||
|
u8 glyph;
|
||||||
|
u8* PixelDataBuffer; // Temporary storage for the character's pixel data - this should be cleanup after the font texture is created
|
||||||
|
u32 TextureDataWidthOffset; // how far into the row does this chars texture data begin
|
||||||
|
u32 TextureDataHeightOffset; // how far into the col does this chars texture data begin
|
||||||
|
Sizeu Size; // Size of glyph
|
||||||
|
glm::vec2 Bearing; // Offset from baseline to left/top of glyph
|
||||||
|
int DownShift; // Size/Amount of glyph below the baseline
|
||||||
|
unsigned int Advance; // Offset to advance to next glyph
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Font
|
||||||
|
{
|
||||||
|
Texture* FontTexture;
|
||||||
|
std::string Name;
|
||||||
|
// The tallest character that does not go below the base line
|
||||||
|
// This is also the distance from the string Y position to the baseline
|
||||||
|
u32 MaxHeight;
|
||||||
|
|
||||||
|
// This is the max distance a char will go below the baseline
|
||||||
|
u32 MaxDownShift;
|
||||||
|
std::map<u8, Character> CharSet;
|
||||||
|
Font() : MaxHeight(0), MaxDownShift(0)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
FT_Library mFTHandle;
|
||||||
|
std::vector<Font> mFonts;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void FreeTempPixelData(std::map<u8, Character>& char_set);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LUNARIUM_TEXT_RENDERER_H_
|
||||||
Loading…
Reference in New Issue