├── images ├── terminal.png ├── terminal2.png ├── terminal3.png ├── style_curly.png ├── style_spiky.png └── style_capped.png ├── LICENSE ├── README.md ├── st-undercurl-0.8.4-20210822.diff └── st-undercurl-0.9-20240103.diff /images/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/terminal.png -------------------------------------------------------------------------------- /images/terminal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/terminal2.png -------------------------------------------------------------------------------- /images/terminal3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/terminal3.png -------------------------------------------------------------------------------- /images/style_curly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/style_curly.png -------------------------------------------------------------------------------- /images/style_spiky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/style_spiky.png -------------------------------------------------------------------------------- /images/style_capped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derriche-massinissa/st-undercurl/HEAD/images/style_capped.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hexoctal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | ST Undercurl 3 |

4 |

A patch for ST (Simple Terminal) adding support for curly and colored underlines.

5 |

6 | Screenshots | 7 | Installation 8 | Style 9 |
10 |
11 | platforms 12 | license 13 |
14 | issues 15 | size 16 | starts 17 |
18 |

19 | 20 | # 21 | 22 | ## Screenshots 23 | 24 | ![terminal neovim lsp](images/terminal3.png) 25 | ![terminal](images/terminal.png) 26 | ![terminal](images/terminal2.png) 27 | 28 | ## Installation 29 | 30 | Install like any other patch. Go into the source directory of ST, and run the 31 | following command: 32 | ```shell 33 | patch < ../st-undercurl-0.8.4-20210424.diff 34 | ``` 35 | 36 | Then build it: 37 | ```shell 38 | make 39 | ``` 40 | 41 | For installation, you either use Make (recommended): 42 | ```shell 43 | make install 44 | ``` 45 | And you're done! 46 | 47 | Or any other method, but in this case, you __HAVE__ to update the `TermInfo` database of 48 | your system, to let terminal programs (Like Vim and such) know that ST supports 49 | special underlines. You do this with the following command: 50 | ```shell 51 | tic -sx st.info 52 | ``` 53 | 54 | ## Style 55 | 56 | You can choose between three different curly underline styles: 57 | 58 | Curly: 59 | ![style curly](images/style_curly.png) 60 | 61 | Spiky: 62 | ![style spiky](images/style_spiky.png) 63 | 64 | Capped: 65 | ![style capped](images/style_capped.png) 66 | 67 | To change the style, edit the `config.def.h` file, it looks like this: 68 | ```cpp 69 | // Available styles 70 | #define UNDERCURL_CURLY 0 71 | #define UNDERCURL_SPIKY 1 72 | #define UNDERCURL_CAPPED 2 73 | // Active style 74 | #define UNDERCURL_STYLE UNDERCURL_CAPPED 75 | ``` 76 | 77 | Just modify `UNDERCURL_STYLE` to one of the three available styles. 78 | -------------------------------------------------------------------------------- /st-undercurl-0.8.4-20210822.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 6f05dce..7ae1b92 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -470,3 +470,27 @@ static char ascii_printable[] = 6 | " !\"#$%&'()*+,-./0123456789:;<=>?" 7 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 8 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 9 | + 10 | +/** 11 | + * Undercurl style. Set UNDERCURL_STYLE to one of the available styles. 12 | + * 13 | + * Curly: Dunno how to draw it *shrug* 14 | + * _ _ _ _ 15 | + * ( ) ( ) ( ) ( ) 16 | + * (_) (_) (_) (_) 17 | + * 18 | + * Spiky: 19 | + * /\ /\ /\ /\ 20 | + * \/ \/ \/ 21 | + * 22 | + * Capped: 23 | + * _ _ _ 24 | + * / \ / \ / \ 25 | + * \_/ \_/ 26 | + */ 27 | +// Available styles 28 | +#define UNDERCURL_CURLY 0 29 | +#define UNDERCURL_SPIKY 1 30 | +#define UNDERCURL_CAPPED 2 31 | +// Active style 32 | +#define UNDERCURL_STYLE UNDERCURL_SPIKY 33 | diff --git a/st.c b/st.c 34 | index 76b7e0d..542ab3a 100644 35 | --- a/st.c 36 | +++ b/st.c 37 | @@ -33,6 +33,7 @@ 38 | #define UTF_SIZ 4 39 | #define ESC_BUF_SIZ (128*UTF_SIZ) 40 | #define ESC_ARG_SIZ 16 41 | +#define CAR_PER_ARG 4 42 | #define STR_BUF_SIZ ESC_BUF_SIZ 43 | #define STR_ARG_SIZ ESC_ARG_SIZ 44 | 45 | @@ -139,6 +140,7 @@ typedef struct { 46 | int arg[ESC_ARG_SIZ]; 47 | int narg; /* nb of args */ 48 | char mode[2]; 49 | + int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */ 50 | } CSIEscape; 51 | 52 | /* STR Escape sequence structs */ 53 | @@ -159,6 +161,7 @@ static void ttywriteraw(const char *, size_t); 54 | 55 | static void csidump(void); 56 | static void csihandle(void); 57 | +static void readcolonargs(char **, int, int[][CAR_PER_ARG]); 58 | static void csiparse(void); 59 | static void csireset(void); 60 | static int eschandle(uchar); 61 | @@ -1131,6 +1134,28 @@ tnewline(int first_col) 62 | tmoveto(first_col ? 0 : term.c.x, y); 63 | } 64 | 65 | +void 66 | +readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG]) 67 | +{ 68 | + int i = 0; 69 | + for (; i < CAR_PER_ARG; i++) 70 | + params[cursor][i] = -1; 71 | + 72 | + if (**p != ':') 73 | + return; 74 | + 75 | + char *np = NULL; 76 | + i = 0; 77 | + 78 | + while (**p == ':' && i < CAR_PER_ARG) { 79 | + while (**p == ':') 80 | + (*p)++; 81 | + params[cursor][i] = strtol(*p, &np, 10); 82 | + *p = np; 83 | + i++; 84 | + } 85 | +} 86 | + 87 | void 88 | csiparse(void) 89 | { 90 | @@ -1153,6 +1178,7 @@ csiparse(void) 91 | v = -1; 92 | csiescseq.arg[csiescseq.narg++] = v; 93 | p = np; 94 | + readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); 95 | if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 96 | break; 97 | p++; 98 | @@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l) 99 | ATTR_STRUCK ); 100 | term.c.attr.fg = defaultfg; 101 | term.c.attr.bg = defaultbg; 102 | + term.c.attr.ustyle = -1; 103 | + term.c.attr.ucolor[0] = -1; 104 | + term.c.attr.ucolor[1] = -1; 105 | + term.c.attr.ucolor[2] = -1; 106 | break; 107 | case 1: 108 | term.c.attr.mode |= ATTR_BOLD; 109 | @@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l) 110 | term.c.attr.mode |= ATTR_ITALIC; 111 | break; 112 | case 4: 113 | - term.c.attr.mode |= ATTR_UNDERLINE; 114 | + term.c.attr.ustyle = csiescseq.carg[i][0]; 115 | + 116 | + if (term.c.attr.ustyle != 0) 117 | + term.c.attr.mode |= ATTR_UNDERLINE; 118 | + else 119 | + term.c.attr.mode &= ~ATTR_UNDERLINE; 120 | + 121 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 122 | break; 123 | case 5: /* slow blink */ 124 | /* FALLTHROUGH */ 125 | @@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l) 126 | case 49: 127 | term.c.attr.bg = defaultbg; 128 | break; 129 | + case 58: 130 | + term.c.attr.ucolor[0] = csiescseq.carg[i][1]; 131 | + term.c.attr.ucolor[1] = csiescseq.carg[i][2]; 132 | + term.c.attr.ucolor[2] = csiescseq.carg[i][3]; 133 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 134 | + break; 135 | + case 59: 136 | + term.c.attr.ucolor[0] = -1; 137 | + term.c.attr.ucolor[1] = -1; 138 | + term.c.attr.ucolor[2] = -1; 139 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 140 | + break; 141 | default: 142 | if (BETWEEN(attr[i], 30, 37)) { 143 | term.c.attr.fg = attr[i] - 30; 144 | diff --git a/st.h b/st.h 145 | index 3d351b6..95bdcbd 100644 146 | --- a/st.h 147 | +++ b/st.h 148 | @@ -34,6 +34,7 @@ enum glyph_attribute { 149 | ATTR_WIDE = 1 << 9, 150 | ATTR_WDUMMY = 1 << 10, 151 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 152 | + ATTR_DIRTYUNDERLINE = 1 << 15, 153 | }; 154 | 155 | enum selection_mode { 156 | @@ -65,6 +66,8 @@ typedef struct { 157 | ushort mode; /* attribute flags */ 158 | uint32_t fg; /* foreground */ 159 | uint32_t bg; /* background */ 160 | + int ustyle; /* underline style */ 161 | + int ucolor[3]; /* underline color */ 162 | } Glyph; 163 | 164 | typedef Glyph *Line; 165 | diff --git a/st.info b/st.info 166 | index 8201ad6..659878c 100644 167 | --- a/st.info 168 | +++ b/st.info 169 | @@ -1,4 +1,5 @@ 170 | st-mono| simpleterm monocolor, 171 | + Su, 172 | acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 173 | am, 174 | bce, 175 | diff --git a/x.c b/x.c 176 | index 210f184..3a0e79e 100644 177 | --- a/x.c 178 | +++ b/x.c 179 | @@ -45,6 +45,14 @@ typedef struct { 180 | signed char appcursor; /* application cursor */ 181 | } Key; 182 | 183 | +/* Undercurl slope types */ 184 | +enum undercurl_slope_type { 185 | + UNDERCURL_SLOPE_ASCENDING = 0, 186 | + UNDERCURL_SLOPE_TOP_CAP = 1, 187 | + UNDERCURL_SLOPE_DESCENDING = 2, 188 | + UNDERCURL_SLOPE_BOTTOM_CAP = 3 189 | +}; 190 | + 191 | /* X modifiers */ 192 | #define XK_ANY_MOD UINT_MAX 193 | #define XK_NO_MOD 0 194 | @@ -1339,6 +1347,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 195 | return numspecs; 196 | } 197 | 198 | +static int isSlopeRising (int x, int iPoint, int waveWidth) 199 | +{ 200 | + // . . . . 201 | + // / \ / \ / \ / \ 202 | + // / \ / \ / \ / \ 203 | + // . . . . . 204 | + 205 | + // Find absolute `x` of point 206 | + x += iPoint * (waveWidth/2); 207 | + 208 | + // Find index of absolute wave 209 | + int absSlope = x / ((float)waveWidth/2); 210 | + 211 | + return (absSlope % 2); 212 | +} 213 | + 214 | +static int getSlope (int x, int iPoint, int waveWidth) 215 | +{ 216 | + // Sizes: Caps are half width of slopes 217 | + // 1_2 1_2 1_2 1_2 218 | + // / \ / \ / \ / \ 219 | + // / \ / \ / \ / \ 220 | + // 0 3_0 3_0 3_0 3_ 221 | + // <2-> <1> <---6----> 222 | + 223 | + // Find type of first point 224 | + int firstType; 225 | + x -= (x / waveWidth) * waveWidth; 226 | + if (x < (waveWidth * (2.f/6.f))) 227 | + firstType = UNDERCURL_SLOPE_ASCENDING; 228 | + else if (x < (waveWidth * (3.f/6.f))) 229 | + firstType = UNDERCURL_SLOPE_TOP_CAP; 230 | + else if (x < (waveWidth * (5.f/6.f))) 231 | + firstType = UNDERCURL_SLOPE_DESCENDING; 232 | + else 233 | + firstType = UNDERCURL_SLOPE_BOTTOM_CAP; 234 | + 235 | + // Find type of given point 236 | + int pointType = (iPoint % 4); 237 | + pointType += firstType; 238 | + pointType %= 4; 239 | + 240 | + return pointType; 241 | +} 242 | + 243 | void 244 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 245 | { 246 | @@ -1461,8 +1514,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 247 | 248 | /* Render underline and strikethrough. */ 249 | if (base.mode & ATTR_UNDERLINE) { 250 | - XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, 251 | - width, 1); 252 | + // Underline Color 253 | + const int widthThreshold = 28; // +1 width every widthThreshold px of font 254 | + int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width 255 | + int linecolor; 256 | + if ((base.ucolor[0] >= 0) && 257 | + !(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) && 258 | + !(base.mode & ATTR_INVISIBLE) 259 | + ) { 260 | + // Special color for underline 261 | + // Index 262 | + if (base.ucolor[1] < 0) { 263 | + linecolor = dc.col[base.ucolor[0]].pixel; 264 | + } 265 | + // RGB 266 | + else { 267 | + XColor lcolor; 268 | + lcolor.red = base.ucolor[0] * 257; 269 | + lcolor.green = base.ucolor[1] * 257; 270 | + lcolor.blue = base.ucolor[2] * 257; 271 | + lcolor.flags = DoRed | DoGreen | DoBlue; 272 | + XAllocColor(xw.dpy, xw.cmap, &lcolor); 273 | + linecolor = lcolor.pixel; 274 | + } 275 | + } else { 276 | + // Foreground color for underline 277 | + linecolor = fg->pixel; 278 | + } 279 | + 280 | + XGCValues ugcv = { 281 | + .foreground = linecolor, 282 | + .line_width = wlw, 283 | + .line_style = LineSolid, 284 | + .cap_style = CapNotLast 285 | + }; 286 | + 287 | + GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), 288 | + GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, 289 | + &ugcv); 290 | + 291 | + // Underline Style 292 | + if (base.ustyle != 3) { 293 | + //XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); 294 | + XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx, 295 | + winy + dc.font.ascent + 1, width, wlw); 296 | + } else if (base.ustyle == 3) { 297 | + int ww = win.cw;//width; 298 | + int wh = dc.font.descent - wlw/2 - 1;//r.height/7; 299 | + int wx = winx; 300 | + int wy = winy + win.ch - dc.font.descent; 301 | + 302 | +#if UNDERCURL_STYLE == UNDERCURL_CURLY 303 | + // Draw waves 304 | + int narcs = charlen * 2 + 1; 305 | + XArc *arcs = xmalloc(sizeof(XArc) * narcs); 306 | + 307 | + int i = 0; 308 | + for (i = 0; i < charlen-1; i++) { 309 | + arcs[i*2] = (XArc) { 310 | + .x = wx + win.cw * i + ww / 4, 311 | + .y = wy, 312 | + .width = win.cw / 2, 313 | + .height = wh, 314 | + .angle1 = 0, 315 | + .angle2 = 180 * 64 316 | + }; 317 | + arcs[i*2+1] = (XArc) { 318 | + .x = wx + win.cw * i + ww * 0.75, 319 | + .y = wy, 320 | + .width = win.cw/2, 321 | + .height = wh, 322 | + .angle1 = 180 * 64, 323 | + .angle2 = 180 * 64 324 | + }; 325 | + } 326 | + // Last wave 327 | + arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh, 328 | + 0, 180 * 64 }; 329 | + // Last wave tail 330 | + arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.), 331 | + wh, 180 * 64, 90 * 64}; 332 | + // First wave tail 333 | + i++; 334 | + arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64, 335 | + 90 * 64 }; 336 | + 337 | + XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs); 338 | + 339 | + free(arcs); 340 | +#elif UNDERCURL_STYLE == UNDERCURL_SPIKY 341 | + // Make the underline corridor larger 342 | + /* 343 | + wy -= wh; 344 | + */ 345 | + wh *= 2; 346 | + 347 | + // Set the angle of the slope to 45° 348 | + ww = wh; 349 | + 350 | + // Position of wave is independent of word, it's absolute 351 | + wx = (wx / (ww/2)) * (ww/2); 352 | + 353 | + int marginStart = winx - wx; 354 | + 355 | + // Calculate number of points with floating precision 356 | + float n = width; // Width of word in pixels 357 | + n = (n / ww) * 2; // Number of slopes (/ or \) 358 | + n += 2; // Add two last points 359 | + int npoints = n; // Convert to int 360 | + 361 | + // Total length of underline 362 | + float waveLength = 0; 363 | + 364 | + if (npoints >= 3) { 365 | + // We add an aditional slot in case we use a bonus point 366 | + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); 367 | + 368 | + // First point (Starts with the word bounds) 369 | + points[0] = (XPoint) { 370 | + .x = wx + marginStart, 371 | + .y = (isSlopeRising(wx, 0, ww)) 372 | + ? (wy - marginStart + ww/2.f) 373 | + : (wy + marginStart) 374 | + }; 375 | + 376 | + // Second point (Goes back to the absolute point coordinates) 377 | + points[1] = (XPoint) { 378 | + .x = (ww/2.f) - marginStart, 379 | + .y = (isSlopeRising(wx, 1, ww)) 380 | + ? (ww/2.f - marginStart) 381 | + : (-ww/2.f + marginStart) 382 | + }; 383 | + waveLength += (ww/2.f) - marginStart; 384 | + 385 | + // The rest of the points 386 | + for (int i = 2; i < npoints-1; i++) { 387 | + points[i] = (XPoint) { 388 | + .x = ww/2, 389 | + .y = (isSlopeRising(wx, i, ww)) 390 | + ? wh/2 391 | + : -wh/2 392 | + }; 393 | + waveLength += ww/2; 394 | + } 395 | + 396 | + // Last point 397 | + points[npoints-1] = (XPoint) { 398 | + .x = ww/2, 399 | + .y = (isSlopeRising(wx, npoints-1, ww)) 400 | + ? wh/2 401 | + : -wh/2 402 | + }; 403 | + waveLength += ww/2; 404 | + 405 | + // End 406 | + if (waveLength < width) { // Add a bonus point? 407 | + int marginEnd = width - waveLength; 408 | + points[npoints] = (XPoint) { 409 | + .x = marginEnd, 410 | + .y = (isSlopeRising(wx, npoints, ww)) 411 | + ? (marginEnd) 412 | + : (-marginEnd) 413 | + }; 414 | + 415 | + npoints++; 416 | + } else if (waveLength > width) { // Is last point too far? 417 | + int marginEnd = waveLength - width; 418 | + points[npoints-1].x -= marginEnd; 419 | + if (isSlopeRising(wx, npoints-1, ww)) 420 | + points[npoints-1].y -= (marginEnd); 421 | + else 422 | + points[npoints-1].y += (marginEnd); 423 | + } 424 | + 425 | + // Draw the lines 426 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, 427 | + CoordModePrevious); 428 | + 429 | + // Draw a second underline with an offset of 1 pixel 430 | + if ( ((win.ch / (widthThreshold/2)) % 2)) { 431 | + points[0].x++; 432 | + 433 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, 434 | + npoints, CoordModePrevious); 435 | + } 436 | + 437 | + // Free resources 438 | + free(points); 439 | + } 440 | +#else // UNDERCURL_CAPPED 441 | + // Cap is half of wave width 442 | + float capRatio = 0.5f; 443 | + 444 | + // Make the underline corridor larger 445 | + wh *= 2; 446 | + 447 | + // Set the angle of the slope to 45° 448 | + ww = wh; 449 | + ww *= 1 + capRatio; // Add a bit of width for the cap 450 | + 451 | + // Position of wave is independent of word, it's absolute 452 | + wx = (wx / ww) * ww; 453 | + 454 | + float marginStart; 455 | + switch(getSlope(winx, 0, ww)) { 456 | + case UNDERCURL_SLOPE_ASCENDING: 457 | + marginStart = winx - wx; 458 | + break; 459 | + case UNDERCURL_SLOPE_TOP_CAP: 460 | + marginStart = winx - (wx + (ww * (2.f/6.f))); 461 | + break; 462 | + case UNDERCURL_SLOPE_DESCENDING: 463 | + marginStart = winx - (wx + (ww * (3.f/6.f))); 464 | + break; 465 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 466 | + marginStart = winx - (wx + (ww * (5.f/6.f))); 467 | + break; 468 | + } 469 | + 470 | + // Calculate number of points with floating precision 471 | + float n = width; // Width of word in pixels 472 | + // ._. 473 | + n = (n / ww) * 4; // Number of points (./ \.) 474 | + n += 2; // Add two last points 475 | + int npoints = n; // Convert to int 476 | + 477 | + // Position of the pen to draw the lines 478 | + float penX = 0; 479 | + float penY = 0; 480 | + 481 | + if (npoints >= 3) { 482 | + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); 483 | + 484 | + // First point (Starts with the word bounds) 485 | + penX = winx; 486 | + switch (getSlope(winx, 0, ww)) { 487 | + case UNDERCURL_SLOPE_ASCENDING: 488 | + penY = wy + wh/2.f - marginStart; 489 | + break; 490 | + case UNDERCURL_SLOPE_TOP_CAP: 491 | + penY = wy; 492 | + break; 493 | + case UNDERCURL_SLOPE_DESCENDING: 494 | + penY = wy + marginStart; 495 | + break; 496 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 497 | + penY = wy + wh/2.f; 498 | + break; 499 | + } 500 | + points[0].x = penX; 501 | + points[0].y = penY; 502 | + 503 | + // Second point (Goes back to the absolute point coordinates) 504 | + switch (getSlope(winx, 1, ww)) { 505 | + case UNDERCURL_SLOPE_ASCENDING: 506 | + penX += ww * (1.f/6.f) - marginStart; 507 | + penY += 0; 508 | + break; 509 | + case UNDERCURL_SLOPE_TOP_CAP: 510 | + penX += ww * (2.f/6.f) - marginStart; 511 | + penY += -wh/2.f + marginStart; 512 | + break; 513 | + case UNDERCURL_SLOPE_DESCENDING: 514 | + penX += ww * (1.f/6.f) - marginStart; 515 | + penY += 0; 516 | + break; 517 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 518 | + penX += ww * (2.f/6.f) - marginStart; 519 | + penY += -marginStart + wh/2.f; 520 | + break; 521 | + } 522 | + points[1].x = penX; 523 | + points[1].y = penY; 524 | + 525 | + // The rest of the points 526 | + for (int i = 2; i < npoints; i++) { 527 | + switch (getSlope(winx, i, ww)) { 528 | + case UNDERCURL_SLOPE_ASCENDING: 529 | + case UNDERCURL_SLOPE_DESCENDING: 530 | + penX += ww * (1.f/6.f); 531 | + penY += 0; 532 | + break; 533 | + case UNDERCURL_SLOPE_TOP_CAP: 534 | + penX += ww * (2.f/6.f); 535 | + penY += -wh / 2.f; 536 | + break; 537 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 538 | + penX += ww * (2.f/6.f); 539 | + penY += wh / 2.f; 540 | + break; 541 | + } 542 | + points[i].x = penX; 543 | + points[i].y = penY; 544 | + } 545 | + 546 | + // End 547 | + float waveLength = penX - winx; 548 | + if (waveLength < width) { // Add a bonus point? 549 | + int marginEnd = width - waveLength; 550 | + penX += marginEnd; 551 | + switch(getSlope(winx, npoints, ww)) { 552 | + case UNDERCURL_SLOPE_ASCENDING: 553 | + case UNDERCURL_SLOPE_DESCENDING: 554 | + //penY += 0; 555 | + break; 556 | + case UNDERCURL_SLOPE_TOP_CAP: 557 | + penY += -marginEnd; 558 | + break; 559 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 560 | + penY += marginEnd; 561 | + break; 562 | + } 563 | + 564 | + points[npoints].x = penX; 565 | + points[npoints].y = penY; 566 | + 567 | + npoints++; 568 | + } else if (waveLength > width) { // Is last point too far? 569 | + int marginEnd = waveLength - width; 570 | + points[npoints-1].x -= marginEnd; 571 | + switch(getSlope(winx, npoints-1, ww)) { 572 | + case UNDERCURL_SLOPE_TOP_CAP: 573 | + points[npoints-1].y += marginEnd; 574 | + break; 575 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 576 | + points[npoints-1].y -= marginEnd; 577 | + break; 578 | + default: 579 | + break; 580 | + } 581 | + } 582 | + 583 | + // Draw the lines 584 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, 585 | + CoordModeOrigin); 586 | + 587 | + // Draw a second underline with an offset of 1 pixel 588 | + if ( ((win.ch / (widthThreshold/2)) % 2)) { 589 | + for (int i = 0; i < npoints; i++) 590 | + points[i].x++; 591 | + 592 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, 593 | + npoints, CoordModeOrigin); 594 | + } 595 | + 596 | + // Free resources 597 | + free(points); 598 | + } 599 | +#endif 600 | + } 601 | + 602 | + XFreeGC(xw.dpy, ugc); 603 | } 604 | 605 | if (base.mode & ATTR_STRUCK) { 606 | -------------------------------------------------------------------------------- /st-undercurl-0.9-20240103.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 6f05dce..7ae1b92 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -470,3 +470,27 @@ static char ascii_printable[] = 6 | " !\"#$%&'()*+,-./0123456789:;<=>?" 7 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 8 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 9 | + 10 | +/** 11 | + * Undercurl style. Set UNDERCURL_STYLE to one of the available styles. 12 | + * 13 | + * Curly: Dunno how to draw it *shrug* 14 | + * _ _ _ _ 15 | + * ( ) ( ) ( ) ( ) 16 | + * (_) (_) (_) (_) 17 | + * 18 | + * Spiky: 19 | + * /\ /\ /\ /\ 20 | + * \/ \/ \/ 21 | + * 22 | + * Capped: 23 | + * _ _ _ 24 | + * / \ / \ / \ 25 | + * \_/ \_/ 26 | + */ 27 | +// Available styles 28 | +#define UNDERCURL_CURLY 0 29 | +#define UNDERCURL_SPIKY 1 30 | +#define UNDERCURL_CAPPED 2 31 | +// Active style 32 | +#define UNDERCURL_STYLE UNDERCURL_SPIKY 33 | diff --git a/st.c b/st.c 34 | index 76b7e0d..542ab3a 100644 35 | --- a/st.c 36 | +++ b/st.c 37 | @@ -33,6 +33,7 @@ 38 | #define UTF_SIZ 4 39 | #define ESC_BUF_SIZ (128*UTF_SIZ) 40 | #define ESC_ARG_SIZ 16 41 | +#define CAR_PER_ARG 4 42 | #define STR_BUF_SIZ ESC_BUF_SIZ 43 | #define STR_ARG_SIZ ESC_ARG_SIZ 44 | 45 | @@ -139,6 +140,7 @@ typedef struct { 46 | int arg[ESC_ARG_SIZ]; 47 | int narg; /* nb of args */ 48 | char mode[2]; 49 | + int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */ 50 | } CSIEscape; 51 | 52 | /* STR Escape sequence structs */ 53 | @@ -159,7 +161,8 @@ static void ttywriteraw(const char *, size_t); 54 | 55 | static void csidump(void); 56 | static void csihandle(void); 57 | +static void readcolonargs(char **, int, int[][CAR_PER_ARG]); 58 | static void csiparse(void); 59 | static void csireset(void); 60 | static void osc_color_response(int, int, int); 61 | static int eschandle(uchar); 62 | @@ -1131,6 +1134,28 @@ tnewline(int first_col) 63 | tmoveto(first_col ? 0 : term.c.x, y); 64 | } 65 | 66 | +void 67 | +readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG]) 68 | +{ 69 | + int i = 0; 70 | + for (; i < CAR_PER_ARG; i++) 71 | + params[cursor][i] = -1; 72 | + 73 | + if (**p != ':') 74 | + return; 75 | + 76 | + char *np = NULL; 77 | + i = 0; 78 | + 79 | + while (**p == ':' && i < CAR_PER_ARG) { 80 | + while (**p == ':') 81 | + (*p)++; 82 | + params[cursor][i] = strtol(*p, &np, 10); 83 | + *p = np; 84 | + i++; 85 | + } 86 | +} 87 | + 88 | void 89 | csiparse(void) 90 | { 91 | @@ -1153,6 +1178,7 @@ csiparse(void) 92 | v = -1; 93 | csiescseq.arg[csiescseq.narg++] = v; 94 | p = np; 95 | + readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); 96 | if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 97 | break; 98 | p++; 99 | @@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l) 100 | ATTR_STRUCK ); 101 | term.c.attr.fg = defaultfg; 102 | term.c.attr.bg = defaultbg; 103 | + term.c.attr.ustyle = -1; 104 | + term.c.attr.ucolor[0] = -1; 105 | + term.c.attr.ucolor[1] = -1; 106 | + term.c.attr.ucolor[2] = -1; 107 | break; 108 | case 1: 109 | term.c.attr.mode |= ATTR_BOLD; 110 | @@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l) 111 | term.c.attr.mode |= ATTR_ITALIC; 112 | break; 113 | case 4: 114 | - term.c.attr.mode |= ATTR_UNDERLINE; 115 | + term.c.attr.ustyle = csiescseq.carg[i][0]; 116 | + 117 | + if (term.c.attr.ustyle != 0) 118 | + term.c.attr.mode |= ATTR_UNDERLINE; 119 | + else 120 | + term.c.attr.mode &= ~ATTR_UNDERLINE; 121 | + 122 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 123 | break; 124 | case 5: /* slow blink */ 125 | /* FALLTHROUGH */ 126 | @@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l) 127 | case 49: 128 | term.c.attr.bg = defaultbg; 129 | break; 130 | + case 58: 131 | + term.c.attr.ucolor[0] = csiescseq.carg[i][1]; 132 | + term.c.attr.ucolor[1] = csiescseq.carg[i][2]; 133 | + term.c.attr.ucolor[2] = csiescseq.carg[i][3]; 134 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 135 | + break; 136 | + case 59: 137 | + term.c.attr.ucolor[0] = -1; 138 | + term.c.attr.ucolor[1] = -1; 139 | + term.c.attr.ucolor[2] = -1; 140 | + term.c.attr.mode ^= ATTR_DIRTYUNDERLINE; 141 | + break; 142 | default: 143 | if (BETWEEN(attr[i], 30, 37)) { 144 | term.c.attr.fg = attr[i] - 30; 145 | diff --git a/st.h b/st.h 146 | index 3d351b6..95bdcbd 100644 147 | --- a/st.h 148 | +++ b/st.h 149 | @@ -34,6 +34,7 @@ enum glyph_attribute { 150 | ATTR_WIDE = 1 << 9, 151 | ATTR_WDUMMY = 1 << 10, 152 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 153 | + ATTR_DIRTYUNDERLINE = 1 << 15, 154 | }; 155 | 156 | enum selection_mode { 157 | @@ -65,6 +66,8 @@ typedef struct { 158 | ushort mode; /* attribute flags */ 159 | uint32_t fg; /* foreground */ 160 | uint32_t bg; /* background */ 161 | + int ustyle; /* underline style */ 162 | + int ucolor[3]; /* underline color */ 163 | } Glyph; 164 | 165 | typedef Glyph *Line; 166 | diff --git a/st.info b/st.info 167 | index 8201ad6..659878c 100644 168 | --- a/st.info 169 | +++ b/st.info 170 | @@ -1,4 +1,5 @@ 171 | st-mono| simpleterm monocolor, 172 | + Su, 173 | acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 174 | am, 175 | bce, 176 | diff --git a/x.c b/x.c 177 | index 210f184..3a0e79e 100644 178 | --- a/x.c 179 | +++ b/x.c 180 | @@ -45,6 +45,14 @@ typedef struct { 181 | signed char appcursor; /* application cursor */ 182 | } Key; 183 | 184 | +/* Undercurl slope types */ 185 | +enum undercurl_slope_type { 186 | + UNDERCURL_SLOPE_ASCENDING = 0, 187 | + UNDERCURL_SLOPE_TOP_CAP = 1, 188 | + UNDERCURL_SLOPE_DESCENDING = 2, 189 | + UNDERCURL_SLOPE_BOTTOM_CAP = 3 190 | +}; 191 | + 192 | /* X modifiers */ 193 | #define XK_ANY_MOD UINT_MAX 194 | #define XK_NO_MOD 0 195 | @@ -1339,6 +1347,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 196 | return numspecs; 197 | } 198 | 199 | +static int isSlopeRising (int x, int iPoint, int waveWidth) 200 | +{ 201 | + // . . . . 202 | + // / \ / \ / \ / \ 203 | + // / \ / \ / \ / \ 204 | + // . . . . . 205 | + 206 | + // Find absolute `x` of point 207 | + x += iPoint * (waveWidth/2); 208 | + 209 | + // Find index of absolute wave 210 | + int absSlope = x / ((float)waveWidth/2); 211 | + 212 | + return (absSlope % 2); 213 | +} 214 | + 215 | +static int getSlope (int x, int iPoint, int waveWidth) 216 | +{ 217 | + // Sizes: Caps are half width of slopes 218 | + // 1_2 1_2 1_2 1_2 219 | + // / \ / \ / \ / \ 220 | + // / \ / \ / \ / \ 221 | + // 0 3_0 3_0 3_0 3_ 222 | + // <2-> <1> <---6----> 223 | + 224 | + // Find type of first point 225 | + int firstType; 226 | + x -= (x / waveWidth) * waveWidth; 227 | + if (x < (waveWidth * (2.f/6.f))) 228 | + firstType = UNDERCURL_SLOPE_ASCENDING; 229 | + else if (x < (waveWidth * (3.f/6.f))) 230 | + firstType = UNDERCURL_SLOPE_TOP_CAP; 231 | + else if (x < (waveWidth * (5.f/6.f))) 232 | + firstType = UNDERCURL_SLOPE_DESCENDING; 233 | + else 234 | + firstType = UNDERCURL_SLOPE_BOTTOM_CAP; 235 | + 236 | + // Find type of given point 237 | + int pointType = (iPoint % 4); 238 | + pointType += firstType; 239 | + pointType %= 4; 240 | + 241 | + return pointType; 242 | +} 243 | + 244 | void 245 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 246 | { 247 | @@ -1461,8 +1514,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 248 | 249 | /* Render underline and strikethrough. */ 250 | if (base.mode & ATTR_UNDERLINE) { 251 | - XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, 252 | - width, 1); 253 | + // Underline Color 254 | + const int widthThreshold = 28; // +1 width every widthThreshold px of font 255 | + int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width 256 | + int linecolor; 257 | + if ((base.ucolor[0] >= 0) && 258 | + !(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) && 259 | + !(base.mode & ATTR_INVISIBLE) 260 | + ) { 261 | + // Special color for underline 262 | + // Index 263 | + if (base.ucolor[1] < 0) { 264 | + linecolor = dc.col[base.ucolor[0]].pixel; 265 | + } 266 | + // RGB 267 | + else { 268 | + XColor lcolor; 269 | + lcolor.red = base.ucolor[0] * 257; 270 | + lcolor.green = base.ucolor[1] * 257; 271 | + lcolor.blue = base.ucolor[2] * 257; 272 | + lcolor.flags = DoRed | DoGreen | DoBlue; 273 | + XAllocColor(xw.dpy, xw.cmap, &lcolor); 274 | + linecolor = lcolor.pixel; 275 | + } 276 | + } else { 277 | + // Foreground color for underline 278 | + linecolor = fg->pixel; 279 | + } 280 | + 281 | + XGCValues ugcv = { 282 | + .foreground = linecolor, 283 | + .line_width = wlw, 284 | + .line_style = LineSolid, 285 | + .cap_style = CapNotLast 286 | + }; 287 | + 288 | + GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), 289 | + GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, 290 | + &ugcv); 291 | + 292 | + // Underline Style 293 | + if (base.ustyle != 3) { 294 | + //XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); 295 | + XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx, 296 | + winy + dc.font.ascent + 1, width, wlw); 297 | + } else if (base.ustyle == 3) { 298 | + int ww = win.cw;//width; 299 | + int wh = dc.font.descent - wlw/2 - 1;//r.height/7; 300 | + int wx = winx; 301 | + int wy = winy + win.ch - dc.font.descent; 302 | + 303 | +#if UNDERCURL_STYLE == UNDERCURL_CURLY 304 | + // Draw waves 305 | + int narcs = charlen * 2 + 1; 306 | + XArc *arcs = xmalloc(sizeof(XArc) * narcs); 307 | + 308 | + int i = 0; 309 | + for (i = 0; i < charlen-1; i++) { 310 | + arcs[i*2] = (XArc) { 311 | + .x = wx + win.cw * i + ww / 4, 312 | + .y = wy, 313 | + .width = win.cw / 2, 314 | + .height = wh, 315 | + .angle1 = 0, 316 | + .angle2 = 180 * 64 317 | + }; 318 | + arcs[i*2+1] = (XArc) { 319 | + .x = wx + win.cw * i + ww * 0.75, 320 | + .y = wy, 321 | + .width = win.cw/2, 322 | + .height = wh, 323 | + .angle1 = 180 * 64, 324 | + .angle2 = 180 * 64 325 | + }; 326 | + } 327 | + // Last wave 328 | + arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh, 329 | + 0, 180 * 64 }; 330 | + // Last wave tail 331 | + arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.), 332 | + wh, 180 * 64, 90 * 64}; 333 | + // First wave tail 334 | + i++; 335 | + arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64, 336 | + 90 * 64 }; 337 | + 338 | + XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs); 339 | + 340 | + free(arcs); 341 | +#elif UNDERCURL_STYLE == UNDERCURL_SPIKY 342 | + // Make the underline corridor larger 343 | + /* 344 | + wy -= wh; 345 | + */ 346 | + wh *= 2; 347 | + 348 | + // Set the angle of the slope to 45° 349 | + ww = wh; 350 | + 351 | + // Position of wave is independent of word, it's absolute 352 | + wx = (wx / (ww/2)) * (ww/2); 353 | + 354 | + int marginStart = winx - wx; 355 | + 356 | + // Calculate number of points with floating precision 357 | + float n = width; // Width of word in pixels 358 | + n = (n / ww) * 2; // Number of slopes (/ or \) 359 | + n += 2; // Add two last points 360 | + int npoints = n; // Convert to int 361 | + 362 | + // Total length of underline 363 | + float waveLength = 0; 364 | + 365 | + if (npoints >= 3) { 366 | + // We add an aditional slot in case we use a bonus point 367 | + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); 368 | + 369 | + // First point (Starts with the word bounds) 370 | + points[0] = (XPoint) { 371 | + .x = wx + marginStart, 372 | + .y = (isSlopeRising(wx, 0, ww)) 373 | + ? (wy - marginStart + ww/2.f) 374 | + : (wy + marginStart) 375 | + }; 376 | + 377 | + // Second point (Goes back to the absolute point coordinates) 378 | + points[1] = (XPoint) { 379 | + .x = (ww/2.f) - marginStart, 380 | + .y = (isSlopeRising(wx, 1, ww)) 381 | + ? (ww/2.f - marginStart) 382 | + : (-ww/2.f + marginStart) 383 | + }; 384 | + waveLength += (ww/2.f) - marginStart; 385 | + 386 | + // The rest of the points 387 | + for (int i = 2; i < npoints-1; i++) { 388 | + points[i] = (XPoint) { 389 | + .x = ww/2, 390 | + .y = (isSlopeRising(wx, i, ww)) 391 | + ? wh/2 392 | + : -wh/2 393 | + }; 394 | + waveLength += ww/2; 395 | + } 396 | + 397 | + // Last point 398 | + points[npoints-1] = (XPoint) { 399 | + .x = ww/2, 400 | + .y = (isSlopeRising(wx, npoints-1, ww)) 401 | + ? wh/2 402 | + : -wh/2 403 | + }; 404 | + waveLength += ww/2; 405 | + 406 | + // End 407 | + if (waveLength < width) { // Add a bonus point? 408 | + int marginEnd = width - waveLength; 409 | + points[npoints] = (XPoint) { 410 | + .x = marginEnd, 411 | + .y = (isSlopeRising(wx, npoints, ww)) 412 | + ? (marginEnd) 413 | + : (-marginEnd) 414 | + }; 415 | + 416 | + npoints++; 417 | + } else if (waveLength > width) { // Is last point too far? 418 | + int marginEnd = waveLength - width; 419 | + points[npoints-1].x -= marginEnd; 420 | + if (isSlopeRising(wx, npoints-1, ww)) 421 | + points[npoints-1].y -= (marginEnd); 422 | + else 423 | + points[npoints-1].y += (marginEnd); 424 | + } 425 | + 426 | + // Draw the lines 427 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, 428 | + CoordModePrevious); 429 | + 430 | + // Draw a second underline with an offset of 1 pixel 431 | + if ( ((win.ch / (widthThreshold/2)) % 2)) { 432 | + points[0].x++; 433 | + 434 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, 435 | + npoints, CoordModePrevious); 436 | + } 437 | + 438 | + // Free resources 439 | + free(points); 440 | + } 441 | +#else // UNDERCURL_CAPPED 442 | + // Cap is half of wave width 443 | + float capRatio = 0.5f; 444 | + 445 | + // Make the underline corridor larger 446 | + wh *= 2; 447 | + 448 | + // Set the angle of the slope to 45° 449 | + ww = wh; 450 | + ww *= 1 + capRatio; // Add a bit of width for the cap 451 | + 452 | + // Position of wave is independent of word, it's absolute 453 | + wx = (wx / ww) * ww; 454 | + 455 | + float marginStart; 456 | + switch(getSlope(winx, 0, ww)) { 457 | + case UNDERCURL_SLOPE_ASCENDING: 458 | + marginStart = winx - wx; 459 | + break; 460 | + case UNDERCURL_SLOPE_TOP_CAP: 461 | + marginStart = winx - (wx + (ww * (2.f/6.f))); 462 | + break; 463 | + case UNDERCURL_SLOPE_DESCENDING: 464 | + marginStart = winx - (wx + (ww * (3.f/6.f))); 465 | + break; 466 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 467 | + marginStart = winx - (wx + (ww * (5.f/6.f))); 468 | + break; 469 | + } 470 | + 471 | + // Calculate number of points with floating precision 472 | + float n = width; // Width of word in pixels 473 | + // ._. 474 | + n = (n / ww) * 4; // Number of points (./ \.) 475 | + n += 2; // Add two last points 476 | + int npoints = n; // Convert to int 477 | + 478 | + // Position of the pen to draw the lines 479 | + float penX = 0; 480 | + float penY = 0; 481 | + 482 | + if (npoints >= 3) { 483 | + XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1)); 484 | + 485 | + // First point (Starts with the word bounds) 486 | + penX = winx; 487 | + switch (getSlope(winx, 0, ww)) { 488 | + case UNDERCURL_SLOPE_ASCENDING: 489 | + penY = wy + wh/2.f - marginStart; 490 | + break; 491 | + case UNDERCURL_SLOPE_TOP_CAP: 492 | + penY = wy; 493 | + break; 494 | + case UNDERCURL_SLOPE_DESCENDING: 495 | + penY = wy + marginStart; 496 | + break; 497 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 498 | + penY = wy + wh/2.f; 499 | + break; 500 | + } 501 | + points[0].x = penX; 502 | + points[0].y = penY; 503 | + 504 | + // Second point (Goes back to the absolute point coordinates) 505 | + switch (getSlope(winx, 1, ww)) { 506 | + case UNDERCURL_SLOPE_ASCENDING: 507 | + penX += ww * (1.f/6.f) - marginStart; 508 | + penY += 0; 509 | + break; 510 | + case UNDERCURL_SLOPE_TOP_CAP: 511 | + penX += ww * (2.f/6.f) - marginStart; 512 | + penY += -wh/2.f + marginStart; 513 | + break; 514 | + case UNDERCURL_SLOPE_DESCENDING: 515 | + penX += ww * (1.f/6.f) - marginStart; 516 | + penY += 0; 517 | + break; 518 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 519 | + penX += ww * (2.f/6.f) - marginStart; 520 | + penY += -marginStart + wh/2.f; 521 | + break; 522 | + } 523 | + points[1].x = penX; 524 | + points[1].y = penY; 525 | + 526 | + // The rest of the points 527 | + for (int i = 2; i < npoints; i++) { 528 | + switch (getSlope(winx, i, ww)) { 529 | + case UNDERCURL_SLOPE_ASCENDING: 530 | + case UNDERCURL_SLOPE_DESCENDING: 531 | + penX += ww * (1.f/6.f); 532 | + penY += 0; 533 | + break; 534 | + case UNDERCURL_SLOPE_TOP_CAP: 535 | + penX += ww * (2.f/6.f); 536 | + penY += -wh / 2.f; 537 | + break; 538 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 539 | + penX += ww * (2.f/6.f); 540 | + penY += wh / 2.f; 541 | + break; 542 | + } 543 | + points[i].x = penX; 544 | + points[i].y = penY; 545 | + } 546 | + 547 | + // End 548 | + float waveLength = penX - winx; 549 | + if (waveLength < width) { // Add a bonus point? 550 | + int marginEnd = width - waveLength; 551 | + penX += marginEnd; 552 | + switch(getSlope(winx, npoints, ww)) { 553 | + case UNDERCURL_SLOPE_ASCENDING: 554 | + case UNDERCURL_SLOPE_DESCENDING: 555 | + //penY += 0; 556 | + break; 557 | + case UNDERCURL_SLOPE_TOP_CAP: 558 | + penY += -marginEnd; 559 | + break; 560 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 561 | + penY += marginEnd; 562 | + break; 563 | + } 564 | + 565 | + points[npoints].x = penX; 566 | + points[npoints].y = penY; 567 | + 568 | + npoints++; 569 | + } else if (waveLength > width) { // Is last point too far? 570 | + int marginEnd = waveLength - width; 571 | + points[npoints-1].x -= marginEnd; 572 | + switch(getSlope(winx, npoints-1, ww)) { 573 | + case UNDERCURL_SLOPE_TOP_CAP: 574 | + points[npoints-1].y += marginEnd; 575 | + break; 576 | + case UNDERCURL_SLOPE_BOTTOM_CAP: 577 | + points[npoints-1].y -= marginEnd; 578 | + break; 579 | + default: 580 | + break; 581 | + } 582 | + } 583 | + 584 | + // Draw the lines 585 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints, 586 | + CoordModeOrigin); 587 | + 588 | + // Draw a second underline with an offset of 1 pixel 589 | + if ( ((win.ch / (widthThreshold/2)) % 2)) { 590 | + for (int i = 0; i < npoints; i++) 591 | + points[i].x++; 592 | + 593 | + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, 594 | + npoints, CoordModeOrigin); 595 | + } 596 | + 597 | + // Free resources 598 | + free(points); 599 | + } 600 | +#endif 601 | + } 602 | + 603 | + XFreeGC(xw.dpy, ugc); 604 | } 605 | 606 | if (base.mode & ATTR_STRUCK) { 607 | --------------------------------------------------------------------------------