From eddf5ad5819fb48b2d8ebccc30a869f86718b565 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Sat, 6 Nov 2021 11:28:28 -0700 Subject: BLF: Refactor blf_glyph.c Cleanup and Simplification of blf_glyph.c See D13095 for details. Differential Revision: https://developer.blender.org/D13095 Reviewed by Campbell Barton --- source/blender/blenfont/intern/blf_glyph.c | 335 +++++++++++++++++++---------- 1 file changed, 226 insertions(+), 109 deletions(-) (limited to 'source/blender/blenfont') diff --git a/source/blender/blenfont/intern/blf_glyph.c b/source/blender/blenfont/intern/blf_glyph.c index c4ffb3f87f1..716670ac9c4 100644 --- a/source/blender/blenfont/intern/blf_glyph.c +++ b/source/blender/blenfont/intern/blf_glyph.c @@ -55,27 +55,25 @@ #include "BLI_math_vector.h" #include "BLI_strict_flags.h" +/* Find a glyph cache that matches a size, dpi, styles. */ GlyphCacheBLF *blf_glyph_cache_find(FontBLF *font, unsigned int size, unsigned int dpi) { - GlyphCacheBLF *p; - - p = (GlyphCacheBLF *)font->cache.first; - while (p) { - if (p->size == size && p->dpi == dpi && (p->bold == ((font->flags & BLF_BOLD) != 0)) && - (p->italic == ((font->flags & BLF_ITALIC) != 0))) { - return p; + GlyphCacheBLF *gc = (GlyphCacheBLF *)font->cache.first; + while (gc) { + if (gc->size == size && gc->dpi == dpi && (gc->bold == ((font->flags & BLF_BOLD) != 0)) && + (gc->italic == ((font->flags & BLF_ITALIC) != 0))) { + return gc; } - p = p->next; + gc = gc->next; } return NULL; } -/* Create a new glyph cache for the current size, dpi, bold, italic. */ +/* Create a new glyph cache for the current size, dpi, styles. */ GlyphCacheBLF *blf_glyph_cache_new(FontBLF *font) { - GlyphCacheBLF *gc; + GlyphCacheBLF *gc = (GlyphCacheBLF *)MEM_callocN(sizeof(GlyphCacheBLF), "blf_glyph_cache_new"); - gc = (GlyphCacheBLF *)MEM_callocN(sizeof(GlyphCacheBLF), "blf_glyph_cache_new"); gc->next = NULL; gc->prev = NULL; gc->size = font->size; @@ -138,34 +136,85 @@ void blf_glyph_cache_free(GlyphCacheBLF *gc) MEM_freeN(gc); } -static GlyphBLF *blf_glyph_search(GlyphCacheBLF *gc, unsigned int c) +/* Try to find a glyph in cache, return NULL if not found. */ +static GlyphBLF *blf_glyph_cache_find_glyph(GlyphCacheBLF *gc, uint charcode) { - GlyphBLF *p; - unsigned int key; - - key = blf_hash(c); - p = gc->bucket[key].first; - while (p) { - if (p->c == c) { - return p; + if (charcode < GLYPH_ASCII_TABLE_SIZE) { + return gc->glyph_ascii_table[charcode]; + } + + GlyphBLF *g = gc->bucket[blf_hash(charcode)].first; + while (g) { + if (g->c == charcode) { + return g; } - p = p->next; + g = g->next; } return NULL; } -static bool blf_glyph_render(FontBLF *font, FT_UInt glyph_index) +/* Add a rendered glyph to a cache. */ +static GlyphBLF *blf_glyph_cache_add_glyph( + FontBLF *font, GlyphCacheBLF *gc, FT_GlyphSlot glyph, uint charcode, FT_UInt glyph_index) +{ + GlyphBLF *g = (GlyphBLF *)MEM_callocN(sizeof(GlyphBLF), "blf_glyph_get"); + g->c = charcode; + g->idx = glyph_index; + g->advance = ((float)glyph->advance.x) / 64.0f; + g->advance_i = (int)g->advance; + g->pos[0] = glyph->bitmap_left; + g->pos[1] = glyph->bitmap_top; + g->dims[0] = (int)glyph->bitmap.width; + g->dims[1] = (int)glyph->bitmap.rows; + g->pitch = glyph->bitmap.pitch; + + FT_BBox bbox; + FT_Outline_Get_CBox(&(glyph->outline), &bbox); + g->box.xmin = ((float)bbox.xMin) / 64.0f; + g->box.xmax = ((float)bbox.xMax) / 64.0f; + g->box.ymin = ((float)bbox.yMin) / 64.0f; + g->box.ymax = ((float)bbox.yMax) / 64.0f; + + const int buffer_size = (int)(glyph->bitmap.width * glyph->bitmap.rows); + if (buffer_size != 0) { + if (font->flags & BLF_MONOCHROME) { + /* Font buffer uses only 0 or 1 values, Blender expects full 0..255 range. */ + for (int i = 0; i < buffer_size; i++) { + glyph->bitmap.buffer[i] = glyph->bitmap.buffer[i] ? 255 : 0; + } + } + g->bitmap = MEM_mallocN((size_t)buffer_size, "glyph bitmap"); + memcpy(g->bitmap, glyph->bitmap.buffer, (size_t)buffer_size); + } + + unsigned int key = blf_hash(g->c); + BLI_addhead(&(gc->bucket[key]), g); + if (charcode < GLYPH_ASCII_TABLE_SIZE) { + gc->glyph_ascii_table[charcode] = g; + } + + return g; +} + +/* Return a glyph index from a charcode. Not found returns zero, which is a valid + * printable character (.notdef or "tofu"). Font is allowed to change here. */ +static FT_UInt blf_glyph_index_from_charcode(FontBLF **font, const uint charcode) +{ + FT_UInt glyph_index = FT_Get_Char_Index((*font)->face, charcode); + /* TODO: If not found in this font, check others, update font pointer. */ + return glyph_index; +} + +/* Load a glyph into the glyph slot of a font's face object. */ +static FT_GlyphSlot blf_glyph_load(FontBLF *font, FT_UInt glyph_index) { int load_flags; - int render_mode; if (font->flags & BLF_MONOCHROME) { load_flags = FT_LOAD_TARGET_MONO; - render_mode = FT_RENDER_MODE_MONO; } else { load_flags = FT_LOAD_NO_BITMAP; - render_mode = FT_RENDER_MODE_NORMAL; if (font->flags & BLF_HINTING_NONE) { load_flags |= FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; } @@ -182,46 +231,29 @@ static bool blf_glyph_render(FontBLF *font, FT_UInt glyph_index) } } - FT_Error err = FT_Load_Glyph(font->face, glyph_index, load_flags); - if (err != 0) { - return false; + if (FT_Load_Glyph(font->face, glyph_index, load_flags) == 0) { + return font->face->glyph; } + return NULL; +} - /* Do not oblique a font that is designed to be italic! */ - if (((font->flags & BLF_ITALIC) != 0) && !(font->face->style_flags & FT_STYLE_FLAG_ITALIC) && - (font->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)) { - /* For (fake) italic: a shear transform with a 6 degree angle. */ - FT_Matrix transform; - transform.xx = 0x10000L; - transform.yx = 0x00000L; - transform.xy = 0x03000L; - transform.yy = 0x10000L; - FT_Outline_Transform(&font->face->glyph->outline, &transform); - } - - /* Do not embolden an already bold font! */ - if (((font->flags & BLF_BOLD) != 0) && !(font->face->style_flags & FT_STYLE_FLAG_BOLD) && - (font->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)) { - /* Strengthen the width more than the height. */ - const FT_Pos extra_x = FT_MulFix(font->face->units_per_EM, font->face->size->metrics.x_scale) / - 14; - const FT_Pos extra_y = FT_MulFix(font->face->units_per_EM, font->face->size->metrics.y_scale) / - 28; - FT_Outline_EmboldenXY(&font->face->glyph->outline, extra_x, extra_y); - if ((font->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) == 0) { - /* Need to increase advance, but not for fixed-width fonts. */ - font->face->glyph->advance.x += (FT_Pos)(((float)extra_x) * 1.05f); - font->face->glyph->advance.y += extra_y; - } - else { - /* Widened fixed-pitch font gets a nudge left. */ - FT_Outline_Translate(&font->face->glyph->outline, (extra_x / -2), 0); - } +/* Convert a glyph from outlines to a bitmap that we can display. */ +static bool blf_glyph_render_bitmap(FontBLF *font, FT_GlyphSlot glyph) +{ + int render_mode; + + if (font->flags & BLF_MONOCHROME) { + render_mode = FT_RENDER_MODE_MONO; + } + else { + render_mode = FT_RENDER_MODE_NORMAL; } - /* get the glyph. */ - FT_GlyphSlot slot = font->face->glyph; - err = FT_Render_Glyph(slot, render_mode); + /* Render the glyph curves to a bitmap. */ + FT_Error err = FT_Render_Glyph(glyph, render_mode); + if (err != 0) { + return false; + } FT_Bitmap tempbitmap; @@ -231,78 +263,163 @@ static bool blf_glyph_render(FontBLF *font, FT_UInt glyph_index) FT_Bitmap_New(&tempbitmap); /* Does Blender use Pitch 1 always? It works so far */ - err += FT_Bitmap_Convert(font->ft_lib, &slot->bitmap, &tempbitmap, 1); - err += FT_Bitmap_Copy(font->ft_lib, &tempbitmap, &slot->bitmap); + err += FT_Bitmap_Convert(font->ft_lib, &glyph->bitmap, &tempbitmap, 1); + err += FT_Bitmap_Copy(font->ft_lib, &tempbitmap, &glyph->bitmap); err += FT_Bitmap_Done(font->ft_lib, &tempbitmap); } - if (err || slot->format != FT_GLYPH_FORMAT_BITMAP) { + if (err || glyph->format != FT_GLYPH_FORMAT_BITMAP) { return false; } return true; } -GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, uint charcode) +/* Convert a floating point value to a FreeType 16.16 fixed point value. */ +static FT_Fixed to_16dot16(double val) { - GlyphBLF *g = (charcode < GLYPH_ASCII_TABLE_SIZE) ? (gc->glyph_ascii_table)[charcode] : - blf_glyph_search(gc, charcode); - if (g) { - return g; + return (FT_Fixed)(lround(val * 65536.0)); +} + +/* Glyph weight by factor. -1 (min stroke width) <= 0 (normal) => 1 (max boldness). */ +static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float factor, bool monospaced) +{ + if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + /* Fake bold if the font does not have this variable axis. */ + const FT_Pos average_width = FT_MulFix(glyph->face->units_per_EM, + glyph->face->size->metrics.x_scale); + FT_Pos change = (FT_Pos)((float)average_width * factor * 0.1f); + FT_Outline_EmboldenXY(&glyph->outline, change, change / 2); + if (monospaced) { + /* Widened fixed-pitch font needs a nudge left. */ + FT_Outline_Translate(&glyph->outline, change / -2, 0); + } + else { + /* Need to increase advance. */ + glyph->advance.x += change; + glyph->advance.y += change / 2; + } + return true; } + return false; +} - FT_UInt glyph_index = FT_Get_Char_Index(font->face, charcode); +/* Glyph oblique by factor. -1 (max negative) <= 0 (no slant) => 1 (max positive). + * Note that left-leaning italics are possibile in some RTL writing systems. */ +static bool blf_glyph_transform_slant(FT_GlyphSlot glyph, float factor) +{ + if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + FT_Matrix transform = {to_16dot16(1), to_16dot16(factor / 2.0f), 0, to_16dot16(1)}; + FT_Outline_Transform(&glyph->outline, &transform); + return true; + } + return false; +} - if (!blf_glyph_render(font, glyph_index)) { - return NULL; +/* Glyph width by factor. -1 (min width) <= 0 (normal) => 1 (max width). */ +static bool UNUSED_FUNCTION(blf_glyph_transform_width)(FT_GlyphSlot glyph, float factor) +{ + if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + float scale = (factor * 0.4f) + 1.0f; /* 0.6f - 1.4f */ + FT_Matrix matrix = {to_16dot16(scale), 0, 0, to_16dot16(1)}; + FT_Outline_Transform(&glyph->outline, &matrix); + glyph->advance.x = (FT_Pos)((double)glyph->advance.x * scale); + return true; } + return false; +} - FT_GlyphSlot slot = font->face->glyph; +/* Transform glyph to fit nicely within a fixed column width. */ +static bool UNUSED_FUNCTION(blf_glyph_transform_monospace)(FT_GlyphSlot glyph, int width) +{ + if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + int gwidth = (int)(glyph->linearHoriAdvance >> 16); + if (gwidth > width) { + float scale = (float)width / (float)gwidth; + FT_Matrix matrix = {to_16dot16(scale), 0, 0, to_16dot16(1)}; + /* Narrowing all points also thins vertical strokes. */ + FT_Outline_Transform(&glyph->outline, &matrix); + const FT_Pos extra_x = (int)((float)(gwidth - width) * 5.65f); + /* Horizontally widen strokes to counteract narrowing. */ + FT_Outline_EmboldenXY(&glyph->outline, extra_x, 0); + } + else if (gwidth < width) { + /* Narrow glyphs only need to be centered. */ + int nudge = (width - gwidth) / 2; + FT_Outline_Translate(&glyph->outline, (FT_Pos)nudge * 64, 0); + } + return true; + } + return false; +} - /* glyphs are dynamically created as needed by font rendering. this means that - * to make font rendering thread safe we have to do locking here. note that this - * must be a lock for the whole library and not just per font, because the font - * renderer uses a shared buffer internally */ - BLI_spin_lock(font->ft_lib_mutex); +/* Create and return a fully-rendered bitmap glyph. */ +static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, + FontBLF *glyph_font, + FT_UInt glyph_index) +{ + if (glyph_font != settings_font) { + FT_Set_Char_Size(glyph_font->face, + 0, + ((FT_F26Dot6)(settings_font->size)) * 64, + settings_font->dpi, + settings_font->dpi); + glyph_font->size = settings_font->size; + glyph_font->dpi = settings_font->dpi; + } - g = (GlyphBLF *)MEM_callocN(sizeof(GlyphBLF), "blf_glyph_get"); - g->c = charcode; - g->idx = glyph_index; - g->advance = ((float)slot->advance.x) / 64.0f; - g->advance_i = (int)g->advance; - g->pos[0] = slot->bitmap_left; - g->pos[1] = slot->bitmap_top; - g->dims[0] = (int)slot->bitmap.width; - g->dims[1] = (int)slot->bitmap.rows; - g->pitch = slot->bitmap.pitch; + FT_GlyphSlot glyph = blf_glyph_load(glyph_font, glyph_index); + if (!glyph) { + return NULL; + } - FT_BBox bbox; - FT_Outline_Get_CBox(&(slot->outline), &bbox); - g->box.xmin = ((float)bbox.xMin) / 64.0f; - g->box.xmax = ((float)bbox.xMax) / 64.0f; - g->box.ymin = ((float)bbox.yMin) / 64.0f; - g->box.ymax = ((float)bbox.yMax) / 64.0f; + if ((settings_font->flags & BLF_ITALIC) != 0) { + /* 37.5% of maximum rightward slant results in 6 degree slope, matching italic + * version (DejaVuSans-Oblique.ttf) of our current font. But a nice median when + * checking others. Worth reevaluating if we change default font. We could also + * narrow the glyph slightly as most italics do, but this one does not. */ + blf_glyph_transform_slant(glyph, 0.375f); + } - const int buffer_size = (int)(slot->bitmap.width * slot->bitmap.rows); - if (buffer_size != 0) { - if (font->flags & BLF_MONOCHROME) { - /* Font buffer uses only 0 or 1 values, Blender expects full 0..255 range */ - for (int i = 0; i < buffer_size; i++) { - slot->bitmap.buffer[i] = slot->bitmap.buffer[i] ? 255 : 0; - } - } - g->bitmap = MEM_mallocN((size_t)buffer_size, "glyph bitmap"); - memcpy(g->bitmap, slot->bitmap.buffer, (size_t)buffer_size); + if ((settings_font->flags & BLF_BOLD) != 0) { + /* 70% of maximum weight results in the same amount of boldness and horizontal + * expansion as the bold version (DejaVuSans-Bold.ttf) of our default font. + * Worth reevaluating if we change default font. */ + blf_glyph_transform_weight(glyph, 0.7f, glyph->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH); } - unsigned int key = blf_hash(g->c); - BLI_addhead(&(gc->bucket[key]), g); - if (charcode < GLYPH_ASCII_TABLE_SIZE) { - gc->glyph_ascii_table[charcode] = g; + if (blf_glyph_render_bitmap(glyph_font, glyph)) { + return glyph; } + return NULL; +} + +/* Create (or load from cache) a fully-rendered bitmap glyph. */ +GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, uint charcode) +{ + GlyphBLF *g = blf_glyph_cache_find_glyph(gc, charcode); + if (g) { + return g; + } + + /* Glyph might not come from the initial font. */ + FontBLF *font_with_glyph = font; + FT_UInt glyph_index = blf_glyph_index_from_charcode(&font_with_glyph, charcode); - BLI_spin_unlock(font->ft_lib_mutex); + /* Glyphs are dynamically created as needed by font rendering. this means that + * to make font rendering thread safe we have to do locking here. note that this + * must be a lock for the whole library and not just per font, because the font + * renderer uses a shared buffer internally. */ + BLI_spin_lock(font_with_glyph->ft_lib_mutex); + + FT_GlyphSlot glyph = blf_glyph_render(font, font_with_glyph, glyph_index); + + if (glyph) { + /* Save this glyph in the initial font's cache. */ + g = blf_glyph_cache_add_glyph(font, gc, glyph, charcode, glyph_index); + } + BLI_spin_unlock(font_with_glyph->ft_lib_mutex); return g; } -- cgit v1.2.3