[dev] [PATCH 1/1] Add support for user-defined fallback fonts

From: Eric Pruitt <eric.pruitt_AT_gmail.com>
Date: Fri, 2 Aug 2024 23:23:43 -0700

This adds support for specifying fonts as an array. This allows the user
to explicitly configure fonts that should be used as fallback fonts when
a glyph is not found in the previous font. Although it is possible to
define fallback fonts with fontconfig's fonts.conf, fonts.conf is a
complicated mess of XML whose configuration can also be further
complicated by interaction distro-specific and font-specific files, and
this patch brings st in line with dwm and dmenu which have supported
specifying fonts as an array for a number of years.
---
 config.def.h |  10 ++++-
 x.c          | 120 +++++++++++++++++++++++++++++++++------------------
 2 files changed, 85 insertions(+), 45 deletions(-)
diff --git a/config.def.h b/config.def.h
index 2cd740a..1da0d69 100644
--- a/config.def.h
+++ b/config.def.h
_AT_@ -3,9 +3,15 @@
 /*
  * appearance
  *
- * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ * fonts: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
  */
-static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
+static char *fonts[] = {
+    "Liberation Mono:pixelsize=12:antialias=true:autohint=true",
+
+    /* CJK Fonts */
+    "VL Gothic:pixelsize=12:antialias=true:autohint=true",
+    "WenQuanYi Micro Hei:pixelsize=12:antialias=true:autohint=true",
+};
 static int borderpx = 2;
 
 /*
diff --git a/x.c b/x.c
index bd23686..b8e6187 100644
--- a/x.c
+++ b/x.c
_AT_@ -132,11 +132,18 @@ typedef struct {
 	FcPattern *pattern;
 } Font;
 
+typedef struct {
+	Font font;
+	Font bfont;
+	Font ifont;
+	Font ibfont;
+} FontFamily;
+
 /* Drawing Context */
 typedef struct {
 	Color *col;
 	size_t collen;
-	Font font, bfont, ifont, ibfont;
+	FontFamily fontfamily;
 	GC gc;
 } DC;
 
_AT_@ -221,6 +228,10 @@ static XWindow xw;
 static XSelection xsel;
 static TermWindow win;
 
+static FontFamily EmptyFontFamily;
+static FontFamily fontfamilies[16];
+static int fontfamiliescount = 0;
+
 /* Font Ring Cache */
 enum {
 	FRC_NORMAL,
_AT_@ -985,6 +996,11 @@ xloadfonts(const char *fontstr, double fontsize)
 {
 	FcPattern *pattern;
 	double fontval;
+	FontFamily fontfamily = EmptyFontFamily;
+
+	if (fontfamiliescount >= LEN(fontfamilies)) {
+		die("Font family array is full.\n");
+	}
 
 	if (fontstr[0] == '-')
 		pattern = XftXlfdParse(fontstr, False, False);
_AT_@ -1017,37 +1033,43 @@ xloadfonts(const char *fontstr, double fontsize)
 		defaultfontsize = usedfontsize;
 	}
 
-	if (xloadfont(&dc.font, pattern))
+	if (xloadfont(&fontfamily.font, pattern))
 		die("can't open font %s\n", fontstr);
 
 	if (usedfontsize < 0) {
-		FcPatternGetDouble(dc.font.match->pattern,
+		FcPatternGetDouble(fontfamily.font.match->pattern,
 		                   FC_PIXEL_SIZE, 0, &fontval);
 		usedfontsize = fontval;
 		if (fontsize == 0)
 			defaultfontsize = fontval;
 	}
 
-	/* Setting character width and height. */
-	win.cw = ceilf(dc.font.width * cwscale);
-	win.ch = ceilf(dc.font.height * chscale);
-
 	FcPatternDel(pattern, FC_SLANT);
 	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
-	if (xloadfont(&dc.ifont, pattern))
+	if (xloadfont(&fontfamily.ifont, pattern))
 		die("can't open font %s\n", fontstr);
 
 	FcPatternDel(pattern, FC_WEIGHT);
 	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
-	if (xloadfont(&dc.ibfont, pattern))
+	if (xloadfont(&fontfamily.ibfont, pattern))
 		die("can't open font %s\n", fontstr);
 
 	FcPatternDel(pattern, FC_SLANT);
 	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
-	if (xloadfont(&dc.bfont, pattern))
+	if (xloadfont(&fontfamily.bfont, pattern))
 		die("can't open font %s\n", fontstr);
 
 	FcPatternDestroy(pattern);
+
+	/* Setting character width and height. */
+	if (!fontfamiliescount) {
+		win.cw = ceilf(fontfamily.font.width * cwscale);
+		win.ch = ceilf(fontfamily.font.height * chscale);
+		dc.fontfamily = fontfamily;
+		usedfont = (char *) fontstr;
+	}
+
+	fontfamilies[fontfamiliescount++] = fontfamily;
 }
 
 void
_AT_@ -1066,10 +1088,13 @@ xunloadfonts(void)
 	while (frclen > 0)
 		XftFontClose(xw.dpy, frc[--frclen].font);
 
-	xunloadfont(&dc.font);
-	xunloadfont(&dc.bfont);
-	xunloadfont(&dc.ifont);
-	xunloadfont(&dc.ibfont);
+	while (fontfamiliescount > 0) {
+		fontfamiliescount--;
+		xunloadfont(&fontfamilies[fontfamiliescount].font);
+		xunloadfont(&fontfamilies[fontfamiliescount].bfont);
+		xunloadfont(&fontfamilies[fontfamiliescount].ifont);
+		xunloadfont(&fontfamilies[fontfamiliescount].ibfont);
+	}
 }
 
 int
_AT_@ -1134,6 +1159,7 @@ xinit(int cols, int rows)
 	Window parent;
 	pid_t thispid = getpid();
 	XColor xmousefg, xmousebg;
+	int i;
 
 	if (!(xw.dpy = XOpenDisplay(NULL)))
 		die("can't open display\n");
_AT_@ -1144,8 +1170,13 @@ xinit(int cols, int rows)
 	if (!FcInit())
 		die("could not init fontconfig.\n");
 
-	usedfont = (opt_font == NULL)? font : opt_font;
-	xloadfonts(usedfont, 0);
+	if (opt_font) {
+		xloadfonts(opt_font, 0);
+	}
+
+	for (i = 0; i < LEN(fonts); i++) {
+		xloadfonts(fonts[i], 0);
+	}
 
 	/* colors */
 	xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
_AT_@ -1242,9 +1273,9 @@ xinit(int cols, int rows)
 int
 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
 {
-	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
-	ushort mode, prevmode = USHRT_MAX;
-	Font *font = &dc.font;
+	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp;
+	ushort mode;
+	Font *font, *reference = NULL;
 	int frcflags = FRC_NORMAL;
 	float runewidth = win.cw;
 	Rune rune;
_AT_@ -1255,7 +1286,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 	FcCharSet *fccharset;
 	int i, f, numspecs = 0;
 
-	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+	for (i = 0, xp = winx; i < len; ++i) {
 		/* Fetch rune and mode for current glyph. */
 		rune = glyphs[i].u;
 		mode = glyphs[i].mode;
_AT_@ -1264,32 +1295,33 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 		if (mode == ATTR_WDUMMY)
 			continue;
 
-		/* Determine font for glyph if different from previous glyph. */
-		if (prevmode != mode) {
-			prevmode = mode;
-			font = &dc.font;
+		/* Lookup character index within user-defined fonts. */
+		for (glyphidx = 0, f = 0; f < fontfamiliescount && !glyphidx; f++) {
+			font = &fontfamilies[f].font;
 			frcflags = FRC_NORMAL;
 			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
 			if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
-				font = &dc.ibfont;
+				font = &fontfamilies[f].ibfont;
 				frcflags = FRC_ITALICBOLD;
 			} else if (mode & ATTR_ITALIC) {
-				font = &dc.ifont;
+				font = &fontfamilies[f].ifont;
 				frcflags = FRC_ITALIC;
 			} else if (mode & ATTR_BOLD) {
-				font = &dc.bfont;
+				font = &fontfamilies[f].bfont;
 				frcflags = FRC_BOLD;
 			}
-			yp = winy + font->ascent;
+
+			if (!f) {
+				reference = font;
+			}
+			glyphidx = XftCharIndex(xw.dpy, font->match, rune);
 		}
 
-		/* Lookup character index with default font. */
-		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
 		if (glyphidx) {
 			specs[numspecs].font = font->match;
 			specs[numspecs].glyph = glyphidx;
 			specs[numspecs].x = (short)xp;
-			specs[numspecs].y = (short)yp;
+			specs[numspecs].y = (short) (winy + font->ascent);
 			xp += runewidth;
 			numspecs++;
 			continue;
_AT_@ -1308,12 +1340,14 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 			}
 		}
 
-		/* Nothing was found. Use fontconfig to find matching font. */
+		/* Nothing was found. Use fontconfig to find matching font using the
+		 * default font as the reference font.
+		 */
 		if (f >= frclen) {
-			if (!font->set)
-				font->set = FcFontSort(0, font->pattern,
-				                       1, 0, &fcres);
-			fcsets[0] = font->set;
+			if (!reference->set)
+				reference->set = FcFontSort(0, reference->pattern,
+				                            1, 0, &fcres);
+			fcsets[0] = reference->set;
 
 			/*
 			 * Nothing was found in the cache. Now use
_AT_@ -1322,7 +1356,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
 			 *
 			 * Xft and fontconfig are design failures.
 			 */
-			fcpattern = FcPatternDuplicate(font->pattern);
+			fcpattern = FcPatternDuplicate(reference->pattern);
 			fccharset = FcCharSetCreate();
 
 			FcCharSetAddChar(fccharset, rune);
_AT_@ -1363,7 +1397,7 @@ 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;
-		specs[numspecs].y = (short)yp;
+		specs[numspecs].y = (short) (winy + frc[f].font->ascent);
 		xp += runewidth;
 		numspecs++;
 	}
_AT_@ -1383,10 +1417,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
 	/* Fallback on color display for attributes not supported by the font */
 	if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
-		if (dc.ibfont.badslant || dc.ibfont.badweight)
+		if (dc.fontfamily.ibfont.badslant || dc.fontfamily.ibfont.badweight)
 			base.fg = defaultattr;
-	} else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
-	    (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
+	} else if ((base.mode & ATTR_ITALIC && dc.fontfamily.ifont.badslant) ||
+	    (base.mode & ATTR_BOLD && dc.fontfamily.bfont.badweight)) {
 		base.fg = defaultattr;
 	}
 
_AT_@ -1493,12 +1527,12 @@ 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,
+		XftDrawRect(xw.draw, fg, winx, winy + dc.fontfamily.font.ascent * chscale + 1,
 				width, 1);
 	}
 
 	if (base.mode & ATTR_STRUCK) {
-		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
+		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.fontfamily.font.ascent * chscale / 3,
 				width, 1);
 	}
 
-- 
2.39.2
Received on Sat Aug 03 2024 - 08:23:43 CEST

This archive was generated by hypermail 2.3.0 : Sat Aug 03 2024 - 10:36:08 CEST