2010-11-14 13:24:56 +01:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2017-02-11 02:24:47 +01:00
|
|
|
* See COPYING for further information.
|
2011-03-05 13:44:21 +01:00
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
2019-07-03 20:00:56 +02:00
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
2010-11-14 13:24:56 +01:00
|
|
|
*/
|
|
|
|
|
2017-11-25 20:45:11 +01:00
|
|
|
#include "taisei.h"
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
#include <ft2build.h>
|
|
|
|
#include FT_FREETYPE_H
|
2019-01-04 23:59:39 +01:00
|
|
|
#include FT_STROKER_H
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2010-11-14 13:24:56 +01:00
|
|
|
#include "font.h"
|
2018-06-29 23:36:51 +02:00
|
|
|
#include "config.h"
|
|
|
|
#include "events.h"
|
|
|
|
#include "renderer/api.h"
|
2019-10-03 01:49:10 +02:00
|
|
|
#include "util.h"
|
|
|
|
#include "util/glm.h"
|
|
|
|
#include "util/graphics.h"
|
|
|
|
#include "util/rectpack.h"
|
|
|
|
#include "video.h"
|
2020-04-05 04:51:00 +02:00
|
|
|
#include "dynarray.h"
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
static void init_fonts(void);
|
|
|
|
static void post_init_fonts(void);
|
|
|
|
static void shutdown_fonts(void);
|
2020-06-09 02:01:53 +02:00
|
|
|
static char *font_path(const char*);
|
2018-06-29 23:36:51 +02:00
|
|
|
static bool check_font_path(const char*);
|
2020-06-09 02:01:53 +02:00
|
|
|
static void load_font(ResourceLoadState *st);
|
2018-06-29 23:36:51 +02:00
|
|
|
static void unload_font(void*);
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
ResourceHandler font_res_handler = {
|
|
|
|
.type = RES_FONT,
|
|
|
|
.typename = "font",
|
|
|
|
.subdir = FONT_PATH_PREFIX,
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
.procs = {
|
|
|
|
.init = init_fonts,
|
|
|
|
.post_init = post_init_fonts,
|
|
|
|
.shutdown = shutdown_fonts,
|
|
|
|
.find = font_path,
|
|
|
|
.check = check_font_path,
|
2020-06-09 02:01:53 +02:00
|
|
|
.load = load_font,
|
2018-06-29 23:36:51 +02:00
|
|
|
.unload = unload_font,
|
|
|
|
},
|
|
|
|
};
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
// 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
|
|
|
|
|
|
|
|
static const struct ft_error_def {
|
|
|
|
FT_Error err_code;
|
|
|
|
const char *err_msg;
|
|
|
|
} ft_errors[] = {
|
|
|
|
#include FT_ERRORS_H
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
return "Unknown error";
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
typedef struct SpriteSheet {
|
|
|
|
LIST_INTERFACE(struct SpriteSheet);
|
2018-09-14 09:37:20 +02:00
|
|
|
Texture *tex;
|
2018-06-29 23:36:51 +02:00
|
|
|
RectPack *rectpack;
|
|
|
|
uint glyphs;
|
|
|
|
} SpriteSheet;
|
2017-12-28 06:31:39 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
typedef LIST_ANCHOR(SpriteSheet) SpriteSheetAnchor;
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2019-08-02 19:50:41 +02:00
|
|
|
typedef struct Glyph {
|
|
|
|
Sprite sprite;
|
|
|
|
SpriteSheet *spritesheet;
|
|
|
|
RectPackSection *spritesheet_section;
|
|
|
|
GlyphMetrics metrics;
|
|
|
|
ulong ft_index;
|
|
|
|
} Glyph;
|
|
|
|
|
2017-12-14 04:48:30 +01:00
|
|
|
struct Font {
|
2018-06-08 06:49:40 +02:00
|
|
|
char *source_path;
|
2020-04-05 04:51:00 +02:00
|
|
|
DYNAMIC_ARRAY(Glyph) glyphs;
|
2018-06-29 23:36:51 +02:00
|
|
|
FT_Face face;
|
2019-01-04 23:59:39 +01:00
|
|
|
FT_Stroker stroker;
|
2018-06-29 23:36:51 +02:00
|
|
|
long base_face_idx;
|
2018-06-08 06:49:40 +02:00
|
|
|
int base_size;
|
2018-06-29 23:36:51 +02:00
|
|
|
ht_int2int_t charcodes_to_glyph_ofs;
|
|
|
|
ht_int2int_t ftindex_to_glyph_ofs;
|
|
|
|
FontMetrics metrics;
|
2019-01-04 23:59:39 +01:00
|
|
|
bool kerning;
|
2018-09-14 09:37:20 +02:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
char debug_label[64];
|
|
|
|
#endif
|
2017-12-14 04:48:30 +01:00
|
|
|
};
|
2010-11-14 13:24:56 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
static struct {
|
|
|
|
FT_Library lib;
|
|
|
|
ShaderProgram *default_shader;
|
2018-09-14 09:37:20 +02:00
|
|
|
Texture *render_tex;
|
|
|
|
Framebuffer *render_buf;
|
2019-09-17 13:35:29 +02:00
|
|
|
SpriteSheetAnchor spritesheets;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
struct {
|
|
|
|
SDL_mutex *new_face;
|
|
|
|
SDL_mutex *done_face;
|
|
|
|
} mutex;
|
|
|
|
} globals;
|
2010-11-14 13:24:56 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
static double global_font_scale(void) {
|
2019-04-04 01:43:03 +02:00
|
|
|
float w, h;
|
2018-07-04 10:36:16 +02:00
|
|
|
video_get_viewport_size(&w, &h);
|
2020-05-09 08:16:07 +02:00
|
|
|
return fmax(0.1, ((double)h / SCREEN_H) * config_get_float(CONFIG_TEXT_QUALITY));
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void reload_fonts(double quality);
|
2018-06-08 06:49:40 +02:00
|
|
|
|
|
|
|
static bool fonts_event(SDL_Event *event, void *arg) {
|
2018-07-04 10:36:16 +02:00
|
|
|
if(!IS_TAISEI_EVENT(event->type)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(TAISEI_EVENT(event->type)) {
|
|
|
|
case TE_VIDEO_MODE_CHANGED: {
|
|
|
|
reload_fonts(global_font_scale());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case TE_CONFIG_UPDATED: {
|
|
|
|
if(event->user.code == CONFIG_TEXT_QUALITY) {
|
|
|
|
ConfigValue *val = event->user.data1;
|
2020-05-09 08:16:07 +02:00
|
|
|
val->f = fmax(0.1, val->f);
|
2018-07-04 10:36:16 +02:00
|
|
|
reload_fonts(global_font_scale());
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-06-08 06:49:40 +02:00
|
|
|
|
2018-07-04 10:36:16 +02:00
|
|
|
return false;
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void try_create_mutex(SDL_mutex **mtx) {
|
|
|
|
if((*mtx = SDL_CreateMutex()) == NULL) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_sdl_error(LOG_WARN, "SDL_CreateMutex");
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void init_fonts(void) {
|
2018-06-29 23:36:51 +02:00
|
|
|
FT_Error err;
|
|
|
|
|
|
|
|
try_create_mutex(&globals.mutex.new_face);
|
|
|
|
try_create_mutex(&globals.mutex.done_face);
|
|
|
|
|
|
|
|
if((err = FT_Init_FreeType(&globals.lib))) {
|
|
|
|
log_fatal("FT_Init_FreeType() failed: %s", ft_error_str(err));
|
|
|
|
}
|
2018-06-08 06:49:40 +02:00
|
|
|
|
|
|
|
events_register_handler(&(EventHandler) {
|
2018-07-04 10:36:16 +02:00
|
|
|
fonts_event, NULL, EPRIO_SYSTEM,
|
2018-06-08 06:49:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
preload_resources(RES_FONT, RESF_PERMANENT,
|
|
|
|
"standard",
|
|
|
|
NULL);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
// WARNING: Preloading the default shader here is unsafe.
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
globals.render_tex = r_texture_create(&(TextureParams) {
|
2018-06-29 23:36:51 +02:00
|
|
|
.filter = { TEX_FILTER_LINEAR, TEX_FILTER_LINEAR },
|
|
|
|
.wrap = { TEX_WRAP_CLAMP, TEX_WRAP_CLAMP },
|
2019-01-04 23:59:39 +01:00
|
|
|
.type = TEX_TYPE_RGBA,
|
2018-06-29 23:36:51 +02:00
|
|
|
.stream = true,
|
|
|
|
.width = 1024,
|
|
|
|
.height = 1024,
|
|
|
|
});
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
#ifdef DEBUG
|
|
|
|
char buf[128];
|
|
|
|
snprintf(buf, sizeof(buf), "Font render texture");
|
|
|
|
r_texture_set_debug_label(globals.render_tex, buf);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
globals.render_buf = r_framebuffer_create();
|
|
|
|
r_framebuffer_attach(globals.render_buf, globals.render_tex, 0, FRAMEBUFFER_ATTACH_COLOR0);
|
|
|
|
r_framebuffer_viewport(globals.render_buf, 0, 0, 1024, 1024);
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
static void post_init_fonts(void) {
|
|
|
|
globals.default_shader = get_resource_data(RES_SHADER_PROGRAM, "text_default", RESF_PERMANENT | RESF_PRELOAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void shutdown_fonts(void) {
|
2018-09-14 09:37:20 +02:00
|
|
|
r_texture_destroy(globals.render_tex);
|
|
|
|
r_framebuffer_destroy(globals.render_buf);
|
2018-06-08 06:49:40 +02:00
|
|
|
events_unregister_handler(fonts_event);
|
2018-06-29 23:36:51 +02:00
|
|
|
FT_Done_FreeType(globals.lib);
|
|
|
|
SDL_DestroyMutex(globals.mutex.new_face);
|
|
|
|
SDL_DestroyMutex(globals.mutex.done_face);
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static char* font_path(const char *name) {
|
|
|
|
return strjoin(FONT_PATH_PREFIX, name, FONT_EXTENSION, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool check_font_path(const char *path) {
|
|
|
|
return strstartswith(path, FONT_PATH_PREFIX) && strendswith(path, FONT_EXTENSION);
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
static ulong ftstream_read(FT_Stream stream, ulong offset, uchar *buffer, ulong count) {
|
|
|
|
SDL_RWops *rwops = stream->descriptor.pointer;
|
|
|
|
ulong error = count ? 0 : 1;
|
|
|
|
|
|
|
|
if(SDL_RWseek(rwops, offset, RW_SEEK_SET) < 0) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("Can't seek in stream (%s)", (char*)stream->pathname.pointer);
|
2018-06-29 23:36:51 +02:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_RWread(rwops, buffer, 1, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ftstream_close(FT_Stream stream) {
|
|
|
|
SDL_RWclose((SDL_RWops*)stream->descriptor.pointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static FT_Error FT_Open_Face_Thread_Safe(FT_Library library, const FT_Open_Args *args, FT_Long face_index, FT_Face *aface) {
|
|
|
|
FT_Error err;
|
|
|
|
SDL_LockMutex(globals.mutex.new_face);
|
|
|
|
err = FT_Open_Face(library, args, face_index, aface);
|
|
|
|
SDL_UnlockMutex(globals.mutex.new_face);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static FT_Error FT_Done_Face_Thread_Safe(FT_Face face) {
|
|
|
|
FT_Error err;
|
|
|
|
SDL_LockMutex(globals.mutex.done_face);
|
|
|
|
err = FT_Done_Face(face);
|
|
|
|
SDL_UnlockMutex(globals.mutex.done_face);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static FT_Face load_font_face(char *vfspath, long index) {
|
2017-04-27 11:10:49 +02:00
|
|
|
char *syspath = vfs_repr(vfspath, true);
|
2017-04-18 21:48:18 +02:00
|
|
|
|
2017-04-27 23:05:09 +02:00
|
|
|
SDL_RWops *rwops = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE);
|
2017-04-18 21:48:18 +02:00
|
|
|
|
|
|
|
if(!rwops) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("VFS error: %s", vfs_get_error());
|
2018-06-29 23:36:51 +02:00
|
|
|
free(syspath);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
FT_Stream ftstream = calloc(1, sizeof(*ftstream));
|
|
|
|
ftstream->descriptor.pointer = rwops;
|
|
|
|
ftstream->pathname.pointer = syspath;
|
|
|
|
ftstream->read = ftstream_read;
|
|
|
|
ftstream->close = ftstream_close;
|
|
|
|
ftstream->size = SDL_RWsize(rwops);
|
|
|
|
|
|
|
|
FT_Open_Args ftargs;
|
|
|
|
memset(&ftargs, 0, sizeof(ftargs));
|
|
|
|
ftargs.flags = FT_OPEN_STREAM;
|
|
|
|
ftargs.stream = ftstream;
|
|
|
|
|
|
|
|
FT_Face face;
|
|
|
|
FT_Error err;
|
|
|
|
|
|
|
|
if((err = FT_Open_Face_Thread_Safe(globals.lib, &ftargs, index, &face))) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("Failed to load font '%s' (face %li): FT_Open_Face() failed: %s", syspath, index, ft_error_str(err));
|
2018-06-29 23:36:51 +02:00
|
|
|
free(syspath);
|
|
|
|
free(ftstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(FT_IS_SCALABLE(face))) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("Font '%s' (face %li) is not scalable. This is not supported, sorry. Load aborted", syspath, index);
|
2018-06-29 23:36:51 +02:00
|
|
|
FT_Done_Face_Thread_Safe(face);
|
|
|
|
free(syspath);
|
|
|
|
free(ftstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
log_info("Loaded font '%s' (face %li)", syspath, index);
|
|
|
|
return face;
|
|
|
|
}
|
|
|
|
|
|
|
|
static FT_Error set_font_size(Font *fnt, uint pxsize, double scale) {
|
|
|
|
FT_Error err = FT_Err_Ok;
|
|
|
|
|
|
|
|
assert(fnt != NULL);
|
|
|
|
assert(fnt->face != NULL);
|
|
|
|
|
|
|
|
FT_Fixed fixed_scale = round(scale * (2 << 15));
|
|
|
|
pxsize = FT_MulFix(pxsize * 64, fixed_scale);
|
|
|
|
|
|
|
|
if((err = FT_Set_Char_Size(fnt->face, 0, pxsize, 0, 0))) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("FT_Set_Char_Size(%u) failed: %s", pxsize, ft_error_str(err));
|
2018-06-29 23:36:51 +02:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2019-03-09 22:58:42 +01:00
|
|
|
if(fnt->stroker) {
|
|
|
|
FT_Stroker_Done(fnt->stroker);
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if((err = FT_Stroker_New(globals.lib, &fnt->stroker))) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("FT_Stroker_New() failed: %s", ft_error_str(err));
|
2019-01-04 23:59:39 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
FT_Stroker_Set(fnt->stroker, FT_MulFix(1 * 64, fixed_scale), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
// 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));
|
|
|
|
fnt->metrics.max_glyph_height = fnt->metrics.ascent - fnt->metrics.descent;
|
|
|
|
fnt->metrics.lineskip = FT_CEIL(FT_MulFix(face->height, fixed_scale));
|
|
|
|
fnt->metrics.scale = scale;
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
bool font_get_kerning_available(Font *font) {
|
|
|
|
return FT_HAS_KERNING(font->face);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool font_get_kerning_enabled(Font *font) {
|
|
|
|
return font->kerning;
|
|
|
|
}
|
|
|
|
|
|
|
|
void font_set_kerning_enabled(Font *font, bool newval) {
|
|
|
|
font->kerning = (newval && FT_HAS_KERNING(font->face));
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
// TODO: Figure out sensible values for these; maybe make them depend on font size in some way.
|
|
|
|
#define SS_WIDTH 1024
|
|
|
|
#define SS_HEIGHT 1024
|
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
static SpriteSheet* add_spritesheet(SpriteSheetAnchor *spritesheets) {
|
2018-06-29 23:36:51 +02:00
|
|
|
SpriteSheet *ss = calloc(1, sizeof(SpriteSheet));
|
|
|
|
ss->rectpack = rectpack_new(SS_WIDTH, SS_HEIGHT);
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
ss->tex = r_texture_create(&(TextureParams) {
|
2018-06-29 23:36:51 +02:00
|
|
|
.width = SS_WIDTH,
|
|
|
|
.height = SS_HEIGHT,
|
2019-01-04 23:59:39 +01:00
|
|
|
.type = TEX_TYPE_RGB_8,
|
2018-07-10 11:14:29 +02:00
|
|
|
.filter.mag = TEX_FILTER_LINEAR,
|
|
|
|
.filter.min = TEX_FILTER_LINEAR,
|
2018-06-29 23:36:51 +02:00
|
|
|
.wrap.s = TEX_WRAP_CLAMP,
|
|
|
|
.wrap.t = TEX_WRAP_CLAMP,
|
2018-08-12 01:38:03 +02:00
|
|
|
.mipmaps = TEX_MIPMAPS_MAX,
|
|
|
|
.mipmap_mode = TEX_MIPMAP_AUTO,
|
2018-06-29 23:36:51 +02:00
|
|
|
});
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
#ifdef DEBUG
|
|
|
|
char buf[128];
|
2019-09-17 13:35:29 +02:00
|
|
|
snprintf(buf, sizeof(buf), "Fonts spritesheet %p", (void*)ss);
|
2018-09-14 09:37:20 +02:00
|
|
|
r_texture_set_debug_label(ss->tex, buf);
|
|
|
|
#endif
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
r_texture_clear(ss->tex, RGBA(0, 0, 0, 0));
|
2018-06-29 23:36:51 +02:00
|
|
|
alist_append(spritesheets, ss);
|
|
|
|
return ss;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The padding is needed to prevent glyph edges from bleeding in due to linear filtering.
|
|
|
|
#define GLYPH_SPRITE_PADDING 1
|
|
|
|
|
2019-08-02 19:50:41 +02:00
|
|
|
static bool add_glyph_to_spritesheet(Glyph *glyph, Pixmap *pixmap, SpriteSheet *ss) {
|
2019-01-04 23:59:39 +01:00
|
|
|
uint padded_w = pixmap->width + 2 * GLYPH_SPRITE_PADDING;
|
|
|
|
uint padded_h = pixmap->height + 2 * GLYPH_SPRITE_PADDING;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-08-02 19:50:41 +02:00
|
|
|
glyph->spritesheet_section = rectpack_add(ss->rectpack, padded_w, padded_h);
|
|
|
|
|
|
|
|
if(glyph->spritesheet_section == NULL) {
|
2018-06-29 23:36:51 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-08-02 19:50:41 +02:00
|
|
|
glyph->spritesheet = ss;
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx ofs = GLYPH_SPRITE_PADDING * (1+I);
|
2019-08-02 19:50:41 +02:00
|
|
|
|
|
|
|
Rect sprite_pos = rectpack_section_rect(glyph->spritesheet_section);
|
2018-06-29 23:36:51 +02:00
|
|
|
sprite_pos.bottom_right += ofs;
|
|
|
|
sprite_pos.top_left += ofs;
|
|
|
|
|
OpenGL ES 3.0 rendering backend (#148)
* First steps towards shader transpilation
Needs to be manually enabled via -Dshader_transpiler=true.
Requires shaderc. https://github.com/google/shaderc
Not yet functional due to missing SPIRV-Cross integration. SPIRV-Cross
currently does not have an official C API, and crossc is too minimal to
be useful. The current plan is to extend crossc and vendor it, while
also sending PRs upstream.
* Integrate crossc; shader transpilation for GLES now works
* fix leak
* gles30 backend now playable on Mesa with 3.2 context
Some rendering issues are present. Identified so far:
- Marisa's lasers are invisible
- Death effect looks wrong
Also, a small pixmap manipulation library has been written, and the
texture uploading API redesigned around it.
* fix marisa lasers in GLES (uniform name clashed with builtin)
* fix player death effect in GLES (another name clash)
* Dump ANGLE's translated shader code in debug log
* fix screenshots
* Drop support for triangle fans, switch to strips
Fans offer no advantage over strips, and they've been removed in D3D10+,
so ANGLE has to emulate them.
* crude workaround for an ANGLE bug
* Re-enable GL debug labels, fix an issue with them that affected ANGLE (but was always technically a bug)
* fix race condition in shaderc initialization
* New SDL_RWops interface for vertex buffers
* Optimize VBO streaming via buffering updates
Measurable performance improvement even with the main gl33 renderer,
drastic improvement with ANGLE.
* Fix the depth texture binding problem under ANGLE
Apparently it hates GL_DEPTH_COMPONENT16 for some reason. Sized internal
formats are not supported in GLES 2.0 anyway, so not using them is
probably a good idea.
* fix GLES2.0 segfault (the backend still doesn't work, though)
* dump GL extensions at info log level, not debug
* get around a Mesa bug; more correct texture format table for GLES2
* Correct GLES3 texture format table according to the spec
Not a Mesa bug after all
* require crossc>=1.5.0, fallback to subproject
* Request at least 8bit per color channel in GL backends
* Forbid lto for static windows builds with shader_transpiler=true
* fix edge case segfault
* Add basic ANGLE bundling support to the build system
Windows only, and no NSIS support yet
* Fix various windows-related build system and installer brokenness
* Disable gles backends by default
* update documentation
2018-10-02 00:36:10 +02:00
|
|
|
r_texture_fill_region(
|
|
|
|
ss->tex,
|
|
|
|
0,
|
|
|
|
rect_x(sprite_pos),
|
|
|
|
rect_y(sprite_pos),
|
2019-01-04 23:59:39 +01:00
|
|
|
pixmap
|
OpenGL ES 3.0 rendering backend (#148)
* First steps towards shader transpilation
Needs to be manually enabled via -Dshader_transpiler=true.
Requires shaderc. https://github.com/google/shaderc
Not yet functional due to missing SPIRV-Cross integration. SPIRV-Cross
currently does not have an official C API, and crossc is too minimal to
be useful. The current plan is to extend crossc and vendor it, while
also sending PRs upstream.
* Integrate crossc; shader transpilation for GLES now works
* fix leak
* gles30 backend now playable on Mesa with 3.2 context
Some rendering issues are present. Identified so far:
- Marisa's lasers are invisible
- Death effect looks wrong
Also, a small pixmap manipulation library has been written, and the
texture uploading API redesigned around it.
* fix marisa lasers in GLES (uniform name clashed with builtin)
* fix player death effect in GLES (another name clash)
* Dump ANGLE's translated shader code in debug log
* fix screenshots
* Drop support for triangle fans, switch to strips
Fans offer no advantage over strips, and they've been removed in D3D10+,
so ANGLE has to emulate them.
* crude workaround for an ANGLE bug
* Re-enable GL debug labels, fix an issue with them that affected ANGLE (but was always technically a bug)
* fix race condition in shaderc initialization
* New SDL_RWops interface for vertex buffers
* Optimize VBO streaming via buffering updates
Measurable performance improvement even with the main gl33 renderer,
drastic improvement with ANGLE.
* Fix the depth texture binding problem under ANGLE
Apparently it hates GL_DEPTH_COMPONENT16 for some reason. Sized internal
formats are not supported in GLES 2.0 anyway, so not using them is
probably a good idea.
* fix GLES2.0 segfault (the backend still doesn't work, though)
* dump GL extensions at info log level, not debug
* get around a Mesa bug; more correct texture format table for GLES2
* Correct GLES3 texture format table according to the spec
Not a Mesa bug after all
* require crossc>=1.5.0, fallback to subproject
* Request at least 8bit per color channel in GL backends
* Forbid lto for static windows builds with shader_transpiler=true
* fix edge case segfault
* Add basic ANGLE bundling support to the build system
Windows only, and no NSIS support yet
* Fix various windows-related build system and installer brokenness
* Disable gles backends by default
* update documentation
2018-10-02 00:36:10 +02:00
|
|
|
);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-08-02 19:50:41 +02:00
|
|
|
Sprite *sprite = &glyph->sprite;
|
2019-01-04 23:59:39 +01:00
|
|
|
sprite->tex = ss->tex;
|
|
|
|
sprite->w = pixmap->width;
|
|
|
|
sprite->h = pixmap->height;
|
2020-04-25 04:35:22 +02:00
|
|
|
|
|
|
|
sprite_set_denormalized_tex_coords(sprite, (FloatRect) {
|
|
|
|
.x = rect_x(sprite_pos),
|
|
|
|
.y = rect_y(sprite_pos),
|
|
|
|
.w = pixmap->width,
|
|
|
|
.h = pixmap->height,
|
|
|
|
});
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
++ss->glyphs;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
static bool add_glyph_to_spritesheets(Glyph *glyph, Pixmap *pixmap, SpriteSheetAnchor *spritesheets) {
|
2018-06-29 23:36:51 +02:00
|
|
|
bool result;
|
|
|
|
|
|
|
|
for(SpriteSheet *ss = spritesheets->first; ss; ss = ss->next) {
|
2019-08-02 19:50:41 +02:00
|
|
|
if((result = add_glyph_to_spritesheet(glyph, pixmap, ss))) {
|
2018-06-29 23:36:51 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
return add_glyph_to_spritesheet(glyph, pixmap, add_spritesheet(spritesheets));
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
2019-01-24 21:21:08 +01:00
|
|
|
static const char* pixmode_name(FT_Pixel_Mode mode) {
|
2018-06-29 23:36:51 +02:00
|
|
|
switch(mode) {
|
|
|
|
case FT_PIXEL_MODE_NONE : return "FT_PIXEL_MODE_NONE";
|
|
|
|
case FT_PIXEL_MODE_MONO : return "FT_PIXEL_MODE_MONO";
|
|
|
|
case FT_PIXEL_MODE_GRAY : return "FT_PIXEL_MODE_GRAY";
|
|
|
|
case FT_PIXEL_MODE_GRAY2 : return "FT_PIXEL_MODE_GRAY2";
|
|
|
|
case FT_PIXEL_MODE_GRAY4 : return "FT_PIXEL_MODE_GRAY4";
|
|
|
|
case FT_PIXEL_MODE_LCD : return "FT_PIXEL_MODE_LCD";
|
|
|
|
case FT_PIXEL_MODE_LCD_V : return "FT_PIXEL_MODE_LCD_V";
|
|
|
|
// case FT_PIXEL_MODE_RGBA: return "FT_PIXEL_MODE_RGBA";
|
|
|
|
default : return "FT_PIXEL_MODE_UNKNOWN";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void delete_spritesheet(SpriteSheetAnchor *spritesheets, SpriteSheet *ss) {
|
2018-09-14 09:37:20 +02:00
|
|
|
r_texture_destroy(ss->tex);
|
2018-06-29 23:36:51 +02:00
|
|
|
rectpack_free(ss->rectpack);
|
|
|
|
alist_unlink(spritesheets, ss);
|
|
|
|
free(ss);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesheets) {
|
2018-07-24 18:42:11 +02:00
|
|
|
// log_debug("Loading glyph 0x%08x", gindex);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
FT_Error err = FT_Load_Glyph(font->face, gindex, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_LIGHT);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
if(err) {
|
|
|
|
log_warn("FT_Load_Glyph(%u) failed: %s", gindex, ft_error_str(err));
|
|
|
|
return NULL;
|
2017-04-18 21:48:18 +02:00
|
|
|
}
|
2017-04-15 23:58:42 +02:00
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
Glyph *glyph = dynarray_append(&font->glyphs);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
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);
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
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;
|
|
|
|
FT_Get_Glyph(font->face->glyph, &g_src);
|
|
|
|
FT_Glyph_Copy(g_src, &g_fill);
|
|
|
|
FT_Glyph_Copy(g_src, &g_border);
|
|
|
|
FT_Glyph_Copy(g_src, &g_inner);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if(have_bitmap) {
|
|
|
|
have_bitmap = ((FT_BitmapGlyph)g_fill)->bitmap.width > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!have_bitmap) {
|
2018-06-29 23:36:51 +02:00
|
|
|
// Some glyphs may be invisible, but we still need the metrics data for them (e.g. space)
|
|
|
|
memset(&glyph->sprite, 0, sizeof(Sprite));
|
|
|
|
} else {
|
2019-01-04 23:59:39 +01:00
|
|
|
FT_Glyph_StrokeBorder(&g_border, font->stroker, false, true);
|
|
|
|
FT_Glyph_To_Bitmap(&g_border, FT_RENDER_MODE_LIGHT, NULL, true);
|
|
|
|
|
|
|
|
FT_Glyph_StrokeBorder(&g_inner, font->stroker, true, true);
|
|
|
|
FT_Glyph_To_Bitmap(&g_inner, FT_RENDER_MODE_LIGHT, NULL, true);
|
|
|
|
|
|
|
|
g_bm_fill = (FT_BitmapGlyph)g_fill;
|
|
|
|
g_bm_border = (FT_BitmapGlyph)g_border;
|
|
|
|
g_bm_inner = (FT_BitmapGlyph)g_inner;
|
|
|
|
|
|
|
|
if(
|
|
|
|
g_bm_fill->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY ||
|
|
|
|
g_bm_border->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY ||
|
|
|
|
g_bm_inner->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY
|
|
|
|
) {
|
2018-06-29 23:36:51 +02:00
|
|
|
log_warn(
|
|
|
|
"Glyph %u returned bitmap with pixel format %s. Only %s is supported, sorry. Ignoring",
|
|
|
|
gindex,
|
2019-01-04 23:59:39 +01:00
|
|
|
pixmode_name(g_bm_fill->bitmap.pixel_mode),
|
2018-06-29 23:36:51 +02:00
|
|
|
pixmode_name(FT_PIXEL_MODE_GRAY)
|
|
|
|
);
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
FT_Done_Glyph(g_src);
|
|
|
|
FT_Done_Glyph(g_fill);
|
|
|
|
FT_Done_Glyph(g_border);
|
|
|
|
FT_Done_Glyph(g_inner);
|
2020-04-05 04:51:00 +02:00
|
|
|
font->glyphs.num_elements--;
|
2018-06-29 23:36:51 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2017-04-15 23:58:42 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
Pixmap px;
|
2020-06-09 02:01:53 +02:00
|
|
|
px.origin = PIXMAP_ORIGIN_BOTTOMLEFT;
|
2019-01-04 23:59:39 +01:00
|
|
|
px.format = PIXMAP_FORMAT_RGB8;
|
|
|
|
px.width = imax(g_bm_fill->bitmap.width, imax(g_bm_border->bitmap.width, g_bm_inner->bitmap.width));
|
|
|
|
px.height = imax(g_bm_fill->bitmap.rows, imax(g_bm_border->bitmap.rows, g_bm_inner->bitmap.rows));
|
|
|
|
px.data.rg8 = pixmap_alloc_buffer_for_copy(&px);
|
|
|
|
|
|
|
|
int ref_left = g_bm_border->left;
|
|
|
|
int ref_top = g_bm_border->top;
|
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
ssize_t fill_ofs_x = (g_bm_fill->left - ref_left);
|
2019-01-04 23:59:39 +01:00
|
|
|
ssize_t fill_ofs_y = -(g_bm_fill->top - ref_top);
|
|
|
|
ssize_t border_ofs_x = (g_bm_border->left - ref_left);
|
|
|
|
ssize_t border_ofs_y = -(g_bm_border->top - ref_top);
|
|
|
|
ssize_t inner_ofs_x = (g_bm_inner->left - ref_left);
|
|
|
|
ssize_t inner_ofs_y = -(g_bm_inner->top - ref_top);
|
|
|
|
|
|
|
|
for(ssize_t x = 0; x < px.width; ++x) {
|
|
|
|
for(ssize_t y = 0; y < px.height; ++y) {
|
|
|
|
PixelRGB8 *p = px.data.rgb8 + (x + y * px.width);
|
|
|
|
|
|
|
|
ssize_t fill_coord_x = x - fill_ofs_x;
|
|
|
|
ssize_t fill_coord_y = y - fill_ofs_y;
|
|
|
|
ssize_t border_coord_x = x - border_ofs_x;
|
|
|
|
ssize_t border_coord_y = y - border_ofs_y;
|
|
|
|
ssize_t inner_coord_x = x - inner_ofs_x;
|
|
|
|
ssize_t inner_coord_y = y - inner_ofs_y;
|
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
ssize_t fill_index = fill_coord_x + (g_bm_fill->bitmap.rows - fill_coord_y - 1) * g_bm_fill->bitmap.pitch;
|
|
|
|
ssize_t border_index = border_coord_x + (g_bm_border->bitmap.rows - border_coord_y - 1) * g_bm_border->bitmap.pitch;
|
|
|
|
ssize_t inner_index = inner_coord_x + (g_bm_inner->bitmap.rows - inner_coord_y - 1) * g_bm_inner->bitmap.pitch;
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
if(
|
|
|
|
fill_coord_x >= 0 && fill_coord_x < g_bm_fill->bitmap.width &&
|
|
|
|
fill_coord_y >= 0 && fill_coord_y < g_bm_fill->bitmap.rows
|
|
|
|
) {
|
|
|
|
p->r = g_bm_fill->bitmap.buffer[fill_index];
|
|
|
|
} else {
|
|
|
|
p->r = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(
|
|
|
|
border_coord_x >= 0 && border_coord_x < g_bm_border->bitmap.width &&
|
|
|
|
border_coord_y >= 0 && border_coord_y < g_bm_border->bitmap.rows
|
|
|
|
) {
|
|
|
|
p->g = g_bm_border->bitmap.buffer[border_index];
|
|
|
|
} else {
|
|
|
|
p->g = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(
|
|
|
|
inner_coord_x >= 0 && inner_coord_x < g_bm_inner->bitmap.width &&
|
|
|
|
inner_coord_y >= 0 && inner_coord_y < g_bm_inner->bitmap.rows
|
|
|
|
) {
|
|
|
|
p->b = g_bm_inner->bitmap.buffer[inner_index];
|
|
|
|
} else {
|
|
|
|
p->b = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
PixmapFormat optimal_fmt = r_texture_optimal_pixmap_format_for_type(TEX_TYPE_RGB_8, px.format);
|
|
|
|
pixmap_convert_inplace_realloc(&px, optimal_fmt);
|
|
|
|
|
|
|
|
if(!r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN)) {
|
|
|
|
pixmap_flip_to_origin_inplace(&px, PIXMAP_ORIGIN_TOPLEFT);
|
|
|
|
}
|
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
if(!add_glyph_to_spritesheets(glyph, &px, spritesheets)) {
|
2018-06-29 23:36:51 +02:00
|
|
|
log_warn(
|
2019-01-04 23:59:39 +01:00
|
|
|
"Glyph %u fill can't fit into any spritesheets (padded bitmap size: %zux%zu; max spritesheet size: %ux%u)",
|
2018-06-29 23:36:51 +02:00
|
|
|
gindex,
|
2019-01-04 23:59:39 +01:00
|
|
|
px.width + 2 * GLYPH_SPRITE_PADDING,
|
|
|
|
px.height + 2 * GLYPH_SPRITE_PADDING,
|
2018-06-29 23:36:51 +02:00
|
|
|
SS_WIDTH,
|
|
|
|
SS_HEIGHT
|
|
|
|
);
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
free(px.data.rg8);
|
|
|
|
FT_Done_Glyph(g_src);
|
|
|
|
FT_Done_Glyph(g_fill);
|
|
|
|
FT_Done_Glyph(g_border);
|
|
|
|
FT_Done_Glyph(g_inner);
|
2020-04-05 04:51:00 +02:00
|
|
|
--font->glyphs.num_elements;
|
2018-06-29 23:36:51 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
free(px.data.rg8);
|
2017-04-15 23:58:42 +02:00
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
FT_Done_Glyph(g_src);
|
|
|
|
FT_Done_Glyph(g_fill);
|
|
|
|
FT_Done_Glyph(g_border);
|
|
|
|
FT_Done_Glyph(g_inner);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
glyph->ft_index = gindex;
|
|
|
|
return glyph;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Glyph* get_glyph(Font *fnt, charcode_t cp) {
|
|
|
|
int64_t ofs;
|
|
|
|
|
|
|
|
if(!ht_lookup(&fnt->charcodes_to_glyph_ofs, cp, &ofs)) {
|
|
|
|
Glyph *glyph;
|
|
|
|
uint ft_index = FT_Get_Char_Index(fnt->face, cp);
|
2018-07-24 18:42:11 +02:00
|
|
|
// log_debug("Glyph for charcode 0x%08lx not cached", cp);
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(ft_index == 0 && cp != UNICODE_UNKNOWN) {
|
|
|
|
log_debug("Font has no glyph for charcode 0x%08lx", cp);
|
|
|
|
glyph = get_glyph(fnt, UNICODE_UNKNOWN);
|
2020-04-05 04:51:00 +02:00
|
|
|
ofs = glyph ? dynarray_indexof(&fnt->glyphs, glyph) : -1;
|
2018-07-31 10:50:04 +02:00
|
|
|
} else if(!ht_lookup(&fnt->ftindex_to_glyph_ofs, ft_index, &ofs)) {
|
2019-09-17 13:35:29 +02:00
|
|
|
glyph = load_glyph(fnt, ft_index, &globals.spritesheets);
|
2020-04-05 04:51:00 +02:00
|
|
|
ofs = glyph ? dynarray_indexof(&fnt->glyphs, glyph) : -1;
|
2018-07-31 10:50:04 +02:00
|
|
|
ht_set(&fnt->ftindex_to_glyph_ofs, ft_index, ofs);
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ht_set(&fnt->charcodes_to_glyph_ofs, cp, ofs);
|
|
|
|
}
|
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
return ofs < 0 ? NULL : dynarray_get_ptr(&fnt->glyphs, ofs);
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
attr_nonnull(1)
|
|
|
|
static void wipe_glyph_cache(Font *font) {
|
2020-04-05 04:51:00 +02:00
|
|
|
dynarray_foreach_elem(&font->glyphs, Glyph *g, {
|
2019-09-17 13:35:29 +02:00
|
|
|
SpriteSheet *ss = g->spritesheet;
|
|
|
|
|
|
|
|
if(ss == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
RectPack *rp = ss->rectpack;
|
|
|
|
assume(rp != NULL);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
RectPackSection *section = g->spritesheet_section;
|
|
|
|
assume(section != NULL);
|
|
|
|
|
|
|
|
rectpack_reclaim(rp, section);
|
|
|
|
|
|
|
|
if(rectpack_is_empty(rp)) {
|
|
|
|
delete_spritesheet(&globals.spritesheets, ss);
|
|
|
|
}
|
2020-04-05 04:51:00 +02:00
|
|
|
});
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-09-17 13:35:29 +02:00
|
|
|
ht_unset_all(&font->charcodes_to_glyph_ofs);
|
|
|
|
ht_unset_all(&font->ftindex_to_glyph_ofs);
|
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
font->glyphs.num_elements = 0;
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void free_font_resources(Font *font) {
|
|
|
|
if(font->face) {
|
|
|
|
FT_Stream stream = font->face->stream;
|
|
|
|
FT_Done_Face_Thread_Safe(font->face);
|
|
|
|
|
|
|
|
if(stream) {
|
|
|
|
free(stream->pathname.pointer);
|
|
|
|
free(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(font->stroker) {
|
|
|
|
FT_Stroker_Done(font->stroker);
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
wipe_glyph_cache(font);
|
|
|
|
|
|
|
|
ht_destroy(&font->charcodes_to_glyph_ofs);
|
|
|
|
ht_destroy(&font->ftindex_to_glyph_ofs);
|
|
|
|
|
|
|
|
free(font->source_path);
|
2020-04-05 04:51:00 +02:00
|
|
|
dynarray_free_data(&font->glyphs);
|
2011-06-26 20:23:28 +02:00
|
|
|
}
|
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
void load_font(ResourceLoadState *st) {
|
2018-06-08 06:49:40 +02:00
|
|
|
Font font;
|
|
|
|
memset(&font, 0, sizeof(font));
|
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]){
|
2018-06-08 06:49:40 +02:00
|
|
|
{ "source", .out_str = &font.source_path },
|
|
|
|
{ "size", .out_int = &font.base_size },
|
2018-06-29 23:36:51 +02:00
|
|
|
{ "face", .out_long = &font.base_face_idx },
|
2018-06-08 06:49:40 +02:00
|
|
|
{ NULL }
|
|
|
|
})) {
|
2020-06-09 02:01:53 +02:00
|
|
|
log_error("Failed to parse font file '%s'", st->path);
|
|
|
|
res_load_failed(st);
|
|
|
|
return;
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
ht_create(&font.charcodes_to_glyph_ofs);
|
|
|
|
ht_create(&font.ftindex_to_glyph_ofs);
|
|
|
|
|
|
|
|
if(!(font.face = load_font_face(font.source_path, font.base_face_idx))) {
|
|
|
|
free_font_resources(&font);
|
2020-06-09 02:01:53 +02:00
|
|
|
res_load_failed(st);
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(set_font_size(&font, font.base_size, global_font_scale())) {
|
|
|
|
free_font_resources(&font);
|
2020-06-09 02:01:53 +02:00
|
|
|
res_load_failed(st);
|
|
|
|
return;
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2020-04-05 04:51:00 +02:00
|
|
|
dynarray_ensure_capacity(&font.glyphs, 32);
|
2018-06-08 06:49:40 +02:00
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
#ifdef DEBUG
|
2020-06-09 02:01:53 +02:00
|
|
|
strlcpy(font.debug_label, st->name, sizeof(font.debug_label));
|
2018-09-14 09:37:20 +02:00
|
|
|
#endif
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
font_set_kerning_enabled(&font, true);
|
2020-06-09 02:01:53 +02:00
|
|
|
res_load_finished(st, memdup(&font, sizeof(font)));
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
void unload_font(void *vfont) {
|
|
|
|
free_font_resources(vfont);
|
|
|
|
free(vfont);
|
|
|
|
}
|
2018-06-08 06:49:40 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
struct rlfonts_arg {
|
|
|
|
double quality;
|
2018-06-08 06:49:40 +02:00
|
|
|
};
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
attr_nonnull(1)
|
|
|
|
static void reload_font(Font *font, double quality) {
|
2018-07-04 10:36:16 +02:00
|
|
|
if(font->metrics.scale != quality) {
|
|
|
|
wipe_glyph_cache(font);
|
|
|
|
set_font_size(font, font->base_size, quality);
|
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
static void reload_fonts(double quality) {
|
|
|
|
resource_for_each(RES_FONT, reload_font_callback, &(struct rlfonts_arg) { quality });
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int apply_kerning(Font *font, uint prev_index, Glyph *gthis) {
|
|
|
|
FT_Vector kvec;
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(!font_get_kerning_enabled(font) || prev_index == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(!FT_Get_Kerning(font->face, prev_index, gthis->ft_index, FT_KERNING_DEFAULT, &kvec)) {
|
|
|
|
return kvec.x >> 6;
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
return 0;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
int text_ucs4_width_raw(Font *font, const uint32_t *text, uint maxlines) {
|
|
|
|
const uint32_t *tptr = text;
|
2018-06-29 23:36:51 +02:00
|
|
|
uint prev_glyph_idx = 0;
|
|
|
|
uint numlines = 0;
|
|
|
|
int x = 0;
|
|
|
|
int width = 0;
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
while(*tptr) {
|
2019-01-04 23:59:39 +01:00
|
|
|
uint32_t uchar = *tptr++;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
if(uchar == '\n') {
|
|
|
|
if(++numlines == maxlines) {
|
|
|
|
break;
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(x > width) {
|
|
|
|
width = x;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
x = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glyph *glyph = get_glyph(font, uchar);
|
|
|
|
|
|
|
|
if(glyph == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
x += apply_kerning(font, prev_glyph_idx, glyph);
|
2018-06-29 23:36:51 +02:00
|
|
|
x += glyph->metrics.advance;
|
|
|
|
prev_glyph_idx = glyph->ft_index;
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(x > width) {
|
|
|
|
width = x;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
return width;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
int text_width_raw(Font *font, const char *text, uint maxlines) {
|
|
|
|
uint32_t buf[strlen(text) + 1];
|
|
|
|
utf8_to_ucs4(text, sizeof(buf), buf);
|
|
|
|
return text_ucs4_width_raw(font, buf, maxlines);
|
|
|
|
}
|
|
|
|
|
|
|
|
void text_ucs4_bbox(Font *font, const uint32_t *text, uint maxlines, BBox *bbox) {
|
|
|
|
const uint32_t *tptr = text;
|
2018-06-29 23:36:51 +02:00
|
|
|
uint prev_glyph_idx = 0;
|
|
|
|
uint numlines = 0;
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
memset(bbox, 0, sizeof(*bbox));
|
|
|
|
int x = 0, y = 0;
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
while(*tptr) {
|
2019-01-04 23:59:39 +01:00
|
|
|
uint32_t uchar = *tptr++;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
if(uchar == '\n') {
|
|
|
|
if(++numlines == maxlines) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
x = 0;
|
|
|
|
y += font->metrics.lineskip;
|
|
|
|
|
|
|
|
continue;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
Glyph *glyph = get_glyph(font, uchar);
|
2018-04-12 16:08:48 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(glyph == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
x += apply_kerning(font, prev_glyph_idx, glyph);
|
2010-11-14 13:24:56 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
int g_x0 = x + glyph->metrics.bearing_x;
|
2019-05-14 11:00:51 +02:00
|
|
|
int g_x1 = g_x0 + imax(glyph->metrics.width, glyph->sprite.w);
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
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);
|
2018-02-06 07:19:25 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
int g_y0 = y - glyph->metrics.bearing_y;
|
2019-05-14 11:00:51 +02:00
|
|
|
int g_y1 = g_y0 + imax(glyph->metrics.height, glyph->sprite.h);
|
2017-04-02 16:06:18 +02:00
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
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);
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
prev_glyph_idx = glyph->ft_index;
|
2019-05-14 11:00:51 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
x += glyph->metrics.advance;
|
2019-05-14 11:00:51 +02:00
|
|
|
bbox->x.max = imax(bbox->x.max, x);
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
void text_bbox(Font *font, const char *text, uint maxlines, BBox *bbox) {
|
|
|
|
uint32_t buf[strlen(text) + 1];
|
|
|
|
utf8_to_ucs4(text, sizeof(buf), buf);
|
|
|
|
text_ucs4_bbox(font, buf, maxlines, bbox);
|
|
|
|
}
|
|
|
|
|
|
|
|
double text_ucs4_width(Font *font, const uint32_t *text, uint maxlines) {
|
|
|
|
return text_ucs4_width_raw(font, text, maxlines) / font->metrics.scale;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
double text_width(Font *font, const char *text, uint maxlines) {
|
|
|
|
return text_width_raw(font, text, maxlines) / font->metrics.scale;
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
int text_ucs4_height_raw(Font *font, const uint32_t *text, uint maxlines) {
|
2018-06-29 23:36:51 +02:00
|
|
|
// FIXME: I'm not sure this is correct. Perhaps it should consider max_glyph_height at least?
|
2017-04-02 16:06:18 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
uint text_lines = 1;
|
2019-01-04 23:59:39 +01:00
|
|
|
const uint32_t *tptr = text;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
while((tptr = ucs4chr(tptr, '\n'))) {
|
2018-06-29 23:36:51 +02:00
|
|
|
if(text_lines++ == maxlines) {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-22 02:07:14 +02:00
|
|
|
|
|
|
|
if(*tptr == '\n') {
|
|
|
|
++tptr;
|
|
|
|
}
|
2017-04-02 16:06:18 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
return font->metrics.lineskip * text_lines;
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
int text_height_raw(Font *font, const char *text, uint maxlines) {
|
|
|
|
uint32_t buf[strlen(text) + 1];
|
|
|
|
utf8_to_ucs4(text, sizeof(buf), buf);
|
|
|
|
return text_ucs4_height_raw(font, buf, maxlines);
|
|
|
|
}
|
|
|
|
|
|
|
|
double text_ucs4_height(Font *font, const uint32_t *text, uint maxlines) {
|
|
|
|
return text_ucs4_height_raw(font, text, maxlines) / font->metrics.scale;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
double text_height(Font *font, const char *text, uint maxlines) {
|
|
|
|
return text_height_raw(font, text, maxlines) / font->metrics.scale;
|
2017-04-02 16:06:18 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static inline void adjust_xpos(Font *font, const uint32_t *ucs4text, Alignment align, double x_orig, double *x) {
|
2018-06-29 23:36:51 +02:00
|
|
|
double line_width;
|
|
|
|
|
|
|
|
switch(align) {
|
|
|
|
case ALIGN_LEFT: {
|
|
|
|
*x = x_orig;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ALIGN_RIGHT: {
|
2019-01-04 23:59:39 +01:00
|
|
|
line_width = text_ucs4_width_raw(font, ucs4text, 1);
|
2018-06-29 23:36:51 +02:00
|
|
|
*x = x_orig - line_width;
|
|
|
|
break;
|
|
|
|
}
|
2017-04-07 01:54:59 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
case ALIGN_CENTER: {
|
2019-01-04 23:59:39 +01:00
|
|
|
line_width = text_ucs4_width_raw(font, ucs4text, 1);
|
2018-06-29 23:36:51 +02:00
|
|
|
*x = x_orig - line_width * 0.5;
|
|
|
|
break;
|
|
|
|
}
|
2017-04-07 01:54:59 +02:00
|
|
|
}
|
2017-03-28 16:52:53 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
ShaderProgram* text_get_default_shader(void) {
|
|
|
|
return globals.default_shader;
|
2018-06-08 06:49:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
attr_returns_nonnull
|
2019-10-03 01:49:10 +02:00
|
|
|
static Font *font_from_params(const TextParams *params) {
|
2018-06-29 23:36:51 +02:00
|
|
|
Font *font = params->font_ptr;
|
|
|
|
|
|
|
|
if(font == NULL) {
|
|
|
|
if(params->font != NULL) {
|
|
|
|
font = get_font(params->font);
|
|
|
|
} else {
|
|
|
|
font = get_font("standard");
|
|
|
|
}
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
2018-06-08 06:49:40 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
assert(font != NULL);
|
|
|
|
return font;
|
2010-11-14 13:24:56 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 04:35:22 +02:00
|
|
|
static void set_batch_texture(SpriteStateParams *stp, Texture *tex) {
|
2019-10-03 01:49:10 +02:00
|
|
|
if(stp->primary_texture != tex) {
|
|
|
|
stp->primary_texture = tex;
|
|
|
|
r_sprite_batch_prepare_state(stp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
attr_nonnull(1, 2, 3)
|
2019-01-04 23:59:39 +01:00
|
|
|
static double _text_ucs4_draw(Font *font, const uint32_t *ucs4text, const TextParams *params) {
|
2019-10-03 01:49:10 +02:00
|
|
|
SpriteStateParams batch_state_params;
|
|
|
|
|
|
|
|
memcpy(batch_state_params.aux_textures, params->aux_textures, sizeof(batch_state_params.aux_textures));
|
|
|
|
|
|
|
|
if((batch_state_params.blend = params->blend) == 0) {
|
|
|
|
batch_state_params.blend = r_blend_current();
|
|
|
|
}
|
|
|
|
|
|
|
|
if((batch_state_params.shader = params->shader_ptr) == NULL) {
|
|
|
|
if(params->shader != NULL) {
|
|
|
|
batch_state_params.shader = r_shader_get(params->shader);
|
|
|
|
} else {
|
|
|
|
batch_state_params.shader = r_shader_current();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
batch_state_params.primary_texture = NULL;
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
BBox bbox;
|
|
|
|
double x = params->pos.x;
|
|
|
|
double y = params->pos.y;
|
2019-05-14 11:00:51 +02:00
|
|
|
double scale = font->metrics.scale;
|
|
|
|
double iscale = 1 / scale;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
struct { double min, max; } x, y;
|
|
|
|
double w, h;
|
|
|
|
} overlay;
|
2017-12-13 20:05:12 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
text_ucs4_bbox(font, ucs4text, 0, &bbox);
|
2019-05-14 11:00:51 +02:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
Color color;
|
2018-02-06 07:19:25 +01:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
if(params->color == NULL) {
|
|
|
|
// XXX: sprite batch code defaults this to RGB(1, 1, 1)
|
|
|
|
color = *r_color_current();
|
|
|
|
} else {
|
|
|
|
color = *params->color;
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
2017-12-13 20:05:12 +01:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
ShaderCustomParams shader_params;
|
2017-12-13 20:05:12 +01:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
if(params->shader_params == NULL) {
|
|
|
|
memset(&shader_params, 0, sizeof(shader_params));
|
|
|
|
} else {
|
|
|
|
shader_params = *params->shader_params;
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
mat4 mat_texture;
|
|
|
|
mat4 mat_model;
|
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_tex_current(mat_texture);
|
|
|
|
r_mat_mv_current(mat_model);
|
2019-10-03 01:49:10 +02:00
|
|
|
|
|
|
|
glm_translate(mat_model, (vec3) { x, y } );
|
|
|
|
glm_scale(mat_model, (vec3) { iscale, iscale, 1 } );
|
2019-05-14 11:00:51 +02:00
|
|
|
|
|
|
|
double orig_x = x;
|
|
|
|
double orig_y = y;
|
2018-06-29 23:36:51 +02:00
|
|
|
x = y = 0;
|
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
adjust_xpos(font, ucs4text, params->align, 0, &x);
|
2019-01-04 23:59:39 +01:00
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
if(params->overlay_projection) {
|
|
|
|
FloatRect *op = params->overlay_projection;
|
|
|
|
overlay.x.min = (op->x - orig_x) * scale;
|
|
|
|
overlay.x.max = overlay.x.min + op->w * scale;
|
|
|
|
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.y.min = bbox.y.min - font->metrics.descent;
|
|
|
|
overlay.y.max = bbox.y.max - font->metrics.descent;
|
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
overlay.w = overlay.x.max - overlay.x.min;
|
|
|
|
overlay.h = overlay.y.max - overlay.y.min;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
glm_scale(mat_texture, (vec3) { 1/overlay.w, 1/overlay.h, 1.0 });
|
|
|
|
glm_translate(mat_texture, (vec3) { -overlay.x.min, overlay.y.min, 0 });
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2018-08-28 10:25:54 +02:00
|
|
|
// FIXME: is there a better way?
|
|
|
|
float texmat_offset_sign;
|
|
|
|
|
|
|
|
if(r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN)) {
|
|
|
|
texmat_offset_sign = -1;
|
|
|
|
} else {
|
|
|
|
texmat_offset_sign = 1;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
uint prev_glyph_idx = 0;
|
2019-01-04 23:59:39 +01:00
|
|
|
const uint32_t *tptr = ucs4text;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
while(*tptr) {
|
2019-01-04 23:59:39 +01:00
|
|
|
uint32_t uchar = *tptr++;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
if(uchar == '\n') {
|
2019-05-14 11:00:51 +02:00
|
|
|
adjust_xpos(font, tptr, params->align, 0, &x);
|
2018-06-29 23:36:51 +02:00
|
|
|
y += font->metrics.lineskip;
|
|
|
|
continue;
|
2017-12-13 20:05:12 +01:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
Glyph *glyph = get_glyph(font, uchar);
|
|
|
|
|
|
|
|
if(glyph == NULL) {
|
|
|
|
continue;
|
2017-12-13 20:05:12 +01:00
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
x += apply_kerning(font, prev_glyph_idx, glyph);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(glyph->sprite.tex != NULL) {
|
2019-10-03 01:49:10 +02:00
|
|
|
Sprite *spr = &glyph->sprite;
|
2020-04-25 04:35:22 +02:00
|
|
|
set_batch_texture(&batch_state_params, spr->tex);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
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 });
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
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 } );
|
2017-10-15 11:19:25 +02:00
|
|
|
|
2020-04-25 04:35:22 +02:00
|
|
|
attribs.texrect = spr->tex_area;
|
2019-10-03 01:49:10 +02:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
2017-10-15 11:19:25 +02:00
|
|
|
|
2019-10-03 01:49:10 +02:00
|
|
|
r_sprite_batch_add_instance(&attribs);
|
2018-06-29 23:36:51 +02:00
|
|
|
}
|
2017-04-02 16:06:18 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
x += glyph->metrics.advance;
|
|
|
|
prev_glyph_idx = glyph->ft_index;
|
2017-04-02 16:06:18 +02:00
|
|
|
}
|
|
|
|
|
2019-05-14 11:00:51 +02:00
|
|
|
return x * iscale;
|
2011-06-13 18:48:36 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static double _text_draw(Font *font, const char *text, const TextParams *params) {
|
|
|
|
uint32_t buf[strlen(text) + 1];
|
|
|
|
utf8_to_ucs4(text, sizeof(buf), buf);
|
|
|
|
|
|
|
|
if(params->max_width > 0) {
|
|
|
|
text_ucs4_shorten(font, buf, params->max_width);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _text_ucs4_draw(font, buf, params);
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
double text_draw(const char *text, const TextParams *params) {
|
|
|
|
return _text_draw(font_from_params(params), text, params);
|
2017-12-28 06:31:39 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
double text_ucs4_draw(const uint32_t *text, const TextParams *params) {
|
|
|
|
Font *font = font_from_params(params);
|
|
|
|
|
|
|
|
if(params->max_width > 0) {
|
|
|
|
uint32_t buf[ucs4len(text) + 1];
|
|
|
|
memcpy(buf, text, sizeof(buf));
|
|
|
|
text_ucs4_shorten(font, buf, params->max_width);
|
|
|
|
return _text_ucs4_draw(font, buf, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _text_ucs4_draw(font, text, params);
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
double text_draw_wrapped(const char *text, double 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);
|
2017-10-23 12:10:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbox) {
|
|
|
|
text_bbox(font, text, 0, out_bbox);
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
int bbox_width = out_bbox->x.max - out_bbox->x.min;
|
|
|
|
int bbox_height = out_bbox->y.max - out_bbox->y.min;
|
2017-12-14 04:48:30 +01:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
if(bbox_height < font->metrics.max_glyph_height) {
|
|
|
|
out_bbox->y.min -= font->metrics.max_glyph_height - bbox_height;
|
|
|
|
bbox_height = out_bbox->y.max - out_bbox->y.min;
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
Texture *tex = globals.render_tex;
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
int tex_new_w = bbox_width; // max(tex->w, bbox_width);
|
|
|
|
int tex_new_h = bbox_height; // max(tex->h, bbox_height);
|
2018-09-14 09:37:20 +02:00
|
|
|
uint tex_w, tex_h;
|
|
|
|
r_texture_get_size(tex, 0, &tex_w, &tex_h);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
if(tex_new_w != tex_w || tex_new_h != tex_h) {
|
2018-06-29 23:36:51 +02:00
|
|
|
log_info(
|
|
|
|
"Resizing texture: %ix%i --> %ix%i",
|
2018-09-14 09:37:20 +02:00
|
|
|
tex_w, tex_h,
|
2018-06-29 23:36:51 +02:00
|
|
|
tex_new_w, tex_new_h
|
|
|
|
);
|
|
|
|
|
2018-07-10 11:14:29 +02:00
|
|
|
TextureParams params;
|
|
|
|
r_texture_get_params(tex, ¶ms);
|
|
|
|
r_texture_destroy(tex);
|
|
|
|
params.width = tex_new_w;
|
|
|
|
params.height = tex_new_h;
|
|
|
|
params.mipmaps = 0;
|
2018-09-14 09:37:20 +02:00
|
|
|
globals.render_tex = tex = r_texture_create(¶ms);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
char buf[128];
|
|
|
|
snprintf(buf, sizeof(buf), "Font render texture");
|
|
|
|
r_texture_set_debug_label(tex, buf);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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);
|
2017-12-14 04:48:30 +01:00
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
r_state_push();
|
|
|
|
|
2018-09-14 09:37:20 +02:00
|
|
|
r_framebuffer(globals.render_buf);
|
|
|
|
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
r_blend(BLEND_PREMUL_ALPHA);
|
2018-06-29 23:36:51 +02:00
|
|
|
r_enable(RCAP_CULL_FACE);
|
2018-08-28 10:25:54 +02:00
|
|
|
r_cull(CULL_BACK);
|
2018-06-29 23:36:51 +02:00
|
|
|
r_disable(RCAP_DEPTH_TEST);
|
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_push_identity();
|
|
|
|
r_mat_proj_push_ortho(tex_new_w, tex_new_h);
|
|
|
|
r_mat_tex_push_identity();
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
font->metrics.scale = 1;
|
|
|
|
|
|
|
|
text_draw(text, &(TextParams) {
|
|
|
|
.font_ptr = font,
|
|
|
|
.pos = { -out_bbox->x.min, -out_bbox->y.min + font->metrics.descent },
|
2018-07-23 19:07:59 +02:00
|
|
|
.color = RGB(1, 1, 1),
|
2018-06-29 23:36:51 +02:00
|
|
|
.shader = "text_default",
|
|
|
|
});
|
2012-07-29 22:39:52 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
font->metrics.scale = fontscale;
|
|
|
|
r_flush_sprites();
|
2012-08-05 03:36:55 +02:00
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_tex_pop();
|
|
|
|
r_mat_proj_pop();
|
|
|
|
r_mat_mv_pop();
|
2017-08-28 13:51:05 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
r_state_pop();
|
|
|
|
|
|
|
|
out_sprite->tex = tex;
|
2020-04-25 04:35:22 +02:00
|
|
|
out_sprite->tex_area.w = 1;
|
|
|
|
out_sprite->tex_area.h = 1;
|
2018-06-29 23:36:51 +02:00
|
|
|
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;
|
2017-12-28 04:49:37 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
void text_ucs4_shorten(Font *font, uint32_t *text, double width) {
|
|
|
|
assert(!ucs4chr(text, '\n'));
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(text_ucs4_width(font, text, 0) <= width) {
|
|
|
|
return;
|
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
int l = ucs4len(text);
|
2017-08-28 15:00:28 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
do {
|
|
|
|
if(l < 1) {
|
2017-08-28 15:00:28 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
text[l] = 0;
|
2019-01-04 23:59:39 +01:00
|
|
|
text[l - 1] = UNICODE_ELLIPSIS;
|
|
|
|
--l;
|
|
|
|
} while(text_ucs4_width(font, text, 0) > width);
|
2017-08-28 13:51:05 +02:00
|
|
|
}
|
2017-10-23 12:10:40 +02:00
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufsize) {
|
2017-10-23 12:10:40 +02:00
|
|
|
assert(bufsize > strlen(src) + 1);
|
|
|
|
assert(width > 0);
|
|
|
|
|
|
|
|
char src_copy[strlen(src) + 1];
|
|
|
|
char *sptr = src_copy;
|
|
|
|
char *next = NULL;
|
|
|
|
char *curline = buf;
|
|
|
|
|
|
|
|
strcpy(src_copy, src);
|
|
|
|
*buf = 0;
|
|
|
|
|
2019-03-11 00:21:43 +01:00
|
|
|
while((next = strtok_r(NULL, " \t", &sptr))) {
|
2017-10-23 12:10:40 +02:00
|
|
|
int curwidth;
|
|
|
|
|
|
|
|
if(!*next) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(*curline) {
|
2018-06-29 23:36:51 +02:00
|
|
|
curwidth = text_width(font, curline, 0);
|
2017-10-23 12:10:40 +02:00
|
|
|
} else {
|
|
|
|
curwidth = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
char tmpbuf[strlen(curline) + strlen(next) + 2];
|
|
|
|
strcpy(tmpbuf, curline);
|
|
|
|
strcat(tmpbuf, " ");
|
|
|
|
strcat(tmpbuf, next);
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
double totalwidth = text_width(font, tmpbuf, 0);
|
2017-10-23 12:10:40 +02:00
|
|
|
|
|
|
|
if(totalwidth > width) {
|
|
|
|
if(curwidth == 0) {
|
|
|
|
log_fatal(
|
|
|
|
"Single word '%s' won't fit on one line. "
|
2018-06-29 23:36:51 +02:00
|
|
|
"Word width: %g, max width: %g, source string: %s",
|
|
|
|
next, text_width(font, tmpbuf, 0), width, src
|
2017-10-23 12:10:40 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
strlcat(buf, "\n", bufsize);
|
|
|
|
curline = strchr(curline, 0);
|
|
|
|
} else {
|
|
|
|
if(*curline) {
|
|
|
|
strlcat(buf, " ", bufsize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
strlcat(buf, next, bufsize);
|
|
|
|
}
|
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
|
|
const FontMetrics* font_get_metrics(Font *font) {
|
|
|
|
return &font->metrics;
|
|
|
|
}
|
|
|
|
|
|
|
|
double font_get_lineskip(Font *font) {
|
|
|
|
return font->metrics.lineskip / font->metrics.scale;
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
double font_get_ascent(Font *font) {
|
|
|
|
return font->metrics.descent / font->metrics.scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
double font_get_descent(Font *font) {
|
|
|
|
return font->metrics.descent / font->metrics.scale;
|
|
|
|
}
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
const GlyphMetrics* font_get_char_metrics(Font *font, charcode_t c) {
|
|
|
|
Glyph *g = get_glyph(font, c);
|
|
|
|
|
|
|
|
if(!g) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return &g->metrics;
|
|
|
|
}
|