sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

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:
Mst.suckless.org/patches/undercurl/index.md | 5++++-
Ast.suckless.org/patches/undercurl/st-undercurl-0.8.4-20210822.diff | 605+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dst.suckless.org/patches/undercurl/st-undercurl-0.8.4.diff | 244-------------------------------------------------------------------------------
Mst.suckless.org/patches/undercurl/undercurl.png | 0
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.