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
This commit is contained in:
Kovid Goyal 2018-05-25 11:28:46 +05:30
parent 4f18342ea6
commit 523aadaa3b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
26 changed files with 736 additions and 639 deletions

View File

@ -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,

View File

@ -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):

View File

@ -8,6 +8,7 @@
#include "threading.h"
#include "state.h"
#include "screen.h"
#include "fonts.h"
#include <termios.h>
#include <unistd.h>
#include <float.h>
@ -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 */
};

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -7,6 +7,7 @@
#pragma once
#include "lineops.h"
#include "state.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <hb.h>
@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -5,6 +5,7 @@
*/
#include "state.h"
#include "fonts.h"
#include <structmember.h>
#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),

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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 */
};

View File

@ -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, &central, &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(&central), wrap_region(&tab_bar), vw, vh, global_state.cell_width, global_state.cell_height);
return Py_BuildValue("NNiiII", wrap_region(&central), 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, &region_desc) != 0) return false;
Py_INCREF((PyObject *) &RegionType);

View File

@ -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);

View File

@ -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),

View File

@ -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

View File

@ -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

View File

@ -2,17 +2,13 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
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)

View File

@ -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))