Title: Font Array Support Author: Eric Pruitt (https://www.codevat.com/, https://github.com/ericpruitt/) Modifies st to support user-defined fallback fonts specified in a NULL terminated array defined as `const char *fonts[]`. This change also resolves an issue where fallback fonts were used in place of default fonts in an inconsistent manner which caused identical sets of text to sometimes use different fonts. In the following example, DejaVu Sans Mono is the primary font with two others specified as fallbacks: const char *fonts[] = { "DejaVu Sans Mono", "VL Gothic", "WenQuanYi Micro Hei", NULL }; diff --git x.c x.c index d43a529..9948b65 100644 --- x.c +++ x.c @@ -125,11 +125,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; @@ -145,7 +152,7 @@ static void xresize(int, int); static void xhints(void); static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); -static void xloadfonts(char *, double); +static void xloadfonts(const char *, double); static void xunloadfont(Font *); static void xunloadfonts(void); static void xsetenv(void); @@ -208,6 +215,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, @@ -898,10 +909,15 @@ xloadfont(Font *f, FcPattern *pattern) } void -xloadfonts(char *fontstr, double fontsize) +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); @@ -935,37 +951,43 @@ xloadfonts(char *fontstr, double fontsize) defaultfontsize = usedfontsize; } - if (xloadfont(&dc.font, pattern)) + if (xloadfont(&fontfamily.font, pattern)) die("st: 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("st: 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("st: 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("st: 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 @@ -984,10 +1006,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); + } } void @@ -998,6 +1023,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"); @@ -1008,8 +1034,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; fonts[i]; i++) { + xloadfonts(fonts[i], 0); + } /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); @@ -1117,9 +1148,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; @@ -1130,7 +1161,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; @@ -1139,32 +1170,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; @@ -1183,12 +1215,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 @@ -1197,7 +1231,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); @@ -1241,7 +1275,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++; } @@ -1261,10 +1295,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; } @@ -1371,12 +1405,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 + 1, + XftDrawRect(xw.draw, fg, winx, winy + dc.fontfamily.font.ascent + 1, width, 1); } if (base.mode & ATTR_STRUCK) { - XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.fontfamily.font.ascent / 3, width, 1); }