Skip to content

Instantly share code, notes, and snippets.

@natanaeljr
Created January 7, 2022 01:35
Show Gist options
  • Save natanaeljr/af96deedcae48a9793c753433cc94ad6 to your computer and use it in GitHub Desktop.
Save natanaeljr/af96deedcae48a9793c753433cc94ad6 to your computer and use it in GitHub Desktop.
Loading fonts and baking bitmaps with FreeType for rendering in opengl (partial implementation)
auto load_font_texture(gsl::span<const uint8_t> data, size_t width, size_t height) -> GLTexture
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
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);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, data.data());
glGenerateMipmap(GL_TEXTURE_2D);
return { texture };
}
struct Glyph {
glm::ivec2 size;
glm::ivec2 offset;
unsigned int advance;
};
struct GLFont {
GLTexture texture;
std::unordered_map<char, Glyph> glyphs;
// Movable but not Copyable
GLFont(GLFont&&) = default;
GLFont(const GLFont&) = delete;
GLFont& operator=(GLFont&&) = default;
GLFont& operator=(const GLFont&) = delete;
};
/// Read font file and upload generated bitmap texture to GPU memory
auto load_font(FT_Library& freetype, const std::string& fontname) -> std::optional<GLFont>
{
const std::string filepath = SPACESHIP_ASSETS_PATH + "/fonts/"s + fontname;
FT_Face face;
int err = FT_New_Face(freetype, filepath.data(), 0, &face);
if (err) {
ERROR("Failed to load font ({}), error: {}", filepath, err);
return std::nullopt;
}
const size_t kPixelHeight = 48;
const size_t kPixelWidth = kPixelHeight * (2.f / 3.f);
const size_t kPixelSize = kPixelHeight * kPixelWidth;
FT_Set_Pixel_Sizes(face, kPixelWidth, kPixelHeight);
std::vector<uint8_t> data;
data.reserve(kPixelSize * 128);
size_t data_height = 0;
std::unordered_map<char, Glyph> glyphs(128);
// Load only ASCII characters
for (unsigned c = 65; c < 128; c++) {
err = FT_Load_Char(face, c, FT_LOAD_RENDER);
if (err) {
WARN("Failed to load char '{}' from font ({}), error: {}", c, fontname, err);
continue;
}
FT_GlyphSlot glyph = face->glyph;
FT_Bitmap& bitmap = glyph->bitmap;
char ch = (c > 32 && c < 128) ? c : ' ';
TRACE("Font ({}) char {} '{}' row {} width {} left {} top {} adx {} ady {}", fontname, c, ch, bitmap.rows, bitmap.width, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x >> 6, glyph->advance.y >> 6);
if(bitmap.rows > kPixelHeight || bitmap.width > kPixelWidth) {
WARN("No support for oversized glyph '{}' ({},{})", glyph->glyph_index, bitmap.rows, bitmap.width);
continue;
}
size_t pad_width = kPixelWidth - bitmap.width;
for (int row = bitmap.rows - 1; row >= 0; row--) {
std::copy_n(&bitmap.buffer[bitmap.width * row], bitmap.width, std::back_inserter(data));
std::fill_n(std::back_inserter(data), pad_width, 0);
}
glyphs.emplace(c, Glyph{
.size = { bitmap.width, bitmap.rows },
.offset = { glyph->bitmap_left, glyph->bitmap_top },
.advance = static_cast<unsigned int>(glyph->advance.x >> 6), // pt to pixel
});
data_height += bitmap.rows;
}
FT_Done_Face(face);
GLTexture texture = load_font_texture(data, kPixelWidth, data_height);
return GLFont{ std::move(texture), std::move(glyphs) };
}
struct Fonts {
GLFont main;
};
Fonts load_fonts()
{
FT_Library freetype;
ASSERT(!FT_Init_FreeType(&freetype));
auto main_font = load_font(freetype, "Menlo-Regular.ttf");
ASSERT(main_font);
ASSERT(!FT_Done_FreeType(freetype));
return {
.main = std::move(*main_font),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment