Skip to content

Commit

Permalink
Allow more subscales
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Dec 5, 2024
1 parent a8cd3f3 commit 095c633
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 54 deletions.
3 changes: 2 additions & 1 deletion gen/apc_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ def parsers() -> None:
keymap = {
'w': ('width', 'uint'),
's': ('scale', 'uint'),
'f': ('subscale', 'uint'),
'n': ('subscale_n', 'uint'),
'd': ('subscale_d', 'uint'),
'v': ('vertical_align', 'uint'),
}
text = generate(
Expand Down
15 changes: 8 additions & 7 deletions kitty/fonts.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ typedef struct {
} Font;

typedef struct RunFont {
unsigned scale, subscale, vertical_align, multicell_y;
unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y;
ssize_t font_idx;
} RunFont;

Expand Down Expand Up @@ -256,9 +256,10 @@ static SpritePosition*
sprite_position_for(FontGroup *fg, RunFont rf, glyph_index *glyphs, unsigned glyph_count, uint8_t ligature_index, unsigned cell_count, int *error) {
bool created;
Font *font = fg->fonts + rf.font_idx;
uint8_t subscale = ((rf.subscale_n & 0xf) << 4) | (rf.subscale_d & 0xf);
SpritePosition *s = find_or_create_sprite_position(
font->sprite_position_hash_table, glyphs, glyph_count, ligature_index, cell_count,
rf.scale, rf.subscale, rf.multicell_y, rf.vertical_align, &created);
rf.scale, subscale, rf.multicell_y, rf.vertical_align, &created);
if (!s) { *error = 1; return NULL; }
if (created) {
s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z;
Expand Down Expand Up @@ -730,8 +731,8 @@ static void
scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) {
*width *= rf.scale;
*height *= rf.scale;
if (rf.subscale) {
double frac = 1. / (rf.subscale + 1);
if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) {
double frac = ((double)rf.subscale_n) / rf.subscale_d;
*width = (unsigned)ceil(frac * *width);
*height = (unsigned)ceil(frac * *height);
}
Expand All @@ -750,7 +751,7 @@ static void
calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) {
unsigned src_height = src->bottom;
Region src_in_full_coords = *src; unsigned full_dest_height = cell_height * rf.scale;
if (rf.subscale) {
if (rf.subscale_n && rf.subscale_d) {
switch(rf.vertical_align) {
case 0: break; // top aligned no change
case 1: // bottom aligned
Expand Down Expand Up @@ -1456,7 +1457,7 @@ cell_cap_for_codepoint(const char_type cp) {

static bool
run_fonts_are_equal(const RunFont *a, const RunFont *b) {
return a->font_idx == b->font_idx && a->scale == b->scale && a->subscale == b->subscale && a->vertical_align == b->vertical_align && a->multicell_y == b->multicell_y;
return a->font_idx == b->font_idx && a->scale == b->scale && a->subscale_n == b->subscale_n && a->subscale_d == b->subscale_d && a->vertical_align == b->vertical_align && a->multicell_y == b->multicell_y;
}

void
Expand All @@ -1479,7 +1480,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1;
continue;
}
cell_font.scale = cpu_cell->scale; cell_font.subscale = cpu_cell->subscale; cell_font.vertical_align = cpu_cell->vertical_align;
cell_font.scale = cpu_cell->scale; cell_font.subscale_n = cpu_cell->subscale_n; cell_font.subscale_d = cpu_cell->subscale_d; cell_font.vertical_align = cpu_cell->vertical_align;
cell_font.multicell_y = cpu_cell->y;
}
text_in_cell(cpu_cell, line->text_cache, lc);
Expand Down
11 changes: 7 additions & 4 deletions kitty/line.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ write_multicell_ansi_prefix(ANSILineState *s, const CPUCell *mcd) {
if (mcd->scale > 1) {
w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, s->output_buf); w(':');
}
if (mcd->subscale) {
w('S'); w('='); nonnegative_integer_as_utf32(mcd->subscale, s->output_buf); w(':');
if (mcd->subscale_n) {
w('n'); w('='); nonnegative_integer_as_utf32(mcd->subscale_n, s->output_buf); w(':');
}
if (mcd->subscale_d) {
w('d'); w('='); nonnegative_integer_as_utf32(mcd->subscale_d, s->output_buf); w(':');
}
if (s->output_buf->buf[s->output_buf->len - 1] == ':') s->output_buf->len--;
w(';');
Expand All @@ -92,12 +95,12 @@ close_multicell(ANSILineState *s) {

static void
start_multicell_if_needed(ANSILineState *s, const CPUCell *c) {
if (!c->natural_width || c->scale > 1 || c->subscale || c->vertical_align) write_multicell_ansi_prefix(s, c);
if (!c->natural_width || c->scale > 1 || c->subscale_n || c->subscale_d || c->vertical_align) write_multicell_ansi_prefix(s, c);
}

static bool
multicell_is_continuation_of_previous(const CPUCell *prev, const CPUCell *curr) {
if (prev->scale != curr->scale || prev->subscale != curr->subscale || prev->vertical_align != curr->vertical_align) return false;
if (prev->scale != curr->scale || prev->subscale_n != curr->subscale_n || prev->subscale_d != curr->subscale_d || prev->vertical_align != curr->vertical_align) return false;
if (prev->natural_width) return curr->natural_width;
return prev->width == curr->width && !curr->natural_width;
}
Expand Down
10 changes: 4 additions & 6 deletions kitty/line.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
// TODO: Handle selection with multicell
// TODO: URL detection with multicell
// TODO: Cursor rendering over multicell
// TODO: Test the escape codes to delete and insert characters and lines with multicell
// TODO: Handle replay of dumped graphics_command and multicell_command
// TODO: Handle rewrap and restitch of multiline chars
// TODO: Handle rewrap when a character is too wide/tall to fit on resized screen
// TODO: Test serialization to ansi only using escape code for explicitly set multicells
// TODO: Test rendering of box drawing at various scales and subscales and alignments
// TODO: Implement baseline align for box drawing

typedef union CellAttrs {
Expand Down Expand Up @@ -64,11 +61,12 @@ typedef union CPUCell {
char_type natural_width: 1;
char_type x : 8;
char_type y : 4;
char_type subscale: 2;
char_type subscale_n: 4;
char_type subscale_d: 4;
char_type scale: 3;
char_type width: 3;
char_type vertical_align: 3;
char_type : 21;
char_type : 15;
};
struct {
char_type ch_and_idx: sizeof(char_type) * 8;
Expand Down Expand Up @@ -103,7 +101,7 @@ typedef struct {
} Line;

typedef struct MultiCellCommand {
unsigned int width, scale, subscale, vertical_align;
unsigned int width, scale, subscale_n, subscale_d, vertical_align;
size_t payload_sz;
} MultiCellCommand;

Expand Down
29 changes: 20 additions & 9 deletions kitty/parse-multicell-command.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions kitty/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -1203,8 +1203,8 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const
self->lc->count = decode_utf8_safe_string(payload, cmd->payload_sz, self->lc->chars);
if (!self->lc->count) return;
CPUCell mcd = {
.width=MIN(cmd->width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale=MIN(cmd->subscale, 3u),
.vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true
.width=MIN(cmd->width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale_n=MIN(cmd->subscale_n, 15u),
.subscale_d=MIN(cmd->subscale_d, 15), .vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true
};
if (mcd.width) handle_fixed_width_multicell_command(self, mcd, self->lc);
else {
Expand Down Expand Up @@ -5188,7 +5188,10 @@ test_parse_written_data(Screen *screen, PyObject *args) {

static PyObject*
multicell_data_as_dict(CPUCell mcd) {
return Py_BuildValue("{sI sI sI sO sI}", "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, "subscale", (unsigned int)mcd.subscale, "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.vertical_align);
return Py_BuildValue("{sI sI sI sI sO sI}",
"scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width,
"subscale_n", (unsigned int)mcd.subscale_n, "subscale_d", (unsigned int)mcd.subscale_d,
"natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.vertical_align);
}

static PyObject*
Expand Down
4 changes: 2 additions & 2 deletions kitty_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def parse_bytes(screen, data, dump_callback=None):
screen.test_parse_written_data(dump_callback)


def draw_multicell(screen: Screen, text: str, width: int = 0, scale: int = 1, subscale: int = 0, vertical_align: int = 0) -> None:
cmd = f'\x1b]{TEXT_SIZE_CODE};w={width}:s={scale}:f={subscale}:v={vertical_align};{text}\a'
def draw_multicell(screen: Screen, text: str, width: int = 0, scale: int = 1, subscale_n: int = 0, subscale_d: int = 0, vertical_align: int = 0) -> None:
cmd = f'\x1b]{TEXT_SIZE_CODE};w={width}:s={scale}:n={subscale_n}:d={subscale_d}:v={vertical_align};{text}\a'
parse_bytes(screen, cmd.encode())


Expand Down
27 changes: 15 additions & 12 deletions kitty_tests/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,39 +211,42 @@ def test_box_drawing(self):
def test_scaled_box_drawing(self):
block_size = self.cell_width * self.cell_height * 4

def full_block(subscale):
def full_block():
return b'\xff' * block_size

def empty_block(subscale):
def empty_block():
return b'\0' * block_size

def half_block(subscale, first=b'\xff', second=b'\0'):
frac = 1 / (subscale + 1)
def half_block(first=b'\xff', second=b'\0'):
frac = 0.5
height = ceil(frac * self.cell_height)
rest = self.cell_height - height
return (first * (rest * self.cell_width * 4)) + (second * height * self.cell_width * 4)

def upper_half_block(subscale):
return half_block(subscale)
def upper_half_block():
return half_block()

def lower_half_block(subscale):
return half_block(subscale, b'\0', b'\xff')
def lower_half_block():
return half_block(b'\0', b'\xff')

s = self.create_screen(cols=8, lines=8, scrollback=0)

def block_test(a=empty_block, b=empty_block, c=empty_block, d=empty_block, scale=2, subscale=1, vertical_align=0):
def block_test(a=empty_block, b=empty_block, c=empty_block, d=empty_block, scale=2, half_block=True, vertical_align=0):
s.reset()
before = len(self.sprites)
draw_multicell(s, '█', scale=scale, subscale=subscale, vertical_align=vertical_align)
subscale_n = subscale_d = 0
if half_block:
subscale_n, subscale_d = 1, 2
draw_multicell(s, '█', scale=scale, subscale_n=subscale_n, subscale_d=subscale_d, vertical_align=vertical_align)
test_render_line(s.line(0))
self.ae(len(self.sprites), before + 2)
test_render_line(s.line(1))
self.ae(len(self.sprites), before + 4)
blocks = tuple(self.sprites)[before:]
for i, (expected, actual) in enumerate(zip((a(subscale), b(subscale), c(subscale), d(subscale)), blocks)):
for i, (expected, actual) in enumerate(zip((a(), b(), c(), d()), blocks)):
self.ae(self.sprites[actual], expected, f'The {i} block differs')

block_test(full_block, full_block, full_block, full_block, subscale=0)
block_test(full_block, full_block, full_block, full_block, half_block=False)
block_test(a=full_block)
block_test(c=full_block, vertical_align=1)
block_test(a=lower_half_block, c=upper_half_block, vertical_align=2)
Expand Down
23 changes: 13 additions & 10 deletions kitty_tests/multicell.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def ae(key):
ae('y')
ae('width')
ae('scale')
ae('subscale')
ae('subscale_n')
ae('subscale_d')
ae('vertical_align')
ae('text')
ae('natural_width')
Expand Down Expand Up @@ -90,21 +91,21 @@ def assert_cursor_at(x, y):
s.reset()
ac(0, 0, is_multicell=False)
multicell(s, 'a')
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True, cursor=(1, 0))
ac(0, 0, is_multicell=True, width=1, scale=1, subscale_n=0, x=0, y=0, text='a', natural_width=True, cursor=(1, 0))
ac(0, 1, is_multicell=False), ac(1, 0, is_multicell=False), ac(1, 1, is_multicell=False)
s.draw('莊')
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True)
ac(1, 0, is_multicell=True, width=2, scale=1, subscale=0, x=0, y=0, text='莊', natural_width=True, cursor=(3, 0))
ac(2, 0, is_multicell=True, width=2, scale=1, subscale=0, x=1, y=0, text='', natural_width=True)
ac(0, 0, is_multicell=True, width=1, scale=1, subscale_n=0, x=0, y=0, text='a', natural_width=True)
ac(1, 0, is_multicell=True, width=2, scale=1, subscale_n=0, x=0, y=0, text='莊', natural_width=True, cursor=(3, 0))
ac(2, 0, is_multicell=True, width=2, scale=1, subscale_n=0, x=1, y=0, text='', natural_width=True)
for x in range(s.columns):
ac(x, 1, is_multicell=False)
s.cursor.x = 0
multicell(s, 'a', width=2, scale=2, subscale=3)
ac(0, 0, is_multicell=True, width=2, scale=2, subscale=3, x=0, y=0, text='a', natural_width=False, cursor=(4, 0))
multicell(s, 'a', width=2, scale=2, subscale_n=3)
ac(0, 0, is_multicell=True, width=2, scale=2, subscale_n=3, x=0, y=0, text='a', natural_width=False, cursor=(4, 0))
for x in range(1, 4):
ac(x, 0, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=0, text='', natural_width=False)
ac(x, 0, is_multicell=True, width=2, scale=2, subscale_n=3, x=x, y=0, text='', natural_width=False)
for x in range(0, 4):
ac(x, 1, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=1, text='', natural_width=False)
ac(x, 1, is_multicell=True, width=2, scale=2, subscale_n=3, x=x, y=1, text='', natural_width=False)

# Test draw with cursor in a multicell
s.reset()
Expand All @@ -116,7 +117,7 @@ def assert_cursor_at(x, y):
s.cursor.x = 0
s.draw('a'), ac(0, 0, is_multicell=False), ac(1, 0, is_multicell=False)
s.reset()
multicell(s, 'a', width=2, scale=2, subscale=3)
multicell(s, 'a', width=2, scale=2, subscale_n=3, subscale_d=4)
s.cursor.x, s.cursor.y = 1, 1
s.draw('b')
self.ae(8, count_multicells())
Expand Down Expand Up @@ -398,6 +399,8 @@ def ta(expected):
s.reset()

s.reset()
multicell(s, 'a', width=2, scale=3, subscale_n=1, subscale_d=2, vertical_align=1)
ta('\x1b]66;w=2:s=3:n=1:d=2;a\x07')
s.draw('a')
multicell(s, 'b', width=2)
s.draw('c')
Expand Down

0 comments on commit 095c633

Please sign in to comment.