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:
parent
4f18342ea6
commit
523aadaa3b
@ -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,
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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 */
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
589
kitty/fonts.c
589
kitty/fonts.c
@ -13,8 +13,9 @@
|
||||
|
||||
#define MISSING_GLYPH 4
|
||||
#define MAX_NUM_EXTRA_GLYPHS 8
|
||||
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
|
||||
|
||||
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*);
|
||||
typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
|
||||
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
|
||||
static PyObject *python_send_to_gpu_impl = NULL;
|
||||
extern PyTypeObject Line_Type;
|
||||
@ -50,14 +51,29 @@ struct SpecialGlyphCache {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t max_array_len, max_texture_size, max_y;
|
||||
size_t max_y;
|
||||
unsigned int x, y, z, xnum, ynum;
|
||||
} GPUSpriteTracker;
|
||||
|
||||
|
||||
static GPUSpriteTracker sprite_tracker = {0};
|
||||
static hb_buffer_t *harfbuzz_buffer = NULL;
|
||||
static char_type shape_buffer[4096] = {0};
|
||||
static size_t max_texture_size = 1024, max_array_len = 1024;
|
||||
|
||||
typedef struct {
|
||||
PyObject *face;
|
||||
bool bold, italic;
|
||||
} Descriptor;
|
||||
|
||||
typedef struct {
|
||||
char_type left, right;
|
||||
size_t font_idx;
|
||||
} SymbolMap;
|
||||
|
||||
static Descriptor *descriptors = NULL;
|
||||
static size_t num_symbol_fonts = 0;
|
||||
static SymbolMap *symbol_maps = NULL;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -69,18 +85,100 @@ typedef struct {
|
||||
} Font;
|
||||
|
||||
typedef struct {
|
||||
char_type left, right;
|
||||
size_t font_idx;
|
||||
} SymbolMap;
|
||||
|
||||
typedef struct {
|
||||
FONTS_DATA_HEAD
|
||||
id_type id;
|
||||
unsigned int baseline, underline_position, underline_thickness;
|
||||
size_t fonts_capacity, fonts_count, fallback_fonts_count;
|
||||
ssize_t medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx;
|
||||
Font *fonts;
|
||||
SymbolMap* symbol_maps;
|
||||
size_t fonts_capacity, fonts_count, symbol_maps_capacity, symbol_maps_count, symbol_map_fonts_count, fallback_fonts_count;
|
||||
ssize_t box_font_idx, medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx;
|
||||
} Fonts;
|
||||
pixel *canvas;
|
||||
GPUSpriteTracker sprite_tracker;
|
||||
} FontGroup;
|
||||
|
||||
static FontGroup* font_groups = NULL;
|
||||
static size_t font_groups_capacity = 0;
|
||||
static size_t num_font_groups = 0;
|
||||
static id_type font_group_id_counter = 0;
|
||||
|
||||
static inline void
|
||||
save_window_font_groups() {
|
||||
for (size_t o = 0; o < global_state.num_os_windows; o++) {
|
||||
OSWindow *w = global_state.os_windows + o;
|
||||
w->temp_font_group_id = w->fonts_data ? ((FontGroup*)(w->fonts_data))->id : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
restore_window_font_groups() {
|
||||
for (size_t o = 0; o < global_state.num_os_windows; o++) {
|
||||
OSWindow *w = global_state.os_windows + o;
|
||||
w->fonts_data = NULL;
|
||||
for (size_t i = 0; i < num_font_groups; i++) {
|
||||
if (font_groups[i].id == w->temp_font_group_id) {
|
||||
w->fonts_data = (FONTS_DATA_HANDLE)(font_groups + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
font_group_is_unused(FontGroup *fg) {
|
||||
for (size_t o = 0; o < global_state.num_os_windows; o++) {
|
||||
OSWindow *w = global_state.os_windows + o;
|
||||
if (w->temp_font_group_id == fg->id) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
trim_unused_font_groups() {
|
||||
save_window_font_groups();
|
||||
size_t i = 0;
|
||||
while (i < num_font_groups) {
|
||||
if (font_group_is_unused(font_groups + i)) {
|
||||
size_t num_to_right = (--num_font_groups) - i;
|
||||
if (!num_to_right) break;
|
||||
memmove(font_groups + i, font_groups + 1 + i, num_to_right * sizeof(FontGroup));
|
||||
} else i++;
|
||||
}
|
||||
restore_window_font_groups();
|
||||
}
|
||||
|
||||
static inline void
|
||||
add_font_group() {
|
||||
if (num_font_groups) trim_unused_font_groups();
|
||||
if (num_font_groups >= font_groups_capacity) {
|
||||
save_window_font_groups();
|
||||
font_groups_capacity += 5;
|
||||
font_groups = realloc(font_groups, sizeof(FontGroup) * num_font_groups);
|
||||
if (font_groups == NULL) fatal("Out of memory creating a new font group");
|
||||
restore_window_font_groups();
|
||||
}
|
||||
num_font_groups++;
|
||||
}
|
||||
|
||||
static inline FontGroup*
|
||||
font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) {
|
||||
for (size_t i = 0; i < num_font_groups; i++) {
|
||||
FontGroup *fg = font_groups + i;
|
||||
if (fg->font_sz_in_pts == font_sz_in_pts && fg->logical_dpi_x == logical_dpi_x && fg->logical_dpi_y == logical_dpi_y) return fg;
|
||||
}
|
||||
add_font_group();
|
||||
FontGroup *fg = font_groups + num_font_groups - 1;
|
||||
memset(fg, 0, sizeof(FontGroup));
|
||||
fg->font_sz_in_pts = font_sz_in_pts;
|
||||
fg->logical_dpi_x = logical_dpi_x;
|
||||
fg->logical_dpi_y = logical_dpi_y;
|
||||
fg->id = ++font_group_id_counter;
|
||||
return fg;
|
||||
}
|
||||
|
||||
static inline void
|
||||
clear_canvas(FontGroup *fg) {
|
||||
if (fg->canvas) memset(fg->canvas, 0, CELLS_IN_CANVAS * fg->cell_width * fg->cell_height * sizeof(pixel));
|
||||
}
|
||||
|
||||
static Fonts fonts = {0};
|
||||
|
||||
|
||||
// Sprites {{{
|
||||
@ -98,20 +196,20 @@ sprite_map_set_error(int error) {
|
||||
}
|
||||
|
||||
void
|
||||
sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len) {
|
||||
sprite_tracker.max_texture_size = max_texture_size;
|
||||
sprite_tracker.max_array_len = MIN(0xfff, max_array_len);
|
||||
sprite_tracker_set_limits(size_t max_texture_size_, size_t max_array_len_) {
|
||||
max_texture_size = max_texture_size_;
|
||||
max_array_len = MIN(0xfff, max_array_len_);
|
||||
}
|
||||
|
||||
static inline void
|
||||
do_increment(int *error) {
|
||||
sprite_tracker.x++;
|
||||
if (sprite_tracker.x >= sprite_tracker.xnum) {
|
||||
sprite_tracker.x = 0; sprite_tracker.y++;
|
||||
sprite_tracker.ynum = MIN(MAX(sprite_tracker.ynum, sprite_tracker.y + 1), sprite_tracker.max_y);
|
||||
if (sprite_tracker.y >= sprite_tracker.max_y) {
|
||||
sprite_tracker.y = 0; sprite_tracker.z++;
|
||||
if (sprite_tracker.z >= MIN(UINT16_MAX, sprite_tracker.max_array_len)) *error = 2;
|
||||
do_increment(FontGroup *fg, int *error) {
|
||||
fg->sprite_tracker.x++;
|
||||
if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) {
|
||||
fg->sprite_tracker.x = 0; fg->sprite_tracker.y++;
|
||||
fg->sprite_tracker.ynum = MIN(MAX(fg->sprite_tracker.ynum, fg->sprite_tracker.y + 1), fg->sprite_tracker.max_y);
|
||||
if (fg->sprite_tracker.y >= fg->sprite_tracker.max_y) {
|
||||
fg->sprite_tracker.y = 0; fg->sprite_tracker.z++;
|
||||
if (fg->sprite_tracker.z >= MIN(UINT16_MAX, max_array_len)) *error = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,7 +226,7 @@ extra_glyphs_equal(ExtraGlyphs *a, ExtraGlyphs *b) {
|
||||
|
||||
|
||||
static SpritePosition*
|
||||
sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) {
|
||||
sprite_position_for(FontGroup *fg, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) {
|
||||
glyph_index idx = glyph & (SPECIAL_GLYPH_CACHE_SIZE - 1);
|
||||
SpritePosition *s = font->sprite_map + idx;
|
||||
// Optimize for the common case of glyph under 1024 already in the cache
|
||||
@ -151,8 +249,8 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui
|
||||
s->filled = true;
|
||||
s->rendered = false;
|
||||
s->colored = false;
|
||||
s->x = sprite_tracker.x; s->y = sprite_tracker.y; s->z = sprite_tracker.z;
|
||||
do_increment(error);
|
||||
s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z;
|
||||
do_increment(fg, error);
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -179,8 +277,9 @@ special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t filled_mask) {
|
||||
}
|
||||
|
||||
void
|
||||
sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z) {
|
||||
*x = sprite_tracker.xnum; *y = sprite_tracker.ynum; *z = sprite_tracker.z;
|
||||
sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z) {
|
||||
FontGroup *fg = (FontGroup*)data;
|
||||
*x = fg->sprite_tracker.xnum; *y = fg->sprite_tracker.ynum; *z = fg->sprite_tracker.z;
|
||||
}
|
||||
|
||||
void
|
||||
@ -230,30 +329,30 @@ clear_special_glyph_cache(Font *font) {
|
||||
#undef CLEAR
|
||||
}
|
||||
|
||||
void
|
||||
sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height) {
|
||||
sprite_tracker.xnum = MIN(MAX(1, sprite_tracker.max_texture_size / cell_width), UINT16_MAX);
|
||||
sprite_tracker.max_y = MIN(MAX(1, sprite_tracker.max_texture_size / cell_height), UINT16_MAX);
|
||||
sprite_tracker.ynum = 1;
|
||||
sprite_tracker.x = 0; sprite_tracker.y = 0; sprite_tracker.z = 0;
|
||||
static void
|
||||
sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_width, unsigned int cell_height) {
|
||||
sprite_tracker->xnum = MIN(MAX(1, max_texture_size / cell_width), UINT16_MAX);
|
||||
sprite_tracker->max_y = MIN(MAX(1, max_texture_size / cell_height), UINT16_MAX);
|
||||
sprite_tracker->ynum = 1;
|
||||
sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0;
|
||||
}
|
||||
// }}}
|
||||
|
||||
static inline PyObject*
|
||||
desc_to_face(PyObject *desc) {
|
||||
desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
|
||||
PyObject *d = specialize_font_descriptor(desc);
|
||||
if (d == NULL) return NULL;
|
||||
PyObject *ans = face_from_descriptor(d);
|
||||
PyObject *ans = face_from_descriptor(d, fg);
|
||||
Py_DECREF(d);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation) {
|
||||
init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation, FONTS_DATA_HANDLE fg) {
|
||||
PyObject *face;
|
||||
if (is_face) { face = descriptor; Py_INCREF(face); }
|
||||
else { face = desc_to_face(descriptor); if (face == NULL) return false; }
|
||||
else { face = desc_to_face(descriptor, fg); if (face == NULL) return false; }
|
||||
f->face = face;
|
||||
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
|
||||
return true;
|
||||
@ -266,37 +365,47 @@ del_font(Font *f) {
|
||||
f->bold = false; f->italic = false;
|
||||
}
|
||||
|
||||
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
|
||||
static pixel *canvas = NULL;
|
||||
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
|
||||
static inline void
|
||||
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); }
|
||||
del_font_group(FontGroup *fg) {
|
||||
free(fg->canvas); fg->canvas = NULL;
|
||||
fg->sprite_map = free_sprite_map(fg->sprite_map);
|
||||
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
|
||||
free(fg->fonts); fg->fonts = NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_font_groups() {
|
||||
if (font_groups) {
|
||||
for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i);
|
||||
free(font_groups); font_groups = NULL;
|
||||
font_groups_capacity = 0; num_font_groups = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) {
|
||||
python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel* buf) {
|
||||
if (python_send_to_gpu_impl != NULL && python_send_to_gpu_impl != Py_None) {
|
||||
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * cell_width * cell_height));
|
||||
if (!num_font_groups) fatal("Cannot call send to gpu with no font groups");
|
||||
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * fg->cell_width * fg->cell_height));
|
||||
if (ret == NULL) PyErr_Print();
|
||||
else Py_DECREF(ret);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline PyObject*
|
||||
update_cell_metrics(bool on_dpi_change UNUSED) {
|
||||
#define CALL(idx, desired_height, force) { if (idx >= 0) { Font *f = fonts.fonts + idx; if ((f)->face) { if(!set_size_for_face((f)->face, desired_height, force)) return NULL; } clear_sprite_map((f)); }}
|
||||
CALL(BOX_FONT, 0, false); CALL(fonts.medium_font_idx, 0, false);
|
||||
CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false);
|
||||
cell_metrics(fonts.fonts[fonts.medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness);
|
||||
if (!cell_width) { PyErr_SetString(PyExc_ValueError, "Failed to calculate cell width for the specified font."); return NULL; }
|
||||
static inline void
|
||||
calc_cell_metrics(FontGroup *fg) {
|
||||
unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness;
|
||||
cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness);
|
||||
if (!cell_width) fatal("Failed to calculate cell width for the specified font");
|
||||
unsigned int before_cell_height = cell_height;
|
||||
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
|
||||
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
|
||||
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
|
||||
int line_height_adjustment = cell_height - before_cell_height;
|
||||
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
|
||||
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
|
||||
if (cell_height < 4) fatal("line height too small after adjustment");
|
||||
if (cell_height > 1000) fatal("line height too large after adjustment");
|
||||
underline_position = MIN(cell_height - 1, underline_position);
|
||||
// ensure there is at least a couple of pixels available to render styled underlines
|
||||
while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--;
|
||||
@ -304,25 +413,11 @@ update_cell_metrics(bool on_dpi_change UNUSED) {
|
||||
baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
|
||||
underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
|
||||
}
|
||||
sprite_tracker_set_layout(cell_width, cell_height);
|
||||
global_state.cell_width = cell_width; global_state.cell_height = cell_height;
|
||||
free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel));
|
||||
if (canvas == NULL) return PyErr_NoMemory();
|
||||
for (ssize_t i = 0, j = fonts.first_symbol_font_idx; i < (ssize_t)fonts.symbol_map_fonts_count; i++, j++) {
|
||||
CALL(j, cell_height, true);
|
||||
}
|
||||
for (ssize_t i = 0, j = fonts.first_fallback_font_idx; i < (ssize_t)fonts.fallback_fonts_count; i++, j++) {
|
||||
CALL(j, cell_height, true);
|
||||
}
|
||||
return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness);
|
||||
#undef CALL
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_font_size(PyObject UNUSED *m, PyObject *args) {
|
||||
int on_dpi_change = 0;
|
||||
if (!PyArg_ParseTuple(args, "f|p", &global_state.font_sz_in_pts, &on_dpi_change)) return NULL;
|
||||
return update_cell_metrics(on_dpi_change != 0);
|
||||
sprite_tracker_set_layout(&fg->sprite_tracker, cell_width, cell_height);
|
||||
fg->cell_width = cell_width; fg->cell_height = cell_height;
|
||||
free(fg->canvas);
|
||||
fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
|
||||
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@ -361,59 +456,59 @@ output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_present
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation) {
|
||||
if (fonts.fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
|
||||
load_fallback_font(FontGroup *fg, Cell *cell, bool bold, bool italic, bool emoji_presentation) {
|
||||
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
|
||||
ssize_t f;
|
||||
|
||||
if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx;
|
||||
else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx;
|
||||
if (f < 0) f = fonts.medium_font_idx;
|
||||
if (bold) f = fg->italic_font_idx > 0 ? fg->bi_font_idx : fg->bold_font_idx;
|
||||
else f = italic ? fg->italic_font_idx : fg->medium_font_idx;
|
||||
if (f < 0) f = fg->medium_font_idx;
|
||||
|
||||
PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic, emoji_presentation);
|
||||
PyObject *face = create_fallback_face(fg->fonts[f].face, cell, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg);
|
||||
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
|
||||
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
|
||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face, true);
|
||||
set_size_for_face(face, cell_height, true);
|
||||
set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg);
|
||||
|
||||
ensure_space_for(&fonts, fonts, Font, fonts.fonts_count + 1, fonts_capacity, 5, true);
|
||||
ssize_t ans = fonts.first_fallback_font_idx + fonts.fallback_fonts_count;
|
||||
Font *af = &fonts.fonts[ans];
|
||||
if (!init_font(af, face, bold, italic, true, emoji_presentation)) fatal("Out of memory");
|
||||
ensure_space_for(fg, fonts, Font, fg->fonts_count + 1, fonts_capacity, 5, true);
|
||||
ssize_t ans = fg->first_fallback_font_idx + fg->fallback_fonts_count;
|
||||
Font *af = &fg->fonts[ans];
|
||||
if (!init_font(af, face, bold, italic, true, emoji_presentation, (FONTS_DATA_HANDLE)fg)) fatal("Out of memory");
|
||||
Py_DECREF(face);
|
||||
fonts.fallback_fonts_count++;
|
||||
fonts.fonts_count++;
|
||||
fg->fallback_fonts_count++;
|
||||
fg->fonts_count++;
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
fallback_font(Cell *cell) {
|
||||
fallback_font(FontGroup *fg, Cell *cell) {
|
||||
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
|
||||
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
|
||||
bool emoji_presentation = has_emoji_presentation(cell);
|
||||
|
||||
// Check if one of the existing fallback fonts has this text
|
||||
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
|
||||
Font *ff = fonts.fonts +j;
|
||||
for (size_t i = 0, j = fg->first_fallback_font_idx; i < fg->fallback_fonts_count; i++, j++) {
|
||||
Font *ff = fg->fonts +j;
|
||||
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cell)) {
|
||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, ff->face, false);
|
||||
return j;
|
||||
}
|
||||
}
|
||||
|
||||
return load_fallback_font(cell, bold, italic, emoji_presentation);
|
||||
return load_fallback_font(fg, cell, bold, italic, emoji_presentation);
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
in_symbol_maps(char_type ch) {
|
||||
for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
|
||||
if (fonts.symbol_maps[i].left <= ch && ch <= fonts.symbol_maps[i].right) return fonts.first_symbol_font_idx + fonts.symbol_maps[i].font_idx;
|
||||
in_symbol_maps(FontGroup *fg, char_type ch) {
|
||||
for (size_t i = 0; i < num_symbol_fonts; i++) {
|
||||
if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx;
|
||||
}
|
||||
return NO_FONT;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
font_for_cell(Cell *cell) {
|
||||
font_for_cell(FontGroup *fg, Cell *cell) {
|
||||
START_ALLOW_CASE_RANGE
|
||||
ssize_t ans;
|
||||
switch(cell->ch) {
|
||||
@ -428,21 +523,21 @@ START_ALLOW_CASE_RANGE
|
||||
case 0xe0b6:
|
||||
return BOX_FONT;
|
||||
default:
|
||||
ans = in_symbol_maps(cell->ch);
|
||||
ans = in_symbol_maps(fg, cell->ch);
|
||||
if (ans > -1) return ans;
|
||||
switch(BI_VAL(cell->attrs)) {
|
||||
case 0:
|
||||
ans = fonts.medium_font_idx; break;
|
||||
ans = fg->medium_font_idx; break;
|
||||
case 1:
|
||||
ans = fonts.bold_font_idx ; break;
|
||||
ans = fg->bold_font_idx ; break;
|
||||
case 2:
|
||||
ans = fonts.italic_font_idx; break;
|
||||
ans = fg->italic_font_idx; break;
|
||||
case 3:
|
||||
ans = fonts.bi_font_idx; break;
|
||||
ans = fg->bi_font_idx; break;
|
||||
}
|
||||
if (ans < 0) ans = fonts.medium_font_idx;
|
||||
if (!has_emoji_presentation(cell) && has_cell_text(fonts.fonts + ans, cell)) return ans;
|
||||
return fallback_font(cell);
|
||||
if (ans < 0) ans = fg->medium_font_idx;
|
||||
if (!has_emoji_presentation(cell) && has_cell_text(fg->fonts + ans, cell)) return ans;
|
||||
return fallback_font(fg, cell);
|
||||
}
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
@ -472,7 +567,7 @@ START_ALLOW_CASE_RANGE
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
|
||||
static PyObject* box_drawing_function = NULL;
|
||||
static PyObject* box_drawing_function = NULL, *prerender_function = NULL;
|
||||
|
||||
void
|
||||
render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) {
|
||||
@ -488,11 +583,11 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de
|
||||
}
|
||||
|
||||
static void
|
||||
render_box_cell(Cell *cell) {
|
||||
render_box_cell(FontGroup *fg, Cell *cell) {
|
||||
int error = 0;
|
||||
glyph_index glyph = box_glyph_id(cell->ch);
|
||||
static ExtraGlyphs extra_glyphs = {{0}};
|
||||
SpritePosition *sp = sprite_position_for(&fonts.fonts[BOX_FONT], glyph, &extra_glyphs, false, &error);
|
||||
SpritePosition *sp = sprite_position_for(fg, &fg->fonts[BOX_FONT], glyph, &extra_glyphs, false, &error);
|
||||
if (sp == NULL) {
|
||||
sprite_map_set_error(error); PyErr_Print();
|
||||
set_sprite(cell, 0, 0, 0);
|
||||
@ -502,13 +597,13 @@ render_box_cell(Cell *cell) {
|
||||
if (sp->rendered) return;
|
||||
sp->rendered = true;
|
||||
sp->colored = false;
|
||||
PyObject *ret = PyObject_CallFunction(box_drawing_function, "I", cell->ch);
|
||||
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
|
||||
if (ret == NULL) { PyErr_Print(); return; }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
|
||||
clear_canvas();
|
||||
Region r = { .right = cell_width, .bottom = cell_height };
|
||||
render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width);
|
||||
current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas);
|
||||
clear_canvas(fg);
|
||||
Region r = { .right = fg->cell_width, .bottom = fg->cell_height };
|
||||
render_alpha_mask(alpha_mask, fg->canvas, &r, &r, fg->cell_width, fg->cell_width);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp->x, sp->y, sp->z, fg->canvas);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
@ -539,20 +634,20 @@ set_cell_sprite(Cell *cell, SpritePosition *sp) {
|
||||
}
|
||||
|
||||
static inline pixel*
|
||||
extract_cell_from_canvas(unsigned int i, unsigned int num_cells) {
|
||||
pixel *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width);
|
||||
unsigned int stride = cell_width * num_cells;
|
||||
for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width * sizeof(pixel));
|
||||
extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) {
|
||||
pixel *ans = fg->canvas + (fg->cell_width * fg->cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = fg->canvas + (i * fg->cell_width);
|
||||
unsigned int stride = fg->cell_width * num_cells;
|
||||
for (unsigned int r = 0; r < fg->cell_height; r++, dest += fg->cell_width, src += stride) memcpy(dest, src, fg->cell_width * sizeof(pixel));
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
|
||||
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
|
||||
static SpritePosition* sprite_position[16];
|
||||
int error = 0;
|
||||
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
sprite_position[i] = sprite_position_for(font, glyph, extra_glyphs, (uint8_t)i, &error);
|
||||
sprite_position[i] = sprite_position_for(fg, font, glyph, extra_glyphs, (uint8_t)i, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; }
|
||||
}
|
||||
if (sprite_position[0]->rendered) {
|
||||
@ -560,17 +655,17 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl
|
||||
return;
|
||||
}
|
||||
|
||||
clear_canvas();
|
||||
clear_canvas(fg);
|
||||
bool was_colored = (cells->attrs & WIDTH_MASK) == 2 && is_emoji(cells->ch);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
sprite_position[i]->rendered = true;
|
||||
sprite_position[i]->colored = was_colored;
|
||||
set_cell_sprite(cells + i, sprite_position[i]);
|
||||
pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells);
|
||||
current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
|
||||
pixel *buf = num_cells == 1 ? fg->canvas : extract_cell_from_canvas(fg, i, num_cells);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
|
||||
}
|
||||
|
||||
}
|
||||
@ -813,7 +908,7 @@ merge_groups_for_pua_space_ligature() {
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_groups(Font *font) {
|
||||
render_groups(FontGroup *fg, Font *font) {
|
||||
unsigned idx = 0;
|
||||
ExtraGlyphs ed;
|
||||
while (idx <= G(group_idx)) {
|
||||
@ -826,7 +921,7 @@ render_groups(Font *font) {
|
||||
int last = -1;
|
||||
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
|
||||
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
|
||||
render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
|
||||
render_group(fg, group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
@ -840,12 +935,16 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
index_type num = 0;
|
||||
while(num < line->xnum && line->cells[num].ch) num += line->cells[num].attrs & WIDTH_MASK;
|
||||
PyObject *face = NULL;
|
||||
Font *font = fonts.fonts + fonts.medium_font_idx;
|
||||
Font *font;
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; }
|
||||
if (path) {
|
||||
face = face_from_path(path, index);
|
||||
face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups);
|
||||
if (face == NULL) return NULL;
|
||||
font = calloc(1, sizeof(Font));
|
||||
font->face = face;
|
||||
} else {
|
||||
FontGroup *fg = font_groups;
|
||||
font = fg->fonts + fg->medium_font_idx;
|
||||
}
|
||||
shape_run(line->cells, num, font);
|
||||
|
||||
@ -868,18 +967,18 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
#undef G
|
||||
|
||||
static inline void
|
||||
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
|
||||
render_run(FontGroup *fg, Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
|
||||
switch(font_idx) {
|
||||
default:
|
||||
shape_run(first_cell, num_cells, &fonts.fonts[font_idx]);
|
||||
shape_run(first_cell, num_cells, &fg->fonts[font_idx]);
|
||||
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
|
||||
render_groups(&fonts.fonts[font_idx]);
|
||||
render_groups(fg, &fg->fonts[font_idx]);
|
||||
break;
|
||||
case BLANK_FONT:
|
||||
while(num_cells--) set_sprite(first_cell++, 0, 0, 0);
|
||||
break;
|
||||
case BOX_FONT:
|
||||
while(num_cells--) render_box_cell(first_cell++);
|
||||
while(num_cells--) render_box_cell(fg, first_cell++);
|
||||
break;
|
||||
case MISSING_FONT:
|
||||
while(num_cells--) set_sprite(first_cell++, MISSING_GLYPH, 0, 0);
|
||||
@ -893,15 +992,16 @@ is_private_use(char_type ch) {
|
||||
}
|
||||
|
||||
void
|
||||
render_line(Line *line) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
|
||||
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
|
||||
FontGroup *fg = (FontGroup*)fg_;
|
||||
ssize_t run_font_idx = NO_FONT;
|
||||
index_type first_cell_in_run, i;
|
||||
attrs_type prev_width = 0;
|
||||
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
|
||||
if (prev_width == 2) { prev_width = 0; continue; }
|
||||
Cell *cell = line->cells + i;
|
||||
ssize_t cell_font_idx = font_for_cell(cell);
|
||||
ssize_t cell_font_idx = font_for_cell(fg, cell);
|
||||
if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
|
||||
// We have a private use char followed by a space char, render it as a two cell ligature.
|
||||
Cell *space_cell = line->cells + i+1;
|
||||
@ -911,7 +1011,7 @@ render_line(Line *line) {
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg;
|
||||
RENDER;
|
||||
render_run(line->cells + i, 2, cell_font_idx, true);
|
||||
render_run(fg, line->cells + i, 2, cell_font_idx, true);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + 2;
|
||||
prev_width = line->cells[i+1].attrs & WIDTH_MASK;
|
||||
@ -929,65 +1029,142 @@ render_line(Line *line) {
|
||||
#undef RENDER
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_font(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL;
|
||||
Py_CLEAR(box_drawing_function);
|
||||
if (!PyArg_ParseTuple(args, "OO!O!fO|OOO", &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL;
|
||||
Py_INCREF(box_drawing_function);
|
||||
fonts.symbol_map_fonts_count = PyTuple_GET_SIZE(smf);
|
||||
size_t num_fonts = 5 + fonts.symbol_map_fonts_count;
|
||||
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
|
||||
ensure_space_for(&fonts, fonts, Font, num_fonts, fonts_capacity, 5, true);
|
||||
fonts.fonts_count = 1;
|
||||
#define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; }
|
||||
A(medium, false, false);
|
||||
A(bold, true, false); A(italic, false, true); A(bi, true, true);
|
||||
#undef A
|
||||
static inline void
|
||||
clear_descriptors() {
|
||||
if (descriptors) {
|
||||
Descriptor *d = descriptors;
|
||||
while(d) {
|
||||
Py_CLEAR(d->face);
|
||||
d++;
|
||||
}
|
||||
free(descriptors); descriptors = NULL;
|
||||
}
|
||||
free(symbol_maps); symbol_maps = NULL;
|
||||
}
|
||||
|
||||
fonts.first_symbol_font_idx = fonts.fonts_count;
|
||||
fonts.symbol_maps_count = PyTuple_GET_SIZE(sm);
|
||||
ensure_space_for(&fonts, symbol_maps, SymbolMap, fonts.symbol_maps_count, symbol_maps_capacity, 5, true);
|
||||
for (size_t i = 0; i < fonts.symbol_map_fonts_count; i++) {
|
||||
static PyObject*
|
||||
set_font_data(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL;
|
||||
free_font_groups();
|
||||
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function);
|
||||
clear_descriptors();
|
||||
if (!PyArg_ParseTuple(args, "OOO!O!fO|OOO", &box_drawing_function, &prerender_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL;
|
||||
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function);
|
||||
descriptors = calloc(5 + PyTuple_GET_SIZE(smf), sizeof(Descriptor));
|
||||
if (descriptors == NULL) return PyErr_NoMemory();
|
||||
symbol_maps = calloc(1 + PyTuple_GET_SIZE(sm), sizeof(SymbolMap));
|
||||
if (symbol_maps == NULL) return PyErr_NoMemory();
|
||||
size_t desc_idx = 0;
|
||||
#define AF(name) { \
|
||||
if (name) { Py_INCREF(name); descriptors[desc_idx].face = name; descriptors[desc_idx].bold = desc_idx & 1; descriptors[desc_idx].italic = desc_idx & 2; } \
|
||||
desc_idx++; \
|
||||
}
|
||||
AF(medium); AF(bold); AF(italic); AF(bi);
|
||||
#undef AF
|
||||
num_symbol_fonts = 0;
|
||||
for (Py_ssize_t s = 0; s < PyTuple_GET_SIZE(smf); s++, desc_idx++) {
|
||||
Descriptor *d = descriptors + desc_idx;
|
||||
PyObject *face;
|
||||
int bold, italic;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL;
|
||||
if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false, false)) return NULL;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, s), "Opp", &face, &bold, &italic)) return NULL;
|
||||
Py_INCREF(face);
|
||||
d->face = face; d->bold = bold != 0; d->italic = italic != 0;
|
||||
num_symbol_fonts++;
|
||||
}
|
||||
for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
|
||||
for (Py_ssize_t s = 0; s < PyTuple_GET_SIZE(sm); s++) {
|
||||
unsigned int left, right, font_idx;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, i), "III", &left, &right, &font_idx)) return NULL;
|
||||
fonts.symbol_maps[i].left = left; fonts.symbol_maps[i].right = right; fonts.symbol_maps[i].font_idx = font_idx;
|
||||
SymbolMap *x = symbol_maps + s;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, s), "III", &left, &right, &font_idx)) return NULL;
|
||||
x->left = left; x->right = right; x->font_idx = font_idx;
|
||||
}
|
||||
fonts.first_fallback_font_idx = fonts.fonts_count;
|
||||
fonts.fallback_fonts_count = 0;
|
||||
return update_cell_metrics(false);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline void
|
||||
send_prerendered_sprites(FontGroup *fg) {
|
||||
int error = 0;
|
||||
sprite_index x = 0, y = 0, z = 0;
|
||||
// blank cell
|
||||
clear_canvas(fg);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas);
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIII", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness);
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
||||
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||
clear_canvas(fg);
|
||||
Region r = { .right = fg->cell_width, .bottom = fg->cell_height };
|
||||
render_alpha_mask(alpha_mask, fg->canvas, &r, &r, fg->cell_width, fg->cell_width);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas);
|
||||
}
|
||||
Py_CLEAR(args);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
initialize_font_group(FontGroup *fg) {
|
||||
if (!descriptors) fatal("Must call set_font_data() before initializing a font group");
|
||||
fg->fonts_capacity = 10 + num_symbol_fonts;
|
||||
fg->fonts = calloc(fg->fonts_capacity, sizeof(Font));
|
||||
if (fg->fonts == NULL) fatal("Out of memory allocating fonts array");
|
||||
fg->fonts_count = 1; // the 0 index font is the box font
|
||||
#define I(ftype) { \
|
||||
size_t idx = fg->fonts_count; \
|
||||
if (!init_font(fg->fonts + fg->fonts_count++, d->face, d->bold, d->italic, false, false, (FONTS_DATA_HANDLE)fg)) { \
|
||||
if (PyErr_Occurred()) PyErr_Print(); \
|
||||
fatal("Failed to initialize %s font: %d", #ftype, idx); \
|
||||
}}
|
||||
#define IF(idx, attr) { \
|
||||
Descriptor *d = descriptors + idx; \
|
||||
if (d->face) { \
|
||||
fg->attr##_font_idx = fg->fonts_count; I(basic); \
|
||||
} else fg->attr##_font_idx = -1; \
|
||||
IF(0, medium); IF(1, bold); IF(2, italic); IF(3, bi); \
|
||||
}
|
||||
#undef IF
|
||||
fg->first_symbol_font_idx = fg->fonts_count; fg->first_fallback_font_idx = fg->fonts_count;
|
||||
fg->fallback_fonts_count = 0;
|
||||
for (size_t i = 0; i < num_symbol_fonts; i++) {
|
||||
Descriptor *d = descriptors + i + 4;
|
||||
I(symbol_mapped);
|
||||
fg->first_fallback_font_idx++;
|
||||
}
|
||||
#undef I
|
||||
calc_cell_metrics(fg);
|
||||
fg->sprite_map = alloc_sprite_map(fg->cell_width, fg->cell_height);
|
||||
if (!fg->sprite_map) fatal("Out of memory allocating a sprite map");
|
||||
send_prerendered_sprites(fg);
|
||||
}
|
||||
|
||||
void
|
||||
load_fonts_for_window(OSWindow *w) {
|
||||
w->fonts_data = NULL;
|
||||
FontGroup *fg = font_group_for(w->font_sz_in_pts, w->logical_dpi_x, w->logical_dpi_y);
|
||||
if (!fg->cell_width) initialize_font_group(fg);
|
||||
w->fonts_data = (FONTS_DATA_HANDLE)fg;
|
||||
}
|
||||
|
||||
static void
|
||||
finalize(void) {
|
||||
Py_CLEAR(python_send_to_gpu_impl);
|
||||
free(canvas);
|
||||
clear_descriptors();
|
||||
Py_CLEAR(box_drawing_function);
|
||||
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
|
||||
free(fonts.symbol_maps); free(fonts.fonts);
|
||||
Py_CLEAR(prerender_function);
|
||||
free_font_groups();
|
||||
if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer);
|
||||
free(group_state.groups);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int w, h;
|
||||
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
|
||||
sprite_tracker_set_limits(w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
sprite_map_set_layout(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int w, h;
|
||||
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
|
||||
sprite_tracker_set_layout(w, h);
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
sprite_tracker_set_layout(&font_groups->sprite_tracker, w, h);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@ -997,33 +1174,13 @@ test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
||||
ExtraGlyphs extra_glyphs = {{0}};
|
||||
if (!PyArg_ParseTuple(args, "H|H", &glyph, &extra_glyphs.data)) return NULL;
|
||||
int error;
|
||||
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error);
|
||||
FontGroup *fg = font_groups;
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
SpritePosition *pos = sprite_position_for(fg, &fg->fonts[fg->medium_font_idx], glyph, &extra_glyphs, 0, &error);
|
||||
if (pos == NULL) { sprite_map_set_error(error); return NULL; }
|
||||
return Py_BuildValue("HHH", pos->x, pos->y, pos->z);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
send_prerendered_sprites(PyObject UNUSED *s, PyObject *args) {
|
||||
int error = 0;
|
||||
sprite_index x = 0, y = 0, z = 0;
|
||||
// blank cell
|
||||
clear_canvas();
|
||||
current_send_sprite_to_gpu(x, y, z, canvas);
|
||||
do_increment(&error);
|
||||
if (error != 0) { sprite_map_set_error(error); return NULL; }
|
||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||
x = sprite_tracker.x; y = sprite_tracker.y; z = sprite_tracker.z;
|
||||
do_increment(&error);
|
||||
if (error != 0) { sprite_map_set_error(error); return NULL; }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||
clear_canvas();
|
||||
Region r = { .right = cell_width, .bottom = cell_height };
|
||||
render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width);
|
||||
current_send_sprite_to_gpu(x, y, z, canvas);
|
||||
}
|
||||
return Py_BuildValue("H", x);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_send_sprite_to_gpu(PyObject UNUSED *self, PyObject *func) {
|
||||
Py_CLEAR(python_send_to_gpu_impl);
|
||||
@ -1037,7 +1194,8 @@ static PyObject*
|
||||
test_render_line(PyObject UNUSED *self, PyObject *args) {
|
||||
PyObject *line;
|
||||
if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL;
|
||||
render_line((Line*)line);
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@ -1080,18 +1238,20 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
||||
|
||||
static PyObject*
|
||||
current_fonts(PYNOARG) {
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
PyObject *ans = PyDict_New();
|
||||
if (!ans) return NULL;
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fonts.fonts[val].face) != 0) { goto error; }}
|
||||
SET(medium, fonts.medium_font_idx);
|
||||
if (fonts.bold_font_idx) SET(bold, fonts.bold_font_idx);
|
||||
if (fonts.italic_font_idx) SET(italic, fonts.italic_font_idx);
|
||||
if (fonts.bi_font_idx) SET(bi, fonts.bi_font_idx);
|
||||
PyObject *ff = PyTuple_New(fonts.fallback_fonts_count);
|
||||
FontGroup *fg = font_groups;
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fg->fonts[val].face) != 0) { goto error; }}
|
||||
SET(medium, fg->medium_font_idx);
|
||||
if (fg->bold_font_idx) SET(bold, fg->bold_font_idx);
|
||||
if (fg->italic_font_idx) SET(italic, fg->italic_font_idx);
|
||||
if (fg->bi_font_idx) SET(bi, fg->bi_font_idx);
|
||||
PyObject *ff = PyTuple_New(fg->fallback_fonts_count);
|
||||
if (!ff) goto error;
|
||||
for (size_t i = 0; i < fonts.fallback_fonts_count; i++) {
|
||||
Py_INCREF(fonts.fonts[fonts.first_fallback_font_idx + i].face);
|
||||
PyTuple_SET_ITEM(ff, i, fonts.fonts[fonts.first_fallback_font_idx + i].face);
|
||||
for (size_t i = 0; i < fg->fallback_fonts_count; i++) {
|
||||
Py_INCREF(fg->fonts[fg->first_fallback_font_idx + i].face);
|
||||
PyTuple_SET_ITEM(ff, i, fg->fonts[fg->first_fallback_font_idx + i].face);
|
||||
}
|
||||
PyDict_SetItemString(ans, "fallback", ff);
|
||||
Py_CLEAR(ff);
|
||||
@ -1103,6 +1263,7 @@ error:
|
||||
|
||||
static PyObject*
|
||||
get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
|
||||
PyObject *text;
|
||||
int bold, italic;
|
||||
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
|
||||
@ -1113,18 +1274,25 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
|
||||
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
|
||||
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
|
||||
ssize_t ans = fallback_font(&cell);
|
||||
FontGroup *fg = font_groups;
|
||||
ssize_t ans = fallback_font(fg, &cell);
|
||||
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
|
||||
return fonts.fonts[ans].face;
|
||||
return fg->fonts[ans].face;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
create_test_font_group(PyObject *self UNUSED, PyObject *args) {
|
||||
double sz, dpix, dpiy;
|
||||
if (!PyArg_ParseTuple(args, "ddd", &sz, &dpix, &dpiy)) return NULL;
|
||||
FontGroup *fg = font_group_for(sz, dpix, dpiy);
|
||||
return Py_BuildValue("II", fg->cell_width, fg->cell_height);
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(set_font_size, METH_VARARGS),
|
||||
METHODB(set_font, METH_VARARGS),
|
||||
METHODB(sprite_map_set_limits, METH_VARARGS),
|
||||
METHODB(set_font_data, METH_VARARGS),
|
||||
METHODB(create_test_font_group, METH_VARARGS),
|
||||
METHODB(sprite_map_set_layout, METH_VARARGS),
|
||||
METHODB(send_prerendered_sprites, METH_VARARGS),
|
||||
METHODB(test_sprite_position_for, METH_VARARGS),
|
||||
METHODB(concat_cells, METH_VARARGS),
|
||||
METHODB(set_send_sprite_to_gpu, METH_O),
|
||||
@ -1146,6 +1314,5 @@ init_fonts(PyObject *module) {
|
||||
hb_buffer_set_cluster_level(harfbuzz_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
current_send_sprite_to_gpu = send_sprite_to_gpu;
|
||||
sprite_tracker_set_limits(2000, 2000);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
36
kitty/glfw.c
36
kitty/glfw.c
@ -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),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
139
kitty/shaders.c
139
kitty/shaders.c
@ -13,13 +13,47 @@ enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT };
|
||||
|
||||
// Sprites {{{
|
||||
typedef struct {
|
||||
unsigned int cell_width, cell_height;
|
||||
int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
|
||||
GLuint texture_id;
|
||||
GLenum texture_unit;
|
||||
GLint max_texture_size, max_array_texture_layers;
|
||||
} SpriteMap;
|
||||
|
||||
static SpriteMap sprite_map = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1, .texture_unit = GL_TEXTURE0 };
|
||||
static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 };
|
||||
static GLint max_texture_size = 0, max_array_texture_layers = 0;
|
||||
|
||||
SPRITE_MAP_HANDLE
|
||||
alloc_sprite_map(unsigned int cell_width, unsigned int cell_height) {
|
||||
if (!max_texture_size) {
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(max_texture_size));
|
||||
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(max_array_texture_layers));
|
||||
#ifdef __APPLE__
|
||||
// Since on Apple we could have multiple GPUs, with different capabilities,
|
||||
// upper bound the values according to the data from http://developer.apple.com/graphicsimaging/opengl/capabilities/
|
||||
max_texture_size = MIN(8192, sprite_map.max_texture_size);
|
||||
max_array_texture_layers = MIN(512, sprite_map.max_array_texture_layers);
|
||||
#endif
|
||||
sprite_tracker_set_limits(max_texture_size, max_array_texture_layers);
|
||||
}
|
||||
SpriteMap *ans = calloc(1, sizeof(SpriteMap));
|
||||
ans->cell_width = cell_width; ans->cell_height = cell_height;
|
||||
if (ans) {
|
||||
*ans = NEW_SPRITE_MAP;
|
||||
ans->max_texture_size = max_texture_size;
|
||||
ans->max_array_texture_layers = max_array_texture_layers;
|
||||
}
|
||||
return (SPRITE_MAP_HANDLE)ans;
|
||||
}
|
||||
|
||||
SPRITE_MAP_HANDLE
|
||||
free_sprite_map(SPRITE_MAP_HANDLE sm) {
|
||||
SpriteMap *sprite_map = (SpriteMap*)sm;
|
||||
if (sprite_map) {
|
||||
if (sprite_map->texture_id) free_texture(&sprite_map->texture_id);
|
||||
free(sprite_map);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool copy_image_warned = false;
|
||||
|
||||
@ -47,7 +81,7 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int
|
||||
|
||||
|
||||
static void
|
||||
realloc_sprite_texture() {
|
||||
realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
|
||||
@ -58,40 +92,43 @@ realloc_sprite_texture() {
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
unsigned int xnum, ynum, z, znum, width, height, src_ynum;
|
||||
sprite_tracker_current_layout(&xnum, &ynum, &z);
|
||||
sprite_tracker_current_layout(fg, &xnum, &ynum, &z);
|
||||
znum = z + 1;
|
||||
width = xnum * global_state.cell_width; height = ynum * global_state.cell_height;
|
||||
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
|
||||
width = xnum * sprite_map->cell_width; height = ynum * sprite_map->cell_height;
|
||||
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum);
|
||||
if (sprite_map.texture_id) {
|
||||
if (sprite_map->texture_id) {
|
||||
// need to re-alloc
|
||||
src_ynum = MAX(1, sprite_map.last_ynum);
|
||||
copy_image_sub_data(sprite_map.texture_id, tex, width, src_ynum * global_state.cell_height, sprite_map.last_num_of_layers);
|
||||
glDeleteTextures(1, &sprite_map.texture_id);
|
||||
src_ynum = MAX(1, sprite_map->last_ynum);
|
||||
copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * sprite_map->cell_height, sprite_map->last_num_of_layers);
|
||||
glDeleteTextures(1, &sprite_map->texture_id);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
sprite_map.last_num_of_layers = znum;
|
||||
sprite_map.last_ynum = ynum;
|
||||
sprite_map.texture_id = tex;
|
||||
sprite_map->last_num_of_layers = znum;
|
||||
sprite_map->last_ynum = ynum;
|
||||
sprite_map->texture_id = tex;
|
||||
}
|
||||
|
||||
static inline void
|
||||
ensure_sprite_map() {
|
||||
if (!sprite_map.texture_id) realloc_sprite_texture();
|
||||
ensure_sprite_map(FONTS_DATA_HANDLE fg) {
|
||||
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
|
||||
if (!sprite_map->texture_id) realloc_sprite_texture(fg);
|
||||
// We have to rebind since we dont know if the texture was ever bound
|
||||
// in the context of the current OSWindow
|
||||
glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
|
||||
}
|
||||
|
||||
void
|
||||
send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel *buf) {
|
||||
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel *buf) {
|
||||
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
|
||||
unsigned int xnum, ynum, znum;
|
||||
sprite_tracker_current_layout(&xnum, &ynum, &znum);
|
||||
if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture();
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id);
|
||||
sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
|
||||
if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) realloc_sprite_texture(fg);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
x *= global_state.cell_width; y *= global_state.cell_height;
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
|
||||
x *= sprite_map->cell_width; y *= sprite_map->cell_height;
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map->cell_width, sprite_map->cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
|
||||
}
|
||||
|
||||
void
|
||||
@ -106,35 +143,6 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
}
|
||||
|
||||
static bool limits_updated = false;
|
||||
|
||||
static void
|
||||
layout_sprite_map() {
|
||||
if (!limits_updated) {
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size));
|
||||
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers));
|
||||
#ifdef __APPLE__
|
||||
// Since on Apple we could have multiple GPUs, with different capabilities,
|
||||
// upper bound the values according to the data from http://developer.apple.com/graphicsimaging/opengl/capabilities/
|
||||
sprite_map.max_texture_size = MIN(8192, sprite_map.max_texture_size);
|
||||
sprite_map.max_array_texture_layers = MIN(512, sprite_map.max_array_texture_layers);
|
||||
#endif
|
||||
sprite_tracker_set_limits(sprite_map.max_texture_size, sprite_map.max_array_texture_layers);
|
||||
limits_updated = true;
|
||||
}
|
||||
if (sprite_map.texture_id) { glDeleteTextures(1, &(sprite_map.texture_id)); sprite_map.texture_id = 0; }
|
||||
realloc_sprite_texture();
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_sprite_map() {
|
||||
/* sprite_map_free(); */
|
||||
if (sprite_map.texture_id) {
|
||||
glDeleteTextures(1, &(sprite_map.texture_id));
|
||||
sprite_map.texture_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Cell {{{
|
||||
@ -236,7 +244,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
|
||||
|
||||
rd->xstart = xstart; rd->ystart = ystart; rd->dx = dx; rd->dy = dy;
|
||||
unsigned int x, y, z;
|
||||
sprite_tracker_current_layout(&x, &y, &z);
|
||||
sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z);
|
||||
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
|
||||
rd->inverted = inverted ? 1 : 0;
|
||||
rd->background_opacity = os_window->background_opacity;
|
||||
@ -250,18 +258,18 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
|
||||
}
|
||||
|
||||
static inline bool
|
||||
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy) {
|
||||
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) {
|
||||
size_t sz;
|
||||
CELL_BUFFERS;
|
||||
void *address;
|
||||
bool changed = false;
|
||||
|
||||
ensure_sprite_map();
|
||||
ensure_sprite_map(fonts_data);
|
||||
|
||||
if (screen->scroll_changed || screen->is_dirty) {
|
||||
sz = sizeof(Cell) * screen->lines * screen->columns;
|
||||
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
|
||||
screen_update_cell_data(screen, address, sz);
|
||||
screen_update_cell_data(screen, address, sz, fonts_data);
|
||||
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;
|
||||
changed = true;
|
||||
}
|
||||
@ -274,7 +282,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines)) {
|
||||
if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size)) {
|
||||
sz = sizeof(GLfloat) * 16 * screen->grman->count;
|
||||
GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
|
||||
for (size_t i = 0; i < screen->grman->count; i++, a += 16) memcpy(a, screen->grman->render_data[i].vertices, sizeof(screen->grman->render_data[0].vertices));
|
||||
@ -420,9 +428,10 @@ send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
changed = true;
|
||||
}
|
||||
if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy)) changed = true;
|
||||
if (os_window->fonts_data) {
|
||||
if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true;
|
||||
}
|
||||
return changed;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
@ -616,13 +625,23 @@ NO_ARG(init_cursor_program)
|
||||
NO_ARG(init_borders_program)
|
||||
|
||||
NO_ARG(init_cell_program)
|
||||
NO_ARG(destroy_sprite_map)
|
||||
NO_ARG(layout_sprite_map)
|
||||
|
||||
static PyObject*
|
||||
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int w, h;
|
||||
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
|
||||
sprite_tracker_set_limits(w, h);
|
||||
max_texture_size = w; max_array_texture_layers = h;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
|
||||
#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
|
||||
static PyMethodDef module_methods[] = {
|
||||
M(compile_program, METH_VARARGS),
|
||||
M(sprite_map_set_limits, METH_VARARGS),
|
||||
MW(create_vao, METH_NOARGS),
|
||||
MW(bind_vertex_array, METH_O),
|
||||
MW(unbind_vertex_array, METH_NOARGS),
|
||||
@ -632,8 +651,6 @@ static PyMethodDef module_methods[] = {
|
||||
MW(init_cursor_program, METH_NOARGS),
|
||||
MW(init_borders_program, METH_NOARGS),
|
||||
MW(init_cell_program, METH_NOARGS),
|
||||
MW(layout_sprite_map, METH_VARARGS),
|
||||
MW(destroy_sprite_map, METH_NOARGS),
|
||||
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@ -82,6 +82,7 @@ add_os_window() {
|
||||
ans->id = ++global_state.os_window_id_counter;
|
||||
ans->tab_bar_render_data.vao_idx = create_cell_vao();
|
||||
ans->background_opacity = OPT(background_opacity);
|
||||
ans->font_sz_in_pts = global_state.font_sz_in_pts;
|
||||
END_WITH_OS_WINDOW_REFS
|
||||
return ans;
|
||||
}
|
||||
@ -250,14 +251,14 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
|
||||
if (os_window->num_tabs > 1) {
|
||||
switch(OPT(tab_bar_edge)) {
|
||||
case TOP_EDGE:
|
||||
central->left = 0; central->top = global_state.cell_height; central->right = os_window->viewport_width - 1;
|
||||
central->left = 0; central->top = os_window->fonts_data->cell_height; central->right = os_window->viewport_width - 1;
|
||||
central->bottom = os_window->viewport_height - 1;
|
||||
tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = 0;
|
||||
tab_bar->bottom = central->top - 1;
|
||||
break;
|
||||
default:
|
||||
central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
|
||||
central->bottom = os_window->viewport_height - global_state.cell_height - 1;
|
||||
central->bottom = os_window->viewport_height - os_window->fonts_data->cell_height - 1;
|
||||
tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = central->bottom + 1;
|
||||
tab_bar->bottom = os_window->viewport_height - 1;
|
||||
break;
|
||||
@ -453,28 +454,34 @@ wrap_region(Region *r) {
|
||||
}
|
||||
|
||||
PYWRAP1(viewport_for_window) {
|
||||
id_type os_window_id = 0;
|
||||
id_type os_window_id;
|
||||
int vw = 100, vh = 100;
|
||||
PA("|K", &os_window_id);
|
||||
unsigned int cell_width = 1, cell_height = 1;
|
||||
PA("K", &os_window_id);
|
||||
Region central = {0}, tab_bar = {0};
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
os_window_regions(os_window, ¢ral, &tab_bar);
|
||||
vw = os_window->viewport_width; vh = os_window->viewport_height;
|
||||
cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
|
||||
goto end;
|
||||
END_WITH_OS_WINDOW
|
||||
end:
|
||||
return Py_BuildValue("NNiiII", wrap_region(¢ral), wrap_region(&tab_bar), vw, vh, global_state.cell_width, global_state.cell_height);
|
||||
return Py_BuildValue("NNiiII", wrap_region(¢ral), wrap_region(&tab_bar), vw, vh, cell_width, cell_height);
|
||||
}
|
||||
|
||||
PYWRAP1(set_dpi_from_os_window) {
|
||||
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
|
||||
PYWRAP1(cell_size_for_window) {
|
||||
id_type os_window_id;
|
||||
unsigned int cell_width = 0, cell_height = 0;
|
||||
PA("K", &os_window_id);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
set_dpi_from_os_window(os_window);
|
||||
Py_RETURN_TRUE;
|
||||
cell_width = os_window->fonts_data->cell_width; cell_height = os_window->fonts_data->cell_height;
|
||||
goto end;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_FALSE;
|
||||
end:
|
||||
return Py_BuildValue("II", cell_width, cell_height);
|
||||
}
|
||||
|
||||
|
||||
PYWRAP1(mark_os_window_for_close) {
|
||||
id_type os_window_id;
|
||||
int yes = 1;
|
||||
@ -572,23 +579,28 @@ PYWRAP1(update_window_visibility) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_logical_dpi) {
|
||||
PA("dd", &global_state.logical_dpi_x, &global_state.logical_dpi_y);
|
||||
Py_RETURN_NONE;
|
||||
static inline double
|
||||
dpi_for_os_window_id(id_type os_window_id) {
|
||||
double dpi = 0;
|
||||
if (os_window_id) {
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
dpi = (os_window->logical_dpi_x + os_window->logical_dpi_y) / 2.;
|
||||
END_WITH_OS_WINDOW
|
||||
}
|
||||
if (dpi == 0) {
|
||||
dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.;
|
||||
}
|
||||
return dpi;
|
||||
}
|
||||
|
||||
PYWRAP1(pt_to_px) {
|
||||
long pt = PyLong_AsLong(args);
|
||||
double dpi = (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.f;
|
||||
double pt, dpi = 0;
|
||||
id_type os_window_id = 0;
|
||||
PA("d|K", &pt, &os_window_id);
|
||||
dpi = dpi_for_os_window_id(os_window_id);
|
||||
return PyLong_FromLong((long)round((pt * (dpi / 72.0))));
|
||||
}
|
||||
|
||||
PYWRAP1(pt_to_px_ceil) {
|
||||
long pt = PyLong_AsLong(args);
|
||||
double dpi = (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.f;
|
||||
return PyLong_FromLong((long)ceil((pt * (dpi / 72.0))));
|
||||
}
|
||||
|
||||
|
||||
PYWRAP1(set_boss) {
|
||||
Py_CLEAR(global_state.boss);
|
||||
@ -603,12 +615,6 @@ PYWRAP0(destroy_global_data) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_display_state) {
|
||||
int vw, vh;
|
||||
PA("iiII", &vw, &vh, &global_state.cell_width, &global_state.cell_height);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
THREE_ID_OBJ(update_window_title)
|
||||
THREE_ID(remove_window)
|
||||
PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); }
|
||||
@ -632,10 +638,7 @@ static PyMethodDef module_methods[] = {
|
||||
MW(set_in_sequence_mode, METH_O),
|
||||
MW(resolve_key_mods, METH_VARARGS),
|
||||
MW(handle_for_window_id, METH_VARARGS),
|
||||
MW(set_logical_dpi, METH_VARARGS),
|
||||
MW(pt_to_px, METH_O),
|
||||
MW(pt_to_px_ceil, METH_O),
|
||||
MW(set_dpi_from_os_window, METH_O),
|
||||
MW(pt_to_px, METH_VARARGS),
|
||||
MW(add_tab, METH_O),
|
||||
MW(add_window, METH_VARARGS),
|
||||
MW(update_window_title, METH_VARARGS),
|
||||
@ -649,6 +652,7 @@ static PyMethodDef module_methods[] = {
|
||||
MW(set_tab_bar_render_data, METH_VARARGS),
|
||||
MW(set_window_render_data, METH_VARARGS),
|
||||
MW(viewport_for_window, METH_VARARGS),
|
||||
MW(cell_size_for_window, METH_VARARGS),
|
||||
MW(mark_os_window_for_close, METH_VARARGS),
|
||||
MW(set_titlebar_color, METH_VARARGS),
|
||||
MW(mark_tab_bar_dirty, METH_O),
|
||||
@ -656,7 +660,6 @@ static PyMethodDef module_methods[] = {
|
||||
MW(background_opacity_of, METH_O),
|
||||
MW(update_window_visibility, METH_VARARGS),
|
||||
MW(set_boss, METH_O),
|
||||
MW(set_display_state, METH_VARARGS),
|
||||
MW(destroy_global_data, METH_NOARGS),
|
||||
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
@ -664,7 +667,13 @@ static PyMethodDef module_methods[] = {
|
||||
|
||||
bool
|
||||
init_state(PyObject *module) {
|
||||
global_state.cell_width = 1; global_state.cell_height = 1;
|
||||
global_state.font_sz_in_pts = 11.0;
|
||||
#ifdef __APPLE__
|
||||
#define DPI 72.0
|
||||
#else
|
||||
#define DPI 96.0
|
||||
#endif
|
||||
global_state.default_dpi.x = DPI; global_state.default_dpi.y = DPI;
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
if (PyStructSequence_InitType2(&RegionType, ®ion_desc) != 0) return false;
|
||||
Py_INCREF((PyObject *) &RegionType);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user