diff --git a/kitty/fonts.c b/kitty/fonts.c index f03be38f6..3817c38a3 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -481,9 +481,9 @@ face_has_codepoint(const void* face, char_type cp) { } static bool -has_emoji_presentation(const ListOfChars *lc) { - if (!lc->is_multicell || !lc->is_topleft || !lc->count) return false; - return is_emoji(lc->chars[0]) && (lc->count == 1 || lc->chars[1] != VS15); +has_emoji_presentation(const CPUCell *c, const ListOfChars *lc) { + if (!c->is_multicell || c->x || c->y || !lc->count) return false; + return is_emoji(lc->chars[0]) && (lc->count == 1 || lc->chars[1] != VS15); } bool @@ -581,10 +581,10 @@ chars_as_utf8(const ListOfChars *lc, char *buf, char_type zero_char) { } static ssize_t -fallback_font(FontGroup *fg, const GPUCell *gpu_cell, const ListOfChars *lc) { +fallback_font(FontGroup *fg, const CPUCell *cpu_cell, const GPUCell *gpu_cell, const ListOfChars *lc) { bool bold = gpu_cell->attrs.bold; bool italic = gpu_cell->attrs.italic; - bool emoji_presentation = has_emoji_presentation(lc); + bool emoji_presentation = has_emoji_presentation(cpu_cell, lc); char style = emoji_presentation ? 'a' : 'A'; if (bold) style += italic ? 3 : 2; else style += italic ? 1 : 0; char cell_text[4 * 32] = {style}; @@ -640,7 +640,7 @@ START_ALLOW_CASE_RANGE case 0xf5d0 ... 0xf60d: // branch drawing characters return BOX_FONT; default: - *is_emoji_presentation = has_emoji_presentation(lc); + *is_emoji_presentation = has_emoji_presentation(cpu_cell, lc); ans = in_symbol_maps(fg, lc->chars[0]); if (ans > -1) return ans; switch(gpu_cell->attrs.bold | (gpu_cell->attrs.italic << 1)) { @@ -655,7 +655,7 @@ START_ALLOW_CASE_RANGE } if (ans < 0) ans = fg->medium_font_idx; if (!*is_emoji_presentation && has_cell_text((bool(*)(const void*, char_type))face_has_codepoint, (fg->fonts + ans)->face, false, lc)) { *is_main_font = true; return ans; } - return fallback_font(fg, gpu_cell, lc); + return fallback_font(fg, cpu_cell, gpu_cell, lc); } END_ALLOW_CASE_RANGE } @@ -776,7 +776,7 @@ static void render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) { int error = 0; // We need to render multicell chars for multicell_y > 0 cell_first_char() returns 0 for such cells - char_type ch = cpu_cell->is_multicell ? tc_first_char_at_index(tc, cpu_cell->ch_or_idx) : cell_first_char(cpu_cell, tc); + char_type ch = cpu_cell->ch_is_idx ? tc_first_char_at_index(tc, cpu_cell->ch_or_idx) : cpu_cell->ch_or_idx; glyph_index glyph = box_glyph_id(ch); ensure_glyph_render_scratch_space(rf.scale); bool all_rendered = true; @@ -824,7 +824,7 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_ while (num_cells) { for (num = 0; num_cells; first_cpu_cell++, first_gpu_cell++, num_cells--) { text_in_cell(first_cpu_cell, tc, &lc); - if (lc.is_multicell && !lc.is_topleft) continue; + if (first_cpu_cell->is_multicell && (first_cpu_cell->x + first_cpu_cell->y)) continue; if (lc.count + num > arraysz(shape_buffer)) break; memcpy(shape_buffer + num, lc.chars, lc.count * sizeof(shape_buffer[0])); num += lc.count; @@ -866,7 +866,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU ensure_canvas_can_fit(fg, num_cells + 1, rf.scale); text_in_cell(cpu_cells, tc, global_glyph_render_scratch.lc); - bool was_colored = has_emoji_presentation(global_glyph_render_scratch.lc); + bool was_colored = has_emoji_presentation(cpu_cells, global_glyph_render_scratch.lc); render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph); if (PyErr_Occurred()) PyErr_Print(); @@ -982,8 +982,7 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell, const TextCache if (cell_data->codepoints_consumed >= cell_data->num_codepoints) { uint16_t width = 1; if (cell_data->cpu_cell->is_multicell) { - MultiCellData mcd = cell_multicell_data(cell_data->cpu_cell, tc); - width = mcd.width * mcd.scale; + width = cell_data->cpu_cell->width * cell_data->cpu_cell->scale; } cell_data->cpu_cell += width; cell_data->gpu_cell += width; @@ -1304,20 +1303,20 @@ collapse_pua_space_ligature(index_type num_cells) { } static bool -is_group_calt_ligature(const Group *group, const TextCache *tc) { +is_group_calt_ligature(const Group *group) { if (group->num_cells < 2 || !group->has_special_glyph) return false; const CPUCell *first_cell = G(first_cpu_cell) + group->first_cell_idx; - return !first_cell->is_multicell || cell_multicell_data(first_cell, tc).width == 1; + return !first_cell->is_multicell || first_cell->width == 1; } static void -split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right, const TextCache *tc) { +split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) { *left = 0; *right = 0; for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) { Group *group = G(groups) + idx; if (group->first_cell_idx <= cursor_offset && cursor_offset < group->first_cell_idx + group->num_cells) { - if (is_group_calt_ligature(group, tc)) { + if (is_group_calt_ligature(group)) { // likely a calt ligature *left = group->first_cell_idx; *right = group->first_cell_idx + group->num_cells; } @@ -1354,8 +1353,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) { while(num < line->xnum && cell_has_text(line->cpu_cells + num)) { index_type width = 1; if ((c = line->cpu_cells + num)->is_multicell) { - MultiCellData mcd = cell_multicell_data(c, line->text_cache); - width = mcd.width * mcd.scale; + width = c->width * c->scale; } num += width; } @@ -1400,7 +1398,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde if (pua_space_ligature) collapse_pua_space_ligature(num_cells); else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER index_type left, right; - split_run_at_offset(cursor_offset, &left, &right, tc); + split_run_at_offset(cursor_offset, &left, &right); if (right > left) { if (left) { shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], false, tc); @@ -1473,24 +1471,22 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, bool center_glyph = false; bool disable_ligature_at_cursor = cursor != NULL && disable_ligature_strategy == DISABLE_LIGATURES_CURSOR && lnum == cursor->y; index_type first_cell_in_run, i; - MultiCellData mcd; for (i=0, first_cell_in_run=0; i < line->xnum; i++) { cell_font = basic_font; CPUCell *cpu_cell = line->cpu_cells + i; if (cpu_cell->is_multicell) { - mcd = cell_multicell_data(cpu_cell, line->text_cache); if (cpu_cell->x) { - i += mcd_x_limit(mcd) - cpu_cell->x - 1; + i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1; continue; } - cell_font.scale = mcd.scale; cell_font.subscale = mcd.subscale; cell_font.vertical_align = mcd.vertical_align; + cell_font.scale = cpu_cell->scale; cell_font.subscale = cpu_cell->subscale; cell_font.vertical_align = cpu_cell->vertical_align; cell_font.multicell_y = cpu_cell->y; } text_in_cell(cpu_cell, line->text_cache, lc); bool is_main_font, is_emoji_presentation; GPUCell *gpu_cell = line->gpu_cells + i; - cell_font.font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation, line->text_cache, lc); const char_type first_ch = lc->chars[0]; + cell_font.font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation, line->text_cache, lc); if ( cell_font.font_idx != MISSING_FONT && ((!is_main_font && !is_emoji_presentation && is_symbol(first_ch)) || (cell_font.font_idx != BOX_FONT && (is_private_use(first_ch))) || is_non_emoji_dingbat(first_ch)) @@ -1831,14 +1827,14 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) { PyObject *text; int bold, italic; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; - GPUCell gpu_cell = {0}; + GPUCell gpu_cell = {0}; CPUCell cpu_cell = {0}; RAII_ListOfChars(lc); lc.count = PyUnicode_GET_LENGTH(text); ensure_space_for_chars(&lc, lc.count); if (!PyUnicode_AsUCS4(text, lc.chars, lc.capacity, 1)) return NULL; if (bold) gpu_cell.attrs.bold = true; if (italic) gpu_cell.attrs.italic = true; FontGroup *fg = font_groups; - ssize_t ans = fallback_font(fg, &gpu_cell, &lc); + ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell, &lc); if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; } if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } return fg->fonts[ans].face; diff --git a/kitty/line-buf.c b/kitty/line-buf.c index c64cf7cc3..a6a2a25a0 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -599,17 +599,17 @@ restitch(Line *dest, index_type at, LineBuf *src, const index_type src_y, TrackC bool found_text = false; while (at < dest->xnum && x < src->xnum) { if (!found_text && cell_has_text(&cp[x])) found_text = true; - if (gp[x].attrs.width == 2) { - if (dest->xnum - at > 1) { - dest->cpu_cells[at] = cp[x]; - dest->gpu_cells[at++] = gp[x++]; - dest->cpu_cells[at] = cp[x]; - dest->gpu_cells[at] = gp[x]; - at++; x++; + if (cp[x].is_multicell) { + // TODO: handle multiline chars + if (dest->xnum - at > cp[x].width) { + for (index_type i = 0; i < cp[x].width; i++) { + dest->cpu_cells[at] = cp[x]; + dest->gpu_cells[at++] = gp[x++]; + } } else { dest->cpu_cells[at] = (CPUCell){0}; dest->gpu_cells[at] = (GPUCell){0}; - at++; + at = dest->xnum; } } else { dest->cpu_cells[at] = cp[x]; @@ -634,7 +634,7 @@ restitch(Line *dest, index_type at, LineBuf *src, const index_type src_y, TrackC } y++; } - dest->gpu_cells[dest->xnum - 1].attrs.next_char_was_wrapped = last_char_has_wrapped_flag; + dest->cpu_cells[dest->xnum - 1].next_char_was_wrapped = last_char_has_wrapped_flag; // remove the copied lines and cells from src if (num_of_lines_removed) { for (index_type i = 0; i < num_of_lines_removed; i++) { diff --git a/kitty/line.c b/kitty/line.c index 476aebfcc..3cab80e18 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -54,21 +54,21 @@ nonnegative_integer_as_utf32(unsigned num, ANSIBuf *output) { } static unsigned -write_multicell_ansi_prefix(MultiCellData mcd, ANSIBuf *output) { +write_multicell_ansi_prefix(const CPUCell *mcd, ANSIBuf *output) { unsigned pos = output->len; ensure_space_for(output, buf, output->buf[0], output->len + 128, capacity, 2048, false); #define w(x) output->buf[output->len++] = x w(0x1b); w(']'); for (unsigned i = 0; i < sizeof(xstr(TEXT_SIZE_CODE)) - 1; i++) w(xstr(TEXT_SIZE_CODE)[i]); w(';'); - if (mcd.width > 1) { - w('w'); w('='); nonnegative_integer_as_utf32(mcd.width, output); w(':'); + if (mcd->width > 1) { + w('w'); w('='); nonnegative_integer_as_utf32(mcd->width, output); w(':'); } - if (mcd.scale > 1) { - w('s'); w('='); nonnegative_integer_as_utf32(mcd.scale, output); w(':'); + if (mcd->scale > 1) { + w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, output); w(':'); } - if (mcd.subscale) { - w('S'); w('='); nonnegative_integer_as_utf32(mcd.subscale, output); w(':'); + if (mcd->subscale) { + w('S'); w('='); nonnegative_integer_as_utf32(mcd->subscale, output); w(':'); } if (output->buf[output->len - 1] == ':') output->len--; w(';'); @@ -78,18 +78,25 @@ write_multicell_ansi_prefix(MultiCellData mcd, ANSIBuf *output) { static unsigned text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) { - if (c->ch_is_idx) { - if (!c->is_multicell) return tc_chars_at_index_ansi(tc, c->ch_or_idx, output); + unsigned n = 0; + if (c->is_multicell) { if (c->x || c->y) return 0; - MultiCellData mcd = cell_multicell_data(c, tc); - unsigned n = write_multicell_ansi_prefix(mcd, output); - n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output) - 1; - output->buf[output->len - 1] = '\a'; - return n; + n = write_multicell_ansi_prefix(c, output); } - ensure_space_for(output, buf, output->buf[0], output->len + 1, capacity, 2048, false); - output->buf[output->len++] = c->ch_or_idx; - return 1; + if (c->ch_is_idx) { + n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output); + } else { + ensure_space_for(output, buf, output->buf[0], output->len + 2, capacity, 2048, false); + if (c->ch_or_idx) { + output->buf[output->len++] = c->ch_or_idx; + n += 1; + } + } + if (c->is_multicell) { + output->buf[output->len++] = '\a'; + n++; + } + return n; } @@ -326,7 +333,7 @@ unicode_in_range(const Line *self, const index_type start, const index_type limi global_unicode_in_range_buf.capacity = ns; global_unicode_in_range_buf.chars = np; lc.chars = global_unicode_in_range_buf.chars + n; lc.capacity = global_unicode_in_range_buf.capacity - n; } - if (lc.is_multicell && !lc.is_topleft) continue; + if (self->cpu_cells[i].is_multicell && (self->cpu_cells[i].x || self->cpu_cells[i].y)) continue; if (!lc.chars[0]) { if (skip_zero_cells) continue; lc.chars[0] = ' '; @@ -449,9 +456,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type } unsigned n = text_in_cell_ansi(self->cpu_cells + pos, self->text_cache, output); - if (output->buf[output->len - n] == 0) { - output->buf[output->len - n] = ' '; - } + if (output->buf[output->len - n] == 0) output->buf[output->len - n] = ' '; if (output->buf[output->len - n] == '\t') { unsigned num_cells_to_skip_for_tab = 0; @@ -516,7 +521,7 @@ width(Line *self, PyObject *val) { const CPUCell *c = self->cpu_cells + x; if (!cell_has_text(c)) return 0; unsigned long ans = 1; - if (c->is_multicell) ans = c->x || c->y ? 0 : cell_multicell_data(c, self->text_cache).width; + if (c->is_multicell) ans = c->x || c->y ? 0 : c->width; return PyLong_FromUnsignedLong(ans); } @@ -678,7 +683,7 @@ line_get_char(Line *self, index_type at) { if (self->cpu_cells[at].ch_is_idx) { RAII_ListOfChars(lc); text_in_cell(self->cpu_cells + at, self->text_cache, &lc); - if (lc.is_multicell && !lc.is_topleft) return 0; + if (self->cpu_cells[at].is_multicell && (self->cpu_cells[at].x || self->cpu_cells[at].y)) return 0; return lc.chars[0]; } else return self->cpu_cells[at].ch_or_idx; } @@ -839,9 +844,8 @@ apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int * MARK; } } else if (line->cpu_cells[x].is_multicell) { - MultiCellData mcd = {.val=lc.chars[lc.count]}; *match_pos += lc.count - 1; - index_type x_limit = MIN(line->xnum, mcd_x_limit(mcd)); + index_type x_limit = MIN(line->xnum, mcd_x_limit(line->cpu_cells + x)); for (; x < x_limit; x++) { MARK; } x--; } else { diff --git a/kitty/line.h b/kitty/line.h index 0239e4574..a25bd7137 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -17,7 +17,7 @@ // 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 of multiline chars +// 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 @@ -54,38 +54,29 @@ typedef struct { } GPUCell; static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell"); -typedef union MultiCellData { - struct { - char_type scale: 3; - char_type width: 3; - char_type subscale: 2; - char_type vertical_align: 3; - char_type explicitly_set: 1; - char_type : 19; - char_type msb : 1; - }; - char_type val; -} MultiCellData; -static_assert(sizeof(MultiCellData) == sizeof(char_type), "Fix the ordering of MultiCellData"); - typedef union CPUCell { struct { char_type ch_or_idx: sizeof(char_type) * 8 - 1; char_type ch_is_idx: 1; char_type hyperlink_id: sizeof(hyperlink_id_type) * 8; + char_type next_char_was_wrapped : 1; + char_type is_multicell : 1; + char_type explicitly_set: 1; char_type x : 8; char_type y : 4; - char_type is_multicell : 1; - char_type next_char_was_wrapped : 1; - char_type : 2; + char_type subscale: 2; + char_type scale: 3; + char_type width: 3; + char_type vertical_align: 3; + char_type : 21; }; struct { char_type ch_and_idx: sizeof(char_type) * 8; - char_type : sizeof(hyperlink_id_type) * 8; - char_type : 16; + char_type : 32; + char_type : 32; }; } CPUCell; -static_assert(sizeof(CPUCell) == sizeof(uint64_t), "Fix the ordering of CPUCell"); +static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell"); typedef union LineAttrs { struct { @@ -130,21 +121,12 @@ static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache } else ans = c->ch_or_idx ? 1 : 0; return ans; } -static inline MultiCellData cell_multicell_data(const CPUCell *c, const TextCache *tc) { - return (MultiCellData){.val = tc_last_char_at_index(tc, c->ch_or_idx)}; -} -static inline unsigned mcd_x_limit(MultiCellData mcd) { return mcd.scale * mcd.width; } +static inline unsigned mcd_x_limit(const CPUCell* mcd) { return mcd->scale * mcd->width; } static inline void text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { - ans->is_multicell = false; if (c->ch_is_idx) { tc_chars_at_index(tc, c->ch_or_idx, ans); - if (c->is_multicell) { - ans->is_multicell = true; - ans->is_topleft = c->x + c->y == 0; - if (ans->count > 0) { ans->count--; } - } } else { ans->count = 1; ans->chars[0] = c->ch_or_idx; @@ -153,14 +135,8 @@ text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { static inline bool text_in_cell_without_alloc(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { - ans->is_multicell = false; if (c->ch_is_idx) { if (!tc_chars_at_index_without_alloc(tc, c->ch_or_idx, ans)) return false; - if (c->is_multicell) { - ans->is_multicell = true; - ans->is_topleft = c->x + c->y == 0; - if (ans->count > 0) { ans->count--; } - } return true; } ans->count = 1; diff --git a/kitty/screen.c b/kitty/screen.c index 5a2c42790..9a562330e 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -650,10 +650,9 @@ static void nuke_multicell_char_at(Screen *self, index_type x_, index_type y_, bool replace_with_spaces) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y_, &cp, &gp); - MultiCellData mcd = cell_multicell_data(cp + x_, self->text_cache); - index_type y_max_limit = MIN(self->lines, y_ + mcd.scale); + index_type y_max_limit = MIN(self->lines, y_ + cp[x_].scale); while (cp[x_].x && x_ > 0) x_--; - index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(mcd)); + index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_])); char_type ch = replace_with_spaces ? ' ' : 0; for (index_type y = y_; y < y_max_limit; y++) { linebuf_init_cells(self->linebuf, y, &cp, &gp); @@ -674,7 +673,7 @@ nuke_multiline_char_intersecting_with(Screen *self, index_type x_start, index_ty CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y, &cp, &gp); for (index_type x = x_start; x < x_limit; x++) { - if (cp[x].is_multicell && cell_multicell_data(cp + x, self->text_cache).scale > 1) nuke_multicell_char_at(self, x, y, replace_with_spaces); + if (cp[x].is_multicell && cp[x].scale > 1) nuke_multicell_char_at(self, x, y, replace_with_spaces); } } } @@ -704,8 +703,7 @@ nuke_split_multicell_char_at_right_boundary(Screen *self, index_type x, index_ty CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, y); CPUCell *c = cp + x; if (c->is_multicell) { - MultiCellData mcd = cell_multicell_data(c, self->text_cache); - unsigned max_x = mcd_x_limit(mcd) - 1; + unsigned max_x = mcd_x_limit(c) - 1; if (c->x < max_x) { nuke_multicell_char_at(self, x, y, replace_with_spaces); } @@ -720,8 +718,7 @@ nuke_incomplete_single_line_multicell_chars_in_range( linebuf_init_cells(self->linebuf, y, &cpu_cells, &gpu_cells); for (index_type x = start; x < limit; x++) { if (cpu_cells[x].is_multicell) { - MultiCellData mcd = cell_multicell_data(cpu_cells + x, self->text_cache); - index_type mcd_x_limit = x + mcd.width - cpu_cells[x].x; + index_type mcd_x_limit = x + cpu_cells[x].width - cpu_cells[x].x; if (cpu_cells[x].x || mcd_x_limit > limit) nuke_in_line(cpu_cells, gpu_cells, x, MIN(mcd_x_limit, limit), replace_with_spaces ? ' ': 0); x = mcd_x_limit; } @@ -749,25 +746,19 @@ static bool halve_multicell_width(Screen *self, index_type x_, index_type y_) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y_, &cp, &gp); - MultiCellData mcd = cell_multicell_data(cp + x_, self->text_cache); int y_min_limit = -1; if (self->linebuf == self->main_linebuf) y_min_limit = -(self->historybuf->count + 1); - int expected_y_min_limit = ((int)y_) - mcd.scale; + int expected_y_min_limit = ((int)y_) - cp[x_].scale; if (expected_y_min_limit < y_min_limit) return false; y_min_limit = expected_y_min_limit; - MultiCellData new_mcd = mcd; new_mcd.width = mcd.width / 2; + unsigned new_width = cp[x_].width / 2; while (cp[x_].x && x_ > 0) x_--; - index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(mcd)); + index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_])); index_type half_x_limit = x_limit / 2; - int y_max_limit = MIN(self->lines, y_ + mcd.scale); - RAII_ListOfChars(lc); - text_in_cell(cp + x_, self->text_cache, &lc); - lc.chars[lc.count++] = new_mcd.val; - cell_set_chars(cp + x_, self->text_cache, &lc); - char_type idx = cp[x_].ch_or_idx; + int y_max_limit = MIN(self->lines, y_ + cp[x_].scale); for (int y = y_min_limit + 1; y < y_max_limit; y++) { Line *line = range_line_(self, y); cp = line->cpu_cells; gp = line->gpu_cells; - for (index_type x = 0; x < half_x_limit; x++) cp[x].ch_or_idx = idx; + for (index_type x = 0; x < half_x_limit; x++) cp[x].width = new_width; for (index_type x = half_x_limit; x < x_limit; x++) { cp[x] = (CPUCell){0}; clear_sprite_position(gp[x]); } @@ -823,29 +814,21 @@ static bool add_combining_char(Screen *self, char_type ch, index_type x, index_type y) { CPUCell *cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, y); CPUCell *cell = cpu_cells + x; - if (!cell_has_text(cell)) return false; // don't allow adding combining chars to a null cell + if (!cell_has_text(cell) || (cell->is_multicell && cell->y)) return false; // don't allow adding combining chars to a null cell + text_in_cell(cell, self->text_cache, self->lc); + ensure_space_for_chars(self->lc, self->lc->count + 1); + self->lc->chars[self->lc->count++] = ch; + cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc); + cell->ch_is_idx = true; if (cell->is_multicell) { + char_type ch_and_idx = cell->ch_and_idx; while (cell->x && x) cell = cpu_cells + --x; - if (cell->y) return false; // not the top left cell - text_in_cell(cell, self->text_cache, self->lc); - MultiCellData mcd = {.val=self->lc->chars[self->lc->count]}; - ensure_space_for_chars(self->lc, self->lc->count + 2); - self->lc->chars[self->lc->count++] = ch; - self->lc->chars[self->lc->count++] = mcd.val; - const char_type idx = tc_get_or_insert_chars(self->text_cache, self->lc); - self->lc->count--; - index_type x_limit = MIN(x + mcd_x_limit(mcd), self->columns); - for (index_type v = y; v < y + mcd.scale; v++) { + index_type x_limit = MIN(x + mcd_x_limit(cell), self->columns); + for (index_type v = y; v < y + cell->scale; v++) { cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, v); - for (index_type h = x; h < x_limit; h++) cpu_cells[h].ch_or_idx = idx; + for (index_type h = x; h < x_limit; h++) cpu_cells[h].ch_and_idx = ch_and_idx; linebuf_mark_line_dirty(self->linebuf, v); } - } else { - text_in_cell(cell, self->text_cache, self->lc); - ensure_space_for_chars(self->lc, self->lc->count + 1); - self->lc->chars[self->lc->count++] = ch; - cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc); - cell->ch_is_idx = true; } return true; } @@ -892,38 +875,23 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) { if (ch == VS16) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji CPUCell *cpu_cell = cp + xpos; GPUCell *gpu_cell = gp + xpos; - if (self->lc->chars[base_pos + 1] == VS16 && (!cpu_cell->is_multicell || cell_multicell_data(cpu_cell, self->text_cache).width < 2) && is_emoji_presentation_base(self->lc->chars[base_pos])) { - bool already_multicell = cpu_cell->is_multicell; - if (already_multicell) { - MultiCellData mcd = {.val=self->lc->chars[self->lc->count]}; - mcd.width *= 2; - self->lc->chars[self->lc->count++] = mcd.val; - } else { - ensure_space_for_chars(self->lc, self->lc->count + 1); - MultiCellData mcd = {.width=2, .scale=1, .msb=1}; - self->lc->chars[self->lc->count++] = mcd.val; - cpu_cell->is_multicell = true; - } - cpu_cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc); - if (!already_multicell) { - if (xpos + 1 < self->columns) { - CPUCell *second = cp + xpos + 1; - if (second->is_multicell) nuke_multicell_char_at(self, xpos, ypos, false); - zero_cells(s, second, gp + xpos + 1); - self->cursor->x++; - second->is_multicell = true; second->ch_or_idx = cpu_cell->ch_or_idx; second->ch_is_idx = true; second->x = 1; - } else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos); - } + if (self->lc->chars[base_pos + 1] == VS16 && (!cpu_cell->is_multicell || cpu_cell->width < 2) && is_emoji_presentation_base(self->lc->chars[base_pos])) { + cpu_cell->is_multicell = true; + cpu_cell->width = 2; + if (!cpu_cell->scale) cpu_cell->scale = 1; + if (xpos + 1 < self->columns) { + CPUCell *second = cp + xpos + 1; + if (second->is_multicell) nuke_multicell_char_at(self, xpos, ypos, false); + zero_cells(s, second, gp + xpos + 1); + self->cursor->x++; + *second = *cpu_cell; second->x = 1; + } else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos); } } else if (ch == VS15) { const CPUCell *cpu_cell = cp + xpos; - if (self->lc->chars[base_pos + 1] == VS15 && cpu_cell->is_multicell && is_emoji_presentation_base(self->lc->chars[base_pos])) { - MultiCellData mcd = {.val=self->lc->chars[self->lc->count]}; - if (mcd.width == 2) { - if (halve_multicell_width(self, xpos, ypos)) { - self->cursor->x -= (mcd.scale * mcd.width / 2); - } - } + if (self->lc->chars[base_pos + 1] == VS15 && cpu_cell->is_multicell && cpu_cell->width == 2 && is_emoji_presentation_base(self->lc->chars[base_pos])) { + index_type deltax = (cpu_cell->scale * cpu_cell->width) / 2; + if (halve_multicell_width(self, xpos, ypos)) self->cursor->x -= deltax; } } } @@ -1061,14 +1029,9 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_ CPUCell *fc = s->cp + self->cursor->x; zero_cells(s, fc, s->gp + self->cursor->x); if (char_width == 2) { - MultiCellData mcd = {.width = 2, .scale = 1, .msb = 1}; - RAII_ListOfChars(lc); - lc.chars[lc.count++] = ch; - lc.chars[lc.count++] = mcd.val; + *fc = (CPUCell){.ch_or_idx=ch, .is_multicell=true, .width=2, .scale=1}; CPUCell *second = fc + 1; if (second->is_multicell) nuke_multicell_char_at(self, self->cursor->x + 1, self->cursor->y, true); - cell_set_chars(fc, self->text_cache, &lc); - fc->x = 0; fc->y = 0; fc->is_multicell = true; *second = *fc; second->x = 1; s->gp[self->cursor->x + 1] = s->gp[self->cursor->x]; self->cursor->x += 2; @@ -1155,16 +1118,17 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const width = wcswidth_string(self->lc->chars); } if (!width) return; - MultiCellData mcd = { - .width=MIN(width, 15), .scale=MAX(1, MIN(cmd->scale, 15)), .subscale=MIN(cmd->subscale, 3), - .explicitly_set=1, .msb=1, .vertical_align=MIN(cmd->vertical_align, 7u) + CPUCell mcd = { + .width=MIN(width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale=MIN(cmd->subscale, 3u), + .explicitly_set=1u, .vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true }; - self->lc->chars[self->lc->count++] = mcd.val; width = mcd.width * mcd.scale; index_type height = mcd.scale; index_type max_height = self->margin_bottom - self->margin_top + 1; if (width > self->columns || height > max_height) return; PREPARE_FOR_DRAW_TEXT; + mcd.hyperlink_id = s.cc.hyperlink_id; + cell_set_chars(&mcd, self->text_cache, self->lc); if (self->columns < self->cursor->x + width) { if (self->modes.mDECAWM) { continue_to_next_line(self); @@ -1187,16 +1151,13 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const if (self->modes.mIRM) insert_characters(self, self->cursor->x, width, y, true); } } - CPUCell c = s.cc; - c.ch_is_idx = true; c.ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc); - c.is_multicell = true; nuke_multicell_char_intersecting_with(self, self->cursor->x, self->cursor->x + width, self->cursor->y, self->cursor->y + height, true); for (index_type y = self->cursor->y; y < self->cursor->y + height; y++) { linebuf_init_cells(self->linebuf, y, &s.cp, &s.gp); linebuf_mark_line_dirty(self->linebuf, y); - c.x = 0; c.y = y - self->cursor->y; - for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, c.x++) { - s.cp[x] = c; s.gp[x] = s.g; + mcd.x = 0; mcd.y = y - self->cursor->y; + for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, mcd.x++) { + s.cp[x] = mcd; s.gp[x] = s.g; } } self->cursor->x += width; @@ -2172,8 +2133,7 @@ screen_fake_move_cursor_to_position(Screen *self, index_type start_x, index_type } found_non_empty_cell = true; if (c->is_multicell) { - MultiCellData mcd = cell_multicell_data(c, self->text_cache); - x += mcd_x_limit(mcd); + x += mcd_x_limit(c); } else x++; count += 1; // zsh requires a single arrow press to move past dualwidth chars } @@ -2369,8 +2329,7 @@ screen_insert_lines(Screen *self, unsigned int count) { cells = linebuf_cpu_cells_for_line(self->linebuf, bottom); for (index_type x = 0; x < self->columns; x++) { if (cells[x].is_multicell) { - MultiCellData mcd = cell_multicell_data(cells + x, self->text_cache); - index_type y_limit = mcd.scale; + index_type y_limit = cells[x].scale; if (cells[x].y + 1u < y_limit) { index_type orig = self->lines; self->lines = bottom + 1; @@ -3753,8 +3712,7 @@ screen_draw_overlay_line(Screen *self) { // When the last character is a split multicell, make sure the next character is visible. CPUCell *c = self->linebuf->line->cpu_cells + len - 1; if (c->is_multicell) { - MultiCellData mcd = cell_multicell_data(c, self->text_cache); - if (c->x < mcd_x_limit(mcd) - 1) { + if (c->x < mcd_x_limit(c) - 1) { do { c->is_multicell = false; c->ch_is_idx = false; c->ch_or_idx = ' '; if (!c->x) break; @@ -4819,7 +4777,7 @@ current_char_width(Screen *self, PyObject *a UNUSED) { const CPUCell *c = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y) + self->cursor->x; if (c->is_multicell) { if (c->x || c->y) ans = 0; - else ans = cell_multicell_data(c, self->text_cache).width; + else ans = c->width; } } return PyLong_FromUnsignedLong(ans); @@ -5173,16 +5131,15 @@ test_parse_written_data(Screen *screen, PyObject *args) { } static PyObject* -multicell_data_as_dict(MultiCellData mcd) { - if (!mcd.msb) { PyErr_SetString(PyExc_RuntimeError, "mcd does not have its msb set"); return NULL; } +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, "explicitly_set", mcd.explicitly_set ? Py_True : Py_False, "vertical_align", mcd.vertical_align); } static PyObject* cpu_cell_as_dict(CPUCell *c, TextCache *tc, ListOfChars *lc, HYPERLINK_POOL_HANDLE h) { text_in_cell(c, tc, lc); - RAII_PyObject(mcd, lc->is_multicell ? multicell_data_as_dict((MultiCellData){.val=lc->chars[lc->count]}) : Py_NewRef(Py_None)); - if ((lc->is_multicell && !lc->is_topleft) || (lc->count == 1 && lc->chars[0] == 0)) lc->count = 0; + RAII_PyObject(mcd, c->is_multicell ? multicell_data_as_dict(*c) : Py_NewRef(Py_None)); + if ((c->is_multicell && (c->x + c->y)) || (lc->count == 1 && lc->chars[0] == 0)) lc->count = 0; RAII_PyObject(text, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc->chars, lc->count)); const char *url = c->hyperlink_id ? get_hyperlink_for_id(h, c->hyperlink_id, false) : NULL; RAII_PyObject(hyperlink, url ? PyUnicode_FromString(url) : Py_NewRef(Py_None)); diff --git a/kitty/shaders.c b/kitty/shaders.c index f474752b5..aefc150ac 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -378,8 +378,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c line_for_cursor && (cursor_cell = line_for_cursor->cpu_cells + cursor->x)->is_multicell && cursor_cell->x == 0 ) { - MultiCellData mcd = cell_multicell_data(cursor_cell, screen->text_cache); - rd->cursor_w = mcd.width * mcd.scale; + rd->cursor_w = mcd_x_limit(cursor_cell); } rd->xnum = screen->columns; rd->ynum = screen->lines; diff --git a/kitty/text-cache.c b/kitty/text-cache.c index 6354e0c00..9339825b0 100644 --- a/kitty/text-cache.c +++ b/kitty/text-cache.c @@ -116,7 +116,8 @@ tc_chars_at_index_ansi(const TextCache *self, char_type idx, ANSIBuf *output) { unsigned count = 0; if (self->array.count > idx) { count = self->array.items[idx].count; - ensure_space_for(output, buf, output->buf[0], output->len + count, capacity, 2048, false); + // we ensure space for one extra byte for ANSI escape code trailer if multicell + ensure_space_for(output, buf, output->buf[0], output->len + count + 1, capacity, 2048, false); memcpy(output->buf + output->len, self->array.items[idx].chars, sizeof(output->buf[0]) * count); output->len += count; } diff --git a/kitty/text-cache.h b/kitty/text-cache.h index f4e245299..0e8224315 100644 --- a/kitty/text-cache.h +++ b/kitty/text-cache.h @@ -12,7 +12,6 @@ typedef struct ListOfChars { char_type *chars; size_t count, capacity; - bool is_multicell, is_topleft; } ListOfChars; #define LIST_OF_CHARS_STACK_SIZE 4 diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 391e78c56..c11f7400a 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -302,7 +302,7 @@ def test_multicell(self: TestMulticell) -> None: assert_line('ab_c\0\0', -2) assert_line('\0__\0\0\0', -1) self.ae(s.historybuf.line(1).as_ansi(), f'a\x1b]{TEXT_SIZE_CODE};s=2;b\x07c') - self.ae(s.historybuf.line(0).as_ansi(), ' ') + self.ae(s.historybuf.line(0).as_ansi(), '') # Insert lines s.reset()