diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index fdad3c453..dc6a19e45 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -15,6 +15,7 @@ in vec3 sprite_pos; in vec3 underline_pos; in vec3 cursor_pos; in vec3 strike_pos; +in vec3 underline_exclusion_pos; in vec3 foreground; in vec4 cursor_color_premult; in vec3 decoration_fg; @@ -128,6 +129,8 @@ vec4 load_text_foreground_color() { vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) { // Return premul foreground color from decorations (cursor, underline, strikethrough) float underline_alpha = texture(sprites, underline_pos).a; + float underline_exclusion = texture(sprites, underline_exclusion_pos).a; + underline_alpha *= 1.0f - underline_exclusion; float strike_alpha = texture(sprites, strike_pos).a; float cursor_alpha = texture(sprites, cursor_pos).a; // Since strike and text are the same color, we simply add the alpha values diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 2cbef2646..d96e7a840 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -8,7 +8,7 @@ layout(std140) uniform CellRenderData { uint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; - uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx; + uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; float cursor_x, cursor_y, cursor_w, cursor_opacity; // must have unique entries with 0 being default_bg and unset being UINT32_MAX @@ -51,6 +51,7 @@ out vec3 underline_pos; out vec3 cursor_pos; out vec4 cursor_color_premult; out vec3 strike_pos; +out vec3 underline_exclusion_pos; out vec3 foreground; out vec3 decoration_fg; out float colored_sprite; @@ -108,9 +109,18 @@ vec3 to_sprite_pos(uvec2 pos, uint idx) { uvec3 c = to_sprite_coords(idx); vec2 s_xpos = vec2(c.x, float(c.x) + 1.0f) * (1.0f / float(sprites_xnum)); vec2 s_ypos = vec2(c.y, float(c.y) + 1.0f) * (1.0f / float(sprites_ynum)); + uint texture_height_px = (cell_height + 1u) * sprites_ynum; + float row_height = 1.0f / float(texture_height_px); + s_ypos[1] -= row_height; // skip the decorations_exclude row return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z); } +vec3 to_underline_exclusion_pos(uvec2 pos, vec3 sprite_pos) { + uvec3 c = to_sprite_coords(sprite_idx[0]); + float y = (float(c.y) + 1.0f) * (1.0f / float(sprites_ynum)); + return vec3(sprite_pos.x, y, sprite_pos.z); +} + uint read_sprite_decorations_idx() { int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK); ivec2 sz = textureSize(sprite_decorations_map, 0); @@ -232,6 +242,7 @@ void main() { uvec2 decs = get_decorations_indices(uint(in_url), text_attrs); strike_pos = to_sprite_pos(cell_data.pos, decs[0]); underline_pos = to_sprite_pos(cell_data.pos, decs[1]); + underline_exclusion_pos = to_underline_exclusion_pos(cell_data.pos, sprite_pos); // Cursor cursor_color_premult = vec4(color_to_vec(cursor_bg) * cursor_opacity, cursor_opacity); diff --git a/kitty/decorations.c b/kitty/decorations.c index dae32f403..1df1ae425 100644 --- a/kitty/decorations.c +++ b/kitty/decorations.c @@ -121,12 +121,13 @@ add_dashed_underline(uint8_t *buf, FontCellMetrics fcm) { return ans; } -static void +static unsigned add_intensity(uint8_t *buf, unsigned x, unsigned y, uint8_t val, unsigned max_y, unsigned position, unsigned cell_width) { y += position; y = MIN(y, max_y); unsigned idx = cell_width * y + x; buf[idx] = MIN(255, buf[idx] + val); + return y; } DecorationGeometry @@ -152,10 +153,12 @@ add_curl_underline(uint8_t *buf, FontCellMetrics fcm) { for (unsigned x = 0; x < fcm.cell_width; x++) { double y = half_height * cos(x * xfactor); unsigned y1 = (unsigned)floor(y - thickness), y2 = (unsigned)ceil(y); - unsigned i1 = (unsigned)(255. * fabs(y - floor(y))); - miny = MIN(miny, y1); maxy = MAX(maxy, y2); - add_intensity(buf, x, y1, 255 - i1, max_y, position, fcm.cell_width); // upper bound - add_intensity(buf, x, y2, i1, max_y, position, fcm.cell_width); // lower bound + unsigned intensity = (unsigned)(255. * fabs(y - floor(y))); + unsigned i1 = 255 - intensity, i2 = intensity; + unsigned yc = add_intensity(buf, x, y1, i1, max_y, position, fcm.cell_width); // upper bound + if (i1) { if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; } + yc = add_intensity(buf, x, y2, i2, max_y, position, fcm.cell_width); // lower bound + if (i2) { if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; } // fill between upper and lower bound for (unsigned t = 1; t <= thickness; t++) add_intensity(buf, x, y1 + t, 255, max_y, position, fcm.cell_width); } diff --git a/kitty/fonts.c b/kitty/fonts.c index 0a99c6319..0e204a657 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -161,7 +161,7 @@ python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) { static void ensure_canvas_can_fit(FontGroup *fg, unsigned cells, unsigned scale) { -#define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * fg->fcm.cell_height * scale * scale) +#define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale) size_t size_in_bytes = cs(cells, scale); if (size_in_bytes > fg->canvas.size_in_bytes) { free(fg->canvas.buf); @@ -335,11 +335,28 @@ sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_wi sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0; } +static void +calculate_underline_exclusion_zones(pixel *buf, const FontGroup *fg, DecorationGeometry dg) { + pixel *ans = buf + fg->fcm.cell_height * fg->fcm.cell_width; + const unsigned bottom = MIN(dg.top + dg.height, fg->fcm.cell_height); + const unsigned thickness = MAX(1u, fg->fcm.underline_thickness / 1); + for (unsigned x = 0; x < fg->fcm.cell_width; x++) { + for (unsigned y = dg.top + 2; y < bottom && !ans[x]; y++) { + if ((buf[y * fg->fcm.cell_width + x] & 0x000000ff) > 0) { + unsigned start_x = x > thickness ? x - thickness : 0; + for (unsigned dx = start_x; dx < MIN(x + thickness, fg->fcm.cell_width); dx++) ans[dx] = 0xffffffff; + break; + } + } + } +} + static sprite_index current_send_sprite_to_gpu(FontGroup *fg, pixel *buf, DecorationMetadata dec) { sprite_index ans = current_sprite_index(&fg->sprite_tracker); if (!do_increment(fg)) return 0; if (python_send_to_gpu_impl) { python_send_to_gpu(fg, ans, buf); return ans; } + if (dec.underline_region.height) calculate_underline_exclusion_zones(buf, fg, dec.underline_region); send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, ans, buf, dec.start_idx); if (0) { printf("Sprite: %u dec_idx: %u\n", ans, dec.start_idx); display_rgba_data(buf, fg->fcm.cell_width, fg->fcm.cell_height); printf("\n"); } return ans; @@ -832,7 +849,7 @@ apply_scale_to_font_group(FontGroup *fg, RunFont *rf) { static pixel* pointer_to_space_for_last_sprite(Canvas *canvas, FontCellMetrics fcm) { - return canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - fcm.cell_width * fcm.cell_height); + return canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - fcm.cell_width * (fcm.cell_height + 1)); } static pixel* @@ -896,12 +913,14 @@ map_scaled_decoration_geometry(DecorationGeometry sdg, Region src, Region dest) unsigned unscaled_top = dest.top + (scaled_top - src.top); unsigned unscaled_bottom = unscaled_top + (scaled_bottom > scaled_top ? scaled_bottom - scaled_top : 0); unscaled_bottom = MIN(unscaled_bottom, dest.bottom); + /*printf("111111 src: (%u, %u) dest: (%u, %u) sdg: (%u, %u) scaled: (%u, %u) unscaled: (%u, %u)\n",*/ + /* src.top, src.bottom, dest.top, dest.bottom, sdg.top, sdg.top + sdg.height, scaled_top, scaled_bottom, unscaled_top, unscaled_bottom);*/ return (Region){.top=unscaled_top, .bottom=MAX(unscaled_top, unscaled_bottom)}; } static void render_scaled_decoration(FontCellMetrics unscaled_metrics, FontCellMetrics scaled_metrics, uint8_t *alpha_mask, pixel *output, Region src, Region dest) { - memset(output, 0, unscaled_metrics.cell_width * unscaled_metrics.cell_height * sizeof(output[0])); + memset(output, 0, unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1) * sizeof(output[0])); unsigned src_limit = MIN(scaled_metrics.cell_height, src.bottom), dest_limit = MIN(unscaled_metrics.cell_height, dest.bottom); unsigned cell_width = MIN(scaled_metrics.cell_width, unscaled_metrics.cell_width); for (unsigned srcy = src.top, desty=dest.top; srcy < src_limit && desty < dest_limit; srcy++, desty++) { @@ -918,7 +937,7 @@ render_decorations(FontGroup *fg, Region src, Region dest, FontCellMetrics scale const FontCellMetrics unscaled_metrics = fg->fcm; scaled_metrics.cell_width = unscaled_metrics.cell_width; RAII_ALLOC(uint8_t, alpha_mask, malloc(scaled_metrics.cell_height * scaled_metrics.cell_width)); - RAII_ALLOC(pixel, buf, malloc(unscaled_metrics.cell_width * unscaled_metrics.cell_height * sizeof(pixel))); + RAII_ALLOC(pixel, buf, malloc(unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1) * sizeof(pixel))); if (!alpha_mask || !buf) fatal("Out of memory"); sprite_index ans = 0; bool is_underline = false; uint32_t underline_top = unscaled_metrics.cell_height, underline_bottom = 0; diff --git a/kitty/shaders.c b/kitty/shaders.c index 28bf78dc4..db690a13d 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -173,7 +173,7 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) { sprite_tracker_current_layout(fg, &xnum, &ynum, &z); znum = z + 1; SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; - width = xnum * fg->fcm.cell_width; height = ynum * fg->fcm.cell_height; + width = xnum * fg->fcm.cell_width; height = ynum * (fg->fcm.cell_height + 1); const GLenum texture_type = GL_TEXTURE_2D_ARRAY; GLuint tex = setup_new_sprites_texture(texture_type); glTexStorage3D(texture_type, 1, GL_SRGB8_ALPHA8, width, height, znum); @@ -223,8 +223,8 @@ send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf, sprite_in glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z); - x *= fg->fcm.cell_width; y *= fg->fcm.cell_height; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); + x *= fg->fcm.cell_width; y *= (fg->fcm.cell_height + 1); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height + 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); } void @@ -368,7 +368,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c GLuint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; - GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx; + GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; GLfloat cursor_x, cursor_y, cursor_w, cursor_opacity; GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7; GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; @@ -459,6 +459,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z); rd->sprites_xnum = x; rd->sprites_ynum = y; rd->inverted = screen_invert_colors(screen) ? 1 : 0; + rd->cell_height = os_window->fonts_data->fcm.cell_height; #undef COLOR rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index 166fb1ef8..32e089181 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -283,7 +283,7 @@ class Rendering(FontBaseTest): def test_sprite_map(self): sprite_map_set_limits(10, 3) - sprite_map_set_layout(5, 5) + sprite_map_set_layout(5, 4) # 4 because of underline_exclusion row self.ae(test_sprite_position_increment(), (0, 0, 0)) self.ae(test_sprite_position_increment(), (1, 0, 0)) self.ae(test_sprite_position_increment(), (0, 1, 0))