From 2e6bc3c61ad691c9ab3880ec38b2b059a2fecbcb Mon Sep 17 00:00:00 2001 From: kraftwerk28 Date: Wed, 22 Jun 2022 01:54:37 +0300 Subject: [PATCH] vt: underline colors and style from kitty --- csi.c | 46 ++++++++++++++++- meson.build | 5 +- meson_options.txt | 3 ++ render.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++ terminal.h | 26 ++++++++++ 5 files changed, 202 insertions(+), 3 deletions(-) diff --git a/csi.c b/csi.c index b83fc9b3..89e8e342 100644 --- a/csi.c +++ b/csi.c @@ -88,17 +88,44 @@ csi_sgr(struct terminal *term) case 1: term->vt.attrs.bold = true; break; case 2: term->vt.attrs.dim = true; break; case 3: term->vt.attrs.italic = true; break; - case 4: term->vt.attrs.underline = true; break; + case 4: { +#if FOOT_EXT_UNDERLINE + const struct vt_subparams p = term->vt.params.v[i].sub; + struct attributes *attr = &term->vt.attrs; + if (p.idx == 0) { + attr->ul_style = UNDERLINE_STRAIGHT; + break; + } + switch (p.value[0]) { + case 0: attr->ul_style = UNDERLINE_NONE; break; + case 1: attr->ul_style = UNDERLINE_STRAIGHT; break; + case 2: attr->ul_style = UNDERLINE_DOUBLE; break; + case 3: attr->ul_style = UNDERLINE_CURLY; break; + case 4: attr->ul_style = UNDERLINE_DOTTED; break; + case 5: attr->ul_style = UNDERLINE_DASHED; break; + } +#else + term->vt.attrs.underline = true; +#endif + break; + } case 5: term->vt.attrs.blink = true; break; case 6: LOG_WARN("ignored: rapid blink"); break; case 7: term->vt.attrs.reverse = true; break; case 8: term->vt.attrs.conceal = true; break; case 9: term->vt.attrs.strikethrough = true; break; - +#if FOOT_EXT_UNDERLINE + case 21: term->vt.attrs.ul_style = UNDERLINE_DOUBLE; break; +#else case 21: break; /* double-underline, not implemented */ +#endif case 22: term->vt.attrs.bold = term->vt.attrs.dim = false; break; case 23: term->vt.attrs.italic = false; break; +#if FOOT_EXT_UNDERLINE + case 24: term->vt.attrs.ul_style = UNDERLINE_NONE; break; +#else case 24: term->vt.attrs.underline = false; break; +#endif case 25: term->vt.attrs.blink = false; break; case 26: break; /* rapid blink, ignored */ case 27: term->vt.attrs.reverse = false; break; @@ -119,6 +146,9 @@ csi_sgr(struct terminal *term) break; case 38: +#if FOOT_EXT_UNDERLINE + case 58: +#endif case 48: { uint32_t color; enum color_source src; @@ -197,18 +227,30 @@ csi_sgr(struct terminal *term) if (param == 38) { term->vt.attrs.fg_src = src; term->vt.attrs.fg = color; +#if FOOT_EXT_UNDERLINE + } else if (param == 58) { + term->vt.attrs.ul_src = src; + term->vt.attrs.ul = color; +#endif } else { xassert(param == 48); term->vt.attrs.bg_src = src; term->vt.attrs.bg = color; } break; + } case 39: term->vt.attrs.fg_src = COLOR_DEFAULT; break; +#if FOOT_EXT_UNDERLINE + case 59: + term->vt.attrs.ul_src = COLOR_DEFAULT; + break; +#endif + /* Regular background colors */ case 40: case 41: diff --git a/meson.build b/meson.build index 719352bc..3f153ed4 100644 --- a/meson.build +++ b/meson.build @@ -76,7 +76,10 @@ add_project_arguments( cc.get_supported_arguments( ['-pedantic', '-fstrict-aliasing', - '-Wstrict-aliasing']), + '-Wstrict-aliasing']) + + (get_option('ext-underline') + ? ['-DFOOT_EXT_UNDERLINE=1'] + : ['-DFOOT_EXT_UNDERLINE=0']), language: 'c', ) diff --git a/meson_options.txt b/meson_options.txt index ab7a07be..7e8b1ba4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -27,3 +27,6 @@ option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutemp description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)') option('utmp-default-helper-path', type: 'string', value: 'auto', description: 'Default path to the utmp helper binary. Default: auto-detect') + +option('ext-underline', type: 'boolean', value: true, + description: 'Enable underline styles & colors from xterm-kitty') diff --git a/render.c b/render.c index d679b5e8..fea2dbf9 100644 --- a/render.c +++ b/render.c @@ -384,6 +384,99 @@ draw_underline(const struct terminal *term, pixman_image_t *pix, x, y + y_ofs, cols * term->cell_width, thickness}); } +#if FOOT_EXT_UNDERLINE +static void +draw_ext_underline(const struct terminal *term, pixman_image_t *pix, + const struct fcft_font *font, + const pixman_color_t *color, + const enum underline_style style, + int x, int y, int cols) +{ + if (style == UNDERLINE_NONE) + return; + const int thickness = font->underline.thickness; + + int y_ofs; + /* Make sure the line isn't positioned below the cell */ + switch (style) { + case UNDERLINE_DOUBLE: + y_ofs = min(underline_offset(term, font), + term->cell_height - thickness * 3); + break; + default: + y_ofs = min(underline_offset(term, font), + term->cell_height - thickness); + break; + } + + const int ceil_w = cols * term->cell_width; + switch (style) { + case UNDERLINE_DOUBLE: { + const pixman_rectangle16_t rects[] = { + {x, y + y_ofs, ceil_w, thickness}, + {x, y + y_ofs + thickness * 2, ceil_w, thickness}}; + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, color, 2, rects); + break; + } + case UNDERLINE_DASHED: { + const int ceil_w = cols * term->cell_width; + const int dash_w = ceil_w / 3 + (ceil_w % 3 > 0); + const pixman_rectangle16_t rects[] = { + {x, y + y_ofs, dash_w, thickness}, + {x + dash_w * 2, y + y_ofs, dash_w, thickness}, + }; + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, pix, color, 2, rects); + break; + } + case UNDERLINE_DOTTED: { + const int ceil_w = cols * term->cell_width; + const int nrects = min(ceil_w / thickness / 2, 16); + pixman_rectangle16_t rects[16] = {0}; + for (int i = 0; i < nrects; i++) { + rects[i] = (pixman_rectangle16_t){ + x + i * thickness * 2, y + y_ofs, thickness, thickness}; + } + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, pix, color, nrects, rects); + break; + } + case UNDERLINE_CURLY: { +#define I(x) pixman_int_to_fixed(x) + const int top = y + y_ofs; + const int bot = top + thickness * 3; + const int th = thickness; + const int half_x = x + ceil_w / 2, full_x = x + ceil_w; + const pixman_trapezoid_t traps[] = { + { + I(top), I(bot), + {{I(x), I(bot - th)}, {I(half_x), I(top - th)}}, + {{I(x), I(bot + th)}, {I(half_x), I(top + th)}}, + }, + { + I(top), I(bot), + {{I(half_x), I(top + th)}, {I(full_x), I(bot + th)}}, + {{I(half_x), I(top - th)}, {I(full_x), I(bot - th)}}, + }}; + // TODO: reuse the fill across all cells + pixman_image_t *fill = pixman_image_create_solid_fill(color); + pixman_composite_trapezoids( + PIXMAN_OP_OVER, fill, pix, PIXMAN_a8, 0, 0, 0, 0, 2, traps); + pixman_image_unref(fill); + break; + } + default: { + const pixman_rectangle16_t rects[] = { + {x, y + y_ofs, ceil_w, thickness}}; + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, pix, color, 1, rects); + break; + } + } +} +#endif + + static void draw_strikeout(const struct terminal *term, pixman_image_t *pix, const struct fcft_font *font, @@ -478,12 +571,18 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag uint32_t _fg = 0; uint32_t _bg = 0; +#if FOOT_EXT_UNDERLINE + uint32_t _ul = 0; +#endif uint16_t alpha = 0xffff; if (is_selected && term->colors.use_custom_selection) { _fg = term->colors.selection_fg; _bg = term->colors.selection_bg; +#if FOOT_EXT_UNDERLINE + _ul = _fg; +#endif } else { /* Use cell specific color, if set, otherwise the default colors (possible reversed) */ switch (cell->attrs.fg_src) { @@ -518,6 +617,24 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag break; } +#if FOOT_EXT_UNDERLINE + switch (cell->attrs.ul_src) { + case COLOR_RGB: + _ul = cell->attrs.ul; + break; + + case COLOR_BASE16: + case COLOR_BASE256: + xassert(cell->attrs.ul < ALEN(term->colors.table)); + _ul = term->colors.table[cell->attrs.bg]; + break; + + case COLOR_DEFAULT: + _ul = _fg; + break; + } +#endif + if (cell->attrs.reverse ^ is_selected) { uint32_t swap = _fg; _fg = _bg; @@ -581,6 +698,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag pixman_color_t fg = color_hex_to_pixman(_fg); pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha); +#if FOOT_EXT_UNDERLINE + pixman_color_t ul = color_hex_to_pixman(_ul); +#endif struct fcft_font *font = attrs_to_font(term, &cell->attrs); const struct composed *composed = NULL; @@ -834,8 +954,13 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag pixman_image_unref(clr_pix); /* Underline */ +#if FOOT_EXT_UNDERLINE + draw_ext_underline(term, pix, font, &ul, cell->attrs.ul_style, + x, y, cell_cols); +#else if (cell->attrs.underline) draw_underline(term, pix, font, &fg, x, y, cell_cols); +#endif if (cell->attrs.strikethrough) draw_strikeout(term, pix, font, &fg, x, y, cell_cols); diff --git a/terminal.h b/terminal.h index a8b65198..501341ca 100644 --- a/terminal.h +++ b/terminal.h @@ -31,6 +31,17 @@ enum color_source { COLOR_RGB, }; +#if FOOT_EXT_UNDERLINE +enum underline_style { + UNDERLINE_NONE, + UNDERLINE_STRAIGHT, + UNDERLINE_CURLY, + UNDERLINE_DOUBLE, + UNDERLINE_DASHED, + UNDERLINE_DOTTED, +}; +#endif + /* * Note: we want the cells to be as small as possible. Larger cells * means fewer scrollback lines (or performance drops due to cache @@ -56,8 +67,19 @@ struct attributes { bool selected:1; bool url:1; uint32_t bg:24; + +#if FOOT_EXT_UNDERLINE + enum underline_style ul_style:4; + enum color_source ul_src:4; + uint32_t ul:24; +#endif }; + +#if FOOT_EXT_UNDERLINE +static_assert(sizeof(struct attributes) == 12, "VT attribute struct too large"); +#else static_assert(sizeof(struct attributes) == 8, "VT attribute struct too large"); +#endif /* Last valid Unicode code point is 0x0010FFFFul */ #define CELL_COMB_CHARS_LO 0x00200000ul @@ -68,7 +90,11 @@ struct cell { char32_t wc; struct attributes attrs; }; +#if FOOT_EXT_UNDERLINE +static_assert(sizeof(struct cell) == 16, "bad size"); +#else static_assert(sizeof(struct cell) == 12, "bad size"); +#endif struct scroll_region { int start;