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
This commit is contained in:
Harley Acheson 2021-11-06 11:28:28 -07:00
parent 6002914f14
commit eddf5ad581
1 changed files with 228 additions and 111 deletions

View File

@ -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;
if (charcode < GLYPH_ASCII_TABLE_SIZE) {
return gc->glyph_ascii_table[charcode];
}
key = blf_hash(c);
p = gc->bucket[key].first;
while (p) {
if (p->c == c) {
return p;
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,47 +231,30 @@ static bool blf_glyph_render(FontBLF *font, FT_UInt glyph_index)
}
}
FT_Error err = FT_Load_Glyph(font->face, glyph_index, load_flags);
if (FT_Load_Glyph(font->face, glyph_index, load_flags) == 0) {
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 != 0) {
return false;
}
/* 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);
}
}
/* get the glyph. */
FT_GlyphSlot slot = font->face->glyph;
err = FT_Render_Glyph(slot, render_mode);
FT_Bitmap tempbitmap;
if (font->flags & BLF_MONOCHROME) {
@ -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;
}
/* 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));
}
/* 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;
}
/* 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;
}
/* 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;
}
/* 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;
}
/* 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;
}
/* Create (or load from cache) a fully-rendered bitmap glyph. */
GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, uint charcode)
{
GlyphBLF *g = (charcode < GLYPH_ASCII_TABLE_SIZE) ? (gc->glyph_ascii_table)[charcode] :
blf_glyph_search(gc, charcode);
GlyphBLF *g = blf_glyph_cache_find_glyph(gc, charcode);
if (g) {
return g;
}
FT_UInt glyph_index = FT_Get_Char_Index(font->face, charcode);
/* 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);
if (!blf_glyph_render(font, glyph_index)) {
return NULL;
}
FT_GlyphSlot slot = font->face->glyph;
/* glyphs are dynamically created as needed by font rendering. this means that
/* 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);
* renderer uses a shared buffer internally. */
BLI_spin_lock(font_with_glyph->ft_lib_mutex);
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_render(font, font_with_glyph, glyph_index);
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;
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 (glyph) {
/* Save this glyph in the initial font's cache. */
g = blf_glyph_cache_add_glyph(font, gc, glyph, charcode, glyph_index);
}
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;
}
BLI_spin_unlock(font->ft_lib_mutex);
BLI_spin_unlock(font_with_glyph->ft_lib_mutex);
return g;
}