st-undercurl-0.9-20240103.diff (16184B)
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) {