st-undercurl-0.8.4-20210822.diff (16126B)
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) {