#include "../../include/mgl/graphics/font.h" #include #define STB_TRUETYPE_IMPLEMENTATION #include "../../external/stb_truetype.h" /* TODO: Use correct atlas padding after resize. Its incorrect because padding from previous size doesn't accumulate */ /* Need padding so filtering doesn't touch pixels in another glyphs area */ #define GLYPH_PADDING 2 #define GLYPH_UPSAMPLE 1 int mgl_font_load_from_file(mgl_font *self, const mgl_memory_mapped_file *mapped_file, unsigned int character_size) { self->texture.id = 0; self->font_atlas.width = 0; self->font_atlas.height = 0; self->font_atlas.prev_width = 0; self->font_atlas.prev_height = 0; self->font_atlas.pointer_position = (mgl_vec2i){ GLYPH_PADDING, GLYPH_PADDING }; self->font_atlas.render_section = MGL_ATLAS_SECTION_INITIAL; self->character_size = character_size; mgl_font_char_map_init(&self->char_map); self->current_line_max_height = 0; self->font_info = NULL; self->font_info = malloc(sizeof(stbtt_fontinfo)); if(!self->font_info) { fprintf(stderr, "Error: failed to load font, error: out of memory\n"); return -1; } /* TODO: Handle font offset with ttc */ if(!stbtt_InitFont(self->font_info, mapped_file->data, stbtt_GetFontOffsetForIndex(mapped_file->data, 0))) { fprintf(stderr, "Error: failed to load font, error: stbtt_InitFont failed\n"); mgl_font_unload(self); return -1; } if(mgl_texture_init(&self->texture) != 0) { mgl_font_unload(self); return -1; } /* TODO: Use stbtt_GetCodepointSDF */ return 0; } void mgl_font_unload(mgl_font *self) { mgl_texture_unload(&self->texture); mgl_font_char_map_deinit(&self->char_map); self->current_line_max_height = 0; free(self->font_info); self->font_info = NULL; } static void mgl_font_handle_new_render_position(mgl_font *self, int glyph_width) { if(self->font_atlas.pointer_position.x + glyph_width + GLYPH_PADDING >= self->font_atlas.width) { if(self->font_atlas.pointer_position.y + self->current_line_max_height + GLYPH_PADDING >= self->font_atlas.height && self->font_atlas.render_section != MGL_ATLAS_SECTION_RIGHT) { /* Texture atlas looks like this with glyphs filling the texture: |----| |ABCD| |EFGH| |----| And then texture atlas gets resized: |----|----| | |ABCD| | <-| 1. The right side gets filled in with new glyphs. |EDFG| | <-| |----|----| | | | | | | | | | | | | | |----|----| | | ^^^^^^^^^^^ | ---------------| 2. The bottom side gets filled in with new glyphs after the right side is filled with glyphs. */ /* Texture overflow, enlargen the font atlas! */ /* TODO: Create a new texture and copy the texture content instead of rendering all glyphs again */ if(mgl_texture_resize(&self->texture, self->font_atlas.width * 2, self->font_atlas.height * 2, NULL) == 0) { self->font_atlas.prev_width = self->font_atlas.width; self->font_atlas.prev_height = self->font_atlas.height; self->font_atlas.width *= 2; self->font_atlas.height *= 2; self->font_atlas.render_section = MGL_ATLAS_SECTION_RIGHT; self->font_atlas.pointer_position.x = self->font_atlas.prev_width; self->font_atlas.pointer_position.y = GLYPH_PADDING; mgl_font_char_map_clear_rendered(&self->char_map); mgl_font_glyph glyph_dummy; mgl_font_char_iterator font_char_it = mgl_font_char_map_begin(&self->char_map); while(font_char_it.value) { mgl_font_get_glyph(self, font_char_it.value->key, &glyph_dummy); mgl_font_char_iterator_next(&font_char_it); } } else { fprintf(stderr, "Error: failed to resize font atlas\n"); } } else if(self->font_atlas.render_section == MGL_ATLAS_SECTION_RIGHT && self->font_atlas.pointer_position.y + self->current_line_max_height + GLYPH_PADDING >= self->font_atlas.prev_height) { self->font_atlas.render_section = MGL_ATLAS_SECTION_BOTTOM; self->font_atlas.pointer_position.x = GLYPH_PADDING; self->font_atlas.pointer_position.y = self->font_atlas.prev_height; } else { if(self->font_atlas.render_section != MGL_ATLAS_SECTION_RIGHT) { self->font_atlas.pointer_position.x = GLYPH_PADDING; } else { self->font_atlas.pointer_position.x = self->font_atlas.prev_width; } self->font_atlas.pointer_position.y += self->current_line_max_height + GLYPH_PADDING; } self->current_line_max_height = 0; } } int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph) { if(self->font_atlas.width == 0) { const int initial_atlas_size = 128; /* TODO: Scale by character size */ mgl_texture_load_options load_options = { .compressed = false, .pixel_coordinates = true }; if(mgl_texture_load_from_memory(&self->texture, NULL, initial_atlas_size, initial_atlas_size, MGL_IMAGE_FORMAT_ALPHA, &load_options) == 0) { /*fprintf(stderr, "Error: failed to create font atlas texture, error: mgl_texture_load_from_memory failed\n");*/ self->font_atlas.width = initial_atlas_size; self->font_atlas.height = initial_atlas_size; } } mgl_font_char_entry *existing_char_entry = mgl_font_char_map_get(&self->char_map, codepoint); if(existing_char_entry) { *glyph = existing_char_entry->value; if(existing_char_entry->rendered) return 0; } /* TODO: Maybe somehow use multiple textures? that would be needed when using a lot of glyphs (such as when using chinese fonts). TODO: Map texture buffer and rasterize the glyph directly into the texture. */ const int glyph_index = stbtt_FindGlyphIndex(self->font_info, codepoint); if(glyph_index == 0) { memset(glyph, 0, sizeof(mgl_font_glyph)); return -1; } const float font_scale = stbtt_ScaleForPixelHeight(self->font_info, self->character_size*GLYPH_UPSAMPLE); int advance = 0; int lsb; stbtt_GetGlyphHMetrics(self->font_info, glyph_index, &advance, &lsb); const float glyph_advance = font_scale * advance; int x0, y0, x1, y1; stbtt_GetGlyphBitmapBox(self->font_info, glyph_index, font_scale, font_scale, &x0, &y0, &x1, &y1); int width = x1 - x0; int height = y1 - y0; int xoff = x0; int yoff = y0; /* TODO: Use stbtt_MakeGlyphBitmapSubpixelPrefilter instead for better text quality */ const size_t pixels_size = (width + GLYPH_PADDING * 2) * (height + GLYPH_PADDING * 2); unsigned char *pixels = malloc(pixels_size); if(pixels) { /* TODO: Is this needed? */ /*memset(pixels, 0, pixels_size);*/ stbtt_MakeGlyphBitmapSubpixel(self->font_info, pixels, width, height, width, font_scale, font_scale, GLYPH_PADDING, GLYPH_PADDING, glyph_index); } mgl_vec2i render_offset; if(existing_char_entry) { render_offset = existing_char_entry->render_offset; } else { mgl_font_handle_new_render_position(self, width); render_offset.x = self->font_atlas.pointer_position.x; render_offset.y = self->font_atlas.pointer_position.y; mgl_font_glyph new_glyph; new_glyph.position = (mgl_vec2i){ xoff/GLYPH_UPSAMPLE, yoff/GLYPH_UPSAMPLE }; new_glyph.size = (mgl_vec2i){ width/GLYPH_UPSAMPLE, height/GLYPH_UPSAMPLE }; new_glyph.texture_position = (mgl_vec2i){ render_offset.x, render_offset.y }; new_glyph.texture_size = (mgl_vec2i){ width, height }; new_glyph.advance = glyph_advance/GLYPH_UPSAMPLE; *glyph = new_glyph; if(mgl_font_char_map_insert(&self->char_map, codepoint, &new_glyph, &existing_char_entry) != 0) { free(pixels); memset(glyph, 0, sizeof(mgl_font_glyph)); return 0; } existing_char_entry->render_offset = render_offset; if(height > self->current_line_max_height) self->current_line_max_height = height; self->font_atlas.pointer_position.x += width + GLYPH_PADDING; } existing_char_entry->rendered = true; if(!pixels) { if(codepoint == ' ' || codepoint == '\t') { return 0; } else { memset(glyph, 0, sizeof(mgl_font_glyph)); return -1; } } const int res = mgl_texture_update(&self->texture, pixels, render_offset.x, render_offset.y, width, height, MGL_IMAGE_FORMAT_ALPHA); free(pixels); return res; } int mgl_font_get_kerning(const mgl_font *self, uint32_t prev_codepoint, uint32_t codepoint) { return 0; /* TODO: */ /*return stbtt_GetCodepointKernAdvance(self->font_info, prev_codepoint, codepoint);*/ }