Add support for colored and styled underlines on tty frames

* src/dispextern.h (face, face_underline_type, syms_of_xfacse)
(internal-set-lisp-face-attribute)
(gui_supports_face_attributes_p): Add definitions for new
underline styles of Double-line, Dots and Dashes.  Rename
FACE_UNDER_LINE and FACE_UNDER_WAVE to make definitions
consistent.  Delete tty_underline_p from the face struct and use
just underline going forward.  Add a flag to check whether styled
underlines are available.
* lisp/cus-face.el (custom-face-attributes): Add entries for
Double-line, Dots and Dashes so they can be set through
`customize'.
* src/termchar.c (tty_display_info): Add an entry for the escape
sequence to set the underline style and color on terminal frames.
* src/term.c (init_tty, tty_capable_p, turn_on_face): Read and
save the underline style escape sequence from the Smulx termcap
(alternatively if the Su flag is set use a default sequence).
Allow checking for support of styled underlines in the current
terminal frame.  Output the necessary escape sequences to activate
a styled underline on turn_on_face; this is currently only used
for the new special underline styles, a default straight underline
will still use the "us" termcap.  Output escape sequence to set
underline color when set in the face and supported by the tty.
Save a default value for this sequence on init_tty when styled
underlines are supported.
* src/xfaces.c (tty_supports_face_attributes_p, realize_tty_face)
(map_tty_color): Assert whether styled underlines are supported by
the current terminal on display-supports-face-attributes-p checks.
Populate the correct underline style and color in the face spec
when realizing a face.  Allow map_tty_color to map underline
colors alongside foreground and background.  The interface of
map_tty_color was amended to allow the caller to supply the
underline color instead of accessing it through the face
attributes.  (bug#62994)
* src/xterm.c (x_draw_glyph_string): Updated to use renamed
FACE_UNDERLINE_SINGLE and FACE_UNDERLINE_WAVE face_underline_type
enumerations.
This commit is contained in:
Mohsin Kaleem 2023-04-20 22:30:12 +01:00 committed by Eli Zaretskii
parent a7d51085cf
commit 9f589eb924
8 changed files with 225 additions and 45 deletions

View file

@ -2685,12 +2685,15 @@ Underline in color @var{color}, a string specifying a color.
@var{color} is either a string, or the symbol @code{foreground-color},
meaning the foreground color of the face. Omitting the attribute
@code{:color} means to use the foreground color of the face.
@var{style} should be a symbol @code{line} or @code{wave}, meaning to
use a straight or wavy line. Omitting the attribute @code{:style}
means to use a straight line. @var{position}, if non-@code{nil}, means to
display the underline at the descent of the text, instead of at the
baseline level. If it is a number, then it specifies the amount of
pixels above the descent to display the underline.
@var{style} is a symbol which sets the line-style to of the underline.
It should be one of @code{line}, @code{double-line}, @code{wave},
@code{dots}, or @code{dashes}. GUI frames only support @code{line} and
@code{wave}. Terminal frames can support all aforementioned underline
styles. Omitting the attribute @code{:style} means to use a straight
line. @var{position}, if non-@code{nil}, means to display the underline
at the descent of the text, instead of at the baseline level. If it is
a number, then it specifies the amount of pixels above the descent to
display the underline.
@end table
@cindex overlined text

View file

@ -446,6 +446,22 @@ Use 'TAB' in the minibuffer to show or hide the password. Likewise,
there is an icon on the mode-line, which toggles the visibility of the
password when clicking with 'mouse-1'.
** Terminal Emacs
---
*** Support for 'styled' and 'colored' underline face attributes on TTY frames.
If your terminals termcap or terminfo database entry has the 'Su' or
'Smulx' capability defined, Emacs will now emit the prescribed escape
sequence necessary to render faces with styled underlines on TTY
frames.
Styled underlines are any underlines containing a non-default
underline style or a color other than the foreground-color.
The available underline styles for TTY frames are 'single',
'double-line', 'wave', 'dots, and 'dashes'. These are currently
supported by Kitty, libvte, and st (through the undercurl patch) among
other terminals.
* Editing Changes in Emacs 30.1

View file

@ -141,7 +141,10 @@
(const :format "" :value :style)
(choice :tag "Style"
(const :tag "Line" line)
(const :tag "Wave" wave))
(const :tag "Double line" double-line)
(const :tag "Wave" wave)
(const :tag "Dots" dots)
(const :tag "Dashes" dashes))
(const :format "" :value :position)
(choice :tag "Position"
(const :tag "At Default Position" nil)

View file

@ -1697,9 +1697,13 @@ enum face_box_type
enum face_underline_type
{
/* Note: Order matches the order of the Smulx terminfo extension. */
FACE_NO_UNDERLINE = 0,
FACE_UNDER_LINE,
FACE_UNDER_WAVE
FACE_UNDERLINE_SINGLE,
FACE_UNDERLINE_DOUBLE_LINE,
FACE_UNDERLINE_WAVE,
FACE_UNDERLINE_DOTS,
FACE_UNDERLINE_DASHES,
};
/* Structure describing a realized face.
@ -1783,7 +1787,7 @@ struct face
ENUM_BF (face_box_type) box : 2;
/* Style of underlining. */
ENUM_BF (face_underline_type) underline : 2;
ENUM_BF (face_underline_type) underline : 3;
/* If `box' above specifies a 3D type, true means use box_color for
drawing shadows. */
@ -1815,7 +1819,6 @@ struct face
string meaning the default color of the TTY. */
bool_bf tty_bold_p : 1;
bool_bf tty_italic_p : 1;
bool_bf tty_underline_p : 1;
bool_bf tty_reverse_p : 1;
bool_bf tty_strike_through_p : 1;
@ -3433,6 +3436,7 @@ enum tool_bar_item_image
#define TTY_CAP_DIM 0x08
#define TTY_CAP_ITALIC 0x10
#define TTY_CAP_STRIKE_THROUGH 0x20
#define TTY_CAP_UNDERLINE_STYLED (0x32 & TTY_CAP_UNDERLINE)
/***********************************************************************

View file

@ -2014,8 +2014,19 @@ turn_on_face (struct frame *f, int face_id)
OUTPUT1 (tty, tty->TS_enter_dim_mode);
}
if (face->tty_underline_p && MAY_USE_WITH_COLORS_P (tty, NC_UNDERLINE))
OUTPUT1_IF (tty, tty->TS_enter_underline_mode);
if (face->underline && MAY_USE_WITH_COLORS_P (tty, NC_UNDERLINE))
{
if (face->underline == FACE_UNDERLINE_SINGLE
|| !tty->TF_set_underline_style)
OUTPUT1_IF (tty, tty->TS_enter_underline_mode);
else if (tty->TF_set_underline_style)
{
char *p;
p = tparam (tty->TF_set_underline_style, NULL, 0, face->underline, 0, 0, 0);
OUTPUT (tty, p);
xfree (p);
}
}
if (face->tty_strike_through_p
&& MAY_USE_WITH_COLORS_P (tty, NC_STRIKE_THROUGH))
@ -2041,6 +2052,14 @@ turn_on_face (struct frame *f, int face_id)
OUTPUT (tty, p);
xfree (p);
}
ts = tty->TF_set_underline_color;
if (ts && face->underline_color)
{
p = tparam (ts, NULL, 0, face->underline_color, 0, 0, 0);
OUTPUT (tty, p);
xfree (p);
}
}
}
@ -2061,7 +2080,7 @@ turn_off_face (struct frame *f, int face_id)
if (face->tty_bold_p
|| face->tty_italic_p
|| face->tty_reverse_p
|| face->tty_underline_p
|| face->underline
|| face->tty_strike_through_p)
{
OUTPUT1_IF (tty, tty->TS_exit_attribute_mode);
@ -2073,7 +2092,7 @@ turn_off_face (struct frame *f, int face_id)
{
/* If we don't have "me" we can only have those appearances
that have exit sequences defined. */
if (face->tty_underline_p)
if (face->underline)
OUTPUT_IF (tty, tty->TS_exit_underline_mode);
}
@ -2104,6 +2123,9 @@ tty_capable_p (struct tty_display_info *tty, unsigned int caps)
TTY_CAPABLE_P_TRY (tty,
TTY_CAP_UNDERLINE, tty->TS_enter_underline_mode,
NC_UNDERLINE);
TTY_CAPABLE_P_TRY (tty,
TTY_CAP_UNDERLINE_STYLED, tty->TF_set_underline_style,
NC_UNDERLINE);
TTY_CAPABLE_P_TRY (tty,
TTY_CAP_BOLD, tty->TS_enter_bold_mode, NC_BOLD);
TTY_CAPABLE_P_TRY (tty,
@ -4360,6 +4382,26 @@ use the Bourne shell command 'TERM=...; export TERM' (C-shell:\n\
tty->TF_underscore = tgetflag ("ul");
tty->TF_teleray = tgetflag ("xt");
/* Styled underlines. Support for this is provided either by the
escape sequence in Smulx or the Su flag. The latter results in a
common default escape sequence and is not recommended. */
#ifdef TERMINFO
tty->TF_set_underline_style = tigetstr ("Smulx");
if (tty->TF_set_underline_style == (char *) (intptr_t) -1)
tty->TF_set_underline_style = NULL;
#else
tty->TF_set_underline_style = tgetstr ("Smulx", address);
#endif
if (!tty->TF_set_underline_style && tgetflag ("Su"))
/* Default to the kitty escape sequence. See
https://sw.kovidgoyal.net/kitty/underlines/. */
tty->TF_set_underline_style = "\x1b[4:%p1%dm";
if (tty->TF_set_underline_style)
/* Standard escape sequence to set the underline color.
Requires a single parameter, the color index. */
tty->TF_set_underline_color = "\x1b[58:2::%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%dm";
#else /* DOS_NT */
#ifdef WINDOWSNT
{

View file

@ -171,6 +171,13 @@ struct tty_display_info
non-blank position. Must clear before writing _. */
int TF_teleray; /* termcap xt flag: many weird consequences.
For t1061. */
const char *TF_set_underline_style; /* termcap Smulx entry: Switches the underline
style based on the parameter. Param should
be one of: 0 (none), 1 (straight), 2 (double-line),
3 (wave), 4 (dots), or 5 (dashes). */
const char *TF_set_underline_color; /* Enabled when TF_set_underline_style is set:
Sets the color of the underline. Accepts a
single parameter, the color index. */
int RPov; /* # chars to start a TS_repeat */

View file

@ -3297,7 +3297,11 @@ FRAME 0 means change the face on all frames, and change the default
}
else if (EQ (key, QCstyle)
&& !(EQ (val, Qline) || EQ (val, Qwave)))
&& !(EQ (val, Qline)
|| EQ (val, Qdouble_line)
|| EQ (val, Qwave)
|| EQ (val, Qdots)
|| EQ (val, Qdashes)))
{
valid_p = false;
break;
@ -5265,6 +5269,7 @@ gui_supports_face_attributes_p (struct frame *f,
Lisp_Object attrs[LFACE_VECTOR_SIZE],
struct face *def_face)
{
Lisp_Object val;
Lisp_Object *def_attrs = def_face->lface;
Lisp_Object lattrs[LFACE_VECTOR_SIZE];
@ -5359,6 +5364,14 @@ gui_supports_face_attributes_p (struct frame *f,
return false;
}
/* Check supported underline styles. */
val = attrs[LFACE_UNDERLINE_INDEX];
if (!UNSPECIFIEDP (val)
&& EQ (CAR_SAFE (val), QCstyle)
&& !(EQ (CAR_SAFE (CDR_SAFE (val)), Qline)
|| EQ (CAR_SAFE (CDR_SAFE (val)), Qwave)))
return false; /* Unsupported underline style. */
/* Everything checks out, this face is supported. */
return true;
}
@ -5452,9 +5465,18 @@ tty_supports_face_attributes_p (struct frame *f,
if (!UNSPECIFIEDP (val))
{
if (STRINGP (val))
return false; /* ttys can't use colored underlines */
else if (EQ (CAR_SAFE (val), QCstyle) && EQ (CAR_SAFE (CDR_SAFE (val)), Qwave))
return false; /* ttys can't use wave underlines */
test_caps |= TTY_CAP_UNDERLINE_STYLED;
else if (EQ (CAR_SAFE (val), QCstyle))
{
if (!(EQ (CAR_SAFE (CDR_SAFE (val)), Qline)
|| EQ (CAR_SAFE (CDR_SAFE (val)), Qdouble_line)
|| EQ (CAR_SAFE (CDR_SAFE (val)), Qwave)
|| EQ (CAR_SAFE (CDR_SAFE (val)), Qdots)
|| EQ (CAR_SAFE (CDR_SAFE (val)), Qdashes)))
return false; /* Face uses an unsupported underline style. */
test_caps |= TTY_CAP_UNDERLINE_STYLED;
}
else if (face_attr_equal_p (val, def_attrs[LFACE_UNDERLINE_INDEX]))
return false; /* same as default */
else
@ -6311,7 +6333,7 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
if (EQ (underline, Qt))
{
/* Use default color (same as foreground color). */
face->underline = FACE_UNDER_LINE;
face->underline = FACE_UNDERLINE_SINGLE;
face->underline_defaulted_p = true;
face->underline_color = 0;
face->underline_at_descent_line_p = false;
@ -6320,7 +6342,7 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
else if (STRINGP (underline))
{
/* Use specified color. */
face->underline = FACE_UNDER_LINE;
face->underline = FACE_UNDERLINE_SINGLE;
face->underline_defaulted_p = false;
face->underline_color
= load_color (f, face, underline,
@ -6340,7 +6362,7 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
{
/* `(:color COLOR :style STYLE)'.
STYLE being one of `line' or `wave'. */
face->underline = FACE_UNDER_LINE;
face->underline = FACE_UNDERLINE_SINGLE;
face->underline_color = 0;
face->underline_defaulted_p = true;
face->underline_at_descent_line_p = false;
@ -6377,9 +6399,11 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
else if (EQ (keyword, QCstyle))
{
if (EQ (value, Qline))
face->underline = FACE_UNDER_LINE;
face->underline = FACE_UNDERLINE_SINGLE;
else if (EQ (value, Qwave))
face->underline = FACE_UNDER_WAVE;
face->underline = FACE_UNDERLINE_WAVE;
else
face->underline = FACE_UNDERLINE_SINGLE;
}
else if (EQ (keyword, QCposition))
{
@ -6430,17 +6454,18 @@ realize_gui_face (struct face_cache *cache, Lisp_Object attrs[LFACE_VECTOR_SIZE]
}
/* Map a specified color of face FACE on frame F to a tty color index.
IDX is either LFACE_FOREGROUND_INDEX or LFACE_BACKGROUND_INDEX, and
specifies which color to map. Set *DEFAULTED to true if mapping to the
/* Map the specified color COLOR of face FACE on frame F to a tty
color index. IDX is one of LFACE_FOREGROUND_INDEX,
LFACE_BACKGROUND_INDEX or LFACE_UNDERLINE_INDEX, and specifies
which color to map. Set *DEFAULTED to true if mapping to the
default foreground/background colors. */
static void
map_tty_color (struct frame *f, struct face *face,
enum lface_attribute_index idx, bool *defaulted)
map_tty_color (struct frame *f, struct face *face, Lisp_Object color,
enum lface_attribute_index idx, bool *defaulted)
{
Lisp_Object frame, color, def;
bool foreground_p = idx == LFACE_FOREGROUND_INDEX;
Lisp_Object frame, def;
bool foreground_p = idx != LFACE_BACKGROUND_INDEX;
unsigned long default_pixel =
foreground_p ? FACE_TTY_DEFAULT_FG_COLOR : FACE_TTY_DEFAULT_BG_COLOR;
unsigned long pixel = default_pixel;
@ -6449,10 +6474,11 @@ map_tty_color (struct frame *f, struct face *face,
foreground_p ? FACE_TTY_DEFAULT_BG_COLOR : FACE_TTY_DEFAULT_FG_COLOR;
#endif
eassert (idx == LFACE_FOREGROUND_INDEX || idx == LFACE_BACKGROUND_INDEX);
eassert (idx == LFACE_FOREGROUND_INDEX
|| idx == LFACE_BACKGROUND_INDEX
|| idx == LFACE_UNDERLINE_INDEX);
XSETFRAME (frame, f);
color = face->lface[idx];
if (STRINGP (color)
&& SCHARS (color)
@ -6497,13 +6523,21 @@ map_tty_color (struct frame *f, struct face *face,
#endif /* MSDOS */
}
if (foreground_p)
face->foreground = pixel;
else
face->background = pixel;
switch (idx)
{
case LFACE_FOREGROUND_INDEX:
face->foreground = pixel;
break;
case LFACE_UNDERLINE_INDEX:
face->underline_color = pixel;
break;
case LFACE_BACKGROUND_INDEX:
default:
face->background = pixel;
break;
}
}
/* Realize the fully-specified face with attributes ATTRS in face
cache CACHE for ASCII characters. Do it for TTY frame CACHE->f.
Value is a pointer to the newly created realized face. */
@ -6514,6 +6548,7 @@ realize_tty_face (struct face_cache *cache,
{
struct face *face;
int weight, slant;
Lisp_Object underline;
bool face_colors_defaulted = false;
struct frame *f = cache->f;
@ -6533,16 +6568,83 @@ realize_tty_face (struct face_cache *cache,
face->tty_bold_p = true;
if (slant != 100)
face->tty_italic_p = true;
if (!NILP (attrs[LFACE_UNDERLINE_INDEX]))
face->tty_underline_p = true;
if (!NILP (attrs[LFACE_INVERSE_INDEX]))
face->tty_reverse_p = true;
if (!NILP (attrs[LFACE_STRIKE_THROUGH_INDEX]))
face->tty_strike_through_p = true;
/* Text underline. */
underline = attrs[LFACE_UNDERLINE_INDEX];
if (NILP (underline))
{
face->underline = FACE_NO_UNDERLINE;
face->underline_color = 0;
}
else if (EQ (underline, Qt))
{
face->underline = FACE_UNDERLINE_SINGLE;
face->underline_color = 0;
}
else if (STRINGP (underline))
{
face->underline = FACE_UNDERLINE_SINGLE;
bool underline_color_defaulted;
map_tty_color (f, face, underline, LFACE_UNDERLINE_INDEX,
&underline_color_defaulted);
}
else if (CONSP (underline))
{
/* `(:color COLOR :style STYLE)'.
STYLE being one of `line', `double-line', `wave', `dots' or `dashes'. */
face->underline = FACE_UNDERLINE_SINGLE;
face->underline_color = 0;
while (CONSP (underline))
{
Lisp_Object keyword, value;
keyword = XCAR (underline);
underline = XCDR (underline);
if (!CONSP (underline))
break;
value = XCAR (underline);
underline = XCDR (underline);
if (EQ (keyword, QCcolor))
{
if (EQ (value, Qforeground_color))
face->underline_color = 0;
else if (STRINGP (value))
{
bool underline_color_defaulted;
map_tty_color (f, face, value, LFACE_UNDERLINE_INDEX,
&underline_color_defaulted);
}
}
else if (EQ (keyword, QCstyle))
{
if (EQ (value, Qline))
face->underline = FACE_UNDERLINE_SINGLE;
else if (EQ (value, Qdouble_line))
face->underline = FACE_UNDERLINE_DOUBLE_LINE;
else if (EQ (value, Qwave))
face->underline = FACE_UNDERLINE_WAVE;
else if (EQ (value, Qdots))
face->underline = FACE_UNDERLINE_DOTS;
else if (EQ (value, Qdashes))
face->underline = FACE_UNDERLINE_DASHES;
else
face->underline = FACE_UNDERLINE_SINGLE;
}
}
}
/* Map color names to color indices. */
map_tty_color (f, face, LFACE_FOREGROUND_INDEX, &face_colors_defaulted);
map_tty_color (f, face, LFACE_BACKGROUND_INDEX, &face_colors_defaulted);
map_tty_color (f, face, face->lface[LFACE_FOREGROUND_INDEX],
LFACE_FOREGROUND_INDEX, &face_colors_defaulted);
map_tty_color (f, face, face->lface[LFACE_BACKGROUND_INDEX],
LFACE_BACKGROUND_INDEX, &face_colors_defaulted);
/* Swap colors if face is inverse-video. If the colors are taken
from the frame colors, they are already inverted, since the
@ -7228,6 +7330,9 @@ syms_of_xfaces (void)
DEFSYM (QCposition, ":position");
DEFSYM (Qline, "line");
DEFSYM (Qwave, "wave");
DEFSYM (Qdouble_line, "double-line");
DEFSYM (Qdots, "dots");
DEFSYM (Qdashes, "dashes");
DEFSYM (Qreleased_button, "released-button");
DEFSYM (Qpressed_button, "pressed-button");
DEFSYM (Qflat_button, "flat-button");

View file

@ -10957,7 +10957,7 @@ x_draw_glyph_string (struct glyph_string *s)
/* Draw underline. */
if (s->face->underline)
{
if (s->face->underline == FACE_UNDER_WAVE)
if (s->face->underline == FACE_UNDERLINE_WAVE)
{
if (s->face->underline_defaulted_p)
x_draw_underwave (s, decoration_width);
@ -10971,13 +10971,13 @@ x_draw_glyph_string (struct glyph_string *s)
XSetForeground (display, s->gc, xgcv.foreground);
}
}
else if (s->face->underline == FACE_UNDER_LINE)
else if (s->face->underline == FACE_UNDERLINE_SINGLE)
{
unsigned long thickness, position;
int y;
if (s->prev
&& s->prev->face->underline == FACE_UNDER_LINE
&& s->prev->face->underline == FACE_UNDERLINE_SINGLE
&& (s->prev->face->underline_at_descent_line_p
== s->face->underline_at_descent_line_p)
&& (s->prev->face->underline_pixels_above_descent_line