From 523aadaa3b79866a9d4bee05e73d433c659005ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 25 May 2018 11:28:46 +0530 Subject: [PATCH] Refactor font group handling Allow kitty to manage multiple groups of fonts with different cell sizes. Will eventually allow kitty to have different font sizes/dpi per OSWindow --- kitty/borders.py | 9 +- kitty/boss.py | 34 +-- kitty/child-monitor.c | 19 +- kitty/core_text.m | 24 +- kitty/data-types.h | 9 + kitty/fontconfig.c | 6 +- kitty/fonts.c | 589 ++++++++++++++++++++++++------------- kitty/fonts.h | 19 +- kitty/fonts/box_drawing.py | 9 +- kitty/fonts/render.py | 78 ++--- kitty/freetype.c | 83 ++---- kitty/glfw.c | 36 ++- kitty/graphics.c | 93 +++--- kitty/graphics.h | 10 +- kitty/main.py | 6 +- kitty/mouse.c | 36 +-- kitty/screen.c | 25 +- kitty/screen.h | 3 +- kitty/shaders.c | 139 +++++---- kitty/state.c | 75 ++--- kitty/state.h | 11 +- kitty/tabs.py | 17 +- kitty/window.py | 11 +- kitty_tests/__init__.py | 4 +- kitty_tests/fonts.py | 21 +- kitty_tests/graphics.py | 9 +- 26 files changed, 736 insertions(+), 639 deletions(-) diff --git a/kitty/borders.py b/kitty/borders.py index 58a8c19a2..37593b182 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -6,8 +6,7 @@ from functools import partial from itertools import chain from .fast_data_types import ( - BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, - pt_to_px + BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program ) from .utils import load_shaders @@ -39,11 +38,11 @@ def load_borders_program(): class Borders: - def __init__(self, os_window_id, tab_id, opts): + def __init__(self, os_window_id, tab_id, opts, border_width, padding_width): self.os_window_id = os_window_id self.tab_id = tab_id - self.border_width = pt_to_px(opts.window_border_width) - self.padding_width = pt_to_px(opts.window_padding_width) + self.border_width = border_width + self.padding_width = padding_width def __call__( self, diff --git a/kitty/boss.py b/kitty/boss.py index 3f6e3ad54..de5246e4b 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -19,14 +19,13 @@ from .constants import ( appname, config_dir, editor, set_boss, supports_primary_selection ) from .fast_data_types import ( - ChildMonitor, background_opacity_of, change_background_opacity, - create_os_window, current_os_window, destroy_global_data, - destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, - layout_sprite_map, mark_os_window_for_close, set_clipboard_string, - set_dpi_from_os_window, set_in_sequence_mode, show_window, - toggle_fullscreen, viewport_for_window + ChildMonitor, background_opacity_of, cell_size_for_window, + change_background_opacity, create_os_window, current_os_window, + destroy_global_data, get_clipboard_string, glfw_post_empty_event, + mark_os_window_for_close, set_clipboard_string, + set_in_sequence_mode, show_window, toggle_fullscreen ) -from .fonts.render import prerender, resize_fonts, set_font_family +from .fonts.render import resize_fonts from .keys import get_shortcut, shortcut_matches from .remote_control import handle_cmd from .rgb import Color, color_from_int @@ -39,11 +38,6 @@ from .utils import ( ) -def initialize_renderer(): - layout_sprite_map() - prerender() - - def listen_on(spec): import socket family, address, socket_path = parse_address_spec(spec) @@ -103,9 +97,7 @@ class Boss: ) set_boss(self) self.current_font_size = opts.font_size - set_font_family(opts) self.opts, self.args = opts, args - initialize_renderer() startup_session = create_session(opts, args) self.add_os_window(startup_session, os_window_id=os_window_id) @@ -317,13 +309,7 @@ class Boss: def on_window_resize(self, os_window_id, w, h, dpi_changed): tm = self.os_window_map.get(os_window_id) if tm is not None: - if dpi_changed: - if set_dpi_from_os_window(os_window_id): - self.on_dpi_change(os_window_id) - else: - tm.resize() - else: - tm.resize() + tm.resize() def increase_font_size(self): self.set_font_size( @@ -340,12 +326,11 @@ class Boss: def _change_font_size(self, new_size=None, on_dpi_change=False): if new_size is not None: self.current_font_size = new_size - old_cell_width, old_cell_height = viewport_for_window()[-2:] windows = tuple(filter(None, self.window_id_map.values())) + old_sz_map = {w.id: cell_size_for_window(w.os_window_id) for w in windows} resize_fonts(self.current_font_size, on_dpi_change=on_dpi_change) - layout_sprite_map() - prerender() for window in windows: + old_cell_width, old_cell_height = old_sz_map[window.id] window.screen.rescale_images(old_cell_width, old_cell_height) window.screen.refresh_sprite_positions() for tm in self.os_window_map.values(): @@ -653,7 +638,6 @@ class Boss: for tm in self.os_window_map.values(): tm.destroy() self.os_window_map = {} - destroy_sprite_map() destroy_global_data() def paste_to_active_window(self, text): diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index f069dc689..ef895ecd8 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -8,6 +8,7 @@ #include "threading.h" #include "state.h" #include "screen.h" +#include "fonts.h" #include #include #include @@ -486,7 +487,7 @@ static double last_render_at = -DBL_MAX; static inline double cursor_width(double w, bool vert, OSWindow *os_window) { - double dpi = vert ? global_state.logical_dpi_x : global_state.logical_dpi_y; + double dpi = vert ? os_window->fonts_data->logical_dpi_x : os_window->fonts_data->logical_dpi_y; double ans = w * dpi / 72.0; // as pixels double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height); return ans * factor; @@ -552,20 +553,6 @@ update_window_title(Window *w, OSWindow *os_window) { return false; } -static PyObject* -simple_render_screen(PyObject UNUSED *self, PyObject *args) { -#define simple_render_screen_doc "Render a Screen object, with no cursor" - Screen *screen; - float xstart, ystart, dx, dy; - static ssize_t vao_idx = -1, gvao_idx = -1; - if (vao_idx == -1) vao_idx = create_cell_vao(); - if (gvao_idx == -1) gvao_idx = create_graphics_vao(); - if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL; - send_cell_data_to_gpu(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window()); - draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window(), true); - Py_RETURN_NONE; -} - static inline bool prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows) { #define TD os_window->tab_bar_render_data @@ -661,6 +648,7 @@ render(double now) { } unsigned int active_window_id = 0, num_visible_windows = 0; color_type active_window_bg = 0; + if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); continue; } if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows)) needs_render = true; if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true; if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows); @@ -1330,7 +1318,6 @@ safe_pipe(PYNOARG) { } static PyMethodDef module_methods[] = { - METHOD(simple_render_screen, METH_VARARGS) METHODB(safe_pipe, METH_NOARGS), {NULL} /* Sentinel */ }; diff --git a/kitty/core_text.m b/kitty/core_text.m index 200a60d46..9647e50a4 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -178,7 +178,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) { } PyObject* -create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation) { +create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation, FONT_DATA_HANDLE fg UNUSED) { CTFace *self = (CTFace*)base_face; CTFontRef new_font; if (emoji_presentation) new_font = CTFontCreateWithName((CFStringRef)@"AppleColorEmoji", self->scaled_point_sz, NULL); @@ -214,14 +214,14 @@ is_glyph_empty(PyObject *s, glyph_index g) { } static inline float -scaled_point_sz() { - return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts; +scaled_point_sz(FONT_DATA_HANDLE fg) { + return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts; } bool -set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force) { +set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force, FONT_DATA_HANDLE fg) { CTFace *self = (CTFace*)s; - float sz = scaled_point_sz(); + float sz = scaled_point_sz(fg); if (!force && self->scaled_point_sz == sz) return true; CTFontRef new_font = CTFontCreateCopyWithAttributes(self->ct_font, sz, NULL, NULL); if (new_font == NULL) fatal("Out of memory"); @@ -282,17 +282,17 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u } PyObject* -face_from_descriptor(PyObject *descriptor) { +face_from_descriptor(PyObject *descriptor, FONT_DATA_HANDLE fg) { CTFontDescriptorRef desc = font_descriptor_from_python(descriptor); if (!desc) return NULL; - CTFontRef font = CTFontCreateWithFontDescriptor(desc, scaled_point_sz(), NULL); + CTFontRef font = CTFontCreateWithFontDescriptor(desc, scaled_point_sz(fg), NULL); CFRelease(desc); desc = NULL; if (!font) { PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); return NULL; } return (PyObject*) ct_face(font); } PyObject* -face_from_path(const char *path, int UNUSED index) { +face_from_path(const char *path, int UNUSED index, FONT_DATA_HANDLE fg UNUSED) { CFStringRef s = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, s, kCFURLPOSIXPathStyle, false); CGDataProviderRef dp = CGDataProviderCreateWithURL(url); @@ -371,7 +371,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned } static inline bool -do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize) { +do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONT_DATA_HANDLE fg) { unsigned int canvas_width = cell_width * num_cells; CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs); if (allow_resize) { @@ -382,7 +382,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g CGFloat sz = CTFontGetSize(ct_font); sz *= canvas_width / right; CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL); - bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false); + bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg); CFRelease(new_font); return ret; } @@ -408,10 +408,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g } bool -render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) { +render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONT_DATA_HANDLE fg) { CTFace *self = (CTFace*)s; for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint; - return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true); + return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg); } diff --git a/kitty/data-types.h b/kitty/data-types.h index e189257ba..07bfda4d8 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -209,6 +209,13 @@ typedef struct { DynamicColor configured, overridden; } ColorProfile; +typedef struct { + unsigned int width, height; +} CellPixelSize; + +typedef struct {int x;} *SPRITE_MAP_HANDLE; +#define FONTS_DATA_HEAD SPRITE_MAP_HANDLE sprite_map; double logical_dpi_x, logical_dpi_y, font_sz_in_pts; unsigned int cell_width, cell_height; +typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE; #define PARSER_BUF_SZ (8 * 1024) #define READ_BUF_SZ (1024*1024) @@ -273,3 +280,5 @@ void fake_scroll(int, bool); void set_special_key_combo(int glfw_key, int mods); void on_key_input(int key, int scancode, int action, int mods, const char*, int); void request_window_attention(id_type, bool); +SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int); +SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE); diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 5d3006b27..22d0d014b 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -182,7 +182,7 @@ specialize_font_descriptor(PyObject *base_descriptor) { AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path"); AP(FcPatternAddInteger, FC_INDEX, face_idx, "index"); AP(FcPatternAddDouble, FC_SIZE, global_state.font_sz_in_pts, "size"); - AP(FcPatternAddDouble, FC_DPI, (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.0, "dpi"); + AP(FcPatternAddDouble, FC_DPI, (global_state.default_dpi.x + global_state.default_dpi.y) / 2.0, "dpi"); ans = _fc_match(pat); if (face_idx > 0) { // For some reason FcFontMatch sets the index to zero, so manually restore it. @@ -194,7 +194,7 @@ end: } PyObject* -create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation) { +create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { PyObject *ans = NULL; FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); @@ -205,7 +205,7 @@ create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool ita size_t num = cell_as_unicode(cell, true, char_buf, ' '); add_charset(pat, num); PyObject *d = _fc_match(pat); - if (d) { ans = face_from_descriptor(d); Py_CLEAR(d); } + if (d) { ans = face_from_descriptor(d, fg); Py_CLEAR(d); } end: if (pat != NULL) FcPatternDestroy(pat); return ans; diff --git a/kitty/fonts.c b/kitty/fonts.c index 317130a66..bbe9fb73c 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -13,8 +13,9 @@ #define MISSING_GLYPH 4 #define MAX_NUM_EXTRA_GLYPHS 8 +#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3) -typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*); +typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*); send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; static PyObject *python_send_to_gpu_impl = NULL; extern PyTypeObject Line_Type; @@ -50,14 +51,29 @@ struct SpecialGlyphCache { }; typedef struct { - size_t max_array_len, max_texture_size, max_y; + size_t max_y; unsigned int x, y, z, xnum, ynum; } GPUSpriteTracker; -static GPUSpriteTracker sprite_tracker = {0}; static hb_buffer_t *harfbuzz_buffer = NULL; static char_type shape_buffer[4096] = {0}; +static size_t max_texture_size = 1024, max_array_len = 1024; + +typedef struct { + PyObject *face; + bool bold, italic; +} Descriptor; + +typedef struct { + char_type left, right; + size_t font_idx; +} SymbolMap; + +static Descriptor *descriptors = NULL; +static size_t num_symbol_fonts = 0; +static SymbolMap *symbol_maps = NULL; + typedef struct { @@ -69,18 +85,100 @@ typedef struct { } Font; typedef struct { - char_type left, right; - size_t font_idx; -} SymbolMap; - -typedef struct { + FONTS_DATA_HEAD + id_type id; + unsigned int baseline, underline_position, underline_thickness; + size_t fonts_capacity, fonts_count, fallback_fonts_count; + ssize_t medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx; Font *fonts; - SymbolMap* symbol_maps; - size_t fonts_capacity, fonts_count, symbol_maps_capacity, symbol_maps_count, symbol_map_fonts_count, fallback_fonts_count; - ssize_t box_font_idx, medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx; -} Fonts; + pixel *canvas; + GPUSpriteTracker sprite_tracker; +} FontGroup; + +static FontGroup* font_groups = NULL; +static size_t font_groups_capacity = 0; +static size_t num_font_groups = 0; +static id_type font_group_id_counter = 0; + +static inline void +save_window_font_groups() { + for (size_t o = 0; o < global_state.num_os_windows; o++) { + OSWindow *w = global_state.os_windows + o; + w->temp_font_group_id = w->fonts_data ? ((FontGroup*)(w->fonts_data))->id : 0; + } +} + +static inline void +restore_window_font_groups() { + for (size_t o = 0; o < global_state.num_os_windows; o++) { + OSWindow *w = global_state.os_windows + o; + w->fonts_data = NULL; + for (size_t i = 0; i < num_font_groups; i++) { + if (font_groups[i].id == w->temp_font_group_id) { + w->fonts_data = (FONTS_DATA_HANDLE)(font_groups + i); + break; + } + } + } +} + +static inline bool +font_group_is_unused(FontGroup *fg) { + for (size_t o = 0; o < global_state.num_os_windows; o++) { + OSWindow *w = global_state.os_windows + o; + if (w->temp_font_group_id == fg->id) return false; + } + return true; +} + +static inline void +trim_unused_font_groups() { + save_window_font_groups(); + size_t i = 0; + while (i < num_font_groups) { + if (font_group_is_unused(font_groups + i)) { + size_t num_to_right = (--num_font_groups) - i; + if (!num_to_right) break; + memmove(font_groups + i, font_groups + 1 + i, num_to_right * sizeof(FontGroup)); + } else i++; + } + restore_window_font_groups(); +} + +static inline void +add_font_group() { + if (num_font_groups) trim_unused_font_groups(); + if (num_font_groups >= font_groups_capacity) { + save_window_font_groups(); + font_groups_capacity += 5; + font_groups = realloc(font_groups, sizeof(FontGroup) * num_font_groups); + if (font_groups == NULL) fatal("Out of memory creating a new font group"); + restore_window_font_groups(); + } + num_font_groups++; +} + +static inline FontGroup* +font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) { + for (size_t i = 0; i < num_font_groups; i++) { + FontGroup *fg = font_groups + i; + if (fg->font_sz_in_pts == font_sz_in_pts && fg->logical_dpi_x == logical_dpi_x && fg->logical_dpi_y == logical_dpi_y) return fg; + } + add_font_group(); + FontGroup *fg = font_groups + num_font_groups - 1; + memset(fg, 0, sizeof(FontGroup)); + fg->font_sz_in_pts = font_sz_in_pts; + fg->logical_dpi_x = logical_dpi_x; + fg->logical_dpi_y = logical_dpi_y; + fg->id = ++font_group_id_counter; + return fg; +} + +static inline void +clear_canvas(FontGroup *fg) { + if (fg->canvas) memset(fg->canvas, 0, CELLS_IN_CANVAS * fg->cell_width * fg->cell_height * sizeof(pixel)); +} -static Fonts fonts = {0}; // Sprites {{{ @@ -98,20 +196,20 @@ sprite_map_set_error(int error) { } void -sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len) { - sprite_tracker.max_texture_size = max_texture_size; - sprite_tracker.max_array_len = MIN(0xfff, max_array_len); +sprite_tracker_set_limits(size_t max_texture_size_, size_t max_array_len_) { + max_texture_size = max_texture_size_; + max_array_len = MIN(0xfff, max_array_len_); } static inline void -do_increment(int *error) { - sprite_tracker.x++; - if (sprite_tracker.x >= sprite_tracker.xnum) { - sprite_tracker.x = 0; sprite_tracker.y++; - sprite_tracker.ynum = MIN(MAX(sprite_tracker.ynum, sprite_tracker.y + 1), sprite_tracker.max_y); - if (sprite_tracker.y >= sprite_tracker.max_y) { - sprite_tracker.y = 0; sprite_tracker.z++; - if (sprite_tracker.z >= MIN(UINT16_MAX, sprite_tracker.max_array_len)) *error = 2; +do_increment(FontGroup *fg, int *error) { + fg->sprite_tracker.x++; + if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) { + fg->sprite_tracker.x = 0; fg->sprite_tracker.y++; + fg->sprite_tracker.ynum = MIN(MAX(fg->sprite_tracker.ynum, fg->sprite_tracker.y + 1), fg->sprite_tracker.max_y); + if (fg->sprite_tracker.y >= fg->sprite_tracker.max_y) { + fg->sprite_tracker.y = 0; fg->sprite_tracker.z++; + if (fg->sprite_tracker.z >= MIN(UINT16_MAX, max_array_len)) *error = 2; } } } @@ -128,7 +226,7 @@ extra_glyphs_equal(ExtraGlyphs *a, ExtraGlyphs *b) { static SpritePosition* -sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) { +sprite_position_for(FontGroup *fg, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) { glyph_index idx = glyph & (SPECIAL_GLYPH_CACHE_SIZE - 1); SpritePosition *s = font->sprite_map + idx; // Optimize for the common case of glyph under 1024 already in the cache @@ -151,8 +249,8 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui s->filled = true; s->rendered = false; s->colored = false; - s->x = sprite_tracker.x; s->y = sprite_tracker.y; s->z = sprite_tracker.z; - do_increment(error); + s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z; + do_increment(fg, error); return s; } @@ -179,8 +277,9 @@ special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t filled_mask) { } void -sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z) { - *x = sprite_tracker.xnum; *y = sprite_tracker.ynum; *z = sprite_tracker.z; +sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z) { + FontGroup *fg = (FontGroup*)data; + *x = fg->sprite_tracker.xnum; *y = fg->sprite_tracker.ynum; *z = fg->sprite_tracker.z; } void @@ -230,30 +329,30 @@ clear_special_glyph_cache(Font *font) { #undef CLEAR } -void -sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height) { - sprite_tracker.xnum = MIN(MAX(1, sprite_tracker.max_texture_size / cell_width), UINT16_MAX); - sprite_tracker.max_y = MIN(MAX(1, sprite_tracker.max_texture_size / cell_height), UINT16_MAX); - sprite_tracker.ynum = 1; - sprite_tracker.x = 0; sprite_tracker.y = 0; sprite_tracker.z = 0; +static void +sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_width, unsigned int cell_height) { + sprite_tracker->xnum = MIN(MAX(1, max_texture_size / cell_width), UINT16_MAX); + sprite_tracker->max_y = MIN(MAX(1, max_texture_size / cell_height), UINT16_MAX); + sprite_tracker->ynum = 1; + sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0; } // }}} static inline PyObject* -desc_to_face(PyObject *desc) { +desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) { PyObject *d = specialize_font_descriptor(desc); if (d == NULL) return NULL; - PyObject *ans = face_from_descriptor(d); + PyObject *ans = face_from_descriptor(d, fg); Py_DECREF(d); return ans; } static inline bool -init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation) { +init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation, FONTS_DATA_HANDLE fg) { PyObject *face; if (is_face) { face = descriptor; Py_INCREF(face); } - else { face = desc_to_face(descriptor); if (face == NULL) return false; } + else { face = desc_to_face(descriptor, fg); if (face == NULL) return false; } f->face = face; f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation; return true; @@ -266,37 +365,47 @@ del_font(Font *f) { f->bold = false; f->italic = false; } -static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0; -static pixel *canvas = NULL; -#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3) static inline void -clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); } +del_font_group(FontGroup *fg) { + free(fg->canvas); fg->canvas = NULL; + fg->sprite_map = free_sprite_map(fg->sprite_map); + for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i); + free(fg->fonts); fg->fonts = NULL; +} + +static inline void +free_font_groups() { + if (font_groups) { + for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i); + free(font_groups); font_groups = NULL; + font_groups_capacity = 0; num_font_groups = 0; + } +} static void -python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) { +python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel* buf) { if (python_send_to_gpu_impl != NULL && python_send_to_gpu_impl != Py_None) { - PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * cell_width * cell_height)); + if (!num_font_groups) fatal("Cannot call send to gpu with no font groups"); + PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * fg->cell_width * fg->cell_height)); if (ret == NULL) PyErr_Print(); else Py_DECREF(ret); } } -static inline PyObject* -update_cell_metrics(bool on_dpi_change UNUSED) { -#define CALL(idx, desired_height, force) { if (idx >= 0) { Font *f = fonts.fonts + idx; if ((f)->face) { if(!set_size_for_face((f)->face, desired_height, force)) return NULL; } clear_sprite_map((f)); }} - CALL(BOX_FONT, 0, false); CALL(fonts.medium_font_idx, 0, false); - CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false); - cell_metrics(fonts.fonts[fonts.medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness); - if (!cell_width) { PyErr_SetString(PyExc_ValueError, "Failed to calculate cell width for the specified font."); return NULL; } +static inline void +calc_cell_metrics(FontGroup *fg) { + unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness; + cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness); + if (!cell_width) fatal("Failed to calculate cell width for the specified font"); unsigned int before_cell_height = cell_height; if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px); if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac); if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px); if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac); int line_height_adjustment = cell_height - before_cell_height; - if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; } - if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; } + if (cell_height < 4) fatal("line height too small after adjustment"); + if (cell_height > 1000) fatal("line height too large after adjustment"); underline_position = MIN(cell_height - 1, underline_position); // ensure there is at least a couple of pixels available to render styled underlines while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--; @@ -304,25 +413,11 @@ update_cell_metrics(bool on_dpi_change UNUSED) { baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); } - sprite_tracker_set_layout(cell_width, cell_height); - global_state.cell_width = cell_width; global_state.cell_height = cell_height; - free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); - if (canvas == NULL) return PyErr_NoMemory(); - for (ssize_t i = 0, j = fonts.first_symbol_font_idx; i < (ssize_t)fonts.symbol_map_fonts_count; i++, j++) { - CALL(j, cell_height, true); - } - for (ssize_t i = 0, j = fonts.first_fallback_font_idx; i < (ssize_t)fonts.fallback_fonts_count; i++, j++) { - CALL(j, cell_height, true); - } - return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness); -#undef CALL -} - -static PyObject* -set_font_size(PyObject UNUSED *m, PyObject *args) { - int on_dpi_change = 0; - if (!PyArg_ParseTuple(args, "f|p", &global_state.font_sz_in_pts, &on_dpi_change)) return NULL; - return update_cell_metrics(on_dpi_change != 0); + sprite_tracker_set_layout(&fg->sprite_tracker, cell_width, cell_height); + fg->cell_width = cell_width; fg->cell_height = cell_height; + free(fg->canvas); + fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel)); + if (!fg->canvas) fatal("Out of memory allocating canvas for font group"); } static inline bool @@ -361,59 +456,59 @@ output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_present } static inline ssize_t -load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation) { - if (fonts.fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; } +load_fallback_font(FontGroup *fg, Cell *cell, bool bold, bool italic, bool emoji_presentation) { + if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; } ssize_t f; - if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx; - else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx; - if (f < 0) f = fonts.medium_font_idx; + if (bold) f = fg->italic_font_idx > 0 ? fg->bi_font_idx : fg->bold_font_idx; + else f = italic ? fg->italic_font_idx : fg->medium_font_idx; + if (f < 0) f = fg->medium_font_idx; - PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic, emoji_presentation); + PyObject *face = create_fallback_face(fg->fonts[f].face, cell, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg); if (face == NULL) { PyErr_Print(); return MISSING_FONT; } if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; } if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face, true); - set_size_for_face(face, cell_height, true); + set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg); - ensure_space_for(&fonts, fonts, Font, fonts.fonts_count + 1, fonts_capacity, 5, true); - ssize_t ans = fonts.first_fallback_font_idx + fonts.fallback_fonts_count; - Font *af = &fonts.fonts[ans]; - if (!init_font(af, face, bold, italic, true, emoji_presentation)) fatal("Out of memory"); + ensure_space_for(fg, fonts, Font, fg->fonts_count + 1, fonts_capacity, 5, true); + ssize_t ans = fg->first_fallback_font_idx + fg->fallback_fonts_count; + Font *af = &fg->fonts[ans]; + if (!init_font(af, face, bold, italic, true, emoji_presentation, (FONTS_DATA_HANDLE)fg)) fatal("Out of memory"); Py_DECREF(face); - fonts.fallback_fonts_count++; - fonts.fonts_count++; + fg->fallback_fonts_count++; + fg->fonts_count++; return ans; } static inline ssize_t -fallback_font(Cell *cell) { +fallback_font(FontGroup *fg, Cell *cell) { bool bold = (cell->attrs >> BOLD_SHIFT) & 1; bool italic = (cell->attrs >> ITALIC_SHIFT) & 1; bool emoji_presentation = has_emoji_presentation(cell); // Check if one of the existing fallback fonts has this text - for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) { - Font *ff = fonts.fonts +j; + for (size_t i = 0, j = fg->first_fallback_font_idx; i < fg->fallback_fonts_count; i++, j++) { + Font *ff = fg->fonts +j; if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cell)) { if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, ff->face, false); return j; } } - return load_fallback_font(cell, bold, italic, emoji_presentation); + return load_fallback_font(fg, cell, bold, italic, emoji_presentation); } static inline ssize_t -in_symbol_maps(char_type ch) { - for (size_t i = 0; i < fonts.symbol_maps_count; i++) { - if (fonts.symbol_maps[i].left <= ch && ch <= fonts.symbol_maps[i].right) return fonts.first_symbol_font_idx + fonts.symbol_maps[i].font_idx; +in_symbol_maps(FontGroup *fg, char_type ch) { + for (size_t i = 0; i < num_symbol_fonts; i++) { + if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx; } return NO_FONT; } static ssize_t -font_for_cell(Cell *cell) { +font_for_cell(FontGroup *fg, Cell *cell) { START_ALLOW_CASE_RANGE ssize_t ans; switch(cell->ch) { @@ -428,21 +523,21 @@ START_ALLOW_CASE_RANGE case 0xe0b6: return BOX_FONT; default: - ans = in_symbol_maps(cell->ch); + ans = in_symbol_maps(fg, cell->ch); if (ans > -1) return ans; switch(BI_VAL(cell->attrs)) { case 0: - ans = fonts.medium_font_idx; break; + ans = fg->medium_font_idx; break; case 1: - ans = fonts.bold_font_idx ; break; + ans = fg->bold_font_idx ; break; case 2: - ans = fonts.italic_font_idx; break; + ans = fg->italic_font_idx; break; case 3: - ans = fonts.bi_font_idx; break; + ans = fg->bi_font_idx; break; } - if (ans < 0) ans = fonts.medium_font_idx; - if (!has_emoji_presentation(cell) && has_cell_text(fonts.fonts + ans, cell)) return ans; - return fallback_font(cell); + if (ans < 0) ans = fg->medium_font_idx; + if (!has_emoji_presentation(cell) && has_cell_text(fg->fonts + ans, cell)) return ans; + return fallback_font(fg, cell); } END_ALLOW_CASE_RANGE } @@ -472,7 +567,7 @@ START_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE } -static PyObject* box_drawing_function = NULL; +static PyObject* box_drawing_function = NULL, *prerender_function = NULL; void render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { @@ -488,11 +583,11 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de } static void -render_box_cell(Cell *cell) { +render_box_cell(FontGroup *fg, Cell *cell) { int error = 0; glyph_index glyph = box_glyph_id(cell->ch); static ExtraGlyphs extra_glyphs = {{0}}; - SpritePosition *sp = sprite_position_for(&fonts.fonts[BOX_FONT], glyph, &extra_glyphs, false, &error); + SpritePosition *sp = sprite_position_for(fg, &fg->fonts[BOX_FONT], glyph, &extra_glyphs, false, &error); if (sp == NULL) { sprite_map_set_error(error); PyErr_Print(); set_sprite(cell, 0, 0, 0); @@ -502,13 +597,13 @@ render_box_cell(Cell *cell) { if (sp->rendered) return; sp->rendered = true; sp->colored = false; - PyObject *ret = PyObject_CallFunction(box_drawing_function, "I", cell->ch); + PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0); if (ret == NULL) { PyErr_Print(); return; } uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); - clear_canvas(); - Region r = { .right = cell_width, .bottom = cell_height }; - render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width); - current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas); + clear_canvas(fg); + Region r = { .right = fg->cell_width, .bottom = fg->cell_height }; + render_alpha_mask(alpha_mask, fg->canvas, &r, &r, fg->cell_width, fg->cell_width); + current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp->x, sp->y, sp->z, fg->canvas); Py_DECREF(ret); } @@ -539,20 +634,20 @@ set_cell_sprite(Cell *cell, SpritePosition *sp) { } static inline pixel* -extract_cell_from_canvas(unsigned int i, unsigned int num_cells) { - pixel *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width); - unsigned int stride = cell_width * num_cells; - for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width * sizeof(pixel)); +extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) { + pixel *ans = fg->canvas + (fg->cell_width * fg->cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = fg->canvas + (i * fg->cell_width); + unsigned int stride = fg->cell_width * num_cells; + for (unsigned int r = 0; r < fg->cell_height; r++, dest += fg->cell_width, src += stride) memcpy(dest, src, fg->cell_width * sizeof(pixel)); return ans; } static inline void -render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) { +render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) { static SpritePosition* sprite_position[16]; int error = 0; num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells); for (unsigned int i = 0; i < num_cells; i++) { - sprite_position[i] = sprite_position_for(font, glyph, extra_glyphs, (uint8_t)i, &error); + sprite_position[i] = sprite_position_for(fg, font, glyph, extra_glyphs, (uint8_t)i, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; } } if (sprite_position[0]->rendered) { @@ -560,17 +655,17 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl return; } - clear_canvas(); + clear_canvas(fg); bool was_colored = (cells->attrs & WIDTH_MASK) == 2 && is_emoji(cells->ch); - render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored); + render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg); if (PyErr_Occurred()) PyErr_Print(); for (unsigned int i = 0; i < num_cells; i++) { sprite_position[i]->rendered = true; sprite_position[i]->colored = was_colored; set_cell_sprite(cells + i, sprite_position[i]); - pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells); - current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); + pixel *buf = num_cells == 1 ? fg->canvas : extract_cell_from_canvas(fg, i, num_cells); + current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); } } @@ -813,7 +908,7 @@ merge_groups_for_pua_space_ligature() { } static inline void -render_groups(Font *font) { +render_groups(FontGroup *fg, Font *font) { unsigned idx = 0; ExtraGlyphs ed; while (idx <= G(group_idx)) { @@ -826,7 +921,7 @@ render_groups(Font *font) { int last = -1; for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; } if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0; - render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed); + render_group(fg, group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed); idx++; } } @@ -840,12 +935,16 @@ test_shape(PyObject UNUSED *self, PyObject *args) { index_type num = 0; while(num < line->xnum && line->cells[num].ch) num += line->cells[num].attrs & WIDTH_MASK; PyObject *face = NULL; - Font *font = fonts.fonts + fonts.medium_font_idx; + Font *font; + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } if (path) { - face = face_from_path(path, index); + face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups); if (face == NULL) return NULL; font = calloc(1, sizeof(Font)); font->face = face; + } else { + FontGroup *fg = font_groups; + font = fg->fonts + fg->medium_font_idx; } shape_run(line->cells, num, font); @@ -868,18 +967,18 @@ test_shape(PyObject UNUSED *self, PyObject *args) { #undef G static inline void -render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) { +render_run(FontGroup *fg, Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) { switch(font_idx) { default: - shape_run(first_cell, num_cells, &fonts.fonts[font_idx]); + shape_run(first_cell, num_cells, &fg->fonts[font_idx]); if (pua_space_ligature) merge_groups_for_pua_space_ligature(); - render_groups(&fonts.fonts[font_idx]); + render_groups(fg, &fg->fonts[font_idx]); break; case BLANK_FONT: while(num_cells--) set_sprite(first_cell++, 0, 0, 0); break; case BOX_FONT: - while(num_cells--) render_box_cell(first_cell++); + while(num_cells--) render_box_cell(fg, first_cell++); break; case MISSING_FONT: while(num_cells--) set_sprite(first_cell++, MISSING_GLYPH, 0, 0); @@ -893,15 +992,16 @@ is_private_use(char_type ch) { } void -render_line(Line *line) { -#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false); +render_line(FONTS_DATA_HANDLE fg_, Line *line) { +#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false); + FontGroup *fg = (FontGroup*)fg_; ssize_t run_font_idx = NO_FONT; index_type first_cell_in_run, i; attrs_type prev_width = 0; for (i=0, first_cell_in_run=0; i < line->xnum; i++) { if (prev_width == 2) { prev_width = 0; continue; } Cell *cell = line->cells + i; - ssize_t cell_font_idx = font_for_cell(cell); + ssize_t cell_font_idx = font_for_cell(fg, cell); if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) { // We have a private use char followed by a space char, render it as a two cell ligature. Cell *space_cell = line->cells + i+1; @@ -911,7 +1011,7 @@ render_line(Line *line) { // for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467 space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg; RENDER; - render_run(line->cells + i, 2, cell_font_idx, true); + render_run(fg, line->cells + i, 2, cell_font_idx, true); run_font_idx = NO_FONT; first_cell_in_run = i + 2; prev_width = line->cells[i+1].attrs & WIDTH_MASK; @@ -929,65 +1029,142 @@ render_line(Line *line) { #undef RENDER } -static PyObject* -set_font(PyObject UNUSED *m, PyObject *args) { - PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL; - Py_CLEAR(box_drawing_function); - if (!PyArg_ParseTuple(args, "OO!O!fO|OOO", &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL; - Py_INCREF(box_drawing_function); - fonts.symbol_map_fonts_count = PyTuple_GET_SIZE(smf); - size_t num_fonts = 5 + fonts.symbol_map_fonts_count; - for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i); - ensure_space_for(&fonts, fonts, Font, num_fonts, fonts_capacity, 5, true); - fonts.fonts_count = 1; -#define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; } - A(medium, false, false); - A(bold, true, false); A(italic, false, true); A(bi, true, true); -#undef A +static inline void +clear_descriptors() { + if (descriptors) { + Descriptor *d = descriptors; + while(d) { + Py_CLEAR(d->face); + d++; + } + free(descriptors); descriptors = NULL; + } + free(symbol_maps); symbol_maps = NULL; +} - fonts.first_symbol_font_idx = fonts.fonts_count; - fonts.symbol_maps_count = PyTuple_GET_SIZE(sm); - ensure_space_for(&fonts, symbol_maps, SymbolMap, fonts.symbol_maps_count, symbol_maps_capacity, 5, true); - for (size_t i = 0; i < fonts.symbol_map_fonts_count; i++) { +static PyObject* +set_font_data(PyObject UNUSED *m, PyObject *args) { + PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL; + free_font_groups(); + Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); + clear_descriptors(); + if (!PyArg_ParseTuple(args, "OOO!O!fO|OOO", &box_drawing_function, &prerender_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL; + Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); + descriptors = calloc(5 + PyTuple_GET_SIZE(smf), sizeof(Descriptor)); + if (descriptors == NULL) return PyErr_NoMemory(); + symbol_maps = calloc(1 + PyTuple_GET_SIZE(sm), sizeof(SymbolMap)); + if (symbol_maps == NULL) return PyErr_NoMemory(); + size_t desc_idx = 0; +#define AF(name) { \ + if (name) { Py_INCREF(name); descriptors[desc_idx].face = name; descriptors[desc_idx].bold = desc_idx & 1; descriptors[desc_idx].italic = desc_idx & 2; } \ + desc_idx++; \ +} + AF(medium); AF(bold); AF(italic); AF(bi); +#undef AF + num_symbol_fonts = 0; + for (Py_ssize_t s = 0; s < PyTuple_GET_SIZE(smf); s++, desc_idx++) { + Descriptor *d = descriptors + desc_idx; PyObject *face; int bold, italic; - if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL; - if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false, false)) return NULL; + if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, s), "Opp", &face, &bold, &italic)) return NULL; + Py_INCREF(face); + d->face = face; d->bold = bold != 0; d->italic = italic != 0; + num_symbol_fonts++; } - for (size_t i = 0; i < fonts.symbol_maps_count; i++) { + for (Py_ssize_t s = 0; s < PyTuple_GET_SIZE(sm); s++) { unsigned int left, right, font_idx; - if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, i), "III", &left, &right, &font_idx)) return NULL; - fonts.symbol_maps[i].left = left; fonts.symbol_maps[i].right = right; fonts.symbol_maps[i].font_idx = font_idx; + SymbolMap *x = symbol_maps + s; + if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, s), "III", &left, &right, &font_idx)) return NULL; + x->left = left; x->right = right; x->font_idx = font_idx; } - fonts.first_fallback_font_idx = fonts.fonts_count; - fonts.fallback_fonts_count = 0; - return update_cell_metrics(false); + Py_RETURN_NONE; +} + +static inline void +send_prerendered_sprites(FontGroup *fg) { + int error = 0; + sprite_index x = 0, y = 0, z = 0; + // blank cell + clear_canvas(fg); + current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas); + do_increment(fg, &error); + if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); } + PyObject *args = PyObject_CallFunction(prerender_function, "IIIII", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness); + if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); } + for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) { + x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z; + do_increment(fg, &error); + if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); } + uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i)); + clear_canvas(fg); + Region r = { .right = fg->cell_width, .bottom = fg->cell_height }; + render_alpha_mask(alpha_mask, fg->canvas, &r, &r, fg->cell_width, fg->cell_width); + current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas); + } + Py_CLEAR(args); +} + + +static void +initialize_font_group(FontGroup *fg) { + if (!descriptors) fatal("Must call set_font_data() before initializing a font group"); + fg->fonts_capacity = 10 + num_symbol_fonts; + fg->fonts = calloc(fg->fonts_capacity, sizeof(Font)); + if (fg->fonts == NULL) fatal("Out of memory allocating fonts array"); + fg->fonts_count = 1; // the 0 index font is the box font +#define I(ftype) { \ + size_t idx = fg->fonts_count; \ + if (!init_font(fg->fonts + fg->fonts_count++, d->face, d->bold, d->italic, false, false, (FONTS_DATA_HANDLE)fg)) { \ + if (PyErr_Occurred()) PyErr_Print(); \ + fatal("Failed to initialize %s font: %d", #ftype, idx); \ + }} +#define IF(idx, attr) { \ + Descriptor *d = descriptors + idx; \ + if (d->face) { \ + fg->attr##_font_idx = fg->fonts_count; I(basic); \ + } else fg->attr##_font_idx = -1; \ + IF(0, medium); IF(1, bold); IF(2, italic); IF(3, bi); \ +} +#undef IF + fg->first_symbol_font_idx = fg->fonts_count; fg->first_fallback_font_idx = fg->fonts_count; + fg->fallback_fonts_count = 0; + for (size_t i = 0; i < num_symbol_fonts; i++) { + Descriptor *d = descriptors + i + 4; + I(symbol_mapped); + fg->first_fallback_font_idx++; + } +#undef I + calc_cell_metrics(fg); + fg->sprite_map = alloc_sprite_map(fg->cell_width, fg->cell_height); + if (!fg->sprite_map) fatal("Out of memory allocating a sprite map"); + send_prerendered_sprites(fg); +} + +void +load_fonts_for_window(OSWindow *w) { + w->fonts_data = NULL; + FontGroup *fg = font_group_for(w->font_sz_in_pts, w->logical_dpi_x, w->logical_dpi_y); + if (!fg->cell_width) initialize_font_group(fg); + w->fonts_data = (FONTS_DATA_HANDLE)fg; } static void finalize(void) { Py_CLEAR(python_send_to_gpu_impl); - free(canvas); + clear_descriptors(); Py_CLEAR(box_drawing_function); - for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i); - free(fonts.symbol_maps); free(fonts.fonts); + Py_CLEAR(prerender_function); + free_font_groups(); if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer); free(group_state.groups); } -static PyObject* -sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { - unsigned int w, h; - if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL; - sprite_tracker_set_limits(w, h); - Py_RETURN_NONE; -} - static PyObject* sprite_map_set_layout(PyObject UNUSED *self, PyObject *args) { unsigned int w, h; if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL; - sprite_tracker_set_layout(w, h); + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } + sprite_tracker_set_layout(&font_groups->sprite_tracker, w, h); Py_RETURN_NONE; } @@ -997,33 +1174,13 @@ test_sprite_position_for(PyObject UNUSED *self, PyObject *args) { ExtraGlyphs extra_glyphs = {{0}}; if (!PyArg_ParseTuple(args, "H|H", &glyph, &extra_glyphs.data)) return NULL; int error; - SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error); + FontGroup *fg = font_groups; + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } + SpritePosition *pos = sprite_position_for(fg, &fg->fonts[fg->medium_font_idx], glyph, &extra_glyphs, 0, &error); if (pos == NULL) { sprite_map_set_error(error); return NULL; } return Py_BuildValue("HHH", pos->x, pos->y, pos->z); } -static PyObject* -send_prerendered_sprites(PyObject UNUSED *s, PyObject *args) { - int error = 0; - sprite_index x = 0, y = 0, z = 0; - // blank cell - clear_canvas(); - current_send_sprite_to_gpu(x, y, z, canvas); - do_increment(&error); - if (error != 0) { sprite_map_set_error(error); return NULL; } - for (ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { - x = sprite_tracker.x; y = sprite_tracker.y; z = sprite_tracker.z; - do_increment(&error); - if (error != 0) { sprite_map_set_error(error); return NULL; } - uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i)); - clear_canvas(); - Region r = { .right = cell_width, .bottom = cell_height }; - render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width); - current_send_sprite_to_gpu(x, y, z, canvas); - } - return Py_BuildValue("H", x); -} - static PyObject* set_send_sprite_to_gpu(PyObject UNUSED *self, PyObject *func) { Py_CLEAR(python_send_to_gpu_impl); @@ -1037,7 +1194,8 @@ static PyObject* test_render_line(PyObject UNUSED *self, PyObject *args) { PyObject *line; if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL; - render_line((Line*)line); + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } + render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line); Py_RETURN_NONE; } @@ -1080,18 +1238,20 @@ concat_cells(PyObject UNUSED *self, PyObject *args) { static PyObject* current_fonts(PYNOARG) { + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } PyObject *ans = PyDict_New(); if (!ans) return NULL; -#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fonts.fonts[val].face) != 0) { goto error; }} - SET(medium, fonts.medium_font_idx); - if (fonts.bold_font_idx) SET(bold, fonts.bold_font_idx); - if (fonts.italic_font_idx) SET(italic, fonts.italic_font_idx); - if (fonts.bi_font_idx) SET(bi, fonts.bi_font_idx); - PyObject *ff = PyTuple_New(fonts.fallback_fonts_count); + FontGroup *fg = font_groups; +#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fg->fonts[val].face) != 0) { goto error; }} + SET(medium, fg->medium_font_idx); + if (fg->bold_font_idx) SET(bold, fg->bold_font_idx); + if (fg->italic_font_idx) SET(italic, fg->italic_font_idx); + if (fg->bi_font_idx) SET(bi, fg->bi_font_idx); + PyObject *ff = PyTuple_New(fg->fallback_fonts_count); if (!ff) goto error; - for (size_t i = 0; i < fonts.fallback_fonts_count; i++) { - Py_INCREF(fonts.fonts[fonts.first_fallback_font_idx + i].face); - PyTuple_SET_ITEM(ff, i, fonts.fonts[fonts.first_fallback_font_idx + i].face); + for (size_t i = 0; i < fg->fallback_fonts_count; i++) { + Py_INCREF(fg->fonts[fg->first_fallback_font_idx + i].face); + PyTuple_SET_ITEM(ff, i, fg->fonts[fg->first_fallback_font_idx + i].face); } PyDict_SetItemString(ans, "fallback", ff); Py_CLEAR(ff); @@ -1103,6 +1263,7 @@ error: static PyObject* get_fallback_font(PyObject UNUSED *self, PyObject *args) { + if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } PyObject *text; int bold, italic; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; @@ -1113,18 +1274,25 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) { for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]); if (bold) cell.attrs |= 1 << BOLD_SHIFT; if (italic) cell.attrs |= 1 << ITALIC_SHIFT; - ssize_t ans = fallback_font(&cell); + FontGroup *fg = font_groups; + ssize_t ans = fallback_font(fg, &cell); if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } - return fonts.fonts[ans].face; + return fg->fonts[ans].face; +} + +static PyObject* +create_test_font_group(PyObject *self UNUSED, PyObject *args) { + double sz, dpix, dpiy; + if (!PyArg_ParseTuple(args, "ddd", &sz, &dpix, &dpiy)) return NULL; + FontGroup *fg = font_group_for(sz, dpix, dpiy); + return Py_BuildValue("II", fg->cell_width, fg->cell_height); } static PyMethodDef module_methods[] = { - METHODB(set_font_size, METH_VARARGS), - METHODB(set_font, METH_VARARGS), - METHODB(sprite_map_set_limits, METH_VARARGS), + METHODB(set_font_data, METH_VARARGS), + METHODB(create_test_font_group, METH_VARARGS), METHODB(sprite_map_set_layout, METH_VARARGS), - METHODB(send_prerendered_sprites, METH_VARARGS), METHODB(test_sprite_position_for, METH_VARARGS), METHODB(concat_cells, METH_VARARGS), METHODB(set_send_sprite_to_gpu, METH_O), @@ -1146,6 +1314,5 @@ init_fonts(PyObject *module) { hb_buffer_set_cluster_level(harfbuzz_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); if (PyModule_AddFunctions(module, module_methods) != 0) return false; current_send_sprite_to_gpu = send_sprite_to_gpu; - sprite_tracker_set_limits(2000, 2000); return true; } diff --git a/kitty/fonts.h b/kitty/fonts.h index b761da465..b6f04dceb 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -7,6 +7,7 @@ #pragma once #include "lineops.h" +#include "state.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include @@ -18,22 +19,20 @@ typedef uint16_t glyph_index; unsigned int glyph_id_for_codepoint(PyObject *, char_type); bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); -bool set_size_for_face(PyObject*, unsigned int, bool); +bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); -bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored); -PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation); +bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE); +PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor); -PyObject* face_from_path(const char *path, int index); -PyObject* face_from_descriptor(PyObject*); +PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE); +PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE); -void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z); +void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z); void render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride); -void render_line(Line *line); +void render_line(FONTS_DATA_HANDLE, Line *line); +void load_fonts_for_window(OSWindow*); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); -void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height); typedef void (*free_extra_data_func)(void*); -PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float); -PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float); static inline void right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 1d3bebfd4..5766d4566 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -6,9 +6,8 @@ import math from functools import partial as p from itertools import repeat -from kitty.fast_data_types import pt_to_px_ceil - scale = (0.001, 1, 1.5, 2) +_dpi = 96.0 def set_scale(new_scale): @@ -18,7 +17,7 @@ def set_scale(new_scale): def thickness(level=1, horizontal=True): pts = scale[level] - return pt_to_px_ceil(pts) + return int(math.ceil(pts * (_dpi / 72.0))) def draw_hline(buf, width, x1, x2, y, level): @@ -534,7 +533,9 @@ for chars, func in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), (' box_chars[ch] = [p(func, which=ch)] -def render_box_char(ch, buf, width, height): +def render_box_char(ch, buf, width, height, dpi=96.0): + global _dpi + _dpi = dpi for func in box_chars[ch]: func(buf, width, height) return buf diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 513077a07..442cee4e1 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -4,16 +4,15 @@ import ctypes import sys -from collections import namedtuple +from functools import partial from math import ceil, floor, pi, sin, sqrt from kitty.config import defaults from kitty.constants import is_macos from kitty.fast_data_types import ( - Screen, get_fallback_font, send_prerendered_sprites, - set_font, set_font_size, set_logical_dpi, set_options, - set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line, - test_shape + Screen, create_test_font_group, get_fallback_font, set_font_data, + set_options, set_send_sprite_to_gpu, sprite_map_set_limits, + test_render_line, test_shape ) from kitty.fonts.box_drawing import render_box_char, render_missing_glyph @@ -36,12 +35,6 @@ def create_symbol_map(opts): return sm, tuple(faces) -FontState = namedtuple( - 'FontState', - 'family pt_sz cell_width cell_height baseline underline_position underline_thickness' -) - - def set_font_family(opts=None, override_font_size=None): opts = opts or defaults sz = override_font_size or opts.font_size @@ -51,24 +44,9 @@ def set_font_family(opts=None, override_font_size=None): if k in font_map: faces.append(font_map[k]) sm, sfonts = create_symbol_map(opts) - cell_width, cell_height, baseline, underline_position, underline_thickness = set_font( - render_box_drawing, sm, sfonts, sz, *faces + set_font_data( + render_box_drawing, prerender_function, sm, sfonts, sz, *faces ) - set_font_family.state = FontState( - opts.font_family, sz, cell_width, cell_height, baseline, - underline_position, underline_thickness - ) - return cell_width, cell_height - - -def resize_fonts(new_sz, on_dpi_change=False): - s = set_font_family.state - cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz, on_dpi_change) - set_font_family.state = FontState( - s.family, new_sz, cell_width, cell_height, baseline, - underline_position, underline_thickness - ) - return cell_width, cell_height def add_line(buf, cell_width, position, thickness, cell_height): @@ -125,10 +103,9 @@ def add_curl(buf, cell_width, position, thickness, cell_height): add_intensity(x, y, dist) -def render_special(underline=0, strikethrough=False, missing=False): - s = set_font_family.state - cell_width, cell_height, baseline = s.cell_width, s.cell_height, s.baseline - underline_position, underline_thickness = s.underline_position, s.underline_thickness +def render_special( + underline=0, strikethrough=False, missing=False, + cell_width=None, cell_height=None, baseline=None, underline_position=None, underline_thickness=None): underline_position = min(underline_position, cell_height - underline_thickness) CharTexture = ctypes.c_ubyte * (cell_width * cell_height) ans = CharTexture if missing else CharTexture() @@ -152,36 +129,36 @@ def render_special(underline=0, strikethrough=False, missing=False): return ans -def prerender(): - # Pre-render the special blank, underline and strikethrough cells - cells = render_special(1), render_special(2), render_special(3), render_special(0, True), render_special(missing=True) - if send_prerendered_sprites(*map(ctypes.addressof, cells)) != len(cells): - raise RuntimeError('Your GPU has too small a max texture size') +def prerender_function(cell_width, cell_height, baseline, underline_position, underline_thickness): + # Pre-render the special underline, strikethrough and missing cells + f = partial( + render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline, + underline_position=underline_position, underline_thickness=underline_thickness) + cells = f(1), f(2), f(3), f(0, True), f(missing=True) + return tuple(map(ctypes.addressof, cells)) + (cells,) -def render_box_drawing(codepoint): - s = set_font_family.state - cell_width, cell_height = s.cell_width, s.cell_height +def render_box_drawing(codepoint, cell_width, cell_height, dpi): CharTexture = ctypes.c_ubyte * (cell_width * cell_height) buf = render_box_char( - chr(codepoint), CharTexture(), cell_width, cell_height + chr(codepoint), CharTexture(), cell_width, dpi ) return ctypes.addressof(buf), buf -def setup_for_testing(family='monospace', size=11.0, dpi=96.0): - opts = defaults._replace(font_family=family) +def setup_for_testing(family='monospace', size=11.0, dpi=96.0, send_to_gpu=None): + from collections import OrderedDict + opts = defaults._replace(font_family=family, font_size=size) set_options(opts) - sprites = {} + sprites = OrderedDict() - def send_to_gpu(x, y, z, data): + def _send_to_gpu(x, y, z, data): sprites[(x, y, z)] = data sprite_map_set_limits(100000, 100) - set_send_sprite_to_gpu(send_to_gpu) - set_logical_dpi(dpi, dpi) - cell_width, cell_height = set_font_family(opts, override_font_size=size) - prerender() + set_send_sprite_to_gpu(send_to_gpu or _send_to_gpu) + set_font_family(opts) + cell_width, cell_height = create_test_font_group(size, dpi, dpi) return sprites, cell_width, cell_height @@ -248,8 +225,7 @@ def test_render_string(text='Hello, world!', family='monospace', size=64.0, dpi= def test_fallback_font(qtext=None, bold=False, italic=False): - set_logical_dpi(96.0, 96.0) - set_font_family() + setup_for_testing() trials = (qtext,) if qtext else ('你', 'He\u0347\u0305', '\U0001F929') for text in trials: f = get_fallback_font(text, bold, italic) diff --git a/kitty/freetype.c b/kitty/freetype.c index 4c9212a5f..587bfbfa6 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -78,13 +78,13 @@ font_units_to_pixels(Face *self, int x) { } static inline bool -set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt xdpi, FT_UInt ydpi, unsigned int desired_height) { +set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt xdpi, FT_UInt ydpi, unsigned int desired_height, unsigned int cell_height) { int error = FT_Set_Char_Size(self->face, 0, char_height, xdpi, ydpi); if (!error) { unsigned int ch = CALC_CELL_HEIGHT(self); if (desired_height && ch != desired_height) { FT_F26Dot6 h = floor((double)char_height * (double)desired_height / (double) ch); - return set_font_size(self, 0, h, xdpi, ydpi, 0); + return set_font_size(self, 0, h, xdpi, ydpi, 0, cell_height); } self->char_width = char_width; self->char_height = char_height; self->xdpi = xdpi; self->ydpi = ydpi; if (self->harfbuzz_font != NULL) { @@ -101,7 +101,7 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt } else { if (!self->is_scalable && self->face->num_fixed_sizes > 0) { int32_t min_diff = INT32_MAX; - if (desired_height == 0) desired_height = global_state.cell_height; + if (desired_height == 0) desired_height = cell_height; if (desired_height == 0) { desired_height = ceil(((double)char_height / 64.) * (double)ydpi / 72.); desired_height += ceil(0.2 * desired_height); @@ -128,13 +128,13 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt } bool -set_size_for_face(PyObject *s, unsigned int desired_height, bool force) { +set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DATA_HANDLE fg) { Face *self = (Face*)s; FT_F26Dot6 w = (FT_F26Dot6)(ceil(global_state.font_sz_in_pts * 64.0)); - FT_UInt xdpi = (FT_UInt)global_state.logical_dpi_x, ydpi = (FT_UInt)global_state.logical_dpi_y; + FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y; if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true; ((Face*)self)->size_in_pts = global_state.font_sz_in_pts; - return set_font_size(self, w, w, xdpi, ydpi, desired_height); + return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height); } static inline int @@ -149,14 +149,14 @@ get_load_flags(int hinting, int hintstyle, int base) { static inline bool -init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) { +init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) { #define CPY(n) self->n = self->face->n; CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); #undef CPY self->is_scalable = FT_IS_SCALABLE(self->face); self->has_color = FT_HAS_COLOR(self->face); self->hinting = hinting; self->hintstyle = hintstyle; - if (!set_size_for_face((PyObject*)self, 0, false)) return false; + if (!set_size_for_face((PyObject*)self, 0, false, fg)) return false; self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); @@ -168,52 +168,7 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) { } PyObject* -ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) { - Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); - if (ans == NULL) return NULL; - int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face); - if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; } - if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; } - ans->extra_data = extra_data; - ans->free_extra_data = fed; - ans->apple_leading = apple_leading; - return (PyObject*)ans; -} - -static inline bool -load_from_path_and_psname(const char *path, const char* psname, Face *ans) { - int error, num_faces, index = 0; - error = FT_New_Face(library, path, index, &ans->face); - if (error) { set_freetype_error("Failed to load face, with error:", error); ans->face = NULL; return false; } - num_faces = ans->face->num_faces; - if (num_faces < 2) return true; - do { - if (ans->face) { - if (!psname || strcmp(FT_Get_Postscript_Name(ans->face), psname) == 0) return true; - FT_Done_Face(ans->face); ans->face = NULL; - } - error = FT_New_Face(library, path, ++index, &ans->face); - if (error) ans->face = NULL; - } while(index < num_faces); - PyErr_Format(PyExc_ValueError, "No face matching the postscript name: %s found in: %s", psname, path); - return false; -} - -PyObject* -ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float apple_leading) { - if (PyUnicode_READY(path) != 0) return NULL; - Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); - if (!ans) return NULL; - if (!load_from_path_and_psname(PyUnicode_AsUTF8(path), psname, ans)) { Py_CLEAR(ans); return NULL; } - if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; } - ans->extra_data = extra_data; - ans->free_extra_data = fed; - ans->apple_leading = apple_leading; - return (PyObject*)ans; -} - -PyObject* -face_from_descriptor(PyObject *descriptor) { +face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) { #define D(key, conv, missing_ok) { \ PyObject *t = PyDict_GetItemString(descriptor, #key); \ if (t == NULL) { \ @@ -233,19 +188,19 @@ face_from_descriptor(PyObject *descriptor) { if (self != NULL) { int error = FT_New_Face(library, path, index, &(self->face)); if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; } - if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style)) { Py_CLEAR(self); return NULL; } + if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, fg)) { Py_CLEAR(self); return NULL; } } return (PyObject*)self; } PyObject* -face_from_path(const char *path, int index) { +face_from_path(const char *path, int index, FONTS_DATA_HANDLE fg) { Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); if (ans == NULL) return NULL; int error; error = FT_New_Face(library, path, index, &ans->face); if (error) { set_freetype_error("Failed to load face, with error:", error); ans->face = NULL; return NULL; } - if (!init_ft_face(ans, Py_None, true, 3)) { Py_CLEAR(ans); return NULL; } + if (!init_ft_face(ans, Py_None, true, 3, fg)) { Py_CLEAR(ans); return NULL; } return (PyObject*)ans; } @@ -349,7 +304,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) { static inline bool -render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale) { +render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) { if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; unsigned int max_width = cell_width * num_cells; FT_Bitmap *bitmap = &self->face->glyph->bitmap; @@ -369,9 +324,9 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_ } else if (rescale && self->is_scalable && extra > 1) { FT_F26Dot6 char_width = self->char_width, char_height = self->char_height; float ar = (float)max_width / (float)bitmap->width; - if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0)) { - if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false)) return false; - if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0)) return false; + if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0, fg->cell_height)) { + if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false, fg)) return false; + if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0, fg->cell_height)) return false; } else return false; } } @@ -506,7 +461,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size static const ProcessedBitmap EMPTY_PBM = {.factor = 1}; bool -render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) { +render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) { Face *self = (Face*)f; bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color; float x = 0.f, y = 0.f, x_offset = 0.f; @@ -518,10 +473,10 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) { if (PyErr_Occurred()) PyErr_Print(); *was_colored = false; - if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true)) return false; + if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) return false; } } else { - if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true)) return false; + if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) return false; } x_offset = x + (float)positions[i].x_offset / 64.0f; y = (float)positions[i].y_offset / 64.0f; diff --git a/kitty/glfw.c b/kitty/glfw.c index b41f588ac..a0dcfd544 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -5,6 +5,7 @@ */ #include "state.h" +#include "fonts.h" #include #include "glfw-wrapper.h" extern bool cocoa_make_window_resizable(void *w); @@ -293,9 +294,9 @@ current_monitor(GLFWwindow *window) { void -set_dpi_from_os_window(OSWindow *w) { +set_os_window_dpi(OSWindow *w) { GLFWmonitor *monitor = NULL; - if (w) { monitor = current_monitor(w->handle); } + if (w && w->handle) { monitor = current_monitor(w->handle); } if (monitor == NULL) monitor = glfwGetPrimaryMonitor(); float xscale = 1, yscale = 1; if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale); @@ -304,8 +305,8 @@ set_dpi_from_os_window(OSWindow *w) { #else double factor = 96.0; #endif - global_state.logical_dpi_x = xscale * factor; - global_state.logical_dpi_y = yscale * factor; + w->logical_dpi_x = xscale * factor; + w->logical_dpi_y = yscale * factor; } static bool is_first_window = true; @@ -378,7 +379,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { current_os_window_ctx = glfw_window; glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0); // a value of 1 makes mouse selection laggy if (is_first_window) { - set_dpi_from_os_window(NULL); gl_init(); PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER)); if (ret == NULL) return NULL; @@ -402,6 +402,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { OSWindow *q = global_state.os_windows + i; q->is_focused = q == w ? true : false; } + set_os_window_dpi(w); + load_fonts_for_window(w); if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo); glfwSetCursor(glfw_window, standard_cursor); update_os_window_viewport(w, false); @@ -449,9 +451,10 @@ show_window(PyObject UNUSED *self, PyObject *args) { w->shown_once = true; push_focus_history(w); if (first_show) { - double before_x = global_state.logical_dpi_x, before_y = global_state.logical_dpi_y; - set_dpi_from_os_window(w); - dpi_changed = before_x != global_state.logical_dpi_x || before_y != global_state.logical_dpi_y; + double before_x = w->logical_dpi_x, before_y = w->logical_dpi_y; + set_os_window_dpi(w); + dpi_changed = before_x != w->logical_dpi_x || before_y != w->logical_dpi_y; + if (dpi_changed) load_fonts_for_window(w); w->has_pending_resizes = true; global_state.has_pending_resizes = true; } @@ -508,6 +511,12 @@ glfw_init(PyObject UNUSED *self, PyObject *args) { glfwInitHint(GLFW_COCOA_MENUBAR, 0); #endif PyObject *ans = glfwInit() ? Py_True: Py_False; + if (ans == Py_True) { + OSWindow w = {0}; + set_os_window_dpi(&w); + global_state.default_dpi.x = w.logical_dpi_x; + global_state.default_dpi.y = w.logical_dpi_y; + } Py_INCREF(ans); return ans; } @@ -767,16 +776,6 @@ os_window_swap_buffers(PyObject UNUSED *self, PyObject *args) { return NULL; } -static PyObject* -ring_bell(PyObject UNUSED *self, PyObject *args) { - id_type os_window_id; - if (!PyArg_ParseTuple(args, "K", &os_window_id)) return NULL; - OSWindow *w = os_window_for_kitty_window(os_window_id); - if (w && w->handle) { - glfwWindowBell(w->handle); - } - Py_RETURN_NONE; -} // Boilerplate {{{ static PyMethodDef module_methods[] = { @@ -790,7 +789,6 @@ static PyMethodDef module_methods[] = { METHODB(glfw_window_hint, METH_VARARGS), METHODB(os_window_should_close, METH_VARARGS), METHODB(os_window_swap_buffers, METH_VARARGS), - METHODB(ring_bell, METH_VARARGS), METHODB(get_primary_selection, METH_NOARGS), METHODB(x11_display, METH_NOARGS), METHODB(x11_window_id, METH_O), diff --git a/kitty/graphics.c b/kitty/graphics.c index a3b1d16b9..322654d18 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -535,17 +535,17 @@ update_src_rect(ImageRef *ref, Image *img) { } static inline void -update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows) { +update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) { uint32_t t; if (num_cols == 0) { t = ref->src_width + ref->cell_x_offset; - num_cols = t / global_state.cell_width; - if (t > num_cols * global_state.cell_width) num_cols += 1; + num_cols = t / cell.width; + if (t > num_cols * cell.width) num_cols += 1; } if (num_rows == 0) { t = ref->src_height + ref->cell_y_offset; - num_rows = t / global_state.cell_height; - if (t > num_rows * global_state.cell_height) num_rows += 1; + num_rows = t / cell.height; + if (t > num_rows * cell.height) num_rows += 1; } ref->effective_num_rows = num_rows; ref->effective_num_cols = num_cols; @@ -553,7 +553,7 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows) { static void -handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) { +handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) { has_add_respose = false; if (img == NULL) img = img_by_client_id(self, g->id); if (img == NULL) { set_add_response("ENOENT", "Put command refers to non-existent image with id: %u", g->id); return; } @@ -575,11 +575,11 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height)); ref->z_index = g->z_index; ref->start_row = c->y; ref->start_column = c->x; - ref->cell_x_offset = MIN(g->cell_x_offset, global_state.cell_width - 1); - ref->cell_y_offset = MIN(g->cell_y_offset, global_state.cell_height - 1); + ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1); + ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1); ref->num_cols = g->num_cells; ref->num_rows = g->num_lines; update_src_rect(ref, img); - update_dest_rect(ref, g->num_cells, g->num_lines); + update_dest_rect(ref, g->num_cells, g->num_lines, cell); // Move the cursor, the screen will take care of ensuring it is in bounds c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1; } @@ -593,7 +593,7 @@ cmp_by_zindex_and_image(const void *a_, const void *b_) { } bool -grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows) { +grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) { if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true; self->last_scrolled_by = scrolled_by; if (!self->layers_dirty) return false; @@ -604,19 +604,19 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree ImageRect r; float screen_width = dx * num_cols, screen_height = dy * num_rows; float screen_bottom = screen_top - screen_height; - float screen_width_px = num_cols * global_state.cell_width; - float screen_height_px = num_rows * global_state.cell_height; + float screen_width_px = num_cols * cell.width; + float screen_height_px = num_rows * cell.height; float y0 = screen_top - dy * scrolled_by; // Iterate over all visible refs and create render data self->count = 0; for (i = 0; i < self->image_count; i++) { img = self->images + i; for (j = 0; j < img->refcnt; j++) { ref = img->refs + j; - r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)global_state.cell_height; + r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height; if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy; else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px; if (r.top <= screen_bottom || r.bottom >= screen_top) continue; // not visible - r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) global_state.cell_width; + r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width; if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx; else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px; @@ -651,7 +651,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree // Image lifetime/scrolling {{{ static inline void -filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*)) { +filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) { Image *img; ImageRef *ref; size_t i, j; @@ -660,7 +660,7 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi img = self->images + i; for (j = img->refcnt; j-- > 0;) { ref = img->refs + j; - if (filter_func(ref, img, data)) { + if (filter_func(ref, img, data, cell)) { remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--); } } @@ -671,7 +671,7 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi } static inline bool -scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { +scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { ScrollData *d = (ScrollData*)data; ref->start_row += d->amt; return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit; @@ -688,7 +688,7 @@ ref_outside_region(ImageRef *ref, index_type margin_top, index_type margin_botto } static inline bool -scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { +scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) { ScrollData *d = (ScrollData*)data; if (ref_within_region(ref, d->margin_top, d->margin_bottom)) { ref->start_row += d->amt; @@ -698,7 +698,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { if (ref->start_row < (int32_t)d->margin_top) { // image moved up clipped_rows = d->margin_top - ref->start_row; - clip_amt = global_state.cell_height * clipped_rows; + clip_amt = cell.height * clipped_rows; if (ref->src_height <= clip_amt) return true; ref->src_y += clip_amt; ref->src_height -= clip_amt; ref->effective_num_rows -= clipped_rows; @@ -707,7 +707,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { } else if (ref->start_row + ref->effective_num_rows > d->margin_bottom) { // image moved down clipped_rows = ref->start_row + ref->effective_num_rows - d->margin_bottom; - clip_amt = global_state.cell_height * clipped_rows; + clip_amt = cell.height * clipped_rows; if (ref->src_height <= clip_amt) return true; ref->src_height -= clip_amt; ref->effective_num_rows -= clipped_rows; @@ -719,66 +719,66 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { } void -grman_scroll_images(GraphicsManager *self, const ScrollData *data) { - filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func); +grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) { + filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell); } static inline bool -clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) { +clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { return ref->start_row + (int32_t)ref->effective_num_rows > 0; } static inline bool -clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data) { +clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { return true; } void -grman_clear(GraphicsManager *self, bool all) { - filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func); +grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) { + filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell); } static inline bool -id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data) { +id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data, CellPixelSize cell UNUSED) { uint32_t iid = *(uint32_t*)data; return img->client_id == iid; } static inline bool -x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { +x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { const GraphicsCommand *g = data; return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols)); } static inline bool -y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { +y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { const GraphicsCommand *g = data; return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows)); } static inline bool -z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { +z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { const GraphicsCommand *g = data; return ref->z_index == g->z_index; } static inline bool -point_filter_func(ImageRef *ref, Image *img, const void *data) { - return x_filter_func(ref, img, data) && y_filter_func(ref, img, data); +point_filter_func(ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { + return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell); } static inline bool -point3d_filter_func(ImageRef *ref, Image *img, const void *data) { - return z_filter_func(ref, img, data) && point_filter_func(ref, img, data); +point3d_filter_func(ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { + return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell); } static void -handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty) { +handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) { static GraphicsCommand d; switch (g->delete_action) { -#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func); *is_dirty = true; break +#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell); *is_dirty = true; break #define D(l, u, data, func) case l: case u: I(u, data, func) #define G(l, u, func) D(l, u, g, func) case 0: @@ -810,22 +810,22 @@ grman_resize(GraphicsManager *self, index_type UNUSED old_lines, index_type UNUS } void -grman_rescale(GraphicsManager *self, unsigned int UNUSED old_cell_width, unsigned int UNUSED old_cell_height) { +grman_rescale(GraphicsManager *self, unsigned int UNUSED old_cell_width, unsigned int UNUSED old_cell_height, CellPixelSize cell) { ImageRef *ref; Image *img; self->layers_dirty = true; for (size_t i = self->image_count; i-- > 0;) { img = self->images + i; for (size_t j = img->refcnt; j-- > 0;) { ref = img->refs + j; - ref->cell_x_offset = MIN(ref->cell_x_offset, global_state.cell_width - 1); - ref->cell_y_offset = MIN(ref->cell_y_offset, global_state.cell_height - 1); - update_dest_rect(ref, ref->num_cols, ref->num_rows); + ref->cell_x_offset = MIN(ref->cell_x_offset, cell.width - 1); + ref->cell_y_offset = MIN(ref->cell_y_offset, cell.height - 1); + update_dest_rect(ref, ref->num_cols, ref->num_rows, cell); } } } const char* -grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty) { +grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) { Image *image; const char *ret = NULL; uint32_t iid, q_iid; @@ -839,7 +839,7 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint if (g->action == 'q') { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } } image = handle_add_command(self, g, payload, is_dirty, iid); ret = create_add_response(self, image != NULL, g->action == 'q' ? q_iid: self->last_init_graphics_command.id); - if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image); + if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image, cell); if (g->action == 'q') remove_images(self, add_trim_predicate, NULL); if (self->used_storage > STORAGE_LIMIT) apply_storage_quota(self, STORAGE_LIMIT, image); break; @@ -848,11 +848,11 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint REPORT_ERROR("Put graphics command without image id"); break; } - handle_put_command(self, g, c, is_dirty, NULL); + handle_put_command(self, g, c, is_dirty, NULL, cell); ret = create_add_response(self, true, g->id); break; case 'd': - handle_delete_command(self, g, c, is_dirty); + handle_delete_command(self, g, c, is_dirty, cell); break; default: REPORT_ERROR("Unknown graphics command action: %c", g->action); @@ -925,8 +925,9 @@ W(set_send_to_gpu) { W(update_layers) { unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy; - PA("IffffII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy); - grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy); + CellPixelSize cell; + PA("IffffIIII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height); + grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy, cell); PyObject *ans = PyTuple_New(self->count); for (size_t i = 0; i < self->count; i++) { ImageRenderData *r = self->render_data + i; diff --git a/kitty/graphics.h b/kitty/graphics.h index 2fe0b586a..5fd657b7c 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -83,9 +83,9 @@ typedef struct { } ScrollData; GraphicsManager* grman_alloc(); -void grman_clear(GraphicsManager*, bool); -const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty); -bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows); -void grman_scroll_images(GraphicsManager *self, const ScrollData*); +void grman_clear(GraphicsManager*, bool, CellPixelSize fg); +const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize fg); +bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize); +void grman_scroll_images(GraphicsManager *self, const ScrollData*, CellPixelSize fg); void grman_resize(GraphicsManager*, index_type, index_type, index_type, index_type); -void grman_rescale(GraphicsManager *self, unsigned int old_cell_width, unsigned int old_cell_height); +void grman_rescale(GraphicsManager *self, unsigned int old_cell_width, unsigned int old_cell_height, CellPixelSize fg); diff --git a/kitty/main.py b/kitty/main.py index 04c2bbc81..4392670d4 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -11,12 +11,15 @@ from .borders import load_borders_program from .boss import Boss from .cli import create_opts, parse_args from .config import cached_values_for, initial_window_size -from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file, config_dir +from .constants import ( + appname, config_dir, glfw_path, is_macos, is_wayland, logo_data_file +) from .fast_data_types import ( create_os_window, glfw_init, glfw_terminate, set_default_window_icon, set_options, show_window ) from .fonts.box_drawing import set_scale +from .fonts.render import set_font_family from .utils import ( detach, end_startup_notification, init_startup_notification, log_error, single_instance @@ -39,6 +42,7 @@ def init_graphics(): def run_app(opts, args): set_scale(opts.box_drawing_scale) set_options(opts, is_wayland, args.debug_gl, args.debug_font_fallback) + set_font_family(opts) with cached_values_for(run_app.cached_values_name) as cached_values: w, h = run_app.initial_window_size(opts, cached_values) window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders) diff --git a/kitty/mouse.c b/kitty/mouse.c index 1dc0c9cb7..1d636b811 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -94,45 +94,45 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) { // }}} static inline double -window_left(Window *w) { - return w->geometry.left - OPT(window_padding_width) * (global_state.logical_dpi_x / 72.0); +window_left(Window *w, OSWindow *os_window) { + return w->geometry.left - OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0); } static inline double -window_right(Window *w) { - return w->geometry.right + OPT(window_padding_width) * (global_state.logical_dpi_x / 72.0); +window_right(Window *w, OSWindow *os_window) { + return w->geometry.right + OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0); } static inline double -window_top(Window *w) { - return w->geometry.top - OPT(window_padding_width) * (global_state.logical_dpi_y / 72.0); +window_top(Window *w, OSWindow *os_window) { + return w->geometry.top - OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0); } static inline double -window_bottom(Window *w) { - return w->geometry.bottom + OPT(window_padding_width) * (global_state.logical_dpi_y / 72.0); +window_bottom(Window *w, OSWindow *os_window) { + return w->geometry.bottom + OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0); } static inline bool -contains_mouse(Window *w) { +contains_mouse(Window *w, OSWindow *os_window) { double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; - return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w)); + return (w->visible && window_left(w, os_window) <= x && x <= window_right(w, os_window) && window_top(w, os_window) <= y && y <= window_bottom(w, os_window)); } static inline bool -cell_for_pos(Window *w, unsigned int *x, unsigned int *y) { +cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) { WindowGeometry *g = &w->geometry; Screen *screen = w->render_data.screen; if (!screen) return false; unsigned int qx = 0, qy = 0; double mouse_x = global_state.callback_os_window->mouse_x; double mouse_y = global_state.callback_os_window->mouse_y; - double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w); + double left = window_left(w, os_window), top = window_top(w, os_window), right = window_right(w, os_window), bottom = window_bottom(w, os_window); if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false; if (mouse_x >= g->right) qx = screen->columns - 1; - else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / global_state.cell_width); + else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / os_window->fonts_data->cell_width); if (mouse_y >= g->bottom) qy = screen->lines - 1; - else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / global_state.cell_height); + else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / os_window->fonts_data->cell_height); if (qx < screen->columns && qy < screen->lines) { *x = qx; *y = qy; return true; @@ -156,8 +156,8 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) { bool drag_scroll(Window *w, OSWindow *frame) { - unsigned int margin = global_state.cell_height / 2; - double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w); + unsigned int margin = frame->fonts_data->cell_height / 2; + double left = window_left(w, frame), top = window_top(w, frame), right = window_right(w, frame), bottom = window_bottom(w, frame); double x = frame->mouse_x, y = frame->mouse_y; if (y < top || y > bottom) return false; if (x < left || x > right) return false; @@ -229,7 +229,7 @@ HANDLER(handle_move_event) { call_boss(switch_focus_to, "I", window_idx); } } - if (!cell_for_pos(w, &x, &y)) return; + if (!cell_for_pos(w, &x, &y, global_state.callback_os_window)) return; Screen *screen = w->render_data.screen; detect_url(screen, x, y); bool mouse_cell_changed = x != w->mouse_cell_x || y != w->mouse_cell_y; @@ -377,7 +377,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) { if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (unsigned int i = 0; i < t->num_windows; i++) { - if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) { + if (contains_mouse(t->windows + i, global_state.callback_os_window) && t->windows[i].render_data.screen) { *window_idx = i; return t->windows + i; } } diff --git a/kitty/screen.c b/kitty/screen.c index f8fdaf58b..23fca7f40 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -60,9 +60,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Screen *self; int ret = 0; PyObject *callbacks = Py_None, *test_child = Py_None; - unsigned int columns=80, lines=24, scrollback=0; + unsigned int columns=80, lines=24, scrollback=0, cell_width=10, cell_height=20; id_type window_id=0; - if (!PyArg_ParseTuple(args, "|OIIIKO", &callbacks, &lines, &columns, &scrollback, &window_id, &test_child)) return NULL; + if (!PyArg_ParseTuple(args, "|OIIIIIKO", &callbacks, &lines, &columns, &scrollback, &cell_width, &cell_height, &window_id, &test_child)) return NULL; self = (Screen *)type->tp_alloc(type, 0); if (self != NULL) { @@ -74,6 +74,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen write_buf_lock mutex: %s", strerror(ret)); return NULL; } + self->cell_size.width = cell_width; self->cell_size.height = cell_height; self->columns = columns; self->lines = lines; self->write_buf = PyMem_RawMalloc(BUFSIZ); self->window_id = window_id; @@ -111,7 +112,7 @@ void screen_reset(Screen *self) { if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); linebuf_clear(self->linebuf, BLANK_CHAR); - grman_clear(self->grman, false); + grman_clear(self->grman, false, self->cell_size); self->modes = empty_modes; #define R(name) self->color_profile->overridden.name = 0 R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg); @@ -207,8 +208,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { static void screen_rescale_images(Screen *self, unsigned int old_cell_width, unsigned int old_cell_height) { - grman_rescale(self->main_grman, old_cell_width, old_cell_height); - grman_rescale(self->alt_grman, old_cell_width, old_cell_height); + grman_rescale(self->main_grman, old_cell_width, old_cell_height, self->cell_size); + grman_rescale(self->alt_grman, old_cell_width, old_cell_height, self->cell_size); } @@ -454,7 +455,7 @@ cursor_within_margins(Screen *self) { void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) { unsigned int x = self->cursor->x, y = self->cursor->y; - const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty); + const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty, self->cell_size); if (response != NULL) write_escape_code_to_child(self, APC, response); if (x != self->cursor->x || y != self->cursor->y) { bool in_margins = cursor_within_margins(self); @@ -471,7 +472,7 @@ screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const u void screen_toggle_screen_buffer(Screen *self) { bool to_alt = self->linebuf == self->main_linebuf; - grman_clear(self->alt_grman, true); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway + grman_clear(self->alt_grman, true, self->cell_size); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway if (to_alt) { linebuf_clear(self->alt_linebuf, BLANK_CHAR); screen_save_cursor(self); @@ -712,7 +713,7 @@ screen_cursor_to_column(Screen *self, unsigned int column) { s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \ s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \ s.margin_top = top; s.margin_bottom = bottom; \ - grman_scroll_images(self->grman, &s); \ + grman_scroll_images(self->grman, &s, self->cell_size); \ } #define INDEX_UP \ @@ -961,7 +962,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) { a = 0; b = self->cursor->y; break; case 2: case 3: - grman_clear(self->grman, how == 3); + grman_clear(self->grman, how == 3, self->cell_size); a = 0; b = self->lines; break; default: return; @@ -1281,7 +1282,7 @@ screen_reset_dirty(Screen *self) { } void -screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz) { +screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz, FONTS_DATA_HANDLE fonts_data) { unsigned int history_line_added_count = self->history_line_added_count; index_type lnum; bool selection_must_be_cleared = self->is_dirty ? true : false; @@ -1292,7 +1293,7 @@ screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz) { lnum = self->scrolled_by - 1 - y; historybuf_init_line(self->historybuf, lnum, self->historybuf->line); if (self->historybuf->line->has_dirty_text) { - render_line(self->historybuf->line); + render_line(fonts_data, self->historybuf->line); historybuf_mark_line_clean(self->historybuf, lnum); } update_line_data(self->historybuf->line, y, address); @@ -1301,7 +1302,7 @@ screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz) { lnum = y - self->scrolled_by; linebuf_init_line(self->linebuf, lnum); if (self->linebuf->line->has_dirty_text) { - render_line(self->linebuf->line); + render_line(fonts_data, self->linebuf->line); linebuf_mark_line_clean(self->linebuf, lnum); } update_line_data(self->linebuf->line, y, address); diff --git a/kitty/screen.h b/kitty/screen.h index 9386c8d97..3bc5b330b 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -58,6 +58,7 @@ typedef struct { PyObject_HEAD unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by, last_selection_scrolled_by; + CellPixelSize cell_size; id_type window_id; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; unsigned int current_charset; @@ -157,7 +158,7 @@ void screen_apply_selection(Screen *self, void *address, size_t size); bool screen_is_selection_dirty(Screen *self); bool screen_has_selection(Screen*); bool screen_invert_colors(Screen *self); -void screen_update_cell_data(Screen *self, void *address, size_t sz); +void screen_update_cell_data(Screen *self, void *address, size_t sz, FONTS_DATA_HANDLE); bool screen_is_cursor_visible(Screen *self); bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end); bool screen_selection_range_for_word(Screen *self, index_type x, index_type *, index_type *, index_type *start, index_type *end); diff --git a/kitty/shaders.c b/kitty/shaders.c index c27de825c..f0c53b598 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -13,13 +13,47 @@ enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT }; // Sprites {{{ typedef struct { + unsigned int cell_width, cell_height; int xnum, ynum, x, y, z, last_num_of_layers, last_ynum; GLuint texture_id; - GLenum texture_unit; GLint max_texture_size, max_array_texture_layers; } SpriteMap; -static SpriteMap sprite_map = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1, .texture_unit = GL_TEXTURE0 }; +static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 }; +static GLint max_texture_size = 0, max_array_texture_layers = 0; + +SPRITE_MAP_HANDLE +alloc_sprite_map(unsigned int cell_width, unsigned int cell_height) { + if (!max_texture_size) { + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(max_texture_size)); + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(max_array_texture_layers)); +#ifdef __APPLE__ + // Since on Apple we could have multiple GPUs, with different capabilities, + // upper bound the values according to the data from http://developer.apple.com/graphicsimaging/opengl/capabilities/ + max_texture_size = MIN(8192, sprite_map.max_texture_size); + max_array_texture_layers = MIN(512, sprite_map.max_array_texture_layers); +#endif + sprite_tracker_set_limits(max_texture_size, max_array_texture_layers); + } + SpriteMap *ans = calloc(1, sizeof(SpriteMap)); + ans->cell_width = cell_width; ans->cell_height = cell_height; + if (ans) { + *ans = NEW_SPRITE_MAP; + ans->max_texture_size = max_texture_size; + ans->max_array_texture_layers = max_array_texture_layers; + } + return (SPRITE_MAP_HANDLE)ans; +} + +SPRITE_MAP_HANDLE +free_sprite_map(SPRITE_MAP_HANDLE sm) { + SpriteMap *sprite_map = (SpriteMap*)sm; + if (sprite_map) { + if (sprite_map->texture_id) free_texture(&sprite_map->texture_id); + free(sprite_map); + } + return NULL; +} static bool copy_image_warned = false; @@ -47,7 +81,7 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int static void -realloc_sprite_texture() { +realloc_sprite_texture(FONTS_DATA_HANDLE fg) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D_ARRAY, tex); @@ -58,40 +92,43 @@ realloc_sprite_texture() { glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); unsigned int xnum, ynum, z, znum, width, height, src_ynum; - sprite_tracker_current_layout(&xnum, &ynum, &z); + sprite_tracker_current_layout(fg, &xnum, &ynum, &z); znum = z + 1; - width = xnum * global_state.cell_width; height = ynum * global_state.cell_height; + SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; + width = xnum * sprite_map->cell_width; height = ynum * sprite_map->cell_height; glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum); - if (sprite_map.texture_id) { + if (sprite_map->texture_id) { // need to re-alloc - src_ynum = MAX(1, sprite_map.last_ynum); - copy_image_sub_data(sprite_map.texture_id, tex, width, src_ynum * global_state.cell_height, sprite_map.last_num_of_layers); - glDeleteTextures(1, &sprite_map.texture_id); + src_ynum = MAX(1, sprite_map->last_ynum); + copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * sprite_map->cell_height, sprite_map->last_num_of_layers); + glDeleteTextures(1, &sprite_map->texture_id); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - sprite_map.last_num_of_layers = znum; - sprite_map.last_ynum = ynum; - sprite_map.texture_id = tex; + sprite_map->last_num_of_layers = znum; + sprite_map->last_ynum = ynum; + sprite_map->texture_id = tex; } static inline void -ensure_sprite_map() { - if (!sprite_map.texture_id) realloc_sprite_texture(); +ensure_sprite_map(FONTS_DATA_HANDLE fg) { + SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; + if (!sprite_map->texture_id) realloc_sprite_texture(fg); // We have to rebind since we dont know if the texture was ever bound // in the context of the current OSWindow glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); - glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); } void -send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel *buf) { +send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel *buf) { + SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; unsigned int xnum, ynum, znum; - sprite_tracker_current_layout(&xnum, &ynum, &znum); - if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture(); - glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); + sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); + if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) realloc_sprite_texture(fg); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - x *= global_state.cell_width; y *= global_state.cell_height; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); + x *= sprite_map->cell_width; y *= sprite_map->cell_height; + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map->cell_width, sprite_map->cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); } void @@ -106,35 +143,6 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); } -static bool limits_updated = false; - -static void -layout_sprite_map() { - if (!limits_updated) { - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size)); - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers)); -#ifdef __APPLE__ - // Since on Apple we could have multiple GPUs, with different capabilities, - // upper bound the values according to the data from http://developer.apple.com/graphicsimaging/opengl/capabilities/ - sprite_map.max_texture_size = MIN(8192, sprite_map.max_texture_size); - sprite_map.max_array_texture_layers = MIN(512, sprite_map.max_array_texture_layers); -#endif - sprite_tracker_set_limits(sprite_map.max_texture_size, sprite_map.max_array_texture_layers); - limits_updated = true; - } - if (sprite_map.texture_id) { glDeleteTextures(1, &(sprite_map.texture_id)); sprite_map.texture_id = 0; } - realloc_sprite_texture(); -} - -static void -destroy_sprite_map() { - /* sprite_map_free(); */ - if (sprite_map.texture_id) { - glDeleteTextures(1, &(sprite_map.texture_id)); - sprite_map.texture_id = 0; - } -} - // }}} // Cell {{{ @@ -236,7 +244,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G rd->xstart = xstart; rd->ystart = ystart; rd->dx = dx; rd->dy = dy; unsigned int x, y, z; - sprite_tracker_current_layout(&x, &y, &z); + sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z); rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y; rd->inverted = inverted ? 1 : 0; rd->background_opacity = os_window->background_opacity; @@ -250,18 +258,18 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G } static inline bool -cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy) { +cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) { size_t sz; CELL_BUFFERS; void *address; bool changed = false; - ensure_sprite_map(); + ensure_sprite_map(fonts_data); if (screen->scroll_changed || screen->is_dirty) { sz = sizeof(Cell) * screen->lines * screen->columns; address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY); - screen_update_cell_data(screen, address, sz); + screen_update_cell_data(screen, address, sz, fonts_data); unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL; changed = true; } @@ -274,7 +282,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa changed = true; } - if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines)) { + if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size)) { sz = sizeof(GLfloat) * 16 * screen->grman->count; GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY); for (size_t i = 0; i < screen->grman->count; i++, a += 16) memcpy(a, screen->grman->render_data[i].vertices, sizeof(screen->grman->render_data[0].vertices)); @@ -420,9 +428,10 @@ send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat glClear(GL_COLOR_BUFFER_BIT); changed = true; } - if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy)) changed = true; + if (os_window->fonts_data) { + if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true; + } return changed; - } void @@ -616,13 +625,23 @@ NO_ARG(init_cursor_program) NO_ARG(init_borders_program) NO_ARG(init_cell_program) -NO_ARG(destroy_sprite_map) -NO_ARG(layout_sprite_map) + +static PyObject* +sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { + unsigned int w, h; + if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL; + sprite_tracker_set_limits(w, h); + max_texture_size = w; max_array_texture_layers = h; + Py_RETURN_NONE; +} + + #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { M(compile_program, METH_VARARGS), + M(sprite_map_set_limits, METH_VARARGS), MW(create_vao, METH_NOARGS), MW(bind_vertex_array, METH_O), MW(unbind_vertex_array, METH_NOARGS), @@ -632,8 +651,6 @@ static PyMethodDef module_methods[] = { MW(init_cursor_program, METH_NOARGS), MW(init_borders_program, METH_NOARGS), MW(init_cell_program, METH_NOARGS), - MW(layout_sprite_map, METH_VARARGS), - MW(destroy_sprite_map, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/state.c b/kitty/state.c index 85e1c7b79..db899458a 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -82,6 +82,7 @@ add_os_window() { ans->id = ++global_state.os_window_id_counter; ans->tab_bar_render_data.vao_idx = create_cell_vao(); ans->background_opacity = OPT(background_opacity); + ans->font_sz_in_pts = global_state.font_sz_in_pts; END_WITH_OS_WINDOW_REFS return ans; } @@ -250,14 +251,14 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) { if (os_window->num_tabs > 1) { switch(OPT(tab_bar_edge)) { case TOP_EDGE: - central->left = 0; central->top = global_state.cell_height; central->right = os_window->viewport_width - 1; + central->left = 0; central->top = os_window->fonts_data->cell_height; central->right = os_window->viewport_width - 1; central->bottom = os_window->viewport_height - 1; tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = 0; tab_bar->bottom = central->top - 1; break; default: central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1; - central->bottom = os_window->viewport_height - global_state.cell_height - 1; + central->bottom = os_window->viewport_height - os_window->fonts_data->cell_height - 1; tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = central->bottom + 1; tab_bar->bottom = os_window->viewport_height - 1; break; @@ -453,28 +454,34 @@ wrap_region(Region *r) { } PYWRAP1(viewport_for_window) { - id_type os_window_id = 0; + id_type os_window_id; int vw = 100, vh = 100; - PA("|K", &os_window_id); + unsigned int cell_width = 1, cell_height = 1; + PA("K", &os_window_id); Region central = {0}, tab_bar = {0}; WITH_OS_WINDOW(os_window_id) os_window_regions(os_window, ¢ral, &tab_bar); vw = os_window->viewport_width; vh = os_window->viewport_height; + cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height; goto end; END_WITH_OS_WINDOW end: - return Py_BuildValue("NNiiII", wrap_region(¢ral), wrap_region(&tab_bar), vw, vh, global_state.cell_width, global_state.cell_height); + return Py_BuildValue("NNiiII", wrap_region(¢ral), wrap_region(&tab_bar), vw, vh, cell_width, cell_height); } -PYWRAP1(set_dpi_from_os_window) { - id_type os_window_id = PyLong_AsUnsignedLongLong(args); +PYWRAP1(cell_size_for_window) { + id_type os_window_id; + unsigned int cell_width = 0, cell_height = 0; + PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) - set_dpi_from_os_window(os_window); - Py_RETURN_TRUE; + cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height; + goto end; END_WITH_OS_WINDOW - Py_RETURN_FALSE; +end: + return Py_BuildValue("II", cell_width, cell_height); } + PYWRAP1(mark_os_window_for_close) { id_type os_window_id; int yes = 1; @@ -572,23 +579,28 @@ PYWRAP1(update_window_visibility) { Py_RETURN_NONE; } -PYWRAP1(set_logical_dpi) { - PA("dd", &global_state.logical_dpi_x, &global_state.logical_dpi_y); - Py_RETURN_NONE; +static inline double +dpi_for_os_window_id(id_type os_window_id) { + double dpi = 0; + if (os_window_id) { + WITH_OS_WINDOW(os_window_id) + dpi = (os_window->logical_dpi_x + os_window->logical_dpi_y) / 2.; + END_WITH_OS_WINDOW + } + if (dpi == 0) { + dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.; + } + return dpi; } PYWRAP1(pt_to_px) { - long pt = PyLong_AsLong(args); - double dpi = (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.f; + double pt, dpi = 0; + id_type os_window_id = 0; + PA("d|K", &pt, &os_window_id); + dpi = dpi_for_os_window_id(os_window_id); return PyLong_FromLong((long)round((pt * (dpi / 72.0)))); } -PYWRAP1(pt_to_px_ceil) { - long pt = PyLong_AsLong(args); - double dpi = (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.f; - return PyLong_FromLong((long)ceil((pt * (dpi / 72.0)))); -} - PYWRAP1(set_boss) { Py_CLEAR(global_state.boss); @@ -603,12 +615,6 @@ PYWRAP0(destroy_global_data) { Py_RETURN_NONE; } -PYWRAP1(set_display_state) { - int vw, vh; - PA("iiII", &vw, &vh, &global_state.cell_width, &global_state.cell_height); - Py_RETURN_NONE; -} - THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); } @@ -632,10 +638,7 @@ static PyMethodDef module_methods[] = { MW(set_in_sequence_mode, METH_O), MW(resolve_key_mods, METH_VARARGS), MW(handle_for_window_id, METH_VARARGS), - MW(set_logical_dpi, METH_VARARGS), - MW(pt_to_px, METH_O), - MW(pt_to_px_ceil, METH_O), - MW(set_dpi_from_os_window, METH_O), + MW(pt_to_px, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), MW(update_window_title, METH_VARARGS), @@ -649,6 +652,7 @@ static PyMethodDef module_methods[] = { MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS), MW(viewport_for_window, METH_VARARGS), + MW(cell_size_for_window, METH_VARARGS), MW(mark_os_window_for_close, METH_VARARGS), MW(set_titlebar_color, METH_VARARGS), MW(mark_tab_bar_dirty, METH_O), @@ -656,7 +660,6 @@ static PyMethodDef module_methods[] = { MW(background_opacity_of, METH_O), MW(update_window_visibility, METH_VARARGS), MW(set_boss, METH_O), - MW(set_display_state, METH_VARARGS), MW(destroy_global_data, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ @@ -664,7 +667,13 @@ static PyMethodDef module_methods[] = { bool init_state(PyObject *module) { - global_state.cell_width = 1; global_state.cell_height = 1; + global_state.font_sz_in_pts = 11.0; +#ifdef __APPLE__ +#define DPI 72.0 +#else +#define DPI 96.0 +#endif + global_state.default_dpi.x = DPI; global_state.default_dpi.y = DPI; if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyStructSequence_InitType2(&RegionType, ®ion_desc) != 0) return false; Py_INCREF((PyObject *) &RegionType); diff --git a/kitty/state.h b/kitty/state.h index 0f93b6b35..f2f6b3d21 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -110,6 +110,7 @@ typedef struct { bool is_focused; double cursor_blink_zero_time, last_mouse_activity_at; double mouse_x, mouse_y; + double logical_dpi_x, logical_dpi_y, font_sz_in_pts; bool mouse_button_pressed[20]; PyObject *window_title; bool is_key_pressed[MAX_KEY_COUNT]; @@ -120,16 +121,15 @@ typedef struct { unsigned int clear_count; color_type last_titlebar_color; float background_opacity; + FONTS_DATA_HANDLE fonts_data; + id_type temp_font_group_id; } OSWindow; typedef struct { Options opts; - double logical_dpi_x, logical_dpi_y; id_type os_window_id_counter, tab_id_counter, window_id_counter; - float font_sz_in_pts; - unsigned int cell_width, cell_height; PyObject *boss; OSWindow *os_windows; size_t num_os_windows, capacity; @@ -139,6 +139,8 @@ typedef struct { bool debug_gl, debug_font_fallback; bool has_pending_resizes; bool in_sequence_mode; + double font_sz_in_pts; + struct { double x, y; } default_dpi; } GlobalState; extern GlobalState global_state; @@ -160,7 +162,6 @@ void mark_os_window_for_close(OSWindow* w, bool yes); void update_os_window_viewport(OSWindow *window, bool); bool should_os_window_close(OSWindow* w); bool should_os_window_be_rendered(OSWindow* w); -void set_dpi_from_os_window(OSWindow *w); void wakeup_main_loop(); void event_loop_wait(double timeout); void swap_window_buffers(OSWindow *w); @@ -183,5 +184,5 @@ void draw_cursor(CursorRenderInfo *, bool); void update_surface_size(int, int, uint32_t); void free_texture(uint32_t*); void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool); -void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, pixel*); +void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*); void set_titlebar_color(OSWindow *w, color_type color); diff --git a/kitty/tabs.py b/kitty/tabs.py index 02ad19ccf..04b540c3a 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -11,9 +11,10 @@ from .child import Child from .config import build_ansi_color_table from .constants import WindowGeometry, appname, get_boss, is_macos, is_wayland from .fast_data_types import ( - DECAWM, Screen, add_tab, glfw_post_empty_event, mark_tab_bar_dirty, - next_window_id, pt_to_px, remove_tab, remove_window, set_active_tab, - set_tab_bar_render_data, swap_tabs, viewport_for_window, x11_window_id + DECAWM, Screen, add_tab, cell_size_for_window, glfw_post_empty_event, + mark_tab_bar_dirty, next_window_id, pt_to_px, remove_tab, remove_window, + set_active_tab, set_tab_bar_render_data, swap_tabs, viewport_for_window, + x11_window_id ) from .layout import Rect, create_layout_object_for, evict_cached_layouts from .session import resolved_shell @@ -38,11 +39,11 @@ class Tab: # {{{ if not self.id: raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id)) self.opts, self.args = tab_manager.opts, tab_manager.args - self.margin_width, self.padding_width = map(pt_to_px, ( - self.opts.window_margin_width, self.opts.window_padding_width)) + self.margin_width, self.padding_width = pt_to_px( + self.opts.window_margin_width, self.os_window_id), pt_to_px(self.opts.window_padding_width, self.os_window_id) self.name = getattr(session_tab, 'name', '') self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts] - self.borders = Borders(self.os_window_id, self.id, self.opts) + self.borders = Borders(self.os_window_id, self.id, self.opts, pt_to_px(self.opts.window_border_width, self.os_window_id), self.padding_width) self.windows = deque() for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()): setattr(self, which + '_window', partial(self.nth_window, num=i)) @@ -306,11 +307,11 @@ class TabBar: # {{{ self.os_window_id = os_window_id self.opts = opts self.num_tabs = 1 - self.cell_width = 1 + self.cell_width, cell_height = cell_size_for_window(self.os_window_id) self.data_buffer_size = 0 self.laid_out_once = False self.dirty = True - self.screen = s = Screen(None, 1, 10) + self.screen = s = Screen(None, 1, 10, 0, 0, self.cell_width, cell_height) s.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) s.color_profile.set_configured_colors( color_as_int(opts.inactive_tab_foreground), diff --git a/kitty/window.py b/kitty/window.py index 043bfe720..63ee02f8e 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -18,10 +18,10 @@ from .fast_data_types import ( CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, DECORATION, DIM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, Screen, add_window, - compile_program, get_clipboard_string, glfw_post_empty_event, - init_cell_program, init_cursor_program, set_clipboard_string, - set_titlebar_color, set_window_render_data, update_window_title, - update_window_visibility, viewport_for_window + cell_size_for_window, compile_program, get_clipboard_string, + glfw_post_empty_event, init_cell_program, init_cursor_program, + set_clipboard_string, set_titlebar_color, set_window_render_data, + update_window_title, update_window_visibility, viewport_for_window ) from .keys import keyboard_mode_name from .rgb import to_color @@ -110,7 +110,8 @@ class Window: self.needs_layout = True self.is_visible_in_layout = True self.child, self.opts = child, opts - self.screen = Screen(self, 24, 80, opts.scrollback_lines, self.id) + cell_width, cell_height = cell_size_for_window(self.os_window_id) + self.screen = Screen(self, 24, 80, opts.scrollback_lines, self.id, cell_width, cell_height) setup_colors(self.screen, opts) @property diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index a53fea0e5..c7b6eeb36 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -72,9 +72,9 @@ class BaseTest(TestCase): ae = TestCase.assertEqual maxDiff = 2000 - def create_screen(self, cols=5, lines=5, scrollback=5): + def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20): c = Callbacks() - return Screen(c, lines, cols, scrollback, 0, c) + return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c) def assertEqualAttributes(self, c1, c2): x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0 diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index ba308a94d..ac14ef63f 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -2,17 +2,13 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal -from collections import OrderedDict - from kitty.constants import is_macos from kitty.fast_data_types import ( - DECAWM, set_logical_dpi, set_send_sprite_to_gpu, sprite_map_set_layout, + DECAWM, set_send_sprite_to_gpu, sprite_map_set_layout, sprite_map_set_limits, test_render_line, test_sprite_position_for, wcwidth ) from kitty.fonts.box_drawing import box_chars -from kitty.fonts.render import ( - prerender, render_string, set_font_family, shape_string -) +from kitty.fonts.render import render_string, setup_for_testing, shape_string from . import BaseTest @@ -20,21 +16,12 @@ from . import BaseTest class Rendering(BaseTest): def setUp(self): - sprite_map_set_limits(100000, 100) - self.sprites = OrderedDict() - - def send_to_gpu(x, y, z, data): - self.sprites[(x, y, z)] = data - - set_send_sprite_to_gpu(send_to_gpu) - set_logical_dpi(96.0, 96.0) - self.cell_width, self.cell_height = set_font_family() - prerender() + self.sprites, self.cell_width, self.cell_height = setup_for_testing() self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4, 5]) def tearDown(self): set_send_sprite_to_gpu(None) - del self.sprites + del self.sprites, self.cell_width, self.cell_height def test_sprite_map(self): sprite_map_set_limits(10, 2) diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index bba5dde01..e02ba1056 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -10,7 +10,7 @@ from base64 import standard_b64encode from io import BytesIO from kitty.fast_data_types import ( - parse_bytes, set_display_state, set_send_to_gpu, shm_unlink, shm_write + parse_bytes, set_send_to_gpu, shm_unlink, shm_write ) from . import BaseTest @@ -87,8 +87,7 @@ def put_helpers(self, cw, ch): iid = 0 def create_screen(): - s = self.create_screen(10, 5) - set_display_state(s.columns * cw, s.lines * ch, cw, ch) + s = self.create_screen(10, 5, cell_width=cw, cell_height=ch) return s, 2 / s.columns, 2 / s.lines def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0): @@ -107,7 +106,7 @@ def put_helpers(self, cw, ch): send_command(screen, cmd) def layers(screen, scrolled_by=0, xstart=-1, ystart=1): - return screen.grman.update_layers(scrolled_by, xstart, ystart, dx, dy, screen.columns, screen.lines) + return screen.grman.update_layers(scrolled_by, xstart, ystart, dx, dy, screen.columns, screen.lines, cw, ch) def rect_eq(r, left, top, right, bottom): for side in 'left top right bottom'.split(): @@ -134,7 +133,7 @@ class TestGraphics(BaseTest): img = sl(p, s=1, v=1, f=f) self.ae(bool(img['is_4byte_aligned']), f == 32) - # Test chuunked load + # Test chunked load self.assertIsNone(l('abcd', s=2, v=2, m=1)) self.assertIsNone(l('efgh', m=1)) self.assertIsNone(l('ijkl', m=1))