├── 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 |
4 | A patch for ST (Simple Terminal) adding support for curly and colored underlines.
5 |
6 | Screenshots |
7 | Installation
8 | Style
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | #
21 |
22 | ## Screenshots
23 |
24 | 
25 | 
26 | 
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 | 
60 |
61 | Spiky:
62 | 
63 |
64 | Capped:
65 | 
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 |
--------------------------------------------------------------------------------