/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2009 Blender Foundation. All rights reserved. */ /** \file * \ingroup blf * * Glyph rendering, texturing and caching. Wraps Freetype and OpenGL functions. */ #include #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_OUTLINE_H #include FT_BITMAP_H #include FT_ADVANCES_H /* For FT_Get_Advance. */ #include "MEM_guardedalloc.h" #include "DNA_userdef_types.h" #include "DNA_vec_types.h" #include "BLI_listbase.h" #include "BLI_rect.h" #include "BLI_threads.h" #include "BLF_api.h" #include "GPU_capabilities.h" #include "GPU_immediate.h" #include "blf_internal.h" #include "blf_internal_types.h" #include "BLI_math_vector.h" #include "BLI_strict_flags.h" /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ /** * Convert a floating point value to a FreeType 16.16 fixed point value. */ static FT_Fixed to_16dot16(double val) { return (FT_Fixed)(lround(val * 65536.0)); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Glyph Cache * \{ */ static GlyphCacheBLF *blf_glyph_cache_find(FontBLF *font, float size, unsigned int dpi) { 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; } gc = gc->next; } return NULL; } static GlyphCacheBLF *blf_glyph_cache_new(FontBLF *font) { GlyphCacheBLF *gc = (GlyphCacheBLF *)MEM_callocN(sizeof(GlyphCacheBLF), "blf_glyph_cache_new"); gc->next = NULL; gc->prev = NULL; gc->size = font->size; gc->dpi = font->dpi; gc->bold = ((font->flags & BLF_BOLD) != 0); gc->italic = ((font->flags & BLF_ITALIC) != 0); memset(gc->glyph_ascii_table, 0, sizeof(gc->glyph_ascii_table)); memset(gc->bucket, 0, sizeof(gc->bucket)); /* Determine ideal fixed-width size for monospaced output. */ FT_UInt gindex = FT_Get_Char_Index(font->face, U'0'); if (gindex) { FT_Fixed advance = 0; FT_Get_Advance(font->face, gindex, FT_LOAD_NO_HINTING, &advance); /* Use CSS 'ch unit' width, advance of zero character. */ gc->fixed_width = (int)(advance >> 16); } else { /* Font does not contain "0" so use CSS fallback of 1/2 of em. */ gc->fixed_width = (int)((font->face->size->metrics.height / 2) >> 6); } if (gc->fixed_width < 1) { gc->fixed_width = 1; } BLI_addhead(&font->cache, gc); return gc; } GlyphCacheBLF *blf_glyph_cache_acquire(FontBLF *font) { BLI_spin_lock(font->glyph_cache_mutex); GlyphCacheBLF *gc = blf_glyph_cache_find(font, font->size, font->dpi); if (!gc) { gc = blf_glyph_cache_new(font); } return gc; } void blf_glyph_cache_release(FontBLF *font) { BLI_spin_unlock(font->glyph_cache_mutex); } static void blf_glyph_cache_free(GlyphCacheBLF *gc) { GlyphBLF *g; for (uint i = 0; i < ARRAY_SIZE(gc->bucket); i++) { while ((g = BLI_pophead(&gc->bucket[i]))) { blf_glyph_free(g); } } if (gc->texture) { GPU_texture_free(gc->texture); } if (gc->bitmap_result) { MEM_freeN(gc->bitmap_result); } MEM_freeN(gc); } void blf_glyph_cache_clear(FontBLF *font) { GlyphCacheBLF *gc; BLI_spin_lock(font->glyph_cache_mutex); while ((gc = BLI_pophead(&font->cache))) { blf_glyph_cache_free(gc); } BLI_spin_unlock(font->glyph_cache_mutex); } /** * Try to find a glyph in cache. * * \return NULL if not found. */ static GlyphBLF *blf_glyph_cache_find_glyph(GlyphCacheBLF *gc, uint charcode) { 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; } g = g->next; } return NULL; } /** * 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_x = (ft_pix)glyph->advance.x; 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 = (ft_pix)bbox.xMin; g->box_xmax = (ft_pix)bbox.xMax; g->box_ymin = (ft_pix)bbox.yMin; g->box_ymax = (ft_pix)bbox.yMax; /* Used to improve advance when hinting is enabled. */ g->lsb_delta = (ft_pix)glyph->lsb_delta; g->rsb_delta = (ft_pix)glyph->rsb_delta; 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 `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; if (font->flags & BLF_MONOCHROME) { load_flags = FT_LOAD_TARGET_MONO; } else { load_flags = FT_LOAD_NO_BITMAP; if (font->flags & BLF_HINTING_NONE) { load_flags |= FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; } else if (font->flags & BLF_HINTING_SLIGHT) { load_flags |= FT_LOAD_TARGET_LIGHT; } else if (font->flags & BLF_HINTING_FULL) { load_flags |= FT_LOAD_TARGET_NORMAL; } else { /* Default, hinting disabled until FreeType has been upgraded * to give good results on all platforms. */ load_flags |= FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; } } if (FT_Load_Glyph(font->face, glyph_index, load_flags) == FT_Err_Ok) { return font->face->glyph; } return NULL; } /** * 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; } /* Render the glyph curves to a bitmap. */ FT_Error err = FT_Render_Glyph(glyph, render_mode); if (err != FT_Err_Ok) { return false; } FT_Bitmap tempbitmap; if (font->flags & BLF_MONOCHROME) { /* Convert result from 1 bit per pixel to 8 bit per pixel */ /* Accumulate errors for later, fine if not interested beyond "ok vs any error" */ FT_Bitmap_New(&tempbitmap); /* Does Blender use Pitch 1 always? It works so far */ 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 || glyph->format != FT_GLYPH_FORMAT_BITMAP) { return false; } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Glyph Transformations * \{ */ /** * Adjust the glyphs weight by a factor. * * \param 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; } /** * Adjust the glyphs slant by a factor (making it oblique). * * \param factor: -1 (max negative) <= 0 (no slant) => 1 (max positive). * * \note that left-leaning italics are possible 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; } /** * Adjust the glyph width by factor. * * \param 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; } /** * 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; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Glyph Access (Ensure/Free) * \{ */ /** * 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; } FT_GlyphSlot glyph = blf_glyph_load(glyph_font, glyph_index); if (!glyph) { return NULL; } 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); } 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); } if (blf_glyph_render_bitmap(glyph_font, glyph)) { return glyph; } return NULL; } 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); /* 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; } void blf_glyph_free(GlyphBLF *g) { if (g->bitmap) { MEM_freeN(g->bitmap); } MEM_freeN(g); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Glyph Bounds Calculation * \{ */ static void blf_glyph_calc_rect(rcti *rect, GlyphBLF *g, const int x, const int y) { rect->xmin = x + g->pos[0]; rect->xmax = rect->xmin + g->dims[0]; rect->ymin = y + g->pos[1]; rect->ymax = rect->ymin - g->dims[1]; } static void blf_glyph_calc_rect_test(rcti *rect, GlyphBLF *g, const int x, const int y) { /* Intentionally check with `g->advance`, because this is the * width used by BLF_width. This allows that the text slightly * overlaps the clipping border to achieve better alignment. */ rect->xmin = x; rect->xmax = rect->xmin + MIN2(ft_pix_to_int(g->advance_x), g->dims[0]); rect->ymin = y; rect->ymax = rect->ymin - g->dims[1]; } static void blf_glyph_calc_rect_shadow( rcti *rect, GlyphBLF *g, const int x, const int y, FontBLF *font) { blf_glyph_calc_rect(rect, g, x + font->shadow_x, y + font->shadow_y); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Glyph Drawing * \{ */ static void blf_texture_draw(const unsigned char color[4], const int glyph_size[2], const int offset, const int x1, const int y1, const int x2, const int y2) { /* Only one vertex per glyph, geometry shader expand it into a quad. */ /* TODO: Get rid of Geom Shader because it's not optimal AT ALL for the GPU. */ copy_v4_fl4(GPU_vertbuf_raw_step(&g_batch.pos_step), (float)(x1 + g_batch.ofs[0]), (float)(y1 + g_batch.ofs[1]), (float)(x2 + g_batch.ofs[0]), (float)(y2 + g_batch.ofs[1])); copy_v4_v4_uchar(GPU_vertbuf_raw_step(&g_batch.col_step), color); copy_v2_v2_int(GPU_vertbuf_raw_step(&g_batch.glyph_size_step), glyph_size); *((int *)GPU_vertbuf_raw_step(&g_batch.offset_step)) = offset; g_batch.glyph_len++; /* Flush cache if it's full. */ if (g_batch.glyph_len == BLF_BATCH_DRAW_LEN_MAX) { blf_batch_draw(); } } static void blf_texture5_draw(const unsigned char color_in[4], const int glyph_size[2], const int offset, const int x1, const int y1, const int x2, const int y2) { int glyph_size_flag[2]; /* flag the x and y component signs for 5x5 blurring */ glyph_size_flag[0] = -glyph_size[0]; glyph_size_flag[1] = -glyph_size[1]; blf_texture_draw(color_in, glyph_size_flag, offset, x1, y1, x2, y2); } static void blf_texture3_draw(const unsigned char color_in[4], const int glyph_size[2], const int offset, const int x1, const int y1, const int x2, const int y2) { int glyph_size_flag[2]; /* flag the x component sign for 3x3 blurring */ glyph_size_flag[0] = -glyph_size[0]; glyph_size_flag[1] = glyph_size[1]; blf_texture_draw(color_in, glyph_size_flag, offset, x1, y1, x2, y2); } void blf_glyph_draw(FontBLF *font, GlyphCacheBLF *gc, GlyphBLF *g, const int x, const int y) { if ((!g->dims[0]) || (!g->dims[1])) { return; } if (g->glyph_cache == NULL) { if (font->tex_size_max == -1) { font->tex_size_max = GPU_max_texture_size(); } g->offset = gc->bitmap_len; int buff_size = g->dims[0] * g->dims[1]; int bitmap_len = gc->bitmap_len + buff_size; if (bitmap_len > gc->bitmap_len_alloc) { int w = font->tex_size_max; int h = bitmap_len / w + 1; gc->bitmap_len_alloc = w * h; gc->bitmap_result = MEM_reallocN(gc->bitmap_result, (size_t)gc->bitmap_len_alloc); /* Keep in sync with the texture. */ if (gc->texture) { GPU_texture_free(gc->texture); } gc->texture = GPU_texture_create_2d(__func__, w, h, 1, GPU_R8, NULL); gc->bitmap_len_landed = 0; } memcpy(&gc->bitmap_result[gc->bitmap_len], g->bitmap, (size_t)buff_size); gc->bitmap_len = bitmap_len; g->glyph_cache = gc; } if (font->flags & BLF_CLIPPING) { rcti rect_test; blf_glyph_calc_rect_test(&rect_test, g, x, y); BLI_rcti_translate(&rect_test, font->pos[0], font->pos[1]); if (!BLI_rcti_inside_rcti(&font->clip_rec, &rect_test)) { return; } } if (g_batch.glyph_cache != g->glyph_cache) { blf_batch_draw(); g_batch.glyph_cache = g->glyph_cache; } if (font->flags & BLF_SHADOW) { rcti rect_ofs; blf_glyph_calc_rect_shadow(&rect_ofs, g, x, y, font); if (font->shadow == 0) { blf_texture_draw(font->shadow_color, g->dims, g->offset, rect_ofs.xmin, rect_ofs.ymin, rect_ofs.xmax, rect_ofs.ymax); } else if (font->shadow <= 4) { blf_texture3_draw(font->shadow_color, g->dims, g->offset, rect_ofs.xmin, rect_ofs.ymin, rect_ofs.xmax, rect_ofs.ymax); } else { blf_texture5_draw(font->shadow_color, g->dims, g->offset, rect_ofs.xmin, rect_ofs.ymin, rect_ofs.xmax, rect_ofs.ymax); } } rcti rect; blf_glyph_calc_rect(&rect, g, x, y); #if BLF_BLUR_ENABLE switch (font->blur) { case 3: blf_texture3_draw( font->color, g->dims, g->offset, rect.xmin, rect.ymin, rect.xmax, rect.ymax); break; case 5: blf_texture5_draw( font->color, g->dims, g->offset, rect.xmin, rect.ymin, rect.xmax, rect.ymax); break; default: blf_texture_draw( font->color, g->dims, g->offset, rect.xmin, rect.ymin, rect.xmax, rect.ymax); } #else blf_texture_draw(font->color, g->dims, g->offset, rect.xmin, rect.ymin, rect.xmax, rect.ymax); #endif } /** \} */