diff --git a/docs/text-sizing-protocol.rst b/docs/text-sizing-protocol.rst index 49c7c635f..81d188cd9 100644 --- a/docs/text-sizing-protocol.rst +++ b/docs/text-sizing-protocol.rst @@ -79,6 +79,8 @@ There are only a handful of metadata keys, defined in the table below: "v", "Integer from 0 to 2", "0", "The vertical alignment to use for fractionally scaled text. ``0`` - top, ``1`` - bottom, ``2`` - centered" + "h", "Integer from 0 to 2", "0", "The horizontal alignment to use for fractionally scaled text. ``0`` - left, ``1`` - right, ``2`` - centered" + How it works ------------------ diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 0d007f2fe..45faca68e 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -322,6 +322,7 @@ def parsers() -> None: 'n': ('subscale_n', 'uint'), 'd': ('subscale_d', 'uint'), 'v': ('vertical_align', 'uint'), + 'h': ('horizontal_align', 'uint'), } text = generate( 'parse_multicell_code', 'screen_handle_multicell_command', 'multicell_command', keymap, 'MultiCellCommand', diff --git a/kitty/fonts.c b/kitty/fonts.c index 177bc4570..a4fe0c061 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -34,7 +34,11 @@ typedef struct { } GPUSpriteTracker; typedef struct RunFont { - unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y; + unsigned scale, subscale_n, subscale_d, multicell_y; + union { + struct { uint8_t vertical: 4; uint8_t horizontal: 4; }; + uint8_t val; + } align; ssize_t font_idx; } RunFont; @@ -92,7 +96,7 @@ typedef struct ScaledFontData { #include "kitty-verstable.h" typedef union DecorationsKey { - struct { uint8_t scale : 8, subscale_n : 8, subscale_d : 8, vertical_align : 8, multicell_y : 8, u1 : 8, u2 : 8, u3 : 8; }; + struct { uint8_t scale : 8, subscale_n : 8, subscale_d : 8, align : 8, multicell_y : 8, u1 : 8, u2 : 8, u3 : 8; }; uint64_t val; } DecorationsKey; static_assert(sizeof(DecorationsKey) == sizeof(uint64_t), "Fix the ordering of DecorationsKey"); @@ -322,7 +326,7 @@ sprite_position_for(FontGroup *fg, RunFont rf, glyph_index *glyphs, unsigned gly 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, subscale, rf.multicell_y, rf.vertical_align, &created); + rf.scale, subscale, rf.multicell_y, rf.align.val, &created); if (!s) { PyErr_NoMemory(); return NULL; } return s; } @@ -905,7 +909,7 @@ calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region unsigned src_height = src->bottom; Region src_in_full_coords = *src; unsigned full_dest_height = cell_height * rf.scale; if (rf.subscale_n && rf.subscale_d) { - switch(rf.vertical_align) { + switch(rf.align.vertical) { case 0: break; // top aligned no change case 1: // bottom aligned src_in_full_coords.top = full_dest_height - src_height; @@ -1012,7 +1016,7 @@ render_decorations(FontGroup *fg, Region src, Region dest, FontCellMetrics scale static DecorationMetadata index_for_decorations(FontGroup *fg, RunFont rf, Region src, Region dest, FontCellMetrics scaled_metrics) { - const DecorationsKey key = {.scale=rf.scale, .subscale_n = rf.subscale_n, .subscale_d = rf.subscale_d, .vertical_align = rf.vertical_align, .multicell_y = rf.multicell_y, .u1 = 0, .u2 = 0, .u3 = 0 }; + const DecorationsKey key = {.scale=rf.scale, .subscale_n = rf.subscale_n, .subscale_d = rf.subscale_d, .align = rf.align.val, .multicell_y = rf.multicell_y, .u1 = 0, .u2 = 0, .u3 = 0 }; decorations_index_map_t_itr i = vt_get(&fg->decorations_index_map, key); if (!vt_is_end(i)) return i.data->val; DecorationMetadata val; @@ -1799,7 +1803,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_n == b->subscale_n && a->subscale_d == b->subscale_d && 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->align.val == b->align.val && a->multicell_y == b->multicell_y; } static bool @@ -1833,7 +1837,8 @@ 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_n = cpu_cell->subscale_n; cell_font.subscale_d = cpu_cell->subscale_d; 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.align.vertical = cpu_cell->valign; cell_font.align.horizontal = cpu_cell->halign; 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 4875005ef..6e17b5aef 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -78,8 +78,11 @@ write_multicell_ansi_prefix(ANSILineState *s, const CPUCell *mcd) { if (mcd->subscale_d) { w('d'); w('='); nonnegative_integer_as_utf32(mcd->subscale_d, s->output_buf); w(':'); } - if (mcd->vertical_align) { - w('v'); w('='); nonnegative_integer_as_utf32(mcd->vertical_align, s->output_buf); w(':'); + if (mcd->valign) { + w('v'); w('='); nonnegative_integer_as_utf32(mcd->valign, s->output_buf); w(':'); + } + if (mcd->halign) { + w('h'); w('='); nonnegative_integer_as_utf32(mcd->halign, s->output_buf); w(':'); } if (s->output_buf->buf[s->output_buf->len - 1] == ':') s->output_buf->len--; w(';'); @@ -98,12 +101,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_n || c->subscale_d || c->vertical_align) write_multicell_ansi_prefix(s, c); + if (!c->natural_width || c->scale > 1 || c->subscale_n || c->subscale_d || c->valign || c->halign) 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_n != curr->subscale_n || prev->subscale_d != curr->subscale_d || 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->valign != curr->valign || prev->halign != curr->halign) 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 169b5c6f9..fe8510544 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -44,6 +44,7 @@ static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell"); #define WIDTH_BITS 3 #define SUBSCALE_BITS 4 #define VALIGN_BITS 2 +#define HALIGN_BITS 2 typedef union CPUCell { struct { @@ -59,9 +60,10 @@ typedef union CPUCell { char_type x : WIDTH_BITS + SCALE_BITS; char_type y : SCALE_BITS; char_type width: WIDTH_BITS; - char_type vertical_align: VALIGN_BITS; + char_type valign: VALIGN_BITS; + char_type halign: HALIGN_BITS; char_type temp_flag: 1; - char_type : 17; + char_type : 15; }; struct { char_type ch_and_idx: sizeof(char_type) * 8; @@ -95,7 +97,7 @@ typedef struct { } Line; typedef struct MultiCellCommand { - unsigned int width, scale, subscale_n, subscale_d, vertical_align; + unsigned int width, scale, subscale_n, subscale_d, vertical_align, horizontal_align; size_t payload_sz; } MultiCellCommand; diff --git a/kitty/parse-multicell-command.h b/kitty/parse-multicell-command.h index 1cd887657..3b1121f41 100644 --- a/kitty/parse-multicell-command.h +++ b/kitty/parse-multicell-command.h @@ -23,7 +23,8 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, scale = 's', subscale_n = 'n', subscale_d = 'd', - vertical_align = 'v' + vertical_align = 'v', + horizontal_align = 'h' }; enum KEYS key = 'a'; @@ -51,6 +52,9 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, case vertical_align: value_state = UINT; break; + case horizontal_align: + value_state = UINT; + break; default: REPORT_ERROR("Malformed MultiCellCommand control block, invalid key " "character: 0x%x", @@ -131,6 +135,7 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, U(subscale_n); U(subscale_d); U(vertical_align); + U(horizontal_align); default: break; } @@ -183,12 +188,13 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, } REPORT_VA_COMMAND( - "K s { sI sI sI sI sI ss#}", self->window_id, "multicell_command", + "K s { sI sI sI sI sI sI ss#}", self->window_id, "multicell_command", "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, + (unsigned int)g.vertical_align, "horizontal_align", + (unsigned int)g.horizontal_align, "", (char *)parser_buf + payload_start, g.payload_sz); diff --git a/kitty/screen.c b/kitty/screen.c index 43f81c056..355953d4a 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1258,7 +1258,8 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const CPUCell mcd = { .width=MIN(cmd->width, M(WIDTH_BITS)), .scale=MAX(1u, MIN(cmd->scale, M(SCALE_BITS))), .subscale_n=MIN(cmd->subscale_n, M(SUBSCALE_BITS)), .subscale_d=MIN(cmd->subscale_d, M(SUBSCALE_BITS)), - .vertical_align=MIN(cmd->vertical_align, M(VALIGN_BITS)), .is_multicell=true + .valign=MIN(cmd->vertical_align, M(VALIGN_BITS)), .halign=MIN(cmd->horizontal_align, M(HALIGN_BITS)), + .is_multicell=true }; #undef M if (mcd.width) handle_fixed_width_multicell_command(self, mcd, self->lc); @@ -5389,10 +5390,10 @@ test_parse_written_data(Screen *screen, PyObject *args) { static PyObject* multicell_data_as_dict(CPUCell mcd) { - return Py_BuildValue("{sI sI sI sI sO sI}", + return Py_BuildValue("{sI sI sI sI sO sI 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); + "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.valign, "horizontal_align", mcd.halign); } static PyObject* diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 146e92c35..ce4848682 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -36,8 +36,10 @@ 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_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' +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, horizontal_align: int = 0 + ) -> None: + cmd = f'\x1b]{TEXT_SIZE_CODE};w={width}:s={scale}:n={subscale_n}:d={subscale_d}:v={vertical_align}:h={horizontal_align};{text}\a' parse_bytes(screen, cmd.encode()) diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 104728290..63e9892b8 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -53,6 +53,7 @@ def test_multicell(self: TestMulticell) -> None: ae('subscale_n') ae('subscale_d') ae('vertical_align') + ae('horizontal_align') ae('text') ae('natural_width') ae('next_char_was_wrapped') @@ -448,8 +449,8 @@ def test_multicell(self: TestMulticell) -> None: s.reset() s.reset() - multicell(s, 'a', width=2, scale=3, subscale_n=1, subscale_d=2, vertical_align=1) - ta(f'\x1b]{TEXT_SIZE_CODE};w=2:s=3:n=1:d=2:v=1;a\x07') + multicell(s, 'a', width=2, scale=3, subscale_n=1, subscale_d=2, vertical_align=3, horizontal_align=3) + ta(f'\x1b]{TEXT_SIZE_CODE};w=2:s=3:n=1:d=2:v=3:h=3;a\x07') s.draw('a') multicell(s, 'b', width=2) s.draw('c')