feat(render): add configurable attribute colors for bold, italic, and underline

Introduce optional color theme entries for bold, italic, and underline text
when the foreground color is default. This allows legacy applications using
SGR attributes instead of explicit colors to be rendered with user-defined
attribute colors. Brightening via `bold_in_bright` is suppressed when an
explicit attribute color is applied.

This adds compatibility with the rxvt-unicode settings `colorBD`,
`colorIT` and `colorUL`.

Fixes: #2261
This commit is contained in:
Florian Best 2026-01-17 03:38:53 +01:00
parent b78cc92322
commit 4864140c10
8 changed files with 60 additions and 2 deletions

View file

@ -1447,6 +1447,9 @@ parse_color_theme(struct context *ctx, struct color_theme *theme)
else if (streq(key, "flash")) color = &theme->flash;
else if (streq(key, "foreground")) color = &theme->fg;
else if (streq(key, "background")) color = &theme->bg;
else if (streq(key, "bold")) color = &theme->bold;
else if (streq(key, "italic")) color = &theme->italic;
else if (streq(key, "underline")) color = &theme->underline;
else if (streq(key, "selection-foreground")) color = &theme->selection_fg;
else if (streq(key, "selection-background")) color = &theme->selection_bg;
@ -3517,6 +3520,9 @@ config_load(struct config *conf, const char *conf_path,
.dim_blend_towards = DIM_BLEND_TOWARDS_BLACK,
.selection_fg = 0x80000000, /* Use default bg */
.selection_bg = 0x80000000, /* Use default fg */
.bold = 0x80000000, /* default: unset */
.italic = 0x80000000, /* default: unset */
.underline = 0x80000000, /* default: unset */
.cursor = {
.text = 0,
.cursor = 0,

View file

@ -141,6 +141,9 @@ struct color_theme {
uint32_t selection_fg;
uint32_t selection_bg;
uint32_t url;
uint32_t bold;
uint32_t italic;
uint32_t underline;
uint32_t dim[8];
uint32_t sixel[16];

View file

@ -1109,6 +1109,21 @@ The default theme used is *colors-dark*, unless
Foreground (text) and background color to use in selected
text. Default: _inverse foreground/background_.
*bold*
Specifies the color to use for bold characters when the foreground color is the default (undefined).
This setting applies primarily to legacy applications that utilize SGR 1 codes instead of SGR 9 codes.
If not set, the behavior defaults to the terminals standard bold rendering. Default: unset.
*italic*
Specifies the color to use for italic characters when the foreground color is the default (undefined).
This setting applies primarily to legacy applications that utilize SGR 1 codes instead of SGR 9 codes.
If not set, the behavior defaults to the terminals standard italic rendering. Default: unset.
*underline*
Specifies the color to use for underline characters when the foreground color is the default (undefined).
This setting applies primarily to legacy applications that utilize SGR 1 codes instead of SGR 9 codes.
If not set, the behavior defaults to the terminals standard underline rendering. Default: unset.
*jump-labels*
Two color values specifying the foreground (text) and background
colors to use when rendering jump labels in URL mode. Default:

View file

@ -160,6 +160,11 @@
# sixel14 = 999954
# sixel15 = cccccc
## Attribute colorization
# bold = <not set>
# italic = <not set>
# underline = <not set>
## Misc colors
# selection-foreground=<inverse foreground/background>
# selection-background=<inverse foreground/background>

View file

@ -703,6 +703,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
uint32_t _fg = 0;
uint32_t _bg = 0;
bool allow_brighten = true;
uint16_t alpha = 0xffff;
const bool is_selected = cell->attrs.selected;
@ -720,7 +721,20 @@ render_cell(struct terminal *term, pixman_image_t *pix,
break;
case COLOR_DEFAULT:
_fg = term->reverse ? term->colors.bg : term->colors.fg;
if (term->reverse) {
_fg = term->colors.bg;
} else if (cell->attrs.bold && term->colors.bold >> 24 == 0) {
_fg = term->colors.bold;
allow_brighten = false;
} else if (cell->attrs.italic && term->colors.italic >> 24 == 0) {
_fg = term->colors.italic;
allow_brighten = false;
} else if (cell->attrs.underline && term->colors.underline >> 24 == 0) {
_fg = term->colors.underline;
allow_brighten = false;
} else {
_fg = term->colors.fg;
}
break;
}
@ -839,7 +853,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
if (cell->attrs.dim)
_fg = color_dim(term, _fg);
if (term->conf->bold_in_bright.enabled && cell->attrs.bold)
if (term->conf->bold_in_bright.enabled && cell->attrs.bold && allow_brighten)
_fg = color_brighten(term, _fg);
if (cell->attrs.blink && term->blink.state == BLINK_OFF)

View file

@ -1317,6 +1317,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor,
.selection_fg = theme->selection_fg,
.selection_bg = theme->selection_bg,
.bold = theme->bold,
.italic = theme->italic,
.underline = theme->underline,
.active_theme = conf->initial_color_theme,
},
.color_stack = {
@ -2090,6 +2093,9 @@ term_theme_apply(struct terminal *term, const struct color_theme *theme)
term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor;
term->colors.selection_fg = theme->selection_fg;
term->colors.selection_bg = theme->selection_bg;
term->colors.bold = theme->bold;
term->colors.italic = theme->italic;
term->colors.underline = theme->underline;
memcpy(term->colors.table, theme->table, sizeof(term->colors.table));
}

View file

@ -404,6 +404,9 @@ struct colors {
uint32_t cursor_bg; /* cursor color */
uint32_t selection_fg;
uint32_t selection_bg;
uint32_t bold;
uint32_t italic;
uint32_t underline;
enum which_color_theme active_theme;
};

View file

@ -739,6 +739,9 @@ test_section_colors_dark(void)
test_color(&ctx, &parse_section_colors_dark, "dim7", false, &conf.colors_dark.dim[7]);
test_color(&ctx, &parse_section_colors_dark, "selection-foreground", false, &conf.colors_dark.selection_fg);
test_color(&ctx, &parse_section_colors_dark, "selection-background", false, &conf.colors_dark.selection_bg);
test_color(&ctx, &parse_section_colors_dark, "bold", false, &conf.colors_dark.bold);
test_color(&ctx, &parse_section_colors_dark, "italic", false, &conf.colors_dark.italic);
test_color(&ctx, &parse_section_colors_dark, "underline", false, &conf.colors_dark.underline);
test_color(&ctx, &parse_section_colors_dark, "urls", false, &conf.colors_dark.url);
test_two_colors(&ctx, &parse_section_colors_dark, "jump-labels", false,
&conf.colors_dark.jump_label.fg,
@ -818,6 +821,9 @@ test_section_colors_light(void)
test_color(&ctx, &parse_section_colors_light, "dim7", false, &conf.colors_light.dim[7]);
test_color(&ctx, &parse_section_colors_light, "selection-foreground", false, &conf.colors_light.selection_fg);
test_color(&ctx, &parse_section_colors_light, "selection-background", false, &conf.colors_light.selection_bg);
test_color(&ctx, &parse_section_colors_light, "bold", false, &conf.colors_light.bold);
test_color(&ctx, &parse_section_colors_light, "italic", false, &conf.colors_light.italic);
test_color(&ctx, &parse_section_colors_light, "underline", false, &conf.colors_light.underline);
test_color(&ctx, &parse_section_colors_light, "urls", false, &conf.colors_light.url);
test_two_colors(&ctx, &parse_section_colors_light, "jump-labels", false,
&conf.colors_light.jump_label.fg,