BLF: Subpixel Positioning, Anti-aliasing, Hinting #105441

Merged
Harley Acheson merged 11 commits from Harley/blender:Subpixel into main 2023-09-21 22:43:24 +02:00
5 changed files with 120 additions and 118 deletions

View File

@ -356,18 +356,6 @@ static void blf_batch_draw_end()
/** \name Glyph Stepping Utilities (Internal)
* \{ */
/* Fast path for runs of ASCII characters. Given that common UTF-8
* input will consist of an overwhelming majority of ASCII
* characters.
*/
BLI_INLINE GlyphBLF *blf_glyph_from_utf8_and_step(
FontBLF *font, GlyphCacheBLF *gc, const char *str, size_t str_len, size_t *i_p)
{
uint charcode = BLI_str_utf8_as_unicode_step_safe(str, str_len, i_p);
return blf_glyph_ensure(font, gc, charcode);
}
BLI_INLINE ft_pix blf_kerning(FontBLF *font, const GlyphBLF *g_prev, const GlyphBLF *g)
{
ft_pix adjustment = 0;
@ -403,6 +391,31 @@ BLI_INLINE ft_pix blf_kerning(FontBLF *font, const GlyphBLF *g_prev, const Glyph
return adjustment;
}
BLI_INLINE GlyphBLF *blf_glyph_from_utf8_and_step(FontBLF *font,
GlyphCacheBLF *gc,
GlyphBLF *g_prev,
const char *str,
size_t str_len,
size_t *i_p,
int32_t *pen_x)
{
uint charcode = BLI_str_utf8_as_unicode_step_safe(str, str_len, i_p);
/* Invalid unicode sequences return the byte value, stepping forward one.
* This allows `latin1` to display (which is sometimes used for file-paths). */
BLI_assert(charcode != BLI_UTF8_ERR);
GlyphBLF *g = blf_glyph_ensure(font, gc, charcode);
if (g && pen_x && !(font->flags & BLF_MONOSPACED)) {
*pen_x += blf_kerning(font, g_prev, g);
#ifndef BLF_SUBPIXEL_POSITION
*pen_x = FT_PIX_ROUND(*pen_x);
#endif
#ifdef BLF_SUBPIXEL_AA
g = blf_glyph_ensure_subpixel(font, gc, g, *pen_x);
#endif
}
return g;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -416,7 +429,7 @@ static void blf_font_draw_ex(FontBLF *font,
ResultBLF *r_info,
const ft_pix pen_y)
{
GlyphBLF *g, *g_prev = nullptr;
GlyphBLF *g = nullptr;
ft_pix pen_x = 0;
size_t i = 0;
@ -428,18 +441,13 @@ static void blf_font_draw_ex(FontBLF *font,
blf_batch_draw_begin(font);
while ((i < str_len) && str[i]) {
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, g, str, str_len, &i, &pen_x);
if (UNLIKELY(g == nullptr)) {
continue;
}
pen_x += blf_kerning(font, g_prev, g);
/* do not return this loop if clipped, we want every character tested */
blf_glyph_draw(font, gc, g, ft_pix_to_int_floor(pen_x), ft_pix_to_int_floor(pen_y));
pen_x = ft_pix_round_advance(pen_x, g->advance_x);
g_prev = g;
pen_x += g->advance_x;
}
blf_batch_draw_end();
@ -470,7 +478,7 @@ int blf_font_draw_mono(FontBLF *font, const char *str, const size_t str_len, int
blf_batch_draw_begin(font);
while ((i < str_len) && str[i]) {
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, nullptr, str, str_len, &i, nullptr);
if (UNLIKELY(g == nullptr)) {
continue;
@ -613,7 +621,7 @@ static void blf_font_draw_buffer_ex(FontBLF *font,
ResultBLF *r_info,
ft_pix pen_y)
{
GlyphBLF *g, *g_prev = nullptr;
GlyphBLF *g = nullptr;
ft_pix pen_x = ft_pix_from_int(font->pos[0]);
ft_pix pen_y_basis = ft_pix_from_int(font->pos[1]) + pen_y;
size_t i = 0;
@ -624,17 +632,13 @@ static void blf_font_draw_buffer_ex(FontBLF *font,
/* another buffer specific call for color conversion */
while ((i < str_len) && str[i]) {
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, g, str, str_len, &i, &pen_x);
if (UNLIKELY(g == nullptr)) {
continue;
}
pen_x += blf_kerning(font, g_prev, g);
blf_glyph_draw_buffer(buf_info, g, pen_x, pen_y_basis);
pen_x = ft_pix_round_advance(pen_x, g->advance_x);
g_prev = g;
pen_x += g->advance_x;
}
if (r_info) {
@ -666,8 +670,7 @@ static bool blf_font_width_to_strlen_glyph_process(
if (UNLIKELY(g == nullptr)) {
return false; /* continue the calling loop. */
}
*pen_x += blf_kerning(font, g_prev, g);
*pen_x = ft_pix_round_advance(*pen_x, g->advance_x);
*pen_x += blf_kerning(font, g_prev, g) + g->advance_x;
/* When true, break the calling loop. */
return (ft_pix_to_int(*pen_x) >= width_i);
@ -687,8 +690,7 @@ size_t blf_font_width_to_strlen(
for (i_prev = i = 0, width_new = pen_x = 0, g_prev = nullptr; (i < str_len) && str[i];
i_prev = i, width_new = pen_x, g_prev = g)
{
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, nullptr, str, str_len, &i, nullptr);
if (blf_font_width_to_strlen_glyph_process(font, g_prev, g, &pen_x, width_i)) {
break;
}
@ -719,7 +721,7 @@ size_t blf_font_width_to_rstrlen(
i_prev = size_t(s_prev - str);
i_tmp = i;
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i_tmp);
g = blf_glyph_from_utf8_and_step(font, gc, nullptr, str, str_len, &i_tmp, nullptr);
for (width_new = pen_x = 0; (s != nullptr);
i = i_prev, s = s_prev, g = g_prev, g_prev = nullptr, width_new = pen_x)
{
@ -728,7 +730,7 @@ size_t blf_font_width_to_rstrlen(
if (s_prev != nullptr) {
i_tmp = i_prev;
g_prev = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i_tmp);
g_prev = blf_glyph_from_utf8_and_step(font, gc, nullptr, str, str_len, &i_tmp, nullptr);
BLI_assert(i_tmp == i);
}
@ -759,7 +761,7 @@ static void blf_font_boundbox_ex(FontBLF *font,
ResultBLF *r_info,
ft_pix pen_y)
{
GlyphBLF *g, *g_prev = nullptr;
GlyphBLF *g = nullptr;
ft_pix pen_x = 0;
size_t i = 0;
@ -769,13 +771,12 @@ static void blf_font_boundbox_ex(FontBLF *font,
ft_pix box_ymax = ft_pix_from_int(-32000);
while ((i < str_len) && str[i]) {
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, g, str, str_len, &i, &pen_x);
if (UNLIKELY(g == nullptr)) {
continue;
}
pen_x += blf_kerning(font, g_prev, g);
const ft_pix pen_x_next = ft_pix_round_advance(pen_x, g->advance_x);
const ft_pix pen_x_next = pen_x + g->advance_x;
const ft_pix gbox_xmin = pen_x;
const ft_pix gbox_xmax = pen_x_next;
@ -797,7 +798,6 @@ static void blf_font_boundbox_ex(FontBLF *font,
}
pen_x = pen_x_next;
g_prev = g;
}
if (box_xmin > box_xmax) {
@ -910,7 +910,7 @@ void blf_font_boundbox_foreach_glyph(FontBLF *font,
BLF_GlyphBoundsFn user_fn,
void *user_data)
{
GlyphBLF *g, *g_prev = nullptr;
GlyphBLF *g = nullptr;
ft_pix pen_x = 0;
size_t i = 0, i_curr;
@ -923,13 +923,11 @@ void blf_font_boundbox_foreach_glyph(FontBLF *font,
while ((i < str_len) && str[i]) {
i_curr = i;
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, g, str, str_len, &i, &pen_x);
if (UNLIKELY(g == nullptr)) {
continue;
}
pen_x += blf_kerning(font, g_prev, g);
rcti bounds;
bounds.xmin = ft_pix_to_int_floor(pen_x) + ft_pix_to_int_floor(g->box_xmin);
bounds.xmax = ft_pix_to_int_floor(pen_x) + ft_pix_to_int_ceil(g->box_xmax);
@ -939,8 +937,7 @@ void blf_font_boundbox_foreach_glyph(FontBLF *font,
if (user_fn(str, i_curr, &bounds, user_data) == false) {
break;
}
pen_x = ft_pix_round_advance(pen_x, g->advance_x);
g_prev = g;
pen_x += g->advance_x;
}
blf_glyph_cache_release(font);
@ -1050,7 +1047,8 @@ static void blf_font_wrap_apply(FontBLF *font,
void *userdata),
void *userdata)
{
GlyphBLF *g, *g_prev = nullptr;
GlyphBLF *g = nullptr;
GlyphBLF *g_prev = nullptr;
ft_pix pen_x = 0;
ft_pix pen_y = 0;
size_t i = 0;
@ -1073,12 +1071,11 @@ static void blf_font_wrap_apply(FontBLF *font,
size_t i_curr = i;
bool do_draw = false;
g = blf_glyph_from_utf8_and_step(font, gc, str, str_len, &i);
g = blf_glyph_from_utf8_and_step(font, gc, g_prev, str, str_len, &i, &pen_x);
if (UNLIKELY(g == nullptr)) {
continue;
}
pen_x += blf_kerning(font, g_prev, g);
/**
* Implementation Detail (utf8).
@ -1088,7 +1085,7 @@ static void blf_font_wrap_apply(FontBLF *font,
*
* This is _only_ done when we know for sure the character is ascii (newline or a space).
*/
pen_x_next = ft_pix_round_advance(pen_x, g->advance_x);
pen_x_next = pen_x + g->advance_x;
if (UNLIKELY((pen_x_next >= wrap.wrap_width) && (wrap.start != wrap.last[0]))) {
do_draw = true;
}
@ -1422,16 +1419,6 @@ bool blf_ensure_face(FontBLF *font)
font->face_flags = font->face->face_flags;
/* XXX: Temporarily disable kerning in our main font. Kerning had been accidentally removed
* from our font in 3.1. In 3.4 we disable kerning here in the new version to keep spacing the
* same
* (#101506). Enable again later with change of font, placement, or rendering - Harley. */
if (font && font->filepath &&
(BLI_path_cmp(BLI_path_basename(font->filepath), BLF_DEFAULT_PROPORTIONAL_FONT) == 0))
{
font->face_flags &= ~FT_FACE_FLAG_KERNING;
}
if (FT_HAS_MULTIPLE_MASTERS(font)) {
FT_Get_MM_Var(font->face, &(font->variations));
}

View File

@ -94,7 +94,6 @@ static GlyphCacheBLF *blf_glyph_cache_new(FontBLF *font)
gc->char_width = font->char_width;
gc->char_spacing = font->char_spacing;
memset(gc->glyph_ascii_table, 0, sizeof(gc->glyph_ascii_table));
memset(gc->bucket, 0, sizeof(gc->bucket));
blf_ensure_size(font);
@ -169,15 +168,13 @@ void blf_glyph_cache_clear(FontBLF *font)
*
* \return nullptr if not found.
*/
static GlyphBLF *blf_glyph_cache_find_glyph(const GlyphCacheBLF *gc, uint charcode)
static GlyphBLF *blf_glyph_cache_find_glyph(const GlyphCacheBLF *gc,
uint charcode,
uint8_t subpixel)
{
if (charcode < GLYPH_ASCII_TABLE_SIZE) {
return gc->glyph_ascii_table[charcode];
}
GlyphBLF *g = static_cast<GlyphBLF *>(gc->bucket[blf_hash(charcode)].first);
GlyphBLF *g = static_cast<GlyphBLF *>(gc->bucket[blf_hash(charcode << 6 | subpixel)].first);
while (g) {
if (g->c == charcode) {
if (g->c == charcode && g->subpixel == subpixel) {
return g;
}
g = g->next;
@ -225,8 +222,12 @@ static uchar blf_glyph_gamma(uchar c)
/**
* 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)
static GlyphBLF *blf_glyph_cache_add_glyph(FontBLF *font,
GlyphCacheBLF *gc,
FT_GlyphSlot glyph,
uint charcode,
FT_UInt glyph_index,
uint8_t subpixel)
{
GlyphBLF *g = (GlyphBLF *)MEM_callocN(sizeof(GlyphBLF), "blf_glyph_get");
g->c = charcode;
@ -237,6 +238,7 @@ static GlyphBLF *blf_glyph_cache_add_glyph(
g->dims[0] = int(glyph->bitmap.width);
g->dims[1] = int(glyph->bitmap.rows);
g->pitch = glyph->bitmap.pitch;
g->subpixel = subpixel;
FT_BBox bbox;
FT_Outline_Get_CBox(&(glyph->outline), &bbox);
@ -269,11 +271,7 @@ static GlyphBLF *blf_glyph_cache_add_glyph(
memcpy(g->bitmap, glyph->bitmap.buffer, size_t(buffer_size));
}
const uint key = blf_hash(g->c);
BLI_addhead(&(gc->bucket[key]), g);
if (charcode < GLYPH_ASCII_TABLE_SIZE) {
gc->glyph_ascii_table[charcode] = g;
}
BLI_addhead(&(gc->bucket[blf_hash(g->c << 6 | subpixel)]), g);
return g;
}
@ -731,9 +729,8 @@ static FT_GlyphSlot blf_glyph_load(FontBLF *font, FT_UInt glyph_index)
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;
/* Default "Auto" is Slight (vertical only) hinting. */
load_flags |= FT_LOAD_TARGET_LIGHT;
}
}
@ -998,6 +995,7 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font,
FontBLF *glyph_font,
FT_UInt glyph_index,
uint charcode,
uint8_t subpixel,
int fixed_width)
{
if (glyph_font != settings_font) {
@ -1071,15 +1069,20 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font,
blf_glyph_transform_spacing(glyph, spacing);
}
FT_Outline_Translate(&glyph->outline, (FT_Pos)subpixel, 0);
if (blf_glyph_render_bitmap(glyph_font, glyph)) {
return glyph;
}
return nullptr;
}
GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, const uint charcode)
static GlyphBLF *blf_glyph_ensure_ex(FontBLF *font,
GlyphCacheBLF *gc,
const uint charcode,
uint8_t subpixel)
{
GlyphBLF *g = blf_glyph_cache_find_glyph(gc, charcode);
GlyphBLF *g = blf_glyph_cache_find_glyph(gc, charcode, subpixel);
if (g) {
return g;
}
@ -1093,16 +1096,44 @@ GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, const uint charcode
}
FT_GlyphSlot glyph = blf_glyph_render(
font, font_with_glyph, glyph_index, charcode, gc->fixed_width);
font, font_with_glyph, glyph_index, charcode, subpixel, gc->fixed_width);
if (glyph) {
/* Save this glyph in the initial font's cache. */
g = blf_glyph_cache_add_glyph(font, gc, glyph, charcode, glyph_index);
g = blf_glyph_cache_add_glyph(font, gc, glyph, charcode, glyph_index, subpixel);
}
return g;
}
GlyphBLF *blf_glyph_ensure(FontBLF *font, GlyphCacheBLF *gc, const uint charcode)
{
return blf_glyph_ensure_ex(font, gc, charcode, 0);
}
#ifdef BLF_SUBPIXEL_AA
GlyphBLF *blf_glyph_ensure_subpixel(FontBLF *font, GlyphCacheBLF *gc, GlyphBLF *g, int32_t pen_x)
{
if (font->flags & BLF_HINTING_NONE) {
/* Not if we are not also hinting.*/
return g;
}
if (font->size > 35.0f || g->dims[0] == 0 || g->advance_x < 0) {
/* Single position for large sizes, spaces, and combining characters. */
return g;
}
/* Four sub-pixel positions up to 16 point, 2 until 35 points. */
const uint8_t subpixel = (uint8_t)(pen_x & ((font->size > 16.0f) ? 32L : 48L));
if (g->subpixel != subpixel) {
g = blf_glyph_ensure_ex(font, gc, g->c, subpixel);
}
return g;
}
#endif
void blf_glyph_free(GlyphBLF *g)
{
if (g->bitmap) {
@ -1130,8 +1161,8 @@ static void blf_glyph_calc_rect_test(rcti *rect, GlyphBLF *g, const int x, const
/* 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->xmin = x + g->pos[0] + 1;
rect->xmax = x + MIN2(ft_pix_to_int(g->advance_x), g->dims[0]);
rect->ymin = y;
rect->ymax = rect->ymin - g->dims[1];
}

View File

@ -24,6 +24,16 @@ struct rcti;
*/
#define BLF_MAX_FONT 64
/**
* If enabled, glyphs positions are on 64ths of a pixel. Disabled, they are on whole pixels.
*/
#define BLF_SUBPIXEL_POSITION
/**
* If enabled, glyphs are rendered at multiple horizontal subpixel positions.
*/
#define BLF_SUBPIXEL_AA
/** Maximum number of opened FT_Face objects managed by cache. 0 is default of 2. */
#define BLF_CACHE_MAX_FACES 4
/** Maximum number of opened FT_Size objects managed by cache. 0 is default of 4 */
@ -168,6 +178,13 @@ void blf_glyph_cache_clear(struct FontBLF *font);
*/
struct GlyphBLF *blf_glyph_ensure(struct FontBLF *font, struct GlyphCacheBLF *gc, uint charcode);
#ifdef BLF_SUBPIXEL_AA
struct GlyphBLF *blf_glyph_ensure_subpixel(struct FontBLF *font,
struct GlyphCacheBLF *gc,
struct GlyphBLF *g,
int32_t pen_x);
#endif
void blf_glyph_free(struct GlyphBLF *g);
void blf_glyph_draw(
struct FontBLF *font, struct GlyphCacheBLF *gc, struct GlyphBLF *g, int x, int y);

View File

@ -44,31 +44,13 @@ typedef int32_t ft_pix;
/* Macros copied from `include/freetype/internal/ftobjs.h`. */
/**
* FIXME(@ideasman42): Follow rounding from Blender 3.1x and older.
* This is what users will expect and changing this creates wider spaced text.
* Use this macro to communicate that rounding should be used, using floor is to avoid
* user visible changes, which can be reviewed and handled separately.
*/
#define USE_LEGACY_SPACING
#define FT_PIX_FLOOR(x) ((x) & ~63)
#define FT_PIX_ROUND(x) FT_PIX_FLOOR((x) + 32)
#define FT_PIX_CEIL(x) ((x) + 63)
#ifdef USE_LEGACY_SPACING
# define FT_PIX_DEFAULT_ROUNDING(x) FT_PIX_FLOOR(x)
#else
# define FT_PIX_DEFAULT_ROUNDING(x) FT_PIX_ROUND(x)
#endif
BLI_INLINE int ft_pix_to_int(ft_pix v)
{
#ifdef USE_LEGACY_SPACING
return (int)(v >> 6);
#else
return (int)(FT_PIX_DEFAULT_ROUNDING(v) >> 6);
#endif
}
BLI_INLINE int ft_pix_to_int_floor(ft_pix v)
@ -91,23 +73,10 @@ BLI_INLINE ft_pix ft_pix_from_float(float v)
return lroundf(v * 64.0f);
}
BLI_INLINE ft_pix ft_pix_round_advance(ft_pix v, ft_pix step)
{
/** See #USE_LEGACY_SPACING, rounding logic could change here. */
return FT_PIX_DEFAULT_ROUNDING(v) + FT_PIX_DEFAULT_ROUNDING(step);
}
#undef FT_PIX_ROUND
#undef FT_PIX_CEIL
#undef FT_PIX_DEFAULT_ROUNDING
/** \} */
#define BLF_BATCH_DRAW_LEN_MAX 2048 /* in glyph */
/** Number of characters in #GlyphCacheBLF.glyph_ascii_table. */
#define GLYPH_ASCII_TABLE_SIZE 128
/** Number of characters in #KerningCacheBLF.table. */
#define KERNING_CACHE_TABLE_SIZE 128
@ -161,9 +130,6 @@ typedef struct GlyphCacheBLF {
/** The glyphs. */
ListBase bucket[257];
/** Fast ascii lookup */
struct GlyphBLF *glyph_ascii_table[GLYPH_ASCII_TABLE_SIZE];
/** Texture array, to draw the glyphs. */
GPUTexture *texture;
char *bitmap_result;
@ -190,6 +156,7 @@ typedef struct GlyphBLF {
ft_pix box_ymax;
ft_pix advance_x;
uint8_t subpixel;
/** The difference in bearings when hinting is active, zero otherwise. */
ft_pix lsb_delta;

View File

@ -1656,7 +1656,7 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle,
strwidth = BLF_width(fstyle->uifont_id, str, max_len);
}
BLI_assert((strwidth <= okwidth) || (okwidth <= 0.0f));
BLI_assert((strwidth <= (okwidth + 1)) || (okwidth <= 0.0f));
return strwidth;
}