Improve performance of processing wide chars

Store multi cell data in the CPUCell rather than in TextCache.
This sends the CPUCell size back to 12 but in benchmarks ASCII
performance is untouched and Unicode performace goes back to what it was
before multicell
This commit is contained in:
Kovid Goyal 2024-11-28 21:48:54 +05:30
parent 21871dcd58
commit 35946f9386
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
9 changed files with 127 additions and 195 deletions

View file

@ -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;

View file

@ -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++) {

View file

@ -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 {

View file

@ -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;

View file

@ -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));

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -302,7 +302,7 @@ def big_a(x, y):
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()