Initial port of code to use TextCache

This commit is contained in:
Kovid Goyal 2024-10-25 13:02:36 +05:30
parent f6347ee1e2
commit cabd6c0589
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
14 changed files with 457 additions and 344 deletions

View file

@ -59,6 +59,7 @@ static inline PyObject* Py_XNewRef(PyObject *o) { Py_XINCREF(o); return o; }
typedef unsigned long long id_type;
typedef uint32_t char_type;
#define MAX_CHAR_TYPE_VALUE UINT32_MAX
typedef uint32_t color_type;
typedef uint16_t hyperlink_id_type;
typedef int key_type;
@ -119,7 +120,6 @@ typedef struct ImageAnchorPosition {
#define BLANK_CHAR 0
#define COL_MASK 0xFFFFFFFF
#define DECORATION_FG_CODE 58
#define CHAR_IS_BLANK(ch) ((ch) == 32 || (ch) == 0)
// PUA character used as an image placeholder.
#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE
@ -197,14 +197,6 @@ typedef struct {
uint32_t left, top, right, bottom;
} Region;
typedef union CharOrIndex {
struct {
char_type ch_is_index: 1;
char_type ch: sizeof(char_type) - 1;
};
char_type val;
} CharOrIndex;
typedef enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind;
typedef struct {int x;} *HYPERLINK_POOL_HANDLE;
typedef struct {

View file

@ -494,7 +494,7 @@ end:
static bool face_has_codepoint(const void *face, char_type cp) { return glyph_id_for_codepoint(face, cp) > 0; }
PyObject*
create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) {
create_fallback_face(PyObject UNUSED *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) {
ensure_initialized();
PyObject *ans = NULL;
RAII_PyObject(d, NULL);
@ -505,7 +505,7 @@ create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool
if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); }
size_t num = cell_as_unicode_for_fallback(cell, char_buf);
size_t num = cell_as_unicode_for_fallback(lc, char_buf);
add_charset(pat, num);
d = _fc_match(pat);
face_from_descriptor:
@ -515,22 +515,22 @@ face_from_descriptor:
while ((q = iter_fallback_faces(fg, &idx))) {
if (face_equals_descriptor(q, d)) {
ans = PyLong_FromSsize_t(idx);
if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, cell, false);
if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, false, lc);
goto end;
}
}
ans = face_from_descriptor(d, fg);
if (!glyph_found && ans) glyph_found = has_cell_text(face_has_codepoint, ans, cell, false);
if (!glyph_found && ans) glyph_found = has_cell_text(face_has_codepoint, ans, false, lc);
}
end:
Py_CLEAR(d);
if (pat != NULL) { FcPatternDestroy(pat); pat = NULL; }
if (!glyph_found && !PyErr_Occurred()) {
if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, cell, false)) {
if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, false, lc)) {
Py_CLEAR(ans);
d = builtin_nerd_font.descriptor; Py_INCREF(d); glyph_found = true; goto face_from_descriptor;
} else {
if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, cell, true);
if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, true, lc);
Py_CLEAR(ans); ans = Py_None; Py_INCREF(ans);
}
}

View file

@ -8,6 +8,7 @@
#include "fonts.h"
#include "pyport.h"
#include "charsets.h"
#include "state.h"
#include "emoji.h"
#include "unicode-data.h"
@ -465,36 +466,38 @@ face_has_codepoint(const void* face, char_type cp) {
}
static bool
has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) {
return gpu_cell->attrs.width == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15;
has_emoji_presentation(const GPUCell *gpu_cell, const ListOfChars *lc) {
if (gpu_cell->attrs.width != 2) return false;
return lc->count > 1 && is_emoji(lc->chars[0]) && lc->chars[1] != VS15;
}
bool
has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, const CPUCell *cell, bool do_debug) {
if (!has_codepoint(face, cell->ch)) goto not_found;
char_type combining_chars[arraysz(cell->cc_idx)];
has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc) {
if (!has_codepoint(face, lc->chars[0])) goto not_found;
unsigned num_cc = 0;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
const char_type ccp = codepoint_for_mark(cell->cc_idx[i]);
if (!is_non_rendered_char(ccp)) combining_chars[num_cc++] = ccp;
for (unsigned i = 1; i < lc->count; i++) {
if (!is_non_rendered_char(lc->chars[i])) lc->chars[i] = 0;
else num_cc++;
}
if (num_cc == 0) return true;
if (num_cc == 1) {
if (has_codepoint(face, combining_chars[0])) return true;
char_type cc = 0;
for (unsigned i = 1; i < lc->count && cc; i++) cc = lc->chars[i];
if (has_codepoint(face, cc)) return true;
char_type ch = 0;
if (hb_unicode_compose(hb_unicode_funcs_get_default(), cell->ch, combining_chars[0], &ch) && face_has_codepoint(face, ch)) return true;
if (hb_unicode_compose(hb_unicode_funcs_get_default(), lc->chars[0], cc, &ch) && face_has_codepoint(face, ch)) return true;
goto not_found;
}
for (unsigned i = 0; i < num_cc; i++) {
if (!has_codepoint(face, combining_chars[i])) goto not_found;
for (unsigned i = 1; i < lc->count; i++) {
if (lc->chars[i] && !has_codepoint(face, lc->chars[i])) goto not_found;
}
return true;
not_found:
if (do_debug) {
debug("The font chosen by the OS for the text: ");
debug("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
debug("U+%x ", codepoint_for_mark(cell->cc_idx[i]));
debug("U+%x ", lc->chars[0]);
for (unsigned i = 1; i < lc->count; i++) {
if (lc->chars[i]) debug("U+%x ", lc->chars[i]);
}
debug("is "); PyObject_Print((PyObject*)face, stderr, 0);
debug(" but it does not actually contain glyphs for that text\n");
@ -503,11 +506,9 @@ not_found:
}
static void
output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face) {
debug("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
debug("U+%x ", codepoint_for_mark(cell->cc_idx[i]));
}
output_cell_fallback_data(const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, PyObject *face) {
debug("U+%x ", lc->chars[0]);
for (unsigned i = 1; i < lc->count; i++) debug("U+%x ", lc->chars[i]);
if (bold) debug("bold ");
if (italic) debug("italic ");
if (emoji_presentation) debug("emoji_presentation ");
@ -527,7 +528,7 @@ iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx) {
}
static ssize_t
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
load_fallback_font(FontGroup *fg, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation) {
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
ssize_t f;
@ -535,10 +536,10 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em
else f = italic ? fg->italic_font_idx : fg->medium_font_idx;
if (f < 0) f = fg->medium_font_idx;
PyObject *face = create_fallback_face(fg->fonts[f].face, cell, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg);
PyObject *face = create_fallback_face(fg->fonts[f].face, lc, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg);
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face);
if (global_state.debug_font_fallback) output_cell_fallback_data(lc, bold, italic, emoji_presentation, face);
if (PyLong_Check(face)) { ssize_t ans = fg->first_fallback_font_idx + PyLong_AsSsize_t(face); Py_DECREF(face); return ans; }
set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg);
@ -552,18 +553,30 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em
return ans;
}
size_t
chars_as_utf8(const ListOfChars *lc, char *buf, char_type zero_char) {
size_t n;
if (lc->count == 1) n = encode_utf8(lc->chars[0] ? lc->chars[0] : zero_char, buf);
else {
n = encode_utf8(lc->chars[0], buf);
if (lc->chars[0] != '\t') for (unsigned i = 1; i < lc->count; i++) n += encode_utf8(lc->chars[i], buf + n);
}
buf[n] = 0;
return n;
}
static ssize_t
fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
fallback_font(FontGroup *fg, 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(cpu_cell, gpu_cell);
bool emoji_presentation = has_emoji_presentation(gpu_cell, lc);
char style = emoji_presentation ? 'a' : 'A';
if (bold) style += italic ? 3 : 2; else style += italic ? 1 : 0;
char cell_text[8 + arraysz(cpu_cell->cc_idx) * 4] = {style};
const size_t cell_text_len = 1 + cell_as_utf8(cpu_cell, true, cell_text + 1, ' ');
char cell_text[4 * 32] = {style};
const size_t cell_text_len = 1 + chars_as_utf8(lc, cell_text + 1, ' ');
fallback_font_map_t_itr fi = vt_get(&fg->fallback_font_map, cell_text);
if (!vt_is_end(fi)) return fi.data->val;
ssize_t idx = load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
ssize_t idx = load_fallback_font(fg, lc, bold, italic, emoji_presentation);
const char *alloced_key = strndup(cell_text, cell_text_len);
if (alloced_key) vt_insert(&fg->fallback_font_map, alloced_key, idx);
return idx;
@ -587,12 +600,13 @@ in_symbol_maps(FontGroup *fg, char_type ch) {
// - BOX_FONT
// - an index in the fonts list
static ssize_t
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_main_font, bool *is_emoji_presentation) {
font_for_cell(FontGroup *fg, const CPUCell *cpu_cell, const GPUCell *gpu_cell, bool *is_main_font, bool *is_emoji_presentation, TextCache *tc, ListOfChars *lc) {
*is_main_font = false;
*is_emoji_presentation = false;
text_in_cell(cpu_cell, tc, lc);
START_ALLOW_CASE_RANGE
ssize_t ans;
switch(cpu_cell->ch) {
switch(lc->chars[0]) {
case 0:
case ' ':
case 0x2002: // en-space
@ -611,8 +625,8 @@ START_ALLOW_CASE_RANGE
case 0xf5d0 ... 0xf60d: // branch drawing characters
return BOX_FONT;
default:
*is_emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
ans = in_symbol_maps(fg, cpu_cell->ch);
*is_emoji_presentation = has_emoji_presentation(gpu_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)) {
case 0:
@ -625,8 +639,8 @@ START_ALLOW_CASE_RANGE
ans = fg->bi_font_idx; break;
}
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, cpu_cell, false)) { *is_main_font = true; return ans; }
return fallback_font(fg, cpu_cell, gpu_cell);
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);
}
END_ALLOW_CASE_RANGE
}
@ -674,9 +688,10 @@ render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Regi
}
static void
render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) {
int error = 0;
glyph_index glyph = box_glyph_id(cpu_cell->ch);
char_type ch = cell_first_char(cpu_cell, tc);
glyph_index glyph = box_glyph_id(ch);
SpritePosition *sp = sprite_position_for(fg, &fg->fonts[BOX_FONT], &glyph, 1, 0, 1, &error);
if (sp == NULL) {
sprite_map_set_error(error); PyErr_Print();
@ -687,7 +702,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
if (sp->rendered) return;
sp->rendered = true;
sp->colored = false;
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cpu_cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", (unsigned int)ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
if (ret == NULL) { PyErr_Print(); return; }
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
ensure_canvas_can_fit(fg, 1);
@ -698,18 +713,19 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
}
static void
load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) {
load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, const TextCache *tc) {
index_type num;
hb_buffer_clear_contents(harfbuzz_buffer);
RAII_ListOfChars(lc);
while (num_cells) {
uint16_t prev_width = 0;
for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cpu_cell->cc_idx); first_cpu_cell++, first_gpu_cell++, num_cells--) {
for (num = 0; num_cells; first_cpu_cell++, first_gpu_cell++, num_cells--) {
if (prev_width == 2) { prev_width = 0; continue; }
shape_buffer[num++] = first_cpu_cell->ch;
text_in_cell(first_cpu_cell, tc, &lc);
if (lc.count + num > arraysz(shape_buffer)) break;
memcpy(shape_buffer + num, lc.chars, lc.count * sizeof(shape_buffer[0]));
num += lc.count;
prev_width = first_gpu_cell->attrs.width;
for (unsigned i = 0; i < arraysz(first_cpu_cell->cc_idx) && first_cpu_cell->cc_idx[i]; i++) {
shape_buffer[num++] = codepoint_for_mark(first_cpu_cell->cc_idx[i]);
}
}
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
}
@ -740,7 +756,7 @@ typedef struct GlyphRenderScratch {
static GlyphRenderScratch global_glyph_render_scratch = {0};
static void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph) {
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph, const TextCache *tc) {
#define sp global_glyph_render_scratch.sprite_positions
int error = 0;
bool all_rendered = true;
@ -761,7 +777,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
}
ensure_canvas_can_fit(fg, num_cells + 1);
bool was_colored = gpu_cells->attrs.width == 2 && is_emoji(cpu_cells->ch);
bool was_colored = gpu_cells->attrs.width == 2 && is_emoji(cell_first_char(cpu_cells, tc));
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();
@ -804,28 +820,23 @@ typedef struct {
static GroupState group_state = {0};
static unsigned int
num_codepoints_in_cell(CPUCell *cell) {
unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans;
}
static void
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature) {
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature, const TextCache *tc) {
if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
if (!group_state.groups) fatal("Out of memory");
}
RAII_ListOfChars(lc);
text_in_cell(first_cpu_cell, tc, &lc);
group_state.previous_cluster = UINT32_MAX;
group_state.prev_was_special = false;
group_state.prev_was_empty = false;
group_state.current_cell_data.cpu_cell = first_cpu_cell;
group_state.current_cell_data.gpu_cell = first_gpu_cell;
group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cpu_cell);
group_state.current_cell_data.num_codepoints = MAX(1, lc.count);
group_state.current_cell_data.codepoints_consumed = 0;
group_state.current_cell_data.current_codepoint = first_cpu_cell->ch;
group_state.current_cell_data.current_codepoint = lc.chars[0];
zero_at_ptr_count(group_state.groups, group_state.groups_capacity);
group_state.group_idx = 0;
group_state.glyph_idx = 0;
@ -835,7 +846,7 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb
group_state.first_gpu_cell = first_gpu_cell;
group_state.last_cpu_cell = first_cpu_cell + (num_cells ? num_cells - 1 : 0);
group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells);
load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells, tc);
size_t num_features = fobj->num_ffs_hb_features;
if (num_features && !disable_ligature) num_features--; // the last feature is always -calt
@ -877,7 +888,7 @@ is_empty_glyph(glyph_index glyph_id, Font *font) {
}
static unsigned int
check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell, const TextCache *tc) {
cell_data->codepoints_consumed++;
if (cell_data->codepoints_consumed >= cell_data->num_codepoints) {
uint16_t width = cell_data->gpu_cell->attrs.width;
@ -885,20 +896,22 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
cell_data->gpu_cell += MAX(1, width);
cell_data->codepoints_consumed = 0;
if (cell_data->cpu_cell <= last_cpu_cell) {
cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cpu_cell);
cell_data->current_codepoint = cell_data->cpu_cell->ch;
cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cpu_cell, tc);
cell_data->current_codepoint = cell_first_char(cell_data->cpu_cell, tc);
} else cell_data->current_codepoint = 0;
return width;
} else {
switch(cell_data->codepoints_consumed) {
case 0:
cell_data->current_codepoint = cell_data->cpu_cell->ch;
cell_data->current_codepoint = cell_first_char(cell_data->cpu_cell, tc);
break;
default: {
index_type mark = cell_data->cpu_cell->cc_idx[cell_data->codepoints_consumed - 1];
RAII_ListOfChars(lc);
text_in_cell(cell_data->cpu_cell, tc, &lc);
char_type cc = lc.chars[cell_data->codepoints_consumed];
// VS15/16 cause rendering to break, as they get marked as
// special glyphs, so map to 0, to avoid that
cell_data->current_codepoint = (mark == VS15 || mark == VS16) ? 0 : codepoint_for_mark(mark);
cell_data->current_codepoint = (cc == VS15 || cc == VS16) ? 0 : cc;
break;
}
}
@ -927,11 +940,12 @@ ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
#define G(x) (group_state.x)
static void
detect_spacer_strategy(hb_font_t *hbf, Font *font) {
CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}};
detect_spacer_strategy(hb_font_t *hbf, Font *font, const TextCache *tc) {
CPUCell cpu_cells[3] = {0};
for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '=');
const CellAttrs w1 = {.width=1};
GPUCell gpu_cells[3] = {{.attrs = w1}, {.attrs = w1}, {.attrs = w1}};
shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false);
shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc);
font->spacer_strategy = SPACERS_BEFORE;
if (G(num_glyphs) > 1) {
glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint;
@ -939,7 +953,7 @@ detect_spacer_strategy(hb_font_t *hbf, Font *font) {
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
if (is_empty) font->spacer_strategy = SPACERS_AFTER;
}
shape(cpu_cells, gpu_cells, 2, hbf, font, false);
shape(cpu_cells, gpu_cells, 2, hbf, font, false, tc);
if (G(num_glyphs)) {
char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0;
for (unsigned i = 0; i < G(num_glyphs); i++) {
@ -956,8 +970,8 @@ detect_spacer_strategy(hb_font_t *hbf, Font *font) {
// If spacer_strategy is still default, check ### glyph to confirm strategy
// https://github.com/kovidgoyal/kitty/issues/4721
if (font->spacer_strategy == SPACERS_BEFORE) {
cpu_cells[0].ch = '#'; cpu_cells[1].ch = '#'; cpu_cells[2].ch = '#';
shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false);
for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '#');
shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc);
if (G(num_glyphs) > 1) {
glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint;
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
@ -1000,7 +1014,7 @@ static LigatureType *ligature_types = NULL;
static size_t ligature_types_sz = 0;
static void
group_iosevka(Font *font, hb_font_t *hbf) {
group_iosevka(Font *font, hb_font_t *hbf, const TextCache *tc) {
// Group as per algorithm discussed in: https://github.com/be5invis/Iosevka/issues/1007
if (ligature_types_sz <= G(num_glyphs)) {
ligature_types_sz = G(num_glyphs) + 16;
@ -1054,7 +1068,7 @@ group_iosevka(Font *font, hb_font_t *hbf) {
} else {
unsigned int num_cells_consumed = 0;
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell));
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc);
G(cell_idx) += w;
num_cells_consumed += w;
num_codepoints_used_by_glyph--;
@ -1067,7 +1081,7 @@ group_iosevka(Font *font, hb_font_t *hbf) {
}
static void
group_normal(Font *font, hb_font_t *hbf) {
group_normal(Font *font, hb_font_t *hbf, const TextCache *tc) {
/* Now distribute the glyphs into groups of cells
* Considerations to keep in mind:
* Group sizes should be as small as possible for best performance
@ -1154,7 +1168,7 @@ group_normal(Font *font, hb_font_t *hbf) {
} else {
unsigned int num_cells_consumed = 0;
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell));
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc);
G(cell_idx) += w;
num_cells_consumed += w;
num_codepoints_used_by_glyph--;
@ -1177,12 +1191,12 @@ group_normal(Font *font, hb_font_t *hbf) {
static void
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature) {
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature, const TextCache *tc) {
hb_font_t *hbf = harfbuzz_font_for_face(font->face);
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font);
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature);
if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf);
else group_normal(font, hbf);
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc);
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc);
if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf, tc);
else group_normal(font, hbf, tc);
#if 0
static char dbuf[1024];
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
@ -1228,7 +1242,7 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ
static void
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
render_groups(FontGroup *fg, Font *font, bool center_glyph, const TextCache *tc) {
unsigned idx = 0;
while (idx <= G(group_idx)) {
Group *group = G(groups) + idx;
@ -1244,7 +1258,7 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) {
global_glyph_render_scratch.sz = sz;
}
for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph);
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc);
}
idx++;
}
@ -1257,7 +1271,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
int index = 0;
if(!PyArg_ParseTuple(args, "O!|zi", &Line_Type, &line, &path, &index)) return NULL;
index_type num = 0;
while(num < line->xnum && line->cpu_cells[num].ch) num += line->gpu_cells[num].attrs.width;
while(num < line->xnum && cell_has_text(line->cpu_cells + num)) num += line->gpu_cells[num].attrs.width;
PyObject *face = NULL;
Font *font;
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; }
@ -1271,7 +1285,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
FontGroup *fg = font_groups;
font = fg->fonts + fg->medium_font_idx;
}
shape_run(line->cpu_cells, line->gpu_cells, num, font, false);
shape_run(line->cpu_cells, line->gpu_cells, num, font, false, line->text_cache);
PyObject *ans = PyList_New(0);
unsigned int idx = 0;
@ -1292,35 +1306,35 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
#undef G
static void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy) {
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc) {
switch(font_idx) {
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS);
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc);
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);
if (right > left) {
if (left) {
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[font_idx], false);
render_groups(fg, &fg->fonts[font_idx], center_glyph);
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[font_idx], false, tc);
render_groups(fg, &fg->fonts[font_idx], center_glyph, tc);
}
shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[font_idx], true);
render_groups(fg, &fg->fonts[font_idx], center_glyph);
shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[font_idx], true, tc);
render_groups(fg, &fg->fonts[font_idx], center_glyph, tc);
if (right < num_cells) {
shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[font_idx], false);
render_groups(fg, &fg->fonts[font_idx], center_glyph);
shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[font_idx], false, tc);
render_groups(fg, &fg->fonts[font_idx], center_glyph, tc);
}
break;
}
}
render_groups(fg, &fg->fonts[font_idx], center_glyph);
render_groups(fg, &fg->fonts[font_idx], center_glyph, tc);
break;
case BLANK_FONT:
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
break;
case BOX_FONT:
while(num_cells--) { render_box_cell(fg, first_cpu_cell, first_gpu_cell); first_cpu_cell++; first_gpu_cell++; }
while(num_cells--) { render_box_cell(fg, first_cpu_cell, first_gpu_cell, tc); first_cpu_cell++; first_gpu_cell++; }
break;
case MISSING_FONT:
while(num_cells--) { set_sprite(first_gpu_cell, MISSING_GLYPH, 0, 0); first_cpu_cell++; first_gpu_cell++; }
@ -1352,11 +1366,11 @@ cell_cap_for_codepoint(const char_type cp) {
void
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy, ListOfChars *lc) {
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
int cursor_offset = -1; \
if (disable_ligature_at_cursor && first_cell_in_run <= cursor->x && cursor->x <= i) cursor_offset = cursor->x - first_cell_in_run; \
render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph, cursor_offset, disable_ligature_strategy); \
render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph, cursor_offset, disable_ligature_strategy, line->text_cache); \
}
FontGroup *fg = (FontGroup*)fg_;
ssize_t run_font_idx = NO_FONT;
@ -1368,30 +1382,32 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
if (prev_width == 2) { prev_width = 0; continue; }
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
text_in_cell(cpu_cell, line->text_cache, lc);
bool is_main_font, is_emoji_presentation;
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation);
ssize_t cell_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];
if (
cell_font_idx != MISSING_FONT &&
((!is_main_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && (is_private_use(cpu_cell->ch))) || is_non_emoji_dingbat(cpu_cell->ch))
((!is_main_font && !is_emoji_presentation && is_symbol(first_ch)) || (cell_font_idx != BOX_FONT && (is_private_use(first_ch))) || is_non_emoji_dingbat(first_ch))
) {
unsigned int desired_cells = 1;
if (cell_font_idx > 0) {
Font *font = (fg->fonts + cell_font_idx);
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
glyph_index glyph_id = glyph_id_for_codepoint(font->face, first_ch);
int width = get_glyph_width(font->face, glyph_id);
desired_cells = (unsigned int)ceilf((float)width / fg->cell_width);
}
desired_cells = MIN(desired_cells, cell_cap_for_codepoint(cpu_cell->ch));
desired_cells = MIN(desired_cells, cell_cap_for_codepoint(first_ch));
unsigned int num_spaces = 0;
while (
i + num_spaces + 1 < line->xnum
&& (line->cpu_cells[i+num_spaces+1].ch == ' ' || line->cpu_cells[i+num_spaces+1].ch == 0x2002) // space or en-space
&& (cell_is_char(line->cpu_cells + i + num_spaces + 1, ' ') || cell_is_char(line->cpu_cells + i + num_spaces + 1, 0x2002)) // space or en-space
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
&& num_spaces + 1 < desired_cells
) {
) {
num_spaces++;
// We have a private use char followed by space(s), render it as a multi-cell ligature.
GPUCell *space_cell = line->gpu_cells + i + num_spaces;
@ -1406,7 +1422,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
center_glyph = true;
RENDER
center_glyph = false;
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph, -1, disable_ligature_strategy);
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph, -1, disable_ligature_strategy, line->text_cache);
run_font_idx = NO_FONT;
first_cell_in_run = i + num_spaces + 1;
prev_width = line->gpu_cells[i+num_spaces].attrs.width;
@ -1622,7 +1638,8 @@ test_render_line(PyObject UNUSED *self, PyObject *args) {
PyObject *line;
if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL;
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; }
render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line, 0, NULL, DISABLE_LIGATURES_NEVER);
RAII_ListOfChars(lc);
render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line, 0, NULL, DISABLE_LIGATURES_NEVER, &lc);
Py_RETURN_NONE;
}
@ -1710,16 +1727,13 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
PyObject *text;
int bold, italic;
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
CPUCell cpu_cell = {0};
GPUCell gpu_cell = {0};
static Py_UCS4 char_buf[2 + arraysz(cpu_cell.cc_idx)];
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
cpu_cell.ch = char_buf[0];
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cpu_cell.cc_idx); i++) cpu_cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
RAII_ListOfChars(lc); ensure_space_for_chars(&lc, PyUnicode_GetLength(text));
if (!PyUnicode_AsUCS4(text, lc.chars, lc.count, 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, &cpu_cell, &gpu_cell);
ssize_t ans = fallback_font(fg, &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

@ -41,7 +41,7 @@ hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* create_fallback_face(PyObject *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* specialize_font_descriptor(PyObject *base_descriptor, double, double, double);
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE);
@ -51,7 +51,7 @@ const char* postscript_name_for_face(const PyObject*);
void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z);
void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb);
void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature);
void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature, ListOfChars*);
void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
typedef void (*free_extra_data_func)(void*);
StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline);
@ -74,7 +74,7 @@ bool create_features_for_face(const char* psname, PyObject *features, FontFeatur
PyObject*
font_features_as_dict(const FontFeatures *font_features);
bool
has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, const CPUCell *cell, bool do_debug);
has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc);
static inline void
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {

View file

@ -602,7 +602,7 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
first--;
CPUCell *cells = cpu_lineptr(self, self->line_map[first]);
for(i = 0; i < self->xnum; i++) {
if ((cells[i].ch) != BLANK_CHAR) { is_empty = false; break; }
if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; }
}
} while(is_empty && first > 0);

View file

@ -12,6 +12,7 @@
#include "wcwidth-std.h"
extern PyTypeObject Cursor_Type;
static_assert(sizeof(char_type) == sizeof(Py_UCS4), "Need to perform conversion to Py_UCS4");
static void
dealloc(Line* self) {
@ -23,24 +24,35 @@ dealloc(Line* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
static unsigned
text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) {
if (c->ch_is_idx) return tc_chars_at_index_ansi(tc, c->ch_or_idx, 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;
}
unsigned int
line_length(Line *self) {
index_type last = self->xnum - 1;
for (index_type i = 0; i < self->xnum; i++) {
if ((self->cpu_cells[last - i].ch) != BLANK_CHAR) return self->xnum - i;
if (!cell_is_char(self->cpu_cells + last - i, BLANK_CHAR)) return self->xnum - i;
}
return 0;
}
PyObject*
cell_text(CPUCell *cell) {
PyObject *ans;
unsigned num = 1;
static Py_UCS4 buf[arraysz(cell->cc_idx) + 1];
buf[0] = cell->ch;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[num++] = codepoint_for_mark(cell->cc_idx[i]);
ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
return ans;
cell_text(CPUCell *cell, TextCache *tc) {
if (cell->ch_is_idx) {
RAII_ListOfChars(lc);
tc_chars_at_index(tc, cell->ch_or_idx, &lc);
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &lc.chars, lc.count);
return ans;
} else {
Py_UCS4 ch = cell->ch_or_idx;
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &ch, 1);
}
}
// URL detection {{{
@ -50,8 +62,21 @@ is_hostname_char(char_type ch) {
return ch == '[' || ch == ']' || is_url_char(ch);
}
static bool
is_hostname_lc(const ListOfChars *lc) {
for (size_t i = 0; i < lc->count; i++) if (!is_hostname_char(lc->chars[i])) return false;
return true;
}
static bool
is_url_lc(const ListOfChars *lc) {
for (size_t i = 0; i < lc->count; i++) if (!is_url_char(lc->chars[i])) return false;
return true;
}
static index_type
find_colon_slash(Line *self, index_type x, index_type limit) {
find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc) {
// Find :// at or before x
index_type pos = MIN(x, self->xnum - 1);
enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH};
@ -59,25 +84,26 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
limit = MAX(2u, limit);
if (pos < limit) return 0;
do {
char_type ch = self->cpu_cells[pos].ch;
if (!is_hostname_char(ch)) return false;
const CPUCell *c = self->cpu_cells + pos;
text_in_cell(c, self->text_cache, lc);
if (!is_hostname_lc(lc)) return false;
if (pos == x) {
if (ch == ':') {
if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH;
} else if (ch == '/') {
if (pos + 1 < self->xnum && self->cpu_cells[pos+1].ch == '/') state = FIRST_SLASH;
if (cell_is_char(c, ':')) {
if (pos + 2 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/') && cell_is_char(self->cpu_cells + pos + 2, '/')) state = SECOND_SLASH;
} else if (cell_is_char(c, '/')) {
if (pos + 1 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/')) state = FIRST_SLASH;
}
}
switch(state) {
case ANY:
if (ch == '/') state = FIRST_SLASH;
if (cell_is_char(c, '/')) state = FIRST_SLASH;
break;
case FIRST_SLASH:
state = ch == '/' ? SECOND_SLASH : ANY;
state = cell_is_char(c, '/') ? SECOND_SLASH : ANY;
break;
case SECOND_SLASH:
if (ch == ':') return pos;
state = ch == '/' ? SECOND_SLASH : ANY;
if (cell_is_char(c, ':')) return pos;
state = cell_is_char(c, '/') ? SECOND_SLASH : ANY;
break;
}
pos--;
@ -90,7 +116,7 @@ prefix_matches(Line *self, index_type at, const char_type* prefix, index_type pr
if (prefix_len > at) return false;
index_type p, i;
for (p = at - prefix_len, i = 0; i < prefix_len && p < self->xnum; i++, p++) {
if ((self->cpu_cells[p].ch) != prefix[i]) return false;
if (!cell_is_char(self->cpu_cells + p, prefix[i])) return false;
}
return i == prefix_len;
}
@ -108,15 +134,18 @@ has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_ty
#define MIN_URL_LEN 5
static bool
has_url_beyond_colon_slash(Line *self, index_type x) {
has_url_beyond_colon_slash(Line *self, index_type x, ListOfChars *lc) {
unsigned num_of_slashes = 0;
for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) {
const char_type ch = self->cpu_cells[i].ch;
const CPUCell *c = self->cpu_cells + i;
text_in_cell(c, self->text_cache, lc);
if (num_of_slashes < 3) {
if (!is_hostname_char(ch)) return false;
if (ch == '/') num_of_slashes++;
if (!is_hostname_lc(lc)) return false;
if (lc->count == 1 && lc->chars[0] == '/') num_of_slashes++;
}
else {
for (size_t n = 0; n < lc->count; n++) if (!is_url_char(lc->chars[n])) return false;
}
else { if (!is_url_char(ch)) return false; }
}
return true;
}
@ -127,34 +156,37 @@ line_url_start_at(Line *self, index_type x) {
// known-prefix://url-chars. If no URL is found self->xnum is returned.
if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return self->xnum;
index_type ds_pos = 0, t;
RAII_ListOfChars(lc);
// First look for :// ahead of x
ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2);
if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos)) {
ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2, &lc);
if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos, &lc)) {
if (has_url_prefix_at(self, ds_pos, ds_pos > x ? ds_pos - x: 0, &t)) return t;
}
ds_pos = find_colon_slash(self, x, 0);
if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos)) return self->xnum;
ds_pos = find_colon_slash(self, x, 0, &lc);
if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos, &lc)) return self->xnum;
if (has_url_prefix_at(self, ds_pos, 0, &t)) return t;
return self->xnum;
}
static bool
is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos) {
is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) {
if (x >= self->xnum) return false;
if (in_hostname && x <= last_hostname_char_pos) return is_hostname_char(self->cpu_cells[x].ch);
return is_url_char(self->cpu_cells[x].ch);
text_in_cell(self->cpu_cells + x, self->text_cache, lc);
if (in_hostname && x <= last_hostname_char_pos) return is_hostname_lc(lc);
return is_url_lc(lc);
}
index_type
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos) {
index_type ans = x;
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos)
if (sentinel) { while (ans < self->xnum && self->cpu_cells[ans].ch != sentinel && pos_ok(ans)) ans++; }
RAII_ListOfChars(lc);
#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos, &lc)
if (sentinel) { while (ans < self->xnum && !cell_is_char(self->cpu_cells + ans, sentinel) && pos_ok(ans)) ans++; }
else { while (ans < self->xnum && pos_ok(ans)) ans++; }
if (ans) ans--;
if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) {
while (ans > x && can_strip_from_end_of_url(self->cpu_cells[ans].ch)) ans--;
while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) ans--;
}
#undef pos_ok
return ans;
@ -162,8 +194,10 @@ line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel,
bool
line_startswith_url_chars(Line *self, bool in_hostname) {
if (in_hostname) return is_hostname_char(self->cpu_cells[0].ch);
return is_url_char(self->cpu_cells[0].ch);
RAII_ListOfChars(lc);
text_in_cell(self->cpu_cells, self->text_cache, &lc);
if (in_hostname) return is_hostname_lc(&lc);
return is_url_lc(&lc);
}
@ -188,54 +222,41 @@ static PyObject*
text_at(Line* self, Py_ssize_t xval) {
#define text_at_doc "[x] -> Return the text in the specified cell"
if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
return cell_text(self->cpu_cells + xval);
return cell_text(self->cpu_cells + xval, self->text_cache);
}
size_t
cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char) {
static size_t
cell_as_unicode(ListOfChars *lc, bool include_cc, Py_UCS4 *buf, char_type zero_char) {
size_t n = 1;
buf[0] = cell->ch ? cell->ch : zero_char;
if (include_cc) {
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[n++] = codepoint_for_mark(cell->cc_idx[i]);
}
buf[0] = lc->chars[0] ? lc->chars[0] : zero_char;
if (include_cc && lc->count > 1) memcpy(buf + 1, lc->chars + 1, lc->count - 1);
return n;
}
size_t
cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf) {
cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf) {
size_t n = 1;
buf[0] = cell->ch ? cell->ch : ' ';
buf[0] = lc->chars[0] ? lc->chars[0] : ' ';
if (buf[0] != '\t') {
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) buf[n++] = codepoint_for_mark(cell->cc_idx[i]);
for (unsigned i = 1; i < lc->count; i++) {
if (lc->chars[i] != VS15 && lc->chars[i] != VS16) buf[n++] = lc->chars[i];
}
} else buf[0] = ' ';
return n;
}
size_t
cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type zero_char) {
char_type ch = cell->ch ? cell->ch : zero_char;
if (ch == '\t') { include_cc = false; }
size_t n = encode_utf8(ch, buf);
if (include_cc) {
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n);
}
buf[n] = 0;
return n;
}
size_t
cell_as_utf8_for_fallback(CPUCell *cell, char *buf) {
char_type ch = cell->ch ? cell->ch : ' ';
cell_as_utf8_for_fallback(CPUCell *cell, TextCache *tc, char *buf) {
RAII_ListOfChars(lc);
text_in_cell(cell, tc, &lc);
char_type ch = lc.chars[0] ? lc.chars[0] : ' ';
bool include_cc = true;
if (ch == '\t') { ch = ' '; include_cc = false; }
size_t n = encode_utf8(ch, buf);
if (include_cc) {
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) {
n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n);
}
for (unsigned i = 1; i < lc.count; i++) {
char_type ch = lc.chars[i];
if (ch != VS15 && ch != VS16) n += encode_utf8(ch, buf + n);
}
}
buf[n] = 0;
@ -243,33 +264,32 @@ cell_as_utf8_for_fallback(CPUCell *cell, char *buf) {
}
PyObject*
unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells) {
size_t n = 0;
static Py_UCS4 buf[4096];
RAII_ListOfChars(lc);
char_type previous_width = 0;
for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cpu_cells->cc_idx); i++) {
char_type ch = self->cpu_cells[i].ch;
if (ch == 0) {
for(index_type i = start; i < limit; i++) {
text_in_cell(self->cpu_cells + i, self->text_cache, &lc);
if (!lc.chars[0]) {
if (previous_width == 2) { previous_width = 0; continue; };
if (skip_zero_cells) continue;
}
if (ch == '\t') {
if (lc.count + n >= arraysz(buf)) break;
if (lc.chars[0] == '\t') {
buf[n++] = '\t';
unsigned num_cells_to_skip_for_tab = self->cpu_cells[i].cc_idx[0];
while (num_cells_to_skip_for_tab && i + 1 < limit && self->cpu_cells[i+1].ch == ' ') {
unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0;
while (num_cells_to_skip_for_tab && i + 1 < limit && cell_is_char(self->cpu_cells+i+1, ' ')) {
i++;
num_cells_to_skip_for_tab--;
}
} else {
n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' ');
n += cell_as_unicode(&lc, include_cc, buf + n, ' ');
}
previous_width = self->gpu_cells[i].attrs.width;
}
if (add_trailing_newline && !self->gpu_cells[self->xnum-1].attrs.next_char_was_wrapped && n < arraysz(buf)) {
buf[n++] = '\n';
}
if (add_trailing_newline && !self->gpu_cells[self->xnum-1].attrs.next_char_was_wrapped && n < arraysz(buf)) buf[n++] = '\n';
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
}
@ -331,7 +351,7 @@ write_mark(const char *mark, ANSIBuf *output) {
bool
line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type start_at, index_type stop_before, char_type prefix_char) {
#define ENSURE_SPACE(extra) ensure_space_for(output, buf, Py_UCS4, output->len + extra, capacity, 2048, false);
#define ENSURE_SPACE(extra) ensure_space_for(output, buf, output->buf[0], output->len + extra, capacity, 2048, false);
#define WRITE_SGR(val) { ENSURE_SPACE(128); escape_code_written = true; write_sgr(val, output); }
#define WRITE_CH(val) { ENSURE_SPACE(1); output->buf[output->len++] = val; }
#define WRITE_HYPERLINK(val) { ENSURE_SPACE(2256); escape_code_written = true; write_hyperlink(val, output); }
@ -363,10 +383,10 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type
const CellAttrs mask_for_sgr = {.val=SGR_MASK};
for (index_type pos=start_at; pos < limit; pos++) {
char_type ch = self->cpu_cells[pos].ch;
if (ch == 0) {
if (previous_width == 2) { previous_width = 0; continue; }
ch = ' ';
unsigned n = text_in_cell_ansi(self->cpu_cells + pos, self->text_cache, output);
if (output->buf[output->len - n] == 0) {
if (previous_width == 2) { previous_width = 0; output->len -= n; continue; }
output->buf[output->len - n] = ' ';
}
if (output->hyperlink_pool) {
hyperlink_id_type hid = self->cpu_cells[pos].hyperlink_id;
@ -384,15 +404,14 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type
if (*sgr) WRITE_SGR(sgr);
}
*prev_cell = cell;
WRITE_CH(ch);
if (ch == '\t') {
unsigned num_cells_to_skip_for_tab = self->cpu_cells[pos].cc_idx[0];
while (num_cells_to_skip_for_tab && pos + 1 < limit && self->cpu_cells[pos+1].ch == ' ') {
num_cells_to_skip_for_tab--; pos++;
if (output->buf[output->len - n] == '\t') {
unsigned num_cells_to_skip_for_tab = 0;
if (n > 1) {
num_cells_to_skip_for_tab = output->buf[output->len - n + 1];
output->len -= n - 1;
}
} else {
for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) {
WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c]));
while (num_cells_to_skip_for_tab && pos + 1 < limit && cell_is_char(self->cpu_cells + pos + 1, ' ')) {
num_cells_to_skip_for_tab--; pos++;
}
}
previous_width = cell->attrs.width;
@ -448,17 +467,19 @@ width(Line *self, PyObject *val) {
return PyLong_FromUnsignedLong((unsigned long) (self->gpu_cells[x].attrs.width));
}
void
line_add_combining_char(CPUCell *cpu_cells, GPUCell *gpu_cells, uint32_t ch, unsigned int x) {
bool
line_add_combining_char(CPUCell *cpu_cells, GPUCell *gpu_cells, TextCache *tc, ListOfChars *lc, uint32_t ch, unsigned int x) {
CPUCell *cell = cpu_cells + x;
if (!cell->ch) {
if (x > 0 && (gpu_cells[x-1].attrs.width) == 2 && cpu_cells[x-1].ch) cell = cpu_cells + x - 1;
else return; // don't allow adding combining chars to a null cell
if (!cell->ch_or_idx && !cell->ch_is_idx) {
if (x > 0 && (gpu_cells[x-1].attrs.width) == 2 && cell_has_text(cpu_cells + x - 1)) cell = cpu_cells + x - 1;
else return false; // don't allow adding combining chars to a null cell
}
for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) {
if (!cell->cc_idx[i]) { cell->cc_idx[i] = mark_for_codepoint(ch); return; }
}
cell->cc_idx[arraysz(cell->cc_idx) - 1] = mark_for_codepoint(ch);
text_in_cell(cell, tc, lc);
ensure_space_for_chars(lc, lc->count + 1);
lc->chars[lc->count++] = ch;
cell->ch_or_idx = tc_get_or_insert_chars(tc, lc);
cell->ch_is_idx = true;
return true;
}
static PyObject*
@ -471,7 +492,8 @@ add_combining_char(Line* self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, "Column index out of bounds");
return NULL;
}
line_add_combining_char(self->cpu_cells, self->gpu_cells, new_char, x);
RAII_ListOfChars(lc);
line_add_combining_char(self->cpu_cells, self->gpu_cells, self->text_cache, &lc, new_char, x);
Py_RETURN_NONE;
}
@ -502,13 +524,12 @@ set_text(Line* self, PyObject *args) {
color_type dfg = cursor->decoration_fg & COL_MASK;
for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) {
self->cpu_cells[i].ch = (PyUnicode_READ(kind, buf, offset));
self->cpu_cells[i].hyperlink_id = 0;
self->cpu_cells[i].val = 0;
self->cpu_cells[i].ch_or_idx = PyUnicode_READ(kind, buf, offset);
self->gpu_cells[i].attrs = attrs;
self->gpu_cells[i].fg = fg;
self->gpu_cells[i].bg = bg;
self->gpu_cells[i].decoration_fg = dfg;
memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx));
}
Py_RETURN_NONE;
@ -537,7 +558,7 @@ cursor_from(Line* self, PyObject *args) {
void
line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) {
const uint16_t width = ch ? 1 : 0;
const CPUCell cc = {.ch=ch};
const CPUCell cc = {.ch_or_idx=ch};
if (at + num > self->xnum) num = self->xnum > at ? self->xnum - at : 0;
memset_array(self->cpu_cells + at, cc, num);
for (index_type i = at; i < at + num; i++) self->gpu_cells[i].attrs.width = width;
@ -584,15 +605,16 @@ apply_cursor(Line* self, PyObject *args) {
Py_RETURN_NONE;
}
void line_right_shift(Line *self, unsigned int at, unsigned int num) {
void
line_right_shift(Line *self, unsigned int at, unsigned int num) {
for(index_type i = self->xnum - 1; i >= at + num; i--) {
COPY_SELF_CELL(i - num, i)
}
// Check if a wide character was split at the right edge
if (self->gpu_cells[self->xnum - 1].attrs.width != 1) {
self->cpu_cells[self->xnum - 1].ch = BLANK_CHAR;
self->cpu_cells[self->xnum - 1].val = 0;
self->cpu_cells[self->xnum - 1].hyperlink_id = 0;
self->gpu_cells[self->xnum - 1].attrs = (CellAttrs){.width=BLANK_CHAR ? 1 : 0};
self->gpu_cells[self->xnum - 1].attrs = (CellAttrs){0};
clear_sprite_position(self->gpu_cells[self->xnum - 1]);
}
}
@ -654,11 +676,18 @@ colors_for_cell(Line *self, const ColorProfile *cp, index_type *x, color_type *f
char_type
line_get_char(Line *self, index_type at) {
char_type ch = self->cpu_cells[at].ch;
if (!ch && at > 0 && (self->gpu_cells[at-1].attrs.width) > 1) ch = self->cpu_cells[at-1].ch;
return ch;
if (self->cpu_cells[at].ch_is_idx) {
RAII_ListOfChars(lc);
text_in_cell(self->cpu_cells + at, self->text_cache, &lc);
return lc.chars[0];
} else {
char_type ch = self->cpu_cells[at].ch_or_idx;
if (!ch && at > 0 && (self->gpu_cells[at-1].attrs.width) > 1) ch = line_get_char(self, at - 1);
return ch;
}
}
void
line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor, hyperlink_id_type hyperlink_id) {
GPUCell *g = self->gpu_cells + at;
@ -670,13 +699,12 @@ line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Curs
g->bg = cursor->bg & COL_MASK;
g->decoration_fg = cursor->decoration_fg & COL_MASK;
}
self->cpu_cells[at].ch = ch;
cell_set_char(self->cpu_cells + at, ch);
self->cpu_cells[at].hyperlink_id = hyperlink_id;
if (OPT(underline_hyperlinks) == UNDERLINE_ALWAYS && hyperlink_id) {
g->decoration_fg = ((OPT(url_color) & COL_MASK) << 8) | 2;
g->attrs.decoration = OPT(url_style);
}
memset(self->cpu_cells[at].cc_idx, 0, sizeof(self->cpu_cells[at].cc_idx));
}
static PyObject*
@ -802,21 +830,20 @@ apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int *
index_type x = *cell_pos;
MARK;
(*match_pos)++;
if (line->cpu_cells[x].ch) {
if (line->cpu_cells[x].ch == '\t') {
unsigned num_cells_to_skip_for_tab = line->cpu_cells[x].cc_idx[0];
while (num_cells_to_skip_for_tab && x + 1 < line->xnum && line->cpu_cells[x+1].ch == ' ') {
RAII_ListOfChars(lc); text_in_cell(line->cpu_cells + x, line->text_cache, &lc);
if (lc.chars[0]) {
if (lc.chars[0] == '\t') {
unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0;
while (num_cells_to_skip_for_tab && x + 1 < line->xnum && cell_is_char(line->cpu_cells+x+1, ' ')) {
x++;
num_cells_to_skip_for_tab--;
MARK;
}
} else if ((line->gpu_cells[x].attrs.width) > 1 && x + 1 < line->xnum && !line->cpu_cells[x+1].ch) {
} else if ((line->gpu_cells[x].attrs.width) > 1 && x + 1 < line->xnum && !cell_has_text(line->cpu_cells+x+1)) {
x++;
MARK;
} else {
for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) {
if (line->cpu_cells[x].cc_idx[i]) (*match_pos)++;
}
*match_pos += lc.count - 1;
}
}
*cell_pos = x + 1;

View file

@ -28,6 +28,10 @@ typedef union CellAttrs {
#define DECORATION_MASK (7u)
#define NUM_UNDERLINE_STYLES (5u)
#define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK, .next_char_was_wrapped=1}).val))
// Text presentation selector
#define VS15 0xfe0e
// Emoji presentation selector
#define VS16 0xfe0f
typedef struct {
color_type fg, bg, decoration_fg;
@ -36,13 +40,16 @@ typedef struct {
} GPUCell;
static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell");
typedef struct {
char_type ch;
hyperlink_id_type hyperlink_id;
combining_type cc_idx[3];
typedef union CPUCell {
struct {
char_type ch_or_idx: sizeof(char_type) * 8;
hyperlink_id_type hyperlink_id: sizeof(hyperlink_id_type) * 8;
bool ch_is_idx: 1;
uint16_t : 15;
};
uint64_t val;
} CPUCell;
static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell");
static_assert(sizeof(CPUCell) == sizeof(uint64_t), "Fix the ordering of CPUCell");
typedef union LineAttrs {
struct {
@ -69,6 +76,37 @@ typedef struct {
Line* alloc_line(TextCache *text_cache);
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count, bool is_group);
const char* cell_as_sgr(const GPUCell *, const GPUCell *);
static inline bool cell_has_text(const CPUCell *c) { return c->ch_is_idx || c->ch_or_idx; }
static inline void cell_set_char(CPUCell *c, char_type ch) { c->ch_is_idx = false; c->ch_or_idx = ch; }
static inline bool cell_is_char(const CPUCell *c, char_type ch) { return !c->ch_is_idx && c->ch_or_idx == ch; }
static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache *tc) {
return c->ch_is_idx ? tc_num_codepoints(tc, c->ch_or_idx) : (c->ch_or_idx ? 1 : 0);
}
static inline void
text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) {
if (c->ch_is_idx) tc_chars_at_index(tc, c->ch_or_idx, ans);
else {
ans->count = 1;
ans->chars[0] = c->ch_or_idx;
}
}
static inline void
cell_set_chars(CPUCell *c, TextCache *tc, const ListOfChars *lc) {
if (lc->count <= 1) cell_set_char(c, lc->chars[0]);
else {
c->ch_or_idx = tc_get_or_insert_chars(tc, lc);
c->ch_is_idx = true;
}
}
static inline char_type
cell_first_char(const CPUCell *c, const TextCache *tc) {
if (c->ch_is_idx) return tc_first_char_at_index(tc, c->ch_or_idx);
return c->ch_or_idx;
}
static inline CellAttrs
cursor_to_attrs(const Cursor *c, const uint16_t width) {

View file

@ -32,8 +32,8 @@ static inline void
clear_chars_in_line(CPUCell *cpu_cells, GPUCell *gpu_cells, index_type xnum, char_type ch) {
// Clear only the char part of each cell, the rest must have been cleared by a memset or similar
if (ch) {
const CellAttrs empty = {.width=1};
for (index_type i = 0; i < xnum; i++) { cpu_cells[i].ch = ch; cpu_cells[i].hyperlink_id = 0; gpu_cells[i].attrs = empty; }
static const CellAttrs empty = {.width=1};
for (index_type i = 0; i < xnum; i++) { cpu_cells[i].val = 0; cpu_cells[i].ch_or_idx = ch; gpu_cells[i].attrs = empty; }
}
}
@ -41,7 +41,8 @@ static inline index_type
xlimit_for_line(const Line *line) {
index_type xlimit = line->xnum;
if (BLANK_CHAR == 0) {
while (xlimit > 0 && (line->cpu_cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--;
const CPUCell *c;
while (xlimit > 0 && !((c = line->cpu_cells + xlimit - 1)->ch_is_idx) && c->ch_or_idx == BLANK_CHAR) xlimit--;
if (xlimit < line->xnum && line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs.width == 2) xlimit++;
}
return xlimit;
@ -67,8 +68,7 @@ left_shift_line(Line *line, index_type at, index_type num) {
const CellAttrs empty = {.width=1};
const CellAttrs zero = {{0}};
if (at < line->xnum && line->gpu_cells[at].attrs.width != 1) {
line->cpu_cells[at].ch = BLANK_CHAR;
line->cpu_cells[at].hyperlink_id = 0;
line->cpu_cells[at].val = 0;
line->gpu_cells[at].attrs = BLANK_CHAR ? empty : zero;
clear_sprite_position(line->gpu_cells[at]);
}
@ -77,7 +77,7 @@ left_shift_line(Line *line, index_type at, index_type num) {
static inline bool
line_is_empty(const Line *line) {
for (index_type i = 0; i < line->xnum; i++) {
if (line->cpu_cells[i].ch != BLANK_CHAR) return false;
if (line->cpu_cells[i].ch_is_idx || line->cpu_cells[i].ch_or_idx != BLANK_CHAR) return false;
}
return true;
}
@ -88,16 +88,14 @@ void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsign
char_type line_get_char(Line *self, index_type at);
void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, hyperlink_id_type);
void line_right_shift(Line *, unsigned int , unsigned int );
void line_add_combining_char(CPUCell *, GPUCell *, uint32_t , unsigned int );
bool line_add_combining_char(CPUCell *, GPUCell *, TextCache*, ListOfChars*, uint32_t , unsigned int );
index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type);
bool line_startswith_url_chars(Line*, bool);
bool line_as_ansi(Line *self, ANSIBuf *output, const GPUCell**, index_type start_at, index_type stop_before, char_type prefix_char) __attribute__((nonnull));
unsigned int line_length(Line *self);
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf);
size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type);
size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf);
size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf);
size_t cell_as_utf8_for_fallback(CPUCell *cell, TextCache *tc, char *buf);
PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells);
PyObject* line_as_unicode(Line *, bool);

View file

@ -67,7 +67,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
src_x_limit = src->xnum;
if (!src_line_is_continued) {
// Trim trailing blanks since there is a hard line break at the end of this line
while(src_x_limit && (src->line->cpu_cells[src_x_limit - 1].ch) == BLANK_CHAR) src_x_limit--;
while(src_x_limit && src->line->cpu_cells[src_x_limit - 1].ch_or_idx == BLANK_CHAR && !src->line->cpu_cells[src_x_limit - 1].ch_is_idx) src_x_limit--;
} else {
src->line->gpu_cells[src->xnum-1].attrs.next_char_was_wrapped = false;
}

View file

@ -136,10 +136,11 @@ new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->grman = self->main_grman;
self->disable_ligatures = OPT(disable_ligatures);
self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool));
self->lc = alloc_list_of_chars();
if (
self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL ||
self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL ||
self->alt_grman == NULL || self->color_profile == NULL
self->alt_grman == NULL || self->color_profile == NULL || self->lc == NULL
) {
Py_CLEAR(self); return NULL;
}
@ -335,7 +336,7 @@ found:
linebuf_init_line(self->main_linebuf, y);
// this is needed because screen_resize() checks to see if the cursor is beyond the content,
// so insert some fake content
self->main_linebuf->line->cpu_cells[0].ch = ' ';
cell_set_char(self->main_linebuf->line->cpu_cells, ' ');
if (y < (int)self->cursor->y) (*num_of_prompt_lines_above_cursor)++;
}
}
@ -346,10 +347,10 @@ static bool
preserve_blank_output_start_line(Cursor *cursor, LineBuf *linebuf) {
if (cursor->x == 0 && cursor->y < linebuf->ynum && !linebuf->line_attrs[cursor->y].is_continued) {
linebuf_init_line(linebuf, cursor->y);
if (!linebuf->line->cpu_cells[0].ch) {
if (!cell_has_text(linebuf->line->cpu_cells)) {
// we have a blank output start line, we need it to be preserved by
// reflow, so insert a dummy char
linebuf->line->cpu_cells[cursor->x++].ch = '<';
cell_set_char(linebuf->line->cpu_cells + cursor->x++, '<');
return true;
}
}
@ -360,7 +361,7 @@ static void
remove_blank_output_line_reservation_marker(Cursor *cursor, LineBuf *linebuf) {
if (cursor->y < linebuf->ynum) {
linebuf_init_line(linebuf, cursor->y);
linebuf->line->cpu_cells[0].ch = 0;
cell_set_char(linebuf->line->cpu_cells, 0);
cursor->x = 0;
}
}
@ -529,6 +530,7 @@ dealloc(Screen* self) {
free_hyperlink_pool(self->hyperlink_pool);
free(self->as_ansi_buf.buf);
free(self->last_rendered_window_char.canvas);
if (self->lc) { cleanup_list_of_chars(self->lc); free(self->lc); self->lc = NULL; }
Py_TYPE(self)->tp_free((PyObject*)self);
} // }}}
@ -643,8 +645,9 @@ draw_second_flag_codepoint(Screen *self, char_type ch) {
CPUCell *cp; GPUCell *gp;
linebuf_init_cells(self->linebuf, ypos, &cp, &gp);
CPUCell *cell = cp + xpos;
if (!is_flag_pair(cell->ch, ch) || cell->cc_idx[0]) return false;
line_add_combining_char(cp, gp, ch, xpos);
text_in_cell(cell, self->text_cache, self->lc);
if (self->lc->count != 1 || !is_flag_pair(self->lc->chars[0], ch)) return false;
line_add_combining_char(cp, gp, self->text_cache, self->lc, ch, xpos);
return true;
}
@ -670,11 +673,12 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
if (has_prev_char) {
CPUCell *cp; GPUCell *gp;
linebuf_init_cells(self->linebuf, ypos, &cp, &gp);
line_add_combining_char(cp, gp, ch, xpos);
if (ch == 0xfe0f) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji
bool added = line_add_combining_char(cp, gp, self->text_cache, self->lc, ch, xpos);
unsigned base_pos = self->lc->count - (added ? 2 : 1);
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 (gpu_cell->attrs.width != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
if (gpu_cell->attrs.width != 2 && added && self->lc->chars[base_pos + 1] == VS16 && is_emoji_presentation_base(self->lc->chars[base_pos])) {
gpu_cell->attrs.width = 2;
if (xpos + 1 < self->columns) {
zero_cells(s, cp + xpos + 1, gp + xpos + 1);
@ -682,13 +686,13 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
self->cursor->x++;
} else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos);
}
} else if (ch == 0xfe0e) {
} else if (ch == VS15) {
CPUCell *cpu_cell = cp + xpos;
GPUCell *gpu_cell = gp + xpos;
if (gpu_cell->attrs.width == 0 && cpu_cell->ch == 0 && xpos > 0) {
if (gpu_cell->attrs.width == 0 && !cell_has_text(cpu_cell) && xpos > 0) {
cpu_cell--; gpu_cell--;
}
if (gpu_cell->attrs.width == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) {
if (gpu_cell->attrs.width == 2 && added && self->lc->chars[base_pos + 1] == VS15 && is_emoji_presentation_base(self->lc->chars[base_pos])) {
gpu_cell->attrs.width = 1;
self->cursor->x--;
}
@ -716,7 +720,7 @@ cursor_on_wide_char_trailer(Screen *self, text_loop_state *s) {
static void
move_cursor_off_wide_char_trailer(Screen *self, text_loop_state *s) {
zero_cells(s, s->cp + self->cursor->x - 1, s->gp + self->cursor->x - 1);
s->cp[self->cursor->x-1].ch = ' ';
cell_set_char(&s->cp[self->cursor->x-1], ' ');
zero_cells(s, s->cp + self->cursor->x, s->gp + self->cursor->x);
}
@ -828,7 +832,7 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_
s->image_placeholder_marked = true;
}
zero_cells(s, s->cp + self->cursor->x, s->gp + self->cursor->x);
s->cp[self->cursor->x].ch = ch;
cell_set_char(&s->cp[self->cursor->x], ch);
self->cursor->x++;
if (char_width == 2) {
s->gp[self->cursor->x-1].attrs.width = 2;
@ -1449,15 +1453,15 @@ screen_tab(Screen *self) {
bool ok = true;
for (combining_type i = 0; i < diff; i++) {
CPUCell *c = cpu_cell + i;
if (c->ch != ' ' && c->ch != 0) { ok = false; break; }
if (cell_has_text(c) && !cell_is_char(c, ' ')) { ok = false; break; }
}
if (ok) {
for (combining_type i = 0; i < diff; i++) {
CPUCell *c = cpu_cell + i;
c->ch = ' '; zero_at_ptr_count(c->cc_idx, arraysz(c->cc_idx));
cell_set_char(c, ' ');
}
cpu_cell->ch = '\t';
cpu_cell->cc_idx[0] = diff;
self->lc->count = 2; self->lc->chars[0] = '\t'; self->lc->chars[1] = diff;
cell_set_chars(cpu_cell, self->text_cache, self->lc);
}
}
self->cursor->x = found;
@ -2618,8 +2622,7 @@ screen_has_marker(Screen *self) {
return self->marker != NULL;
}
static uint32_t diacritic_to_rowcolumn(combining_type m) {
char_type c = codepoint_for_mark(m);
static uint32_t diacritic_to_rowcolumn(char_type c) {
return diacritic_to_num(c);
}
@ -2656,21 +2659,22 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row) {
uint32_t cur_img_id_higher8bits = 0;
uint32_t cur_img_row = 0;
uint32_t cur_img_col = 0;
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) {
line->attrs.has_image_placeholders = true;
// The lower 24 bits of the image id are encoded in the foreground
// color, and the placement id is (optionally) in the underline color.
cur_img_id_lower24bits = color_to_id(gpu_cell->fg);
cur_placement_id = color_to_id(gpu_cell->decoration_fg);
text_in_cell(cpu_cell, self->text_cache, self->lc);
// If the char has diacritics, use them as row and column indices.
if (cpu_cell->cc_idx[0])
cur_img_row = diacritic_to_rowcolumn(cpu_cell->cc_idx[0]);
if (cpu_cell->cc_idx[1])
cur_img_col = diacritic_to_rowcolumn(cpu_cell->cc_idx[1]);
if (self->lc->count > 1 && self->lc->chars[1])
cur_img_row = diacritic_to_rowcolumn(self->lc->chars[1]);
if (self->lc->count > 2 && self->lc->chars[2])
cur_img_col = diacritic_to_rowcolumn(self->lc->chars[2]);
// The third diacritic is used to encode the higher 8 bits of the
// image id (optional).
if (cpu_cell->cc_idx[2])
cur_img_id_higher8bits = diacritic_to_rowcolumn(cpu_cell->cc_idx[2]);
if (self->lc->count > 3 && self->lc->chars[3])
cur_img_id_higher8bits = diacritic_to_rowcolumn(self->lc->chars[3]);
}
// The current run is continued if the lower 24 bits of the image id and
// the placement id are the same as in the previous cell and everything
@ -2697,7 +2701,7 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row) {
prev_img_row - 1, run_length, 1, self->cell_size);
}
// Start a new run.
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) {
run_length = 1;
if (!cur_img_col) cur_img_col = 1;
if (!cur_img_row) cur_img_row = 1;
@ -2755,7 +2759,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
for (index_type y = 0; y < self->lines; y++) {
linebuf_init_line(linebuf, y);
if (linebuf->line->attrs.has_dirty_text) {
render_line(fonts_data, linebuf->line, y, &self->paused_rendering.cursor, self->disable_ligatures);
render_line(fonts_data, linebuf->line, y, &self->paused_rendering.cursor, self->disable_ligatures, self->lc);
screen_render_line_graphics(self, linebuf->line, y);
if (linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, linebuf->line);
linebuf_mark_line_clean(linebuf, y);
@ -2780,7 +2784,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
// the unicode placeholder was first scanned can alter it.
screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by);
if (self->historybuf->line->attrs.has_dirty_text) {
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures, self->lc);
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
historybuf_mark_line_clean(self->historybuf, lnum);
}
@ -2791,7 +2795,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
linebuf_init_line(self->linebuf, lnum);
if (self->linebuf->line->attrs.has_dirty_text ||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) {
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures, self->lc);
screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by);
if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, self->linebuf->line, fonts_data);
@ -3032,9 +3036,9 @@ limit_without_trailing_whitespace(const Line *line, index_type limit) {
if (!limit) return limit;
if (limit > line->xnum) limit = line->xnum;
while (limit > 0) {
CPUCell *cell = line->cpu_cells + limit - 1;
if (cell->cc_idx[0]) break;
switch(cell->ch) {
const CPUCell *cell = line->cpu_cells + limit - 1;
if (cell->ch_is_idx) break;
switch(cell->ch_or_idx) {
case ' ': case '\t': case '\n': case '\r': case 0: break;
default:
return limit;
@ -3216,13 +3220,13 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname);
has_newline = !line->attrs.is_continued;
if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false;
if (sentinel && next_line_starts_with_url_chars && line->cpu_cells[0].ch == sentinel) next_line_starts_with_url_chars = false;
if (sentinel && next_line_starts_with_url_chars && cell_is_char(line->cpu_cells, sentinel)) next_line_starts_with_url_chars = false;
}
line = screen_visual_line(screen, *y + 1);
if (!line) break;
if (in_hostname) {
for (last_hostname_char_pos = 0; last_hostname_char_pos < line->xnum; last_hostname_char_pos++) {
if (line->cpu_cells[last_hostname_char_pos].ch == '/') {
if (cell_is_char(line->cpu_cells + last_hostname_char_pos, '/')) {
if (last_hostname_char_pos > 0) last_hostname_char_pos--;
else { in_hostname = false; last_hostname_char_pos = line->xnum; }
break;
@ -3235,7 +3239,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
}
if (sentinel && *x == 0 && *y > orig_y) {
line = screen_visual_line(screen, *y);
if (line && line->cpu_cells[0].ch == sentinel) {
if (line && cell_is_char(line->cpu_cells, sentinel)) {
*y -= 1; *x = line->xnum - 1;
}
}
@ -3244,7 +3248,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
static char_type
get_url_sentinel(Line *line, index_type url_start) {
char_type before = 0, sentinel;
if (url_start > 0 && url_start < line->xnum) before = line->cpu_cells[url_start - 1].ch;
if (url_start > 0 && url_start < line->xnum) before = cell_first_char(line->cpu_cells + url_start - 1, line->text_cache);
switch(before) {
case '"':
case '\'':
@ -3291,8 +3295,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
sentinel = get_url_sentinel(line, url_start);
index_type slash_count = 0;
for (index_type i = url_start; i < line->xnum; i++) {
const char_type ch = line->cpu_cells[i].ch;
if (ch == '/' && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; }
if (cell_is_char(line->cpu_cells + i, '/') && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; }
}
url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos);
}
@ -3442,7 +3445,7 @@ render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data) {
#define ol self->overlay_line
line_save_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells);
screen_draw_overlay_line(self);
render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures);
render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures, self->lc);
line_save_cells(line, 0, line->xnum, ol.gpu_cells, ol.cpu_cells);
line_reset_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells);
ol.is_dirty = false;
@ -4028,14 +4031,18 @@ text_for_marked_url(Screen *self, PyObject *args) {
return text_for_selections(self, &self->url_ranges, ansi, strip_trailing_whitespace);
}
static bool
cell_is_blank(const CPUCell *c) {
return !cell_has_text(c) || cell_is_char(c, ' ');
}
bool
screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) {
if (y >= self->lines) { return false; }
Line *line = visual_line_(self, y);
index_type xlimit = line->xnum, xstart = 0;
while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--;
while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++;
while (xlimit > 0 && cell_is_blank(line->cpu_cells + xlimit - 1)) xlimit--;
while (xstart < xlimit && cell_is_blank(line->cpu_cells + xstart)) xstart++;
*start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0;
return true;
}
@ -4060,11 +4067,10 @@ is_opt_word_char(char_type ch, bool forward) {
static bool
is_char_ok_for_word_extension(Line* line, index_type x, bool forward) {
char_type ch = line->cpu_cells[x].ch;
char_type ch = cell_first_char(line->cpu_cells + x, line->text_cache);
if (is_word_char(ch) || is_opt_word_char(ch, forward)) return true;
// pass : from :// so that common URLs are matched
if (ch == ':' && x + 2 < line->xnum && line->cpu_cells[x+1].ch == '/' && line->cpu_cells[x+2].ch == '/') return true;
return false;
return ch == ':' && x + 2 < line->xnum && cell_is_char(line->cpu_cells + x + 1, '/') && cell_is_char(line->cpu_cells + x + 2, '/');
}
bool

View file

@ -172,6 +172,7 @@ typedef struct {
Selections selections, url_ranges;
} paused_rendering;
CharsetState charset;
ListOfChars *lc;
} Screen;

View file

@ -13,7 +13,7 @@ typedef struct Chars {
#define NAME chars_map
#define KEY_TY Chars
#define VAL_TY CharOrIndex
#define VAL_TY char_type
static uint64_t hash_chars(Chars k);
static bool cmpr_chars(Chars a, Chars b);
#define HASH_FN hash_chars
@ -21,7 +21,7 @@ static bool cmpr_chars(Chars a, Chars b);
#include "kitty-verstable.h"
typedef struct TextCache {
struct { Chars *items; size_t count, capacity; } array;
struct { Chars *items; size_t capacity; char_type count; } array;
chars_map map;
unsigned refcnt;
} TextCache;
@ -52,7 +52,7 @@ tc_clear(TextCache *ans) {
static void
free_text_cache(TextCache *self) {
vt_cleanup(&self->map);
for (size_t i = 0; i < self->array.count; i++) free((char_type*)self->array.items[i].chars);
for (char_type i = 0; i < self->array.count; i++) free((char_type*)self->array.items[i].chars);
free(self->array.items);
free(self);
}
@ -69,32 +69,49 @@ tc_decref(TextCache *self) {
return NULL;
}
char_type
tc_first_char_at_index(const TextCache *self, char_type idx) {
if (self->array.count > idx) return self->array.items[idx].chars[0];
return 0;
}
void
tc_chars_at_index(const TextCache *self, CharOrIndex idx, ListOfChars *ans) {
if (idx.ch_is_index) {
if (self->array.count > idx.ch) {
ans->count = self->array.items[idx.ch].count;
ensure_space_for(ans, chars, char_type, ans->count, capacity, 8, false);
memcpy(ans->chars, self->array.items[idx.ch].chars, sizeof(ans->chars[0]) * ans->count);
} else {
ans->count = 0;
}
tc_chars_at_index(const TextCache *self, char_type idx, ListOfChars *ans) {
if (self->array.count > idx) {
ans->count = self->array.items[idx].count;
ensure_space_for_chars(ans, ans->count);
memcpy(ans->chars, self->array.items[idx].chars, sizeof(ans->chars[0]) * ans->count);
} else {
ans->count = 1;
ensure_space_for(ans, chars, char_type, 1, capacity, 8, false);
ans->chars[0] = idx.ch;
ans->count = 0;
}
}
static CharOrIndex
unsigned
tc_num_codepoints(const TextCache *self, char_type idx) {
return self->array.count > idx ? self->array.items[idx].count : 0;
}
unsigned
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);
memcpy(output->buf + output->len, self->array.items[idx].chars, sizeof(output->buf[0]) * count);
output->len += count;
}
return count;
}
static char_type
copy_and_insert(TextCache *self, const Chars key) {
if (self->array.count >= (1llu << (8*sizeof(char_type) - 1)) - 1) fatal("Too many items in TextCache");
if (self->array.count > MAX_CHAR_TYPE_VALUE) fatal("Too many items in TextCache");
ensure_space_for(&(self->array), items, Chars, self->array.count + 1, capacity, 256, false);
char_type *copy = malloc(key.count * sizeof(key.chars[0]));
if (!copy) fatal("Out of memory");
memcpy(copy, key.chars, key.count * sizeof(key.chars[0]));
CharOrIndex ans;
ans.ch_is_index = 1; ans.ch = self->array.count;
char_type ans = self->array.count;
Chars *k = self->array.items + self->array.count++;
k->count = key.count; k->chars = copy;
chars_map_itr i = vt_insert(&self->map, *k, ans);
@ -102,13 +119,8 @@ copy_and_insert(TextCache *self, const Chars key) {
return ans;
}
CharOrIndex
char_type
tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars) {
if (chars->count == 1) {
CharOrIndex ans = {0};
ans.ch = chars->chars[0];
return ans;
}
Chars key = {.count=chars->count, .chars=chars->chars};
chars_map_itr i = vt_get(&self->map, key);
if (vt_is_end(i)) return copy_and_insert(self, key);

View file

@ -14,6 +14,33 @@ typedef struct ListOfChars {
size_t count, capacity;
} ListOfChars;
#define LIST_OF_CHARS_STACK_SIZE 4
static inline void cleanup_list_of_chars(ListOfChars *lc) { if (lc->capacity > LIST_OF_CHARS_STACK_SIZE) free(lc->chars); }
#define RAII_ListOfChars(name) char_type name##lcbuf[LIST_OF_CHARS_STACK_SIZE]; __attribute__((cleanup(cleanup_list_of_chars))) ListOfChars name = {.chars=name##lcbuf, .capacity = LIST_OF_CHARS_STACK_SIZE};
static inline ListOfChars* alloc_list_of_chars(void) {
ListOfChars *ans = calloc(1, sizeof(ListOfChars));
if (ans) {
ans->capacity = LIST_OF_CHARS_STACK_SIZE * 2;
ans->chars = malloc(ans->capacity * sizeof(ans->chars[0]));
if (!ans->chars) { free(ans); ans = NULL; }
}
return ans;
}
static inline void
ensure_space_for_chars(ListOfChars *lc, size_t count) {
if (lc->capacity >= count) return;
if (lc->capacity > LIST_OF_CHARS_STACK_SIZE) {
ensure_space_for(lc, chars, char_type, count, capacity, count, false);
} else {
lc->capacity = count + LIST_OF_CHARS_STACK_SIZE;
void *chars = malloc(lc->capacity * sizeof(lc->chars[0]));
if (!chars) fatal("Out of memory allocating LCChars char space");
memcpy(chars, lc->chars, lc->count * sizeof(lc->chars[0]));
lc->chars = chars;
}
}
#ifndef TEXT_CACHE_IMPLEMENTATION
typedef struct {int x; } *TextCache;
#endif
@ -22,5 +49,8 @@ TextCache* tc_alloc(void);
TextCache* tc_incref(TextCache *self);
TextCache* tc_decref(TextCache *self);
void tc_clear(TextCache *ans);
void tc_chars_at_index(const TextCache *self, CharOrIndex idx, ListOfChars *ans);
CharOrIndex tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars);
void tc_chars_at_index(const TextCache *self, char_type idx, ListOfChars *ans);
unsigned tc_chars_at_index_ansi(const TextCache *self, char_type idx, ANSIBuf *output);
char_type tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars);
char_type tc_first_char_at_index(const TextCache *self, char_type idx);
unsigned tc_num_codepoints(const TextCache *self, char_type idx);

View file

@ -1,9 +1,6 @@
#pragma once
#include "data-types.h"
#include "state.h"
// START_KNOWN_MARKS
static const combining_type VS15 = 1364, VS16 = 1365;
// END_KNOWN_MARKS
// Converts row/column diacritics to numbers.
int diacritic_to_num(char_type ch);
@ -14,8 +11,6 @@ bool is_word_char(char_type ch);
bool is_CZ_category(char_type);
bool is_P_category(char_type);
bool is_non_rendered_char(char_type);
char_type codepoint_for_mark(combining_type m);
combining_type mark_for_codepoint(char_type c);
static inline bool
is_excluded_from_url(uint32_t ch) {