jes-ruby/src/Font.cc

199 lines
5.3 KiB
C++

#include "Font.h"
#include "GLTexture.h"
#include <iostream>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <unordered_map>
#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<Glyph> GlyphRef;
typedef struct
{
FT_Face face;
std::unordered_map<FT_ULong, GlyphRef> 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;
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);
}
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();
}
}