Make the freetype text rendering code re-useable in multiple contexts
This commit is contained in:
parent
b1da76eefa
commit
0ec7e6c36f
4 changed files with 68 additions and 32 deletions
|
|
@ -237,7 +237,6 @@ PyInit_fast_data_types(void) {
|
|||
if (!init_child_monitor(m)) return NULL;
|
||||
if (!init_ColorProfile(m)) return NULL;
|
||||
if (!init_Screen(m)) return NULL;
|
||||
if (!init_glfw(m)) return NULL;
|
||||
if (!init_child(m)) return NULL;
|
||||
if (!init_state(m)) return NULL;
|
||||
if (!init_keys(m)) return NULL;
|
||||
|
|
@ -257,6 +256,7 @@ PyInit_fast_data_types(void) {
|
|||
if (!init_freetype_render_ui_text(m)) return NULL;
|
||||
#endif
|
||||
if (!init_fonts(m)) return NULL;
|
||||
if (!init_glfw(m)) return NULL;
|
||||
|
||||
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
|
||||
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
|
||||
|
|
|
|||
|
|
@ -37,11 +37,18 @@ typedef struct {
|
|||
int bitmap_left, bitmap_top;
|
||||
} ProcessedBitmap;
|
||||
|
||||
typedef struct RenderCtx {
|
||||
bool created;
|
||||
Face main_face;
|
||||
FontConfigFace main_face_information;
|
||||
FamilyInformation main_face_family;
|
||||
hb_buffer_t *hb_buffer;
|
||||
} RenderCtx;
|
||||
|
||||
Face main_face = {0};
|
||||
FontConfigFace main_face_information = {0};
|
||||
FamilyInformation main_face_family = {0};
|
||||
hb_buffer_t *hb_buffer = NULL;
|
||||
#define main_face ctx->main_face
|
||||
#define main_face_information ctx->main_face_information
|
||||
#define main_face_family ctx->main_face_family
|
||||
#define hb_buffer ctx->hb_buffer
|
||||
|
||||
static inline FT_UInt
|
||||
glyph_id_for_codepoint(Face *face, char_type cp) {
|
||||
|
|
@ -58,7 +65,7 @@ free_face(Face *face) {
|
|||
}
|
||||
|
||||
static void
|
||||
cleanup(void) {
|
||||
cleanup(RenderCtx *ctx) {
|
||||
free_face(&main_face);
|
||||
free(main_face_information.path); main_face_information.path = NULL;
|
||||
free(main_face_family.name);
|
||||
|
|
@ -68,9 +75,10 @@ cleanup(void) {
|
|||
}
|
||||
|
||||
void
|
||||
set_main_face_family(const char *family, bool bold, bool italic) {
|
||||
set_main_face_family(FreeTypeRenderCtx ctx_, const char *family, bool bold, bool italic) {
|
||||
RenderCtx *ctx = (RenderCtx*)ctx_;
|
||||
if (family == main_face_family.name || (main_face_family.name && strcmp(family, main_face_family.name) == 0)) return;
|
||||
cleanup();
|
||||
cleanup(ctx);
|
||||
main_face_family.name = strdup(family);
|
||||
main_face_family.bold = bold; main_face_family.italic = italic;
|
||||
}
|
||||
|
|
@ -85,7 +93,6 @@ get_load_flags(int hinting, int hintstyle, int base) {
|
|||
return flags;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
load_font(FontConfigFace *info, Face *ans) {
|
||||
ans->freetype = native_face_from_path(info->path, info->index);
|
||||
|
|
@ -98,7 +105,7 @@ load_font(FontConfigFace *info, Face *ans) {
|
|||
}
|
||||
|
||||
static bool
|
||||
ensure_state(void) {
|
||||
ensure_state(RenderCtx *ctx) {
|
||||
if (main_face.freetype && main_face.hb) return true;
|
||||
if (!information_for_font_family(main_face_family.name, main_face_family.bold, main_face_family.italic, &main_face_information)) return false;
|
||||
if (!load_font(&main_face_information, &main_face)) return false;
|
||||
|
|
@ -107,6 +114,8 @@ ensure_state(void) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int
|
||||
font_units_to_pixels_y(FT_Face face, int x) {
|
||||
return (int)ceil((double)FT_MulFix(x, face->size->metrics.y_scale) / 64.0);
|
||||
|
|
@ -129,7 +138,7 @@ choose_bitmap_size(FT_Face face, FT_UInt desired_height) {
|
|||
}
|
||||
|
||||
static void
|
||||
set_pixel_size(Face *face, FT_UInt sz) {
|
||||
set_pixel_size(RenderCtx *ctx, Face *face, FT_UInt sz) {
|
||||
if (sz != face->pixel_size) {
|
||||
if (FT_HAS_COLOR(face->freetype)) sz = choose_bitmap_size(face->freetype, font_units_to_pixels_y(main_face.freetype, main_face.freetype->height));
|
||||
else FT_Set_Pixel_Sizes(face->freetype, sz, sz);
|
||||
|
|
@ -220,7 +229,7 @@ populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBit
|
|||
}
|
||||
|
||||
static bool
|
||||
render_run(RenderState *rs) {
|
||||
render_run(RenderCtx *ctx, RenderState *rs) {
|
||||
hb_buffer_guess_segment_properties(hb_buffer);
|
||||
if (!HB_DIRECTION_IS_HORIZONTAL(hb_buffer_get_direction(hb_buffer))) {
|
||||
PyErr_SetString(PyExc_ValueError, "Vertical text is not supported");
|
||||
|
|
@ -229,7 +238,7 @@ render_run(RenderState *rs) {
|
|||
FT_Face face = rs->current_face->freetype;
|
||||
bool has_color = FT_HAS_COLOR(face);
|
||||
FT_UInt pixel_size = rs->sz_px;
|
||||
set_pixel_size(rs->current_face, pixel_size);
|
||||
set_pixel_size(ctx, rs->current_face, pixel_size);
|
||||
hb_shape(rs->current_face->hb, hb_buffer, NULL, 0);
|
||||
unsigned int len = hb_buffer_get_length(hb_buffer);
|
||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
|
||||
|
|
@ -281,7 +290,7 @@ render_run(RenderState *rs) {
|
|||
}
|
||||
|
||||
static Face*
|
||||
find_fallback_font_for(char_type codep, char_type next_codep) {
|
||||
find_fallback_font_for(RenderCtx *ctx, char_type codep, char_type next_codep) {
|
||||
if (glyph_id_for_codepoint(&main_face, codep) > 0) return &main_face;
|
||||
for (size_t i = 0; i < main_face.count; i++) {
|
||||
if (glyph_id_for_codepoint(main_face.fallbacks + i, codep) > 0) return main_face.fallbacks + i;
|
||||
|
|
@ -301,7 +310,7 @@ find_fallback_font_for(char_type codep, char_type next_codep) {
|
|||
}
|
||||
|
||||
static bool
|
||||
process_codepoint(RenderState *rs, char_type codep, char_type next_codep) {
|
||||
process_codepoint(RenderCtx *ctx, RenderState *rs, char_type codep, char_type next_codep) {
|
||||
bool add_to_current_buffer = false;
|
||||
Face *fallback_font = NULL;
|
||||
if (is_combining_char(codep)) {
|
||||
|
|
@ -311,12 +320,12 @@ process_codepoint(RenderState *rs, char_type codep, char_type next_codep) {
|
|||
if (!add_to_current_buffer) fallback_font = &main_face;
|
||||
} else {
|
||||
if (glyph_id_for_codepoint(rs->current_face, codep) > 0) fallback_font = rs->current_face;
|
||||
else fallback_font = find_fallback_font_for(codep, next_codep);
|
||||
else fallback_font = find_fallback_font_for(ctx, codep, next_codep);
|
||||
add_to_current_buffer = !fallback_font || rs->current_face == fallback_font;
|
||||
}
|
||||
if (!add_to_current_buffer) {
|
||||
if (rs->pending_in_buffer) {
|
||||
if (!render_run(rs)) return false;
|
||||
if (!render_run(ctx, rs)) return false;
|
||||
rs->pending_in_buffer = 0;
|
||||
hb_buffer_clear_contents(hb_buffer);
|
||||
}
|
||||
|
|
@ -328,8 +337,9 @@ process_codepoint(RenderState *rs, char_type codep, char_type next_codep) {
|
|||
}
|
||||
|
||||
bool
|
||||
render_single_line(const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset) {
|
||||
if (!ensure_state()) return false;
|
||||
render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset) {
|
||||
RenderCtx *ctx = (RenderCtx*)ctx_;
|
||||
if (!ctx->created) return false;
|
||||
bool has_text = text && text[0];
|
||||
pixel pbg = premult_pixel(bg, ((bg >> 24) & 0xff));
|
||||
for (pixel *px = (pixel*)output_buf, *end = ((pixel*)output_buf) + width * height; px < end; px++) *px = pbg;
|
||||
|
|
@ -342,7 +352,7 @@ render_single_line(const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t
|
|||
if (!unicode) { PyErr_NoMemory(); return false; }
|
||||
bool ok = false;
|
||||
text_len = decode_utf8_string(text, text_len, unicode);
|
||||
set_pixel_size(&main_face, sz_px);
|
||||
set_pixel_size(ctx, &main_face, sz_px);
|
||||
unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height);
|
||||
if (text_height < height) y_offset -= (height - text_height) / 2;
|
||||
RenderState rs = {
|
||||
|
|
@ -351,10 +361,10 @@ render_single_line(const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t
|
|||
};
|
||||
|
||||
for (size_t i = 0; i < text_len && rs.x < rs.output_width; i++) {
|
||||
if (!process_codepoint(&rs, unicode[i], unicode[i + 1])) goto end;
|
||||
if (!process_codepoint(ctx, &rs, unicode[i], unicode[i + 1])) goto end;
|
||||
}
|
||||
if (rs.pending_in_buffer && rs.x < rs.output_width) {
|
||||
if (!render_run(&rs)) goto end;
|
||||
if (!render_run(ctx, &rs)) goto end;
|
||||
rs.pending_in_buffer = 0;
|
||||
hb_buffer_clear_contents(hb_buffer);
|
||||
}
|
||||
|
|
@ -364,6 +374,16 @@ end:
|
|||
return ok;
|
||||
}
|
||||
|
||||
FreeTypeRenderCtx
|
||||
create_freetype_render_context(void) {
|
||||
RenderCtx *ctx = calloc(1, sizeof(RenderCtx));
|
||||
if (!ensure_state(ctx)) { free(ctx); return NULL; }
|
||||
ctx->created = true;
|
||||
return (FreeTypeRenderCtx)ctx;
|
||||
}
|
||||
|
||||
void
|
||||
release_freetype_render_context(FreeTypeRenderCtx ctx) { if (ctx) { cleanup((RenderCtx*)ctx); free(ctx); } }
|
||||
|
||||
static PyObject*
|
||||
render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
||||
|
|
@ -376,11 +396,13 @@ render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|||
float x_offset = 0, y_offset = 0;
|
||||
static const char* kwlist[] = {"text", "width", "height", "font_family", "bold", "italic", "fg", "bg", "x_offset", "y_offset", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "|sIIzppkkff", (char**)kwlist, &text, &width, &height, &family, &bold, &italic, &fg, &bg, &x_offset, &y_offset)) return NULL;
|
||||
if (family) set_main_face_family(family, bold, italic);
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, width * height * 4);
|
||||
if (!ans) return NULL;
|
||||
uint8_t *buffer = (u_int8_t*) PyBytes_AS_STRING(ans);
|
||||
if (!render_single_line(text, 3 * height / 4, 0, 0xffffffff, buffer, width, height, x_offset, y_offset)) {
|
||||
RenderCtx *ctx = (RenderCtx*)create_freetype_render_context();
|
||||
if (!ctx) return NULL;
|
||||
if (family) set_main_face_family((FreeTypeRenderCtx)ctx, family, bold, italic);
|
||||
if (!render_single_line((FreeTypeRenderCtx)ctx, text, 3 * height / 4, 0, 0xffffffff, buffer, width, height, x_offset, y_offset)) {
|
||||
Py_CLEAR(ans);
|
||||
if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "Unknown error while rendering text");
|
||||
ans = NULL;
|
||||
|
|
@ -395,6 +417,7 @@ render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|||
#undef c
|
||||
}
|
||||
}
|
||||
release_freetype_render_context((FreeTypeRenderCtx)ctx);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
|
@ -433,9 +456,5 @@ static PyMethodDef module_methods[] = {
|
|||
bool
|
||||
init_freetype_render_ui_text(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
if (Py_AtExit(cleanup) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the fontconfig library at exit handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,12 @@
|
|||
#include "data-types.h"
|
||||
#include <hb-ft.h>
|
||||
|
||||
bool render_single_line(const char *text, unsigned sz_px, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset);
|
||||
typedef struct {bool created;} *FreeTypeRenderCtx;
|
||||
|
||||
FreeTypeRenderCtx create_freetype_render_context(void);
|
||||
void set_main_face_family(FreeTypeRenderCtx ctx, const char *family, bool bold, bool italic);
|
||||
bool render_single_line(FreeTypeRenderCtx ctx, const char *text, unsigned sz_px, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset);
|
||||
void release_freetype_render_context(FreeTypeRenderCtx ctx);
|
||||
|
||||
typedef struct FontConfigFace {
|
||||
char *path;
|
||||
|
|
@ -24,5 +29,3 @@ bool fallback_font(char_type ch, const char *family, bool bold, bool italic, boo
|
|||
bool freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest);
|
||||
FT_Library freetype_library(void);
|
||||
void set_freetype_error(const char* prefix, int err_code);
|
||||
|
||||
void set_main_face_family(const char *family, bool bold, bool italic);
|
||||
|
|
|
|||
16
kitty/glfw.c
16
kitty/glfw.c
|
|
@ -394,14 +394,25 @@ apple_file_open_callback(const char* filepath) {
|
|||
}
|
||||
#else
|
||||
|
||||
static FreeTypeRenderCtx csd_title_render_ctx = NULL;
|
||||
|
||||
static bool
|
||||
draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset) {
|
||||
if (!set_callback_window(window)) return false;
|
||||
if (!csd_title_render_ctx) {
|
||||
csd_title_render_ctx = create_freetype_render_context();
|
||||
if (!csd_title_render_ctx) {
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
double xdpi, ydpi;
|
||||
get_window_dpi(window, &xdpi, &ydpi);
|
||||
unsigned px_sz = (unsigned)(global_state.callback_os_window->font_sz_in_pts * ydpi / 72.);
|
||||
px_sz = MIN(px_sz, 3 * height / 4);
|
||||
return render_single_line(text, px_sz, fg, bg, output_buf, width, height, x_offset, y_offset);
|
||||
bool ok = render_single_line(csd_title_render_ctx, text, px_sz, fg, bg, output_buf, width, height, x_offset, y_offset);
|
||||
if (!ok && PyErr_Occurred()) PyErr_Print();
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
// }}}
|
||||
|
|
@ -1406,6 +1417,9 @@ static PyMethodDef module_methods[] = {
|
|||
void cleanup_glfw(void) {
|
||||
if (logo.pixels) free(logo.pixels);
|
||||
logo.pixels = NULL;
|
||||
#ifndef __APPLE__
|
||||
release_freetype_render_context(csd_title_render_ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
// constants {{{
|
||||
|
|
|
|||
Loading…
Reference in a new issue