mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-20 20:22:10 +00:00
Widen the cell for Thai/Lao vowel sign AM (U+0E33, U+0EB3)
These two code points have Grapheme_Cluster_Break=SpacingMark but a width of one, the only such code points in Unicode. Grapheme segmentation (GB9a) adds them to the previous cell, so "จำ" occupied a single cell in kitty while wcwidth() based programs count two. As a result the SARA AA glyph was clipped, only the nikhahit was visible and the cursor drifted right by one cell per AM. Now, when such a code point combines into a plain one cell wide cell, the cell is widened to two cells, using the same mechanism as U+FE0F does for narrow emoji. The C and Go wcswidth() implementations are updated to match so that kitty's own width calculations agree with the screen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1f20d43198
commit
0b29c33e89
8 changed files with 95 additions and 24 deletions
|
|
@ -202,6 +202,8 @@ Detailed list of changes
|
|||
0.49.0 [future]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Thai/Lao: Render the vowel sign AM (U+0E33/U+0EB3) correctly by widening the cell it combines into, matching wcwidth() based programs
|
||||
|
||||
- Support for :doc:`/custom-shaders` for adding various graphical effects (:iss:`10344`)
|
||||
|
||||
- Various throughput performance improvements for a 15-35% real world improvement depending on workload
|
||||
|
|
|
|||
|
|
@ -412,7 +412,10 @@ For each decoded code point:
|
|||
|
||||
#. If there is no boundary, the current code point is added to the previous
|
||||
cell and processing of the code point is finished. See the :ref:`var_select`
|
||||
section below for handling of Unicode Variation selectors.
|
||||
section below for handling of Unicode Variation selectors. If the code point has
|
||||
``Grapheme_Cluster_Break=SpacingMark`` and a non-zero width and the previous
|
||||
cell is one cell wide, the previous cell becomes two cells wide. This affects
|
||||
only :code:`U+0E33 THAI CHARACTER SARA AM` and :code:`U+0EB3 LAO VOWEL SIGN AM`.
|
||||
|
||||
#. If there is a boundary, but the width of the current code point is zero,
|
||||
it is added to the previous cell and processing is finished.
|
||||
|
|
|
|||
|
|
@ -1172,6 +1172,32 @@ move_widened_char_past_multiline_chars(Screen *self, text_loop_state *s, CPUCell
|
|||
else init_text_loop_line(self, s);
|
||||
}
|
||||
|
||||
static void
|
||||
widen_cell_to_two(Screen *self, text_loop_state *s, CPUCell *cp, GPUCell *gp, index_type xpos) {
|
||||
CPUCell *cpu_cell = cp + xpos;
|
||||
GPUCell *gpu_cell = gp + xpos;
|
||||
cpu_cell->is_multicell = true;
|
||||
cpu_cell->width = 2;
|
||||
cpu_cell->natural_width = true;
|
||||
if (!cpu_cell->scale) cpu_cell->scale = 1;
|
||||
if (xpos + 1 < self->columns) {
|
||||
CPUCell *second = cp + xpos + 1;
|
||||
if (second->is_multicell) {
|
||||
if (second->y) {
|
||||
move_widened_char_past_multiline_chars(self, s, cpu_cell, gpu_cell, xpos, s->prev.y);
|
||||
return;
|
||||
}
|
||||
nuke_multicell_char_at(self, xpos + 1, s->prev.y, false);
|
||||
}
|
||||
zero_cells(s, second, gp + xpos + 1);
|
||||
self->cursor->x++;
|
||||
*second = *cpu_cell;
|
||||
second->x = 1;
|
||||
} else {
|
||||
move_widened_char_past_multiline_chars(self, s, cpu_cell, gpu_cell, xpos, s->prev.y);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
is_emoji_presentation_base(char_type ch) {
|
||||
return char_props_for(ch).is_emoji_presentation_base == 1;
|
||||
|
|
@ -1187,29 +1213,8 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
|
|||
if (!add_combining_char(self, ch, xpos, s->prev.y) || self->lc->count < 2) return;
|
||||
unsigned base_pos = self->lc->count - 2;
|
||||
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 && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
cpu_cell->is_multicell = true;
|
||||
cpu_cell->width = 2;
|
||||
cpu_cell->natural_width = true;
|
||||
if (!cpu_cell->scale) cpu_cell->scale = 1;
|
||||
if (xpos + 1 < self->columns) {
|
||||
CPUCell *second = cp + xpos + 1;
|
||||
if (second->is_multicell) {
|
||||
if (second->y) {
|
||||
move_widened_char_past_multiline_chars(self, s, cpu_cell, gpu_cell, xpos, s->prev.y);
|
||||
return;
|
||||
}
|
||||
nuke_multicell_char_at(self, xpos + 1, s->prev.y, false);
|
||||
}
|
||||
zero_cells(s, second, gp + xpos + 1);
|
||||
self->cursor->x++;
|
||||
*second = *cpu_cell;
|
||||
second->x = 1;
|
||||
} else {
|
||||
move_widened_char_past_multiline_chars(self, s, cpu_cell, gpu_cell, xpos, s->prev.y);
|
||||
}
|
||||
if (self->lc->chars[base_pos + 1] == VS16 && !cp[xpos].is_multicell && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
widen_cell_to_two(self, s, cp, gp, xpos);
|
||||
}
|
||||
} else if (ch == VS15) {
|
||||
const CPUCell *cpu_cell = cp + xpos;
|
||||
|
|
@ -1220,6 +1225,10 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
|
|||
init_segmentation_state(self, s);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Thai/Lao SARA AM is a SpacingMark with non-zero width, it widens a narrow base cell
|
||||
CharProps ch_props = char_props_for(ch);
|
||||
if (ch_props.grapheme_break == GBP_SpacingMark && wcwidth_std(ch_props) > 0 && !cp[xpos].is_multicell) widen_cell_to_two(self, s, cp, gp, xpos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ wcswidth_step(WCSState *state, const char_type ch) {
|
|||
state->prev_width = 1;
|
||||
} else state->prev_width = 0;
|
||||
break;
|
||||
default:
|
||||
// Thai/Lao SARA AM: a SpacingMark with non-zero width widens a narrow base cell
|
||||
if (cp.grapheme_break == GBP_SpacingMark && wcwidth_std(cp) > 0 && state->prev_width == 1) {
|
||||
ans = 1;
|
||||
state->prev_width = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -578,6 +578,12 @@ class TestDataTypes(BaseTest):
|
|||
self.ae(wcswidth('\U0001f610\ufe0e'), 1)
|
||||
self.ae(wcswidth('\U0001f1e6a'), 3)
|
||||
self.ae(wcswidth('\U0001f1e6a\U0001f1e8a'), 6)
|
||||
# Thai/Lao SARA AM is a SpacingMark with width 1, it widens the cell it combines into
|
||||
self.ae(wcswidth('จำ'), 2)
|
||||
self.ae(wcswidth('ำ'), 1)
|
||||
self.ae(wcswidth('กิ'), 1)
|
||||
self.ae(wcswidth('จำำ'), 2)
|
||||
self.ae(wcswidth('ກຳ'), 2)
|
||||
self.ae(wcswidth('\U0001f1e6\U0001f1e8a'), 3)
|
||||
self.ae(wcswidth('\U0001f1e6\U0001f1e8\U0001f1e6'), 4)
|
||||
self.ae(wcswidth('a\u00adb'), 2)
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,38 @@ class TestScreen(BaseTest):
|
|||
s.reset()
|
||||
s.draw('\N{HEAVY EXCLAMATION MARK SYMBOL}' + 4500 * '\N{VARIATION SELECTOR-16}')
|
||||
|
||||
def test_spacing_mark_widens_narrow_cell(self):
|
||||
# Thai SARA AM and Lao AM are SpacingMarks with width 1 that widen the cell they combine into
|
||||
s = self.create_screen(cols=5)
|
||||
s.draw('จำ')
|
||||
self.ae(s.cursor.x, 2)
|
||||
self.assertTrue(str(s.line(0)).startswith('จำ'))
|
||||
c = s.cpu_cells(0, 0)
|
||||
self.assertTrue(c['mcd'])
|
||||
self.ae(c['mcd']['width'], 2)
|
||||
self.ae(c['x'], 0)
|
||||
self.ae(s.cpu_cells(0, 1)['x'], 1)
|
||||
s.reset()
|
||||
s.draw('จ')
|
||||
self.ae(s.cursor.x, 1)
|
||||
s.draw('ำ')
|
||||
self.ae(s.cursor.x, 2)
|
||||
s.reset()
|
||||
s.draw('จำำ')
|
||||
self.ae(s.cursor.x, 2)
|
||||
s.reset()
|
||||
s.draw('ກຳ')
|
||||
self.ae(s.cursor.x, 2)
|
||||
s.reset()
|
||||
s.draw('กิ')
|
||||
self.ae(s.cursor.x, 1)
|
||||
# widened char on the last column wraps onto the next line
|
||||
s = self.create_screen(cols=2)
|
||||
s.draw('aจำ')
|
||||
self.ae((s.cursor.x, s.cursor.y), (2, 1))
|
||||
self.ae(str(s.line(0)), 'a')
|
||||
self.ae(str(s.line(1)), 'จำ')
|
||||
|
||||
def test_writing_with_cursor_on_trailer_of_wide_character(self):
|
||||
s = self.create_screen()
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,12 @@ func (self *WCWidthIterator) handle_rune(ch rune) error {
|
|||
self.current_width -= 1
|
||||
self.prev_width = 1
|
||||
}
|
||||
default:
|
||||
// Thai/Lao SARA AM: a SpacingMark with non-zero width widens a narrow base cell
|
||||
if cp.Grapheme_break() == uint8(GBP_SpacingMark) && cp.Width() > 0 && self.prev_width == 1 {
|
||||
self.current_width += 1
|
||||
self.prev_width = 2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
width := cp.Width()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ func TestWCSWidth(t *testing.T) {
|
|||
wcswidth("\U0001F1E6\U0001F1E8a", 3)
|
||||
wcswidth("\U0001F1E6\U0001F1E8\U0001F1E6", 4)
|
||||
wcswidth("a\u00adb", 2)
|
||||
// Thai/Lao SARA AM is a SpacingMark with width 1, it widens the cell it combines into
|
||||
wcswidth("จำ", 2)
|
||||
wcswidth("ำ", 1)
|
||||
wcswidth("กิ", 1)
|
||||
wcswidth("จำำ", 2)
|
||||
wcswidth("ກຳ", 2)
|
||||
wcswidth("a\x1b[22bcd", 25)
|
||||
// Flags individually and together
|
||||
wcwidth("\U0001f1ee\U0001f1f3", 2, 2)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue