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
6 changed files with 120 additions and 94 deletions
Showing only changes of commit 5aa78be7c9 - Show all commits

View File

@ -348,6 +348,8 @@ enum {
BLF_BAD_FONT = 1 << 16,
/** This font is managed by the FreeType cache subsystem. */
BLF_CACHED = 1 << 17,
/** For legacy DejaVu. No hinting and half-pixel too tight. */
BLF_LEGACY_SPACING = 1 << 18,
};
#define BLF_DRAW_STR_DUMMY_MAX 1024

View File

@ -356,25 +356,17 @@ 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(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);
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;
if (font->flags & BLF_LEGACY_SPACING) {
if (font->flags & BLF_HINTING_NONE || !g_prev || g_prev->c == ' ') {
return 0;
}
adjustment -= 32;
}
/* Small adjust if there is hinting. */
adjustment += g->lsb_delta - ((g_prev) ? g_prev->rsb_delta : 0);
@ -406,6 +398,35 @@ 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(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);
g = blf_glyph_ensure_subpixel(font, gc, g, *pen_x);
}
return g;
}
BLI_INLINE ft_pix blf_pen_advance(FontBLF *font, ft_pix v, ft_pix step)
{
if (font->flags & BLF_LEGACY_SPACING && font->flags & BLF_HINTING_NONE) {
/* DejaVu with no hinting, so truncate pen position to match old spacing. */
return (v + step) & ~63;
}
return v + step;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -419,7 +440,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;
@ -431,18 +452,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 = blf_pen_advance(font, pen_x, g->advance_x);
}
blf_batch_draw_end();
@ -473,7 +489,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;
@ -620,7 +636,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;
@ -631,17 +647,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 = blf_pen_advance(font, pen_x, g->advance_x);
}
if (r_info) {
@ -674,7 +686,7 @@ static bool blf_font_width_to_strlen_glyph_process(
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_pen_advance(font, *pen_x, g->advance_x);
/* When true, break the calling loop. */
return (ft_pix_to_int(*pen_x) >= width_i);
@ -694,8 +706,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;
}
@ -726,7 +737,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)
{
@ -735,7 +746,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);
}
@ -766,7 +777,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;
@ -776,13 +787,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 = blf_pen_advance(font, pen_x, g->advance_x);
const ft_pix gbox_xmin = pen_x;
const ft_pix gbox_xmax = pen_x_next;
@ -804,7 +814,6 @@ static void blf_font_boundbox_ex(FontBLF *font,
}
pen_x = pen_x_next;
g_prev = g;
}
if (box_xmin > box_xmax) {
@ -917,7 +926,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;
@ -930,13 +939,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);
@ -946,8 +953,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 = blf_pen_advance(font, pen_x, g->advance_x);
}
blf_glyph_cache_release(font);
@ -1055,7 +1061,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;
@ -1078,12 +1085,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).
@ -1093,7 +1099,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 = blf_pen_advance(font, pen_x, g->advance_x);
if (UNLIKELY((pen_x_next >= wrap.wrap_width) && (wrap.start != wrap.last[0]))) {
do_draw = true;
}
@ -1427,14 +1433,9 @@ 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 (font->face && STREQ(font->face->family_name, "DejaVu Sans")) {
/* So our legacy font can keep its too-tight spacing. */
font->flags |= BLF_LEGACY_SPACING;
}
if (FT_HAS_MULTIPLE_MASTERS(font)) {

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;
}
}
@ -997,6 +994,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,42 @@ 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);
}
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;
}
void blf_glyph_free(GlyphBLF *g)
{
if (g->bitmap) {

View File

@ -168,6 +168,11 @@ void blf_glyph_cache_clear(struct FontBLF *font);
*/
struct GlyphBLF *blf_glyph_ensure(struct FontBLF *font, struct GlyphCacheBLF *gc, uint charcode);
struct GlyphBLF *blf_glyph_ensure_subpixel(struct FontBLF *font,
struct GlyphCacheBLF *gc,
struct GlyphBLF *g,
int32_t pen_x);
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

@ -91,12 +91,6 @@ 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
@ -105,9 +99,6 @@ BLI_INLINE ft_pix ft_pix_round_advance(ft_pix v, ft_pix step)
#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 +152,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 +178,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

@ -1663,7 +1663,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) || (okwidth <= 0.0f));
return strwidth;
}