undercurl

This commit is contained in:
Luca Bilke 2023-06-13 21:57:23 +02:00
parent 1865c95a26
commit 27af760f77
6 changed files with 1087 additions and 4 deletions

View File

@ -484,3 +484,27 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
/**
* Undercurl style. Set UNDERCURL_STYLE to one of the available styles.
*
* Curly: Dunno how to draw it *shrug*
* _ _ _ _
* ( ) ( ) ( ) ( )
* (_) (_) (_) (_)
*
* Spiky:
* /\ /\ /\ /\
* \/ \/ \/
*
* Capped:
* _ _ _
* / \ / \ / \
* \_/ \_/
*/
// Available styles
#define UNDERCURL_CURLY 0
#define UNDERCURL_SPIKY 1
#define UNDERCURL_CAPPED 2
// Active style
#define UNDERCURL_STYLE UNDERCURL_SPIKY

View File

@ -0,0 +1,605 @@
diff --git a/config.def.h b/config.def.h
index 6f05dce..7ae1b92 100644
--- a/config.def.h
+++ b/config.def.h
@@ -470,3 +470,27 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/**
+ * Undercurl style. Set UNDERCURL_STYLE to one of the available styles.
+ *
+ * Curly: Dunno how to draw it *shrug*
+ * _ _ _ _
+ * ( ) ( ) ( ) ( )
+ * (_) (_) (_) (_)
+ *
+ * Spiky:
+ * /\ /\ /\ /\
+ * \/ \/ \/
+ *
+ * Capped:
+ * _ _ _
+ * / \ / \ / \
+ * \_/ \_/
+ */
+// Available styles
+#define UNDERCURL_CURLY 0
+#define UNDERCURL_SPIKY 1
+#define UNDERCURL_CAPPED 2
+// Active style
+#define UNDERCURL_STYLE UNDERCURL_SPIKY
diff --git a/st.c b/st.c
index 76b7e0d..542ab3a 100644
--- a/st.c
+++ b/st.c
@@ -33,6 +33,7 @@
#define UTF_SIZ 4
#define ESC_BUF_SIZ (128*UTF_SIZ)
#define ESC_ARG_SIZ 16
+#define CAR_PER_ARG 4
#define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_SIZ
@@ -139,6 +140,7 @@ typedef struct {
int arg[ESC_ARG_SIZ];
int narg; /* nb of args */
char mode[2];
+ int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
} CSIEscape;
/* STR Escape sequence structs */
@@ -159,6 +161,7 @@ static void ttywriteraw(const char *, size_t);
static void csidump(void);
static void csihandle(void);
+static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
static void csiparse(void);
static void csireset(void);
static int eschandle(uchar);
@@ -1131,6 +1134,28 @@ tnewline(int first_col)
tmoveto(first_col ? 0 : term.c.x, y);
}
+void
+readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
+{
+ int i = 0;
+ for (; i < CAR_PER_ARG; i++)
+ params[cursor][i] = -1;
+
+ if (**p != ':')
+ return;
+
+ char *np = NULL;
+ i = 0;
+
+ while (**p == ':' && i < CAR_PER_ARG) {
+ while (**p == ':')
+ (*p)++;
+ params[cursor][i] = strtol(*p, &np, 10);
+ *p = np;
+ i++;
+ }
+}
+
void
csiparse(void)
{
@@ -1153,6 +1178,7 @@ csiparse(void)
v = -1;
csiescseq.arg[csiescseq.narg++] = v;
p = np;
+ readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
break;
p++;
@@ -1369,6 +1395,10 @@ tsetattr(int *attr, int l)
ATTR_STRUCK );
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
+ term.c.attr.ustyle = -1;
+ term.c.attr.ucolor[0] = -1;
+ term.c.attr.ucolor[1] = -1;
+ term.c.attr.ucolor[2] = -1;
break;
case 1:
term.c.attr.mode |= ATTR_BOLD;
@@ -1380,7 +1410,14 @@ tsetattr(int *attr, int l)
term.c.attr.mode |= ATTR_ITALIC;
break;
case 4:
- term.c.attr.mode |= ATTR_UNDERLINE;
+ term.c.attr.ustyle = csiescseq.carg[i][0];
+
+ if (term.c.attr.ustyle != 0)
+ term.c.attr.mode |= ATTR_UNDERLINE;
+ else
+ term.c.attr.mode &= ~ATTR_UNDERLINE;
+
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
case 5: /* slow blink */
/* FALLTHROUGH */
@@ -1431,6 +1468,18 @@ tsetattr(int *attr, int l)
case 49:
term.c.attr.bg = defaultbg;
break;
+ case 58:
+ term.c.attr.ucolor[0] = csiescseq.carg[i][1];
+ term.c.attr.ucolor[1] = csiescseq.carg[i][2];
+ term.c.attr.ucolor[2] = csiescseq.carg[i][3];
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
+ break;
+ case 59:
+ term.c.attr.ucolor[0] = -1;
+ term.c.attr.ucolor[1] = -1;
+ term.c.attr.ucolor[2] = -1;
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
+ break;
default:
if (BETWEEN(attr[i], 30, 37)) {
term.c.attr.fg = attr[i] - 30;
diff --git a/st.h b/st.h
index 3d351b6..95bdcbd 100644
--- a/st.h
+++ b/st.h
@@ -34,6 +34,7 @@ enum glyph_attribute {
ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+ ATTR_DIRTYUNDERLINE = 1 << 15,
};
enum selection_mode {
@@ -65,6 +66,8 @@ typedef struct {
ushort mode; /* attribute flags */
uint32_t fg; /* foreground */
uint32_t bg; /* background */
+ int ustyle; /* underline style */
+ int ucolor[3]; /* underline color */
} Glyph;
typedef Glyph *Line;
diff --git a/st.info b/st.info
index 8201ad6..659878c 100644
--- a/st.info
+++ b/st.info
@@ -1,4 +1,5 @@
st-mono| simpleterm monocolor,
+ Su,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
am,
bce,
diff --git a/x.c b/x.c
index 210f184..3a0e79e 100644
--- a/x.c
+++ b/x.c
@@ -45,6 +45,14 @@ typedef struct {
signed char appcursor; /* application cursor */
} Key;
+/* Undercurl slope types */
+enum undercurl_slope_type {
+ UNDERCURL_SLOPE_ASCENDING = 0,
+ UNDERCURL_SLOPE_TOP_CAP = 1,
+ UNDERCURL_SLOPE_DESCENDING = 2,
+ UNDERCURL_SLOPE_BOTTOM_CAP = 3
+};
+
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
@@ -1339,6 +1347,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
return numspecs;
}
+static int isSlopeRising (int x, int iPoint, int waveWidth)
+{
+ // . . . .
+ // / \ / \ / \ / \
+ // / \ / \ / \ / \
+ // . . . . .
+
+ // Find absolute `x` of point
+ x += iPoint * (waveWidth/2);
+
+ // Find index of absolute wave
+ int absSlope = x / ((float)waveWidth/2);
+
+ return (absSlope % 2);
+}
+
+static int getSlope (int x, int iPoint, int waveWidth)
+{
+ // Sizes: Caps are half width of slopes
+ // 1_2 1_2 1_2 1_2
+ // / \ / \ / \ / \
+ // / \ / \ / \ / \
+ // 0 3_0 3_0 3_0 3_
+ // <2-> <1> <---6---->
+
+ // Find type of first point
+ int firstType;
+ x -= (x / waveWidth) * waveWidth;
+ if (x < (waveWidth * (2.f/6.f)))
+ firstType = UNDERCURL_SLOPE_ASCENDING;
+ else if (x < (waveWidth * (3.f/6.f)))
+ firstType = UNDERCURL_SLOPE_TOP_CAP;
+ else if (x < (waveWidth * (5.f/6.f)))
+ firstType = UNDERCURL_SLOPE_DESCENDING;
+ else
+ firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
+
+ // Find type of given point
+ int pointType = (iPoint % 4);
+ pointType += firstType;
+ pointType %= 4;
+
+ return pointType;
+}
+
void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{
@@ -1461,8 +1514,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
- width, 1);
+ // Underline Color
+ const int widthThreshold = 28; // +1 width every widthThreshold px of font
+ int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
+ int linecolor;
+ if ((base.ucolor[0] >= 0) &&
+ !(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
+ !(base.mode & ATTR_INVISIBLE)
+ ) {
+ // Special color for underline
+ // Index
+ if (base.ucolor[1] < 0) {
+ linecolor = dc.col[base.ucolor[0]].pixel;
+ }
+ // RGB
+ else {
+ XColor lcolor;
+ lcolor.red = base.ucolor[0] * 257;
+ lcolor.green = base.ucolor[1] * 257;
+ lcolor.blue = base.ucolor[2] * 257;
+ lcolor.flags = DoRed | DoGreen | DoBlue;
+ XAllocColor(xw.dpy, xw.cmap, &lcolor);
+ linecolor = lcolor.pixel;
+ }
+ } else {
+ // Foreground color for underline
+ linecolor = fg->pixel;
+ }
+
+ XGCValues ugcv = {
+ .foreground = linecolor,
+ .line_width = wlw,
+ .line_style = LineSolid,
+ .cap_style = CapNotLast
+ };
+
+ GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
+ GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
+ &ugcv);
+
+ // Underline Style
+ if (base.ustyle != 3) {
+ //XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
+ XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
+ winy + dc.font.ascent + 1, width, wlw);
+ } else if (base.ustyle == 3) {
+ int ww = win.cw;//width;
+ int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
+ int wx = winx;
+ int wy = winy + win.ch - dc.font.descent;
+
+#if UNDERCURL_STYLE == UNDERCURL_CURLY
+ // Draw waves
+ int narcs = charlen * 2 + 1;
+ XArc *arcs = xmalloc(sizeof(XArc) * narcs);
+
+ int i = 0;
+ for (i = 0; i < charlen-1; i++) {
+ arcs[i*2] = (XArc) {
+ .x = wx + win.cw * i + ww / 4,
+ .y = wy,
+ .width = win.cw / 2,
+ .height = wh,
+ .angle1 = 0,
+ .angle2 = 180 * 64
+ };
+ arcs[i*2+1] = (XArc) {
+ .x = wx + win.cw * i + ww * 0.75,
+ .y = wy,
+ .width = win.cw/2,
+ .height = wh,
+ .angle1 = 180 * 64,
+ .angle2 = 180 * 64
+ };
+ }
+ // Last wave
+ arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
+ 0, 180 * 64 };
+ // Last wave tail
+ arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
+ wh, 180 * 64, 90 * 64};
+ // First wave tail
+ i++;
+ arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
+ 90 * 64 };
+
+ XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
+
+ free(arcs);
+#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
+ // Make the underline corridor larger
+ /*
+ wy -= wh;
+ */
+ wh *= 2;
+
+ // Set the angle of the slope to 45°
+ ww = wh;
+
+ // Position of wave is independent of word, it's absolute
+ wx = (wx / (ww/2)) * (ww/2);
+
+ int marginStart = winx - wx;
+
+ // Calculate number of points with floating precision
+ float n = width; // Width of word in pixels
+ n = (n / ww) * 2; // Number of slopes (/ or \)
+ n += 2; // Add two last points
+ int npoints = n; // Convert to int
+
+ // Total length of underline
+ float waveLength = 0;
+
+ if (npoints >= 3) {
+ // We add an aditional slot in case we use a bonus point
+ XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
+
+ // First point (Starts with the word bounds)
+ points[0] = (XPoint) {
+ .x = wx + marginStart,
+ .y = (isSlopeRising(wx, 0, ww))
+ ? (wy - marginStart + ww/2.f)
+ : (wy + marginStart)
+ };
+
+ // Second point (Goes back to the absolute point coordinates)
+ points[1] = (XPoint) {
+ .x = (ww/2.f) - marginStart,
+ .y = (isSlopeRising(wx, 1, ww))
+ ? (ww/2.f - marginStart)
+ : (-ww/2.f + marginStart)
+ };
+ waveLength += (ww/2.f) - marginStart;
+
+ // The rest of the points
+ for (int i = 2; i < npoints-1; i++) {
+ points[i] = (XPoint) {
+ .x = ww/2,
+ .y = (isSlopeRising(wx, i, ww))
+ ? wh/2
+ : -wh/2
+ };
+ waveLength += ww/2;
+ }
+
+ // Last point
+ points[npoints-1] = (XPoint) {
+ .x = ww/2,
+ .y = (isSlopeRising(wx, npoints-1, ww))
+ ? wh/2
+ : -wh/2
+ };
+ waveLength += ww/2;
+
+ // End
+ if (waveLength < width) { // Add a bonus point?
+ int marginEnd = width - waveLength;
+ points[npoints] = (XPoint) {
+ .x = marginEnd,
+ .y = (isSlopeRising(wx, npoints, ww))
+ ? (marginEnd)
+ : (-marginEnd)
+ };
+
+ npoints++;
+ } else if (waveLength > width) { // Is last point too far?
+ int marginEnd = waveLength - width;
+ points[npoints-1].x -= marginEnd;
+ if (isSlopeRising(wx, npoints-1, ww))
+ points[npoints-1].y -= (marginEnd);
+ else
+ points[npoints-1].y += (marginEnd);
+ }
+
+ // Draw the lines
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
+ CoordModePrevious);
+
+ // Draw a second underline with an offset of 1 pixel
+ if ( ((win.ch / (widthThreshold/2)) % 2)) {
+ points[0].x++;
+
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
+ npoints, CoordModePrevious);
+ }
+
+ // Free resources
+ free(points);
+ }
+#else // UNDERCURL_CAPPED
+ // Cap is half of wave width
+ float capRatio = 0.5f;
+
+ // Make the underline corridor larger
+ wh *= 2;
+
+ // Set the angle of the slope to 45°
+ ww = wh;
+ ww *= 1 + capRatio; // Add a bit of width for the cap
+
+ // Position of wave is independent of word, it's absolute
+ wx = (wx / ww) * ww;
+
+ float marginStart;
+ switch(getSlope(winx, 0, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ marginStart = winx - wx;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ marginStart = winx - (wx + (ww * (2.f/6.f)));
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ marginStart = winx - (wx + (ww * (3.f/6.f)));
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ marginStart = winx - (wx + (ww * (5.f/6.f)));
+ break;
+ }
+
+ // Calculate number of points with floating precision
+ float n = width; // Width of word in pixels
+ // ._.
+ n = (n / ww) * 4; // Number of points (./ \.)
+ n += 2; // Add two last points
+ int npoints = n; // Convert to int
+
+ // Position of the pen to draw the lines
+ float penX = 0;
+ float penY = 0;
+
+ if (npoints >= 3) {
+ XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
+
+ // First point (Starts with the word bounds)
+ penX = winx;
+ switch (getSlope(winx, 0, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ penY = wy + wh/2.f - marginStart;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penY = wy;
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ penY = wy + marginStart;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penY = wy + wh/2.f;
+ break;
+ }
+ points[0].x = penX;
+ points[0].y = penY;
+
+ // Second point (Goes back to the absolute point coordinates)
+ switch (getSlope(winx, 1, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ penX += ww * (1.f/6.f) - marginStart;
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penX += ww * (2.f/6.f) - marginStart;
+ penY += -wh/2.f + marginStart;
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ penX += ww * (1.f/6.f) - marginStart;
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penX += ww * (2.f/6.f) - marginStart;
+ penY += -marginStart + wh/2.f;
+ break;
+ }
+ points[1].x = penX;
+ points[1].y = penY;
+
+ // The rest of the points
+ for (int i = 2; i < npoints; i++) {
+ switch (getSlope(winx, i, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ case UNDERCURL_SLOPE_DESCENDING:
+ penX += ww * (1.f/6.f);
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penX += ww * (2.f/6.f);
+ penY += -wh / 2.f;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penX += ww * (2.f/6.f);
+ penY += wh / 2.f;
+ break;
+ }
+ points[i].x = penX;
+ points[i].y = penY;
+ }
+
+ // End
+ float waveLength = penX - winx;
+ if (waveLength < width) { // Add a bonus point?
+ int marginEnd = width - waveLength;
+ penX += marginEnd;
+ switch(getSlope(winx, npoints, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ case UNDERCURL_SLOPE_DESCENDING:
+ //penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penY += -marginEnd;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penY += marginEnd;
+ break;
+ }
+
+ points[npoints].x = penX;
+ points[npoints].y = penY;
+
+ npoints++;
+ } else if (waveLength > width) { // Is last point too far?
+ int marginEnd = waveLength - width;
+ points[npoints-1].x -= marginEnd;
+ switch(getSlope(winx, npoints-1, ww)) {
+ case UNDERCURL_SLOPE_TOP_CAP:
+ points[npoints-1].y += marginEnd;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ points[npoints-1].y -= marginEnd;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Draw the lines
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
+ CoordModeOrigin);
+
+ // Draw a second underline with an offset of 1 pixel
+ if ( ((win.ch / (widthThreshold/2)) % 2)) {
+ for (int i = 0; i < npoints; i++)
+ points[i].x++;
+
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
+ npoints, CoordModeOrigin);
+ }
+
+ // Free resources
+ free(points);
+ }
+#endif
+ }
+
+ XFreeGC(xw.dpy, ugc);
}
if (base.mode & ATTR_STRUCK) {

51
st.c
View File

@ -33,6 +33,7 @@
#define UTF_SIZ 4
#define ESC_BUF_SIZ (128*UTF_SIZ)
#define ESC_ARG_SIZ 16
#define CAR_PER_ARG 4
#define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_SIZ
@ -139,6 +140,7 @@ typedef struct {
int arg[ESC_ARG_SIZ];
int narg; /* nb of args */
char mode[2];
int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
} CSIEscape;
/* STR Escape sequence structs */
@ -159,6 +161,7 @@ static void ttywriteraw(const char *, size_t);
static void csidump(void);
static void csihandle(void);
static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
static void csiparse(void);
static void csireset(void);
static void osc_color_response(int, int, int);
@ -1127,6 +1130,28 @@ tnewline(int first_col)
tmoveto(first_col ? 0 : term.c.x, y);
}
void
readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
{
int i = 0;
for (; i < CAR_PER_ARG; i++)
params[cursor][i] = -1;
if (**p != ':')
return;
char *np = NULL;
i = 0;
while (**p == ':' && i < CAR_PER_ARG) {
while (**p == ':')
(*p)++;
params[cursor][i] = strtol(*p, &np, 10);
*p = np;
i++;
}
}
void
csiparse(void)
{
@ -1149,6 +1174,7 @@ csiparse(void)
v = -1;
csiescseq.arg[csiescseq.narg++] = v;
p = np;
readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
break;
p++;
@ -1368,6 +1394,10 @@ tsetattr(const int *attr, int l)
ATTR_STRUCK );
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
term.c.attr.ustyle = -1;
term.c.attr.ucolor[0] = -1;
term.c.attr.ucolor[1] = -1;
term.c.attr.ucolor[2] = -1;
break;
case 1:
term.c.attr.mode |= ATTR_BOLD;
@ -1379,7 +1409,14 @@ tsetattr(const int *attr, int l)
term.c.attr.mode |= ATTR_ITALIC;
break;
case 4:
term.c.attr.mode |= ATTR_UNDERLINE;
term.c.attr.ustyle = csiescseq.carg[i][0];
if (term.c.attr.ustyle != 0)
term.c.attr.mode |= ATTR_UNDERLINE;
else
term.c.attr.mode &= ~ATTR_UNDERLINE;
term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
case 5: /* slow blink */
/* FALLTHROUGH */
@ -1430,6 +1467,18 @@ tsetattr(const int *attr, int l)
case 49:
term.c.attr.bg = defaultbg;
break;
case 58:
term.c.attr.ucolor[0] = csiescseq.carg[i][1];
term.c.attr.ucolor[1] = csiescseq.carg[i][2];
term.c.attr.ucolor[2] = csiescseq.carg[i][3];
term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
case 59:
term.c.attr.ucolor[0] = -1;
term.c.attr.ucolor[1] = -1;
term.c.attr.ucolor[2] = -1;
term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
default:
if (BETWEEN(attr[i], 30, 37)) {
term.c.attr.fg = attr[i] - 30;

3
st.h
View File

@ -36,6 +36,7 @@ enum glyph_attribute {
ATTR_WDUMMY = 1 << 10,
ATTR_BOXDRAW = 1 << 11,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
ATTR_DIRTYUNDERLINE = 1 << 15,
};
enum selection_mode {
@ -67,6 +68,8 @@ typedef struct {
ushort mode; /* attribute flags */
uint32_t fg; /* foreground */
uint32_t bg; /* background */
int ustyle; /* underline style */
int ucolor[3]; /* underline color */
} Glyph;
typedef Glyph *Line;

View File

@ -1,4 +1,5 @@
st-mono| simpleterm monocolor,
Su,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
am,
bce,

407
x.c
View File

@ -46,6 +46,14 @@ typedef struct {
signed char appcursor; /* application cursor */
} Key;
/* Undercurl slope types */
enum undercurl_slope_type {
UNDERCURL_SLOPE_ASCENDING = 0,
UNDERCURL_SLOPE_TOP_CAP = 1,
UNDERCURL_SLOPE_DESCENDING = 2,
UNDERCURL_SLOPE_BOTTOM_CAP = 3
};
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
@ -1431,6 +1439,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
return numspecs;
}
static int isSlopeRising (int x, int iPoint, int waveWidth)
{
// . . . .
// / \ / \ / \ / \
// / \ / \ / \ / \
// . . . . .
// Find absolute `x` of point
x += iPoint * (waveWidth/2);
// Find index of absolute wave
int absSlope = x / ((float)waveWidth/2);
return (absSlope % 2);
}
static int getSlope (int x, int iPoint, int waveWidth)
{
// Sizes: Caps are half width of slopes
// 1_2 1_2 1_2 1_2
// / \ / \ / \ / \
// / \ / \ / \ / \
// 0 3_0 3_0 3_0 3_
// <2-> <1> <---6---->
// Find type of first point
int firstType;
x -= (x / waveWidth) * waveWidth;
if (x < (waveWidth * (2.f/6.f)))
firstType = UNDERCURL_SLOPE_ASCENDING;
else if (x < (waveWidth * (3.f/6.f)))
firstType = UNDERCURL_SLOPE_TOP_CAP;
else if (x < (waveWidth * (5.f/6.f)))
firstType = UNDERCURL_SLOPE_DESCENDING;
else
firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
// Find type of given point
int pointType = (iPoint % 4);
pointType += firstType;
pointType %= 4;
return pointType;
}
void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{
@ -1557,9 +1610,357 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1);
}
// Underline Color
const int widthThreshold = 28; // +1 width every widthThreshold px of font
int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
int linecolor;
if ((base.ucolor[0] >= 0) &&
!(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
!(base.mode & ATTR_INVISIBLE)
) {
// Special color for underline
// Index
if (base.ucolor[1] < 0) {
linecolor = dc.col[base.ucolor[0]].pixel;
}
// RGB
else {
XColor lcolor;
lcolor.red = base.ucolor[0] * 257;
lcolor.green = base.ucolor[1] * 257;
lcolor.blue = base.ucolor[2] * 257;
lcolor.flags = DoRed | DoGreen | DoBlue;
XAllocColor(xw.dpy, xw.cmap, &lcolor);
linecolor = lcolor.pixel;
}
} else {
// Foreground color for underline
linecolor = fg->pixel;
}
XGCValues ugcv = {
.foreground = linecolor,
.line_width = wlw,
.line_style = LineSolid,
.cap_style = CapNotLast
};
GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
&ugcv);
// Underline Style
if (base.ustyle != 3) {
//XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, width, 1);
XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
winy + dc.font.ascent * chscale + 1, width, wlw);
} else if (base.ustyle == 3) {
int ww = win.cw;//width;
int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
int wx = winx;
int wy = winy + win.ch - dc.font.descent;
#if UNDERCURL_STYLE == UNDERCURL_CURLY
// Draw waves
int narcs = charlen * 2 + 1;
XArc *arcs = xmalloc(sizeof(XArc) * narcs);
int i = 0;
for (i = 0; i < charlen-1; i++) {
arcs[i*2] = (XArc) {
.x = wx + win.cw * i + ww / 4,
.y = wy,
.width = win.cw / 2,
.height = wh,
.angle1 = 0,
.angle2 = 180 * 64
};
arcs[i*2+1] = (XArc) {
.x = wx + win.cw * i + ww * 0.75,
.y = wy,
.width = win.cw/2,
.height = wh,
.angle1 = 180 * 64,
.angle2 = 180 * 64
};
}
// Last wave
arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
0, 180 * 64 };
// Last wave tail
arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
wh, 180 * 64, 90 * 64};
// First wave tail
i++;
arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
90 * 64 };
XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
free(arcs);
#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
// Make the underline corridor larger
/*
wy -= wh;
*/
wh *= 2;
// Set the angle of the slope to 45°
ww = wh;
// Position of wave is independent of word, it's absolute
wx = (wx / (ww/2)) * (ww/2);
int marginStart = winx - wx;
// Calculate number of points with floating precision
float n = width; // Width of word in pixels
n = (n / ww) * 2; // Number of slopes (/ or \)
n += 2; // Add two last points
int npoints = n; // Convert to int
// Total length of underline
float waveLength = 0;
if (npoints >= 3) {
// We add an aditional slot in case we use a bonus point
XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
// First point (Starts with the word bounds)
points[0] = (XPoint) {
.x = wx + marginStart,
.y = (isSlopeRising(wx, 0, ww))
? (wy - marginStart + ww/2.f)
: (wy + marginStart)
};
// Second point (Goes back to the absolute point coordinates)
points[1] = (XPoint) {
.x = (ww/2.f) - marginStart,
.y = (isSlopeRising(wx, 1, ww))
? (ww/2.f - marginStart)
: (-ww/2.f + marginStart)
};
waveLength += (ww/2.f) - marginStart;
// The rest of the points
for (int i = 2; i < npoints-1; i++) {
points[i] = (XPoint) {
.x = ww/2,
.y = (isSlopeRising(wx, i, ww))
? wh/2
: -wh/2
};
waveLength += ww/2;
}
// Last point
points[npoints-1] = (XPoint) {
.x = ww/2,
.y = (isSlopeRising(wx, npoints-1, ww))
? wh/2
: -wh/2
};
waveLength += ww/2;
// End
if (waveLength < width) { // Add a bonus point?
int marginEnd = width - waveLength;
points[npoints] = (XPoint) {
.x = marginEnd,
.y = (isSlopeRising(wx, npoints, ww))
? (marginEnd)
: (-marginEnd)
};
npoints++;
} else if (waveLength > width) { // Is last point too far?
int marginEnd = waveLength - width;
points[npoints-1].x -= marginEnd;
if (isSlopeRising(wx, npoints-1, ww))
points[npoints-1].y -= (marginEnd);
else
points[npoints-1].y += (marginEnd);
}
// Draw the lines
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
CoordModePrevious);
// Draw a second underline with an offset of 1 pixel
if ( ((win.ch / (widthThreshold/2)) % 2)) {
points[0].x++;
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
npoints, CoordModePrevious);
}
// Free resources
free(points);
}
#else // UNDERCURL_CAPPED
// Cap is half of wave width
float capRatio = 0.5f;
// Make the underline corridor larger
wh *= 2;
// Set the angle of the slope to 45°
ww = wh;
ww *= 1 + capRatio; // Add a bit of width for the cap
// Position of wave is independent of word, it's absolute
wx = (wx / ww) * ww;
float marginStart;
switch(getSlope(winx, 0, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
marginStart = winx - wx;
break;
case UNDERCURL_SLOPE_TOP_CAP:
marginStart = winx - (wx + (ww * (2.f/6.f)));
break;
case UNDERCURL_SLOPE_DESCENDING:
marginStart = winx - (wx + (ww * (3.f/6.f)));
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
marginStart = winx - (wx + (ww * (5.f/6.f)));
break;
}
// Calculate number of points with floating precision
float n = width; // Width of word in pixels
// ._.
n = (n / ww) * 4; // Number of points (./ \.)
n += 2; // Add two last points
int npoints = n; // Convert to int
// Position of the pen to draw the lines
float penX = 0;
float penY = 0;
if (npoints >= 3) {
XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
// First point (Starts with the word bounds)
penX = winx;
switch (getSlope(winx, 0, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
penY = wy + wh/2.f - marginStart;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penY = wy;
break;
case UNDERCURL_SLOPE_DESCENDING:
penY = wy + marginStart;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penY = wy + wh/2.f;
break;
}
points[0].x = penX;
points[0].y = penY;
// Second point (Goes back to the absolute point coordinates)
switch (getSlope(winx, 1, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
penX += ww * (1.f/6.f) - marginStart;
penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penX += ww * (2.f/6.f) - marginStart;
penY += -wh/2.f + marginStart;
break;
case UNDERCURL_SLOPE_DESCENDING:
penX += ww * (1.f/6.f) - marginStart;
penY += 0;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penX += ww * (2.f/6.f) - marginStart;
penY += -marginStart + wh/2.f;
break;
}
points[1].x = penX;
points[1].y = penY;
// The rest of the points
for (int i = 2; i < npoints; i++) {
switch (getSlope(winx, i, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
case UNDERCURL_SLOPE_DESCENDING:
penX += ww * (1.f/6.f);
penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penX += ww * (2.f/6.f);
penY += -wh / 2.f;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penX += ww * (2.f/6.f);
penY += wh / 2.f;
break;
}
points[i].x = penX;
points[i].y = penY;
}
// End
float waveLength = penX - winx;
if (waveLength < width) { // Add a bonus point?
int marginEnd = width - waveLength;
penX += marginEnd;
switch(getSlope(winx, npoints, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
case UNDERCURL_SLOPE_DESCENDING:
//penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penY += -marginEnd;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penY += marginEnd;
break;
}
points[npoints].x = penX;
points[npoints].y = penY;
npoints++;
} else if (waveLength > width) { // Is last point too far?
int marginEnd = waveLength - width;
points[npoints-1].x -= marginEnd;
switch(getSlope(winx, npoints-1, ww)) {
case UNDERCURL_SLOPE_TOP_CAP:
points[npoints-1].y += marginEnd;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
points[npoints-1].y -= marginEnd;
break;
default:
break;
}
}
// Draw the lines
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
CoordModeOrigin);
// Draw a second underline with an offset of 1 pixel
if ( ((win.ch / (widthThreshold/2)) % 2)) {
for (int i = 0; i < npoints; i++)
points[i].x++;
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
npoints, CoordModeOrigin);
}
// Free resources
free(points);
}
#endif
}
XFreeGC(xw.dpy, ugc);
if (base.mode & ATTR_STRUCK) {
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,