From a2c4f830b3e7ed7d6ab94df1ee677a908e3c5fb3 Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Thu, 13 Jan 2022 17:27:02 +0100 Subject: [PATCH 1/6] Enable use of higher options for underlining text. In `Colored and styled underlines` it's proposed that the SGR codes \e[4:4m and \e[4:5m are used to add a dotted or dashed underline to the rendering context respectively. This commit prepares the necessary changes to add the two additional underline style, while still rendering them as a normal underline and curly underline. --- kitty/cell_vertex.glsl | 4 ++-- kitty/cursor.c | 4 ++-- kitty/data-types.h | 2 +- kitty/fonts/render.py | 4 ++-- kitty/shaders.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index e44c57053..af772a0c5 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -79,8 +79,8 @@ const uint COLOR_MASK = uint(0x4000); const uint ZERO = uint(0); const uint ONE = uint(1); const uint TWO = uint(2); -const uint THREE = uint(3); const uint FOUR = uint(4); +const uint SEVEN = uint(7); vec3 color_to_vec(uint c) { uint r, g, b; @@ -193,7 +193,7 @@ void main() { foreground = choose_color(float(is_selected & ONE), selection_color, foreground); decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg); // Underline and strike through (rendered via sprites) - underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & THREE, ZERO, ZERO)); + underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & SEVEN, ZERO, ZERO)); strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO); // Cursor diff --git a/kitty/cursor.c b/kitty/cursor.c index f129c438b..8c1aee84e 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -90,7 +90,7 @@ START_ALLOW_CASE_RANGE case 3: self->italic = true; break; case 4: - if (i < count) { self->decoration = MIN(3, params[i]); i++; } + if (i < count) { self->decoration = MIN(5, params[i]); i++; } else self->decoration = 1; break; case 7: @@ -161,7 +161,7 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un S(italic, true); case 4: { uint8_t val = 1; - if (i < count) { val = MIN(3, params[i]); i++; } + if (i < count) { val = MIN(5, params[i]); i++; } S(decoration, val); } case 7: diff --git a/kitty/data-types.h b/kitty/data-types.h index 42287b00e..b04cb75c2 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -142,7 +142,7 @@ typedef struct { typedef union CellAttrs { struct { uint16_t width : 2; - uint16_t decoration : 2; + uint16_t decoration : 3; uint16_t bold : 1; uint16_t italic : 1; uint16_t reverse : 1; diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 989497ab9..2c34de2b3 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -313,7 +313,7 @@ def render_special( t = underline_thickness if underline > 1: t = max(1, min(cell_height - underline_position - 1, t)) - dl([add_line, add_line, add_dline, add_curl][underline], underline_position, t, cell_height) + dl([add_line, add_line, add_dline, add_curl, add_line, add_curl][underline], underline_position, t, cell_height) if strikethrough: dl(add_line, strikethrough_position, strikethrough_thickness, cell_height) @@ -384,7 +384,7 @@ def prerender_function( render_cursor, cursor_beam_thickness=cursor_beam_thickness, cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width, cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y) - cells = f(1), f(2), f(3), f(0, True), f(missing=True), c(1), c(2), c(3) + cells = f(1), f(2), f(3), f(4), f(5), f(0, True), f(missing=True), c(1), c(2), c(3) return tuple(map(ctypes.addressof, cells)), cells diff --git a/kitty/shaders.c b/kitty/shaders.c index f448c8f0d..d99166ff0 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -314,7 +314,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c } rd->use_cell_for_selection_bg = IS_SPECIAL_COLOR(highlight_bg) ? 1. : 0.; // Cursor position - enum { BLOCK_IDX = 0, BEAM_IDX = 6, UNDERLINE_IDX = 7, UNFOCUSED_IDX = 8 }; + enum { BLOCK_IDX = 0, BEAM_IDX = 8, UNDERLINE_IDX = 9, UNFOCUSED_IDX = 10 }; if (cursor->is_visible) { rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y; if (cursor->is_focused) { From e86c7d668cd7a32ed5faeb22dfe98719fe1e12d5 Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Mon, 17 Jan 2022 13:25:08 +0100 Subject: [PATCH 2/6] Add rendering functions for dotted and dashed underline. Dashed underline looks pretty good regardless of conditions, but the dotted underline only looks good/correct on certain font-sizes. This is due to the underline being rendered on a per cell/glyph basis (so one can not place a dot directly between two letters, say. Could be remedied by pulling the rendering of the underlines into the shader, but that is more work. --- kitty/fonts/render.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 2c34de2b3..763f65e75 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -279,6 +279,21 @@ def add_curl(buf: CBufType, cell_width: int, position: int, thickness: int, cell add_intensity(x, y1 + t, 255) +def add_dots(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None: + y = 1 + position - thickness // 2 + for i in range(y, min(y + thickness, cell_height)): + for j in range(0, cell_width, 2 * thickness): + buf[cell_width * i + j:cell_width * i + min(j + thickness, cell_width)] = [255] * min(thickness, cell_width - j) + + +def add_dashes(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None: + halfspace_width = cell_width // 4 + y = 1 + position - thickness // 2 + for i in range(y, min(y + thickness, cell_height)): + buf[cell_width * i:cell_width * i + (cell_width - 3 * halfspace_width)] = [255] * (cell_width - 3 * halfspace_width) + buf[cell_width * i + 3 * halfspace_width:cell_width * (i + 1)] = [255] * (cell_width - 3 * halfspace_width) + + def render_special( underline: int = 0, strikethrough: bool = False, @@ -313,7 +328,7 @@ def render_special( t = underline_thickness if underline > 1: t = max(1, min(cell_height - underline_position - 1, t)) - dl([add_line, add_line, add_dline, add_curl, add_line, add_curl][underline], underline_position, t, cell_height) + dl([add_line, add_line, add_dline, add_curl, add_dots, add_dashes][underline], underline_position, t, cell_height) if strikethrough: dl(add_line, strikethrough_position, strikethrough_thickness, cell_height) From 3c6c36487f0cd357af9f6c4cb74bb11443166628 Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Tue, 18 Jan 2022 12:13:51 +0100 Subject: [PATCH 3/6] Update underline documentation --- docs/underlines.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/underlines.rst b/docs/underlines.rst index de63eaae2..f4407ffa4 100644 --- a/docs/underlines.rst +++ b/docs/underlines.rst @@ -13,8 +13,8 @@ To set the underline style:: [4:1m # this is a straight underline [4:2m # this is a double underline [4:3m # this is a curly underline - [4:4m # this is a dotted underline (not implemented in kitty) - [4:5m # this is a dashed underline (not implemented in kitty) + [4:4m # this is a dotted underline + [4:5m # this is a dashed underline [4m # this is a straight underline (for backwards compat) [24m # this is no underline (for backwards compat) From dd192ad0b79277dd0d1f0d7f07dc7172cf4032a9 Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Tue, 18 Jan 2022 12:28:24 +0100 Subject: [PATCH 4/6] Make old tests run Previously, because of the new underline styles a couple of tests were failing due to an unexpected number of sprites being returned from the test-set-up. No new tests were added. --- kitty_tests/fonts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index d30b7c6b3..d841f0687 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -28,7 +28,7 @@ class Rendering(BaseTest): self.test_ctx.__enter__() self.sprites, self.cell_width, self.cell_height = self.test_ctx.__enter__() try: - self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4, 5, 6, 7, 8]) + self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) except Exception: self.test_ctx.__exit__() del self.test_ctx From da486153de38ebcfe0728b94c136c24544ed944c Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Tue, 18 Jan 2022 12:52:59 +0100 Subject: [PATCH 5/6] Add (possibly erroneous) test for new underline. Also make more clear *what* exactly is rendered in the cell (i.e. a strikethrough). --- kitty/fonts/render.py | 2 +- kitty_tests/fonts.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 763f65e75..cee46aa99 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -399,7 +399,7 @@ def prerender_function( render_cursor, cursor_beam_thickness=cursor_beam_thickness, cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width, cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y) - cells = f(1), f(2), f(3), f(4), f(5), f(0, True), f(missing=True), c(1), c(2), c(3) + cells = f(1), f(2), f(3), f(4), f(5), f(0, strikethrough=True), f(missing=True), c(1), c(2), c(3) return tuple(map(ctypes.addressof, cells)), cells diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index d841f0687..613de3e24 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -44,15 +44,15 @@ class Rendering(BaseTest): sprite_map_set_limits(10, 2) sprite_map_set_layout(5, 5) self.ae(test_sprite_position_for(0), (0, 0, 0)) - self.ae(test_sprite_position_for(0), (0, 0, 0)) self.ae(test_sprite_position_for(1), (1, 0, 0)) self.ae(test_sprite_position_for(2), (0, 1, 0)) self.ae(test_sprite_position_for(3), (1, 1, 0)) self.ae(test_sprite_position_for(4), (0, 0, 1)) self.ae(test_sprite_position_for(5), (1, 0, 1)) - self.ae(test_sprite_position_for(0, 1), (0, 1, 1)) - self.ae(test_sprite_position_for(0, 2), (1, 1, 1)) - self.ae(test_sprite_position_for(0, 2), (1, 1, 1)) + self.ae(test_sprite_position_for(6), (0, 1, 1)) + self.ae(test_sprite_position_for(7), (1, 1, 1)) + self.ae(test_sprite_position_for(0, 1), (0, 0, 2)) + self.ae(test_sprite_position_for(0, 2), (1, 0, 2)) def test_box_drawing(self): prerendered = len(self.sprites) From ee2c9775a73487b89e7dcc18da6eed435607db13 Mon Sep 17 00:00:00 2001 From: Joseph Adams Date: Tue, 18 Jan 2022 15:46:31 +0100 Subject: [PATCH 6/6] Use `distribute_dots()` to even out spacing in underline --- kitty/fonts/render.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index cee46aa99..2e3aa7768 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -17,7 +17,7 @@ from kitty.fast_data_types import ( test_render_line, test_shape ) from kitty.fonts.box_drawing import ( - BufType, render_box_char, render_missing_glyph + BufType, distribute_dots, render_box_char, render_missing_glyph ) from kitty.options.types import Options, defaults from kitty.typing import CoreTextFont, FontConfigPattern @@ -280,10 +280,12 @@ def add_curl(buf: CBufType, cell_width: int, position: int, thickness: int, cell def add_dots(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None: + spacing, size = distribute_dots(cell_width, cell_width // (2 * thickness)) + y = 1 + position - thickness // 2 for i in range(y, min(y + thickness, cell_height)): - for j in range(0, cell_width, 2 * thickness): - buf[cell_width * i + j:cell_width * i + min(j + thickness, cell_width)] = [255] * min(thickness, cell_width - j) + for j, s in enumerate(spacing): + buf[cell_width * i + j * size + s: cell_width * i + (j + 1) * size + s] = [255] * size def add_dashes(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None: