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