#include "../../include/mgl/graphics/text.h" #include "../../include/mgl/graphics/font.h" #include "../../include/mgl/system/utf8.h" #include "../../include/mgl/window/window.h" #include "../../include/mgl/mgl.h" #include /* TODO: Cache mgl_font_get_glyph */ #define TAB_WIDTH 4 typedef struct { size_t codepoint_index; uint32_t codepoint; float kerning; float glyph_width; const mgl_font_glyph *glyph; mgl_vec2f glyph_offset; } codepoint_loop_callback_data; static float max_float(float a, float b) { return a >= b ? a : b; } static float float_abs(float value) { return value >= 0.0f ? value : -value; } /* Return false to stop the loop. |glyph| is not valid if codepoint is '\n' */ typedef bool (*codepoint_loop_callback)(codepoint_loop_callback_data callback_data, void *userdata); static void mgl_text_for_each_codepoint(const mgl_text *self, codepoint_loop_callback callback, void *userdata) { mgl_font_glyph glyph; size_t codepoint_index = 0; float kerning = 0.0f; float glyph_width = 0.0f; uint32_t prev_codepoint = 0; mgl_vec2f glyph_offset = {0, 0}; unsigned int rows = 1; for(size_t i = 0; i < self->text_size;) { unsigned char *cp = (unsigned char*)&self->text[i]; uint32_t codepoint = 0; size_t clen = 0; if(!mgl_utf8_decode(cp, self->text_size - i, &codepoint, &clen)) { codepoint = *cp; clen = 1; } if(codepoint == '\t') { if(mgl_font_get_glyph(self->font, ' ', &glyph) == 0) { kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); glyph_width = glyph.advance * TAB_WIDTH; } else { goto next; } } else if(codepoint == '\n') { kerning = 0.0f; glyph_width = 0.0f; glyph.size = (mgl_vec2i){ (int)glyph_width, (int)self->font->character_size }; } else { if(mgl_font_get_glyph(self->font, codepoint, &glyph) == 0) { kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); glyph_width = glyph.advance; } else { goto next; } } if(((glyph_offset.x + kerning + glyph_width > self->max_width) && self->max_width > 0.001f) || codepoint == '\n') { glyph_offset.x = 0.0f; glyph_offset.y += self->font->character_size; prev_codepoint = 0; if(rows == self->max_rows) return; ++rows; } else { glyph_offset.x += kerning; } if(!callback((codepoint_loop_callback_data){ codepoint_index, codepoint, kerning, glyph_width, &glyph, glyph_offset }, userdata)) return; glyph_offset.x += glyph_width; //if(codepoint == '\n') // glyph_offset.y += self->font->character_size; prev_codepoint = codepoint; next: i += clen; ++codepoint_index; } } typedef struct { mgl_vec2f bounds; } CalculateBoundsUserdata; static bool calculate_bounds_callback(codepoint_loop_callback_data callback_data, void *userdata) { CalculateBoundsUserdata *calculate_bounds_userdata = userdata; calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, callback_data.glyph_offset.x + callback_data.glyph_width); calculate_bounds_userdata->bounds.y = max_float(calculate_bounds_userdata->bounds.y, callback_data.glyph_offset.y + callback_data.glyph->size.y); return true; } static mgl_vec2f mgl_text_calculate_bounds(mgl_text *self) { CalculateBoundsUserdata calculate_bounds_userdata; calculate_bounds_userdata.bounds.x = 0.0f; calculate_bounds_userdata.bounds.y = self->font->character_size; mgl_text_for_each_codepoint(self, calculate_bounds_callback, &calculate_bounds_userdata); return calculate_bounds_userdata.bounds; } void mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_size) { self->font = font; self->color = (mgl_color){ 255, 255, 255, 255 }; self->position = (mgl_vec2f){ 0.0f, 0.0f }; self->text = NULL; self->text_size = 0; self->bounds_dirty = true; self->max_width = 0.0f; self->max_rows = 0; mgl_text_set_string(self, str, str_size); } void mgl_text_deinit(mgl_text *self) { self->font = NULL; self->position = (mgl_vec2f){ 0.0f, 0.0f }; self->text = NULL; self->text_size = 0; self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; } void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size) { self->text = str; self->text_size = str_size; self->bounds_dirty = true; } void mgl_text_set_font(mgl_text *self, mgl_font *font) { self->font = font; self->bounds_dirty = true; } void mgl_text_set_position(mgl_text *self, mgl_vec2f position) { self->position = position; } void mgl_text_set_color(mgl_text *self, mgl_color color) { self->color = color; } void mgl_text_set_max_width(mgl_text *self, float max_width) { if(float_abs(max_width - self->max_width) < 0.01) return; self->bounds_dirty = true; self->max_width = max_width; } void mgl_text_set_max_rows(mgl_text *self, unsigned int max_rows) { if(max_rows != self->max_rows) self->bounds_dirty = true; self->max_rows = max_rows; } mgl_vec2f mgl_text_get_bounds(mgl_text *self) { if(self->bounds_dirty) { self->bounds_dirty = false; if(self->text && self->text_size > 0 && self->font) self->bounds = mgl_text_calculate_bounds(self); else if(self->font) self->bounds = (mgl_vec2f){ 0.0f, (float)self->font->character_size }; else self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; } return self->bounds; } typedef struct { mgl_vec2f glyph_offset; const mgl_text *text; size_t index; } FindCharacterPosUserdata; static bool find_character_pos_callback(codepoint_loop_callback_data callback_data, void *userdata) { FindCharacterPosUserdata *find_character_pos_userdata = userdata; find_character_pos_userdata->glyph_offset = callback_data.glyph_offset; if(callback_data.codepoint_index >= find_character_pos_userdata->index) return false; find_character_pos_userdata->glyph_offset.x += callback_data.glyph_width; if(callback_data.codepoint == '\n') find_character_pos_userdata->glyph_offset.y += find_character_pos_userdata->text->font->character_size; return true; } mgl_vec2f mgl_text_find_character_pos(mgl_text *self, size_t index) { FindCharacterPosUserdata find_character_pos_userdata; find_character_pos_userdata.glyph_offset = (mgl_vec2f){0.0f, 0.0f}; find_character_pos_userdata.text = self; find_character_pos_userdata.index = index; mgl_text_for_each_codepoint(self, find_character_pos_callback, &find_character_pos_userdata); return (mgl_vec2f){ self->position.x + find_character_pos_userdata.glyph_offset.x, self->position.y + find_character_pos_userdata.glyph_offset.y }; } static void mgl_text_draw_glyph(mgl_context *context, const mgl_font_glyph *glyph, mgl_vec2i position) { context->gl.glTexCoord2f(glyph->texture_position.x, glyph->texture_position.y); context->gl.glVertex3f(position.x + glyph->position.x, position.y + glyph->position.y, 0.0f); context->gl.glTexCoord2f(glyph->texture_position.x + glyph->texture_size.x, glyph->texture_position.y); context->gl.glVertex3f(position.x + glyph->position.x + glyph->size.x, position.y + glyph->position.y, 0.0f); context->gl.glTexCoord2f(glyph->texture_position.x + glyph->texture_size.x, glyph->texture_position.y + glyph->texture_size.y); context->gl.glVertex3f(position.x + glyph->position.x + glyph->size.x, position.y + glyph->position.y + glyph->size.y, 0.0f); context->gl.glTexCoord2f(glyph->texture_position.x, glyph->texture_position.y + glyph->texture_size.y); context->gl.glVertex3f(position.x + glyph->position.x, position.y + glyph->position.y + glyph->size.y, 0.0f); } typedef struct { mgl_vec2f position; const mgl_text *text; mgl_context *context; } TextDrawUserdata; static bool text_draw_callback(codepoint_loop_callback_data callback_data, void *userdata) { TextDrawUserdata *text_draw_userdata = userdata; if(callback_data.codepoint != '\t' && callback_data.codepoint != '\n') { mgl_text_draw_glyph(text_draw_userdata->context, callback_data.glyph, (mgl_vec2i){ text_draw_userdata->position.x + callback_data.glyph_offset.x, text_draw_userdata->position.y + callback_data.glyph_offset.y }); } return true; } /* TODO: Use opengl buffer object instead */ /* TODO: Cache texture bind to not bind texture if its already bound and do not bind texture 0 */ void mgl_text_draw(mgl_context *context, mgl_text *text) { if(!text->text || text->text_size == 0 || !text->font) return; /* Calculate bounds beforehand if needed, which also generates glyphs because opengl outputs an error if that is done inside glBegin/glEnd */ /* TODO: Only run this on the new glyphs */ mgl_text_get_bounds(text); TextDrawUserdata text_draw_userdata; text_draw_userdata.position = (mgl_vec2f){ text->position.x, text->position.y }; text_draw_userdata.text = text; text_draw_userdata.context = context; mgl_window_set_texture_blend_func(context->current_window); context->gl.glColor4ub(text->color.r, text->color.g, text->color.b, text->color.a); mgl_texture_use(&text->font->texture); context->gl.glBegin(GL_QUADS); mgl_text_for_each_codepoint(text, text_draw_callback, &text_draw_userdata); context->gl.glEnd(); mgl_texture_use(NULL); mgl_window_set_render_blend_func(context->current_window); }