[hackers] [st][PATCH] add diacritics support to xdraws()

From: Valentina Demiciseaux <vallyyyyy_AT_proton.me>
Date: Mon, 05 May 2025 20:00:53 +0000

Here diacritics will now combine, but not stack (no terminal does proper
stacking). The diacritic was added to the glyph struct so that it won't
occupy a seperate column (matching its presentation).

specbuf is doubled in size to handle 2 specs per glyph (base char, and its
combiner, if any).

REP behavior is consistant with xterm when there was a diacritic part of
the last character, which is that only base is repeated.
---
There is for sure a better way of dealing with the 'badcombining' thing, but I'm
not very familiar with X(ft). Anyone here with more insight? To repro:
 - Delete the badcombining test (x.c:977)
 - Compile st with "Liberation Mono" font, notice diacritics are off by 1 along x
(it's trying to back-up for us?)
 - Compile st with "SourceCodePro" font, notice diacritics are correct
Please let me know what yall think!
Have a test string (notice 'o\xcc\x81' (U+006F, U+0301) is the decomposed form
of 'รณ' (U+00F3)):
    printf 'Bo\xcc\x81br k*rwa ja p*erdole jakie bydle\xcc\xa8! w\xcc\x82a\xe2\x83\x97?\numlaut replacing: o\xcc\x83\xcc\x88!\numlaut replacing the previous line: o\xcc\x83  \n\xcc\x88'
Regards,
Vally.
 TODO |  3 +--
 st.c | 47 +++++++++++++++++++++++++++++++------
 st.h |  1 +
 x.c  | 75 +++++++++++++++++++++++++++++++++++++++++++++---------------
 4 files changed, 99 insertions(+), 27 deletions(-)
diff --git a/TODO b/TODO
index 5f74cd5..c2a528c 100644
--- a/TODO
+++ b/TODO
_AT_@ -10,8 +10,7 @@ code & interface
 
 drawing
 -------
-* add diacritics support to xdraws()
-	* switch to a suckless font drawing library
+* switch to a suckless font drawing library
 * make the font cache simpler
 * add better support for brightening of the upper colors
 
diff --git a/st.c b/st.c
index 03b9bc8..c82a990 100644
--- a/st.c
+++ b/st.c
_AT_@ -189,6 +189,7 @@ static void tscrollup(int, int);
 static void tscrolldown(int, int);
 static void tsetattr(const int *, int);
 static void tsetchar(Rune, const Glyph *, int, int);
+static void tsetdiacritic(Rune, const Glyph *, int, int);
 static void tsetdirt(int, int);
 static void tsetscroll(int, int);
 static void tswapscreen(void);
_AT_@ -412,7 +413,7 @@ tlinelen(int y)
 	if (term.line[y][i - 1].mode & ATTR_WRAP)
 		return i;
 
-	while (i > 0 && term.line[y][i - 1].u == ' ')
+	while (i > 0 && term.line[y][i - 1].u == ' ' && term.line[y][i - 1].d == '\0')
 		--i;
 
 	return i;
_AT_@ -591,7 +592,7 @@ getsel(void)
 	if (sel.ob.x == -1)
 		return NULL;
 
-	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
+	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ * 2;
 	ptr = str = xmalloc(bufsize);
 
 	/* append every set & selected glyph to the selection */
_AT_@ -609,7 +610,7 @@ getsel(void)
 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 		}
 		last = &term.line[y][MIN(lastx, linelen-1)];
-		while (last >= gp && last->u == ' ')
+		while (last >= gp && last->u == ' ' && last->d == '\0')
 			--last;
 
 		for ( ; gp <= last; ++gp) {
_AT_@ -617,6 +618,8 @@ getsel(void)
 				continue;
 
 			ptr += utf8encode(gp->u, ptr);
+			if (gp->d)
+				ptr += utf8encode(gp->d, ptr);
 		}
 
 		/*
_AT_@ -1220,6 +1223,28 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
 	term.line[y][x].u = u;
 }
 
+void
+tsetdiacritic(Rune d, const Glyph * attr, int x, int y)
+{
+	Rune u;
+	if (x > 0) {
+		--x;
+	} else if (y > 0) {
+		--y;
+		x = tlinelen(y);
+		if (x > 0)
+			--x;
+	}
+	if (x > 0 && (term.line[y][x].mode & ATTR_WDUMMY)) {
+		--x;
+	}
+	term.dirty[y] = 1;
+	u = term.line[y][x].u;
+	term.line[y][x] = *attr;
+	term.line[y][x].u = u; /* *attr stomps on u */
+	term.line[y][x].d = d;
+}
+
 void
 tclearregion(int x1, int y1, int x2, int y2)
 {
_AT_@ -1246,6 +1271,7 @@ tclearregion(int x1, int y1, int x2, int y2)
 			gp->bg = term.c.attr.bg;
 			gp->mode = 0;
 			gp->u = ' ';
+			gp->d = '\0';
 		}
 	}
 }
_AT_@ -2088,9 +2114,12 @@ tdumpline(int n)
 
 	bp = &term.line[n][0];
 	end = &bp[MIN(tlinelen(n), term.col) - 1];
-	if (bp != end || bp->u != ' ') {
-		for ( ; bp <= end; ++bp)
+	if (bp != end || bp->u != ' ' || bp->d != '\0') {
+		for ( ; bp <= end; ++bp) {
 			tprinter(buf, utf8encode(bp->u, buf));
+			if (bp->d)
+				tprinter(buf, utf8encode(bp->d, buf));
+		}
 	}
 	tprinter("\n", 1);
 }
_AT_@ -2493,8 +2522,12 @@ check_control_code:
 		gp = &term.line[term.c.y][term.c.x];
 	}
 
-	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
-	term.lastc = u;
+	if (width > 0) {
+		tsetchar(u, &term.c.attr, term.c.x, term.c.y);
+		term.lastc = u;
+	} else {
+		tsetdiacritic(u, &term.c.attr, term.c.x, term.c.y);
+	}
 
 	if (width == 2) {
 		gp->mode |= ATTR_WIDE;
diff --git a/st.h b/st.h
index fd3b0d8..cddccde 100644
--- a/st.h
+++ b/st.h
_AT_@ -62,6 +62,7 @@ typedef uint_least32_t Rune;
 #define Glyph Glyph_
 typedef struct {
 	Rune u;           /* character code */
+	Rune d;           /* diacritic */
 	ushort mode;      /* attribute flags */
 	uint32_t fg;      /* foreground  */
 	uint32_t bg;      /* background  */
diff --git a/x.c b/x.c
index d73152b..c23ff8e 100644
--- a/x.c
+++ b/x.c
_AT_@ -125,6 +125,7 @@ typedef struct {
 	int descent;
 	int badslant;
 	int badweight;
+	int badcombining;
 	short lbearing;
 	short rbearing;
 	XftFont *match;
_AT_@ -142,7 +143,7 @@ typedef struct {
 
 static inline ushort sixd_to_16bit(int);
 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
 static void xdrawglyph(Glyph, int, int);
 static void xclear(int, int, int, int);
 static int xgeommasktogravity(int);
_AT_@ -757,7 +758,7 @@ xresize(int col, int row)
 	xclear(0, 0, win.w, win.h);
 
 	/* resize to new width */
-	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+	xw.specbuf = xrealloc(xw.specbuf, col * 2 * sizeof(GlyphFontSpec));
 }
 
 ushort
_AT_@ -977,6 +978,16 @@ xloadfont(Font *f, FcPattern *pattern)
 	f->height = f->ascent + f->descent;
 	f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
 
+	/*
+	 * Test xoff to see if the font is trying to
+	 * help us position diacritics along x.
+	 */
+	XftTextExtentsUtf8(xw.dpy, f->match,
+		(const FcChar8 *) "a\xcc\x81",
+		strlen("a\xcc\x81"), &extents);
+
+	f->badcombining = (extents.xOff <= f->width) ? 1 : 0;
+
 	return 0;
 }
 
_AT_@ -1188,7 +1199,7 @@ xinit(int cols, int rows)
 	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
 
 	/* font spec buffer */
-	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+	xw.specbuf = xmalloc(cols * 2 * sizeof(GlyphFontSpec));
 
 	/* Xft rendering context */
 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
_AT_@ -1256,11 +1267,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 	FcPattern *fcpattern, *fontpattern;
 	FcFontSet *fcsets[] = { NULL };
 	FcCharSet *fccharset;
-	int i, f, numspecs = 0;
+	int i, f, numspecs = 0, ondiacritic = 0;
 
 	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
 		/* Fetch rune and mode for current glyph. */
-		rune = glyphs[i].u;
+		rune = ondiacritic ? glyphs[i].d : glyphs[i].u;
 		mode = glyphs[i].mode;
 
 		/* Skip dummy wide-character spacing. */
_AT_@ -1291,10 +1302,19 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 		if (glyphidx) {
 			specs[numspecs].font = font->match;
 			specs[numspecs].glyph = glyphidx;
-			specs[numspecs].x = (short)xp;
+			specs[numspecs].x = (short)(xp + (
+				(ondiacritic && font->badcombining) ?
+					runewidth :
+					0.0));
 			specs[numspecs].y = (short)yp;
-			xp += runewidth;
 			numspecs++;
+			if (!ondiacritic && glyphs[i].d) {
+				ondiacritic = 1;
+				--i;
+			} else {
+				ondiacritic = 0;
+				xp += runewidth;
+			}
 			continue;
 		}
 
_AT_@ -1365,18 +1385,32 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 
 		specs[numspecs].font = frc[f].font;
 		specs[numspecs].glyph = glyphidx;
-		specs[numspecs].x = (short)xp;
+		/*
+		 * TODO: fallback font didn't come from xloadfont,
+		 * don't know if it's badcombining.
+		 */
+		specs[numspecs].x = (short)(xp + (
+			(ondiacritic && font->badcombining) ?
+				runewidth :
+				0.0));
 		specs[numspecs].y = (short)yp;
-		xp += runewidth;
 		numspecs++;
+		if (!ondiacritic && glyphs[i].d) {
+			ondiacritic = 1;
+			--i;
+		} else {
+			ondiacritic = 0;
+			xp += runewidth;
+		}
 	}
 
 	return numspecs;
 }
 
 void
-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int numspecs, int numdiacritics, int x, int y)
 {
+	int len = numspecs - numdiacritics;
 	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
 	int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
 	    width = charlen * win.cw;
_AT_@ -1492,7 +1526,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
 
 	/* Render the glyphs. */
-	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+	XftDrawGlyphFontSpec(xw.draw, fg, specs, numspecs);
 
 	/* Render underline and strikethrough. */
 	if (base.mode & ATTR_UNDERLINE) {
_AT_@ -1513,10 +1547,10 @@ void
 xdrawglyph(Glyph g, int x, int y)
 {
 	int numspecs;
-	XftGlyphFontSpec spec;
+	XftGlyphFontSpec specs[2];
 
-	numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
-	xdrawglyphfontspecs(&spec, g, numspecs, x, y);
+	numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
+	xdrawglyphfontspecs(specs, g, numspecs, g.d ? 1 : 0, x, y);
 }
 
 void
_AT_@ -1657,12 +1691,12 @@ xstartdraw(void)
 void
 xdrawline(Line line, int x1, int y1, int x2)
 {
-	int i, x, ox, numspecs;
+	int i, x, ox, numspecs, numdiacritics;
 	Glyph base, new;
 	XftGlyphFontSpec *specs = xw.specbuf;
 
 	numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
-	i = ox = 0;
+	i = ox = numdiacritics = 0;
 	for (x = x1; x < x2 && i < numspecs; x++) {
 		new = line[x];
 		if (new.mode == ATTR_WDUMMY)
_AT_@ -1670,19 +1704,24 @@ xdrawline(Line line, int x1, int y1, int x2)
 		if (selected(x, y1))
 			new.mode ^= ATTR_REVERSE;
 		if (i > 0 && ATTRCMP(base, new)) {
-			xdrawglyphfontspecs(specs, base, i, ox, y1);
+			xdrawglyphfontspecs(specs, base, i, numdiacritics, ox, y1);
 			specs += i;
 			numspecs -= i;
 			i = 0;
+			numdiacritics = 0;
 		}
 		if (i == 0) {
 			ox = x;
 			base = new;
 		}
 		i++;
+		if (new.d) {
+			i++;
+			numdiacritics++;
+		}
 	}
 	if (i > 0)
-		xdrawglyphfontspecs(specs, base, i, ox, y1);
+		xdrawglyphfontspecs(specs, base, i, numdiacritics, ox, y1);
 }
 
 void
-- 
2.49.0
Received on Mon May 05 2025 - 22:00:53 CEST

This archive was generated by hypermail 2.3.0 : Mon May 05 2025 - 22:12:42 CEST