commit 9bb304a974185cbd9fa48c890450c6582d3e0546
parent 2bf3f182e05cd8d556670e487f4c37a7f0e658f1
Author: HexOctal <hex0octal@gmail.com>
Date:   Sun, 22 Aug 2021 16:09:21 +0100
[st][patch][st-undercurl] Added new line styles
Added two new underline styles, and changed default style.
Updated image example with new style.
Added a dynamic line width that updates with terminal font size.
Diffstat:
4 files changed, 609 insertions(+), 245 deletions(-)
diff --git a/st.suckless.org/patches/undercurl/index.md b/st.suckless.org/patches/undercurl/index.md
@@ -12,6 +12,9 @@ following [SGR](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR) parameters:
 * 58:2:r:g:b - Where r, g and b are the red, green and blue color values of the underline respectively.
 * 59 - Resets the underline color to the foreground color.
 
+Three styles are available to choose from. You can do so in the `config.def.h`
+file by editing the `UNDERCURL_STYLE` define.
+
 Notes
 -----
 These escape codes aren't standard, but they are supported by most other
@@ -19,7 +22,7 @@ terminal emulators, as well as many terminal programs (Such as Vim).
 
 Download
 --------
-* [st-undercurl-0.8.4.diff](st-undercurl-0.8.4.diff)
+* [st-undercurl-0.8.4-20210822.diff](st-undercurl-0.8.4-20210822.diff)
 
 Authors
 -------
diff --git a/st.suckless.org/patches/undercurl/st-undercurl-0.8.4-20210822.diff b/st.suckless.org/patches/undercurl/st-undercurl-0.8.4-20210822.diff
@@ -0,0 +1,605 @@
+diff --git a/config.def.h b/config.def.h
+index 6f05dce..7ae1b92 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -470,3 +470,27 @@ static char ascii_printable[] =
+ 	" !\"#$%&'()*+,-./0123456789:;<=>?"
+ 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ 	"`abcdefghijklmnopqrstuvwxyz{|}~";
++
++/**
++ * Undercurl style. Set UNDERCURL_STYLE to one of the available styles.
++ *
++ * Curly: Dunno how to draw it *shrug*
++ *  _   _   _   _
++ * ( ) ( ) ( ) ( )
++ *	 (_) (_) (_) (_)
++ *
++ * Spiky:
++ * /\  /\   /\	/\
++ *   \/  \/	  \/
++ *
++ * Capped:
++ *	_     _     _
++ * / \   / \   / \
++ *    \_/   \_/
++ */
++// Available styles
++#define UNDERCURL_CURLY 0
++#define UNDERCURL_SPIKY 1
++#define UNDERCURL_CAPPED 2
++// Active style
++#define UNDERCURL_STYLE UNDERCURL_SPIKY
+diff --git a/st.c b/st.c
+index 76b7e0d..542ab3a 100644
+--- a/st.c
++++ b/st.c
+@@ -33,6 +33,7 @@
+ #define UTF_SIZ       4
+ #define ESC_BUF_SIZ   (128*UTF_SIZ)
+ #define ESC_ARG_SIZ   16
++#define CAR_PER_ARG   4
+ #define STR_BUF_SIZ   ESC_BUF_SIZ
+ #define STR_ARG_SIZ   ESC_ARG_SIZ
+ 
+@@ -139,6 +140,7 @@ typedef struct {
+ 	int arg[ESC_ARG_SIZ];
+ 	int narg;              /* nb of args */
+ 	char mode[2];
++	int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
+ } CSIEscape;
+ 
+ /* STR Escape sequence structs */
+@@ -159,6 +161,7 @@ static void ttywriteraw(const char *, size_t);
+ 
+ static void csidump(void);
+ static void csihandle(void);
++static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
+ static void csiparse(void);
+ static void csireset(void);
+ static int eschandle(uchar);
+@@ -1131,6 +1134,28 @@ tnewline(int first_col)
+ 	tmoveto(first_col ? 0 : term.c.x, y);
+ }
+ 
++void
++readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
++{
++	int i = 0;
++	for (; i < CAR_PER_ARG; i++)
++		params[cursor][i] = -1;
++
++	if (**p != ':')
++		return;
++
++	char *np = NULL;
++	i = 0;
++
++	while (**p == ':' && i < CAR_PER_ARG) {
++		while (**p == ':')
++			(*p)++;
++		params[cursor][i] = strtol(*p, &np, 10);
++		*p = np;
++		i++;
++	}
++}
++
+ void
+ csiparse(void)
+ {
+@@ -1153,6 +1178,7 @@ csiparse(void)
+ 			v = -1;
+ 		csiescseq.arg[csiescseq.narg++] = v;
+ 		p = np;
++		readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
+ 		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
+ 			break;
+ 		p++;
+@@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l)
+ 				ATTR_STRUCK     );
+ 			term.c.attr.fg = defaultfg;
+ 			term.c.attr.bg = defaultbg;
++			term.c.attr.ustyle = -1;
++			term.c.attr.ucolor[0] = -1;
++			term.c.attr.ucolor[1] = -1;
++			term.c.attr.ucolor[2] = -1;
+ 			break;
+ 		case 1:
+ 			term.c.attr.mode |= ATTR_BOLD;
+@@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l)
+ 			term.c.attr.mode |= ATTR_ITALIC;
+ 			break;
+ 		case 4:
+-			term.c.attr.mode |= ATTR_UNDERLINE;
++			term.c.attr.ustyle = csiescseq.carg[i][0];
++
++			if (term.c.attr.ustyle != 0)
++				term.c.attr.mode |= ATTR_UNDERLINE;
++			else
++				term.c.attr.mode &= ~ATTR_UNDERLINE;
++
++			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
+ 			break;
+ 		case 5: /* slow blink */
+ 			/* FALLTHROUGH */
+@@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l)
+ 		case 49:
+ 			term.c.attr.bg = defaultbg;
+ 			break;
++		case 58:
++			term.c.attr.ucolor[0] = csiescseq.carg[i][1];
++			term.c.attr.ucolor[1] = csiescseq.carg[i][2];
++			term.c.attr.ucolor[2] = csiescseq.carg[i][3];
++			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
++			break;
++		case 59:
++			term.c.attr.ucolor[0] = -1;
++			term.c.attr.ucolor[1] = -1;
++			term.c.attr.ucolor[2] = -1;
++			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
++			break;
+ 		default:
+ 			if (BETWEEN(attr[i], 30, 37)) {
+ 				term.c.attr.fg = attr[i] - 30;
+diff --git a/st.h b/st.h
+index 3d351b6..95bdcbd 100644
+--- a/st.h
++++ b/st.h
+@@ -34,6 +34,7 @@ enum glyph_attribute {
+ 	ATTR_WIDE       = 1 << 9,
+ 	ATTR_WDUMMY     = 1 << 10,
+ 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
++	ATTR_DIRTYUNDERLINE = 1 << 15,
+ };
+ 
+ enum selection_mode {
+@@ -65,6 +66,8 @@ typedef struct {
+ 	ushort mode;      /* attribute flags */
+ 	uint32_t fg;      /* foreground  */
+ 	uint32_t bg;      /* background  */
++	int ustyle;	  /* underline style */
++	int ucolor[3];    /* underline color */
+ } Glyph;
+ 
+ typedef Glyph *Line;
+diff --git a/st.info b/st.info
+index 8201ad6..659878c 100644
+--- a/st.info
++++ b/st.info
+@@ -1,4 +1,5 @@
+ st-mono| simpleterm monocolor,
++	Su,
+ 	acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
+ 	am,
+ 	bce,
+diff --git a/x.c b/x.c
+index 210f184..3a0e79e 100644
+--- a/x.c
++++ b/x.c
+@@ -45,6 +45,14 @@ typedef struct {
+ 	signed char appcursor; /* application cursor */
+ } Key;
+ 
++/* Undercurl slope types */
++enum undercurl_slope_type {
++	UNDERCURL_SLOPE_ASCENDING = 0,
++	UNDERCURL_SLOPE_TOP_CAP = 1,
++	UNDERCURL_SLOPE_DESCENDING = 2,
++	UNDERCURL_SLOPE_BOTTOM_CAP = 3
++};
++
+ /* X modifiers */
+ #define XK_ANY_MOD    UINT_MAX
+ #define XK_NO_MOD     0
+@@ -1339,6 +1347,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
+ 	return numspecs;
+ }
+ 
++static int isSlopeRising (int x, int iPoint, int waveWidth)
++{
++	//    .     .     .     .
++	//   / \   / \   / \   / \
++	//  /   \ /   \ /   \ /   \
++	// .     .     .     .     .
++
++	// Find absolute `x` of point
++	x += iPoint * (waveWidth/2);
++
++	// Find index of absolute wave
++	int absSlope = x / ((float)waveWidth/2);
++
++	return (absSlope % 2);
++}
++
++static int getSlope (int x, int iPoint, int waveWidth)
++{
++	// Sizes: Caps are half width of slopes
++	//    1_2       1_2       1_2      1_2
++	//   /   \     /   \     /   \    /   \
++	//  /     \   /     \   /     \  /     \
++	// 0       3_0       3_0      3_0       3_
++	// <2->    <1>         <---6---->
++
++	// Find type of first point
++	int firstType;
++	x -= (x / waveWidth) * waveWidth;
++	if (x < (waveWidth * (2.f/6.f)))
++		firstType = UNDERCURL_SLOPE_ASCENDING;
++	else if (x < (waveWidth * (3.f/6.f)))
++		firstType = UNDERCURL_SLOPE_TOP_CAP;
++	else if (x < (waveWidth * (5.f/6.f)))
++		firstType = UNDERCURL_SLOPE_DESCENDING;
++	else
++		firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
++
++	// Find type of given point
++	int pointType = (iPoint % 4);
++	pointType += firstType;
++	pointType %= 4;
++
++	return pointType;
++}
++
+ void
+ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
+ {
+@@ -1461,8 +1514,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+ 
+ 	/* Render underline and strikethrough. */
+ 	if (base.mode & ATTR_UNDERLINE) {
+-		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
+-				width, 1);
++		// Underline Color
++		const int widthThreshold  = 28; // +1 width every widthThreshold px of font
++		int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
++		int linecolor;
++		if ((base.ucolor[0] >= 0) &&
++			!(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
++			!(base.mode & ATTR_INVISIBLE)
++		) {
++			// Special color for underline
++			// Index
++			if (base.ucolor[1] < 0) {
++				linecolor = dc.col[base.ucolor[0]].pixel;
++			}
++			// RGB
++			else {
++				XColor lcolor;
++				lcolor.red = base.ucolor[0] * 257;
++				lcolor.green = base.ucolor[1] * 257;
++				lcolor.blue = base.ucolor[2] * 257;
++				lcolor.flags = DoRed | DoGreen | DoBlue;
++				XAllocColor(xw.dpy, xw.cmap, &lcolor);
++				linecolor = lcolor.pixel;
++			}
++		} else {
++			// Foreground color for underline
++			linecolor = fg->pixel;
++		}
++
++		XGCValues ugcv = {
++			.foreground = linecolor,
++			.line_width = wlw,
++			.line_style = LineSolid,
++			.cap_style = CapNotLast
++		};
++
++		GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
++			GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
++			&ugcv);
++
++		// Underline Style
++		if (base.ustyle != 3) {
++			//XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
++			XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
++				winy + dc.font.ascent + 1, width, wlw);
++		} else if (base.ustyle == 3) {
++			int ww = win.cw;//width;
++			int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
++			int wx = winx;
++			int wy = winy + win.ch - dc.font.descent;
++
++#if UNDERCURL_STYLE == UNDERCURL_CURLY
++			// Draw waves
++			int narcs = charlen * 2 + 1;
++			XArc *arcs = xmalloc(sizeof(XArc) * narcs);
++
++			int i = 0;
++			for (i = 0; i < charlen-1; i++) {
++				arcs[i*2] = (XArc) {
++					.x = wx + win.cw * i + ww / 4,
++					.y = wy,
++					.width = win.cw / 2,
++					.height = wh,
++					.angle1 = 0,
++					.angle2 = 180 * 64
++				};
++				arcs[i*2+1] = (XArc) {
++					.x = wx + win.cw * i + ww * 0.75,
++					.y = wy,
++					.width = win.cw/2,
++					.height = wh,
++					.angle1 = 180 * 64,
++					.angle2 = 180 * 64
++				};
++			}
++			// Last wave
++			arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
++			0, 180 * 64 };
++			// Last wave tail
++			arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
++			wh, 180 * 64, 90 * 64};
++			// First wave tail
++			i++;
++			arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
++			90 * 64 };
++
++			XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
++
++			free(arcs);
++#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
++			// Make the underline corridor larger
++			/*
++			wy -= wh;
++			*/
++			wh *= 2;
++
++			// Set the angle of the slope to 45°
++			ww = wh;
++
++			// Position of wave is independent of word, it's absolute
++			wx = (wx / (ww/2)) * (ww/2);
++
++			int marginStart = winx - wx;
++
++			// Calculate number of points with floating precision
++			float n = width;					// Width of word in pixels
++			n = (n / ww) * 2;					// Number of slopes (/ or \)
++			n += 2;								// Add two last points
++			int npoints = n;					// Convert to int
++
++			// Total length of underline
++			float waveLength = 0;
++
++			if (npoints >= 3) {
++				// We add an aditional slot in case we use a bonus point
++				XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
++
++				// First point (Starts with the word bounds)
++				points[0] = (XPoint) {
++					.x = wx + marginStart,
++					.y = (isSlopeRising(wx, 0, ww))
++						? (wy - marginStart + ww/2.f)
++						: (wy + marginStart)
++				};
++
++				// Second point (Goes back to the absolute point coordinates)
++				points[1] = (XPoint) {
++					.x = (ww/2.f) - marginStart,
++					.y = (isSlopeRising(wx, 1, ww))
++						? (ww/2.f - marginStart)
++						: (-ww/2.f + marginStart)
++				};
++				waveLength += (ww/2.f) - marginStart;
++
++				// The rest of the points
++				for (int i = 2; i < npoints-1; i++) {
++					points[i] = (XPoint) {
++						.x = ww/2,
++						.y = (isSlopeRising(wx, i, ww))
++							? wh/2
++							: -wh/2
++					};
++					waveLength += ww/2;
++				}
++
++				// Last point
++				points[npoints-1] = (XPoint) {
++					.x = ww/2,
++					.y = (isSlopeRising(wx, npoints-1, ww))
++						? wh/2
++						: -wh/2
++				};
++				waveLength += ww/2;
++
++				// End
++				if (waveLength < width) { // Add a bonus point?
++					int marginEnd = width - waveLength;
++					points[npoints] = (XPoint) {
++						.x = marginEnd,
++						.y = (isSlopeRising(wx, npoints, ww))
++							? (marginEnd)
++							: (-marginEnd)
++					};
++
++					npoints++;
++				} else if (waveLength > width) { // Is last point too far?
++					int marginEnd = waveLength - width;
++					points[npoints-1].x -= marginEnd;
++					if (isSlopeRising(wx, npoints-1, ww))
++						points[npoints-1].y -= (marginEnd);
++					else
++						points[npoints-1].y += (marginEnd);
++				}
++
++				// Draw the lines
++				XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
++						CoordModePrevious);
++
++				// Draw a second underline with an offset of 1 pixel
++				if ( ((win.ch / (widthThreshold/2)) % 2)) {
++					points[0].x++;
++
++					XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
++							npoints, CoordModePrevious);
++				}
++
++				// Free resources
++				free(points);
++			}
++#else // UNDERCURL_CAPPED
++			// Cap is half of wave width
++			float capRatio = 0.5f;
++
++			// Make the underline corridor larger
++			wh *= 2;
++
++			// Set the angle of the slope to 45°
++			ww = wh;
++			ww *= 1 + capRatio; // Add a bit of width for the cap
++
++			// Position of wave is independent of word, it's absolute
++			wx = (wx / ww) * ww;
++
++			float marginStart;
++			switch(getSlope(winx, 0, ww)) {
++				case UNDERCURL_SLOPE_ASCENDING:
++					marginStart = winx - wx;
++					break;
++				case UNDERCURL_SLOPE_TOP_CAP:
++					marginStart = winx - (wx + (ww * (2.f/6.f)));
++					break;
++				case UNDERCURL_SLOPE_DESCENDING:
++					marginStart = winx - (wx + (ww * (3.f/6.f)));
++					break;
++				case UNDERCURL_SLOPE_BOTTOM_CAP:
++					marginStart = winx - (wx + (ww * (5.f/6.f)));
++					break;
++			}
++
++			// Calculate number of points with floating precision
++			float n = width;					// Width of word in pixels
++												//					   ._.
++			n = (n / ww) * 4;					// Number of points (./   \.)
++			n += 2;								// Add two last points
++			int npoints = n;					// Convert to int
++
++			// Position of the pen to draw the lines
++			float penX = 0;
++			float penY = 0;
++
++			if (npoints >= 3) {
++				XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
++
++				// First point (Starts with the word bounds)
++				penX = winx;
++				switch (getSlope(winx, 0, ww)) {
++					case UNDERCURL_SLOPE_ASCENDING:
++						penY = wy + wh/2.f - marginStart;
++						break;
++					case UNDERCURL_SLOPE_TOP_CAP:
++						penY = wy;
++						break;
++					case UNDERCURL_SLOPE_DESCENDING:
++						penY = wy + marginStart;
++						break;
++					case UNDERCURL_SLOPE_BOTTOM_CAP:
++						penY = wy + wh/2.f;
++						break;
++				}
++				points[0].x = penX;
++				points[0].y = penY;
++
++				// Second point (Goes back to the absolute point coordinates)
++				switch (getSlope(winx, 1, ww)) {
++					case UNDERCURL_SLOPE_ASCENDING:
++						penX += ww * (1.f/6.f) - marginStart;
++						penY += 0;
++						break;
++					case UNDERCURL_SLOPE_TOP_CAP:
++						penX += ww * (2.f/6.f) - marginStart;
++						penY += -wh/2.f + marginStart;
++						break;
++					case UNDERCURL_SLOPE_DESCENDING:
++						penX += ww * (1.f/6.f) - marginStart;
++						penY += 0;
++						break;
++					case UNDERCURL_SLOPE_BOTTOM_CAP:
++						penX += ww * (2.f/6.f) - marginStart;
++						penY += -marginStart + wh/2.f;
++						break;
++				}
++				points[1].x = penX;
++				points[1].y = penY;
++
++				// The rest of the points
++				for (int i = 2; i < npoints; i++) {
++					switch (getSlope(winx, i, ww)) {
++						case UNDERCURL_SLOPE_ASCENDING:
++						case UNDERCURL_SLOPE_DESCENDING:
++							penX += ww * (1.f/6.f);
++							penY += 0;
++							break;
++						case UNDERCURL_SLOPE_TOP_CAP:
++							penX += ww * (2.f/6.f);
++							penY += -wh / 2.f;
++							break;
++						case UNDERCURL_SLOPE_BOTTOM_CAP:
++							penX += ww * (2.f/6.f);
++							penY += wh / 2.f;
++							break;
++					}
++					points[i].x = penX;
++					points[i].y = penY;
++				}
++
++				// End
++				float waveLength = penX - winx;
++				if (waveLength < width) { // Add a bonus point?
++					int marginEnd = width - waveLength;
++					penX += marginEnd;
++					switch(getSlope(winx, npoints, ww)) {
++						case UNDERCURL_SLOPE_ASCENDING:
++						case UNDERCURL_SLOPE_DESCENDING:
++							//penY += 0;
++							break;
++						case UNDERCURL_SLOPE_TOP_CAP:
++							penY += -marginEnd;
++							break;
++						case UNDERCURL_SLOPE_BOTTOM_CAP:
++							penY += marginEnd;
++							break;
++					}
++
++					points[npoints].x = penX;
++					points[npoints].y = penY;
++
++					npoints++;
++				} else if (waveLength > width) { // Is last point too far?
++					int marginEnd = waveLength - width;
++					points[npoints-1].x -= marginEnd;
++					switch(getSlope(winx, npoints-1, ww)) {
++						case UNDERCURL_SLOPE_TOP_CAP:
++							points[npoints-1].y += marginEnd;
++							break;
++						case UNDERCURL_SLOPE_BOTTOM_CAP:
++							points[npoints-1].y -= marginEnd;
++							break;
++						default:
++							break;
++					}
++				}
++
++				// Draw the lines
++				XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
++						CoordModeOrigin);
++
++				// Draw a second underline with an offset of 1 pixel
++				if ( ((win.ch / (widthThreshold/2)) % 2)) {
++					for (int i = 0; i < npoints; i++)
++						points[i].x++;
++
++					XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
++							npoints, CoordModeOrigin);
++				}
++
++				// Free resources
++				free(points);
++			}
++#endif
++		}
++
++		XFreeGC(xw.dpy, ugc);
+ 	}
+ 
+ 	if (base.mode & ATTR_STRUCK) {
diff --git a/st.suckless.org/patches/undercurl/st-undercurl-0.8.4.diff b/st.suckless.org/patches/undercurl/st-undercurl-0.8.4.diff
@@ -1,244 +0,0 @@
-diff --git a/st.c b/st.c
-index 76b7e0d..542ab3a 100644
---- a/st.c
-+++ b/st.c
-@@ -33,6 +33,7 @@
- #define UTF_SIZ       4
- #define ESC_BUF_SIZ   (128*UTF_SIZ)
- #define ESC_ARG_SIZ   16
-+#define CAR_PER_ARG   4
- #define STR_BUF_SIZ   ESC_BUF_SIZ
- #define STR_ARG_SIZ   ESC_ARG_SIZ
- 
-@@ -139,6 +140,7 @@ typedef struct {
- 	int arg[ESC_ARG_SIZ];
- 	int narg;              /* nb of args */
- 	char mode[2];
-+	int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
- } CSIEscape;
- 
- /* STR Escape sequence structs */
-@@ -159,6 +161,7 @@ static void ttywriteraw(const char *, size_t);
- 
- static void csidump(void);
- static void csihandle(void);
-+static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
- static void csiparse(void);
- static void csireset(void);
- static int eschandle(uchar);
-@@ -1131,6 +1134,28 @@ tnewline(int first_col)
- 	tmoveto(first_col ? 0 : term.c.x, y);
- }
- 
-+void
-+readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
-+{
-+	int i = 0;
-+	for (; i < CAR_PER_ARG; i++)
-+		params[cursor][i] = -1;
-+
-+	if (**p != ':')
-+		return;
-+
-+	char *np = NULL;
-+	i = 0;
-+
-+	while (**p == ':' && i < CAR_PER_ARG) {
-+		while (**p == ':')
-+			(*p)++;
-+		params[cursor][i] = strtol(*p, &np, 10);
-+		*p = np;
-+		i++;
-+	}
-+}
-+
- void
- csiparse(void)
- {
-@@ -1153,6 +1178,7 @@ csiparse(void)
- 			v = -1;
- 		csiescseq.arg[csiescseq.narg++] = v;
- 		p = np;
-+		readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
- 		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
- 			break;
- 		p++;
-@@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l)
- 				ATTR_STRUCK     );
- 			term.c.attr.fg = defaultfg;
- 			term.c.attr.bg = defaultbg;
-+			term.c.attr.ustyle = -1;
-+			term.c.attr.ucolor[0] = -1;
-+			term.c.attr.ucolor[1] = -1;
-+			term.c.attr.ucolor[2] = -1;
- 			break;
- 		case 1:
- 			term.c.attr.mode |= ATTR_BOLD;
-@@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l)
- 			term.c.attr.mode |= ATTR_ITALIC;
- 			break;
- 		case 4:
--			term.c.attr.mode |= ATTR_UNDERLINE;
-+			term.c.attr.ustyle = csiescseq.carg[i][0];
-+
-+			if (term.c.attr.ustyle != 0)
-+				term.c.attr.mode |= ATTR_UNDERLINE;
-+			else
-+				term.c.attr.mode &= ~ATTR_UNDERLINE;
-+
-+			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
- 			break;
- 		case 5: /* slow blink */
- 			/* FALLTHROUGH */
-@@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l)
- 		case 49:
- 			term.c.attr.bg = defaultbg;
- 			break;
-+		case 58:
-+			term.c.attr.ucolor[0] = csiescseq.carg[i][1];
-+			term.c.attr.ucolor[1] = csiescseq.carg[i][2];
-+			term.c.attr.ucolor[2] = csiescseq.carg[i][3];
-+			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
-+			break;
-+		case 59:
-+			term.c.attr.ucolor[0] = -1;
-+			term.c.attr.ucolor[1] = -1;
-+			term.c.attr.ucolor[2] = -1;
-+			term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
-+			break;
- 		default:
- 			if (BETWEEN(attr[i], 30, 37)) {
- 				term.c.attr.fg = attr[i] - 30;
-diff --git a/st.h b/st.h
-index 3d351b6..2cfac88 100644
---- a/st.h
-+++ b/st.h
-@@ -34,6 +34,7 @@ enum glyph_attribute {
- 	ATTR_WIDE       = 1 << 9,
- 	ATTR_WDUMMY     = 1 << 10,
- 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
-+	ATTR_DIRTYUNDERLINE = 1 << 15,
- };
- 
- enum selection_mode {
-@@ -65,6 +66,8 @@ typedef struct {
- 	ushort mode;      /* attribute flags */
- 	uint32_t fg;      /* foreground  */
- 	uint32_t bg;      /* background  */
-+	int ustyle;	  /* underline style */
-+	int ucolor[3];    /* underline color */
- } Glyph;
- 
- typedef Glyph *Line;
-diff --git a/st.info b/st.info
-index 8201ad6..659878c 100644
---- a/st.info
-+++ b/st.info
-@@ -1,4 +1,5 @@
- st-mono| simpleterm monocolor,
-+	Su,
- 	acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
- 	am,
- 	bce,
-diff --git a/x.c b/x.c
-index 210f184..9a6a2bd 100644
---- a/x.c
-+++ b/x.c
-@@ -1461,8 +1461,95 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
- 
- 	/* Render underline and strikethrough. */
- 	if (base.mode & ATTR_UNDERLINE) {
--		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
--				width, 1);
-+		// Underline Color
-+		int wlw = 1; // Wave Line Width
-+		int linecolor;
-+		if ((base.ucolor[0] >= 0) &&
-+			!(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
-+			!(base.mode & ATTR_INVISIBLE)
-+		) {
-+			// Special color for underline
-+			// Index
-+			if (base.ucolor[1] < 0) {
-+				linecolor = dc.col[base.ucolor[0]].pixel;
-+			}
-+			// RGB
-+			else {
-+				XColor lcolor;
-+				lcolor.red = base.ucolor[0] * 257;
-+				lcolor.green = base.ucolor[1] * 257;
-+				lcolor.blue = base.ucolor[2] * 257;
-+				lcolor.flags = DoRed | DoGreen | DoBlue;
-+				XAllocColor(xw.dpy, xw.cmap, &lcolor);
-+				linecolor = lcolor.pixel;
-+			}
-+		} else {
-+			// Foreground color for underline
-+			linecolor = fg->pixel;
-+		}
-+
-+		XGCValues ugcv = {
-+			.foreground = linecolor,
-+			.line_width = wlw,
-+			.line_style = LineSolid,
-+			.cap_style = CapNotLast
-+		};
-+
-+		GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
-+			GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
-+			&ugcv);
-+
-+		// Underline Style
-+		if (base.ustyle != 3) {
-+			//XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
-+			XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
-+				winy + dc.font.ascent + 1, width, wlw);
-+		} else if (base.ustyle == 3) {
-+			int ww = win.cw;//width;
-+			int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
-+			int wx = winx;
-+			int wy = winy + win.ch - dc.font.descent;
-+
-+			// Draw waves
-+			int narcs = charlen * 2 + 1;
-+			XArc *arcs = xmalloc(sizeof(XArc) * narcs);
-+
-+			int i = 0;
-+			for (i = 0; i < charlen-1; i++) {
-+				arcs[i*2] = (XArc) {
-+					.x = wx + win.cw * i + ww / 4,
-+					.y = wy,
-+					.width = win.cw / 2,
-+					.height = wh,
-+					.angle1 = 0,
-+					.angle2 = 180 * 64
-+				};
-+				arcs[i*2+1] = (XArc) {
-+					.x = wx + win.cw * i + ww * 0.75,
-+					.y = wy,
-+					.width = win.cw/2,
-+					.height = wh,
-+					.angle1 = 180 * 64,
-+					.angle2 = 180 * 64
-+				};
-+			}
-+			// Last wave
-+			arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
-+			0, 180 * 64 };
-+			// Last wave tail
-+			arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
-+			wh, 180 * 64, 90 * 64};
-+			// First wave tail
-+			i++;
-+			arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
-+			90 * 64 };
-+
-+			XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
-+
-+			free(arcs);
-+		}
-+
-+		XFreeGC(xw.dpy, ugc);
- 	}
- 
- 	if (base.mode & ATTR_STRUCK) {
diff --git a/st.suckless.org/patches/undercurl/undercurl.png b/st.suckless.org/patches/undercurl/undercurl.png
Binary files differ.