From 0b8e8bff1661123a48e53883f04ae6ee59bff932 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Nov 2017 16:35:29 +0530 Subject: [PATCH] Infra to display rendered strings as images for testing --- kitty/fonts.c | 28 ++++++++++++++++++++++++++-- kitty/fonts/render.py | 34 ++++++++++++++++++++++++++++++++++ kitty/icat.py | 2 +- kitty/line.c | 11 +++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/kitty/fonts.c b/kitty/fonts.c index eb0f08845..087b2f036 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -369,6 +369,7 @@ render_run(Cell *first_cell, index_type num_cells, Font UNUSED *font, FontType f void render_line(Line *line) { +#define RENDER if ((run_font != NULL || run_font_type != FONT) && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font, run_font_type); Font *run_font = NULL; FontType run_font_type = MISSING_FONT; index_type first_cell_in_run, i; @@ -380,11 +381,12 @@ render_line(Line *line) { FontType cell_font_type = font_for_cell(cell, &cell_font); prev_width = cell->attrs & WIDTH_MASK; if (cell_font_type == run_font_type && cell_font == run_font) continue; - if ((run_font != NULL || run_font_type != FONT) && i > first_cell_in_run) render_run(cell, i - first_cell_in_run, run_font, run_font_type); + RENDER; run_font = cell_font; run_font_type = cell_font_type; first_cell_in_run = i; } - if ((run_font != NULL || run_font_type != FONT) && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font, run_font_type); + RENDER; +#undef RENDER } static PyObject* @@ -503,6 +505,27 @@ test_render_line(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +concat_cells(PyObject UNUSED *self, PyObject *args) { + unsigned int cell_width, cell_height; + PyObject *cells; + if (!PyArg_ParseTuple(args, "IIO!", &cell_width, &cell_height, &PyTuple_Type, &cells)) return NULL; + size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i; + PyObject *ans = PyBytes_FromStringAndSize(NULL, 3 * cell_width * cell_height * num_cells); + if (ans == NULL) return PyErr_NoMemory(); + uint8_t *dest = (uint8_t*)PyBytes_AS_STRING(ans), *src; + for (r = 0; r < cell_height; r++) { + for (c = 0; c < num_cells; c++) { + src = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c))) + cell_width * r; + for (i = 0; i < cell_width; i++, dest += 3) { + dest[0] = src[i]; dest[1] = src[i]; dest[2] = src[i]; + } + + } + } + return ans; +} + static PyMethodDef module_methods[] = { METHODB(set_font_size, METH_VARARGS), METHODB(set_font, METH_VARARGS), @@ -510,6 +533,7 @@ static PyMethodDef module_methods[] = { 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), METHODB(test_render_line, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 39ea69ae6..493512316 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -175,3 +175,37 @@ def render_box_drawing(codepoint): chr(codepoint), CharTexture(), cell_width, cell_height ) return ctypes.addressof(buf), buf + + +def test_render_string(text='\'Qing👁a⧽', size=200.0, dpi=96.0): + from tempfile import NamedTemporaryFile + from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu, Screen, sprite_map_set_limits, test_render_line + from kitty.icat import detect_support, show + if not detect_support(): + raise SystemExit('Your terminal does not support the graphics protocol') + sprites = {} + + 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) + try: + cell_width, cell_height = set_font_family(override_dpi=(dpi, dpi), override_font_size=size) + s = Screen(None, 1, len(text)*2) + line = s.line(0) + s.draw(text) + test_render_line(line) + finally: + set_send_sprite_to_gpu(None) + cells = [] + for i in range(s.columns): + sp = line.sprite_at(i) + if sp != (0, 0, 0): + cells.append(sprites[sp]) + rgb_data = concat_cells(cell_width, cell_height, tuple(cells)) + with NamedTemporaryFile(delete=False) as f: + f.write(rgb_data) + print('Rendered string below: ({}x{})'.format(cell_width, cell_height)) + show(f.name, cell_width * len(cells), cell_height, 24) + print() diff --git a/kitty/icat.py b/kitty/icat.py index f72a8537c..547a930cf 100755 --- a/kitty/icat.py +++ b/kitty/icat.py @@ -124,7 +124,7 @@ def write_chunked(cmd, data): cmd.clear() -def show(outfile, width, height, fmt, transmit_mode): +def show(outfile, width, height, fmt, transmit_mode='t'): cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height} set_cursor(cmd, width, height) if detect_support.has_files: diff --git a/kitty/line.c b/kitty/line.c index c6629ab4a..17b59bf7c 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -203,6 +203,16 @@ as_unicode(Line* self) { return unicode_in_range(self, 0, xlimit_for_line(self), true, 0); } +static PyObject* +sprite_at(Line* self, PyObject *x) { +#define sprite_at_doc "[x] -> Return the sprite in the specified cell" + unsigned long xval = PyLong_AsUnsignedLong(x); + if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; } + Cell *c = self->cells + xval; + return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z); +} + + static inline bool write_sgr(unsigned int val, Py_UCS4 *buf, index_type buflen, index_type *i) { static char s[20] = {0}; @@ -584,6 +594,7 @@ static PyMethodDef methods[] = { METHOD(width, METH_O) METHOD(url_start_at, METH_O) METHOD(url_end_at, METH_O) + METHOD(sprite_at, METH_O) {NULL} /* Sentinel */ };