From d776f1a20924f8bf5fc891c793bc5d22ba7dcaa8 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 23 Jan 2018 12:08:46 -0500 Subject: [PATCH] add Font and Glyph classes --- src/Font.cc | 91 +++++++++++++++++++++++++++++++++++++++++ src/Font.h | 27 ++++++++++++ src/Glyph.cc | 52 +++++++++++++++++++++++ src/Glyph.h | 27 ++++++++++++ src/{app.cc => main.cc} | 0 wscript | 3 +- 6 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/Font.cc create mode 100644 src/Font.h create mode 100644 src/Glyph.cc create mode 100644 src/Glyph.h rename src/{app.cc => main.cc} (100%) diff --git a/src/Font.cc b/src/Font.cc new file mode 100644 index 0000000..66381fd --- /dev/null +++ b/src/Font.cc @@ -0,0 +1,91 @@ +#include "Font.h" + +#define round_up_26_6(val) (((val) + 63) >> 6u) + +static FT_Library ft_library_handle; +static bool ft_initialized; + +bool Initialize_FreeType() +{ + if (ft_initialized) + { + return true; + } + + if (FT_Init_FreeType(&ft_library_handle) != 0) + { + return false; + } + + ft_initialized = true; + return true; +} + +bool Font::load(const char * fname, int size) +{ + if (!Initialize_FreeType()) + { + return false; + } + + if (FT_New_Face(ft_library_handle, fname, 0, &m_face) != 0) + { + return false; + } + + FT_Set_Pixel_Sizes(m_face, 0, size); + + return preload_glyphs(); +} + +bool Font::preload_glyphs() +{ + static const char preload_glyph_list[] = "HMgjqxy_|^"; + + int max_top = -9999; + int min_bottom = 9999; + m_advance = 0; + + for (size_t i = 0u; i < sizeof(preload_glyph_list) - 1u; i++) + { + std::shared_ptr g = get_glyph(preload_glyph_list[i]); + if (g == NULL) + { + return false; + } + if (m_face->glyph->bitmap_top > max_top) + { + max_top = m_face->glyph->bitmap_top; + } + int bitmap_bottom = m_face->glyph->bitmap_top - m_face->glyph->bitmap.rows; + if (bitmap_bottom < min_bottom) + { + min_bottom = bitmap_bottom; + } + if (g->get_advance() > m_advance) + { + m_advance = g->get_advance(); + } + } + + m_line_height = round_up_26_6(m_face->size->metrics.height); + m_baseline_offset = (m_line_height - (max_top - min_bottom)) / 2 - min_bottom; + + return true; +} + +std::shared_ptr Font::get_glyph(FT_ULong character) +{ + auto it = m_glyphs.find(character); + if (it != m_glyphs.end()) + { + return it->second; + } + std::shared_ptr glyph = std::make_shared(); + if (!glyph->load(m_face, character)) + { + glyph = nullptr; + } + m_glyphs[character] = glyph; + return glyph; +} diff --git a/src/Font.h b/src/Font.h new file mode 100644 index 0000000..2590246 --- /dev/null +++ b/src/Font.h @@ -0,0 +1,27 @@ +#ifndef FONT_H +#define FONT_H + +#include +#include +#include "Glyph.h" + +class Font +{ +public: + bool load(const char * fname, int size); + std::shared_ptr get_glyph(FT_ULong character); + int get_advance() { return m_advance; } + int get_line_height() { return m_line_height; } + int get_baseline_offset() { return m_baseline_offset; } + +protected: + bool preload_glyphs(); + + FT_Face m_face; + std::unordered_map> m_glyphs; + int m_advance; + int m_line_height; + int m_baseline_offset; +}; + +#endif diff --git a/src/Glyph.cc b/src/Glyph.cc new file mode 100644 index 0000000..8f0a40d --- /dev/null +++ b/src/Glyph.cc @@ -0,0 +1,52 @@ +#include "Glyph.h" + +#define round_up_26_6(val) (((val) + 63) >> 6u) + +bool Glyph::load(FT_Face face, FT_ULong char_code) +{ + if (FT_Load_Char(face, char_code, FT_LOAD_RENDER) != 0) + return false; + + m_texture = glcxx::Texture::create(); + m_texture->bind(GL_TEXTURE_2D); + int width = face->glyph->bitmap.width; + int rounded_width = glcxx::Texture::next_power_of_2(width); + int height = face->glyph->bitmap.rows; + int rounded_height = glcxx::Texture::next_power_of_2(height); + m_advance = round_up_26_6(face->glyph->advance.x); + uint8_t * texture = new uint8_t[rounded_width * rounded_height]; + memset(texture, 0, rounded_width * rounded_height); + for (int i = 0; i < height; i++) + { + memcpy(&texture[rounded_width * i], + &face->glyph->bitmap.buffer[width * (height - i - 1)], + width); + } + + 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); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, rounded_width, rounded_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texture); + delete[] texture; + + m_array = glcxx::Array::create(); + m_array->bind(); + + int left = face->glyph->bitmap_left; + int top = face->glyph->bitmap_top; + float s_max = width / (float)rounded_width; + float t_max = height / (float)rounded_height; + m_buffer = glcxx::Buffer::create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, { + (GLfloat)left, (GLfloat)(top - height), 0.0, 0.0, + (GLfloat)(left + width), (GLfloat)(top - height), s_max, 0.0, + (GLfloat)left, (GLfloat)top, 0.0, t_max, + (GLfloat)(left + width), (GLfloat)top, s_max, t_max, + }); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); + + return true; +} diff --git a/src/Glyph.h b/src/Glyph.h new file mode 100644 index 0000000..05913a8 --- /dev/null +++ b/src/Glyph.h @@ -0,0 +1,27 @@ +#ifndef GLYPH_H +#define GLYPH_H + +#include +#include FT_FREETYPE_H +#include "glcxx.hpp" + +class Glyph +{ +public: + bool load(FT_Face face, FT_ULong char_code); + int get_advance() { return m_advance; } + void render() + { + m_array->bind(); + m_texture->bind(GL_TEXTURE_2D); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + +protected: + int m_advance; + std::shared_ptr m_texture; + std::shared_ptr m_buffer; + std::shared_ptr m_array; +}; + +#endif diff --git a/src/app.cc b/src/main.cc similarity index 100% rename from src/app.cc rename to src/main.cc diff --git a/wscript b/wscript index 7a40e8c..16dc7e9 100644 --- a/wscript +++ b/wscript @@ -12,6 +12,7 @@ def configure(conf): conf.env.CXXFLAGS += ["-Wall"] conf.env.CXXFLAGS += ["-std=gnu++14"] conf.check_cfg(package = "sdl2", args = "--cflags --libs") + conf.check_cfg(package = "freetype2", uselib_store = "FreeType2", args = "--cflags --libs") if "MINGW" in os.popen("uname").read(): # TODO: set CXX to mingw32-g++ pass @@ -34,7 +35,7 @@ def build(bld): bld.program( source = sources, target = "app", - uselib = "SDL2", + uselib = ["SDL2", "FreeType2"], lib = libs, linkflags = ["-Wl,-rpath,$ORIGIN"], install_path = None)