From 095c63342cb0ab8a405189381b3f68fccea578a0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Dec 2024 10:55:59 +0530 Subject: [PATCH] Allow more subscales --- gen/apc_parsers.py | 3 ++- kitty/fonts.c | 15 ++++++++------- kitty/line.c | 11 +++++++---- kitty/line.h | 10 ++++------ kitty/parse-multicell-command.h | 29 ++++++++++++++++++++--------- kitty/screen.c | 9 ++++++--- kitty_tests/__init__.py | 4 ++-- kitty_tests/fonts.py | 27 +++++++++++++++------------ kitty_tests/multicell.py | 23 +++++++++++++---------- 9 files changed, 77 insertions(+), 54 deletions(-) diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index e57e583613..408c8b4aa2 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -316,7 +316,8 @@ def parsers() -> None: keymap = { 'w': ('width', 'uint'), 's': ('scale', 'uint'), - 'f': ('subscale', 'uint'), + 'n': ('subscale_n', 'uint'), + 'd': ('subscale_d', 'uint'), 'v': ('vertical_align', 'uint'), } text = generate( diff --git a/kitty/fonts.c b/kitty/fonts.c index 7bee66870a..6e1d7d2de9 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -63,7 +63,7 @@ typedef struct { } Font; typedef struct RunFont { - unsigned scale, subscale, vertical_align, multicell_y; + unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y; ssize_t font_idx; } RunFont; @@ -256,9 +256,10 @@ static SpritePosition* sprite_position_for(FontGroup *fg, RunFont rf, glyph_index *glyphs, unsigned glyph_count, uint8_t ligature_index, unsigned cell_count, int *error) { bool created; Font *font = fg->fonts + rf.font_idx; + uint8_t subscale = ((rf.subscale_n & 0xf) << 4) | (rf.subscale_d & 0xf); SpritePosition *s = find_or_create_sprite_position( font->sprite_position_hash_table, glyphs, glyph_count, ligature_index, cell_count, - rf.scale, rf.subscale, rf.multicell_y, rf.vertical_align, &created); + rf.scale, subscale, rf.multicell_y, rf.vertical_align, &created); if (!s) { *error = 1; return NULL; } if (created) { s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z; @@ -730,8 +731,8 @@ static void scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) { *width *= rf.scale; *height *= rf.scale; - if (rf.subscale) { - double frac = 1. / (rf.subscale + 1); + if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) { + double frac = ((double)rf.subscale_n) / rf.subscale_d; *width = (unsigned)ceil(frac * *width); *height = (unsigned)ceil(frac * *height); } @@ -750,7 +751,7 @@ static void calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) { unsigned src_height = src->bottom; Region src_in_full_coords = *src; unsigned full_dest_height = cell_height * rf.scale; - if (rf.subscale) { + if (rf.subscale_n && rf.subscale_d) { switch(rf.vertical_align) { case 0: break; // top aligned no change case 1: // bottom aligned @@ -1456,7 +1457,7 @@ cell_cap_for_codepoint(const char_type cp) { static bool run_fonts_are_equal(const RunFont *a, const RunFont *b) { - return a->font_idx == b->font_idx && a->scale == b->scale && a->subscale == b->subscale && a->vertical_align == b->vertical_align && a->multicell_y == b->multicell_y; + return a->font_idx == b->font_idx && a->scale == b->scale && a->subscale_n == b->subscale_n && a->subscale_d == b->subscale_d && a->vertical_align == b->vertical_align && a->multicell_y == b->multicell_y; } void @@ -1479,7 +1480,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1; continue; } - cell_font.scale = cpu_cell->scale; cell_font.subscale = cpu_cell->subscale; cell_font.vertical_align = cpu_cell->vertical_align; + cell_font.scale = cpu_cell->scale; cell_font.subscale_n = cpu_cell->subscale_n; cell_font.subscale_d = cpu_cell->subscale_d; cell_font.vertical_align = cpu_cell->vertical_align; cell_font.multicell_y = cpu_cell->y; } text_in_cell(cpu_cell, line->text_cache, lc); diff --git a/kitty/line.c b/kitty/line.c index b0226c5855..306f3a9ddd 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -72,8 +72,11 @@ write_multicell_ansi_prefix(ANSILineState *s, const CPUCell *mcd) { if (mcd->scale > 1) { w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, s->output_buf); w(':'); } - if (mcd->subscale) { - w('S'); w('='); nonnegative_integer_as_utf32(mcd->subscale, s->output_buf); w(':'); + if (mcd->subscale_n) { + w('n'); w('='); nonnegative_integer_as_utf32(mcd->subscale_n, s->output_buf); w(':'); + } + if (mcd->subscale_d) { + w('d'); w('='); nonnegative_integer_as_utf32(mcd->subscale_d, s->output_buf); w(':'); } if (s->output_buf->buf[s->output_buf->len - 1] == ':') s->output_buf->len--; w(';'); @@ -92,12 +95,12 @@ close_multicell(ANSILineState *s) { static void start_multicell_if_needed(ANSILineState *s, const CPUCell *c) { - if (!c->natural_width || c->scale > 1 || c->subscale || c->vertical_align) write_multicell_ansi_prefix(s, c); + if (!c->natural_width || c->scale > 1 || c->subscale_n || c->subscale_d || c->vertical_align) write_multicell_ansi_prefix(s, c); } static bool multicell_is_continuation_of_previous(const CPUCell *prev, const CPUCell *curr) { - if (prev->scale != curr->scale || prev->subscale != curr->subscale || prev->vertical_align != curr->vertical_align) return false; + if (prev->scale != curr->scale || prev->subscale_n != curr->subscale_n || prev->subscale_d != curr->subscale_d || prev->vertical_align != curr->vertical_align) return false; if (prev->natural_width) return curr->natural_width; return prev->width == curr->width && !curr->natural_width; } diff --git a/kitty/line.h b/kitty/line.h index 86d0caf90a..d99f392509 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -15,12 +15,9 @@ // TODO: Handle selection with multicell // TODO: URL detection with multicell // TODO: Cursor rendering over multicell -// TODO: Test the escape codes to delete and insert characters and lines with multicell // TODO: Handle replay of dumped graphics_command and multicell_command // TODO: Handle rewrap and restitch of multiline chars // TODO: Handle rewrap when a character is too wide/tall to fit on resized screen -// TODO: Test serialization to ansi only using escape code for explicitly set multicells -// TODO: Test rendering of box drawing at various scales and subscales and alignments // TODO: Implement baseline align for box drawing typedef union CellAttrs { @@ -64,11 +61,12 @@ typedef union CPUCell { char_type natural_width: 1; char_type x : 8; char_type y : 4; - char_type subscale: 2; + char_type subscale_n: 4; + char_type subscale_d: 4; char_type scale: 3; char_type width: 3; char_type vertical_align: 3; - char_type : 21; + char_type : 15; }; struct { char_type ch_and_idx: sizeof(char_type) * 8; @@ -103,7 +101,7 @@ typedef struct { } Line; typedef struct MultiCellCommand { - unsigned int width, scale, subscale, vertical_align; + unsigned int width, scale, subscale_n, subscale_d, vertical_align; size_t payload_sz; } MultiCellCommand; diff --git a/kitty/parse-multicell-command.h b/kitty/parse-multicell-command.h index 35bd474e8e..b9f9ef6621 100644 --- a/kitty/parse-multicell-command.h +++ b/kitty/parse-multicell-command.h @@ -18,7 +18,13 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, (void)is_negative; size_t sz; - enum KEYS { width = 'w', scale = 's', subscale = 'f', vertical_align = 'v' }; + enum KEYS { + width = 'w', + scale = 's', + subscale_n = 'n', + subscale_d = 'd', + vertical_align = 'v' + }; enum KEYS key = 'a'; if (parser_buf[pos] == ';') @@ -36,7 +42,10 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, case scale: value_state = UINT; break; - case subscale: + case subscale_n: + value_state = UINT; + break; + case subscale_d: value_state = UINT; break; case vertical_align: @@ -119,7 +128,8 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, switch (key) { U(width); U(scale); - U(subscale); + U(subscale_n); + U(subscale_d); U(vertical_align); default: break; @@ -172,14 +182,15 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, break; } - REPORT_VA_COMMAND("K s { sI sI sI sI sI} y#", self->window_id, - "multicell_command", + REPORT_VA_COMMAND( + "K s { sI sI sI sI sI sI} y#", self->window_id, "multicell_command", - "width", (unsigned int)g.width, "scale", - (unsigned int)g.scale, "subscale", (unsigned int)g.subscale, - "vertical_align", (unsigned int)g.vertical_align, + "width", (unsigned int)g.width, "scale", (unsigned int)g.scale, + "subscale_n", (unsigned int)g.subscale_n, "subscale_d", + (unsigned int)g.subscale_d, "vertical_align", + (unsigned int)g.vertical_align, - "payload_sz", g.payload_sz, parser_buf, g.payload_sz); + "payload_sz", g.payload_sz, parser_buf, g.payload_sz); screen_handle_multicell_command(self->screen, &g, parser_buf + payload_start); } diff --git a/kitty/screen.c b/kitty/screen.c index 292d3b5cbb..a98a685741 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1203,8 +1203,8 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const self->lc->count = decode_utf8_safe_string(payload, cmd->payload_sz, self->lc->chars); if (!self->lc->count) return; CPUCell mcd = { - .width=MIN(cmd->width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale=MIN(cmd->subscale, 3u), - .vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true + .width=MIN(cmd->width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale_n=MIN(cmd->subscale_n, 15u), + .subscale_d=MIN(cmd->subscale_d, 15), .vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true }; if (mcd.width) handle_fixed_width_multicell_command(self, mcd, self->lc); else { @@ -5188,7 +5188,10 @@ test_parse_written_data(Screen *screen, PyObject *args) { static PyObject* multicell_data_as_dict(CPUCell mcd) { - return Py_BuildValue("{sI sI sI sO sI}", "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, "subscale", (unsigned int)mcd.subscale, "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.vertical_align); + return Py_BuildValue("{sI sI sI sI sO sI}", + "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, + "subscale_n", (unsigned int)mcd.subscale_n, "subscale_d", (unsigned int)mcd.subscale_d, + "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.vertical_align); } static PyObject* diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 4e3290eec3..dad64557e9 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -37,8 +37,8 @@ def parse_bytes(screen, data, dump_callback=None): screen.test_parse_written_data(dump_callback) -def draw_multicell(screen: Screen, text: str, width: int = 0, scale: int = 1, subscale: int = 0, vertical_align: int = 0) -> None: - cmd = f'\x1b]{TEXT_SIZE_CODE};w={width}:s={scale}:f={subscale}:v={vertical_align};{text}\a' +def draw_multicell(screen: Screen, text: str, width: int = 0, scale: int = 1, subscale_n: int = 0, subscale_d: int = 0, vertical_align: int = 0) -> None: + cmd = f'\x1b]{TEXT_SIZE_CODE};w={width}:s={scale}:n={subscale_n}:d={subscale_d}:v={vertical_align};{text}\a' parse_bytes(screen, cmd.encode()) diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index c414f26f37..f1f455948b 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -211,39 +211,42 @@ def test_box_drawing(self): def test_scaled_box_drawing(self): block_size = self.cell_width * self.cell_height * 4 - def full_block(subscale): + def full_block(): return b'\xff' * block_size - def empty_block(subscale): + def empty_block(): return b'\0' * block_size - def half_block(subscale, first=b'\xff', second=b'\0'): - frac = 1 / (subscale + 1) + def half_block(first=b'\xff', second=b'\0'): + frac = 0.5 height = ceil(frac * self.cell_height) rest = self.cell_height - height return (first * (rest * self.cell_width * 4)) + (second * height * self.cell_width * 4) - def upper_half_block(subscale): - return half_block(subscale) + def upper_half_block(): + return half_block() - def lower_half_block(subscale): - return half_block(subscale, b'\0', b'\xff') + def lower_half_block(): + return half_block(b'\0', b'\xff') s = self.create_screen(cols=8, lines=8, scrollback=0) - def block_test(a=empty_block, b=empty_block, c=empty_block, d=empty_block, scale=2, subscale=1, vertical_align=0): + def block_test(a=empty_block, b=empty_block, c=empty_block, d=empty_block, scale=2, half_block=True, vertical_align=0): s.reset() before = len(self.sprites) - draw_multicell(s, '█', scale=scale, subscale=subscale, vertical_align=vertical_align) + subscale_n = subscale_d = 0 + if half_block: + subscale_n, subscale_d = 1, 2 + draw_multicell(s, '█', scale=scale, subscale_n=subscale_n, subscale_d=subscale_d, vertical_align=vertical_align) test_render_line(s.line(0)) self.ae(len(self.sprites), before + 2) test_render_line(s.line(1)) self.ae(len(self.sprites), before + 4) blocks = tuple(self.sprites)[before:] - for i, (expected, actual) in enumerate(zip((a(subscale), b(subscale), c(subscale), d(subscale)), blocks)): + for i, (expected, actual) in enumerate(zip((a(), b(), c(), d()), blocks)): self.ae(self.sprites[actual], expected, f'The {i} block differs') - block_test(full_block, full_block, full_block, full_block, subscale=0) + block_test(full_block, full_block, full_block, full_block, half_block=False) block_test(a=full_block) block_test(c=full_block, vertical_align=1) block_test(a=lower_half_block, c=upper_half_block, vertical_align=2) diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 9cc884cb42..023f239275 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -41,7 +41,8 @@ def ae(key): ae('y') ae('width') ae('scale') - ae('subscale') + ae('subscale_n') + ae('subscale_d') ae('vertical_align') ae('text') ae('natural_width') @@ -90,21 +91,21 @@ def assert_cursor_at(x, y): s.reset() ac(0, 0, is_multicell=False) multicell(s, 'a') - ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True, cursor=(1, 0)) + ac(0, 0, is_multicell=True, width=1, scale=1, subscale_n=0, x=0, y=0, text='a', natural_width=True, cursor=(1, 0)) ac(0, 1, is_multicell=False), ac(1, 0, is_multicell=False), ac(1, 1, is_multicell=False) s.draw('莊') - ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True) - ac(1, 0, is_multicell=True, width=2, scale=1, subscale=0, x=0, y=0, text='莊', natural_width=True, cursor=(3, 0)) - ac(2, 0, is_multicell=True, width=2, scale=1, subscale=0, x=1, y=0, text='', natural_width=True) + ac(0, 0, is_multicell=True, width=1, scale=1, subscale_n=0, x=0, y=0, text='a', natural_width=True) + ac(1, 0, is_multicell=True, width=2, scale=1, subscale_n=0, x=0, y=0, text='莊', natural_width=True, cursor=(3, 0)) + ac(2, 0, is_multicell=True, width=2, scale=1, subscale_n=0, x=1, y=0, text='', natural_width=True) for x in range(s.columns): ac(x, 1, is_multicell=False) s.cursor.x = 0 - multicell(s, 'a', width=2, scale=2, subscale=3) - ac(0, 0, is_multicell=True, width=2, scale=2, subscale=3, x=0, y=0, text='a', natural_width=False, cursor=(4, 0)) + multicell(s, 'a', width=2, scale=2, subscale_n=3) + ac(0, 0, is_multicell=True, width=2, scale=2, subscale_n=3, x=0, y=0, text='a', natural_width=False, cursor=(4, 0)) for x in range(1, 4): - ac(x, 0, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=0, text='', natural_width=False) + ac(x, 0, is_multicell=True, width=2, scale=2, subscale_n=3, x=x, y=0, text='', natural_width=False) for x in range(0, 4): - ac(x, 1, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=1, text='', natural_width=False) + ac(x, 1, is_multicell=True, width=2, scale=2, subscale_n=3, x=x, y=1, text='', natural_width=False) # Test draw with cursor in a multicell s.reset() @@ -116,7 +117,7 @@ def assert_cursor_at(x, y): s.cursor.x = 0 s.draw('a'), ac(0, 0, is_multicell=False), ac(1, 0, is_multicell=False) s.reset() - multicell(s, 'a', width=2, scale=2, subscale=3) + multicell(s, 'a', width=2, scale=2, subscale_n=3, subscale_d=4) s.cursor.x, s.cursor.y = 1, 1 s.draw('b') self.ae(8, count_multicells()) @@ -398,6 +399,8 @@ def ta(expected): s.reset() s.reset() + multicell(s, 'a', width=2, scale=3, subscale_n=1, subscale_d=2, vertical_align=1) + ta('\x1b]66;w=2:s=3:n=1:d=2;a\x07') s.draw('a') multicell(s, 'b', width=2) s.draw('c')