#include "Font.h" #include "GLTexture.h" #include #include #include FT_FREETYPE_H #include #define round_up_26_6(val) (((val) + 63) >> 6u) class Glyph { public: Glyph(); bool load(FT_Face face, FT_ULong char_code); int get_advance() { return m_advance; } void render() { m_texture->bind(); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } protected: bool m_loaded; int m_advance; GLTextureRef m_texture; GLuint m_vbo_id; }; typedef Ref GlyphRef; typedef struct { FT_Face face; std::unordered_map glyphs; } Font; static FT_Library ft; static VALUE ruby_class; static void Init_FreeType() { static bool initialized = false; if (initialized) return; if (FT_Init_FreeType(&ft) != 0) { rb_raise(rb_eRuntimeError, "Error initializing FreeType"); } initialized = true; } void Font_free(void * ptr) { Font * font = (Font *)ptr; FT_Done_Face(font->face); delete font; } static GlyphRef get_glyph(Font * font, FT_ULong char_code) { auto it = font->glyphs.find(char_code); if (it != font->glyphs.end()) return it->second; GlyphRef glyph = new Glyph(); glyph = glyph->load(font->face, char_code) ? glyph : NULL; font->glyphs[char_code] = glyph; return glyph; } static VALUE Font_new(VALUE klass, VALUE fname, VALUE size) { Init_FreeType(); Font * font = new Font(); fname = rb_funcall(fname, rb_intern("to_s"), 0); const char * fname_cstr = StringValueCStr(fname); if (FT_New_Face(ft, fname_cstr, 0, &font->face) != 0) { delete font; rb_raise(rb_eRuntimeError, "Could not load font %s", fname_cstr); } FT_Set_Pixel_Sizes(font->face, 0, NUM2INT(size)); static const char preload_glyphs[] = "HMgjqxy_|^"; int max_top = -9999; int min_bottom = 9999; for (unsigned int i = 0; i < sizeof(preload_glyphs) - 1u; i++) { GlyphRef g = get_glyph(font, preload_glyphs[i]); if (g == NULL) { delete font; rb_raise(rb_eRuntimeError, "Error loading glyph '%c' from font '%s'", preload_glyphs[i], fname_cstr); } if (font->face->glyph->bitmap_top > max_top) max_top = font->face->glyph->bitmap_top; int bitmap_bottom = font->face->glyph->bitmap_top - font->face->glyph->bitmap.rows; if (bitmap_bottom < min_bottom) min_bottom = bitmap_bottom; } int line_height = round_up_26_6(font->face->size->metrics.height); int baseline_offset = (line_height - (max_top - min_bottom)) / 2 - min_bottom; VALUE rv = Data_Wrap_Struct(klass, NULL, Font_free, font); rb_iv_set(rv, "@advance", INT2FIX(get_glyph(font, 'x')->get_advance())); rb_iv_set(rv, "@line_height", INT2FIX(line_height)); rb_iv_set(rv, "@baseline_offset", INT2FIX(baseline_offset)); return rv; } void Font_Init() { ruby_class = rb_define_class("Font", rb_cObject); rb_define_singleton_method(ruby_class, "new", (VALUE(*)(...))Font_new, 2); rb_define_attr(ruby_class, "advance", 1, 0); rb_define_attr(ruby_class, "line_height", 1, 0); rb_define_attr(ruby_class, "baseline_offset", 1, 0); } Glyph::Glyph() { m_loaded = false; } bool Glyph::load(FT_Face face, FT_ULong char_code) { if (FT_Load_Char(face, char_code, FT_LOAD_RENDER) != 0) return false; int width = face->glyph->bitmap.width; int height = face->glyph->bitmap.rows; int left = face->glyph->bitmap_left; int top = face->glyph->bitmap_top; m_advance = round_up_26_6(face->glyph->advance.x); uint8_t * texture = new uint8_t[width * height]; for (int i = 0; i < height; i++) { memcpy(&texture[width * i], &face->glyph->bitmap.buffer[width * (height - i - 1)], width); } m_texture = new GLTexture(); m_texture->create(width, height); 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); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, texture); delete[] texture; float s_max = width / (float)m_texture->get_width(); float t_max = height / (float)m_texture->get_height(); GLfloat box[4][4] = { {(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}, }; glGenBuffers(1, &m_vbo_id); glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id); glBufferData(GL_ARRAY_BUFFER, sizeof(box), box, GL_STATIC_DRAW); m_loaded = true; return true; } void Font_RenderGlyph(VALUE font_obj, unsigned long char_code) { Font * font; Data_Get_Struct(font_obj, Font, font); GlyphRef g = get_glyph(font, char_code); if (g != NULL) { g->render(); } }