resource/font: lots of text rendering improvements

* Consistently use floats in the API and internally; don't round stuff
  arbitrarily.
* Use freetype's "normal" hinting algorithm.
* Implement subpixel positioning to avoid bad glyph spacing at small
  font sizes.
* Improved border control: inner and outer borders can be adjusted
  separately and per-font. (The "inner border" affects the "highlight"
  effect used in some shaders (blue channel)).
* Fix borders causing all glyphs to be misaligned (shifted down-right)
  by a constant offset.
* Various other improvements.
This commit is contained in:
Andrei Alexeyev 2023-05-09 02:50:48 +02:00
parent 18408a46fa
commit 9a476f58e2
No known key found for this signature in database
GPG key ID: 72D26128040B9690
6 changed files with 312 additions and 219 deletions

View file

@ -1,3 +1,7 @@
source = res/fonts/immortal.ttf
size = 35
border_inner = 0.3
border_outer = 0.8

View file

@ -1,3 +1,7 @@
source = res/fonts/Exo2-Regular-Taisei.ttf
size = 12
border_inner = 0.2
border_outer = 1.5

View file

@ -1,3 +1,7 @@
source = res/fonts/Exo2-Regular-Taisei.ttf
size = 17
border_inner = 0.4
border_outer = 1.25

View file

@ -50,16 +50,25 @@ ResourceHandler font_res_handler = {
},
};
// Handy routines for converting from fixed point
// From SDL_ttf
#define FT_FLOOR(X) (((X) & -64) / 64)
#define FT_CEIL(X) ((((X) + 63) & -64) / 64)
#undef FTERRORS_H_
#define FT_ERRORDEF(e, v, s) { e, s },
#undef FT_ERROR_START_LIST
#undef FT_ERROR_END_LIST
#define FIXED_CONVERSION(ft_typename, func_typename, factor) \
attr_unused INLINE float func_typename##_to_float(ft_typename x) { \
return x * (1.0f / factor); \
} \
\
attr_unused INLINE ft_typename float_to_##func_typename(float x) { \
return roundf(x * factor); \
}
FIXED_CONVERSION(FT_F26Dot6, f26dot6, 64.0f)
FIXED_CONVERSION(FT_Fixed, f16dot16, 65536.0f)
#define GLOBAL_RENDER_MODE FT_RENDER_MODE_NORMAL
static const struct ft_error_def {
FT_Error err_code;
const char *err_msg;
@ -68,7 +77,7 @@ static const struct ft_error_def {
{ 0, NULL }
};
static const char* ft_error_str(FT_Error err_code) {
static const char *ft_error_str(FT_Error err_code) {
for(const struct ft_error_def *e = ft_errors; e->err_msg; ++e) {
if(e->err_code == err_code) {
return e->err_msg;
@ -102,6 +111,8 @@ struct Font {
FT_Stroker stroker;
long base_face_idx;
int base_size;
float base_border_inner;
float base_border_outer;
ht_int2int_t charcodes_to_glyph_ofs;
ht_int2int_t ftindex_to_glyph_ofs;
FontMetrics metrics;
@ -127,13 +138,13 @@ static struct {
ResourceGroup rg;
} globals;
static double global_font_scale(void) {
static float global_font_scale(void) {
float w, h;
video_get_viewport_size(&w, &h);
return fmax(0.1, ((double)h / SCREEN_H) * config_get_float(CONFIG_TEXT_QUALITY));
return fmaxf(0.1, (h / SCREEN_H) * config_get_float(CONFIG_TEXT_QUALITY));
}
static void reload_fonts(double quality);
static void reload_fonts(float quality);
static bool fonts_event(SDL_Event *event, void *arg) {
if(!IS_TAISEI_EVENT(event->type)) {
@ -244,7 +255,7 @@ static void shutdown_fonts(void) {
SDL_DestroyMutex(globals.mutex.done_face);
}
static char* font_path(const char *name) {
static char *font_path(const char *name) {
return strjoin(FONT_PATH_PREFIX, name, FONT_EXTENSION, NULL);
}
@ -330,14 +341,14 @@ static FT_Face load_font_face(char *vfspath, long index) {
return face;
}
static FT_Error set_font_size(Font *fnt, uint pxsize, double scale) {
static FT_Error set_font_size(Font *fnt, float scale) {
FT_Error err = FT_Err_Ok;
uint pxsize = fnt->base_size;
assert(fnt != NULL);
assert(fnt->face != NULL);
FT_Fixed fixed_scale = round(scale * (2 << 15));
pxsize = FT_MulFix(pxsize * 64, fixed_scale);
pxsize = float_to_f26dot6(pxsize * scale);
if((err = FT_Set_Char_Size(fnt->face, 0, pxsize, 0, 0))) {
log_error("FT_Set_Char_Size(%u) failed: %s", pxsize, ft_error_str(err));
@ -353,15 +364,12 @@ static FT_Error set_font_size(Font *fnt, uint pxsize, double scale) {
return err;
}
FT_Stroker_Set(fnt->stroker, FT_MulFix(1.5 * 64, fixed_scale), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
// Based on SDL_ttf
FT_Face face = fnt->face;
fixed_scale = face->size->metrics.y_scale;
fnt->metrics.ascent = FT_CEIL(FT_MulFix(face->ascender, fixed_scale));
fnt->metrics.descent = FT_CEIL(FT_MulFix(face->descender, fixed_scale));
FT_Fixed fixed_scale = face->size->metrics.y_scale;
fnt->metrics.ascent = f26dot6_to_float(FT_MulFix(face->ascender, fixed_scale));
fnt->metrics.descent = f26dot6_to_float(FT_MulFix(face->descender, fixed_scale));
fnt->metrics.max_glyph_height = fnt->metrics.ascent - fnt->metrics.descent;
fnt->metrics.lineskip = FT_CEIL(FT_MulFix(face->height, fixed_scale));
fnt->metrics.lineskip = f26dot6_to_float(FT_MulFix(face->height, fixed_scale));
fnt->metrics.scale = scale;
return err;
@ -386,7 +394,7 @@ void font_set_kerning_enabled(Font *font, bool newval) {
#define SS_TEXTURE_TYPE TEX_TYPE_RGB_8
#define SS_TEXTURE_FLAGS 0
static SpriteSheet* add_spritesheet(SpriteSheetAnchor *spritesheets) {
static SpriteSheet *add_spritesheet(SpriteSheetAnchor *spritesheets) {
auto ss = ALLOC(SpriteSheet, {
.tex = r_texture_create(&(TextureParams) {
.width = SS_WIDTH,
@ -473,7 +481,7 @@ static bool add_glyph_to_spritesheets(Glyph *glyph, Pixmap *pixmap, SpriteSheetA
return add_glyph_to_spritesheet(glyph, pixmap, add_spritesheet(spritesheets));
}
static const char* pixmode_name(FT_Pixel_Mode mode) {
static const char *pixmode_name(FT_Pixel_Mode mode) {
switch(mode) {
case FT_PIXEL_MODE_NONE : return "FT_PIXEL_MODE_NONE";
case FT_PIXEL_MODE_MONO : return "FT_PIXEL_MODE_MONO";
@ -494,10 +502,14 @@ static void delete_spritesheet(SpriteSheetAnchor *spritesheets, SpriteSheet *ss)
mem_free(ss);
}
static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesheets) {
static Glyph *load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesheets) {
// log_debug("Loading glyph 0x%08x", gindex);
FT_Error err = FT_Load_Glyph(font->face, gindex, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_LIGHT);
FT_Render_Mode render_mode = GLOBAL_RENDER_MODE;
FT_Error err = FT_Load_Glyph(font->face, gindex,
FT_LOAD_NO_BITMAP |
FT_LOAD_TARGET_(render_mode) |
0);
if(err) {
log_warn("FT_Load_Glyph(%u) failed: %s", gindex, ft_error_str(err));
@ -506,11 +518,13 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
Glyph *glyph = dynarray_append(&font->glyphs);
glyph->metrics.bearing_x = FT_FLOOR(font->face->glyph->metrics.horiBearingX);
glyph->metrics.bearing_y = FT_FLOOR(font->face->glyph->metrics.horiBearingY);
glyph->metrics.width = FT_CEIL(font->face->glyph->metrics.width);
glyph->metrics.height = FT_CEIL(font->face->glyph->metrics.height);
glyph->metrics.advance = FT_CEIL(font->face->glyph->metrics.horiAdvance);
glyph->metrics.bearing_x = f26dot6_to_float(font->face->glyph->metrics.horiBearingX);
glyph->metrics.bearing_y = f26dot6_to_float(font->face->glyph->metrics.horiBearingY);
glyph->metrics.width = f26dot6_to_float(font->face->glyph->metrics.width);
glyph->metrics.height = f26dot6_to_float(font->face->glyph->metrics.height);
glyph->metrics.advance = f26dot6_to_float(font->face->glyph->metrics.horiAdvance);
glyph->metrics.lsb_delta = f26dot6_to_float(font->face->glyph->lsb_delta);
glyph->metrics.rsb_delta = f26dot6_to_float(font->face->glyph->rsb_delta);
FT_Glyph g_src = NULL, g_fill = NULL, g_border = NULL, g_inner = NULL;
FT_BitmapGlyph g_bm_fill = NULL, g_bm_border = NULL, g_bm_inner = NULL;
@ -521,7 +535,7 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
assert(g_src->format == FT_GLYPH_FORMAT_OUTLINE);
bool have_bitmap = FT_Glyph_To_Bitmap(&g_fill, FT_RENDER_MODE_LIGHT, NULL, true) == FT_Err_Ok;
bool have_bitmap = FT_Glyph_To_Bitmap(&g_fill, render_mode, NULL, true) == FT_Err_Ok;
if(have_bitmap) {
have_bitmap = ((FT_BitmapGlyph)g_fill)->bitmap.width > 0;
@ -531,11 +545,25 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
// Some glyphs may be invisible, but we still need the metrics data for them (e.g. space)
memset(&glyph->sprite, 0, sizeof(Sprite));
} else {
FT_Stroker_Set(font->stroker,
FT_MulFix(
float_to_f26dot6(font->base_border_outer),
font->face->size->metrics.y_scale),
FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0
);
FT_Glyph_StrokeBorder(&g_border, font->stroker, false, true);
FT_Glyph_To_Bitmap(&g_border, FT_RENDER_MODE_LIGHT, NULL, true);
FT_Glyph_To_Bitmap(&g_border, GLOBAL_RENDER_MODE, NULL, true);
FT_Stroker_Set(font->stroker,
FT_MulFix(
float_to_f26dot6(font->base_border_inner),
font->face->size->metrics.y_scale),
FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_BEVEL, 0
);
FT_Glyph_StrokeBorder(&g_inner, font->stroker, true, true);
FT_Glyph_To_Bitmap(&g_inner, FT_RENDER_MODE_LIGHT, NULL, true);
FT_Glyph_To_Bitmap(&g_inner, GLOBAL_RENDER_MODE, NULL, true);
g_bm_fill = (FT_BitmapGlyph)g_fill;
g_bm_border = (FT_BitmapGlyph)g_border;
@ -653,6 +681,14 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
}
mem_free(px.data.rg8);
float xpad = px.width - g_bm_fill->bitmap.width;
float ypad = px.height - g_bm_fill->bitmap.rows;
glyph->sprite.padding.extent.w = xpad;
glyph->sprite.padding.extent.h = ypad;
glyph->sprite.padding.offset.x = -xpad;
glyph->sprite.padding.offset.y = -ypad;
glyph->sprite.extent.as_cmplx += glyph->sprite.padding.extent.as_cmplx;
}
FT_Done_Glyph(g_src);
@ -664,7 +700,7 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
return glyph;
}
static Glyph* get_glyph(Font *fnt, charcode_t cp) {
static Glyph *get_glyph(Font *fnt, charcode_t cp) {
int64_t ofs;
if(!ht_lookup(&fnt->charcodes_to_glyph_ofs, cp, &ofs)) {
@ -741,8 +777,10 @@ static void free_font_resources(Font *font) {
static void finish_reload(ResourceLoadState *st);
void load_font(ResourceLoadState *st) {
Font font;
memset(&font, 0, sizeof(font));
Font font = {
.base_border_inner = 0.5f,
.base_border_outer = 1.5f,
};
SDL_RWops *rw = res_open_file(st, st->path, VFS_MODE_READ);
@ -756,6 +794,8 @@ void load_font(ResourceLoadState *st) {
{ "source", .out_str = &font.source_path },
{ "size", .out_int = &font.base_size },
{ "face", .out_long = &font.base_face_idx },
{ "border_inner", .out_float = &font.base_border_inner, },
{ "border_outer", .out_float = &font.base_border_outer, },
{ NULL }
});
@ -782,7 +822,7 @@ void load_font(ResourceLoadState *st) {
res_load_failed(st);
}
if(set_font_size(&font, font.base_size, global_font_scale())) {
if(set_font_size(&font, global_font_scale())) {
free_font_resources(&font);
res_load_failed(st);
return;
@ -815,47 +855,97 @@ void unload_font(void *vfont) {
}
struct rlfonts_arg {
double quality;
float quality;
};
attr_nonnull(1)
static void reload_font(Font *font, double quality) {
static void reload_font(Font *font, float quality) {
if(font->metrics.scale != quality) {
wipe_glyph_cache(font);
set_font_size(font, font->base_size, quality);
set_font_size(font, quality);
}
}
static void* reload_font_callback(const char *name, Resource *res, void *varg) {
static void *reload_font_callback(const char *name, Resource *res, void *varg) {
struct rlfonts_arg *a = varg;
reload_font((Font*)res->data, a->quality);
return NULL;
}
static void reload_fonts(double quality) {
static void reload_fonts(float quality) {
res_for_each(RES_FONT, reload_font_callback, &(struct rlfonts_arg) { quality });
}
static inline int apply_kerning(Font *font, uint prev_index, Glyph *gthis) {
static inline float apply_kerning(Font *font, uint prev_index, Glyph *gthis) {
FT_Vector kvec;
if(!font_get_kerning_enabled(font) || prev_index == 0) {
return 0;
}
if(!FT_Get_Kerning(font->face, prev_index, gthis->ft_index, FT_KERNING_DEFAULT, &kvec)) {
return kvec.x >> 6;
if(!FT_Get_Kerning(font->face, prev_index, gthis->ft_index, FT_KERNING_UNFITTED, &kvec)) {
return f26dot6_to_float(kvec.x);
}
return 0;
}
int text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) {
static float apply_subpixel_shift(Glyph *g, float prev_rsb_delta) {
float shift = 0;
float diff = prev_rsb_delta - g->metrics.lsb_delta;
#if 0
if(diff > 32 * FIXED_TO_FLOAT) {
shift = -FIXED_TO_FLOAT;
} else if(diff < -31 * FIXED_TO_FLOAT) {
shift = FIXED_TO_FLOAT;
}
#else
shift -= diff;
#endif
return shift;
}
typedef struct Cursor {
Font *font;
float x;
float prev_rsb_delta;
uint prev_glyph_idx;
} Cursor;
static Cursor cursor_init(Font *font) {
return (Cursor) { .font = font };
}
static void cursor_reset(Cursor *c) {
c->x = 0;
c->prev_rsb_delta = 0;
c->prev_glyph_idx = 0;
}
static float cursor_advance(Cursor *c, Glyph *g) {
if(g == NULL) {
return c->x;
}
c->x += apply_kerning(c->font, c->prev_glyph_idx, g);
c->x += apply_subpixel_shift(g, c->prev_rsb_delta);
float startpos = c->x;
c->x += g->metrics.advance;
c->prev_rsb_delta = g->metrics.rsb_delta;
c->prev_glyph_idx = g->ft_index;
return startpos;
}
float text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) {
const uint32_t *tptr = text;
uint prev_glyph_idx = 0;
uint numlines = 0;
int x = 0;
int width = 0;
float width = 0;
Cursor c = cursor_init(font);
while(*tptr) {
uint32_t uchar = *tptr++;
@ -865,45 +955,33 @@ int text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) {
break;
}
if(x > width) {
width = x;
}
x = 0;
cursor_reset(&c);
continue;
}
Glyph *glyph = get_glyph(font, uchar);
cursor_advance(&c, get_glyph(font, uchar));
if(glyph == NULL) {
continue;
if(c.x > width) {
width = c.x;
}
x += apply_kerning(font, prev_glyph_idx, glyph);
x += glyph->metrics.advance;
prev_glyph_idx = glyph->ft_index;
}
if(x > width) {
width = x;
}
return width;
}
int text_width_raw(Font *font, const char *text, uint maxlines) {
float text_width_raw(Font *font, const char *text, uint maxlines) {
uint32_t buf[strlen(text) + 1];
utf8_to_ucs4(text, ARRAY_SIZE(buf), buf);
return text_ucs4_width_raw(font, buf, maxlines);
}
void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, BBox *bbox) {
void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, TextBBox *bbox) {
const uint32_t *tptr = text;
uint prev_glyph_idx = 0;
uint numlines = 0;
memset(bbox, 0, sizeof(*bbox));
int x = 0, y = 0;
float y = 0;
Cursor c = cursor_init(font);
while(*tptr) {
uint32_t uchar = *tptr++;
@ -913,9 +991,8 @@ void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, BBox *bbox)
break;
}
x = 0;
y += font->metrics.lineskip;
cursor_reset(&c);
continue;
}
@ -925,46 +1002,44 @@ void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, BBox *bbox)
continue;
}
x += apply_kerning(font, prev_glyph_idx, glyph);
float x = cursor_advance(&c, glyph);
bbox->x.max = fmaxf(bbox->x.max, c.x);
int g_x0 = x + glyph->metrics.bearing_x;
int g_x1 = g_x0 + imax(glyph->metrics.width, glyph->sprite.w);
FloatOffset ofs = glyph->sprite.padding.offset;
bbox->x.max = imax(bbox->x.max, g_x0);
bbox->x.max = imax(bbox->x.max, g_x1);
bbox->x.min = imin(bbox->x.min, g_x0);
bbox->x.min = imin(bbox->x.min, g_x1);
float g_x0 = x + glyph->metrics.bearing_x + ofs.x;
float g_x1 = g_x0 + fmaxf(glyph->metrics.width, glyph->sprite.w);
int g_y0 = y - glyph->metrics.bearing_y;
int g_y1 = g_y0 + imax(glyph->metrics.height, glyph->sprite.h);
bbox->x.max = fmaxf(bbox->x.max, g_x0);
bbox->x.max = fmaxf(bbox->x.max, g_x1);
bbox->x.min = fminf(bbox->x.min, g_x0);
bbox->x.min = fminf(bbox->x.min, g_x1);
bbox->y.max = imax(bbox->y.max, g_y0);
bbox->y.max = imax(bbox->y.max, g_y1);
bbox->y.min = imin(bbox->y.min, g_y0);
bbox->y.min = imin(bbox->y.min, g_y1);
float g_y0 = y - glyph->metrics.bearing_y + ofs.y;
float g_y1 = g_y0 + fmaxf(glyph->metrics.height, glyph->sprite.h);
prev_glyph_idx = glyph->ft_index;
x += glyph->metrics.advance;
bbox->x.max = imax(bbox->x.max, x);
bbox->y.max = fmaxf(bbox->y.max, g_y0);
bbox->y.max = fmaxf(bbox->y.max, g_y1);
bbox->y.min = fminf(bbox->y.min, g_y0);
bbox->y.min = fminf(bbox->y.min, g_y1);
}
}
void text_bbox(Font *font, const char *text, uint maxlines, BBox *bbox) {
void text_bbox(Font *font, const char *text, uint maxlines, TextBBox *bbox) {
uint32_t buf[strlen(text) + 1];
utf8_to_ucs4(text, ARRAY_SIZE(buf), buf);
text_ucs4_bbox(font, buf, maxlines, bbox);
}
double text_ucs4_width(Font *font, const uint32_t *text, uint maxlines) {
float text_ucs4_width(Font *font, const uint32_t *text, uint maxlines) {
return text_ucs4_width_raw(font, text, maxlines) / font->metrics.scale;
}
double text_width(Font *font, const char *text, uint maxlines) {
float text_width(Font *font, const char *text, uint maxlines) {
return text_width_raw(font, text, maxlines) / font->metrics.scale;
}
int text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) {
float text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) {
// FIXME: I'm not sure this is correct. Perhaps it should consider max_glyph_height at least?
uint text_lines = 1;
@ -983,22 +1058,24 @@ int text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) {
return font->metrics.lineskip * text_lines;
}
int text_height_raw(Font *font, const char *text, uint maxlines) {
float text_height_raw(Font *font, const char *text, uint maxlines) {
uint32_t buf[strlen(text) + 1];
utf8_to_ucs4(text, ARRAY_SIZE(buf), buf);
return text_ucs4_height_raw(font, buf, maxlines);
}
double text_ucs4_height(Font *font, const uint32_t *text, uint maxlines) {
float text_ucs4_height(Font *font, const uint32_t *text, uint maxlines) {
return text_ucs4_height_raw(font, text, maxlines) / font->metrics.scale;
}
double text_height(Font *font, const char *text, uint maxlines) {
float text_height(Font *font, const char *text, uint maxlines) {
return text_height_raw(font, text, maxlines) / font->metrics.scale;
}
static inline void adjust_xpos(Font *font, const uint32_t *ucs4text, Alignment align, double x_orig, double *x) {
double line_width;
static inline void adjust_xpos(
Font *font, const uint32_t *ucs4text, Alignment align, float x_orig, float *x
) {
float line_width;
switch(align) {
case ALIGN_LEFT: {
@ -1020,7 +1097,7 @@ static inline void adjust_xpos(Font *font, const uint32_t *ucs4text, Alignment a
}
}
ShaderProgram* text_get_default_shader(void) {
ShaderProgram *text_get_default_shader(void) {
return globals.default_shader;
}
@ -1048,7 +1125,7 @@ static void set_batch_texture(SpriteStateParams *stp, Texture *tex) {
}
attr_nonnull(1, 2, 3)
static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextParams *params) {
static float _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextParams *params) {
SpriteStateParams batch_state_params;
memcpy(batch_state_params.aux_textures, params->aux_textures, sizeof(batch_state_params.aux_textures));
@ -1067,15 +1144,17 @@ static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextPa
batch_state_params.primary_texture = NULL;
BBox bbox;
double x = params->pos.x;
double y = params->pos.y;
double scale = font->metrics.scale;
double iscale = 1 / scale;
TextBBox bbox;
float y = params->pos.y;
float scale = font->metrics.scale;
float iscale = 1.0f / scale;
Cursor c = cursor_init(font);
c.x = params->pos.x;
struct {
struct { double min, max; } x, y;
double w, h;
struct { float min, max; } x, y;
float w, h;
} overlay;
text_ucs4_bbox(font, ucs4text, 0, &bbox);
@ -1103,14 +1182,14 @@ static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextPa
r_mat_tex_current(mat_texture);
r_mat_mv_current(mat_model);
glm_translate(mat_model, (vec3) { x, y } );
glm_translate(mat_model, (vec3) { c.x, y } );
glm_scale(mat_model, (vec3) { iscale, iscale, 1 } );
double orig_x = x;
double orig_y = y;
x = y = 0;
float orig_x = c.x;
float orig_y = y;
c.x = y = 0;
adjust_xpos(font, ucs4text, params->align, 0, &x);
adjust_xpos(font, ucs4text, params->align, 0, &c.x);
if(params->overlay_projection) {
FloatRect *op = params->overlay_projection;
@ -1119,8 +1198,8 @@ static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextPa
overlay.y.min = (op->y - orig_y) * scale;
overlay.y.max = overlay.y.min + op->h * scale;
} else {
overlay.x.min = bbox.x.min + x;
overlay.x.max = bbox.x.max + x;
overlay.x.min = bbox.x.min + c.x;
overlay.x.max = bbox.x.max + c.x;
overlay.y.min = bbox.y.min - font->metrics.descent;
overlay.y.max = bbox.y.max - font->metrics.descent;
}
@ -1140,14 +1219,14 @@ static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextPa
texmat_offset_sign = 1;
}
uint prev_glyph_idx = 0;
const uint32_t *tptr = ucs4text;
while(*tptr) {
uint32_t uchar = *tptr++;
if(uchar == '\n') {
adjust_xpos(font, tptr, params->align, 0, &x);
cursor_reset(&c);
adjust_xpos(font, tptr, params->align, 0, &c.x);
y += font->metrics.lineskip;
continue;
}
@ -1158,46 +1237,53 @@ static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextPa
continue;
}
x += apply_kerning(font, prev_glyph_idx, glyph);
float x = cursor_advance(&c, glyph);
if(glyph->sprite.tex != NULL) {
Sprite *spr = &glyph->sprite;
set_batch_texture(&batch_state_params, spr->tex);
SpriteInstanceAttribs attribs;
attribs.rgba = color;
attribs.custom = shader_params;
float g_x = x + glyph->metrics.bearing_x + spr->w * 0.5;
float g_y = y - glyph->metrics.bearing_y + spr->h * 0.5 - font->metrics.descent;
glm_translate_to(mat_texture, (vec3) { g_x - spr->w * 0.5, g_y * texmat_offset_sign + overlay.h - spr->h * 0.5 }, attribs.tex_transform );
glm_scale(attribs.tex_transform, (vec3) { spr->w, spr->h, 1.0 });
glm_translate_to(mat_model, (vec3) { g_x, g_y }, attribs.mv_transform);
glm_scale(attribs.mv_transform, (vec3) { spr->w, spr->h, 1.0 } );
attribs.texrect = spr->tex_area;
// NOTE: Glyphs have their sprite w/h unadjusted for scale.
attribs.sprite_size.w = spr->w * iscale;
attribs.sprite_size.h = spr->h * iscale;
if(params->glyph_callback.func != NULL) {
params->glyph_callback.func(font, uchar, &attribs, params->glyph_callback.userdata);
}
r_sprite_batch_add_instance(&attribs);
if(glyph->sprite.tex == NULL) {
continue;
}
x += glyph->metrics.advance;
prev_glyph_idx = glyph->ft_index;
Sprite *spr = &glyph->sprite;
set_batch_texture(&batch_state_params, spr->tex);
SpriteInstanceAttribs attribs;
attribs.rgba = color;
attribs.custom = shader_params;
FloatOffset ofs = spr->padding.offset;
FloatExtent imgdims = spr->extent;
imgdims.as_cmplx -= spr->padding.extent.as_cmplx;
float g_x = x + glyph->metrics.bearing_x + spr->w * 0.5f + ofs.x;
float g_y = y - glyph->metrics.bearing_y + spr->h * 0.5f - font->metrics.descent + ofs.y;
glm_translate_to(mat_texture, (vec3) {
g_x - imgdims.w * 0.5f,
g_y * texmat_offset_sign + overlay.h - imgdims.h * 0.5f
}, attribs.tex_transform);
glm_scale(attribs.tex_transform, (vec3) { imgdims.w, imgdims.h, 1.0 });
glm_translate_to(mat_model, (vec3) { g_x, g_y }, attribs.mv_transform);
glm_scale(attribs.mv_transform, (vec3) { imgdims.w, imgdims.h, 1.0 } );
attribs.texrect = spr->tex_area;
// NOTE: Glyphs have their sprite w/h unadjusted for scale.
attribs.sprite_size.w = imgdims.w * iscale;
attribs.sprite_size.h = imgdims.h * iscale;
if(params->glyph_callback.func != NULL) {
params->glyph_callback.func(
font, uchar, &attribs, params->glyph_callback.userdata);
}
r_sprite_batch_add_instance(&attribs);
}
return x * iscale;
return c.x * iscale;
}
static double _text_draw(Font *font, const char *text, const TextParams *params) {
static float _text_draw(Font *font, const char *text, const TextParams *params) {
uint32_t buf[strlen(text) + 1];
utf8_to_ucs4(text, ARRAY_SIZE(buf), buf);
@ -1208,11 +1294,11 @@ static double _text_draw(Font *font, const char *text, const TextParams *params)
return _text_ucs4_draw(font, buf, params);
}
double text_draw(const char *text, const TextParams *params) {
float text_draw(const char *text, const TextParams *params) {
return _text_draw(font_from_params(params), text, params);
}
double text_ucs4_draw(const uint32_t *text, const TextParams *params) {
float text_ucs4_draw(const uint32_t *text, const TextParams *params) {
Font *font = font_from_params(params);
if(params->max_width > 0) {
@ -1225,18 +1311,18 @@ double text_ucs4_draw(const uint32_t *text, const TextParams *params) {
return _text_ucs4_draw(font, text, params);
}
double text_draw_wrapped(const char *text, double max_width, const TextParams *params) {
float text_draw_wrapped(const char *text, float max_width, const TextParams *params) {
Font *font = font_from_params(params);
char buf[strlen(text) * 2 + 1];
text_wrap(font, text, max_width, buf, sizeof(buf));
return _text_draw(font, buf, params);
}
void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbox) {
void text_render(const char *text, Font *font, Sprite *out_sprite, TextBBox *out_bbox) {
text_bbox(font, text, 0, out_bbox);
int bbox_width = out_bbox->x.max - out_bbox->x.min;
int bbox_height = out_bbox->y.max - out_bbox->y.min;
float bbox_width = out_bbox->x.max - out_bbox->x.min;
float bbox_height = out_bbox->y.max - out_bbox->y.min;
if(bbox_height < font->metrics.max_glyph_height) {
out_bbox->y.min -= font->metrics.max_glyph_height - bbox_height;
@ -1245,8 +1331,8 @@ void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbo
Texture *tex = globals.render_tex;
int tex_new_w = bbox_width; // max(tex->w, bbox_width);
int tex_new_h = bbox_height; // max(tex->h, bbox_height);
uint tex_new_w = ceilf(bbox_width); // max(tex->w, bbox_width);
uint tex_new_h = ceilf(bbox_height); // max(tex->h, bbox_height);
uint tex_w, tex_h;
r_texture_get_size(tex, 0, &tex_w, &tex_h);
@ -1265,12 +1351,7 @@ void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbo
params.mipmaps = 0;
globals.render_tex = tex = r_texture_create(&params);
#ifdef DEBUG
char buf[128];
snprintf(buf, sizeof(buf), "Font render texture");
r_texture_set_debug_label(tex, buf);
#endif
r_texture_set_debug_label(tex, "Font render texture");
r_framebuffer_attach(globals.render_buf, tex, 0, FRAMEBUFFER_ATTACH_COLOR0);
r_framebuffer_viewport(globals.render_buf, 0, 0, tex_new_w, tex_new_h);
}
@ -1291,7 +1372,7 @@ void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbo
// HACK: Coordinates are in texel space, font scale must not be used.
// This probably should be exposed in the text_draw API.
double fontscale = font->metrics.scale;
float fontscale = font->metrics.scale;
font->metrics.scale = 1;
text_draw(text, &(TextParams) {
@ -1311,15 +1392,15 @@ void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbo
r_state_pop();
out_sprite->tex = tex;
out_sprite->tex_area.w = 1;
out_sprite->tex_area.h = 1;
out_sprite->tex_area.w = bbox_width / tex_new_w;
out_sprite->tex_area.h = bbox_height / tex_new_h;
out_sprite->tex_area.x = 0;
out_sprite->tex_area.y = 0;
out_sprite->w = bbox_width / font->metrics.scale;
out_sprite->h = bbox_height / font->metrics.scale;
}
void text_ucs4_shorten(Font *font, uint32_t *text, double width) {
void text_ucs4_shorten(Font *font, uint32_t *text, float width) {
assert(!ucs4chr(text, '\n'));
if(text_ucs4_width(font, text, 0) <= width) {
@ -1339,7 +1420,7 @@ void text_ucs4_shorten(Font *font, uint32_t *text, double width) {
} while(text_ucs4_width(font, text, 0) > width);
}
void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufsize) {
void text_wrap(Font *font, const char *src, float width, char *buf, size_t bufsize) {
assert(bufsize > strlen(src) + 1);
assert(width > 0);
@ -1352,7 +1433,7 @@ void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufs
*buf = 0;
while((next = strtok_r(NULL, " \t", &sptr))) {
int curwidth;
float curwidth;
if(!*next) {
continue;
@ -1369,7 +1450,7 @@ void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufs
strcat(tmpbuf, " ");
strcat(tmpbuf, next);
double totalwidth = text_width(font, tmpbuf, 0);
float totalwidth = text_width(font, tmpbuf, 0);
if(totalwidth > width) {
if(curwidth == 0) {
@ -1392,23 +1473,23 @@ void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufs
}
}
const FontMetrics* font_get_metrics(Font *font) {
const FontMetrics *font_get_metrics(Font *font) {
return &font->metrics;
}
double font_get_lineskip(Font *font) {
float font_get_lineskip(Font *font) {
return font->metrics.lineskip / font->metrics.scale;
}
double font_get_ascent(Font *font) {
float font_get_ascent(Font *font) {
return font->metrics.descent / font->metrics.scale;
}
double font_get_descent(Font *font) {
float font_get_descent(Font *font) {
return font->metrics.descent / font->metrics.scale;
}
const GlyphMetrics* font_get_char_metrics(Font *font, charcode_t c) {
const GlyphMetrics *font_get_char_metrics(Font *font, charcode_t c) {
Glyph *g = get_glyph(font, c);
if(!g) {

View file

@ -24,36 +24,37 @@ typedef ulong charcode_t;
typedef struct Font Font;
typedef struct FontMetrics {
int ascent;
int descent;
int max_glyph_height;
int lineskip;
double scale;
float ascent;
float descent;
float max_glyph_height;
float lineskip;
float scale;
} FontMetrics;
typedef struct GlyphMetrics {
int bearing_x;
int bearing_y;
int width;
int height;
int advance;
float bearing_x;
float bearing_y;
float width;
float height;
float advance;
float lsb_delta;
float rsb_delta;
} GlyphMetrics;
// TODO: maybe move this into util/geometry.h
typedef struct BBox {
typedef struct TextBBox {
struct {
int min;
int max;
float min;
float max;
} x;
struct {
int min;
int max;
float min;
float max;
} y;
} BBox;
} TextBBox;
// FIXME: this is a quite crude low-level-ish hack, and probably should be replaced with some kind of markup system.
typedef int (*GlyphDrawCallback)(Font *font, charcode_t charcode, SpriteInstanceAttribs *spr_instance, void *userdata);
typedef void (*GlyphDrawCallback)(Font *font, charcode_t charcode, SpriteInstanceAttribs *spr_instance, void *userdata);
typedef struct TextParams {
const char *font;
@ -65,13 +66,13 @@ typedef struct TextParams {
void *userdata;
} glyph_callback;
union {
struct { double x, y; };
cmplx as_cmplx;
struct { float x, y; };
cmplxf as_cmplx;
} pos;
const Color *color;
const ShaderCustomParams *shader_params;
Texture *aux_textures[R_NUM_SPRITE_AUX_TEXTURES];
double max_width;
float max_width;
FloatRect *overlay_projection;
BlendMode blend;
Alignment align;
@ -80,42 +81,42 @@ typedef struct TextParams {
DEFINE_RESOURCE_GETTER(Font, res_font, RES_FONT)
DEFINE_OPTIONAL_RESOURCE_GETTER(Font, res_font_optional, RES_FONT)
ShaderProgram* text_get_default_shader(void)
ShaderProgram *text_get_default_shader(void)
attr_returns_nonnull;
const FontMetrics* font_get_metrics(Font *font) attr_nonnull(1) attr_returns_nonnull;
const FontMetrics *font_get_metrics(Font *font) attr_nonnull(1) attr_returns_nonnull;
double font_get_ascent(Font *font) attr_nonnull(1);
double font_get_descent(Font *font) attr_nonnull(1);
double font_get_lineskip(Font *font) attr_nonnull(1);
float font_get_ascent(Font *font) attr_nonnull(1);
float font_get_descent(Font *font) attr_nonnull(1);
float font_get_lineskip(Font *font) attr_nonnull(1);
const GlyphMetrics* font_get_char_metrics(Font *font, charcode_t c) attr_nonnull(1);
const GlyphMetrics *font_get_char_metrics(Font *font, charcode_t c) attr_nonnull(1);
double text_draw(const char *text, const TextParams *params) attr_nonnull(1, 2);
double text_ucs4_draw(const uint32_t *text, const TextParams *params) attr_nonnull(1, 2);
float text_draw(const char *text, const TextParams *params) attr_nonnull(1, 2);
float text_ucs4_draw(const uint32_t *text, const TextParams *params) attr_nonnull(1, 2);
double text_draw_wrapped(const char *text, double max_width, const TextParams *params) attr_nonnull(1, 3);
float text_draw_wrapped(const char *text, float max_width, const TextParams *params) attr_nonnull(1, 3);
void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbox) attr_nonnull(1, 2, 3, 4);
void text_render(const char *text, Font *font, Sprite *out_sprite, TextBBox *out_bbox) attr_nonnull(1, 2, 3, 4);
void text_ucs4_shorten(Font *font, uint32_t *text, double width) attr_nonnull(1, 2);
void text_ucs4_shorten(Font *font, uint32_t *text, float width) attr_nonnull(1, 2);
void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufsize) attr_nonnull(1, 2, 4);
void text_wrap(Font *font, const char *src, float width, char *buf, size_t bufsize) attr_nonnull(1, 2, 4);
void text_bbox(Font *font, const char *text, uint maxlines, BBox *bbox) attr_nonnull(1, 2, 4);
void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, BBox *bbox) attr_nonnull(1, 2, 4);
void text_bbox(Font *font, const char *text, uint maxlines, TextBBox *bbox) attr_nonnull(1, 2, 4);
void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, TextBBox *bbox) attr_nonnull(1, 2, 4);
int text_width_raw(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
int text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
float text_width_raw(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
float text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
double text_width(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
double text_ucs4_width(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
float text_width(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
float text_ucs4_width(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
int text_height_raw(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
int text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
float text_height_raw(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
float text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
double text_height(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
double text_ucs4_height(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
float text_height(Font *font, const char *text, uint maxlines) attr_nonnull(1, 2);
float text_ucs4_height(Font *font, const uint32_t *text, uint maxlines) attr_nonnull(1, 2);
// FIXME: come up with a better, stateless API for this
bool font_get_kerning_available(Font *font) attr_nonnull(1);

View file

@ -477,7 +477,7 @@ static void apply_shader_rules(ShaderRule *shaderrules, FBPair *fbos) {
static void draw_wall_of_text(float f, const char *txt) {
Sprite spr;
BBox bbox;
TextBBox bbox;
char buf[strlen(txt) + 4];
memcpy(buf, txt, sizeof(buf) - 4);
@ -1071,7 +1071,7 @@ struct glyphcb_state {
Color *color1, *color2;
};
static int draw_numeric_callback(Font *font, charcode_t charcode, SpriteInstanceAttribs *spr_attribs, void *userdata) {
static void draw_numeric_callback(Font *font, charcode_t charcode, SpriteInstanceAttribs *spr_attribs, void *userdata) {
struct glyphcb_state *st = userdata;
if(charcode != '0' && charcode != ',') {
@ -1079,7 +1079,6 @@ static int draw_numeric_callback(Font *font, charcode_t charcode, SpriteInstance
}
spr_attribs->rgba = *st->color1;
return 0;
}
static inline void stage_draw_hud_power_value(float xpos, float ypos) {